From da58c8af5555db126ee03e12dd0b7913be47faf0 Mon Sep 17 00:00:00 2001 From: Ignacio Corderi Date: Tue, 25 Jul 2023 17:07:26 +0200 Subject: [PATCH 01/66] ledger: generic kv trackers backend implementation (#5488) --- Makefile | 2 +- .../generickv/accounts_ext_reader.go | 521 ++++++++++++++++ .../generickv/accounts_ext_writer.go | 265 +++++++++ .../trackerdb/generickv/accounts_reader.go | 405 +++++++++++++ .../trackerdb/generickv/accounts_writer.go | 188 ++++++ .../store/trackerdb/generickv/catchpoint.go | 70 +++ .../trackerdb/generickv/init_accounts.go | 60 ++ .../store/trackerdb/generickv/migrations.go | 225 +++++++ ledger/store/trackerdb/generickv/msgp_gen.go | 146 +++++ .../trackerdb/generickv/msgp_gen_test.go | 75 +++ .../generickv/onlineaccounts_reader.go | 165 ++++++ .../generickv/onlineaccounts_writer.go | 64 ++ ledger/store/trackerdb/generickv/reader.go | 52 ++ ledger/store/trackerdb/generickv/schema.go | 171 ++++++ .../trackerdb/generickv/stateproof_reader.go | 104 ++++ .../trackerdb/generickv/stateproof_writer.go | 76 +++ ledger/store/trackerdb/generickv/writer.go | 91 +++ ledger/store/trackerdb/testsuite/README.md | 29 + .../testsuite/accounts_ext_kv_test.go | 300 ++++++++++ .../trackerdb/testsuite/accounts_kv_test.go | 480 +++++++++++++++ .../trackerdb/testsuite/dbsemantics_test.go | 82 +++ .../trackerdb/testsuite/migration_test.go | 140 +++++ .../store/trackerdb/testsuite/mockdb_test.go | 34 ++ .../testsuite/onlineaccounts_kv_test.go | 554 ++++++++++++++++++ .../trackerdb/testsuite/sqlitedb_test.go | 43 ++ .../testsuite/stateproofs_kv_test.go | 130 ++++ .../store/trackerdb/testsuite/utils_test.go | 440 ++++++++++++++ 27 files changed, 4911 insertions(+), 1 deletion(-) create mode 100644 ledger/store/trackerdb/generickv/accounts_ext_reader.go create mode 100644 ledger/store/trackerdb/generickv/accounts_ext_writer.go create mode 100644 ledger/store/trackerdb/generickv/accounts_reader.go create mode 100644 ledger/store/trackerdb/generickv/accounts_writer.go create mode 100644 ledger/store/trackerdb/generickv/catchpoint.go create mode 100644 ledger/store/trackerdb/generickv/init_accounts.go create mode 100644 ledger/store/trackerdb/generickv/migrations.go create mode 100644 ledger/store/trackerdb/generickv/msgp_gen.go create mode 100644 ledger/store/trackerdb/generickv/msgp_gen_test.go create mode 100644 ledger/store/trackerdb/generickv/onlineaccounts_reader.go create mode 100644 ledger/store/trackerdb/generickv/onlineaccounts_writer.go create mode 100644 ledger/store/trackerdb/generickv/reader.go create mode 100644 ledger/store/trackerdb/generickv/schema.go create mode 100644 ledger/store/trackerdb/generickv/stateproof_reader.go create mode 100644 ledger/store/trackerdb/generickv/stateproof_writer.go create mode 100644 ledger/store/trackerdb/generickv/writer.go create mode 100644 ledger/store/trackerdb/testsuite/README.md create mode 100644 ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go create mode 100644 ledger/store/trackerdb/testsuite/accounts_kv_test.go create mode 100644 ledger/store/trackerdb/testsuite/dbsemantics_test.go create mode 100644 ledger/store/trackerdb/testsuite/migration_test.go create mode 100644 ledger/store/trackerdb/testsuite/mockdb_test.go create mode 100644 ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go create mode 100644 ledger/store/trackerdb/testsuite/sqlitedb_test.go create mode 100644 ledger/store/trackerdb/testsuite/stateproofs_kv_test.go create mode 100644 ledger/store/trackerdb/testsuite/utils_test.go diff --git a/Makefile b/Makefile index 0c49744025..9afd53a357 100644 --- a/Makefile +++ b/Makefile @@ -91,7 +91,7 @@ ALGOD_API_PACKAGES := $(sort $(shell GOPATH=$(GOPATH) && GO111MODULE=off && cd d GOMOD_DIRS := ./tools/block-generator ./tools/x-repo-types -MSGP_GENERATE := ./protocol ./protocol/test ./crypto ./crypto/merklearray ./crypto/merklesignature ./crypto/stateproof ./data/basics ./data/transactions ./data/stateproofmsg ./data/committee ./data/bookkeeping ./data/hashable ./agreement ./rpcs ./network ./node ./ledger ./ledger/ledgercore ./ledger/store/trackerdb ./ledger/encoded ./stateproof ./data/account ./daemon/algod/api/spec/v2 +MSGP_GENERATE := ./protocol ./protocol/test ./crypto ./crypto/merklearray ./crypto/merklesignature ./crypto/stateproof ./data/basics ./data/transactions ./data/stateproofmsg ./data/committee ./data/bookkeeping ./data/hashable ./agreement ./rpcs ./network ./node ./ledger ./ledger/ledgercore ./ledger/store/trackerdb ./ledger/store/trackerdb/generickv ./ledger/encoded ./stateproof ./data/account ./daemon/algod/api/spec/v2 default: build diff --git a/ledger/store/trackerdb/generickv/accounts_ext_reader.go b/ledger/store/trackerdb/generickv/accounts_ext_reader.go new file mode 100644 index 0000000000..553d143a45 --- /dev/null +++ b/ledger/store/trackerdb/generickv/accounts_ext_reader.go @@ -0,0 +1,521 @@ +// 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 generickv + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" +) + +func (r *accountsReader) AccountsRound() (rnd basics.Round, err error) { + // SQL at time of impl: + // + // "SELECT rnd FROM acctrounds WHERE id='acctbase'" + + // read round entry + value, closer, err := r.kvr.Get(roundKey()) + if err != nil { + return + } + defer closer.Close() + + // parse the bytes into a u64 + rnd = basics.Round(binary.BigEndian.Uint64(value)) + + return +} + +func (r *accountsReader) AccountsTotals(ctx context.Context, catchpointStaging bool) (totals ledgercore.AccountTotals, err error) { + // read round entry + value, closer, err := r.kvr.Get(totalsKey(catchpointStaging)) + if err != nil { + return + } + defer closer.Close() + + err = protocol.Decode(value, &totals) + if err != nil { + return + } + + return +} + +func (r *accountsReader) AccountsHashRound(ctx context.Context) (hashrnd basics.Round, err error) { + // TODO: catchpoint + return +} + +func (r *accountsReader) LookupAccountAddressFromAddressID(ctx context.Context, ref trackerdb.AccountRef) (address basics.Address, err error) { + // TODO: catchpoint + return +} + +func (r *accountsReader) LookupAccountRowID(addr basics.Address) (ref trackerdb.AccountRef, err error) { + // Note: [perf] this is not a very cheap operation since we have to pull up the entire record + acc, err := r.LookupAccount(addr) + if err != nil { + return + } + + if acc.Ref == nil { + return nil, trackerdb.ErrNotFound + } + + return acc.Ref, nil +} + +func (r *accountsReader) LookupResourceDataByAddrID(accRef trackerdb.AccountRef, aidx basics.CreatableIndex) (data []byte, err error) { + // TODO: this can probably get removed in favor of LookupResources + // the only issue here is that the only caller of this is not doing anything with the ctype + // so we might have to change the signature of LookupResources to skip the ctype, which might be reasonable + if accRef == nil { + return data, trackerdb.ErrNotFound + } + xref := accRef.(accountRef) + + value, closer, err := r.kvr.Get(resourceKey(xref.addr, aidx)) + if err != nil { + return + } + defer closer.Close() + + return value, nil +} + +func (r *accountsReader) TotalResources(ctx context.Context) (total uint64, err error) { + // TODO: catchpoint + return +} + +func (r *accountsReader) TotalAccounts(ctx context.Context) (total uint64, err error) { + // TODO: catchpoint + return +} + +func (r *accountsReader) TotalKVs(ctx context.Context) (total uint64, err error) { + // TODO: catchpoint + return +} + +// TODO: this replicates some functionality from LookupOnlineHistory, implemented for onlineAccountsReader +func (r *accountsReader) LookupOnlineAccountDataByAddress(addr basics.Address) (ref trackerdb.OnlineAccountRef, data []byte, err error) { + low := onlineAccountOnlyPartialKey(addr) + high := onlineAccountOnlyPartialKey(addr) + high[len(high)-1]++ + iter := r.kvr.NewIter(low, high, true) + defer iter.Close() + + if iter.Next() { + // key is -- = + key := iter.Key() + + rndOffset := len(kvPrefixOnlineAccount) + 1 + 32 + 1 + u64Rnd := binary.BigEndian.Uint64(key[rndOffset : rndOffset+8]) + + addrOffset := len(kvPrefixOnlineAccount) + 1 + var addr basics.Address + copy(addr[:], key[addrOffset:addrOffset+32]) + + data, err = iter.Value() + if err != nil { + return + } + + var oa trackerdb.BaseOnlineAccountData + err = protocol.Decode(data, &oa) + if err != nil { + return + } + + ref = onlineAccountRef{ + addr: addr, + round: basics.Round(u64Rnd), + normBalance: oa.NormalizedOnlineBalance(r.proto), + } + } else { + err = trackerdb.ErrNotFound + return + } + + return +} + +// AccountsOnlineTop returns the top n online accounts starting at position offset +// (that is, the top offset'th account through the top offset+n-1'th account). +// +// The accounts are sorted by their normalized balance and address. The normalized +// balance has to do with the reward parts of online account balances. See the +// normalization procedure in AccountData.NormalizedOnlineBalance(). +// +// Note that this does not check if the accounts have a vote key valid for any +// particular round (past, present, or future). +func (r *accountsReader) AccountsOnlineTop(rnd basics.Round, offset uint64, n uint64, proto config.ConsensusParams) (data map[basics.Address]*ledgercore.OnlineAccount, err error) { + // The SQL before the impl + // SELECT + // address, normalizedonlinebalance, data, max(updround) FROM onlineaccounts + // WHERE updround <= ? + // GROUP BY address HAVING normalizedonlinebalance > 0 + // ORDER BY normalizedonlinebalance DESC, address + // DESC LIMIT ? + // OFFSET ? + + // initialize return map + data = make(map[basics.Address]*ledgercore.OnlineAccount) + + // prepare iter over online accounts (by balance) + low := []byte(kvPrefixOnlineAccountBalance) + low = append(low, "-"...) + high := onlineAccountBalanceOnlyPartialKey(rnd) + high[len(high)-1]++ + // reverse order iterator to get high-to-low + iter := r.kvr.NewIter(low, high, true) + defer iter.Close() + + var value []byte + + // first, drop the results from 0 to the offset + for i := uint64(0); i < offset; i++ { + iter.Next() + } + + // add the other results to the map + for i := uint64(0); i < n; i++ { + // if no more results, return early + if !iter.Next() { + return + } + + // key is --- = + key := iter.Key() + + // TODO: make this cleaner + // get the offset where the address starts + offset := len(kvPrefixOnlineAccountBalance) + 1 + 8 + 1 + 8 + 1 + + // extract address + var addr basics.Address + copy(addr[:], key[offset:]) + + // skip if already in map + if _, ok := data[addr]; ok { + continue + } + + value, err = iter.Value() + if err != nil { + return + } + + oa := trackerdb.BaseOnlineAccountData{} + err = protocol.Decode(value, &oa) + if err != nil { + return + } + // load the data as a ledgercore OnlineAccount + data[addr] = &ledgercore.OnlineAccount{ + Address: addr, + MicroAlgos: oa.MicroAlgos, + RewardsBase: oa.RewardsBase, + NormalizedOnlineBalance: oa.NormalizedOnlineBalance(proto), + VoteFirstValid: oa.VoteFirstValid, + VoteLastValid: oa.VoteLastValid, + StateProofID: oa.StateProofID, + } + } + return +} + +func (r *accountsReader) AccountsOnlineRoundParams() (onlineRoundParamsData []ledgercore.OnlineRoundParamsData, endRound basics.Round, err error) { + // The SQL at the time of writing: + // + // SELECT rnd, data FROM onlineroundparamstail ORDER BY rnd ASC + + start := []byte(kvOnlineAccountRoundParams + "-") + end := []byte(kvOnlineAccountRoundParams + ".") + iter := r.kvr.NewIter(start, end, false) + defer iter.Close() + + var value []byte + + for iter.Next() { + // read the key + // schema: - + key := iter.Key() + + // extract the round from the key + rndOffset := len(kvOnlineAccountRoundParams) + 1 + u64Rnd := binary.BigEndian.Uint64(key[rndOffset : rndOffset+8]) + // assign current item round as endRound + endRound = basics.Round(u64Rnd) + + // get value for current item in the iterator + value, err = iter.Value() + if err != nil { + return nil, endRound, err + } + + // decode the param + roundParams := ledgercore.OnlineRoundParamsData{} + err = protocol.Decode(value, &roundParams) + if err != nil { + return nil, endRound, err + } + + // add the params to the return list + onlineRoundParamsData = append(onlineRoundParamsData, roundParams) + } + + return +} + +// OnlineAccountsAll returns all online accounts up to a provided maximum +// the returned list of PersistedOnlineAccountData includes all of the available +// data for each included account in ascending order of account and round +// (example [account-1-round-1, account1-round-2, ..., account2-round-1, ...]) +func (r *accountsReader) OnlineAccountsAll(maxAccounts uint64) ([]trackerdb.PersistedOnlineAccountData, error) { + // The SQL at the time of impl: + // + // SELECT rowid, address, updround, data + // FROM onlineaccounts + // ORDER BY address, updround ASC + // + // Note: the SQL implementation does not seem to load the current db round to the resulting objects + + // read the current db round + var round basics.Round + round, err := r.AccountsRound() + if err != nil { + return nil, err + } + + low := []byte(kvPrefixOnlineAccount + "-") + high := []byte(kvPrefixOnlineAccount + ".") + iter := r.kvr.NewIter(low, high, false) + defer iter.Close() + + result := make([]trackerdb.PersistedOnlineAccountData, 0, maxAccounts) + + var value []byte + var updround uint64 + + // keep track of the most recently seen account so we can tally up the total number seen + lastAddr := basics.Address{} + seen := uint64(0) + + for iter.Next() { + pitem := trackerdb.PersistedOnlineAccountData{Round: round} + + // schema: -- + key := iter.Key() + + addrOffset := len(kvPrefixOnlineAccount) + 1 + var addr basics.Address + copy(addr[:], key[addrOffset:addrOffset+32]) + // extract updround, it's the last section after the "-" + + rndOffset := addrOffset + 32 + 1 + updround = binary.BigEndian.Uint64(key[rndOffset : rndOffset+8]) + + // load addr, round and data into the persisted item + pitem.Addr = addr + pitem.UpdRound = basics.Round(updround) + // get value for current item in the iterator + value, err = iter.Value() + if err != nil { + return nil, err + } + // decode raw value + err = protocol.Decode(value, &pitem.AccountData) + if err != nil { + return nil, err + } + // set ref + normBalance := pitem.AccountData.NormalizedOnlineBalance(r.proto) + pitem.Ref = onlineAccountRef{addr, normBalance, pitem.UpdRound} + // if maxAccounts is supplied, potentially stop reading data if we've collected enough + if maxAccounts > 0 { + // we have encountered a new address + if !bytes.Equal(addr[:], lastAddr[:]) { + copy(lastAddr[:], addr[:]) + seen++ + } + // this newest account seen is beyond the maxAccounts requested, meaning we've seen all the data we need + if seen > maxAccounts { + break + } + } + + // append entry to accum + result = append(result, pitem) + } + + return result, nil +} + +// ExpiredOnlineAccountsForRound implements trackerdb.AccountsReaderExt +func (r *accountsReader) ExpiredOnlineAccountsForRound(rnd basics.Round, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (data map[basics.Address]*ledgercore.OnlineAccountData, err error) { + // The SQL at the time of writing: + // + // SELECT address, data, max(updround) + // FROM onlineaccounts + // WHERE updround <= ? <---- ? = rnd + // GROUP BY address + // HAVING votelastvalid < ? and votelastvalid > 0 <---- ? = voteRnd + // ORDER BY address + + // initialize return map + data = make(map[basics.Address]*ledgercore.OnlineAccountData) + expired := make(map[basics.Address]struct{}) + + // prepare iter over online accounts (by balance) + low := []byte(kvPrefixOnlineAccountBalance) + low = append(low, "-"...) + high := onlineAccountBalanceOnlyPartialKey(rnd) + high[len(high)-1]++ + // reverse order iterator to get high-to-low + iter := r.kvr.NewIter(low, high, true) + defer iter.Close() + + var value []byte + + // add the other results to the map + for iter.Next() { + // key is --- = + key := iter.Key() + + // get the addrOffset where the address starts + addrOffset := len(kvPrefixOnlineAccountBalance) + 1 + 8 + 1 + 8 + 1 + + // extract address + var addr basics.Address + copy(addr[:], key[addrOffset:]) + + // skip if already in map + // we keep only the one with `max(updround)` + // the reverse iter makes us hit the max first + if _, ok := data[addr]; ok { + continue + } + // when the a ccount is expired we do not add it to the data + // but we might have an older version that is not expired show up + // this would be wrong, so we skip those accounts if the latest version is expired + if _, ok := expired[addr]; ok { + continue + } + + value, err = iter.Value() + if err != nil { + return + } + + oa := trackerdb.BaseOnlineAccountData{} + err = protocol.Decode(value, &oa) + if err != nil { + return + } + + // filter by vote expiration + // sql: HAVING votelastvalid < ? and votelastvalid > 0 + // Note: we might have to add an extra index during insert if this doing this in memory becomes a perf issue + if !(oa.VoteLastValid < voteRnd && oa.VoteLastValid > 0) { + expired[addr] = struct{}{} + continue + } + + // load the data as a ledgercore OnlineAccount + oadata := oa.GetOnlineAccountData(proto, rewardsLevel) + data[addr] = &oadata + } + + return +} + +func (r *accountsReader) LoadTxTail(ctx context.Context, dbRound basics.Round) (roundData []*trackerdb.TxTailRound, roundHash []crypto.Digest, baseRound basics.Round, err error) { + // The SQL at the time of writing: + // + // "SELECT rnd, data FROM txtail ORDER BY rnd DESC" + + start := []byte(kvTxTail + "-") + end := []byte(kvTxTail + ".") + iter := r.kvr.NewIter(start, end, true) + defer iter.Close() + + var value []byte + + expectedRound := dbRound + for iter.Next() { + // read the key + key := iter.Key() + + // extract the txTail round from the key + rndOffset := len(kvTxTail) + 1 + u64Rnd := binary.BigEndian.Uint64(key[rndOffset : rndOffset+8]) + round := basics.Round(u64Rnd) + // check that we are on the right round + if round != expectedRound { + return nil, nil, 0, fmt.Errorf("txtail table contain unexpected round %d; round %d was expected", round, expectedRound) + } + + // get value for current item in the iterator + value, err = iter.Value() + if err != nil { + return nil, nil, 0, err + } + + // decode the TxTail + tail := &trackerdb.TxTailRound{} + err = protocol.Decode(value, tail) + if err != nil { + return nil, nil, 0, err + } + + // add the tail + roundData = append(roundData, tail) + // add the hash + roundHash = append(roundHash, crypto.Hash(value)) + + // step the round down (we expect the "previous" round next..) + expectedRound-- + } + + // reverse the array ordering in-place so that it would be incremental order. + for i := 0; i < len(roundData)/2; i++ { + roundData[i], roundData[len(roundData)-i-1] = roundData[len(roundData)-i-1], roundData[i] + roundHash[i], roundHash[len(roundHash)-i-1] = roundHash[len(roundHash)-i-1], roundHash[i] + } + return roundData, roundHash, expectedRound + 1, nil +} + +func (r *accountsReader) LoadAllFullAccounts(ctx context.Context, balancesTable string, resourcesTable string, acctCb func(basics.Address, basics.AccountData)) (count int, err error) { + // TODO: catchpoint CLI + return +} + +func (r *accountsReader) Testing() trackerdb.AccountsReaderTestExt { + // TODO: this can wait + return nil +} diff --git a/ledger/store/trackerdb/generickv/accounts_ext_writer.go b/ledger/store/trackerdb/generickv/accounts_ext_writer.go new file mode 100644 index 0000000000..bdb05b9138 --- /dev/null +++ b/ledger/store/trackerdb/generickv/accounts_ext_writer.go @@ -0,0 +1,265 @@ +// 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 generickv + +import ( + "context" + "encoding/binary" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" +) + +func (w *accountsWriter) AccountsReset(ctx context.Context) error { + // TODO: catchpoint + return nil +} + +func (w *accountsWriter) ResetAccountHashes(ctx context.Context) (err error) { + // TODO: catchpoint + return +} + +func (w *accountsWriter) TxtailNewRound(ctx context.Context, baseRound basics.Round, roundData [][]byte, forgetBeforeRound basics.Round) error { + // The SQL at the time fo writing: + // + // for i, data := range roundData: + // the inserted rnd value is baseRound + i + // + // INSERT INTO txtail(rnd, data) VALUES(?, ?) + // + // then it also cleans up everything before `forgetBeforeRound`: + // + // DELETE FROM txtail WHERE rnd < ? + + // insert the new txTail's + for i, data := range roundData { + rnd := basics.Round(int(baseRound) + i) + err := w.kvw.Set(txTailKey(rnd), data) + if err != nil { + return err + } + } + + // delete old ones + start := []byte(kvTxTail + "-") + end := txTailKey(forgetBeforeRound) + err := w.kvw.DeleteRange(start, end) + if err != nil { + return err + } + + return nil +} + +func (w *accountsWriter) UpdateAccountsRound(rnd basics.Round) (err error) { + // The SQL at the time of writing: + // + // UPDATE acctrounds SET rnd=? WHERE id='acctbase' AND rnd "-".join(kvPrefixOnlineAccount, addr, round) + // - and the `onlineAccountBalanceKey(round, normBalance, addr) -> "-".join(kvPrefixOnlineAccountBalance, round, normBalance, addr) + + // 1. read from the `onlineAccountBalanceKey` range since we can the addr's that will need to be deleted + start := []byte(kvPrefixOnlineAccountBalance + "-") + end := []byte(kvPrefixOnlineAccountBalance + "-") + end = append(end, bigEndianUint64(uint64(forgetBefore))...) + iter := w.kvr.NewIter(start, end, true) + defer iter.Close() + + toDeletePrimaryIndex := make([]struct { + basics.Address + basics.Round + }, 0) + + toDeleteSecondaryIndex := make([][]byte, 0) + + var prevAddr basics.Address + + for iter.Next() { + // read the key + // schema: --- + key := iter.Key() + + // extract the round from the key (offset: 1) + rndOffset := len(kvPrefixOnlineAccountBalance) + 1 + u64Rnd := binary.BigEndian.Uint64(key[rndOffset : rndOffset+8]) + round := basics.Round(u64Rnd) + + // get the offset where the address starts + addrOffset := len(kvPrefixOnlineAccountBalance) + 1 + 8 + 1 + 8 + 1 + var addr basics.Address + copy(addr[:], key[addrOffset:addrOffset+32]) + + if addr != prevAddr { + // new address + // if the first (latest) entry is + // - offline then delete all + // - online then safe to delete all previous except this first (latest) + + // reset the state + prevAddr = addr + + // delete on voting empty + var oad trackerdb.BaseOnlineAccountData + var data []byte + data, err = iter.Value() + if err != nil { + return err + } + err = protocol.Decode(data, &oad) + if err != nil { + return err + } + if oad.IsVotingEmpty() { + // delete this and all subsequent + toDeletePrimaryIndex = append(toDeletePrimaryIndex, struct { + basics.Address + basics.Round + }{addr, round}) + toDeleteSecondaryIndex = append(toDeleteSecondaryIndex, key) + } + + // restart the loop + // if there are some subsequent entries, they will deleted on the next iteration + // if no subsequent entries, the loop will reset the state and the latest entry does not get deleted + continue + } + + // mark the item for deletion + toDeletePrimaryIndex = append(toDeletePrimaryIndex, struct { + basics.Address + basics.Round + }{addr, round}) + toDeleteSecondaryIndex = append(toDeleteSecondaryIndex, key) + } + + // 2. delete the individual addr+round entries + for _, item := range toDeletePrimaryIndex { + // TODO: [perf] we might be able to optimize this with a SingleDelete call + err = w.kvw.Delete(onlineAccountKey(item.Address, item.Round)) + if err != nil { + return + } + } + + // 3. delete the range from `onlineAccountBalanceKey` + for _, key := range toDeleteSecondaryIndex { + // TODO: [perf] we might be able to optimize this with a SingleDelete call + err = w.kvw.Delete(key) + if err != nil { + return + } + } + + return +} + +func (w *accountsWriter) AccountsPutOnlineRoundParams(onlineRoundParamsData []ledgercore.OnlineRoundParamsData, startRound basics.Round) error { + // The SQL at the time of impl: + // + // for i, data := range onlineRoundParamsData { + // the inserted rnd value is startRound + i + // + // INSERT INTO onlineroundparamstail (rnd, data) VALUES (?, ?) + // + + // insert the round params + for i := range onlineRoundParamsData { + rnd := basics.Round(int(startRound) + i) + raw := protocol.Encode(&onlineRoundParamsData[i]) + err := w.kvw.Set(onlineAccountRoundParamsKey(rnd), raw) + if err != nil { + return err + } + } + + return nil +} + +func (w *accountsWriter) AccountsPruneOnlineRoundParams(deleteBeforeRound basics.Round) error { + // The SQL at the time of impl: + // + // DELETE FROM onlineroundparamstail WHERE rnd. + +package generickv + +import ( + "encoding/binary" + "fmt" + "io" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" +) + +// KvRead is a low level KV db interface for reading. +type KvRead interface { + Get(key []byte) ([]byte, io.Closer, error) + NewIter(low, high []byte, reverse bool) KvIter +} + +// KvIter is a low level KV iterator. +type KvIter interface { + Next() bool + Key() []byte + KeySlice() Slice + Value() ([]byte, error) + ValueSlice() (Slice, error) + Valid() bool + Close() +} + +// Slice is a low level slice used during the KV iterator. +type Slice interface { + Data() []byte + Free() + Size() int + Exists() bool +} + +type accountsReader struct { + kvr KvRead + proto config.ConsensusParams +} + +// MakeAccountsReader returns a kv db agnostic AccountsReader. +func MakeAccountsReader(kvr KvRead, proto config.ConsensusParams) *accountsReader { + return &accountsReader{kvr, proto} +} + +func (r *accountsReader) LookupAccount(addr basics.Address) (data trackerdb.PersistedAccountData, err error) { + // SQL impl at time of writing: + // + // SELECT + // accountbase.rowid, + // acctrounds.rnd, + // accountbase.data + // FROM acctrounds + // LEFT JOIN accountbase ON address=? + // WHERE id='acctbase' + + data.Addr = addr + + // read the current db round + data.Round, err = r.AccountsRound() + if err != nil { + return + } + + value, closer, err := r.kvr.Get(accountKey(addr)) + if err == trackerdb.ErrNotFound { + // Note: the SQL implementation returns a data value and no error even when the account does not exist. + return data, nil + } else if err != nil { + return + } + defer closer.Close() + + err = protocol.Decode(value, &data.AccountData) + if err != nil { + return + } + + data.Ref = accountRef{addr} + + return +} + +func (r *accountsReader) LookupResources(addr basics.Address, aidx basics.CreatableIndex, ctype basics.CreatableType) (data trackerdb.PersistedResourcesData, err error) { + data.Aidx = aidx + + // read the current db round + data.Round, err = r.AccountsRound() + if err != nil { + return + } + + value, closer, err := r.kvr.Get(resourceKey(addr, aidx)) + if err == trackerdb.ErrNotFound { + // Note: the SQL implementation returns a data value and no error even when the account does not exist. + data.Data = trackerdb.MakeResourcesData(0) + return data, nil + } else if err != nil { + err = fmt.Errorf("unable to query resource data for address %v aidx %v ctype %v : %w", addr, aidx, ctype, err) + return + } + defer closer.Close() + + err = protocol.Decode(value, &data.Data) + if err != nil { + return + } + + // Note: the ctype is not filtered during the query, but rather asserted to be what the caller expected + if ctype == basics.AssetCreatable && !data.Data.IsAsset() { + err = fmt.Errorf("lookupResources asked for an asset but got %v", data.Data) + } + if ctype == basics.AppCreatable && !data.Data.IsApp() { + err = fmt.Errorf("lookupResources asked for an app but got %v", data.Data) + } + + data.AcctRef = accountRef{addr} + + return +} + +func (r *accountsReader) LookupAllResources(addr basics.Address) (data []trackerdb.PersistedResourcesData, rnd basics.Round, err error) { + low := resourceAddrOnlyPartialKey(addr) + high := resourceAddrOnlyPartialKey(addr) + high[len(high)-1]++ + + iter := r.kvr.NewIter(low, high, false) + defer iter.Close() + + var value []byte + var aidx uint64 + + // read the current db round + rnd, err = r.AccountsRound() + if err != nil { + return + } + + for iter.Next() { + pitem := trackerdb.PersistedResourcesData{AcctRef: accountRef{addr}, Round: rnd} + + // read the key to parse the aidx + // key is -- = + key := iter.Key() + + aidxOffset := len(kvPrefixResource) + 1 + 32 + 1 + aidx = binary.BigEndian.Uint64(key[aidxOffset : aidxOffset+8]) + pitem.Aidx = basics.CreatableIndex(aidx) + + // get value for current item in the iterator + value, err = iter.Value() + if err != nil { + return + } + // decode raw value + err = protocol.Decode(value, &pitem.Data) + if err != nil { + return + } + // append entry to accum + data = append(data, pitem) + } + + return +} + +func (r *accountsReader) LookupKeyValue(key string) (pv trackerdb.PersistedKVData, err error) { + // read the current db round + pv.Round, err = r.AccountsRound() + if err != nil { + return + } + + value, closer, err := r.kvr.Get(appKvKey(key)) + if err == trackerdb.ErrNotFound { + // Note: the SQL implementation returns a data value and no error even when the account does not exist. + return pv, nil + } else if err != nil { + return + } + defer closer.Close() + + pv.Value = value + + return +} + +// TODO: lifted from sql.go, we might want to refactor it +func keyPrefixIntervalPreprocessing(prefix []byte) ([]byte, []byte) { + if prefix == nil { + prefix = []byte{} + } + prefixIncr := make([]byte, len(prefix)) + copy(prefixIncr, prefix) + for i := len(prefix) - 1; i >= 0; i-- { + currentByteIncr := int(prefix[i]) + 1 + if currentByteIncr > 0xFF { + prefixIncr = prefixIncr[:len(prefixIncr)-1] + continue + } + prefixIncr[i] = byte(currentByteIncr) + return prefix, prefixIncr + } + return prefix, nil +} + +func (r *accountsReader) LookupKeysByPrefix(prefix string, maxKeyNum uint64, results map[string]bool, resultCount uint64) (round basics.Round, err error) { + // SQL at time of writing: + // + // SELECT acctrounds.rnd, kvstore.key + // FROM acctrounds LEFT JOIN kvstore ON kvstore.key >= ? AND kvstore.key < ? + // WHERE id='acctbase' + + // read the current db round + round, err = r.AccountsRound() + if err != nil { + return + } + + start, end := keyPrefixIntervalPreprocessing([]byte(prefix)) + + iter := r.kvr.NewIter(start, end, false) + defer iter.Close() + + var value []byte + + for iter.Next() { + // end iteration if we reached max results + if resultCount == maxKeyNum { + return + } + + // read the key + key := string(iter.Key()) + + // get value for current item in the iterator + value, err = iter.Value() + if err != nil { + return + } + + // mark if the key has data on the result map + results[key] = len(value) > 0 + + // inc results in range + resultCount++ + } + + return +} + +func (r *accountsReader) ListCreatables(maxIdx basics.CreatableIndex, maxResults uint64, ctype basics.CreatableType) (results []basics.CreatableLocator, dbRound basics.Round, err error) { + // The old SQL impl: + // + // SELECT + // acctrounds.rnd, + // assetcreators.asset, + // assetcreators.creator + // FROM acctrounds + // LEFT JOIN assetcreators ON assetcreators.asset <= ? AND assetcreators.ctype = ? + // WHERE acctrounds.id='acctbase' + // ORDER BY assetcreators.asset desc + // LIMIT ? + + // read the current db round + dbRound, err = r.AccountsRound() + if err != nil { + return + } + + start := []byte(fmt.Sprintf("%s-", kvPrefixCreatorIndex)) + end := creatableKey(basics.CreatableIndex(uint64(maxIdx) + 1)) + + // assets are returned in descending order of cidx + iter := r.kvr.NewIter(start, end, true) + defer iter.Close() + + var value []byte + var resultCount uint64 + var cidx uint64 + + for iter.Next() { + // end iteration if we reached max results + if resultCount == maxResults { + return + } + + // read the key + // schema: - + key := iter.Key() + + // extract cidx + cidxOffset := len(kvPrefixCreatorIndex) + 1 + cidx = binary.BigEndian.Uint64(key[cidxOffset : cidxOffset+8]) + + // get value for current item in the iterator + value, err = iter.Value() + if err != nil { + return + } + + // decode the raw value + var entry creatableEntry + err = protocol.Decode(value, &entry) + if err != nil { + return + } + + // TODO: the ctype as part of key makes this filterable during the iter + if entry.Ctype != ctype { + continue + } + + // create the "creatable" struct + cl := basics.CreatableLocator{Type: ctype, Index: basics.CreatableIndex(cidx)} + copy(cl.Creator[:], entry.CreatorAddr) + + // add it to the the results + results = append(results, cl) + + // inc results in range + resultCount++ + } + + // read the current db round + dbRound, err = r.AccountsRound() + if err != nil { + return + } + + return +} + +func (r *accountsReader) LookupCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (addr basics.Address, ok bool, dbRound basics.Round, err error) { + // The old SQL impl: + // + // SELECT + // acctrounds.rnd, + // assetcreators.creator + // FROM acctrounds + // LEFT JOIN assetcreators ON asset = ? AND ctype = ? + // WHERE id='acctbase' + + // read the current db round + dbRound, err = r.AccountsRound() + if err != nil { + return + } + + value, closer, err := r.kvr.Get(creatableKey(cidx)) + if err == trackerdb.ErrNotFound { + // the record does not exist + // clean up the error and just return ok=false + err = nil + ok = false + return + } else if err != nil { + return + } + defer closer.Close() + + // decode the raw value + var entry creatableEntry + err = protocol.Decode(value, &entry) + if err != nil { + return + } + + // assert that the ctype is the one expected + if entry.Ctype != ctype { + ok = false + return + } + + // copy the addr to the return + copy(addr[:], entry.CreatorAddr) + + // mark result as ok + ok = true + + return +} + +func (r *accountsReader) Close() { + +} diff --git a/ledger/store/trackerdb/generickv/accounts_writer.go b/ledger/store/trackerdb/generickv/accounts_writer.go new file mode 100644 index 0000000000..b2f808a436 --- /dev/null +++ b/ledger/store/trackerdb/generickv/accounts_writer.go @@ -0,0 +1,188 @@ +// 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 generickv + +import ( + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" +) + +// KvWrite is a low level KV db interface for writing. +type KvWrite interface { + Set(key, value []byte) error + Delete(key []byte) error + DeleteRange(start, end []byte) error +} + +type accountsWriter struct { + kvw KvWrite + kvr KvRead +} + +type accountRef struct { + addr basics.Address +} + +func (ref accountRef) AccountRefMarker() {} + +type resourceRef struct { + addr basics.Address + aidx basics.CreatableIndex +} + +func (ref resourceRef) ResourceRefMarker() {} + +type creatableRef struct { + cidx basics.CreatableIndex +} + +func (ref creatableRef) CreatableRefMarker() {} + +// MakeAccountsWriter returns a kv db agnostic AccountsWriter. +// TODO: we should discuss what is the best approach for this `kvr KvRead`. +// the problem is that `OnlineAccountsDelete` requires reading and writing. +// the cleanest approach is to move that method to a separate interface, and have it only be available on 'transactions'. +// although a snapshot+batch should be able to support it too since its all reads, then writes. +func MakeAccountsWriter(kvw KvWrite, kvr KvRead) *accountsWriter { + return &accountsWriter{kvw, kvr} +} + +func (w *accountsWriter) InsertAccount(addr basics.Address, normBalance uint64, data trackerdb.BaseAccountData) (ref trackerdb.AccountRef, err error) { + // write account entry + raw := protocol.Encode(&data) + err = w.kvw.Set(accountKey(addr), raw) + if err != nil { + return nil, err + } + + return accountRef{addr}, nil +} + +func (w *accountsWriter) DeleteAccount(ref trackerdb.AccountRef) (rowsAffected int64, err error) { + xref := ref.(accountRef) + + // delete account entry + err = w.kvw.Delete(accountKey(xref.addr)) + if err != nil { + return 0, err + } + + return 1, nil +} + +func (w *accountsWriter) UpdateAccount(ref trackerdb.AccountRef, normBalance uint64, data trackerdb.BaseAccountData) (rowsAffected int64, err error) { + xref := ref.(accountRef) + + // overwrite account entry + raw := protocol.Encode(&data) + err = w.kvw.Set(accountKey(xref.addr), raw) + if err != nil { + return 0, err + } + + return 1, nil +} + +func (w *accountsWriter) InsertResource(acctRef trackerdb.AccountRef, aidx basics.CreatableIndex, data trackerdb.ResourcesData) (ref trackerdb.ResourceRef, err error) { + xref := acctRef.(accountRef) + + // write resource entry + raw := protocol.Encode(&data) + err = w.kvw.Set(resourceKey(xref.addr, aidx), raw) + if err != nil { + return nil, err + } + + return resourceRef{xref.addr, aidx}, nil +} + +func (w *accountsWriter) DeleteResource(acctRef trackerdb.AccountRef, aidx basics.CreatableIndex) (rowsAffected int64, err error) { + xref := acctRef.(accountRef) + + // delete resource entry + err = w.kvw.Delete(resourceKey(xref.addr, aidx)) + if err != nil { + return 0, err + } + + return 1, nil +} + +func (w *accountsWriter) UpdateResource(acctRef trackerdb.AccountRef, aidx basics.CreatableIndex, data trackerdb.ResourcesData) (rowsAffected int64, err error) { + xref := acctRef.(accountRef) + + // update resource entry + raw := protocol.Encode(&data) + err = w.kvw.Set(resourceKey(xref.addr, aidx), raw) + if err != nil { + return 0, err + } + + return 1, nil +} + +func (w *accountsWriter) UpsertKvPair(key string, value []byte) error { + // upsert kv entry + err := w.kvw.Set(appKvKey(key), value) + if err != nil { + return err + } + + return nil +} + +func (w *accountsWriter) DeleteKvPair(key string) error { + // delete kv entry + err := w.kvw.Delete(appKvKey(key)) + if err != nil { + return err + } + + return nil +} + +type creatableEntry struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + Ctype basics.CreatableType + CreatorAddr []byte +} + +func (w *accountsWriter) InsertCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType, creator []byte) (ref trackerdb.CreatableRef, err error) { + // insert creatable entry + raw := protocol.Encode(&creatableEntry{Ctype: ctype, CreatorAddr: creator}) + err = w.kvw.Set(creatableKey(cidx), raw) + if err != nil { + return + } + + return creatableRef{cidx}, nil +} + +func (w *accountsWriter) DeleteCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType) (rowsAffected int64, err error) { + // delete creatable entry + err = w.kvw.Delete(creatableKey(cidx)) + if err != nil { + return 0, err + } + + return 1, nil +} + +func (w *accountsWriter) Close() { + +} diff --git a/ledger/store/trackerdb/generickv/catchpoint.go b/ledger/store/trackerdb/generickv/catchpoint.go new file mode 100644 index 0000000000..a4977cda86 --- /dev/null +++ b/ledger/store/trackerdb/generickv/catchpoint.go @@ -0,0 +1,70 @@ +// 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 generickv + +import ( + "context" + + "github.com/algorand/go-algorand/ledger/store/trackerdb" +) + +type catchpoint struct{} + +// MakeCatchpoint returns a trackerdb.Catchpoint for a KV +func MakeCatchpoint() trackerdb.Catchpoint { + return &catchpoint{} +} + +// MakeCatchpointPendingHashesIterator implements trackerdb.Catchpoint +func (*catchpoint) MakeCatchpointPendingHashesIterator(hashCount int) trackerdb.CatchpointPendingHashesIter { + panic("unimplemented") +} + +// MakeCatchpointReader implements trackerdb.Catchpoint +func (*catchpoint) MakeCatchpointReader() (trackerdb.CatchpointReader, error) { + panic("unimplemented") +} + +// MakeCatchpointReaderWriter implements trackerdb.Catchpoint +func (*catchpoint) MakeCatchpointReaderWriter() (trackerdb.CatchpointReaderWriter, error) { + panic("unimplemented") +} + +// MakeCatchpointWriter implements trackerdb.Catchpoint +func (*catchpoint) MakeCatchpointWriter() (trackerdb.CatchpointWriter, error) { + panic("unimplemented") +} + +// MakeEncodedAccoutsBatchIter implements trackerdb.Catchpoint +func (*catchpoint) MakeEncodedAccoutsBatchIter() trackerdb.EncodedAccountsBatchIter { + panic("unimplemented") +} + +// MakeKVsIter implements trackerdb.Catchpoint +func (*catchpoint) MakeKVsIter(ctx context.Context) (trackerdb.KVsIter, error) { + panic("unimplemented") +} + +// MakeMerkleCommitter implements trackerdb.Catchpoint +func (*catchpoint) MakeMerkleCommitter(staging bool) (trackerdb.MerkleCommitter, error) { + panic("unimplemented") +} + +// MakeOrderedAccountsIter implements trackerdb.Catchpoint +func (*catchpoint) MakeOrderedAccountsIter(accountCount int) trackerdb.OrderedAccountsIter { + panic("unimplemented") +} diff --git a/ledger/store/trackerdb/generickv/init_accounts.go b/ledger/store/trackerdb/generickv/init_accounts.go new file mode 100644 index 0000000000..af587af105 --- /dev/null +++ b/ledger/store/trackerdb/generickv/init_accounts.go @@ -0,0 +1,60 @@ +// 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 generickv + +import ( + "context" + "testing" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" + "github.com/stretchr/testify/require" +) + +type dbForInit interface { + trackerdb.Store + KvRead + KvWrite +} + +// AccountsInitTest initializes the database for testing with the given accounts. +func AccountsInitTest(tb testing.TB, db dbForInit, initAccounts map[basics.Address]basics.AccountData, proto protocol.ConsensusVersion) (newDatabase bool) { + params := trackerdb.Params{ + InitAccounts: initAccounts, + InitProto: proto, + } + _, err := RunMigrations(context.Background(), db, params, trackerdb.AccountDBVersion) + require.NoError(tb, err) + return true +} + +// AccountsInitLightTest initializes the database for testing with the given accounts. +// +// This is duplicate due to a specific legacy test in accdeltas_test.go. +// TODO: remove the need for this. +func AccountsInitLightTest(tb testing.TB, db dbForInit, initAccounts map[basics.Address]basics.AccountData, proto config.ConsensusParams) (newDatabase bool, err error) { + params := trackerdb.Params{ + InitAccounts: initAccounts, + // TODO: how do we get the correct version from the proto arg? + InitProto: protocol.ConsensusCurrentVersion, + } + _, err = RunMigrations(context.Background(), db, params, trackerdb.AccountDBVersion) + require.NoError(tb, err) + return true, nil +} diff --git a/ledger/store/trackerdb/generickv/migrations.go b/ledger/store/trackerdb/generickv/migrations.go new file mode 100644 index 0000000000..9c8e942e39 --- /dev/null +++ b/ledger/store/trackerdb/generickv/migrations.go @@ -0,0 +1,225 @@ +// 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 generickv + +import ( + "context" + "encoding/binary" + "fmt" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" +) + +func getSchemaVersion(ctx context.Context, kvr KvRead) (int32, error) { + // read version entry + value, closer, err := kvr.Get(schemaVersionKey()) + if err == trackerdb.ErrNotFound { + // ignore the error, return version 0 + return 0, nil + } else if err != nil { + return 0, err + } + defer closer.Close() + + // parse the bytes into a i32 + version := int32(binary.BigEndian.Uint32(value)) + + return version, nil +} + +func setSchemaVersion(ctx context.Context, kvw KvWrite, version int32) error { + // write version entry + raw := bigEndianUint32(uint32(version)) + err := kvw.Set(schemaVersionKey(), raw) + if err != nil { + return err + } + + return nil +} + +type dbForMigrations interface { + trackerdb.Store + KvRead + KvWrite +} + +// RunMigrations runs the migrations on the store up to the target version. +func RunMigrations(ctx context.Context, db dbForMigrations, params trackerdb.Params, targetVersion int32) (mgr trackerdb.InitParams, err error) { + + dbVersion, err := getSchemaVersion(ctx, db) + if err != nil { + return + } + + mgr.SchemaVersion = dbVersion + mgr.VacuumOnStartup = false + + migrator := &migrator{ + currentVersion: dbVersion, + targetVersion: targetVersion, + params: params, + db: db, + } + + err = migrator.Migrate(ctx) + if err != nil { + return + } + + mgr.SchemaVersion = migrator.currentVersion + + return mgr, nil +} + +type migrator struct { + currentVersion int32 + targetVersion int32 + params trackerdb.Params + db dbForMigrations +} + +func (m *migrator) Migrate(ctx context.Context) error { + // we cannot rollback + if m.currentVersion > m.targetVersion { + return nil + } + // upgrade the db one version at at time + for m.currentVersion < m.targetVersion { + // run next version upgrade + switch m.currentVersion { + case 0: // initial version + err := m.initialVersion(ctx) + if err != nil { + return err + } + default: + // any other version we do nothing + return nil + } + } + return nil +} + +func (m *migrator) setVersion(ctx context.Context, version int32) error { + // update crrent version in the db + err := setSchemaVersion(ctx, m.db, version) + if err != nil { + return err + } + // update current version in the migrator + m.currentVersion = version + return nil +} + +func (m *migrator) initialVersion(ctx context.Context) error { + proto := config.Consensus[m.params.InitProto] + + // TODO: make this a batch scope + err := m.db.TransactionContext(ctx, func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { + aow, err := tx.MakeAccountsOptimizedWriter(true, false, false, false) + if err != nil { + return err + } + + oaow, err := tx.MakeOnlineAccountsOptimizedWriter(true) + if err != nil { + return err + } + + aw, err := tx.MakeAccountsWriter() + if err != nil { + return err + } + + updRound := basics.Round(0) + + // mark the db as round 0 + err = aw.UpdateAccountsRound(updRound) + if err != nil { + return err + } + + var ot basics.OverflowTracker + var totals ledgercore.AccountTotals + + // insert initial accounts + for addr, account := range m.params.InitAccounts { + // build a trackerdb.BaseAccountData to pass to the DB + var bad trackerdb.BaseAccountData + bad.SetAccountData(&account) + // insert the account + _, err = aow.InsertAccount(addr, account.NormalizedOnlineBalance(proto), bad) + if err != nil { + return err + } + + // build a ledgercore.AccountData to track the totals + ad := ledgercore.ToAccountData(account) + // track the totals + totals.AddAccount(proto, ad, &ot) + + // insert online account (if online) + if bad.Status == basics.Online { + var baseOnlineAD trackerdb.BaseOnlineAccountData + baseOnlineAD.BaseVotingData = bad.BaseVotingData + baseOnlineAD.MicroAlgos = bad.MicroAlgos + baseOnlineAD.RewardsBase = bad.RewardsBase + + _, err = oaow.InsertOnlineAccount(addr, account.NormalizedOnlineBalance(proto), baseOnlineAD, uint64(updRound), uint64(baseOnlineAD.VoteLastValid)) + if err != nil { + return err + } + } + } + + // make sure we didn't overflow + if ot.Overflowed { + return fmt.Errorf("overflow computing totals") + } + + // insert the totals + err = aw.AccountsPutTotals(totals, false) + if err != nil { + return err + } + + // insert online params + params := []ledgercore.OnlineRoundParamsData{ + { + OnlineSupply: totals.Online.Money.Raw, + RewardsLevel: totals.RewardsLevel, + CurrentProtocol: m.params.InitProto, + }, + } + err = aw.AccountsPutOnlineRoundParams(params, basics.Round(0)) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + // KV store starts at version 10 + return m.setVersion(ctx, 10) +} diff --git a/ledger/store/trackerdb/generickv/msgp_gen.go b/ledger/store/trackerdb/generickv/msgp_gen.go new file mode 100644 index 0000000000..fd825ca99d --- /dev/null +++ b/ledger/store/trackerdb/generickv/msgp_gen.go @@ -0,0 +1,146 @@ +package generickv + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +import ( + "github.com/algorand/msgp/msgp" +) + +// The following msgp objects are implemented in this file: +// creatableEntry +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// + +// MarshalMsg implements msgp.Marshaler +func (z *creatableEntry) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if len((*z).CreatorAddr) == 0 { + zb0001Len-- + zb0001Mask |= 0x1 + } + if (*z).Ctype.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x2 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x1) == 0 { // if not empty + // string "CreatorAddr" + o = append(o, 0xab, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72) + o = msgp.AppendBytes(o, (*z).CreatorAddr) + } + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "Ctype" + o = append(o, 0xa5, 0x43, 0x74, 0x79, 0x70, 0x65) + o = (*z).Ctype.MarshalMsg(o) + } + } + return +} + +func (_ *creatableEntry) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*creatableEntry) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *creatableEntry) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Ctype.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Ctype") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).CreatorAddr, bts, err = msgp.ReadBytesBytes(bts, (*z).CreatorAddr) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "CreatorAddr") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = creatableEntry{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "Ctype": + bts, err = (*z).Ctype.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Ctype") + return + } + case "CreatorAddr": + (*z).CreatorAddr, bts, err = msgp.ReadBytesBytes(bts, (*z).CreatorAddr) + if err != nil { + err = msgp.WrapError(err, "CreatorAddr") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *creatableEntry) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*creatableEntry) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *creatableEntry) Msgsize() (s int) { + s = 1 + 6 + (*z).Ctype.Msgsize() + 12 + msgp.BytesPrefixSize + len((*z).CreatorAddr) + return +} + +// MsgIsZero returns whether this is a zero value +func (z *creatableEntry) MsgIsZero() bool { + return ((*z).Ctype.MsgIsZero()) && (len((*z).CreatorAddr) == 0) +} diff --git a/ledger/store/trackerdb/generickv/msgp_gen_test.go b/ledger/store/trackerdb/generickv/msgp_gen_test.go new file mode 100644 index 0000000000..88d79eec21 --- /dev/null +++ b/ledger/store/trackerdb/generickv/msgp_gen_test.go @@ -0,0 +1,75 @@ +//go:build !skip_msgp_testing +// +build !skip_msgp_testing + +package generickv + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +import ( + "testing" + + "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestMarshalUnmarshalcreatableEntry(t *testing.T) { + partitiontest.PartitionTest(t) + v := creatableEntry{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingcreatableEntry(t *testing.T) { + protocol.RunEncodingTest(t, &creatableEntry{}) +} + +func BenchmarkMarshalMsgcreatableEntry(b *testing.B) { + v := creatableEntry{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgcreatableEntry(b *testing.B) { + v := creatableEntry{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalcreatableEntry(b *testing.B) { + v := creatableEntry{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/ledger/store/trackerdb/generickv/onlineaccounts_reader.go b/ledger/store/trackerdb/generickv/onlineaccounts_reader.go new file mode 100644 index 0000000000..029b0317c2 --- /dev/null +++ b/ledger/store/trackerdb/generickv/onlineaccounts_reader.go @@ -0,0 +1,165 @@ +// 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 generickv + +import ( + "encoding/binary" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" +) + +// LookupOnline pulls the Online Account data for a given account+round +func (r *accountsReader) LookupOnline(addr basics.Address, rnd basics.Round) (data trackerdb.PersistedOnlineAccountData, err error) { + // SQL at the time of writing this: + // + // SELECT + // onlineaccounts.rowid, onlineaccounts.updround, + // acctrounds.rnd, + // onlineaccounts.data + // FROM acctrounds + // LEFT JOIN onlineaccounts ON address=? AND updround <= ? + // WHERE id='acctbase' + // ORDER BY updround DESC LIMIT 1 + + // read the current db round + data.Round, err = r.AccountsRound() + if err != nil { + return + } + + // read latest account up to `rnd`` + low := onlineAccountOnlyPartialKey(addr) + high := onlineAccountKey(addr, rnd) + // inc the last byte to make it inclusive + high[len(high)-1]++ + iter := r.kvr.NewIter(low, high, true) + defer iter.Close() + + var value []byte + var updRound uint64 + + if iter.Next() { + // schema: -- + key := iter.Key() + + // extract updround, its the last section after the "-" + rndOffset := len(kvPrefixOnlineAccount) + 1 + 32 + 1 + updRound = binary.BigEndian.Uint64(key[rndOffset : rndOffset+8]) + if err != nil { + return + } + data.Addr = addr + data.UpdRound = basics.Round(updRound) + + // get value for current item in the iterator + value, err = iter.Value() + if err != nil { + return + } + + // parse the value + err = protocol.Decode(value, &data.AccountData) + if err != nil { + return + } + + normBalance := data.AccountData.NormalizedOnlineBalance(r.proto) + data.Ref = onlineAccountRef{addr, normBalance, rnd} + + // we have the record, we can leave + return + } + + // nothing was found + // Note: the SQL implementation returns a data value and no error even when the account does not exist. + return data, nil +} + +// LookupOnlineTotalsHistory pulls the total Online Algos on a given round +func (r *accountsReader) LookupOnlineTotalsHistory(round basics.Round) (basics.MicroAlgos, error) { + // SQL at the time of writing this: + // + // SELECT data FROM onlineroundparamstail WHERE rnd=? + + value, closer, err := r.kvr.Get(onlineAccountRoundParamsKey(round)) + if err != nil { + return basics.MicroAlgos{}, err + } + defer closer.Close() + data := ledgercore.OnlineRoundParamsData{} + err = protocol.Decode(value, &data) + if err != nil { + return basics.MicroAlgos{}, err + } + return basics.MicroAlgos{Raw: data.OnlineSupply}, nil +} + +func (r *accountsReader) LookupOnlineHistory(addr basics.Address) (result []trackerdb.PersistedOnlineAccountData, rnd basics.Round, err error) { + low := onlineAccountOnlyPartialKey(addr) + high := onlineAccountOnlyPartialKey(addr) + high[len(high)-1]++ + iter := r.kvr.NewIter(low, high, false) + defer iter.Close() + + var value []byte + var updround uint64 + + // read the current db round + rnd, err = r.AccountsRound() + if err != nil { + return + } + + for iter.Next() { + pitem := trackerdb.PersistedOnlineAccountData{} + + // schema: -- + key := iter.Key() + // extract updround, its the last section after the "-" + rndOffset := len(kvPrefixOnlineAccount) + 1 + 32 + 1 + updround = binary.BigEndian.Uint64(key[rndOffset : rndOffset+8]) + if err != nil { + return + } + pitem.Addr = addr + pitem.UpdRound = basics.Round(updround) + // Note: for compatibility with the SQL impl, this is not included on each item + // pitem.Round = rnd + + // get value for current item in the iterator + value, err = iter.Value() + if err != nil { + return + } + // decode raw value + err = protocol.Decode(value, &pitem.AccountData) + if err != nil { + return + } + + // set the ref + pitem.Ref = onlineAccountRef{addr, pitem.AccountData.NormalizedOnlineBalance(r.proto), pitem.UpdRound} + + // append entry to accum + result = append(result, pitem) + } + + return +} diff --git a/ledger/store/trackerdb/generickv/onlineaccounts_writer.go b/ledger/store/trackerdb/generickv/onlineaccounts_writer.go new file mode 100644 index 0000000000..cca847c608 --- /dev/null +++ b/ledger/store/trackerdb/generickv/onlineaccounts_writer.go @@ -0,0 +1,64 @@ +// 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 generickv + +import ( + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" +) + +type onlineAccountsWriter struct { + kvw KvWrite +} + +type onlineAccountRef struct { + addr basics.Address + normBalance uint64 + round basics.Round +} + +func (ref onlineAccountRef) OnlineAccountRefMarker() {} + +// MakeOnlineAccountsWriter constructs an kv agnostic OnlineAccountsWriter +func MakeOnlineAccountsWriter(kvw KvWrite) trackerdb.OnlineAccountsWriter { + return &onlineAccountsWriter{kvw} +} + +func (w *onlineAccountsWriter) InsertOnlineAccount(addr basics.Address, normBalance uint64, data trackerdb.BaseOnlineAccountData, updRound uint64, voteLastValid uint64) (ref trackerdb.OnlineAccountRef, err error) { + raw := protocol.Encode(&data) + rnd := basics.Round(updRound) + + // write to the online account key + err = w.kvw.Set(onlineAccountKey(addr, rnd), raw) + if err != nil { + return nil, err + } + + // write to the secondary account balance key + // TODO: this is not the most efficient use of space, but its a tradeoff with a second lookup per object. + // this impacts `AccountsOnlineTop`, and some experiments will be needed to determine if we do extra lookups or duplicate the values. + err = w.kvw.Set(onlineAccountBalanceKey(updRound, normBalance, addr), raw) + if err != nil { + return nil, err + } + + return onlineAccountRef{addr, normBalance, rnd}, nil +} + +func (w *onlineAccountsWriter) Close() { +} diff --git a/ledger/store/trackerdb/generickv/reader.go b/ledger/store/trackerdb/generickv/reader.go new file mode 100644 index 0000000000..88761c6013 --- /dev/null +++ b/ledger/store/trackerdb/generickv/reader.go @@ -0,0 +1,52 @@ +// 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 generickv + +import ( + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/ledger/store/trackerdb" +) + +type reader struct { + proto config.ConsensusParams + KvRead +} + +// MakeReader returns a trackerdb.Reader for a KV +func MakeReader(kvr KvRead, proto config.ConsensusParams) trackerdb.Reader { + return &reader{proto, kvr} +} + +// MakeAccountsOptimizedReader implements trackerdb.Reader +func (r *reader) MakeAccountsOptimizedReader() (trackerdb.AccountsReader, error) { + return MakeAccountsReader(r, r.proto), nil +} + +// MakeAccountsReader implements trackerdb.Reader +func (r *reader) MakeAccountsReader() (trackerdb.AccountsReaderExt, error) { + return MakeAccountsReader(r, r.proto), nil +} + +// MakeOnlineAccountsOptimizedReader implements trackerdb.Reader +func (r *reader) MakeOnlineAccountsOptimizedReader() (trackerdb.OnlineAccountsReader, error) { + return MakeAccountsReader(r, r.proto), nil +} + +// MakeSpVerificationCtxReader implements trackerdb.Reader +func (r *reader) MakeSpVerificationCtxReader() trackerdb.SpVerificationCtxReader { + return MakeStateproofReader(r) +} diff --git a/ledger/store/trackerdb/generickv/schema.go b/ledger/store/trackerdb/generickv/schema.go new file mode 100644 index 0000000000..8afb41e9e2 --- /dev/null +++ b/ledger/store/trackerdb/generickv/schema.go @@ -0,0 +1,171 @@ +// 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 generickv + +import ( + "encoding/binary" + + "github.com/algorand/go-algorand/data/basics" +) + +const ( + kvPrefixAccount = "account" + kvPrefixResource = "resource" + kvPrefixAppKv = "appkv" + kvPrefixCreatorIndex = "creator" + kvPrefixOnlineAccount = "online_account_base" + kvPrefixOnlineAccountBalance = "online_account_balance" + kvRoundKey = "global_round" + kvSchemaVersionKey = "global_schema_version" + kvTotalsKey = "global_total" + kvTxTail = "txtail" + kvOnlineAccountRoundParams = "online_account_round_params" + kvPrefixStateproof = "stateproofs" +) + +// return the big-endian binary encoding of a uint64 +func bigEndianUint64(v uint64) []byte { + ret := make([]byte, 8) + binary.BigEndian.PutUint64(ret, v) + return ret +} + +// return the big-endian binary encoding of a uint32 +func bigEndianUint32(v uint32) []byte { + ret := make([]byte, 4) + binary.BigEndian.PutUint32(ret, v) + return ret +} + +// accountKey: 4-byte prefix + 32-byte address +func accountKey(address basics.Address) []byte { + ret := []byte(kvPrefixAccount) + ret = append(ret, "-"...) + ret = append(ret, address[:]...) + return ret +} + +// resourceKey: 4-byte prefix + 32-byte address + 8-byte big-endian uint64 +func resourceKey(address basics.Address, aidx basics.CreatableIndex) []byte { + ret := []byte(kvPrefixResource) + ret = append(ret, "-"...) + ret = append(ret, address[:]...) + ret = append(ret, "-"...) + ret = append(ret, bigEndianUint64(uint64(aidx))...) + return ret +} + +func resourceAddrOnlyPartialKey(address basics.Address) []byte { + ret := []byte(kvPrefixResource) + ret = append(ret, "-"...) + ret = append(ret, address[:]...) + ret = append(ret, "-"...) + return ret +} + +func appKvKey(key string) []byte { + ret := []byte(kvPrefixAppKv) + ret = append(ret, "-"...) + ret = append(ret, key...) + return ret +} + +func creatableKey(cidx basics.CreatableIndex) []byte { + ret := []byte(kvPrefixCreatorIndex) + ret = append(ret, "-"...) + ret = append(ret, bigEndianUint64(uint64(cidx))...) + return ret +} + +func onlineAccountKey(address basics.Address, round basics.Round) []byte { + ret := []byte(kvPrefixOnlineAccount) + ret = append(ret, "-"...) + ret = append(ret, address[:]...) + ret = append(ret, "-"...) + ret = append(ret, bigEndianUint64(uint64(round))...) + return ret +} + +func onlineAccountOnlyPartialKey(address basics.Address) []byte { + ret := []byte(kvPrefixOnlineAccount) + ret = append(ret, "-"...) + ret = append(ret, address[:]...) + ret = append(ret, "-"...) + return ret +} + +// TODO: use basics.Round +func onlineAccountBalanceKey(round uint64, normBalance uint64, address basics.Address) []byte { + ret := []byte(kvPrefixOnlineAccountBalance) + ret = append(ret, "-"...) + ret = append(ret, bigEndianUint64(round)...) + ret = append(ret, "-"...) + ret = append(ret, bigEndianUint64(normBalance)...) + ret = append(ret, "-"...) + ret = append(ret, address[:]...) + return ret +} + +func onlineAccountBalanceOnlyPartialKey(round basics.Round) []byte { + ret := []byte(kvPrefixOnlineAccountBalance) + ret = append(ret, "-"...) + ret = append(ret, bigEndianUint64(uint64(round))...) + ret = append(ret, "-"...) + return ret +} + +func roundKey() []byte { + ret := []byte(kvRoundKey) + return ret +} + +func schemaVersionKey() []byte { + ret := []byte(kvSchemaVersionKey) + return ret +} + +func totalsKey(catchpointStaging bool) []byte { + ret := []byte(kvTotalsKey) + ret = append(ret, "-"...) + if catchpointStaging { + ret = append(ret, "staging"...) + } else { + ret = append(ret, "live"...) + } + return ret +} + +func txTailKey(rnd basics.Round) []byte { + ret := []byte(kvTxTail) + ret = append(ret, "-"...) + ret = append(ret, bigEndianUint64(uint64(rnd))...) + return ret +} + +func onlineAccountRoundParamsKey(rnd basics.Round) []byte { + ret := []byte(kvOnlineAccountRoundParams) + ret = append(ret, "-"...) + ret = append(ret, bigEndianUint64(uint64(rnd))...) + return ret +} + +func stateproofKey(rnd basics.Round) []byte { + ret := []byte(kvPrefixStateproof) + ret = append(ret, "-"...) + ret = append(ret, bigEndianUint64(uint64(rnd))...) + return ret +} diff --git a/ledger/store/trackerdb/generickv/stateproof_reader.go b/ledger/store/trackerdb/generickv/stateproof_reader.go new file mode 100644 index 0000000000..0c3ed62b58 --- /dev/null +++ b/ledger/store/trackerdb/generickv/stateproof_reader.go @@ -0,0 +1,104 @@ +// 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 generickv + +import ( + "context" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" +) + +type stateproofReader struct { + kvr KvRead +} + +// MakeStateproofReader returns a trackerdb.SpVerificationCtxReader for a KV +func MakeStateproofReader(kvr KvRead) trackerdb.SpVerificationCtxReader { + return &stateproofReader{kvr} +} + +// LookupSPContext implements trackerdb.SpVerificationCtxReader +func (r *stateproofReader) LookupSPContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + // SQL at the time of writing: + // + // SELECT + // verificationcontext + // FROM stateproofverification + // WHERE lastattestedround=? + + value, closer, err := r.kvr.Get(stateproofKey(stateProofLastAttestedRound)) + if err != nil { + return nil, err + } + defer closer.Close() + + var vc ledgercore.StateProofVerificationContext + err = protocol.Decode(value, &vc) + if err != nil { + return nil, err + } + + return &vc, nil +} + +// GetAllSPContexts implements trackerdb.SpVerificationCtxReader +func (r *stateproofReader) GetAllSPContexts(ctx context.Context) ([]ledgercore.StateProofVerificationContext, error) { + // SQL at the time of writing: + // + // SELECT + // verificationContext + // FROM stateProofVerification + // ORDER BY lastattestedround + + low := []byte(kvPrefixStateproof + "-") + high := []byte(kvPrefixStateproof + ".") + iter := r.kvr.NewIter(low, high, false) + defer iter.Close() + + results := make([]ledgercore.StateProofVerificationContext, 0) + + var value []byte + var err error + for iter.Next() { + // get value for current item in the iterator + value, err = iter.Value() + if err != nil { + return nil, err + } + + // decode the value + vc := ledgercore.StateProofVerificationContext{} + err = protocol.Decode(value, &vc) + if err != nil { + return nil, err + } + + // add the item to the results + results = append(results, vc) + } + + return results, nil +} + +// GetAllSPContextsFromCatchpointTbl implements trackerdb.SpVerificationCtxReader +func (r *stateproofReader) GetAllSPContextsFromCatchpointTbl(ctx context.Context) ([]ledgercore.StateProofVerificationContext, error) { + // TODO: catchpoint + return nil, nil +} diff --git a/ledger/store/trackerdb/generickv/stateproof_writer.go b/ledger/store/trackerdb/generickv/stateproof_writer.go new file mode 100644 index 0000000000..42565d9edc --- /dev/null +++ b/ledger/store/trackerdb/generickv/stateproof_writer.go @@ -0,0 +1,76 @@ +// 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 generickv + +import ( + "context" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" +) + +type stateproofWriter struct { + kvw KvWrite +} + +// MakeStateproofWriter returns a trackerdb.SpVerificationCtxWriter for a KV +func MakeStateproofWriter(kvw KvWrite) trackerdb.SpVerificationCtxWriter { + return &stateproofWriter{kvw} +} + +// StoreSPContexts implements trackerdb.SpVerificationCtxWriter +func (w *stateproofWriter) StoreSPContexts(ctx context.Context, verificationContext []*ledgercore.StateProofVerificationContext) error { + // SQL at the time of writing: + // + // INSERT INTO stateProofVerification + // (lastattestedround, verificationContext) + // VALUES + // (?, ?) + + for i := range verificationContext { + // write stateproof entry + vc := verificationContext[i] + raw := protocol.Encode(vc) + err := w.kvw.Set(stateproofKey(vc.LastAttestedRound), raw) + if err != nil { + return err + } + } + + return nil +} + +// DeleteOldSPContexts implements trackerdb.SpVerificationCtxWriter +func (w *stateproofWriter) DeleteOldSPContexts(ctx context.Context, earliestLastAttestedRound basics.Round) error { + // SQL at the time of writing: + // + // DELETE FROM stateproofverification + // WHERE lastattestedround < ? + + start := []byte(kvPrefixStateproof + "-") + end := stateproofKey(earliestLastAttestedRound) + + return w.kvw.DeleteRange(start, end) +} + +// StoreSPContextsToCatchpointTbl implements trackerdb.SpVerificationCtxWriter +func (w *stateproofWriter) StoreSPContextsToCatchpointTbl(ctx context.Context, verificationContexts []ledgercore.StateProofVerificationContext) error { + // TODO: catchpoint + return nil +} diff --git a/ledger/store/trackerdb/generickv/writer.go b/ledger/store/trackerdb/generickv/writer.go new file mode 100644 index 0000000000..c5d8dcfc0f --- /dev/null +++ b/ledger/store/trackerdb/generickv/writer.go @@ -0,0 +1,91 @@ +// 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 generickv + +import ( + "context" + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" + "testing" +) + +type writer struct { + // TODO: the need for the store here is quite broken + // this is due to exposing RunMigrations and AccountsInit on the writers + // the internals of this methods completly ignore the "writer" and recreate a transaction + store trackerdb.Store + KvWrite + KvRead +} + +// MakeWriter returns a trackerdb.Writer for a KV +func MakeWriter(store trackerdb.Store, kvw KvWrite, kvr KvRead) trackerdb.Writer { + return &writer{store, kvw, kvr} +} + +// MakeAccountsOptimizedWriter implements trackerdb.Writer +func (w *writer) MakeAccountsOptimizedWriter(hasAccounts bool, hasResources bool, hasKvPairs bool, hasCreatables bool) (trackerdb.AccountsWriter, error) { + return MakeAccountsWriter(w, w), nil +} + +// MakeAccountsWriter implements trackerdb.Writer +func (w *writer) MakeAccountsWriter() (trackerdb.AccountsWriterExt, error) { + return MakeAccountsWriter(w, w), nil +} + +// MakeOnlineAccountsOptimizedWriter implements trackerdb.Writer +func (w *writer) MakeOnlineAccountsOptimizedWriter(hasAccounts bool) (trackerdb.OnlineAccountsWriter, error) { + return MakeOnlineAccountsWriter(w), nil +} + +// MakeSpVerificationCtxWriter implements trackerdb.Writer +func (w *writer) MakeSpVerificationCtxWriter() trackerdb.SpVerificationCtxWriter { + return MakeStateproofWriter(w) +} + +// Testing implements trackerdb.Writer +func (w *writer) Testing() trackerdb.WriterTestExt { + return &writerForTesting{w.store, w, w} +} + +type writerForTesting struct { + trackerdb.Store + KvWrite + KvRead +} + +// AccountsInitLightTest implements trackerdb.WriterTestExt +func (w *writerForTesting) AccountsInitLightTest(tb testing.TB, initAccounts map[basics.Address]basics.AccountData, proto config.ConsensusParams) (newDatabase bool, err error) { + panic("unimplemented") +} + +// AccountsInitTest implements trackerdb.WriterTestExt +func (w *writerForTesting) AccountsInitTest(tb testing.TB, initAccounts map[basics.Address]basics.AccountData, proto protocol.ConsensusVersion) (newDatabase bool) { + return AccountsInitTest(tb, w, initAccounts, proto) +} + +// AccountsUpdateSchemaTest implements trackerdb.WriterTestExt +func (w *writerForTesting) AccountsUpdateSchemaTest(ctx context.Context) (err error) { + panic("unimplemented") +} + +// ModifyAcctBaseTest implements trackerdb.WriterTestExt +func (w *writerForTesting) ModifyAcctBaseTest() error { + panic("unimplemented") +} diff --git a/ledger/store/trackerdb/testsuite/README.md b/ledger/store/trackerdb/testsuite/README.md new file mode 100644 index 0000000000..15270cfde9 --- /dev/null +++ b/ledger/store/trackerdb/testsuite/README.md @@ -0,0 +1,29 @@ +!! **DO NOT IMPORT THIS MODULE** !! + +--- + +This is the `generickv` tests implemented _outside_ the package. +This is done to avoid cirucular dependencies on some of the testing wizardry taking place. +Namely, making the tests polymorphic on each database implementation, +so we can reuse the test suite against multiple backends. + +# Adding tests to the suite + +1. Use the following signature on your tests: + +```go +func CustomTestDoingSomething(t *customT) { + // your test.. +} +``` + +The `customT` type behaves just like `testing.T` but has some extras. + +2. Register your test with the suite in the `init()` function in your test file. + +```go +func init() { + // register tests that will run on each KV implementation + registerTest("something", CustomTestDoingSomething) +} +``` \ No newline at end of file diff --git a/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go new file mode 100644 index 0000000000..3c17c96b8e --- /dev/null +++ b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go @@ -0,0 +1,300 @@ +// 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 testsuite + +import ( + "context" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" + "github.com/stretchr/testify/require" +) + +func init() { + // register tests that will run on each KV implementation + registerTest("global-round-update", CustomTestRoundUpdate) + registerTest("global-totals", CustomTestTotals) + registerTest("txtail-update", CustomTestTxTail) + registerTest("online_accounts-round_params-update", CustomTestOnlineAccountParams) + registerTest("accounts-lookup_by_rowid", CustomTestAccountLookupByRowID) + registerTest("resources-lookup_by_rowid", CustomTestResourceLookupByRowID) +} + +func CustomTestRoundUpdate(t *customT) { + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // update the round + err = aw.UpdateAccountsRound(basics.Round(1)) + require.NoError(t, err) + + // read the round + rnd, err := ar.AccountsRound() + require.NoError(t, err) + require.Equal(t, basics.Round(1), rnd) +} + +func CustomTestTotals(t *customT) { + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // generate some test data + totals := ledgercore.AccountTotals{ + Online: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 42}}, + Offline: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 1000}}, + NotParticipating: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 8}}, + RewardsLevel: 9000, + } + + // update the totals + err = aw.AccountsPutTotals(totals, false) + require.NoError(t, err) + + // read the totals + readTotals, err := ar.AccountsTotals(context.Background(), false) + require.NoError(t, err) + require.Equal(t, totals, readTotals) + + // generate some staging values + stagingTotals := ledgercore.AccountTotals{ + Online: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 1}}, + Offline: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 2}}, + NotParticipating: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 3}}, + RewardsLevel: 4, + } + + // update the (staging) totals + err = aw.AccountsPutTotals(stagingTotals, true) + require.NoError(t, err) + + // read the totals + readTotals, err = ar.AccountsTotals(context.Background(), true) + require.NoError(t, err) + require.Equal(t, stagingTotals, readTotals) + + // double check the live data is still there + readTotals, err = ar.AccountsTotals(context.Background(), false) + require.NoError(t, err) + require.Equal(t, totals, readTotals) +} + +func CustomTestTxTail(t *customT) { + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // generate some test data + baseRound := basics.Round(0) + roundData := []*trackerdb.TxTailRound{ + { + TxnIDs: []transactions.Txid{transactions.Txid(crypto.Hash([]byte("tx-0")))}, + }, + { + TxnIDs: []transactions.Txid{transactions.Txid(crypto.Hash([]byte("tx-1")))}, + }, + { + TxnIDs: []transactions.Txid{transactions.Txid(crypto.Hash([]byte("tx-2")))}, + }, + } + // TODO: remove this conversion once we change the API to take the actual types + var rawRoundData [][]byte + for _, tail := range roundData { + raw := protocol.Encode(tail) + rawRoundData = append(rawRoundData, raw) + } + + // write TxTail's + err = aw.TxtailNewRound(context.Background(), baseRound, rawRoundData, baseRound) + require.NoError(t, err) + + // load TxTail's (error, must be the latest round) + _, _, _, err = ar.LoadTxTail(context.Background(), basics.Round(1)) + require.Error(t, err) + + // load TxTail's + txtails, hashes, readBaseRound, err := ar.LoadTxTail(context.Background(), basics.Round(2)) + require.NoError(t, err) + require.Len(t, txtails, 3) // assert boundries + require.Equal(t, roundData[0], txtails[0]) // assert ordering + require.Len(t, hashes, 3) + require.Equal(t, basics.Round(0), readBaseRound) + + // generate some more test data + roundData = []*trackerdb.TxTailRound{ + { + TxnIDs: []transactions.Txid{transactions.Txid(crypto.Hash([]byte("tx-3")))}, + }, + } + // reset data + rawRoundData = make([][]byte, 0) + // TODO: remove this conversion once we change the API to take the actual types + for _, tail := range roundData { + raw := protocol.Encode(tail) + rawRoundData = append(rawRoundData, raw) + } + // write TxTail's (delete everything before round 2) + err = aw.TxtailNewRound(context.Background(), basics.Round(3), rawRoundData, basics.Round(2)) + require.NoError(t, err) + + // load TxTail's + txtails, hashes, readBaseRound, err = ar.LoadTxTail(context.Background(), basics.Round(3)) + require.NoError(t, err) + require.Len(t, txtails, 2) + require.Len(t, hashes, 2) + require.Equal(t, basics.Round(2), readBaseRound) +} + +func CustomTestOnlineAccountParams(t *customT) { + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // generate some test data + startRound := basics.Round(0) + roundParams := []ledgercore.OnlineRoundParamsData{ + {OnlineSupply: 100}, + {OnlineSupply: 42}, + {OnlineSupply: 9000}, + } + + // clean up the db before starting with the test + // Note: some engines might start with some data built-in data for round 0 + err = aw.AccountsPruneOnlineRoundParams(basics.Round(42)) + require.NoError(t, err) + + // write round params + err = aw.AccountsPutOnlineRoundParams(roundParams, startRound) + require.NoError(t, err) + + // read round params + readParams, endRound, err := ar.AccountsOnlineRoundParams() + require.NoError(t, err) + require.Len(t, readParams, 3) // assert boundries + require.Equal(t, roundParams[0], readParams[0]) // assert ordering + require.Equal(t, basics.Round(2), endRound) // check round + + // prune params + err = aw.AccountsPruneOnlineRoundParams(basics.Round(1)) + require.NoError(t, err) + + // read round params (again, after prunning) + readParams, endRound, err = ar.AccountsOnlineRoundParams() + require.NoError(t, err) + require.Len(t, readParams, 2) // assert boundries + require.Equal(t, roundParams[1], readParams[0]) // assert ordering, and first item + require.Equal(t, basics.Round(2), endRound) // check round +} + +func CustomTestAccountLookupByRowID(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, false, false, false) + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // generate some test data + addrA := RandomAddress() + dataA := trackerdb.BaseAccountData{ + RewardsBase: 1000, + } + normBalanceA := dataA.NormalizedOnlineBalance(t.proto) + refA, err := aow.InsertAccount(addrA, normBalanceA, dataA) + require.NoError(t, err) + + // + // test + // + + // non-existing account + _, err = ar.LookupAccountRowID(RandomAddress()) + require.Error(t, err) + require.Equal(t, err, trackerdb.ErrNotFound) + + // read account + ref, err := ar.LookupAccountRowID(addrA) + require.NoError(t, err) + require.Equal(t, refA, ref) +} + +func CustomTestResourceLookupByRowID(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, false) + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // generate some test data + addrA := RandomAddress() + accDataA := trackerdb.BaseAccountData{RewardsBase: 1000} + refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA) + require.NoError(t, err) + + // generate some test data + resDataA0 := trackerdb.MakeResourcesData(0) + resDataA0.SetAssetParams(basics.AssetParams{ + Total: 100, + UnitName: "t", + AssetName: "test-asset", + Manager: addrA, + Reserve: addrA, + Freeze: addrA, + Clawback: addrA, + URL: "http://127.0.0.1/8000", + }, true) + resDataA0.SetAssetHolding(basics.AssetHolding{Amount: 10}) + aidxResA0 := basics.CreatableIndex(0) + _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0) + require.NoError(t, err) + + // + // test + // + + // non-existing resource + _, err = ar.LookupResourceDataByAddrID(refAccA, basics.CreatableIndex(100)) + require.Error(t, err) + require.Equal(t, err, trackerdb.ErrNotFound) + + // read resource + data, err := ar.LookupResourceDataByAddrID(refAccA, aidxResA0) + require.NoError(t, err) + // parse the raw data + var res trackerdb.ResourcesData + err = protocol.Decode(data, &res) + require.NoError(t, err) + // assert that we got the resource + require.Equal(t, resDataA0, res) + + // read resource on nil account + _, err = ar.LookupResourceDataByAddrID(nil, basics.CreatableIndex(100)) + require.Error(t, err) + require.Equal(t, err, trackerdb.ErrNotFound) +} diff --git a/ledger/store/trackerdb/testsuite/accounts_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_kv_test.go new file mode 100644 index 0000000000..0b8e74a715 --- /dev/null +++ b/ledger/store/trackerdb/testsuite/accounts_kv_test.go @@ -0,0 +1,480 @@ +// 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 testsuite + +import ( + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/stretchr/testify/require" +) + +func init() { + // register tests that will run on each KV implementation + registerTest("accounts-crud", CustomTestAccountsCrud) + registerTest("resources-crud", CustomTestResourcesCrud) + registerTest("resources-query-all", CustomTestResourcesQueryAll) + registerTest("kv-crud", CustomTestAppKVCrud) + registerTest("creatables-crud", CustomTestCreatablesCrud) + registerTest("creatables-query-all", CustomTestCreatablesQueryAll) +} + +func CustomTestAccountsCrud(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, false, false, false) + require.NoError(t, err) + + aor, err := t.db.MakeAccountsOptimizedReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // generate some test data + addrA := RandomAddress() + dataA := trackerdb.BaseAccountData{ + RewardsBase: 1000, + } + + // insert the account + normBalanceA := dataA.NormalizedOnlineBalance(t.proto) + refA, err := aow.InsertAccount(addrA, normBalanceA, dataA) + require.NoError(t, err) + + // read the account + padA, err := aor.LookupAccount(addrA) + require.NoError(t, err) + require.Equal(t, addrA, padA.Addr) // addr is present and correct + require.Equal(t, refA, padA.Ref) // same ref as when we inserted it + require.Equal(t, dataA, padA.AccountData) // same data + require.Equal(t, expectedRound, padA.Round) // db round + + // read the accounts "ref" + readRefA, err := ar.LookupAccountRowID(addrA) + require.NoError(t, err) + require.Equal(t, refA, readRefA) // same ref as when we inserted it + + // update the account + dataA.RewardsBase = 98287 + normBalanceA = dataA.NormalizedOnlineBalance(t.proto) + _, err = aow.UpdateAccount(refA, normBalanceA, dataA) + require.NoError(t, err) + + // read updated account + padA, err = aor.LookupAccount(addrA) + require.NoError(t, err) + require.Equal(t, dataA, padA.AccountData) // same updated data + + // delete account + _, err = aow.DeleteAccount(refA) + require.NoError(t, err) + + // read deleted account + // Note: this is a bit counter-intuitive but lookup returns a value + // even when the account doesnt exist. + padA, err = aor.LookupAccount(addrA) + require.NoError(t, err) + require.Equal(t, addrA, padA.Addr) // the addr is there + require.Empty(t, padA.AccountData) // no data + require.Equal(t, expectedRound, padA.Round) // db round (this is present even if record does not exist) +} + +func CustomTestResourcesCrud(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, false) + require.NoError(t, err) + + aor, err := t.db.MakeAccountsOptimizedReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // + // pre-fill the db with an account for testing + // + + // account + addrA := RandomAddress() + accDataA := trackerdb.BaseAccountData{RewardsBase: 1000} + refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA) + require.NoError(t, err) + + // + // test + // + + // generate some test data + resDataA0 := trackerdb.MakeResourcesData(0) + resDataA0.SetAssetParams(basics.AssetParams{ + Total: 100, + UnitName: "t", + AssetName: "test-asset", + Manager: addrA, + Reserve: addrA, + Freeze: addrA, + Clawback: addrA, + URL: "http://127.0.0.1/8000", + }, true) + resDataA0.SetAssetHolding(basics.AssetHolding{Amount: 10}) + aidxResA0 := basics.CreatableIndex(0) + + // insert the resource + refResA0, err := aow.InsertResource(refAccA, aidxResA0, resDataA0) + require.NoError(t, err) + require.NotNil(t, refResA0) + + // read the resource + prdA0, err := aor.LookupResources(addrA, aidxResA0, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, aidxResA0, prdA0.Aidx) // aidx is present and correct + require.Equal(t, refAccA, prdA0.AcctRef) // acctRef is present and correct + require.Equal(t, resDataA0, prdA0.Data) // same data + require.Equal(t, expectedRound, prdA0.Round) // db round + + // update the resource + resDataA0.Amount = 900 + _, err = aow.UpdateResource(refAccA, aidxResA0, resDataA0) + require.NoError(t, err) + + // read updated resource + prdA0, err = aor.LookupResources(addrA, aidxResA0, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, resDataA0, prdA0.Data) // same updated data + + // delete resource + _, err = aow.DeleteResource(refAccA, aidxResA0) + require.NoError(t, err) + + // read deleted resource + // Note: this is a bit counter-intuitive but lookup returns a value + // even when the account doesnt exist. + prdA0, err = aor.LookupResources(addrA, aidxResA0, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, aidxResA0, prdA0.Aidx) // the aidx is there + require.Nil(t, prdA0.AcctRef) // the account ref is not present + require.Equal(t, trackerdb.MakeResourcesData(0), prdA0.Data) // rnd 0, clean data + require.Equal(t, expectedRound, prdA0.Round) // db round (this is present even if record does not exist) +} + +func CustomTestResourcesQueryAll(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, false) + require.NoError(t, err) + + aor, err := t.db.MakeAccountsOptimizedReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // + // pre-fill the db with an account for testing + // + + // account A + addrA := RandomAddress() + accDataA := trackerdb.BaseAccountData{RewardsBase: 1000} + refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA) + require.NoError(t, err) + + // resource A-0 + resDataA0 := trackerdb.ResourcesData{} + aidxResA0 := basics.CreatableIndex(0) + _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0) + require.NoError(t, err) + + // resource A-1 + resDataA1 := trackerdb.ResourcesData{} + aidxResA1 := basics.CreatableIndex(1) + _, err = aow.InsertResource(refAccA, aidxResA1, resDataA1) + require.NoError(t, err) + + // + // test + // + + prs, rnd, err := aor.LookupAllResources(addrA) + require.NoError(t, err) + require.Equal(t, aidxResA0, prs[0].Aidx) + require.Equal(t, aidxResA1, prs[1].Aidx) + require.Equal(t, expectedRound, prs[0].Round) // db round (inside resources) + require.Equal(t, expectedRound, rnd) // db round (from the return) +} + +func CustomTestAppKVCrud(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, true, true, false) + require.NoError(t, err) + + aor, err := t.db.MakeAccountsOptimizedReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // + // pre-fill the db with an account for testing + // + + // account + addrA := RandomAddress() + accDataA := trackerdb.BaseAccountData{RewardsBase: 1000} + refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA) + require.NoError(t, err) + // resource + resDataA0 := trackerdb.ResourcesData{} + aidxResA0 := basics.CreatableIndex(0) + refResA0, err := aow.InsertResource(refAccA, aidxResA0, resDataA0) + require.NoError(t, err) + require.NotNil(t, refResA0) + + // + // test + // + + // insert the kv + kvKey := "foobar-mykey" + kvValue := []byte("1234") + err = aow.UpsertKvPair(kvKey, kvValue) + require.NoError(t, err) + + // read the kv + pv1, err := aor.LookupKeyValue(kvKey) + require.NoError(t, err) + require.Equal(t, kvValue, pv1.Value) // same data + require.Equal(t, expectedRound, pv1.Round) // db round + + // update the kv + kvValue = []byte("777") + err = aow.UpsertKvPair(kvKey, kvValue) + require.NoError(t, err) + + // read updated kv + pv1, err = aor.LookupKeyValue(kvKey) + require.NoError(t, err) + require.Equal(t, kvValue, pv1.Value) // same data + + // delete the kv + err = aow.DeleteKvPair(kvKey) + require.NoError(t, err) + + // read deleted kv + require.NoError(t, err) + + // read deleted kv + // Note: this is a bit counter-intuitive but lookup returns a value + // even when the record doesn't exist. + pv1, err = aor.LookupKeyValue(kvKey) + require.NoError(t, err) + require.Equal(t, expectedRound, pv1.Round) // db round (this is present even if record does not exist) +} + +func CustomTestCreatablesCrud(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, true) + require.NoError(t, err) + + aor, err := t.db.MakeAccountsOptimizedReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // + // pre-fill the db with an account for testing + // + + // account A + addrA := RandomAddress() + accDataA := trackerdb.BaseAccountData{RewardsBase: 1000} + refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA) + require.NoError(t, err) + + // resource A-0 + resDataA0 := trackerdb.ResourcesData{} + aidxResA0 := basics.CreatableIndex(0) + _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0) + require.NoError(t, err) + + // resource A-1 + resDataA1 := trackerdb.ResourcesData{} + aidxResA1 := basics.CreatableIndex(1) + _, err = aow.InsertResource(refAccA, aidxResA1, resDataA1) + require.NoError(t, err) + + // + // test + // + + // insert creator for A0 + resA0ctype := basics.AssetCreatable + cRefA0, err := aow.InsertCreatable(aidxResA0, resA0ctype, addrA[:]) + require.NoError(t, err) + require.NotNil(t, cRefA0) + + // insert creator for A1 + resA1ctype := basics.AppCreatable + cRefA1, err := aow.InsertCreatable(aidxResA1, resA1ctype, addrA[:]) + require.NoError(t, err) + require.NotNil(t, cRefA1) + + // lookup creator (correct ctype) + addr, ok, rnd, err := aor.LookupCreator(aidxResA0, basics.AssetCreatable) + require.NoError(t, err) + require.True(t, ok) // ok=true when it works + require.Equal(t, addrA, addr) // correct owner + require.Equal(t, expectedRound, rnd) // db round + + // lookup creator (invalid ctype) + _, ok, rnd, err = aor.LookupCreator(aidxResA0, basics.AppCreatable) + require.NoError(t, err) + require.False(t, ok) // ok=false when its doesnt match + require.Equal(t, expectedRound, rnd) // db round (this is present even if record does not exist) + + // lookup creator (unknown index) + _, ok, rnd, err = aor.LookupCreator(basics.CreatableIndex(999), basics.AppCreatable) + require.NoError(t, err) + require.False(t, ok) // ok=false when it doesn't exist + require.Equal(t, expectedRound, rnd) // db round (this is present even if record does not exist) + +} + +func CustomTestCreatablesQueryAll(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, true) + require.NoError(t, err) + + aor, err := t.db.MakeAccountsOptimizedReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // + // pre-fill the db with an account for testing + // + + // account A + addrA := RandomAddress() + accDataA := trackerdb.BaseAccountData{RewardsBase: 1000} + refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA) + require.NoError(t, err) + + // resource A-0 + resDataA0 := trackerdb.ResourcesData{} + aidxResA0 := basics.CreatableIndex(0) + _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0) + require.NoError(t, err) + + // resource A-1 + resDataA1 := trackerdb.ResourcesData{} + aidxResA1 := basics.CreatableIndex(1) + _, err = aow.InsertResource(refAccA, aidxResA1, resDataA1) + require.NoError(t, err) + + // resource A-2 + resDataA2 := trackerdb.ResourcesData{} + aidxResA2 := basics.CreatableIndex(2) + _, err = aow.InsertResource(refAccA, aidxResA2, resDataA2) + require.NoError(t, err) + + // creator for A0 + resA0ctype := basics.AssetCreatable + cRefA0, err := aow.InsertCreatable(aidxResA0, resA0ctype, addrA[:]) + require.NoError(t, err) + require.NotNil(t, cRefA0) + + // creator for A1 + resA1ctype := basics.AppCreatable + cRefA1, err := aow.InsertCreatable(aidxResA1, resA1ctype, addrA[:]) + require.NoError(t, err) + require.NotNil(t, cRefA1) + + // creator for A2 + resA2ctype := basics.AppCreatable + cRefA2, err := aow.InsertCreatable(aidxResA2, resA2ctype, addrA[:]) + require.NoError(t, err) + require.NotNil(t, cRefA2) + + // + // test + // + + // filter by type + cls, rnd, err := aor.ListCreatables(basics.CreatableIndex(99), 5, basics.AssetCreatable) + require.NoError(t, err) + require.Len(t, cls, 1) // only one asset + require.Equal(t, aidxResA0, cls[0].Index) // resource A-0 + require.Equal(t, expectedRound, rnd) // db round + + // with multiple results + cls, rnd, err = aor.ListCreatables(basics.CreatableIndex(99), 5, basics.AppCreatable) + require.NoError(t, err) + require.Len(t, cls, 2) // two apps + require.Equal(t, aidxResA2, cls[0].Index) // resource A-2 + require.Equal(t, aidxResA1, cls[1].Index) // resource A-1 + require.Equal(t, expectedRound, rnd) // db round + + // limit results + cls, rnd, err = aor.ListCreatables(basics.CreatableIndex(99), 1, basics.AppCreatable) + require.NoError(t, err) + require.Len(t, cls, 1) // two apps + require.Equal(t, aidxResA2, cls[0].Index) // resource A-2 + require.Equal(t, expectedRound, rnd) // db round + + // filter maxId + cls, rnd, err = aor.ListCreatables(aidxResA2, 10, basics.AppCreatable) + require.NoError(t, err) + require.Len(t, cls, 2) // only one app since to that cidx + require.Equal(t, aidxResA2, cls[0].Index) // resource A-2 (checks for order too) + require.Equal(t, aidxResA1, cls[1].Index) // resource A-1 + require.Equal(t, expectedRound, rnd) // db round + +} diff --git a/ledger/store/trackerdb/testsuite/dbsemantics_test.go b/ledger/store/trackerdb/testsuite/dbsemantics_test.go new file mode 100644 index 0000000000..b155b918a9 --- /dev/null +++ b/ledger/store/trackerdb/testsuite/dbsemantics_test.go @@ -0,0 +1,82 @@ +// 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 testsuite + +import ( + "context" + + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/stretchr/testify/require" +) + +func init() { + // register tests that will run on each KV implementation + registerTest("db-semantics-transaction", CustomTestTransaction) +} + +// This test will ensure that transaction semantics carry the same meaning across all engine implementations. +func CustomTestTransaction(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, false, false, false) + require.NoError(t, err) + + aor, err := t.db.MakeAccountsOptimizedReader() + require.NoError(t, err) + + // generate some test data + addrA := RandomAddress() + dataA := trackerdb.BaseAccountData{ + RewardsBase: 1000, + } + + // insert the account + normBalanceA := dataA.NormalizedOnlineBalance(t.proto) + refA, err := aow.InsertAccount(addrA, normBalanceA, dataA) + require.NoError(t, err) + + // + // test + // + + err = t.db.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { + // create a scoped writer + aow, err := tx.MakeAccountsOptimizedWriter(true, false, false, false) + require.NoError(t, err) + + // create a scoped reader + aor, err := tx.MakeAccountsOptimizedReader() + require.NoError(t, err) + + // read an account + padA, err := aor.LookupAccount(addrA) + require.NoError(t, err) + require.Equal(t, refA, padA.Ref) // same ref as when we inserted it + + // update the account + dataA.RewardsBase = 98287 + normBalanceA = dataA.NormalizedOnlineBalance(t.proto) + _, err = aow.UpdateAccount(refA, normBalanceA, dataA) + require.NoError(t, err) + + return nil + }) + require.NoError(t, err) + + // read the updated record outside the transaction to make sure it was commited + padA, err := aor.LookupAccount(addrA) + require.NoError(t, err) + require.Equal(t, uint64(98287), padA.AccountData.RewardsBase) // same updated data +} diff --git a/ledger/store/trackerdb/testsuite/migration_test.go b/ledger/store/trackerdb/testsuite/migration_test.go new file mode 100644 index 0000000000..10d9c74737 --- /dev/null +++ b/ledger/store/trackerdb/testsuite/migration_test.go @@ -0,0 +1,140 @@ +// 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 testsuite + +import ( + "context" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/protocol" + "github.com/stretchr/testify/require" +) + +func init() { + // register tests that will run on each KV implementation + registerTest("db-migration-check-basic", CustomTestChecBasicMigration) + // Disabled since it's technically broken the way its written. + // registerTest("db-migration-check-with-accounts", CustomTestCheckMigrationWithAccounts) +} + +func CustomTestChecBasicMigration(t *customT) { + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // check round + round, err := ar.AccountsRound() + require.NoError(t, err) + require.Equal(t, basics.Round(0), round) // initialized to round 0 + + // check account totals + totals, err := ar.AccountsTotals(context.Background(), false) + require.NoError(t, err) + require.Equal(t, uint64(0), totals.RewardsLevel) + require.Equal(t, ledgercore.AlgoCount{}, totals.Online) + require.Equal(t, ledgercore.AlgoCount{}, totals.Offline) + require.Equal(t, ledgercore.AlgoCount{}, totals.NotParticipating) + + // check tx-tails + txTailData, hashes, baseRound, err := ar.LoadTxTail(context.Background(), basics.Round(0)) + require.NoError(t, err) + require.Len(t, txTailData, 0) // no data + require.Len(t, hashes, 0) // no data + require.Equal(t, basics.Round(1), baseRound) // (the impls return +1 at the end) + + // check online accounts + oas, err := ar.OnlineAccountsAll(99) + require.NoError(t, err) + require.Len(t, oas, 0) + + // check online round params + oparams, endRound, err := ar.AccountsOnlineRoundParams() + require.NoError(t, err) + require.Len(t, oparams, 1) + require.Equal(t, basics.Round(0), endRound) + require.Equal(t, uint64(0), oparams[0].OnlineSupply) + require.Equal(t, uint64(0), oparams[0].RewardsLevel) + require.Equal(t, protocol.ConsensusCurrentVersion, oparams[0].CurrentProtocol) +} + +func makeAccountData(status basics.Status, algos basics.MicroAlgos) basics.AccountData { + ad := basics.AccountData{Status: status, MicroAlgos: algos} + if status == basics.Online { + ad.VoteFirstValid = 1 + ad.VoteLastValid = 100_000 + } + return ad +} + +func CustomTestCheckMigrationWithAccounts(t *customT) { + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // reset + aw.AccountsReset(context.Background()) + + initAccounts := make(map[basics.Address]basics.AccountData) + + addrA := basics.Address(crypto.Hash([]byte("a"))) + initAccounts[addrA] = makeAccountData(basics.Online, basics.MicroAlgos{Raw: 100}) + + addrB := basics.Address(crypto.Hash([]byte("b"))) + initAccounts[addrB] = makeAccountData(basics.Online, basics.MicroAlgos{Raw: 42}) + + addrC := basics.Address(crypto.Hash([]byte("c"))) + initAccounts[addrC] = makeAccountData(basics.Offline, basics.MicroAlgos{Raw: 30}) + + addrD := basics.Address(crypto.Hash([]byte("d"))) + initAccounts[addrD] = makeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 7}) + + params := trackerdb.Params{ + InitProto: protocol.ConsensusCurrentVersion, + InitAccounts: initAccounts, + } + + // re-run migrations + _, err = t.db.RunMigrations(context.Background(), params, logging.TestingLog(t), trackerdb.AccountDBVersion) + require.NoError(t, err) + + // check account totals + totals, err := ar.AccountsTotals(context.Background(), false) + require.NoError(t, err) + require.Equal(t, uint64(0), totals.RewardsLevel) + require.Equal(t, ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 142}}, totals.Online) + require.Equal(t, ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 30}}, totals.Offline) + require.Equal(t, ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 7}}, totals.NotParticipating) + + // check online accounts + oas, err := ar.OnlineAccountsAll(99) + require.NoError(t, err) + require.Len(t, oas, 2) + + // check online round params + oparams, endRound, err := ar.AccountsOnlineRoundParams() + require.NoError(t, err) + require.Len(t, oparams, 1) + require.Equal(t, basics.Round(0), endRound) + require.Equal(t, uint64(142), oparams[0].OnlineSupply) + require.Equal(t, uint64(0), oparams[0].RewardsLevel) + require.Equal(t, protocol.ConsensusCurrentVersion, oparams[0].CurrentProtocol) +} diff --git a/ledger/store/trackerdb/testsuite/mockdb_test.go b/ledger/store/trackerdb/testsuite/mockdb_test.go new file mode 100644 index 0000000000..cc9397f2b4 --- /dev/null +++ b/ledger/store/trackerdb/testsuite/mockdb_test.go @@ -0,0 +1,34 @@ +// 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 testsuite + +import ( + "testing" + + "github.com/algorand/go-algorand/config" +) + +func TestMockDB(t *testing.T) { + dbFactory := func(proto config.ConsensusParams) dbForTests { + db := makeMockDB(proto) + + seedDb(t, db) + + return db + } + runGenericTestsWithDB(t, dbFactory) +} diff --git a/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go new file mode 100644 index 0000000000..ed444794da --- /dev/null +++ b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go @@ -0,0 +1,554 @@ +// 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 testsuite + +import ( + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" + "github.com/stretchr/testify/require" +) + +func init() { + // register tests that will run on each KV implementation + registerTest("online-accounts-write-read", CustomTestOnlineAccountsWriteRead) + registerTest("online-accounts-all", CustomTestOnlineAccountsAll) + registerTest("online-accounts-top", CustomTestAccountsOnlineTop) + registerTest("online-accounts-get-by-addr", CustomTestLookupOnlineAccountDataByAddress) + registerTest("online-accounts-history", CustomTestOnlineAccountHistory) + registerTest("online-accounts-totals", CustomTestOnlineAccountTotals) + registerTest("online-accounts-delete", CustomTestOnlineAccountsDelete) + registerTest("online-accounts-expired", CustomTestAccountsOnlineExpired) +} + +func CustomTestOnlineAccountsWriteRead(t *customT) { + oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true) + require.NoError(t, err) + + oar, err := t.db.MakeOnlineAccountsOptimizedReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // generate some test data + addrA := RandomAddress() + updRoundA := uint64(400) + lastValidA := uint64(500) + dataA := trackerdb.BaseOnlineAccountData{ + BaseVotingData: trackerdb.BaseVotingData{}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(100)}, + RewardsBase: uint64(200), + } + normalizedBalA := dataA.NormalizedOnlineBalance(t.proto) + + // write + refA, err := oaw.InsertOnlineAccount(addrA, normalizedBalA, dataA, updRoundA, lastValidA) + require.NoError(t, err) + + // read + poA, err := oar.LookupOnline(addrA, basics.Round(updRoundA)) + require.NoError(t, err) + require.Equal(t, addrA, poA.Addr) + require.Equal(t, refA, poA.Ref) + require.Equal(t, dataA, poA.AccountData) + require.Equal(t, basics.Round(updRoundA), poA.UpdRound) // check the "update round" was read + require.Equal(t, expectedRound, poA.Round) + + // write a new version + dataA.MicroAlgos = basics.MicroAlgos{Raw: uint64(321)} + normalizedBalA = dataA.NormalizedOnlineBalance(t.proto) + updRoundA = uint64(450) + _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA, dataA, updRoundA, lastValidA) + require.NoError(t, err) + + // read (latest) + poA, err = oar.LookupOnline(addrA, basics.Round(500)) + require.NoError(t, err) + require.Equal(t, dataA, poA.AccountData) // check the data is from the new version + require.Equal(t, basics.Round(updRoundA), poA.UpdRound) // check the "update round" + + // read (original) + poA, err = oar.LookupOnline(addrA, basics.Round(405)) + require.NoError(t, err) + require.Equal(t, basics.MicroAlgos{Raw: uint64(100)}, poA.AccountData.MicroAlgos) // check the data is from the new version + require.Equal(t, basics.Round(400), poA.UpdRound) // check the "update round" + + // read (at upd round) + poA, err = oar.LookupOnline(addrA, basics.Round(450)) + require.NoError(t, err) + require.Equal(t, basics.MicroAlgos{Raw: uint64(321)}, poA.AccountData.MicroAlgos) // check the data is from the new version + require.Equal(t, basics.Round(450), poA.UpdRound) // check the "update round" +} + +func CustomTestOnlineAccountHistory(t *customT) { + oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true) + require.NoError(t, err) + + oar, err := t.db.MakeOnlineAccountsOptimizedReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // generate some test data + addrA := RandomAddress() + dataA1 := trackerdb.BaseOnlineAccountData{ + BaseVotingData: trackerdb.BaseVotingData{}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(20)}, + RewardsBase: uint64(200), + } + normalizedBalA1 := dataA1.NormalizedOnlineBalance(t.proto) + + refA1, err := oaw.InsertOnlineAccount(addrA, normalizedBalA1, dataA1, uint64(2), uint64(2)) + require.NoError(t, err) + + // generate some test data + dataA2 := trackerdb.BaseOnlineAccountData{ + BaseVotingData: trackerdb.BaseVotingData{}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(100)}, + RewardsBase: uint64(200), + } + normalizedBalA2 := dataA2.NormalizedOnlineBalance(t.proto) + + refA2, err := oaw.InsertOnlineAccount(addrA, normalizedBalA2, dataA2, uint64(3), uint64(3)) + require.NoError(t, err) + + // generate some test data + addrB := RandomAddress() + dataB1 := trackerdb.BaseOnlineAccountData{ + BaseVotingData: trackerdb.BaseVotingData{}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(75)}, + RewardsBase: uint64(200), + } + normalizedBalB1 := dataB1.NormalizedOnlineBalance(t.proto) + + refB1, err := oaw.InsertOnlineAccount(addrB, normalizedBalB1, dataB1, uint64(3), uint64(3)) + require.NoError(t, err) + + // + // the test + // + + resultsA, rnd, err := oar.LookupOnlineHistory(addrA) + require.NoError(t, err) + require.Equal(t, expectedRound, rnd) // check the db round + require.Len(t, resultsA, 2) + require.Equal(t, basics.Round(2), resultsA[0].UpdRound) // check ordering + require.Equal(t, basics.Round(3), resultsA[1].UpdRound) // check ordering + // check item fields + require.Empty(t, resultsA[0].Round) // check the db round is not set + require.Equal(t, addrA, resultsA[0].Addr) // check addr + require.Equal(t, refA1, resultsA[0].Ref) // check ref + require.Equal(t, dataA1, resultsA[0].AccountData) // check data + // check ref is valid on all + require.Equal(t, refA2, resultsA[1].Ref) // check ref + + // check for B + resultsB, _, err := oar.LookupOnlineHistory(addrB) + require.NoError(t, err) + require.Len(t, resultsB, 1) + require.Equal(t, addrB, resultsB[0].Addr) // check addr + require.Equal(t, refB1, resultsB[0].Ref) // check ref +} + +func CustomTestOnlineAccountsAll(t *customT) { + oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true) + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + voteLastValid := uint64(0) + + // generate some test data + addrA := basics.Address(crypto.Hash([]byte("a"))) + dataA0 := trackerdb.BaseOnlineAccountData{ + MicroAlgos: basics.MicroAlgos{Raw: uint64(200)}, + } + _, err = oaw.InsertOnlineAccount(addrA, dataA0.NormalizedOnlineBalance(t.proto), dataA0, 0, voteLastValid) + require.NoError(t, err) + + dataA1 := trackerdb.BaseOnlineAccountData{ + MicroAlgos: basics.MicroAlgos{Raw: uint64(250)}, + } + _, err = oaw.InsertOnlineAccount(addrA, dataA1.NormalizedOnlineBalance(t.proto), dataA1, 1, voteLastValid) + require.NoError(t, err) + + addrB := basics.Address(crypto.Hash([]byte("b"))) + dataB := trackerdb.BaseOnlineAccountData{ + MicroAlgos: basics.MicroAlgos{Raw: uint64(100)}, + } + _, err = oaw.InsertOnlineAccount(addrB, dataB.NormalizedOnlineBalance(t.proto), dataB, 0, voteLastValid) + require.NoError(t, err) + + addrC := basics.Address(crypto.Hash([]byte("c"))) + dataC := trackerdb.BaseOnlineAccountData{ + MicroAlgos: basics.MicroAlgos{Raw: uint64(30)}, + } + _, err = oaw.InsertOnlineAccount(addrC, dataC.NormalizedOnlineBalance(t.proto), dataC, 0, voteLastValid) + require.NoError(t, err) + + // + // test + // + + // read all accounts (with max accounts) + poA, err := ar.OnlineAccountsAll(2) + require.NoError(t, err) + require.Len(t, poA, 3) // account A has 2 records + 1 record from account B + + require.Equal(t, addrA, poA[0].Addr) + require.Equal(t, basics.MicroAlgos{Raw: uint64(200)}, poA[0].AccountData.MicroAlgos) + require.Equal(t, basics.Round(0), poA[0].UpdRound) + + require.Equal(t, addrA, poA[1].Addr) + require.Equal(t, basics.MicroAlgos{Raw: uint64(250)}, poA[1].AccountData.MicroAlgos) + require.Equal(t, basics.Round(1), poA[1].UpdRound) + + require.Equal(t, addrB, poA[2].Addr) + require.Equal(t, basics.MicroAlgos{Raw: uint64(100)}, poA[2].AccountData.MicroAlgos) + require.Equal(t, basics.Round(0), poA[2].UpdRound) +} + +func CustomTestAccountsOnlineTop(t *customT) { + oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true) + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // generate some test data + var testData []basics.Address + updRound := uint64(0) + for i := 0; i < 10; i++ { + addr := RandomAddress() + microAlgos := basics.MicroAlgos{Raw: uint64(10 + i*100)} + rewardBase := uint64(200 + i) + lastValid := uint64(500 + i) + data := trackerdb.BaseOnlineAccountData{ + BaseVotingData: trackerdb.BaseVotingData{}, + MicroAlgos: microAlgos, + RewardsBase: rewardBase, + } + normalizedBal := data.NormalizedOnlineBalance(t.proto) + + // write + _, err := oaw.InsertOnlineAccount(addr, normalizedBal, data, updRound, lastValid) + require.NoError(t, err) + + testData = append(testData, addr) + } + + // read (all) + poA, err := ar.AccountsOnlineTop(basics.Round(0), 0, 10, t.proto) + require.NoError(t, err) + require.Contains(t, poA, testData[9]) // most money + require.Contains(t, poA, testData[0]) // least money + + // read (just a few) + poA, err = ar.AccountsOnlineTop(basics.Round(0), 1, 2, t.proto) + require.NoError(t, err) + require.Len(t, poA, 2) + require.Contains(t, poA, testData[8]) // (second most money, we skipped 1) + require.Contains(t, poA, testData[7]) // (third, we only have 2 items) +} + +func CustomTestLookupOnlineAccountDataByAddress(t *customT) { + oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true) + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // generate some test data + addrA := RandomAddress() + updRoundA := uint64(400) + lastValidA := uint64(500) + dataA := trackerdb.BaseOnlineAccountData{ + BaseVotingData: trackerdb.BaseVotingData{}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(100)}, + RewardsBase: uint64(200), + } + normalizedBalA := dataA.NormalizedOnlineBalance(t.proto) + + refA, err := oaw.InsertOnlineAccount(addrA, normalizedBalA, dataA, updRoundA, lastValidA) + require.NoError(t, err) + + // + // test + // + + // check non-existing account + nonExistingAddr := RandomAddress() + _, _, err = ar.LookupOnlineAccountDataByAddress(nonExistingAddr) + require.Error(t, err) + require.Equal(t, trackerdb.ErrNotFound, err) // check the error type + + // read existing addr + readRef, readData, err := ar.LookupOnlineAccountDataByAddress(addrA) + require.NoError(t, err) + require.Equal(t, refA, readRef) // check ref is the same + // the method returns raw bytes, parse them + var badA trackerdb.BaseOnlineAccountData + err = protocol.Decode(readData, &badA) + require.NoError(t, err) + require.Equal(t, dataA, badA) +} + +func CustomTestOnlineAccountTotals(t *customT) { + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + oaor, err := t.db.MakeOnlineAccountsOptimizedReader() + require.NoError(t, err) + + // generate some test data + roundParams := []ledgercore.OnlineRoundParamsData{ + {OnlineSupply: 100}, + {OnlineSupply: 42}, + {OnlineSupply: 9000}, + } + err = aw.AccountsPutOnlineRoundParams(roundParams, basics.Round(3)) + require.NoError(t, err) + + // + // test + // + + // lookup totals + totals, err := oaor.LookupOnlineTotalsHistory(basics.Round(4)) + require.NoError(t, err) + require.Equal(t, basics.MicroAlgos{Raw: uint64(42)}, totals) + + // lookup not found + _, err = oaor.LookupOnlineTotalsHistory(basics.Round(121)) + require.Error(t, err) + require.Equal(t, trackerdb.ErrNotFound, err) +} + +func CustomTestOnlineAccountsDelete(t *customT) { + oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true) + require.NoError(t, err) + + oar, err := t.db.MakeOnlineAccountsOptimizedReader() + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // generate some test data + addrA := RandomAddress() + dataA1 := trackerdb.BaseOnlineAccountData{ + // some value so its NOT empty + BaseVotingData: trackerdb.BaseVotingData{VoteKeyDilution: 1}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(20)}, + RewardsBase: uint64(200), + } + normalizedBalA1 := dataA1.NormalizedOnlineBalance(t.proto) + + _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA1, dataA1, uint64(0), uint64(2)) + require.NoError(t, err) + + // generate some test data + dataA2 := trackerdb.BaseOnlineAccountData{ + // some value so its NOT empty + BaseVotingData: trackerdb.BaseVotingData{VoteKeyDilution: 1}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(100)}, + RewardsBase: uint64(200), + } + normalizedBalA2 := dataA2.NormalizedOnlineBalance(t.proto) + + _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA2, dataA2, uint64(1), uint64(3)) + require.NoError(t, err) + + // generate some test data + addrB := RandomAddress() + dataB1 := trackerdb.BaseOnlineAccountData{ + // some value so its NOT empty + BaseVotingData: trackerdb.BaseVotingData{VoteKeyDilution: 1}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(75)}, + RewardsBase: uint64(200), + } + normalizedBalB1 := dataB1.NormalizedOnlineBalance(t.proto) + + _, err = oaw.InsertOnlineAccount(addrB, normalizedBalB1, dataB1, uint64(2), uint64(3)) + require.NoError(t, err) + + // timeline + // round 0: A touched [0] + // round 1: A touched [1,0] + // round 2: B touched [2] + A remains [1,0] + + // + // the test + // + + // delete before 0 (no changes) + err = aw.OnlineAccountsDelete(basics.Round(0)) + require.NoError(t, err) + + // check they are all there + oas, err := ar.AccountsOnlineTop(basics.Round(0), 0, 10, t.proto) + require.NoError(t, err) + require.Len(t, oas, 1) + require.Equal(t, oas[addrA].MicroAlgos, basics.MicroAlgos{Raw: uint64(20)}) // check item + // read the accounts directly + poaA, err := oar.LookupOnline(addrA, basics.Round(0)) + require.NoError(t, err) + require.NotNil(t, poaA.Ref) // A was found + + // delete before round 1 + err = aw.OnlineAccountsDelete(basics.Round(1)) + require.NoError(t, err) + + // check they are all there + oas, err = ar.AccountsOnlineTop(basics.Round(1), 0, 10, t.proto) + require.NoError(t, err) + require.Len(t, oas, 1) + require.Equal(t, oas[addrA].MicroAlgos, basics.MicroAlgos{Raw: uint64(100)}) // check item + // read the accounts directly + poaA, err = oar.LookupOnline(addrA, basics.Round(1)) + require.NoError(t, err) + require.NotNil(t, poaA.Ref) // A was found + + // delete before round 2 + err = aw.OnlineAccountsDelete(basics.Round(2)) + require.NoError(t, err) + + // check they are all there + oas, err = ar.AccountsOnlineTop(basics.Round(2), 0, 10, t.proto) + require.NoError(t, err) + require.Len(t, oas, 2) + require.Equal(t, oas[addrB].MicroAlgos, basics.MicroAlgos{Raw: uint64(75)}) // check item + // read the accounts directly + poaA, err = oar.LookupOnline(addrA, basics.Round(2)) + require.NoError(t, err) + require.NotNil(t, poaA.Ref) // A is still found, the latest record is kept + require.Equal(t, basics.Round(1), poaA.UpdRound) // the latest we find is at 1 + poaB, err := oar.LookupOnline(addrB, basics.Round(2)) + require.NoError(t, err) + require.NotNil(t, poaB.Ref) // B was found +} + +func CustomTestAccountsOnlineExpired(t *customT) { + oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true) + require.NoError(t, err) + + ar, err := t.db.MakeAccountsReader() + require.NoError(t, err) + + // generate some test data + addrA := RandomAddress() + dataA1 := trackerdb.BaseOnlineAccountData{ + BaseVotingData: trackerdb.BaseVotingData{VoteLastValid: basics.Round(2)}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(20)}, + RewardsBase: uint64(0), + } + normalizedBalA1 := dataA1.NormalizedOnlineBalance(t.proto) + + _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA1, dataA1, uint64(0), uint64(2)) + require.NoError(t, err) + + // generate some test data + dataA2 := trackerdb.BaseOnlineAccountData{ + BaseVotingData: trackerdb.BaseVotingData{VoteLastValid: basics.Round(5)}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(100)}, + RewardsBase: uint64(0), + } + normalizedBalA2 := dataA2.NormalizedOnlineBalance(t.proto) + + _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA2, dataA2, uint64(1), uint64(5)) + require.NoError(t, err) + + // generate some test data + addrB := RandomAddress() + dataB1 := trackerdb.BaseOnlineAccountData{ + // some value so its NOT empty + BaseVotingData: trackerdb.BaseVotingData{VoteLastValid: basics.Round(7)}, + MicroAlgos: basics.MicroAlgos{Raw: uint64(75)}, + RewardsBase: uint64(0), + } + normalizedBalB1 := dataB1.NormalizedOnlineBalance(t.proto) + + _, err = oaw.InsertOnlineAccount(addrB, normalizedBalB1, dataB1, uint64(2), uint64(7)) + require.NoError(t, err) + + // timeline + // round 0: A touched [0] // A expires at 2 + // round 1: A touched [1,0] // A expires at 5 + // round 2: B touched [2] + A remains [1,0] // A expires at 5, B expires at 7 + + // + // the test + // + + // read (none) + expAccts, err := ar.ExpiredOnlineAccountsForRound(basics.Round(0), basics.Round(0), t.proto, 0) + require.NoError(t, err) + require.Empty(t, expAccts) + + // read (at acct round, voteRnd > lastValid) + expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(0), basics.Round(4), t.proto, 0) + require.NoError(t, err) + require.Len(t, expAccts, 1) + require.Equal(t, expAccts[addrA].MicroAlgosWithRewards, basics.MicroAlgos{Raw: uint64(20)}) // check item + + // read (at acct round, voteRnd = lastValid) + expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(0), basics.Round(2), t.proto, 0) + require.NoError(t, err) + require.Empty(t, expAccts) + + // read (at acct round, voteRnd < lastValid) + expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(0), basics.Round(1), t.proto, 0) + require.NoError(t, err) + require.Empty(t, expAccts) + + // read (take latest exp value) + expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(1), basics.Round(4), t.proto, 0) + require.NoError(t, err) + require.Len(t, expAccts, 0) + + // read (all) + expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(3), basics.Round(20), t.proto, 0) + require.Len(t, expAccts, 2) + require.Equal(t, expAccts[addrA].MicroAlgosWithRewards, basics.MicroAlgos{Raw: uint64(100)}) // check item + require.Equal(t, expAccts[addrB].MicroAlgosWithRewards, basics.MicroAlgos{Raw: uint64(75)}) // check item +} diff --git a/ledger/store/trackerdb/testsuite/sqlitedb_test.go b/ledger/store/trackerdb/testsuite/sqlitedb_test.go new file mode 100644 index 0000000000..c4a3d1a7b8 --- /dev/null +++ b/ledger/store/trackerdb/testsuite/sqlitedb_test.go @@ -0,0 +1,43 @@ +// 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 testsuite + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/ledger/store/trackerdb/sqlitedriver" + "github.com/algorand/go-algorand/logging" + "github.com/stretchr/testify/require" +) + +func TestSqliteDB(t *testing.T) { + dbFactory := func(config.ConsensusParams) dbForTests { + // create a tmp dir for the db, the testing runtime will clean it up automatically + fn := fmt.Sprintf("%s/tracker-db.sqlite", t.TempDir()) + db, err := sqlitedriver.Open(fn, false, logging.TestingLog(t)) + require.NoError(t, err) + + seedDb(t, db) + + return db + } + + // run the suite + runGenericTestsWithDB(t, dbFactory) +} diff --git a/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go b/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go new file mode 100644 index 0000000000..bc42141735 --- /dev/null +++ b/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go @@ -0,0 +1,130 @@ +// 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 testsuite + +import ( + "context" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/stretchr/testify/require" +) + +func init() { + // register tests that will run on each KV implementation + registerTest("stateproofs-crud", CustomTestStateproofsReadWrite) + registerTest("stateproofs-query-all", CustomTestStateproofsQueryAll) +} + +func CustomTestStateproofsReadWrite(t *customT) { + spw := t.db.MakeSpVerificationCtxWriter() + spr := t.db.MakeSpVerificationCtxReader() + + // + // test + // + + // store no items + err := spw.StoreSPContexts(context.Background(), []*ledgercore.StateProofVerificationContext{}) + require.NoError(t, err) + + // store some items + vcs := []*ledgercore.StateProofVerificationContext{ + { + LastAttestedRound: basics.Round(0), + OnlineTotalWeight: basics.MicroAlgos{Raw: 42}, + }, + { + LastAttestedRound: basics.Round(1), + OnlineTotalWeight: basics.MicroAlgos{Raw: 100}, + }, + { + LastAttestedRound: basics.Round(2), + OnlineTotalWeight: basics.MicroAlgos{Raw: 200}, + }, + } + err = spw.StoreSPContexts(context.Background(), vcs) + require.NoError(t, err) + + // read non-existing item + vc, err := spr.LookupSPContext(basics.Round(9000)) + require.Error(t, err) + require.Equal(t, trackerdb.ErrNotFound, err) + + // read back a single item + vc, err = spr.LookupSPContext(basics.Round(0)) + require.NoError(t, err) + require.Equal(t, basics.Round(0), vc.LastAttestedRound) // check round is set + require.Equal(t, basics.MicroAlgos{Raw: 42}, vc.OnlineTotalWeight) // check payload is read + + // delete some items + err = spw.DeleteOldSPContexts(context.Background(), basics.Round(1)) + require.NoError(t, err) + + // read delete items + vc, err = spr.LookupSPContext(basics.Round(0)) + require.Error(t, err) + require.Equal(t, trackerdb.ErrNotFound, err) + + // read back remaining items + vc, err = spr.LookupSPContext(basics.Round(1)) + require.NoError(t, err) + require.Equal(t, basics.Round(1), vc.LastAttestedRound) // check round is set + require.Equal(t, basics.MicroAlgos{Raw: 100}, vc.OnlineTotalWeight) // check payload is read + + // read back remaining items + vc, err = spr.LookupSPContext(basics.Round(2)) + require.NoError(t, err) + require.Equal(t, basics.Round(2), vc.LastAttestedRound) // check round is set + require.Equal(t, basics.MicroAlgos{Raw: 200}, vc.OnlineTotalWeight) // check payload is read +} + +func CustomTestStateproofsQueryAll(t *customT) { + spw := t.db.MakeSpVerificationCtxWriter() + spr := t.db.MakeSpVerificationCtxReader() + + // prepare the test with some data + // store some items + vcs := []*ledgercore.StateProofVerificationContext{ + { + LastAttestedRound: basics.Round(0), + OnlineTotalWeight: basics.MicroAlgos{Raw: 42}, + }, + { + LastAttestedRound: basics.Round(1), + OnlineTotalWeight: basics.MicroAlgos{Raw: 100}, + }, + { + LastAttestedRound: basics.Round(2), + OnlineTotalWeight: basics.MicroAlgos{Raw: 200}, + }, + } + err := spw.StoreSPContexts(context.Background(), vcs) + require.NoError(t, err) + + // + // test + // + + // read all data + result, err := spr.GetAllSPContexts(context.Background()) + require.NoError(t, err) + require.Len(t, result, 3) // check all items are present + require.Equal(t, basics.Round(0), result[0].LastAttestedRound) // check first item + require.Equal(t, basics.Round(2), result[2].LastAttestedRound) // check last item +} diff --git a/ledger/store/trackerdb/testsuite/utils_test.go b/ledger/store/trackerdb/testsuite/utils_test.go new file mode 100644 index 0000000000..342b8a5b89 --- /dev/null +++ b/ledger/store/trackerdb/testsuite/utils_test.go @@ -0,0 +1,440 @@ +// 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 testsuite + +// A collection of utility functions and types to write the tests in this module. + +import ( + "bytes" + "context" + "io" + "sort" + "testing" + "time" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/ledger/store/trackerdb/generickv" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/util/db" + "github.com/stretchr/testify/require" +) + +type customT struct { + db dbForTests + proto config.ConsensusParams + *testing.T +} + +type dbForTests interface { + // generickv.KvWrite + // generickv.KvRead + trackerdb.Store +} + +type genericTestEntry struct { + name string + f func(*customT) +} + +// list of tests to be run on each KV DB implementation +var genericTests []genericTestEntry + +// registerTest registers the given test with the suite +func registerTest(name string, f func(*customT)) { + genericTests = append(genericTests, genericTestEntry{name, f}) +} + +// runGenericTestsWithDB runs a generic set of tests on the given database +func runGenericTestsWithDB(t *testing.T, dbFactory func(config.ConsensusParams) (db dbForTests)) { + proto := config.Consensus[protocol.ConsensusCurrentVersion] + for _, entry := range genericTests { + // run each test defined in the suite using the Golang subtest + t.Run(entry.name, func(t *testing.T) { + partitiontest.PartitionTest(t) + // instantiate a new db for each test + entry.f(&customT{dbFactory(proto), proto, t}) + }) + } +} + +func seedDb(t *testing.T, db dbForTests) { + params := trackerdb.Params{InitProto: protocol.ConsensusCurrentVersion} + _, err := db.RunMigrations(context.Background(), params, logging.TestingLog(t), trackerdb.AccountDBVersion) + require.NoError(t, err) +} + +// RandomAddress generates a random address +// +// TODO: this method is defined in ledgertesting, should be moved up to basics so it can be used in more places +func RandomAddress() basics.Address { + var addr basics.Address + crypto.RandBytes(addr[:]) + return addr +} + +type mockDB struct { + kvs kvstore + proto config.ConsensusParams + // use the generickv implementations + trackerdb.Reader + trackerdb.Writer + trackerdb.Catchpoint +} + +func makeMockDB(proto config.ConsensusParams) trackerdb.Store { + kvs := kvstore{data: make(map[string][]byte)} + var db trackerdb.Store + db = &mockDB{ + kvs, + proto, + generickv.MakeReader(&kvs, proto), + generickv.MakeWriter(db, &kvs, &kvs), + generickv.MakeCatchpoint(), + } + return db +} + +func (db *mockDB) SetSynchronousMode(ctx context.Context, mode db.SynchronousMode, fullfsync bool) (err error) { + // TODO + return nil +} + +func (db *mockDB) IsSharedCacheConnection() bool { + return false +} + +// RunMigrations implements trackerdb.Store +func (db *mockDB) RunMigrations(ctx context.Context, params trackerdb.Params, log logging.Logger, targetVersion int32) (mgr trackerdb.InitParams, err error) { + // create a anonym struct that impls the interface for the migration runner + aux := struct { + *mockDB + *kvstore + }{db, &db.kvs} + return generickv.RunMigrations(ctx, aux, params, targetVersion) +} + +// Batch implements trackerdb.Store +func (db *mockDB) Batch(fn trackerdb.BatchFn) (err error) { + return db.BatchContext(context.Background(), fn) +} + +// BatchContext implements trackerdb.Store +func (db *mockDB) BatchContext(ctx context.Context, fn trackerdb.BatchFn) (err error) { + handle, err := db.BeginBatch(ctx) + if err != nil { + return + } + defer handle.Close() + + // run the batch + err = fn(ctx, handle) + if err != nil { + return + } + + // commit the batch + err = handle.Commit() + if err != nil { + return + } + + return err +} + +// BeginBatch implements trackerdb.Store +func (db *mockDB) BeginBatch(ctx context.Context) (trackerdb.Batch, error) { + scope := mockBatch{db} + return &struct { + mockBatch + trackerdb.Writer + }{scope, generickv.MakeWriter(db, &scope, &db.kvs)}, nil +} + +// Snapshot implements trackerdb.Store +func (db *mockDB) Snapshot(fn trackerdb.SnapshotFn) (err error) { + return db.SnapshotContext(context.Background(), fn) +} + +// SnapshotContext implements trackerdb.Store +func (db *mockDB) SnapshotContext(ctx context.Context, fn trackerdb.SnapshotFn) (err error) { + handle, err := db.BeginSnapshot(ctx) + if err != nil { + return + } + defer handle.Close() + + // run the snapshot + err = fn(ctx, handle) + if err != nil { + return + } + + return err +} + +// BeginSnapshot implements trackerdb.Store +func (db *mockDB) BeginSnapshot(ctx context.Context) (trackerdb.Snapshot, error) { + scope := mockSnapshot{db} + return &struct { + mockSnapshot + trackerdb.Reader + }{scope, generickv.MakeReader(&scope, db.proto)}, nil +} + +// Transaction implements trackerdb.Store +func (db *mockDB) Transaction(fn trackerdb.TransactionFn) (err error) { + return db.TransactionContext(context.Background(), fn) +} + +// TransactionContext implements trackerdb.Store +func (db *mockDB) TransactionContext(ctx context.Context, fn trackerdb.TransactionFn) (err error) { + handle, err := db.BeginTransaction(ctx) + if err != nil { + return + } + defer handle.Close() + + // run the transaction + err = fn(ctx, handle) + if err != nil { + return + } + + // commit the transaction + err = handle.Commit() + if err != nil { + return + } + + return err +} + +// BeginTransaction implements trackerdb.Store +func (db *mockDB) BeginTransaction(ctx context.Context) (trackerdb.Transaction, error) { + scope := mockTransaction{db, db.proto} + + return &struct { + mockTransaction + trackerdb.Reader + trackerdb.Writer + trackerdb.Catchpoint + }{scope, generickv.MakeReader(&scope, db.proto), generickv.MakeWriter(db, &scope, &scope), generickv.MakeCatchpoint()}, nil +} + +func (db *mockDB) Vacuum(ctx context.Context) (stats db.VacuumStats, err error) { + // TODO + return stats, nil +} + +func (db *mockDB) ResetToV6Test(ctx context.Context) error { + // TODO + return nil +} + +func (db *mockDB) Close() { + // TODO +} + +type kvstore struct { + data map[string][]byte +} + +func (kvs *kvstore) Set(key, value []byte) error { + kvs.data[string(key)] = value + return nil +} + +func (kvs *kvstore) Get(key []byte) (data []byte, closer io.Closer, err error) { + data, ok := kvs.data[string(key)] + if !ok { + err = trackerdb.ErrNotFound + return + } + return data, io.NopCloser(bytes.NewReader(data)), nil +} + +func (kvs *kvstore) NewIter(low, high []byte, reverse bool) generickv.KvIter { + // + var keys []string + + slow := string(low) + shigh := string(high) + + for k := range kvs.data { + if k > slow && k < shigh { + keys = append(keys, k) + } + } + + sort.Strings(keys) + if reverse { + for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 { + keys[i], keys[j] = keys[j], keys[i] + } + } + + return &mockIter{kvs, keys, -1} +} + +func (kvs *kvstore) Delete(key []byte) error { + delete(kvs.data, string(key)) + return nil +} + +func (kvs *kvstore) DeleteRange(start, end []byte) error { + var toDelete []string + for k := range kvs.data { + if k > string(start) && k < string(end) { + toDelete = append(toDelete, k) + } + } + for i := range toDelete { + delete(kvs.data, toDelete[i]) + } + return nil +} + +type mockSnapshot struct { + db *mockDB +} + +func (ss mockSnapshot) Get(key []byte) (value []byte, closer io.Closer, err error) { + return ss.db.kvs.Get(key) +} + +func (ss mockSnapshot) NewIter(low, high []byte, reverse bool) generickv.KvIter { + return ss.db.kvs.NewIter(low, high, reverse) +} +func (ss mockSnapshot) Close() error { + return nil +} + +type mockTransaction struct { + db *mockDB + proto config.ConsensusParams +} + +func (txs mockTransaction) RunMigrations(ctx context.Context, params trackerdb.Params, log logging.Logger, targetVersion int32) (mgr trackerdb.InitParams, err error) { + // create a anonym struct that impls the interface for the migration runner + aux := struct { + *mockDB + *kvstore + }{txs.db, &txs.db.kvs} + return generickv.RunMigrations(ctx, aux, params, targetVersion) +} + +func (txs mockTransaction) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) { + return time.Now(), nil +} + +func (txs mockTransaction) Close() error { + return nil +} + +func (txs mockTransaction) Commit() error { + return nil +} + +func (txs mockTransaction) Set(key, value []byte) error { + return txs.db.kvs.Set(key, value) +} + +func (txs mockTransaction) Get(key []byte) (value []byte, closer io.Closer, err error) { + return txs.db.kvs.Get(key) +} + +func (txs mockTransaction) NewIter(low, high []byte, reverse bool) generickv.KvIter { + return txs.db.kvs.NewIter(low, high, reverse) +} + +func (txs mockTransaction) Delete(key []byte) error { + return txs.db.kvs.Delete(key) +} + +func (txs mockTransaction) DeleteRange(start, end []byte) error { + return txs.db.kvs.DeleteRange(start, end) +} + +type mockBatch struct { + db *mockDB +} + +func (bs mockBatch) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) { + return time.Now(), nil +} + +func (bs mockBatch) Close() error { + return nil +} + +func (bs mockBatch) Commit() error { + return nil +} + +func (bs mockBatch) Set(key, value []byte) error { + return bs.db.kvs.Set(key, value) +} + +func (bs mockBatch) Delete(key []byte) error { + return bs.db.kvs.Delete(key) +} + +func (bs mockBatch) DeleteRange(start, end []byte) error { + return bs.db.kvs.DeleteRange(start, end) +} + +type mockIter struct { + kvs *kvstore + keys []string + curr int +} + +func (iter *mockIter) Next() bool { + if iter.curr < len(iter.keys)-1 { + iter.curr++ + return true + } + iter.curr = -1 + return false +} + +func (iter *mockIter) Key() []byte { + return []byte(iter.keys[iter.curr]) +} + +func (iter *mockIter) KeySlice() generickv.Slice { + return nil +} + +func (iter *mockIter) Value() ([]byte, error) { + return iter.kvs.data[iter.keys[iter.curr]], nil +} + +func (iter *mockIter) ValueSlice() (generickv.Slice, error) { + return nil, nil +} + +func (iter *mockIter) Valid() bool { + return iter.curr != -1 +} + +func (iter *mockIter) Close() {} From 68243b2a0d33d55d551b4efd9144b0ea20f5a2b0 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 25 Jul 2023 11:33:57 -0400 Subject: [PATCH 02/66] ledger: rename catchpointWriter to catchpointFileWriter for clarity (#5594) --- ...pointwriter.go => catchpointfilewriter.go} | 43 +++++++++---------- ...r_test.go => catchpointfilewriter_test.go} | 22 +++++----- ledger/catchpointtracker.go | 8 ++-- 3 files changed, 36 insertions(+), 37 deletions(-) rename ledger/{catchpointwriter.go => catchpointfilewriter.go} (89%) rename ledger/{catchpointwriter_test.go => catchpointfilewriter_test.go} (97%) diff --git a/ledger/catchpointwriter.go b/ledger/catchpointfilewriter.go similarity index 89% rename from ledger/catchpointwriter.go rename to ledger/catchpointfilewriter.go index 763c6f9a33..140223c3ff 100644 --- a/ledger/catchpointwriter.go +++ b/ledger/catchpointfilewriter.go @@ -46,11 +46,11 @@ const ( SPContextPerCatchpointFile = 70000 ) -// catchpointWriter is the struct managing the persistence of accounts data into the catchpoint file. -// it's designed to work in a step fashion : a caller will call the WriteStep method in a loop until +// catchpointFileWriter is the struct managing the persistence of accounts data into the catchpoint file. +// it's designed to work in a step fashion : a caller will call the FileWriteStep method in a loop until // the writing is complete. It might take multiple steps until the operation is over, and the caller // has the option of throttling the CPU utilization in between the calls. -type catchpointWriter struct { +type catchpointFileWriter struct { ctx context.Context tx trackerdb.SnapshotScope filePath string @@ -106,7 +106,7 @@ func (data catchpointStateProofVerificationContext) ToBeHashed() (protocol.HashI return protocol.StateProofVerCtx, protocol.Encode(&data) } -func makeCatchpointWriter(ctx context.Context, filePath string, tx trackerdb.SnapshotScope, maxResourcesPerChunk int) (*catchpointWriter, error) { +func makeCatchpointFileWriter(ctx context.Context, filePath string, tx trackerdb.SnapshotScope, maxResourcesPerChunk int) (*catchpointFileWriter, error) { aw, err := tx.MakeAccountsReader() if err != nil { return nil, err @@ -136,7 +136,7 @@ func makeCatchpointWriter(ctx context.Context, filePath string, tx trackerdb.Sna } tar := tar.NewWriter(compressor) - res := &catchpointWriter{ + res := &catchpointFileWriter{ ctx: ctx, tx: tx, filePath: filePath, @@ -151,7 +151,7 @@ func makeCatchpointWriter(ctx context.Context, filePath string, tx trackerdb.Sna return res, nil } -func (cw *catchpointWriter) Abort() error { +func (cw *catchpointFileWriter) Abort() error { cw.accountsIterator.Close() cw.tar.Close() cw.compressor.Close() @@ -159,7 +159,7 @@ func (cw *catchpointWriter) Abort() error { return os.Remove(cw.filePath) } -func (cw *catchpointWriter) WriteStateProofVerificationContext(encodedData []byte) error { +func (cw *catchpointFileWriter) FileWriteSPVerificationContext(encodedData []byte) error { err := cw.tar.WriteHeader(&tar.Header{ Name: catchpointSPVerificationFileName, Mode: 0600, @@ -182,16 +182,16 @@ func (cw *catchpointWriter) WriteStateProofVerificationContext(encodedData []byt return nil } -// WriteStep works for a short period of time (determined by stepCtx) to get +// FileWriteStep works for a short period of time (determined by stepCtx) to get // some more data (accounts/resources/kvpairs) by using readDatabaseStep, and // write that data to the open tar file in cw.tar. The writing is done in // asyncWriter, so that it can proceed concurrently with reading the data from // the db. asyncWriter only runs long enough to process the data read during a -// single call to WriteStep, and WriteStep ensures that asyncWriter has finished +// single call to FileWriteStep, and FileWriteStep ensures that asyncWriter has finished // writing by waiting for it in a defer block, collecting any errors that may -// have occurred during writing. Therefore, WriteStep looks like a simple +// have occurred during writing. Therefore, FileWriteStep looks like a simple // synchronous function to its callers. -func (cw *catchpointWriter) WriteStep(stepCtx context.Context) (more bool, err error) { +func (cw *catchpointFileWriter) FileWriteStep(stepCtx context.Context) (more bool, err error) { // have we timed-out / canceled by that point ? if more, err = hasContextDeadlineExceeded(stepCtx); more || err != nil { return @@ -206,15 +206,14 @@ func (cw *catchpointWriter) WriteStep(stepCtx context.Context) (more bool, err e // writerResponse is drained, ensuring any problems from asyncWriter are // noted (and that the writing is done). close(writerRequest) - drain: + + // drain the writerResponse queue for { - select { - case writerError, open := <-writerResponse: - if open { - err = writerError - } else { - break drain - } + writerError, open := <-writerResponse + if open { + err = writerError + } else { + break } } if !more { @@ -228,7 +227,7 @@ func (cw *catchpointWriter) WriteStep(stepCtx context.Context) (more bool, err e } cw.writtenBytes = fileInfo.Size() - // These don't HAVE to be closed, since the "owning" tx will be cmmmitted/rolledback + // These don't HAVE to be closed, since the "owning" tx will be committed/rolledback cw.accountsIterator.Close() if cw.kvRows != nil { cw.kvRows.Close() @@ -276,7 +275,7 @@ func (cw *catchpointWriter) WriteStep(stepCtx context.Context) (more bool, err e } } -func (cw *catchpointWriter) asyncWriter(chunks chan catchpointFileChunkV6, response chan error, chunkNum uint64) { +func (cw *catchpointFileWriter) asyncWriter(chunks chan catchpointFileChunkV6, response chan error, chunkNum uint64) { defer close(response) for chk := range chunks { chunkNum++ @@ -308,7 +307,7 @@ func (cw *catchpointWriter) asyncWriter(chunks chan catchpointFileChunkV6, respo // all of the account chunks first, and then the kv chunks. Even if the accounts // are evenly divisible by BalancesPerCatchpointFileChunk, it must not return an // empty chunk between accounts and kvs. -func (cw *catchpointWriter) readDatabaseStep(ctx context.Context) error { +func (cw *catchpointFileWriter) readDatabaseStep(ctx context.Context) error { if !cw.accountsDone { balances, numAccounts, err := cw.accountsIterator.Next(ctx, BalancesPerCatchpointFileChunk, cw.maxResourcesPerChunk) if err != nil { diff --git a/ledger/catchpointwriter_test.go b/ledger/catchpointfilewriter_test.go similarity index 97% rename from ledger/catchpointwriter_test.go rename to ledger/catchpointfilewriter_test.go index d985317a1c..da5bc3c555 100644 --- a/ledger/catchpointwriter_test.go +++ b/ledger/catchpointfilewriter_test.go @@ -147,7 +147,7 @@ func verifyStateProofVerificationContextWrite(t *testing.T, data []ledgercore.St require.NoError(t, err) err = ml.trackerDB().Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { - writer, err := makeCatchpointWriter(context.Background(), fileName, tx, ResourcesPerCatchpointFileChunk) + writer, err := makeCatchpointFileWriter(context.Background(), fileName, tx, ResourcesPerCatchpointFileChunk) if err != nil { return err } @@ -157,12 +157,12 @@ func verifyStateProofVerificationContextWrite(t *testing.T, data []ledgercore.St } _, encodedData := crypto.EncodeAndHash(catchpointStateProofVerificationContext{Data: rawData}) - err = writer.WriteStateProofVerificationContext(encodedData) + err = writer.FileWriteSPVerificationContext(encodedData) if err != nil { return err } for { - more, err := writer.WriteStep(context.Background()) + more, err := writer.FileWriteStep(context.Background()) require.NoError(t, err) if !more { break @@ -262,7 +262,7 @@ func TestBasicCatchpointWriter(t *testing.T) { fileName := filepath.Join(temporaryDirectory, "15.data") err = ml.trackerDB().Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { - writer, err := makeCatchpointWriter(context.Background(), fileName, tx, ResourcesPerCatchpointFileChunk) + writer, err := makeCatchpointFileWriter(context.Background(), fileName, tx, ResourcesPerCatchpointFileChunk) if err != nil { return err } @@ -271,12 +271,12 @@ func TestBasicCatchpointWriter(t *testing.T) { return err } _, encodedData := crypto.EncodeAndHash(catchpointStateProofVerificationContext{Data: rawData}) - err = writer.WriteStateProofVerificationContext(encodedData) + err = writer.FileWriteSPVerificationContext(encodedData) if err != nil { return err } for { - more, err := writer.WriteStep(context.Background()) + more, err := writer.FileWriteStep(context.Background()) require.NoError(t, err) if !more { break @@ -306,7 +306,7 @@ func testWriteCatchpoint(t *testing.T, rdb trackerdb.Store, datapath string, fil } err := rdb.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { - writer, err := makeCatchpointWriter(context.Background(), datapath, tx, maxResourcesPerChunk) + writer, err := makeCatchpointFileWriter(context.Background(), datapath, tx, maxResourcesPerChunk) if err != nil { return err } @@ -320,12 +320,12 @@ func testWriteCatchpoint(t *testing.T, rdb trackerdb.Store, datapath string, fil return err } _, encodedData := crypto.EncodeAndHash(catchpointStateProofVerificationContext{Data: rawData}) - err = writer.WriteStateProofVerificationContext(encodedData) + err = writer.FileWriteSPVerificationContext(encodedData) if err != nil { return err } for { - more, err := writer.WriteStep(context.Background()) + more, err := writer.FileWriteStep(context.Background()) require.NoError(t, err) if !more { break @@ -433,7 +433,7 @@ func TestCatchpointReadDatabaseOverflowSingleAccount(t *testing.T) { totalAccountsWritten := uint64(0) totalResources := 0 totalChunks := 0 - cw, err := makeCatchpointWriter(context.Background(), catchpointDataFilePath, tx, maxResourcesPerChunk) + cw, err := makeCatchpointFileWriter(context.Background(), catchpointDataFilePath, tx, maxResourcesPerChunk) require.NoError(t, err) ar, err := tx.MakeAccountsReader() @@ -539,7 +539,7 @@ func TestCatchpointReadDatabaseOverflowAccounts(t *testing.T) { totalAccountsWritten := uint64(0) totalResources := 0 - cw, err := makeCatchpointWriter(context.Background(), catchpointDataFilePath, tx, maxResourcesPerChunk) + cw, err := makeCatchpointFileWriter(context.Background(), catchpointDataFilePath, tx, maxResourcesPerChunk) require.NoError(t, err) // repeat this until read all accts diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index 8baccf0752..427494dec1 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -1147,17 +1147,17 @@ func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, account chunkExecutionDuration = shortChunkExecutionDuration } - var catchpointWriter *catchpointWriter + var catchpointWriter *catchpointFileWriter start := time.Now() ledgerGeneratecatchpointCount.Inc(nil) err = ct.dbs.SnapshotContext(ctx, func(dbCtx context.Context, tx trackerdb.SnapshotScope) (err error) { - catchpointWriter, err = makeCatchpointWriter(dbCtx, catchpointDataFilePath, tx, ResourcesPerCatchpointFileChunk) + catchpointWriter, err = makeCatchpointFileWriter(dbCtx, catchpointDataFilePath, tx, ResourcesPerCatchpointFileChunk) if err != nil { return } - err = catchpointWriter.WriteStateProofVerificationContext(encodedSPData) + err = catchpointWriter.FileWriteSPVerificationContext(encodedSPData) if err != nil { return } @@ -1165,7 +1165,7 @@ func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, account for more { stepCtx, stepCancelFunction := context.WithTimeout(dbCtx, chunkExecutionDuration) writeStepStartTime := time.Now() - more, err = catchpointWriter.WriteStep(stepCtx) + more, err = catchpointWriter.FileWriteStep(stepCtx) // accumulate the actual time we've spent writing in this step. catchpointGenerationStats.CPUTime += uint64(time.Since(writeStepStartTime).Nanoseconds()) stepCancelFunction() From 670010aef8f53909a751f1c1cb5f009ace0a289e Mon Sep 17 00:00:00 2001 From: Ignacio Corderi Date: Tue, 25 Jul 2023 18:49:58 +0200 Subject: [PATCH 03/66] ledger: fixes to store interface after kv merge (#5607) --- .../store/trackerdb/generickv/catchpoint.go | 22 ----- ledger/store/trackerdb/generickv/msgp_gen.go | 10 ++ .../generickv/onlineaccounts_reader.go | 21 ++++ ledger/store/trackerdb/generickv/reader.go | 26 +++++ .../trackerdb/testsuite/accounts_kv_test.go | 99 ------------------- .../testsuite/onlineaccounts_kv_test.go | 33 ------- .../store/trackerdb/testsuite/utils_test.go | 5 + 7 files changed, 62 insertions(+), 154 deletions(-) diff --git a/ledger/store/trackerdb/generickv/catchpoint.go b/ledger/store/trackerdb/generickv/catchpoint.go index a4977cda86..99c8a7d955 100644 --- a/ledger/store/trackerdb/generickv/catchpoint.go +++ b/ledger/store/trackerdb/generickv/catchpoint.go @@ -17,8 +17,6 @@ package generickv import ( - "context" - "github.com/algorand/go-algorand/ledger/store/trackerdb" ) @@ -29,16 +27,6 @@ func MakeCatchpoint() trackerdb.Catchpoint { return &catchpoint{} } -// MakeCatchpointPendingHashesIterator implements trackerdb.Catchpoint -func (*catchpoint) MakeCatchpointPendingHashesIterator(hashCount int) trackerdb.CatchpointPendingHashesIter { - panic("unimplemented") -} - -// MakeCatchpointReader implements trackerdb.Catchpoint -func (*catchpoint) MakeCatchpointReader() (trackerdb.CatchpointReader, error) { - panic("unimplemented") -} - // MakeCatchpointReaderWriter implements trackerdb.Catchpoint func (*catchpoint) MakeCatchpointReaderWriter() (trackerdb.CatchpointReaderWriter, error) { panic("unimplemented") @@ -49,16 +37,6 @@ func (*catchpoint) MakeCatchpointWriter() (trackerdb.CatchpointWriter, error) { panic("unimplemented") } -// MakeEncodedAccoutsBatchIter implements trackerdb.Catchpoint -func (*catchpoint) MakeEncodedAccoutsBatchIter() trackerdb.EncodedAccountsBatchIter { - panic("unimplemented") -} - -// MakeKVsIter implements trackerdb.Catchpoint -func (*catchpoint) MakeKVsIter(ctx context.Context) (trackerdb.KVsIter, error) { - panic("unimplemented") -} - // MakeMerkleCommitter implements trackerdb.Catchpoint func (*catchpoint) MakeMerkleCommitter(staging bool) (trackerdb.MerkleCommitter, error) { panic("unimplemented") diff --git a/ledger/store/trackerdb/generickv/msgp_gen.go b/ledger/store/trackerdb/generickv/msgp_gen.go index fd825ca99d..4f5cba66b1 100644 --- a/ledger/store/trackerdb/generickv/msgp_gen.go +++ b/ledger/store/trackerdb/generickv/msgp_gen.go @@ -4,6 +4,8 @@ package generickv import ( "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/data/basics" ) // The following msgp objects are implemented in this file: @@ -14,6 +16,7 @@ import ( // |-----> (*) CanUnmarshalMsg // |-----> (*) Msgsize // |-----> (*) MsgIsZero +// |-----> CreatableEntryMaxSize() // // MarshalMsg implements msgp.Marshaler @@ -144,3 +147,10 @@ func (z *creatableEntry) Msgsize() (s int) { func (z *creatableEntry) MsgIsZero() bool { return ((*z).Ctype.MsgIsZero()) && (len((*z).CreatorAddr) == 0) } + +// MaxSize returns a maximum valid message size for this message type +func CreatableEntryMaxSize() (s int) { + s = 1 + 6 + basics.CreatableTypeMaxSize() + 12 + panic("Unable to determine max size: Byteslice type z.CreatorAddr is unbounded") + return +} diff --git a/ledger/store/trackerdb/generickv/onlineaccounts_reader.go b/ledger/store/trackerdb/generickv/onlineaccounts_reader.go index 029b0317c2..a2ca831603 100644 --- a/ledger/store/trackerdb/generickv/onlineaccounts_reader.go +++ b/ledger/store/trackerdb/generickv/onlineaccounts_reader.go @@ -163,3 +163,24 @@ func (r *accountsReader) LookupOnlineHistory(addr basics.Address) (result []trac return } + +func (r *accountsReader) LookupOnlineRoundParams(rnd basics.Round) (onlineRoundParamsData ledgercore.OnlineRoundParamsData, err error) { + // SQL impl at time of writing: + // + // SELECT data + // FROM onlineroundparamstail + // WHERE rnd=? + + value, closer, err := r.kvr.Get(onlineAccountRoundParamsKey(rnd)) + if err != nil { + return + } + defer closer.Close() + + err = protocol.Decode(value, &onlineRoundParamsData) + if err != nil { + return + } + + return +} diff --git a/ledger/store/trackerdb/generickv/reader.go b/ledger/store/trackerdb/generickv/reader.go index 88761c6013..1532f2fca6 100644 --- a/ledger/store/trackerdb/generickv/reader.go +++ b/ledger/store/trackerdb/generickv/reader.go @@ -17,6 +17,8 @@ package generickv import ( + "context" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/ledger/store/trackerdb" ) @@ -50,3 +52,27 @@ func (r *reader) MakeOnlineAccountsOptimizedReader() (trackerdb.OnlineAccountsRe func (r *reader) MakeSpVerificationCtxReader() trackerdb.SpVerificationCtxReader { return MakeStateproofReader(r) } + +// MakeCatchpointPendingHashesIterator implements trackerdb.Reader +func (r *reader) MakeCatchpointPendingHashesIterator(hashCount int) trackerdb.CatchpointPendingHashesIter { + // TODO: catchpoint + panic("unimplemented") +} + +// MakeCatchpointReader implements trackerdb.Reader +func (r *reader) MakeCatchpointReader() (trackerdb.CatchpointReader, error) { + // TODO: catchpoint + panic("unimplemented") +} + +// MakeEncodedAccoutsBatchIter implements trackerdb.Reader +func (r *reader) MakeEncodedAccoutsBatchIter() trackerdb.EncodedAccountsBatchIter { + // TODO: catchpoint + panic("unimplemented") +} + +// MakeKVsIter implements trackerdb.Reader +func (r *reader) MakeKVsIter(ctx context.Context) (trackerdb.KVsIter, error) { + // TODO: catchpoint + panic("unimplemented") +} diff --git a/ledger/store/trackerdb/testsuite/accounts_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_kv_test.go index 0b8e74a715..1ca7b742a6 100644 --- a/ledger/store/trackerdb/testsuite/accounts_kv_test.go +++ b/ledger/store/trackerdb/testsuite/accounts_kv_test.go @@ -29,7 +29,6 @@ func init() { registerTest("resources-query-all", CustomTestResourcesQueryAll) registerTest("kv-crud", CustomTestAppKVCrud) registerTest("creatables-crud", CustomTestCreatablesCrud) - registerTest("creatables-query-all", CustomTestCreatablesQueryAll) } func CustomTestAccountsCrud(t *customT) { @@ -380,101 +379,3 @@ func CustomTestCreatablesCrud(t *customT) { require.Equal(t, expectedRound, rnd) // db round (this is present even if record does not exist) } - -func CustomTestCreatablesQueryAll(t *customT) { - aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, true) - require.NoError(t, err) - - aor, err := t.db.MakeAccountsOptimizedReader() - require.NoError(t, err) - - aw, err := t.db.MakeAccountsWriter() - require.NoError(t, err) - - // set round to 3 - // Note: this will be used to check that we read the round - expectedRound := basics.Round(3) - err = aw.UpdateAccountsRound(expectedRound) - require.NoError(t, err) - - // - // pre-fill the db with an account for testing - // - - // account A - addrA := RandomAddress() - accDataA := trackerdb.BaseAccountData{RewardsBase: 1000} - refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA) - require.NoError(t, err) - - // resource A-0 - resDataA0 := trackerdb.ResourcesData{} - aidxResA0 := basics.CreatableIndex(0) - _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0) - require.NoError(t, err) - - // resource A-1 - resDataA1 := trackerdb.ResourcesData{} - aidxResA1 := basics.CreatableIndex(1) - _, err = aow.InsertResource(refAccA, aidxResA1, resDataA1) - require.NoError(t, err) - - // resource A-2 - resDataA2 := trackerdb.ResourcesData{} - aidxResA2 := basics.CreatableIndex(2) - _, err = aow.InsertResource(refAccA, aidxResA2, resDataA2) - require.NoError(t, err) - - // creator for A0 - resA0ctype := basics.AssetCreatable - cRefA0, err := aow.InsertCreatable(aidxResA0, resA0ctype, addrA[:]) - require.NoError(t, err) - require.NotNil(t, cRefA0) - - // creator for A1 - resA1ctype := basics.AppCreatable - cRefA1, err := aow.InsertCreatable(aidxResA1, resA1ctype, addrA[:]) - require.NoError(t, err) - require.NotNil(t, cRefA1) - - // creator for A2 - resA2ctype := basics.AppCreatable - cRefA2, err := aow.InsertCreatable(aidxResA2, resA2ctype, addrA[:]) - require.NoError(t, err) - require.NotNil(t, cRefA2) - - // - // test - // - - // filter by type - cls, rnd, err := aor.ListCreatables(basics.CreatableIndex(99), 5, basics.AssetCreatable) - require.NoError(t, err) - require.Len(t, cls, 1) // only one asset - require.Equal(t, aidxResA0, cls[0].Index) // resource A-0 - require.Equal(t, expectedRound, rnd) // db round - - // with multiple results - cls, rnd, err = aor.ListCreatables(basics.CreatableIndex(99), 5, basics.AppCreatable) - require.NoError(t, err) - require.Len(t, cls, 2) // two apps - require.Equal(t, aidxResA2, cls[0].Index) // resource A-2 - require.Equal(t, aidxResA1, cls[1].Index) // resource A-1 - require.Equal(t, expectedRound, rnd) // db round - - // limit results - cls, rnd, err = aor.ListCreatables(basics.CreatableIndex(99), 1, basics.AppCreatable) - require.NoError(t, err) - require.Len(t, cls, 1) // two apps - require.Equal(t, aidxResA2, cls[0].Index) // resource A-2 - require.Equal(t, expectedRound, rnd) // db round - - // filter maxId - cls, rnd, err = aor.ListCreatables(aidxResA2, 10, basics.AppCreatable) - require.NoError(t, err) - require.Len(t, cls, 2) // only one app since to that cidx - require.Equal(t, aidxResA2, cls[0].Index) // resource A-2 (checks for order too) - require.Equal(t, aidxResA1, cls[1].Index) // resource A-1 - require.Equal(t, expectedRound, rnd) // db round - -} diff --git a/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go index ed444794da..9062587b99 100644 --- a/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go +++ b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go @@ -19,7 +19,6 @@ package testsuite import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/protocol" "github.com/stretchr/testify/require" @@ -32,7 +31,6 @@ func init() { registerTest("online-accounts-top", CustomTestAccountsOnlineTop) registerTest("online-accounts-get-by-addr", CustomTestLookupOnlineAccountDataByAddress) registerTest("online-accounts-history", CustomTestOnlineAccountHistory) - registerTest("online-accounts-totals", CustomTestOnlineAccountTotals) registerTest("online-accounts-delete", CustomTestOnlineAccountsDelete) registerTest("online-accounts-expired", CustomTestAccountsOnlineExpired) } @@ -324,37 +322,6 @@ func CustomTestLookupOnlineAccountDataByAddress(t *customT) { require.Equal(t, dataA, badA) } -func CustomTestOnlineAccountTotals(t *customT) { - aw, err := t.db.MakeAccountsWriter() - require.NoError(t, err) - - oaor, err := t.db.MakeOnlineAccountsOptimizedReader() - require.NoError(t, err) - - // generate some test data - roundParams := []ledgercore.OnlineRoundParamsData{ - {OnlineSupply: 100}, - {OnlineSupply: 42}, - {OnlineSupply: 9000}, - } - err = aw.AccountsPutOnlineRoundParams(roundParams, basics.Round(3)) - require.NoError(t, err) - - // - // test - // - - // lookup totals - totals, err := oaor.LookupOnlineTotalsHistory(basics.Round(4)) - require.NoError(t, err) - require.Equal(t, basics.MicroAlgos{Raw: uint64(42)}, totals) - - // lookup not found - _, err = oaor.LookupOnlineTotalsHistory(basics.Round(121)) - require.Error(t, err) - require.Equal(t, trackerdb.ErrNotFound, err) -} - func CustomTestOnlineAccountsDelete(t *customT) { oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true) require.NoError(t, err) diff --git a/ledger/store/trackerdb/testsuite/utils_test.go b/ledger/store/trackerdb/testsuite/utils_test.go index 342b8a5b89..7862155d0b 100644 --- a/ledger/store/trackerdb/testsuite/utils_test.go +++ b/ledger/store/trackerdb/testsuite/utils_test.go @@ -324,6 +324,11 @@ func (ss mockSnapshot) Get(key []byte) (value []byte, closer io.Closer, err erro func (ss mockSnapshot) NewIter(low, high []byte, reverse bool) generickv.KvIter { return ss.db.kvs.NewIter(low, high, reverse) } + +func (ss mockSnapshot) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) { + return time.Now(), nil +} + func (ss mockSnapshot) Close() error { return nil } From d82691daa34160ddc91f9b590ad16ad5f1d6d026 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Wed, 26 Jul 2023 13:46:06 -0400 Subject: [PATCH 04/66] goal: `--all-trace-options` keeps track of everything in exec trace (#5609) --- cmd/goal/clerk.go | 26 +++++++++++++++++------ test/scripts/e2e_subs/e2e-app-simulate.sh | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index d84d43becd..20a6a7c491 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -73,9 +73,11 @@ var ( simulateAllowMoreLogging bool simulateAllowMoreOpcodeBudget bool simulateExtraOpcodeBudget uint64 - simulateEnableRequestTrace bool - simulateStackChange bool - simulateScratchChange bool + + simulateFullTrace bool + simulateEnableRequestTrace bool + simulateStackChange bool + simulateScratchChange bool ) func init() { @@ -164,6 +166,8 @@ func init() { simulateCmd.Flags().BoolVar(&simulateAllowMoreLogging, "allow-more-logging", false, "Lift the limits on log opcode during simulation") simulateCmd.Flags().BoolVar(&simulateAllowMoreOpcodeBudget, "allow-more-opcode-budget", false, "Apply max extra opcode budget for apps per transaction group (default 320000) during simulation") simulateCmd.Flags().Uint64Var(&simulateExtraOpcodeBudget, "extra-opcode-budget", 0, "Apply extra opcode budget for apps per transaction group during simulation") + + simulateCmd.Flags().BoolVar(&simulateFullTrace, "full-trace", false, "Enable all options for simulation execution trace") simulateCmd.Flags().BoolVar(&simulateEnableRequestTrace, "trace", false, "Enable simulation time execution trace of app calls") simulateCmd.Flags().BoolVar(&simulateStackChange, "stack", false, "Report stack change during simulation time") simulateCmd.Flags().BoolVar(&simulateScratchChange, "scratch", false, "Report scratch change during simulation time") @@ -1368,9 +1372,17 @@ func decodeTxnsFromFile(file string) []transactions.SignedTxn { } func traceCmdOptionToSimulateTraceConfigModel() simulation.ExecTraceConfig { - return simulation.ExecTraceConfig{ - Enable: simulateEnableRequestTrace, - Stack: simulateStackChange, - Scratch: simulateScratchChange, + var traceConfig simulation.ExecTraceConfig + if simulateFullTrace { + traceConfig = simulation.ExecTraceConfig{ + Enable: true, + Stack: true, + Scratch: true, + } } + traceConfig.Enable = traceConfig.Enable || simulateEnableRequestTrace + traceConfig.Stack = traceConfig.Stack || simulateStackChange + traceConfig.Scratch = traceConfig.Scratch || simulateScratchChange + + return traceConfig } diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index 0576e8aea3..8dbcc849cf 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -401,7 +401,7 @@ APPID=$(echo "$RES" | grep Created | awk '{ print $6 }') ${gcmd} app call --app-id $APPID --app-arg "int:10" --from $ACCOUNT 2>&1 -o "${TEMPDIR}/stack-and-scratch.tx" ${gcmd} clerk sign -i "${TEMPDIR}/stack-and-scratch.tx" -o "${TEMPDIR}/stack-and-scratch.stx" -RES=$(${gcmd} clerk simulate --trace --stack --scratch -t "${TEMPDIR}/stack-and-scratch.stx") +RES=$(${gcmd} clerk simulate --full-trace -t "${TEMPDIR}/stack-and-scratch.stx") if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then date '+app-simulate-test FAIL the app call for stack and scratch trace should pass %Y%m%d_%H%M%S' From 0903cbfa5bd0c011cba64623f8eb17f44e119f07 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Fri, 28 Jul 2023 14:26:38 -0400 Subject: [PATCH 05/66] tools: option to run block generator test multiple times (#5617) --- tools/block-generator/generator/generate.go | 10 +-- tools/block-generator/runner/run.go | 75 ++++++++++++--------- tools/block-generator/runner/runner.go | 3 +- 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index 1a88ea0fe5..6e9d9fc444 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -288,7 +288,9 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { if round == cachedRound { // one round behind, so write the cached block (if non-empty) - fmt.Printf("Received round request %d, but nextRound=%d. Not finishing round.\n", round, nextRound) + if g.verbose { + fmt.Printf("Received round request %d, but nextRound=%d. Not finishing round.\n", round, nextRound) + } if len(g.latestBlockMsgp) != 0 { // write the msgpack bytes for a block _, err := output.Write(g.latestBlockMsgp) @@ -304,7 +306,7 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { if err != nil { return err } - if g.round == 0 { + if g.verbose && g.round == 0 { fmt.Printf("starting txnCounter: %d\n", g.txnCounter) } minTxnsForBlock := g.minTxnsForBlock(g.round) @@ -335,8 +337,8 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { if err != nil { return fmt.Errorf("failed to evaluate block: %w", err) } - if ledgerTxnCount != g.txnCounter + intra { - return fmt.Errorf("evaluateBlock() txn count mismatches theoretical intra: %d != %d", ledgerTxnCount, g.txnCounter + intra) + if ledgerTxnCount != g.txnCounter+intra { + return fmt.Errorf("evaluateBlock() txn count mismatches theoretical intra: %d != %d", ledgerTxnCount, g.txnCounter+intra) } err = g.ledger.AddValidatedBlock(*vBlock, cert.Certificate) diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 929bdd35e9..626a4493a6 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -54,12 +54,13 @@ type Args struct { RunDuration time.Duration RunnerVerbose bool ConduitLogLevel string - ReportDirectory string + BaseReportDirectory string ResetReportDir bool RunValidation bool KeepDataDir bool GenesisFile string ResetDB bool + Times uint64 } type config struct { @@ -74,43 +75,54 @@ type config struct { // The test will run against the generator configuration file specified by 'args.Path'. // If 'args.Path' is a directory it should contain generator configuration files, a test will run using each file. func Run(args Args) error { - if _, err := os.Stat(args.ReportDirectory); !os.IsNotExist(err) { - if args.ResetReportDir { - fmt.Printf("Resetting existing report directory '%s'\n", args.ReportDirectory) - if err := os.RemoveAll(args.ReportDirectory); err != nil { - return fmt.Errorf("failed to reset report directory: %w", err) + defer fmt.Println("Done running tests!") + for i := uint64(0); i < args.Times; i++ { + reportDirectory := args.BaseReportDirectory + if args.Times != 1 { + fmt.Println("* Starting test", i+1, "of", args.Times, "times") + reportDirectory = fmt.Sprintf("%s_%d", args.BaseReportDirectory, i+1) + } + if _, err := os.Stat(reportDirectory); !os.IsNotExist(err) { + if args.ResetReportDir { + fmt.Printf("Resetting existing report directory '%s'\n", reportDirectory) + if err := os.RemoveAll(reportDirectory); err != nil { + return fmt.Errorf("failed to reset report directory: %w", err) + } + } else { + return fmt.Errorf("report directory '%s' already exists", reportDirectory) } - } else { - return fmt.Errorf("report directory '%s' already exists", args.ReportDirectory) } - } - err := os.Mkdir(args.ReportDirectory, os.ModeDir|os.ModePerm) - if err != nil { - return err - } - - defer fmt.Println("Done running tests!") - return filepath.Walk(args.Path, func(path string, info os.FileInfo, err error) error { + err := os.Mkdir(reportDirectory, os.ModeDir|os.ModePerm) if err != nil { - return fmt.Errorf("run.go Run(): failed to walk path: %w", err) + return err } - // Ignore the directory - if info.IsDir() { - return nil + + err = filepath.Walk(args.Path, func(path string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("run.go Run(): failed to walk path: %w", err) + } + // Ignore the directory + if info.IsDir() { + return nil + } + runnerArgs := args + runnerArgs.Path = path + fmt.Printf("Running test for configuration '%s'\n", path) + return runnerArgs.run(reportDirectory) + }) + if err != nil { + return fmt.Errorf("failed to walk path: %w", err) } - runnerArgs := args - runnerArgs.Path = path - fmt.Printf("Running test for configuration '%s'\n", path) - return runnerArgs.run() - }) + } + return nil } -func (r *Args) run() error { +func (r *Args) run(reportDirectory string) error { baseName := filepath.Base(r.Path) baseNameNoExt := strings.TrimSuffix(baseName, filepath.Ext(baseName)) - reportfile := path.Join(r.ReportDirectory, fmt.Sprintf("%s.report", baseNameNoExt)) - logfile := path.Join(r.ReportDirectory, fmt.Sprintf("%s.conduit-log", baseNameNoExt)) - dataDir := path.Join(r.ReportDirectory, fmt.Sprintf("%s_data", baseNameNoExt)) + reportfile := path.Join(reportDirectory, fmt.Sprintf("%s.report", baseNameNoExt)) + logfile := path.Join(reportDirectory, fmt.Sprintf("%s.conduit-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 { return fmt.Errorf("failed to create data directory: %w", err) @@ -452,6 +464,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) cmd := exec.Command( conduitBinary, "-r", strconv.FormatUint(round, 10), @@ -470,13 +483,13 @@ func startConduit(dataDir string, conduitBinary string, round uint64) (func() er return func() error { if err := cmd.Process.Signal(os.Interrupt); err != nil { - fmt.Printf("failed to kill conduit process: %s\n", err) + fmt.Printf("failed to interrupt conduit process: %s\n", err) if err := cmd.Process.Kill(); err != nil { return fmt.Errorf("failed to kill conduit process: %w", err) } } if err := cmd.Wait(); err != nil { - fmt.Printf("exiting block generator runner: %s\n", err) + fmt.Printf("Conduit exiting: %s\n", err) } return nil }, nil diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go index 1bb3cd9cf6..ff2b49948d 100644 --- a/tools/block-generator/runner/runner.go +++ b/tools/block-generator/runner/runner.go @@ -48,7 +48,7 @@ func init() { RunnerCmd.Flags().Uint64VarP(&runnerArgs.MetricsPort, "metrics-port", "p", 9999, "Port to start the metrics server at.") RunnerCmd.Flags().StringVarP(&runnerArgs.PostgresConnectionString, "postgres-connection-string", "c", "", "Postgres connection string.") RunnerCmd.Flags().DurationVarP(&runnerArgs.RunDuration, "test-duration", "d", 5*time.Minute, "Duration to use for each scenario.") - RunnerCmd.Flags().StringVarP(&runnerArgs.ReportDirectory, "report-directory", "r", "", "Location to place test reports.") + RunnerCmd.Flags().StringVarP(&runnerArgs.BaseReportDirectory, "report-directory", "r", "", "Location to place test reports. If --times is used, this is the prefix for multiple report directories.") RunnerCmd.Flags().BoolVarP(&runnerArgs.RunnerVerbose, "verbose", "v", false, "If set the runner will print debugging information from the generator and ledger.") RunnerCmd.Flags().StringVarP(&runnerArgs.ConduitLogLevel, "conduit-log-level", "l", "error", "LogLevel to use when starting Conduit. [panic, fatal, error, warn, info, debug, trace]") RunnerCmd.Flags().StringVarP(&runnerArgs.CPUProfilePath, "cpuprofile", "", "", "Path where Conduit writes its CPU profile.") @@ -57,6 +57,7 @@ func init() { RunnerCmd.Flags().BoolVarP(&runnerArgs.KeepDataDir, "keep-data-dir", "k", false, "If set the validator will not delete the data directory after tests complete.") RunnerCmd.Flags().StringVarP(&runnerArgs.GenesisFile, "genesis-file", "f", "", "file path to the genesis associated with the db snapshot") RunnerCmd.Flags().BoolVarP(&runnerArgs.ResetDB, "reset-db", "", false, "If set database will be deleted before running tests.") + RunnerCmd.Flags().Uint64VarP(&runnerArgs.Times, "times", "t", 1, "Number of times to run the scenario(s).") RunnerCmd.MarkFlagRequired("scenario") RunnerCmd.MarkFlagRequired("conduit-binary") From 021f0c35664956f24579237e5ded77d55c1468b9 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:27:40 -0400 Subject: [PATCH 06/66] netdeploy: allow simple local net topologies (#5612) --- netdeploy/network.go | 53 ++++++++++------ netdeploy/networkTemplate.go | 4 ++ netdeploy/networkTemplates_test.go | 63 +++++++++++++++++++ netdeploy/network_test.go | 18 ++++-- netdeploy/remote/nodeConfig.go | 1 + .../nettemplates/FiveNodesTwoRelays.json | 56 +++++++++++++++++ 6 files changed, 170 insertions(+), 25 deletions(-) create mode 100644 test/testdata/nettemplates/FiveNodesTwoRelays.json diff --git a/netdeploy/network.go b/netdeploy/network.go index 520f185a3c..02202d5559 100644 --- a/netdeploy/network.go +++ b/netdeploy/network.go @@ -30,8 +30,10 @@ import ( "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/gen" "github.com/algorand/go-algorand/libgoal" + "github.com/algorand/go-algorand/netdeploy/remote" "github.com/algorand/go-algorand/nodecontrol" "github.com/algorand/go-algorand/util" + "golang.org/x/exp/maps" ) const configFileName = "network.json" @@ -43,8 +45,8 @@ type NetworkCfg struct { Name string `json:"Name,omitempty"` // RelayDirs are directories where relays live (where we check for connection IP:Port) // They are stored relative to root dir (e.g. "Primary") - RelayDirs []string `json:"RelayDirs,omitempty"` - TemplateFile string `json:"TemplateFile,omitempty"` // Template file used to create the network + RelayDirs []string `json:"RelayDirs,omitempty"` + Template NetworkTemplate `json:"Template,omitempty"` // Template file used to create the network } // Network represents an instance of a deployed network @@ -108,6 +110,7 @@ func CreateNetworkFromTemplate(name, rootDir string, templateReader io.Reader, b return n, err } n.gen = template.Genesis + n.cfg.Template = template err = n.Save(rootDir) n.SetConsensus(binDir, consensus) @@ -278,9 +281,9 @@ func (n Network) Start(binDir string, redirectOutput bool) error { // Start Prime Relay and get its listening address - var peerAddressListBuilder strings.Builder var relayAddress string var err error + relayNameToAddress := map[string]string{} for _, relayDir := range n.cfg.RelayDirs { nodeFullPath := n.getNodeFullPath(relayDir) nc := nodecontrol.MakeNodeController(binDir, nodeFullPath) @@ -299,15 +302,10 @@ func (n Network) Start(binDir string, redirectOutput bool) error { if err != nil { return err } - - if peerAddressListBuilder.Len() != 0 { - peerAddressListBuilder.WriteString(";") - } - peerAddressListBuilder.WriteString(relayAddress) + relayNameToAddress[relayDir] = relayAddress } - peerAddressList := peerAddressListBuilder.String() - err = n.startNodes(binDir, peerAddressList, redirectOutput) + err = n.startNodes(binDir, relayNameToAddress, redirectOutput) return err } @@ -337,21 +335,38 @@ func (n Network) GetPeerAddresses(binDir string) []string { if err != nil { continue } - if strings.HasPrefix(relayAddress, "http://") { - relayAddress = relayAddress[7:] - } - peerAddresses = append(peerAddresses, relayAddress) + peerAddresses = append(peerAddresses, strings.TrimPrefix(relayAddress, "http://")) } return peerAddresses } -func (n Network) startNodes(binDir, relayAddress string, redirectOutput bool) error { - args := nodecontrol.AlgodStartArgs{ - PeerAddress: relayAddress, - RedirectOutput: redirectOutput, - ExitErrorCallback: n.nodeExitCallback, +func (n Network) startNodes(binDir string, relayNameToAddress map[string]string, redirectOutput bool) error { + allRelaysAddresses := strings.Join(maps.Values(relayNameToAddress), ";") + + nodeConfigToEntry := make(map[string]remote.NodeConfigGoal, len(n.cfg.Template.Nodes)) + for _, n := range n.cfg.Template.Nodes { + nodeConfigToEntry[n.Name] = n } + for _, nodeDir := range n.nodeDirs { + args := nodecontrol.AlgodStartArgs{ + PeerAddress: allRelaysAddresses, + RedirectOutput: redirectOutput, + ExitErrorCallback: n.nodeExitCallback, + } + if n, ok := nodeConfigToEntry[nodeDir]; ok && len(n.PeerList) > 0 { + relayNames := strings.Split(n.PeerList, ";") + var peerAddresses []string + for _, relayName := range relayNames { + relayAddress, ok := relayNameToAddress[relayName] + if !ok { + return fmt.Errorf("relay %s is not defined in the network", relayName) + } + peerAddresses = append(peerAddresses, relayAddress) + } + args.PeerAddress = strings.Join(peerAddresses, ";") + } + nc := nodecontrol.MakeNodeController(binDir, n.getNodeFullPath(nodeDir)) _, err := nc.StartAlgod(args) if err != nil { diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go index 1a140e74d6..2af5d79309 100644 --- a/netdeploy/networkTemplate.go +++ b/netdeploy/networkTemplate.go @@ -235,10 +235,14 @@ func (t NetworkTemplate) Validate() error { } // Follow nodes cannot be relays + // Relays cannot have peer list for _, cfg := range t.Nodes { if cfg.IsRelay && isEnableFollowMode(cfg.ConfigJSONOverride) { return fmt.Errorf("invalid template: follower nodes may not be relays") } + if cfg.IsRelay && len(cfg.PeerList) > 0 { + return fmt.Errorf("invalid template: relays may not have a peer list") + } } if t.Genesis.DevMode && len(t.Nodes) != 1 { diff --git a/netdeploy/networkTemplates_test.go b/netdeploy/networkTemplates_test.go index 63bff9f2d8..f21c37ace9 100644 --- a/netdeploy/networkTemplates_test.go +++ b/netdeploy/networkTemplates_test.go @@ -52,6 +52,7 @@ func TestLoadMissingConfig(t *testing.T) { a := require.New(t) templateDir, err := filepath.Abs("../test/testdata/nettemplates") + a.NoError(err) template, err := loadTemplate(filepath.Join(templateDir, ".json")) a.Error(err) a.Equal(template.Genesis.NetworkName, "") @@ -102,6 +103,68 @@ func TestValidate(t *testing.T) { template, _ = loadTemplate(filepath.Join(templateDir, "TwoNodesOneRelay1000Accounts.json")) err = template.Validate() a.NoError(err) + + templateDir, _ = filepath.Abs("../test/testdata/nettemplates") + template, _ = loadTemplate(filepath.Join(templateDir, "FiveNodesTwoRelays.json")) + err = template.Validate() + a.NoError(err) +} + +func TestPeerListValidate(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + devmodeGenesis := gen.GenesisData{ + Wallets: []gen.WalletData{ + { + Stake: 100, + }, + }, + } + + t.Run("PeerList is optional", func(t *testing.T) { + t.Parallel() + tmpl := NetworkTemplate{ + Genesis: devmodeGenesis, + Nodes: []remote.NodeConfigGoal{ + { + IsRelay: true, + }, + { + IsRelay: false, + }, + }, + } + require.NoError(t, tmpl.Validate()) + }) + + t.Run("Relays cannot have PeerList", func(t *testing.T) { + t.Parallel() + tmpl := NetworkTemplate{ + Genesis: devmodeGenesis, + Nodes: []remote.NodeConfigGoal{ + { + IsRelay: true, + PeerList: "R2", + }, + }, + } + require.ErrorContains(t, tmpl.Validate(), "relays may not have a peer list") + }) + + t.Run("Non-relays might have PeerList", func(t *testing.T) { + t.Parallel() + tmpl := NetworkTemplate{ + Genesis: devmodeGenesis, + Nodes: []remote.NodeConfigGoal{ + { + IsRelay: false, + PeerList: "R2", + }, + }, + } + require.NoError(t, tmpl.Validate()) + }) } func TestDevModeValidate(t *testing.T) { diff --git a/netdeploy/network_test.go b/netdeploy/network_test.go index 6b035547cd..cf3ea427ce 100644 --- a/netdeploy/network_test.go +++ b/netdeploy/network_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/gen" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -34,9 +35,11 @@ func TestSaveNetworkCfg(t *testing.T) { a := require.New(t) cfg := NetworkCfg{ - Name: "testName", - RelayDirs: []string{"testPND"}, - TemplateFile: "testTemplate", + Name: "testName", + RelayDirs: []string{"testPND"}, + Template: NetworkTemplate{ + Genesis: gen.DefaultGenesis, + }, } tmpFolder := t.TempDir() @@ -44,6 +47,7 @@ func TestSaveNetworkCfg(t *testing.T) { err := saveNetworkCfg(cfg, cfgFile) a.Nil(err) cfg1, err := loadNetworkCfg(cfgFile) + a.NoError(err) a.Equal(cfg, cfg1) } @@ -63,9 +67,11 @@ func TestSaveConsensus(t *testing.T) { net := Network{ cfg: NetworkCfg{ - Name: "testName", - RelayDirs: []string{relayDir}, - TemplateFile: "testTemplate", + Name: "testName", + RelayDirs: []string{relayDir}, + Template: NetworkTemplate{ + Genesis: gen.DefaultGenesis, + }, }, nodeDirs: map[string]string{ "node1": nodeDir, diff --git a/netdeploy/remote/nodeConfig.go b/netdeploy/remote/nodeConfig.go index 5a8b3ac436..c3daed6713 100644 --- a/netdeploy/remote/nodeConfig.go +++ b/netdeploy/remote/nodeConfig.go @@ -59,4 +59,5 @@ type NodeConfigGoal struct { Wallets []NodeWalletData DeadlockDetection int `json:"-"` ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete + PeerList string `json:",omitempty"` // Semicolon separated list of peers to connect to. Only applicable for non-relays } diff --git a/test/testdata/nettemplates/FiveNodesTwoRelays.json b/test/testdata/nettemplates/FiveNodesTwoRelays.json new file mode 100644 index 0000000000..5dabb59a1b --- /dev/null +++ b/test/testdata/nettemplates/FiveNodesTwoRelays.json @@ -0,0 +1,56 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "LastPartKeyRound": 5000, + "Wallets": [ + { + "Name": "LargeWallet", + "Stake": 85, + "Online": true + }, + { + "Name": "SmallWallet", + "Stake": 10, + "Online": true + }, + { + "Name": "NonPartWallet", + "Stake": 5, + "Online": true + } + ] + }, + "Nodes": [ + { + "Name": "Relay1", + "IsRelay": true + }, + { + "Name": "Relay2", + "IsRelay": true + }, + { + "Name": "PartNode1", + "Wallets": [{ + "Name": "LargeWallet", + "ParticipationOnly": true + }], + "PeerList": "Relay1;Relay2" + }, + { + "Name": "PartNode2", + "Wallets": [{ + "Name": "SmallWallet", + "ParticipationOnly": true + }], + "PeerList": "Relay2" + }, + { + "Name": "NonPartNode", + "Wallets": [{ + "Name": "NonPartWallet" + }], + "PeerList": "Relay1" + } + ] +} From 0da044b0ba3313ec0203d06d6a335690dcfbf7e1 Mon Sep 17 00:00:00 2001 From: algobarb <78746954+algobarb@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:37:38 -0400 Subject: [PATCH 07/66] CICD: Macos dependencies (#5618) --- scripts/configure_dev.sh | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/scripts/configure_dev.sh b/scripts/configure_dev.sh index 474e869fbf..d40b551474 100755 --- a/scripts/configure_dev.sh +++ b/scripts/configure_dev.sh @@ -79,15 +79,13 @@ elif [ "${OS}" = "darwin" ]; then install_or_upgrade pkg-config install_or_upgrade libtool install_or_upgrade shellcheck - if [ "${CIRCLECI}" != "true" ]; then - install_or_upgrade jq - install_or_upgrade autoconf - install_or_upgrade automake - install_or_upgrade python3 - install_or_upgrade lnav - install_or_upgrade diffutils - lnav -i "$SCRIPTPATH/algorand_node_log.json" - fi + install_or_upgrade jq + install_or_upgrade autoconf + install_or_upgrade automake + install_or_upgrade python3 + install_or_upgrade lnav + install_or_upgrade diffutils + lnav -i "$SCRIPTPATH/algorand_node_log.json" elif [ "${OS}" = "windows" ]; then if ! $msys2 pacman -S --disable-download-timeout --noconfirm git automake autoconf m4 libtool make mingw-w64-x86_64-gcc mingw-w64-x86_64-python mingw-w64-x86_64-jq unzip procps; then echo "Error installing pacman dependencies" From 3f32d61918f7791289c49f4b508120afb65560d9 Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:52:23 -0400 Subject: [PATCH 08/66] p2p: adding peerstore (#5576) --- go.mod | 67 ++++- go.sum | 333 ++++++++++++++++++++++-- network/p2p/peerstore/peerstore.go | 58 +++++ network/p2p/peerstore/peerstore_test.go | 90 +++++++ network/p2p/peerstore/utils.go | 51 ++++ network/p2p/peerstore/utils_test.go | 74 ++++++ tools/block-generator/go.mod | 16 +- tools/block-generator/go.sum | 41 +-- tools/x-repo-types/go.mod | 3 +- tools/x-repo-types/go.sum | 13 +- 10 files changed, 678 insertions(+), 68 deletions(-) create mode 100644 network/p2p/peerstore/peerstore.go create mode 100644 network/p2p/peerstore/peerstore_test.go create mode 100644 network/p2p/peerstore/utils.go create mode 100644 network/p2p/peerstore/utils_test.go diff --git a/go.mod b/go.mod index a69db49ce5..41bbf0f7aa 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/algorand/websocket v1.4.6 github.com/aws/aws-sdk-go v1.33.0 github.com/consensys/gnark-crypto v0.7.0 - github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 + github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c github.com/dchest/siphash v1.2.1 github.com/fatih/color v1.13.0 github.com/getkin/kin-openapi v0.107.0 @@ -24,56 +24,101 @@ require ( github.com/golang/snappy v0.0.4 github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.8.0 + github.com/ipfs/go-datastore v0.6.0 + github.com/ipfs/go-ds-pebble v0.2.4 github.com/jmoiron/sqlx v1.2.0 github.com/karalabe/usb v0.0.2 github.com/labstack/echo/v4 v4.9.1 + github.com/libp2p/go-libp2p v0.29.0 github.com/mattn/go-sqlite3 v1.10.0 - github.com/miekg/dns v1.1.41 + github.com/miekg/dns v1.1.55 + github.com/multiformats/go-multiaddr v0.10.1 github.com/olivere/elastic v6.2.14+incompatible github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.3.0 - github.com/stretchr/testify v1.8.1 - golang.org/x/crypto v0.1.0 - golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 - golang.org/x/sys v0.7.0 - golang.org/x/text v0.9.0 + github.com/stretchr/testify v1.8.4 + golang.org/x/crypto v0.11.0 + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + golang.org/x/sys v0.10.0 + golang.org/x/text v0.11.0 gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 pgregory.net/rapid v0.6.2 ) require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cockroachdb/errors v1.8.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect + github.com/cockroachdb/pebble v0.0.0-20230227185959-8285e8dd5c08 // indirect + github.com/cockroachdb/redact v1.0.8 // indirect + github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/fortytw2/leaktest v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/swag v0.19.5 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/invopop/yaml v0.1.0 // indirect + github.com/ipfs/go-cid v0.4.1 // indirect + github.com/ipfs/go-log/v2 v2.5.1 // indirect + github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/kr/pretty v0.2.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/labstack/gommon v0.4.0 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multicodec v0.9.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-multistream v0.4.1 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/term v0.7.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.12.0 // indirect + golang.org/x/term v0.10.0 // indirect golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect + golang.org/x/tools v0.11.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/blake3 v1.2.1 // indirect ) diff --git a/go.sum b/go.sum index 4e2d9d22ca..7e8656700d 100644 --- a/go.sum +++ b/go.sum @@ -46,17 +46,26 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= +github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/algorand/avm-abi v0.2.0 h1:bkjsG+BOEcxUcnGSALLosmltE0JZdg+ZisXKx0UDX2k= github.com/algorand/avm-abi v0.2.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= github.com/algorand/falcon v0.1.0 h1:xl832kfZ7hHG6B4p90DQynjfKFGbIUgUOnsRiMZXfAo= @@ -81,22 +90,30 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.33.0 h1:Bq5Y6VTLbfnJp1IV8EL/qUU5qO1DYHda/zis/sqevkY= github.com/aws/aws-sdk-go v1.33.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -114,19 +131,49 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4= +github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= +github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= +github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= +github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/cockroachdb/pebble v0.0.0-20230227185959-8285e8dd5c08 h1:acwj2RLsc4NK34fzBUQp5oy4oN+XTbRsEZVCX4puE+U= +github.com/cockroachdb/pebble v0.0.0-20230227185959-8285e8dd5c08/go.mod h1:9lRMC4XN3/BLPtIp6kAKwIaHu369NOf2rMucPzipz50= +github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= +github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/consensys/gnark-crypto v0.7.0 h1:rwdy8+ssmLYRqKp+ryRRgQJl/rCq2uv+n83cOydm5UE= github.com/consensys/gnark-crypto v0.7.0/go.mod h1:KPSuJzyxkJA8xZ/+CV47tyqkr9MmpZA3PXivK4VPrVg= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0= -github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -138,23 +185,40 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/getkin/kin-openapi v0.107.0 h1:bxhL6QArW7BXQj8NjXfIJQy680NsMKd25nwhvpCXchg= github.com/getkin/kin-openapi v0.107.0/go.mod h1:9Dhr+FasATJZjS4iOLvB0hkaxgYdulrNYm2e9epLWOo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= @@ -163,11 +227,19 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.7.0 h1:pGFUjl501gafK9HBt1VGL1KCOd/YhIooID+xgyJCf3g= github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -201,9 +273,12 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -218,6 +293,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -248,8 +324,10 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= @@ -270,9 +348,12 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU= +github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= @@ -281,53 +362,106 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= +github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= +github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= +github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1Ro= +github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= +github.com/ipfs/go-ds-pebble v0.2.4 h1:Y/Pma4GIIxvenyuFU2oPv6eS3Yh7UebaZ6/xqpBJuds= +github.com/ipfs/go-ds-pebble v0.2.4/go.mod h1:gcsz9feIlROgsWzO81ZlrMpWtK2mQ+b/a8F+advE+Hw= +github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= +github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= +github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= +github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/karalabe/usb v0.0.2 h1:M6QQBNxF+CQ8OFvxrT90BA0qBOXymndZnk5q235mFc4= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= +github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= +github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= +github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y= github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/libp2p/go-libp2p v0.29.0 h1:QduJ2XQr/Crg4EnloueWDL0Jj86N3Ezhyyj7XH+XwHI= +github.com/libp2p/go-libp2p v0.29.0/go.mod h1:iNKL7mEnZ9wAss+03IjAwM9ZAQXfVUAPUUmOACQfQ/g= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -335,20 +469,34 @@ github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= +github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -365,16 +513,53 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= +github.com/multiformats/go-multiaddr v0.10.1 h1:HghtFrWyZEPrpTvgAMFJi6gFdgHfs2cb0pyfDsk+lqU= +github.com/multiformats/go-multiaddr v0.10.1/go.mod h1:jLEZsA61rwWNZQTHHnqq2HNa+4os/Hz54eqiRnsRqYQ= +github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= +github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= +github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= +github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= +github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olivere/elastic v6.2.14+incompatible h1:k+KadwNP/dkXE0/eu+T6otk1+5fe0tEpPyQJ4XVm5i8= github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -387,35 +572,68 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -431,14 +649,29 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -457,21 +690,34 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -482,8 +728,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -511,13 +757,18 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -526,6 +777,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -537,6 +789,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -551,10 +804,13 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -572,6 +828,7 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -583,13 +840,14 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -599,14 +857,20 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -620,7 +884,11 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -628,6 +896,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -638,6 +907,7 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -652,11 +922,16 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -666,19 +941,23 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -728,6 +1007,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -771,6 +1052,7 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -834,6 +1116,7 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -875,6 +1158,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -882,15 +1167,21 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 h1:q/fZgS8MMadqFFGa8WL4Oyz+TmjiZfi8UrzWhTl8d5w= gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009/go.mod h1:O0bY1e/dSoxMYZYTHP0SWKxG5EWLEvKR9/cOjWPPMKU= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -905,6 +1196,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw= pgregory.net/rapid v0.6.2/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/network/p2p/peerstore/peerstore.go b/network/p2p/peerstore/peerstore.go new file mode 100644 index 0000000000..03cf49d1f1 --- /dev/null +++ b/network/p2p/peerstore/peerstore.go @@ -0,0 +1,58 @@ +// 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 peerstore + +import ( + "context" + "fmt" + + ds "github.com/ipfs/go-datastore" + pebbledb "github.com/ipfs/go-ds-pebble" + "github.com/libp2p/go-libp2p/core/peer" + libp2p "github.com/libp2p/go-libp2p/core/peerstore" + "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds" +) + +// PeerStore implements libp2p.Peerstore +type PeerStore struct { + libp2p.Peerstore +} + +func initDBStore(path string) (ds.Batching, error) { + store, err := pebbledb.NewDatastore(path, nil) + return store, err +} + +// NewPeerStore creates a new peerstore backed by a datastore. +func NewPeerStore(ctx context.Context, path string, addrInfo []*peer.AddrInfo) (*PeerStore, error) { + datastore, err := initDBStore(path) + if err != nil { + return nil, fmt.Errorf("cannot initialize a peerstore, invalid path for datastore: %w", err) + } + ps, err := pstoreds.NewPeerstore(ctx, datastore, pstoreds.DefaultOpts()) + if err != nil { + return nil, fmt.Errorf("cannot initialize a peerstore: %w", err) + } + + // initialize peerstore with addresses + for i := 0; i < len(addrInfo); i++ { + info := addrInfo[i] + ps.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL) + } + pstore := &PeerStore{ps} + return pstore, nil +} diff --git a/network/p2p/peerstore/peerstore_test.go b/network/p2p/peerstore/peerstore_test.go new file mode 100644 index 0000000000..7219799221 --- /dev/null +++ b/network/p2p/peerstore/peerstore_test.go @@ -0,0 +1,90 @@ +// 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 peerstore + +import ( + "context" + "crypto/rand" + "fmt" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + libp2p_crypto "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + libp2p "github.com/libp2p/go-libp2p/core/peerstore" + "github.com/stretchr/testify/require" +) + +func TestPeerstore(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + peerAddrs := []string{ + "/dns4/ams-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", + "/ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Na", + "/ip6/2604:1380:2000:7a00::1/udp/4001/quic/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj73Nb", + "/ip4/198.51.100.0/tcp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N", + } + + addrInfo, _ := PeerInfoFromAddrs(peerAddrs) + dir := t.TempDir() + ps, err := NewPeerStore(context.Background(), dir, addrInfo) + require.NoError(t, err) + defer ps.Close() + + // peerstore is initialized with addresses + peers := ps.PeersWithAddrs() + require.Equal(t, 4, len(peers)) + + // add peer addresses + var addrs []string + var peerIDS []peer.ID + for i := 0; i < 4; i++ { + privKey, _, err := libp2p_crypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + peerID, err := peer.IDFromPrivateKey(privKey) + require.NoError(t, err) + peerIDS = append(peerIDS, peerID) + maddrStr := fmt.Sprintf("/ip4/1.2.3.4/tcp/%d/p2p/%s", 4000+i, peerID.String()) + addrs = append(addrs, maddrStr) + } + addrInfo, _ = PeerInfoFromAddrs(addrs) + require.NoError(t, err) + for i := 0; i < len(addrInfo); i++ { + info := addrInfo[i] + ps.AddAddrs(info.ID, info.Addrs, libp2p.PermanentAddrTTL) + } + + // peerstore should have 6 peers now + peers = ps.PeersWithAddrs() + require.Equal(t, 8, len(peers)) + + // remove a peer addr + ps.Peerstore.ClearAddrs(peerIDS[0]) + peers = ps.PeersWithAddrs() + require.Equal(t, 7, len(peers)) + +} + +func TestPeerStoreInitErrors(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + // bad datastore path + _, err := NewPeerStore(context.Background(), "//", []*peer.AddrInfo{}) + require.Contains(t, err.Error(), "invalid path for datastore") + +} diff --git a/network/p2p/peerstore/utils.go b/network/p2p/peerstore/utils.go new file mode 100644 index 0000000000..eabcccbdae --- /dev/null +++ b/network/p2p/peerstore/utils.go @@ -0,0 +1,51 @@ +// 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 peerstore + +import ( + "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" +) + +// PeerInfoFromAddrs extracts the AddrInfo from a multiaddr string slice. +func PeerInfoFromAddrs(addrs []string) ([]*peer.AddrInfo, map[string]string) { + var addrInfo []*peer.AddrInfo + malformedAddrs := make(map[string]string) + for _, addr := range addrs { + info, err := PeerInfoFromAddr(addr) + // track malformed addresses + if err != nil { + malformedAddrs[addr] = err.Error() + continue + } + addrInfo = append(addrInfo, info) + } + return addrInfo, malformedAddrs +} + +// PeerInfoFromAddr extracts the AddrInfo from a multiaddr string. +func PeerInfoFromAddr(addr string) (*peer.AddrInfo, error) { + maddr, err := multiaddr.NewMultiaddr(addr) + if err != nil { + return nil, err + } + info, err := peer.AddrInfoFromP2pAddr(maddr) + if err != nil { + return nil, err + } + return info, nil +} diff --git a/network/p2p/peerstore/utils_test.go b/network/p2p/peerstore/utils_test.go new file mode 100644 index 0000000000..c8927d27ee --- /dev/null +++ b/network/p2p/peerstore/utils_test.go @@ -0,0 +1,74 @@ +// 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 peerstore + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestPeerInfoFromAddr(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + testcases := []struct { + name string + addr string + errMsg string + }{ + {"invalid multiaddr string", "/ip4/", "failed to parse multiaddr"}, + {"invalid tcp port", "/ip4/1.2.3.4/tcp/AAAAAAA", "failed to parse port addr"}, + {"unknown protocol", "/ip4/1.2.3.4/tcp/443/AAAAAAA", "unknown protocol"}, + {"badprotocol", "/badprotocol/1.2.3.4/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", "failed to parse multiaddr"}, + {"invalid peerID", "/ip4/1.2.3.4/tcp/4041/p2p/AAAAAAA", "failed to parse p2p addr"}, + {"invalid value for protocol", "/ip4/ams-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", "invalid value \"ams-2.bootstrap.libp2p.io\" for protocol ip4"}, + {"dns4", "/dns4/ams-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", ""}, + {"ipv4", "/ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Na", ""}, + } + for _, tc := range testcases { + tc := tc + t.Run(fmt.Sprintf("test %s", tc.name), func(t *testing.T) { + t.Parallel() + _, err := PeerInfoFromAddr(tc.addr) + if tc.errMsg != "" { + require.Contains(t, err.Error(), tc.errMsg) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestPeerInfoFromAddrs(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + addrs := []string{ + "/ip4/1.2.3.4/tcp/4041/p2p/AAAAAAA", + "/ip4/1.2.3.4/tcp/AAAAAAA", + "/dns4/ams-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", + "/ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Na", + } + peerInfos, malformedAddrs := PeerInfoFromAddrs(addrs) + require.Len(t, peerInfos, 2) + require.Len(t, malformedAddrs, 2) + require.Contains(t, malformedAddrs["/ip4/1.2.3.4/tcp/4041/p2p/AAAAAAA"], "failed to parse multiaddr") + require.Contains(t, malformedAddrs["/ip4/1.2.3.4/tcp/AAAAAAA"], "failed to parse port addr") +} diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 12649d09bc..0d30c99192 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -11,7 +11,7 @@ require ( github.com/algorand/go-deadlock v0.2.2 github.com/lib/pq v1.10.9 github.com/spf13/cobra v1.7.0 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.8.4 gopkg.in/yaml.v3 v3.0.1 ) @@ -26,7 +26,7 @@ require ( github.com/aws/aws-sdk-go v1.33.0 // indirect github.com/consensys/gnark-crypto v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 // indirect + github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/dchest/siphash v1.2.1 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.3.0 // indirect @@ -36,7 +36,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-sqlite3 v1.10.0 // indirect - github.com/miekg/dns v1.1.41 // indirect + github.com/miekg/dns v1.1.55 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olivere/elastic v6.2.14+incompatible // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect @@ -44,9 +44,11 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/crypto v0.1.0 // indirect - golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.12.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/tools v0.11.0 // indirect gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 // indirect ) diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index 86dd5643dc..d3326e54a2 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -27,8 +27,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0= -github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -54,8 +54,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= @@ -79,43 +79,50 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/tools/x-repo-types/go.mod b/tools/x-repo-types/go.mod index 776b76a0ec..8c0146b70c 100644 --- a/tools/x-repo-types/go.mod +++ b/tools/x-repo-types/go.mod @@ -7,13 +7,12 @@ replace github.com/algorand/go-algorand => ../.. require ( github.com/algorand/go-algorand v0.0.0-20230502140608-e24a35add0bb github.com/spf13/cobra v1.7.0 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.4 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tools/x-repo-types/go.sum b/tools/x-repo-types/go.sum index dba66ee268..7929646e02 100644 --- a/tools/x-repo-types/go.sum +++ b/tools/x-repo-types/go.sum @@ -1,12 +1,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -15,15 +12,9 @@ github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 0dbbc915906f94466a25dee24342f90cfc054753 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Fri, 28 Jul 2023 12:24:16 -0700 Subject: [PATCH 09/66] p2p: Add peerID and feature flag (#5591) --- config/localTemplate.go | 14 ++- config/local_defaults.go | 5 +- go.mod | 1 + go.sum | 3 +- installer/config.json.example | 5 +- network/p2p/peerID.go | 90 +++++++++++++++++++ network/p2p/peerID_test.go | 104 +++++++++++++++++++++ test/testdata/configs/config-v29.json | 124 ++++++++++++++++++++++++++ 8 files changed, 342 insertions(+), 4 deletions(-) create mode 100644 network/p2p/peerID.go create mode 100644 network/p2p/peerID_test.go create mode 100644 test/testdata/configs/config-v29.json diff --git a/config/localTemplate.go b/config/localTemplate.go index 47cfa48ca5..f13fc97f1f 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -41,7 +41,7 @@ type Local struct { // Version tracks the current version of the defaults so we can migrate old -> new // This is specifically important whenever we decide to change the default value // for an existing parameter. This field tag must be updated any time we add a new version. - Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28"` + Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29"` // environmental (may be overridden) // When enabled, stores blocks indefinitely, otherwise, only the most recent blocks @@ -520,6 +520,18 @@ type Local struct { // BlockServiceMemCap is the memory capacity in bytes which is allowed for the block service to use for HTTP block requests. // When it exceeds this capacity, it redirects the block requests to a different node BlockServiceMemCap uint64 `version[28]:"500000000"` + + // P2PEnable turns on the peer to peer network + P2PEnable bool `version[29]:"false"` + + // P2PPersistPeerID will write the private key used for the node's PeerID to the P2PPrivateKeyLocation. + // This is only used when P2PEnable is true. If P2PPrivateKey is not specified, it uses the default location. + P2PPersistPeerID bool `version[29]:"true"` + + // P2PPrivateKeyLocation allows the user to specify a custom path to the private key used for the node's PeerID. + // The private key provided must be an ed25519 private key. + // This is only used when P2PEnable is true. If the parameter is not set, it uses the default location. + P2PPrivateKeyLocation string `version[29]:""` } // DNSBootstrapArray returns an array of one or more DNS Bootstrap identifiers diff --git a/config/local_defaults.go b/config/local_defaults.go index a721cd8eb8..a3c55c1e1b 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -20,7 +20,7 @@ package config var defaultLocal = Local{ - Version: 28, + Version: 29, AccountUpdatesStatsInterval: 5000000000, AccountsRebuildSynchronousMode: 1, AgreementIncomingBundlesQueueLength: 15, @@ -106,6 +106,9 @@ var defaultLocal = Local{ OptimizeAccountsDatabaseOnStartup: false, OutgoingMessageFilterBucketCount: 3, OutgoingMessageFilterBucketSize: 128, + P2PEnable: false, + P2PPersistPeerID: true, + P2PPrivateKeyLocation: "", ParticipationKeysRefreshInterval: 60000000000, PeerConnectionsUpdateInterval: 3600, PeerPingPeriodSeconds: 0, diff --git a/go.mod b/go.mod index 41bbf0f7aa..a2694e4a1a 100644 --- a/go.mod +++ b/go.mod @@ -63,6 +63,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect diff --git a/go.sum b/go.sum index 7e8656700d..e6d91c2726 100644 --- a/go.sum +++ b/go.sum @@ -293,7 +293,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/installer/config.json.example b/installer/config.json.example index 7b6ceb5326..8522011ce6 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -1,5 +1,5 @@ { - "Version": 28, + "Version": 29, "AccountUpdatesStatsInterval": 5000000000, "AccountsRebuildSynchronousMode": 1, "AgreementIncomingBundlesQueueLength": 15, @@ -85,6 +85,9 @@ "OptimizeAccountsDatabaseOnStartup": false, "OutgoingMessageFilterBucketCount": 3, "OutgoingMessageFilterBucketSize": 128, + "P2PEnable": false, + "P2PPersistPeerID": true, + "P2PPrivateKeyLocation": "", "ParticipationKeysRefreshInterval": 60000000000, "PeerConnectionsUpdateInterval": 3600, "PeerPingPeriodSeconds": 0, diff --git a/network/p2p/peerID.go b/network/p2p/peerID.go new file mode 100644 index 0000000000..fce32a3afc --- /dev/null +++ b/network/p2p/peerID.go @@ -0,0 +1,90 @@ +// 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 node is the Algorand node itself, with functions exposed to the frontend + +package p2p + +import ( + "crypto/rand" + "fmt" + "github.com/algorand/go-algorand/util" + "os" + "path" + + "github.com/libp2p/go-libp2p/core/crypto" + + "github.com/algorand/go-algorand/config" +) + +// DefaultPrivKeyPath is the default path inside the node's root directory at which the private key +// for p2p identity is found and persisted to when a new one is generated. +const DefaultPrivKeyPath = "peerIDPrivKey.pem" + +// GetPrivKey manages loading and creation of private keys for network PeerIDs +// It prioritizes, in this order: +// 1. user supplied path to privKey +// 2. default path to privKey, +// 3. generating a new privKey. +// +// If a new privKey is generated it will be saved to default path if cfg.P2PPersistPeerID. +func GetPrivKey(cfg config.Local, dataDir string) (crypto.PrivKey, error) { + // if user-supplied, try to load it from there + if cfg.P2PPrivateKeyLocation != "" { + return loadPrivateKeyFromFile(cfg.P2PPrivateKeyLocation) + } + // if a default path key exists load it + defaultPrivKeyPath := path.Join(dataDir, DefaultPrivKeyPath) + if util.FileExists(defaultPrivKeyPath) { + return loadPrivateKeyFromFile(defaultPrivKeyPath) + } + // generate a new key + privKey, err := generatePrivKey() + if err != nil { + return privKey, fmt.Errorf("failed to generate private key %w", err) + } + // if we want persistent PeerID, save the generated PrivKey + if cfg.P2PPersistPeerID { + return privKey, writePrivateKeyToFile(defaultPrivKeyPath, privKey) + } + return privKey, nil +} + +// loadPrivateKeyFromFile attempts to read raw privKey bytes from path +// It only supports Ed25519 keys. +func loadPrivateKeyFromFile(path string) (crypto.PrivKey, error) { + bytes, err := os.ReadFile(path) + if err != nil { + return nil, err + } + // We only support Ed25519 keys + return crypto.UnmarshalEd25519PrivateKey(bytes) +} + +// writePrivateKeyToFile attempts to write raw privKey bytes to path +func writePrivateKeyToFile(path string, privKey crypto.PrivKey) error { + bytes, err := privKey.Raw() + if err != nil { + return err + } + return os.WriteFile(path, bytes, 0600) +} + +// generatePrivKey creates a new Ed25519 key +func generatePrivKey() (crypto.PrivKey, error) { + priv, _, err := crypto.GenerateEd25519Key(rand.Reader) + return priv, err +} diff --git a/network/p2p/peerID_test.go b/network/p2p/peerID_test.go new file mode 100644 index 0000000000..1f15b32141 --- /dev/null +++ b/network/p2p/peerID_test.go @@ -0,0 +1,104 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package p2p + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestGetPrivKeyUserSupplied(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + tempdir := t.TempDir() + cfg := config.GetDefaultLocal() + customPath := path.Join(tempdir, "foobar.pem") + // generate a new private key + privKey, err := generatePrivKey() + require.NoError(t, err) + // write it to our custom path + err = writePrivateKeyToFile(customPath, privKey) + require.NoError(t, err) + cfg.P2PPrivateKeyLocation = customPath + // make sure GetPrivKey loads our custom key + loadedPrivKey, err := GetPrivKey(cfg, tempdir) + assert.NoError(t, err) + assert.Equal(t, privKey, loadedPrivKey) +} + +func TestGetPrivKeyUserSuppliedDoesNotExistErrors(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + tempdir := t.TempDir() + cfg := config.GetDefaultLocal() + cfg.P2PPrivateKeyLocation = path.Join(tempdir, "foobar.pem") + _, err := GetPrivKey(cfg, tempdir) + assert.True(t, os.IsNotExist(err)) +} + +func TestGetPrivKeyDefault(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + tempdir := t.TempDir() + cfg := config.GetDefaultLocal() + + // generate a new private key + privKey, err := generatePrivKey() + require.NoError(t, err) + // write it to the default path + err = writePrivateKeyToFile(path.Join(tempdir, DefaultPrivKeyPath), privKey) + require.NoError(t, err) + // fetch the default private key + loadedPrivKey, err := GetPrivKey(cfg, tempdir) + assert.NoError(t, err) + assert.Equal(t, privKey, loadedPrivKey) +} + +func TestGetPrivKeyUserGeneratedPersisted(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + tempdir := t.TempDir() + cfg := config.GetDefaultLocal() + // get a generated private key + privKey, err := GetPrivKey(cfg, tempdir) + require.NoError(t, err) + // make sure it was persisted + loadedPrivKey, err := loadPrivateKeyFromFile(path.Join(tempdir, DefaultPrivKeyPath)) + assert.NoError(t, err) + assert.Equal(t, privKey, loadedPrivKey) +} + +func TestGetPrivKeyUserGeneratedEphemeral(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + tempdir := t.TempDir() + cfg := config.GetDefaultLocal() + cfg.P2PPersistPeerID = false + // get a generated private key + _, err := GetPrivKey(cfg, tempdir) + require.NoError(t, err) + // make sure it was not persisted + _, err = loadPrivateKeyFromFile(path.Join(tempdir, DefaultPrivKeyPath)) + assert.True(t, os.IsNotExist(err)) +} diff --git a/test/testdata/configs/config-v29.json b/test/testdata/configs/config-v29.json new file mode 100644 index 0000000000..8522011ce6 --- /dev/null +++ b/test/testdata/configs/config-v29.json @@ -0,0 +1,124 @@ +{ + "Version": 29, + "AccountUpdatesStatsInterval": 5000000000, + "AccountsRebuildSynchronousMode": 1, + "AgreementIncomingBundlesQueueLength": 15, + "AgreementIncomingProposalsQueueLength": 50, + "AgreementIncomingVotesQueueLength": 20000, + "AnnounceParticipationKey": true, + "Archival": false, + "BaseLoggerDebugLevel": 4, + "BlockServiceCustomFallbackEndpoints": "", + "BlockServiceMemCap": 500000000, + "BroadcastConnectionsLimit": -1, + "CadaverDirectory": "", + "CadaverSizeTarget": 0, + "CatchpointFileHistoryLength": 365, + "CatchpointInterval": 10000, + "CatchpointTracking": 0, + "CatchupBlockDownloadRetryAttempts": 1000, + "CatchupBlockValidateMode": 0, + "CatchupFailurePeerRefreshRate": 10, + "CatchupGossipBlockFetchTimeoutSec": 4, + "CatchupHTTPBlockFetchTimeoutSec": 4, + "CatchupLedgerDownloadRetryAttempts": 50, + "CatchupParallelBlocks": 16, + "ConnectionsRateLimitingCount": 60, + "ConnectionsRateLimitingWindowSeconds": 1, + "DNSBootstrapID": ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)", + "DNSSecurityFlags": 1, + "DeadlockDetection": 0, + "DeadlockDetectionThreshold": 30, + "DisableLedgerLRUCache": false, + "DisableLocalhostConnectionRateLimit": true, + "DisableNetworking": false, + "DisableOutgoingConnectionThrottling": false, + "EnableAccountUpdatesStats": false, + "EnableAgreementReporting": false, + "EnableAgreementTimeMetrics": false, + "EnableAssembleStats": false, + "EnableBlockService": false, + "EnableBlockServiceFallbackToArchiver": true, + "EnableCatchupFromArchiveServers": false, + "EnableDeveloperAPI": false, + "EnableExperimentalAPI": false, + "EnableFollowMode": false, + "EnableGossipBlockService": true, + "EnableIncomingMessageFilter": false, + "EnableLedgerService": false, + "EnableMetricReporting": false, + "EnableOutgoingNetworkMessageFiltering": true, + "EnablePingHandler": true, + "EnableProcessBlockStats": false, + "EnableProfiler": false, + "EnableRequestLogger": false, + "EnableRuntimeMetrics": false, + "EnableTopAccountsReporting": false, + "EnableTxBacklogRateLimiting": false, + "EnableTxnEvalTracer": false, + "EnableUsageLog": false, + "EnableVerbosedTransactionSyncLogging": false, + "EndpointAddress": "127.0.0.1:0", + "FallbackDNSResolverAddress": "", + "ForceFetchTransactions": false, + "ForceRelayMessages": false, + "GossipFanout": 4, + "HeartbeatUpdateInterval": 600, + "IncomingConnectionsLimit": 2400, + "IncomingMessageFilterBucketCount": 5, + "IncomingMessageFilterBucketSize": 512, + "LedgerSynchronousMode": 2, + "LogArchiveMaxAge": "", + "LogArchiveName": "node.archive.log", + "LogSizeLimit": 1073741824, + "MaxAPIBoxPerApplication": 100000, + "MaxAPIResourcesPerAccount": 100000, + "MaxAcctLookback": 4, + "MaxCatchpointDownloadDuration": 43200000000000, + "MaxConnectionsPerIP": 15, + "MinCatchpointFileDownloadBytesPerSecond": 20480, + "NetAddress": "", + "NetworkMessageTraceServer": "", + "NetworkProtocolVersion": "", + "NodeExporterListenAddress": ":9100", + "NodeExporterPath": "./node_exporter", + "OptimizeAccountsDatabaseOnStartup": false, + "OutgoingMessageFilterBucketCount": 3, + "OutgoingMessageFilterBucketSize": 128, + "P2PEnable": false, + "P2PPersistPeerID": true, + "P2PPrivateKeyLocation": "", + "ParticipationKeysRefreshInterval": 60000000000, + "PeerConnectionsUpdateInterval": 3600, + "PeerPingPeriodSeconds": 0, + "PriorityPeers": {}, + "ProposalAssemblyTime": 500000000, + "PublicAddress": "", + "ReconnectTime": 60000000000, + "ReservedFDs": 256, + "RestConnectionsHardLimit": 2048, + "RestConnectionsSoftLimit": 1024, + "RestReadTimeoutSeconds": 15, + "RestWriteTimeoutSeconds": 120, + "RunHosted": false, + "StorageEngine": "sqlite", + "SuggestedFeeBlockHistory": 3, + "SuggestedFeeSlidingWindowSize": 50, + "TLSCertFile": "", + "TLSKeyFile": "", + "TelemetryToLog": true, + "TransactionSyncDataExchangeRate": 0, + "TransactionSyncSignificantMessageThreshold": 0, + "TxBacklogReservedCapacityPerPeer": 20, + "TxBacklogServiceRateWindowSeconds": 10, + "TxBacklogSize": 26000, + "TxIncomingFilterMaxSize": 500000, + "TxIncomingFilteringFlags": 1, + "TxPoolExponentialIncreaseFactor": 2, + "TxPoolSize": 75000, + "TxSyncIntervalSeconds": 60, + "TxSyncServeResponseSize": 1000000, + "TxSyncTimeoutSeconds": 30, + "UseXForwardedForAddressField": "", + "VerifiedTranscationsCacheSize": 150000 +} From 93d4f58951b29c9f8374ebaf03b9a1e02d78520e Mon Sep 17 00:00:00 2001 From: Ian Suvak Date: Fri, 28 Jul 2023 14:27:29 -0500 Subject: [PATCH 10/66] node: use correct allocbound on netprioResponse Nonce field (#5613) --- node/msgp_gen.go | 28 +++++++++++++------------- node/netprio.go | 4 +++- node/netprio_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ protocol/tags.go | 2 +- 4 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 node/netprio_test.go diff --git a/node/msgp_gen.go b/node/msgp_gen.go index 88c92b37c9..146e8635bb 100644 --- a/node/msgp_gen.go +++ b/node/msgp_gen.go @@ -77,8 +77,8 @@ func (z *netPrioResponse) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "struct-from-array", "Nonce") return } - if zb0003 > netPrioChallengeSize { - err = msgp.ErrOverflow(uint64(zb0003), uint64(netPrioChallengeSize)) + if zb0003 > netPrioChallengeSizeBase64Encoded { + err = msgp.ErrOverflow(uint64(zb0003), uint64(netPrioChallengeSizeBase64Encoded)) return } (*z).Nonce, bts, err = msgp.ReadStringBytes(bts) @@ -117,8 +117,8 @@ func (z *netPrioResponse) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "Nonce") return } - if zb0004 > netPrioChallengeSize { - err = msgp.ErrOverflow(uint64(zb0004), uint64(netPrioChallengeSize)) + if zb0004 > netPrioChallengeSizeBase64Encoded { + err = msgp.ErrOverflow(uint64(zb0004), uint64(netPrioChallengeSizeBase64Encoded)) return } (*z).Nonce, bts, err = msgp.ReadStringBytes(bts) @@ -157,7 +157,7 @@ func (z *netPrioResponse) MsgIsZero() bool { // MaxSize returns a maximum valid message size for this message type func NetPrioResponseMaxSize() (s int) { - s = 1 + 6 + msgp.StringPrefixSize + netPrioChallengeSize + s = 1 + 6 + msgp.StringPrefixSize + netPrioChallengeSizeBase64Encoded return } @@ -260,8 +260,8 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "struct-from-array", "Response", "struct-from-array", "Nonce") return } - if zb0005 > netPrioChallengeSize { - err = msgp.ErrOverflow(uint64(zb0005), uint64(netPrioChallengeSize)) + if zb0005 > netPrioChallengeSizeBase64Encoded { + err = msgp.ErrOverflow(uint64(zb0005), uint64(netPrioChallengeSizeBase64Encoded)) return } (*z).Response.Nonce, bts, err = msgp.ReadStringBytes(bts) @@ -300,8 +300,8 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "struct-from-array", "Response", "Nonce") return } - if zb0006 > netPrioChallengeSize { - err = msgp.ErrOverflow(uint64(zb0006), uint64(netPrioChallengeSize)) + if zb0006 > netPrioChallengeSizeBase64Encoded { + err = msgp.ErrOverflow(uint64(zb0006), uint64(netPrioChallengeSizeBase64Encoded)) return } (*z).Response.Nonce, bts, err = msgp.ReadStringBytes(bts) @@ -384,8 +384,8 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "Response", "struct-from-array", "Nonce") return } - if zb0009 > netPrioChallengeSize { - err = msgp.ErrOverflow(uint64(zb0009), uint64(netPrioChallengeSize)) + if zb0009 > netPrioChallengeSizeBase64Encoded { + err = msgp.ErrOverflow(uint64(zb0009), uint64(netPrioChallengeSizeBase64Encoded)) return } (*z).Response.Nonce, bts, err = msgp.ReadStringBytes(bts) @@ -424,8 +424,8 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "Response", "Nonce") return } - if zb0010 > netPrioChallengeSize { - err = msgp.ErrOverflow(uint64(zb0010), uint64(netPrioChallengeSize)) + if zb0010 > netPrioChallengeSizeBase64Encoded { + err = msgp.ErrOverflow(uint64(zb0010), uint64(netPrioChallengeSizeBase64Encoded)) return } (*z).Response.Nonce, bts, err = msgp.ReadStringBytes(bts) @@ -491,6 +491,6 @@ func (z *netPrioResponseSigned) MsgIsZero() bool { // MaxSize returns a maximum valid message size for this message type func NetPrioResponseSignedMaxSize() (s int) { - s = 1 + 9 + 1 + 6 + msgp.StringPrefixSize + netPrioChallengeSize + 6 + basics.RoundMaxSize() + 7 + basics.AddressMaxSize() + 4 + crypto.OneTimeSignatureMaxSize() + s = 1 + 9 + 1 + 6 + msgp.StringPrefixSize + netPrioChallengeSizeBase64Encoded + 6 + basics.RoundMaxSize() + 7 + basics.AddressMaxSize() + 4 + crypto.OneTimeSignatureMaxSize() return } diff --git a/node/netprio.go b/node/netprio.go index 8b1ab62f18..79a669056a 100644 --- a/node/netprio.go +++ b/node/netprio.go @@ -28,10 +28,12 @@ import ( const netPrioChallengeSize = 32 +const netPrioChallengeSizeBase64Encoded = 44 // 32 * (4/3) rounded up to nearest multiple of 4 -> 44 + type netPrioResponse struct { _struct struct{} `codec:",omitempty,omitemptyarray"` - Nonce string `codec:"Nonce,allocbound=netPrioChallengeSize"` + Nonce string `codec:"Nonce,allocbound=netPrioChallengeSizeBase64Encoded"` } type netPrioResponseSigned struct { diff --git a/node/netprio_test.go b/node/netprio_test.go new file mode 100644 index 0000000000..489099e5c9 --- /dev/null +++ b/node/netprio_test.go @@ -0,0 +1,47 @@ +// 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 node + +import ( + "encoding/base64" + "testing" + + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +// TestBase64AllocboundSize tests that the base64 encoded size of the Nonce is the same as the allocbound +// used on the Nonce field in the struct for netprio response messages. +func TestBase64AllocboundSize(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + require.Equal(t, netPrioChallengeSizeBase64Encoded, base64.StdEncoding.EncodedLen(netPrioChallengeSize)) + node := AlgorandFullNode{} + nonce := node.NewPrioChallenge() + require.Len(t, nonce, netPrioChallengeSizeBase64Encoded) + + npr := netPrioResponse{Nonce: nonce} + e := protocol.Encode(&npr) + + npr2 := netPrioResponse{} + err := protocol.Decode(e, &npr2) + require.NoError(t, err) + require.Equal(t, nonce, npr2.Nonce) + +} diff --git a/protocol/tags.go b/protocol/tags.go index 249da317ef..a9eb2a6bbc 100644 --- a/protocol/tags.go +++ b/protocol/tags.go @@ -60,7 +60,7 @@ const MsgOfInterestTagMaxSize = 45 const MsgDigestSkipTagMaxSize = 69 // NetPrioResponseTagMaxSize is the maximum size of a NetPrioResponseTag message -const NetPrioResponseTagMaxSize = 838 +const NetPrioResponseTagMaxSize = 850 // NetIDVerificationTagMaxSize is the maximum size of a NetIDVerificationTag message const NetIDVerificationTagMaxSize = 215 From 12e537caa4a6afbcaa2c7c10c406c9c47a2eea24 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 28 Jul 2023 15:28:18 -0400 Subject: [PATCH 11/66] ledger: increase locks granularity in lookupWithoutRewards (#5527) --- ledger/acctupdates.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index bb495f3f8f..77e76039c9 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1327,15 +1327,21 @@ func (au *accountUpdates) lookupWithoutRewards(rnd basics.Round, addr basics.Add return } + deltas := au.deltas[:offset] rewardsVersion = au.versions[offset] rewardsLevel = au.roundTotals[offset].RewardsLevel // check if we've had this address modified in the past rounds. ( i.e. if it's in the deltas ) macct, indeltas := au.accounts[addr] + if synchronized { + au.accountsMu.RUnlock() + needUnlock = false + } + if indeltas { // Check if this is the most recent round, in which case, we can // use a cache of the most recent account state. - if offset == uint64(len(au.deltas)) { + if offset == uint64(currentDeltaLen) { return macct.data, rnd, rewardsVersion, rewardsLevel, nil } // the account appears in the deltas, but we don't know if it appears in the @@ -1343,7 +1349,7 @@ func (au *accountUpdates) lookupWithoutRewards(rnd basics.Round, addr basics.Add // backwards to ensure that later updates take priority if present. for offset > 0 { offset-- - d, ok := au.deltas[offset].Accts.GetData(addr) + d, ok := deltas[offset].Accts.GetData(addr) if ok { // the returned validThrough here is not optimal, but it still correct. We could get a more accurate value by scanning // the deltas forward, but this would be time consuming loop, which might not pay off. @@ -1358,6 +1364,10 @@ func (au *accountUpdates) lookupWithoutRewards(rnd basics.Round, addr basics.Add rnd = currentDbRound + basics.Round(currentDeltaLen) } + if synchronized { + au.accountsMu.RLock() + needUnlock = true + } // check the baseAccounts - if macct, has := au.baseAccounts.read(addr); has { // we don't technically need this, since it's already in the baseAccounts, however, writing this over From fd15f4e4eb1941a94fd0660af4c7aef894679d35 Mon Sep 17 00:00:00 2001 From: Ian Suvak Date: Fri, 28 Jul 2023 15:08:06 -0500 Subject: [PATCH 12/66] agreement: add omitempty to MessageHandle in agreement message (#5593) --- agreement/message.go | 2 +- agreement/message_test.go | 2 + agreement/msgp_gen.go | 80 ++++++++++++++++++++++----------------- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/agreement/message.go b/agreement/message.go index d7d52bc8ab..1486dd98e4 100644 --- a/agreement/message.go +++ b/agreement/message.go @@ -28,7 +28,7 @@ type message struct { // this field is for backwards compatibility with crash state serialized using go-codec prior to explicit unexport. // should be removed after the next consensus update. - MessageHandle msgp.Raw + MessageHandle msgp.Raw `codec:"MessageHandle,omitempty"` // explicitly unexport this field since we can't define serializers for interface{} type // the only implementation of this is gossip.messageMetadata which doesn't have exported fields to serialize. messageHandle MessageHandle diff --git a/agreement/message_test.go b/agreement/message_test.go index 3c3ab77f0b..fb0558fc15 100644 --- a/agreement/message_test.go +++ b/agreement/message_test.go @@ -111,6 +111,7 @@ func TestMessageBackwardCompatibility(t *testing.T) { Tag: protocol.ProposalPayloadTag, } + require.Containsf(t, string(encoded), "MessageHandle", "encoded message does not contain MessageHandle field") var m1, m2, m3, m4 message // Both msgp and reflection should decode the message containing old MessageHandle successfully err = protocol.Decode(encoded, &m1) @@ -123,6 +124,7 @@ func TestMessageBackwardCompatibility(t *testing.T) { e1 := protocol.Encode(&m1) e2 := protocol.EncodeReflect(&m2) require.Equal(t, e1, e2) + require.NotContainsf(t, string(e1), "MessageHandle", "encoded message still contains MessageHandle field") err = protocol.DecodeReflect(e1, &m3) require.NoError(t, err) err = protocol.Decode(e2, &m4) diff --git a/agreement/msgp_gen.go b/agreement/msgp_gen.go index ae4e513b00..19a803b759 100644 --- a/agreement/msgp_gen.go +++ b/agreement/msgp_gen.go @@ -2662,40 +2662,52 @@ func FreshnessDataMaxSize() (s int) { // MarshalMsg implements msgp.Marshaler func (z *message) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) - // map header, size 9 - // string "Bundle" - o = append(o, 0x89, 0xa6, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65) - o = (*z).Bundle.MarshalMsg(o) - // string "CompoundMessage" - o = append(o, 0xaf, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x75, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65) - // map header, size 2 - // string "Proposal" - o = append(o, 0x82, 0xa8, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c) - o = (*z).CompoundMessage.Proposal.MarshalMsg(o) - // string "Vote" - o = append(o, 0xa4, 0x56, 0x6f, 0x74, 0x65) - o = (*z).CompoundMessage.Vote.MarshalMsg(o) - // string "MessageHandle" - o = append(o, 0xad, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65) - o = (*z).MessageHandle.MarshalMsg(o) - // string "Proposal" - o = append(o, 0xa8, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c) - o = (*z).Proposal.MarshalMsg(o) - // string "Tag" - o = append(o, 0xa3, 0x54, 0x61, 0x67) - o = (*z).Tag.MarshalMsg(o) - // string "UnauthenticatedBundle" - o = append(o, 0xb5, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65) - o = (*z).UnauthenticatedBundle.MarshalMsg(o) - // string "UnauthenticatedProposal" - o = append(o, 0xb7, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c) - o = (*z).UnauthenticatedProposal.MarshalMsg(o) - // string "UnauthenticatedVote" - o = append(o, 0xb3, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x56, 0x6f, 0x74, 0x65) - o = (*z).UnauthenticatedVote.MarshalMsg(o) - // string "Vote" - o = append(o, 0xa4, 0x56, 0x6f, 0x74, 0x65) - o = (*z).Vote.MarshalMsg(o) + // omitempty: check for empty values + zb0001Len := uint32(9) + var zb0001Mask uint16 /* 11 bits */ + if (*z).MessageHandle.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + // string "Bundle" + o = append(o, 0xa6, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65) + o = (*z).Bundle.MarshalMsg(o) + // string "CompoundMessage" + o = append(o, 0xaf, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x75, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65) + // map header, size 2 + // string "Proposal" + o = append(o, 0x82, 0xa8, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c) + o = (*z).CompoundMessage.Proposal.MarshalMsg(o) + // string "Vote" + o = append(o, 0xa4, 0x56, 0x6f, 0x74, 0x65) + o = (*z).CompoundMessage.Vote.MarshalMsg(o) + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "MessageHandle" + o = append(o, 0xad, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65) + o = (*z).MessageHandle.MarshalMsg(o) + } + // string "Proposal" + o = append(o, 0xa8, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c) + o = (*z).Proposal.MarshalMsg(o) + // string "Tag" + o = append(o, 0xa3, 0x54, 0x61, 0x67) + o = (*z).Tag.MarshalMsg(o) + // string "UnauthenticatedBundle" + o = append(o, 0xb5, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65) + o = (*z).UnauthenticatedBundle.MarshalMsg(o) + // string "UnauthenticatedProposal" + o = append(o, 0xb7, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c) + o = (*z).UnauthenticatedProposal.MarshalMsg(o) + // string "UnauthenticatedVote" + o = append(o, 0xb3, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x56, 0x6f, 0x74, 0x65) + o = (*z).UnauthenticatedVote.MarshalMsg(o) + // string "Vote" + o = append(o, 0xa4, 0x56, 0x6f, 0x74, 0x65) + o = (*z).Vote.MarshalMsg(o) + } return } From cfaeeea27deb6a563d00a9136797b96b28fe953a Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 31 Jul 2023 09:14:14 -0400 Subject: [PATCH 13/66] AVM: Enable pooling of logicsig execution across a group (#5528) --- cmd/goal/clerk.go | 10 +- cmd/tealdbg/debugger_test.go | 16 +- cmd/tealdbg/local.go | 30 +- cmd/tealdbg/localLedger_test.go | 2 +- config/consensus.go | 5 + daemon/algod/api/server/v2/dryrun.go | 10 +- daemon/algod/api/server/v2/dryrun_test.go | 1 + data/transactions/logic/README.md | 18 +- data/transactions/logic/README_in.md | 18 +- data/transactions/logic/assembler.go | 2 +- .../transactions/logic/backwardCompat_test.go | 84 +-- data/transactions/logic/blackbox_test.go | 13 +- data/transactions/logic/debugger_eval_test.go | 6 +- data/transactions/logic/eval.go | 215 +++--- data/transactions/logic/evalAppTxn_test.go | 24 +- data/transactions/logic/evalBench_test.go | 9 +- data/transactions/logic/evalCrypto_test.go | 26 +- data/transactions/logic/evalStateful_test.go | 338 ++++++--- data/transactions/logic/eval_test.go | 710 ++++++++---------- data/transactions/logic/export_test.go | 25 +- data/transactions/logic/fields_test.go | 37 +- data/transactions/logic/opcodes.go | 2 +- data/transactions/logic/resources_test.go | 384 +++++----- data/transactions/logic/tracer_test.go | 12 +- data/transactions/verify/artifact_test.go | 11 +- data/transactions/verify/txn.go | 73 +- data/transactions/verify/txn_test.go | 14 +- data/transactions/verify/verifiedTxnCache.go | 4 +- .../verify/verifiedTxnCache_test.go | 12 +- ledger/apply/application_test.go | 154 ++-- ledger/eval/eval.go | 2 +- ledger/simulation/simulation_eval_test.go | 4 +- test/scripts/e2e_subs/app-group.py | 1 + test/scripts/e2e_subs/app-inner-calls-csp.py | 1 + test/scripts/e2e_subs/app-inner-calls.py | 1 + test/scripts/e2e_subs/app-rekey.py | 1 + test/scripts/e2e_subs/example.py | 2 +- test/scripts/e2e_subs/goal/goal.py | 4 +- test/scripts/e2e_subs/hdr-access.py | 1 + test/scripts/e2e_subs/lsig-budget.py | 87 +++ test/scripts/e2e_subs/shared-resources.py | 1 + 41 files changed, 1242 insertions(+), 1128 deletions(-) create mode 100755 test/scripts/e2e_subs/lsig-budget.py diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 20a6a7c491..80689635bc 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -451,7 +451,7 @@ var sendCmd = &cobra.Command{ CurrentProtocol: proto, }, } - groupCtx, err := verify.PrepareGroupContext([]transactions.SignedTxn{uncheckedTxn}, &blockHeader, nil) + groupCtx, err := verify.PrepareGroupContext([]transactions.SignedTxn{uncheckedTxn}, &blockHeader, nil, nil) if err == nil { err = verify.LogicSigSanityCheck(0, groupCtx) } @@ -852,7 +852,7 @@ var signCmd = &cobra.Command{ } var groupCtx *verify.GroupContext if lsig.Logic != nil { - groupCtx, err = verify.PrepareGroupContext(txnGroup, &contextHdr, nil) + groupCtx, err = verify.PrepareGroupContext(txnGroup, &contextHdr, nil, nil) if err != nil { // this error has to be unsupported protocol reportErrorf("%s: %v", txFilename, err) @@ -1124,7 +1124,6 @@ var dryrunCmd = &cobra.Command{ Long: "Test a TEAL program offline under various conditions and verbosity.", Run: func(cmd *cobra.Command, args []string) { stxns := decodeTxnsFromFile(txFilename) - txgroup := transactions.WrapSignedTxnsWithAD(stxns) proto, params := getProto(protoVersion) if dumpForDryrun { // Write dryrun data to file @@ -1145,15 +1144,14 @@ var dryrunCmd = &cobra.Command{ if timeStamp <= 0 { timeStamp = time.Now().Unix() } - for i, txn := range txgroup { + for i, txn := range stxns { if txn.Lsig.Blank() { continue } if uint64(txn.Lsig.Len()) > params.LogicSigMaxSize { reportErrorf("program size too large: %d > %d", len(txn.Lsig.Logic), params.LogicSigMaxSize) } - ep := logic.NewEvalParams(txgroup, ¶ms, nil) - ep.SigLedger = logic.NoHeaderLedger{} + ep := logic.NewSigEvalParams(stxns, ¶ms, logic.NoHeaderLedger{}) err := logic.CheckSignature(i, ep) if err != nil { reportErrorf("program failed Check: %s", err) diff --git a/cmd/tealdbg/debugger_test.go b/cmd/tealdbg/debugger_test.go index 295a30b005..8bcbd4bbc3 100644 --- a/cmd/tealdbg/debugger_test.go +++ b/cmd/tealdbg/debugger_test.go @@ -103,17 +103,13 @@ func TestDebuggerSimple(t *testing.T) { da := makeTestDbgAdapter(t) debugger.AddAdapter(da) - ep := logic.NewEvalParams(make([]transactions.SignedTxnWithAD, 1), &proto, nil) - ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(debugger) - ep.SigLedger = logic.NoHeaderLedger{} - - source := `int 0 -int 1 -+ -` - ops, err := logic.AssembleStringWithVersion(source, 1) + ops, err := logic.AssembleStringWithVersion("int 0; int 1; +", 1) require.NoError(t, err) - ep.TxnGroup[0].Lsig.Logic = ops.Program + txn := transactions.SignedTxn{} + txn.Lsig.Logic = ops.Program + + ep := logic.NewSigEvalParams([]transactions.SignedTxn{txn}, &proto, logic.NoHeaderLedger{}) + ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(debugger) _, err = logic.EvalSignature(0, ep) require.NoError(t, err) diff --git a/cmd/tealdbg/local.go b/cmd/tealdbg/local.go index d3a9e57fce..cd319cf6d5 100644 --- a/cmd/tealdbg/local.go +++ b/cmd/tealdbg/local.go @@ -237,13 +237,13 @@ type evaluation struct { states AppState } -func (e *evaluation) eval(gi int, ep *logic.EvalParams) (pass bool, err error) { +func (e *evaluation) eval(gi int, sep *logic.EvalParams, aep *logic.EvalParams) (pass bool, err error) { if e.mode == modeStateful { - pass, _, err = e.ba.StatefulEval(gi, ep, e.aidx, e.program) + pass, _, err = e.ba.StatefulEval(gi, aep, e.aidx, e.program) return } - ep.TxnGroup[gi].Lsig.Logic = e.program - return logic.EvalSignature(gi, ep) + sep.TxnGroup[gi].Lsig.Logic = e.program + return logic.EvalSignature(gi, sep) } // LocalRunner runs local eval @@ -531,23 +531,17 @@ func (r *LocalRunner) RunAll() error { return fmt.Errorf("no program to debug") } - configureDebugger := func(ep *logic.EvalParams) { - // Workaround for Go's nil/empty interfaces nil check after nil assignment, i.e. - // r.debugger = nil - // ep.Debugger = r.debugger - // if ep.Debugger != nil // FALSE - if r.debugger != nil { - ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(r.debugger) - } - } - txngroup := transactions.WrapSignedTxnsWithAD(r.txnGroup) failed := 0 start := time.Now() - ep := logic.NewEvalParams(txngroup, &r.proto, &transactions.SpecialAddresses{}) - ep.SigLedger = logic.NoHeaderLedger{} - configureDebugger(ep) + sep := logic.NewSigEvalParams(r.txnGroup, &r.proto, &logic.NoHeaderLedger{}) + aep := logic.NewAppEvalParams(txngroup, &r.proto, &transactions.SpecialAddresses{}) + if r.debugger != nil { + t := logic.MakeEvalTracerDebuggerAdaptor(r.debugger) + sep.Tracer = t + aep.Tracer = t + } var last error for i := range r.runs { @@ -556,7 +550,7 @@ func (r *LocalRunner) RunAll() error { r.debugger.SaveProgram(run.name, run.program, run.source, run.offsetToLine, run.states) } - run.result.pass, run.result.err = run.eval(int(run.groupIndex), ep) + run.result.pass, run.result.err = run.eval(int(run.groupIndex), sep, aep) if run.result.err != nil { failed++ last = run.result.err diff --git a/cmd/tealdbg/localLedger_test.go b/cmd/tealdbg/localLedger_test.go index 623215b463..38258c54d5 100644 --- a/cmd/tealdbg/localLedger_test.go +++ b/cmd/tealdbg/localLedger_test.go @@ -112,7 +112,7 @@ int 2 a.NoError(err) proto := config.Consensus[protocol.ConsensusCurrentVersion] - ep := logic.NewEvalParams([]transactions.SignedTxnWithAD{{SignedTxn: txn}}, &proto, &transactions.SpecialAddresses{}) + ep := logic.NewAppEvalParams([]transactions.SignedTxnWithAD{{SignedTxn: txn}}, &proto, &transactions.SpecialAddresses{}) pass, delta, err := ba.StatefulEval(0, ep, appIdx, program) a.NoError(err) a.True(pass) diff --git a/config/consensus.go b/config/consensus.go index 01d8848634..8c217c18c1 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -112,6 +112,10 @@ type ConsensusParams struct { // rather than check each individual app call is within the budget. EnableAppCostPooling bool + // EnableLogicSigCostPooling specifies LogicSig budgets are pooled across a + // group. The total available is len(group) * LogicSigMaxCost) + EnableLogicSigCostPooling bool + // RewardUnit specifies the number of MicroAlgos corresponding to one reward // unit. // @@ -1356,6 +1360,7 @@ func initConsensusProtocols() { vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} vFuture.LogicSigVersion = 10 // When moving this to a release, put a new higher LogicSigVersion here + vFuture.EnableLogicSigCostPooling = true Consensus[protocol.ConsensusFuture] = vFuture diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index 685ec1e689..5b9309e449 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -399,7 +399,8 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) { proto := config.Consensus[protocol.ConsensusVersion(dr.ProtocolVersion)] txgroup := transactions.WrapSignedTxnsWithAD(dr.Txns) specials := transactions.SpecialAddresses{} - ep := logic.NewEvalParams(txgroup, &proto, &specials) + ep := logic.NewAppEvalParams(txgroup, &proto, &specials) + sep := logic.NewSigEvalParams(dr.Txns, &proto, &dl) origEnableAppCostPooling := proto.EnableAppCostPooling // Enable EnableAppCostPooling so that dryrun @@ -421,11 +422,10 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) { response.Txns = make([]model.DryrunTxnResult, len(dr.Txns)) for ti, stxn := range dr.Txns { var result model.DryrunTxnResult - if len(stxn.Lsig.Logic) > 0 { + if !stxn.Lsig.Blank() { var debug dryrunDebugReceiver - ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(&debug) - ep.SigLedger = &dl - pass, err := logic.EvalSignature(ti, ep) + sep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(&debug) + pass, err := logic.EvalSignature(ti, sep) var messages []string result.Disassembly = debug.lines // Keep backwards compat result.LogicSigDisassembly = &debug.lines // Also add to Lsig specific diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go index 2f43b12a65..99e0601185 100644 --- a/daemon/algod/api/server/v2/dryrun_test.go +++ b/daemon/algod/api/server/v2/dryrun_test.go @@ -363,6 +363,7 @@ func init() { } func checkLogicSigPass(t *testing.T, response *model.DryrunResponse) { + t.Helper() if len(response.Txns) < 1 { t.Error("no response txns") } else if len(response.Txns) == 0 { diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 7c51a9c233..6f4124f048 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -140,13 +140,17 @@ of a contract account. The bytecode plus the length of all Args must add up to no more than 1000 bytes (consensus parameter LogicSigMaxSize). Each opcode has an -associated cost and the program cost must total no more than 20,000 -(consensus parameter LogicSigMaxCost). Most opcodes have a cost of 1, -but a few slow cryptographic operations have a much higher cost. Prior -to v4, the program's cost was estimated as the static sum of all the -opcode costs in the program (whether they were actually executed or -not). Beginning with v4, the program's cost is tracked dynamically, -while being evaluated. If the program exceeds its budget, it fails. +associated cost, usually 1, but a few slow operations have higher +costs. Prior to v4, the program's cost was estimated as the static sum +of all the opcode costs in the program (whether they were actually +executed or not). Beginning with v4, the program's cost is tracked +dynamically, while being evaluated. If the program exceeds its budget, +it fails. + +The total program cost of all Smart Signatures in a group must not +exceed 20,000 (consensus parameter LogicSigMaxCost) times the number +of transactions in the group. + ## Execution Environment for Smart Contracts (Applications) diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index e0cc32c33a..04c67758cc 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -125,13 +125,17 @@ of a contract account. The bytecode plus the length of all Args must add up to no more than 1000 bytes (consensus parameter LogicSigMaxSize). Each opcode has an -associated cost and the program cost must total no more than 20,000 -(consensus parameter LogicSigMaxCost). Most opcodes have a cost of 1, -but a few slow cryptographic operations have a much higher cost. Prior -to v4, the program's cost was estimated as the static sum of all the -opcode costs in the program (whether they were actually executed or -not). Beginning with v4, the program's cost is tracked dynamically, -while being evaluated. If the program exceeds its budget, it fails. +associated cost, usually 1, but a few slow operations have higher +costs. Prior to v4, the program's cost was estimated as the static sum +of all the opcode costs in the program (whether they were actually +executed or not). Beginning with v4, the program's cost is tracked +dynamically, while being evaluated. If the program exceeds its budget, +it fails. + +The total program cost of all Smart Signatures in a group must not +exceed 20,000 (consensus parameter LogicSigMaxCost) times the number +of transactions in the group. + ## Execution Environment for Smart Contracts (Applications) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 20ba9563ac..4939d9ded2 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -2370,7 +2370,7 @@ func (ops *OpStream) resolveLabels() { } // AssemblerDefaultVersion what version of code do we emit by default -// AssemblerDefaultVersion is set to 1 on puprose +// AssemblerDefaultVersion is set to 1 on purpose // to prevent accidental building of v1 official templates with version 2 // because these templates are not aware of rekeying. const AssemblerDefaultVersion = 1 diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index 0e0292ea99..0ae1ba319e 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -22,6 +22,7 @@ import ( "strings" "testing" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/test/partitiontest" @@ -277,23 +278,25 @@ func TestBackwardCompatTEALv1(t *testing.T) { Data: data[:], }) - ep, tx, _ := makeSampleEnvWithVersion(1) + stxn := makeSampleTxn() // RekeyTo disallowed on AVM v0/v1 - tx.RekeyTo = basics.Address{} - - ep.TxnGroup[0].Lsig.Logic = program - ep.TxnGroup[0].Lsig.Args = [][]byte{data[:], sig[:], pk[:], tx.Sender[:], tx.Note} + stxn.Txn.RekeyTo = basics.Address{} + stxn.Lsig.Logic = program + stxn.Lsig.Args = [][]byte{data[:], sig[:], pk[:], stxn.Txn.Sender[:], stxn.Txn.Note} // ensure v1 program runs well on latest evaluator require.Equal(t, uint8(1), program[0]) - // Cost should stay exactly 2140 - ep.Proto.LogicSigMaxCost = 2139 - err = CheckSignature(0, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "static cost") + maxCost := func(cost uint64) protoOpt { + return func(p *config.ConsensusParams) { + p.LogicSigMaxCost = cost + } + } - ep.Proto.LogicSigMaxCost = 2140 + // Cost should stay exactly 2140 for v1, even as future changes are made + err = CheckSignature(0, optSigParams(maxCost(2139), stxn)) + require.ErrorContains(t, err, "static cost") + ep := optSigParams(maxCost(2140), stxn) err = CheckSignature(0, ep) require.NoError(t, err) @@ -305,42 +308,29 @@ func TestBackwardCompatTEALv1(t *testing.T) { require.NoError(t, err) require.True(t, pass) - // Costs for v2 should be higher because of hash opcode cost changes - ep2, tx, _ := makeSampleEnvWithVersion(2) - ep2.Proto.LogicSigMaxCost = 2307 - ep2.TxnGroup[0].Lsig.Args = [][]byte{data[:], sig[:], pk[:], tx.Sender[:], tx.Note} + // Costs for v2 programs should be higher because of hash opcode cost changes // Eval doesn't fail, but it would be ok (better?) if it did - testLogicBytes(t, opsV2.Program, ep2, "static cost", "") - - ep2.Proto.LogicSigMaxCost = 2308 - testLogicBytes(t, opsV2.Program, ep2) + testLogicBytes(t, opsV2.Program, optSigParams(maxCost(2307), stxn), "static cost", "") + testLogicBytes(t, opsV2.Program, optSigParams(maxCost(2308), stxn)) // ensure v0 program runs well on latest evaluator - ep, tx, _ = makeSampleEnv() program[0] = 0 sig = c.Sign(Msg{ ProgramHash: crypto.HashObj(Program(program)), Data: data[:], }) - ep.TxnGroup[0].Lsig.Logic = program - ep.TxnGroup[0].Lsig.Args = [][]byte{data[:], sig[:], pk[:], tx.Sender[:], tx.Note} + stxn.Lsig.Logic = program + stxn.Lsig.Args = [][]byte{data[:], sig[:], pk[:], stxn.Txn.Sender[:], stxn.Txn.Note} // Cost remains the same, because v0 does not get dynamic treatment - ep.Proto.LogicSigMaxCost = 2139 - ep.MinAvmVersion = new(uint64) // Was higher because sample txn has a rekey - testLogicBytes(t, program, ep, "static cost", "") - - ep.Proto.LogicSigMaxCost = 2140 - testLogicBytes(t, program, ep) + testLogicBytes(t, program, optSigParams(maxCost(2139), stxn), "static cost", "") + testLogicBytes(t, program, optSigParams(maxCost(2140), stxn)) // But in v4, cost is now dynamic and exactly 1 less than v2/v3, // because bnz skips "err". It's caught during Eval program[0] = 4 - ep.Proto.LogicSigMaxCost = 2306 - testLogicBytes(t, program, ep, "dynamic cost") - - ep.Proto.LogicSigMaxCost = 2307 - testLogicBytes(t, program, ep) + testLogicBytes(t, program, optSigParams(maxCost(2306), stxn), "dynamic cost") + testLogicBytes(t, program, optSigParams(maxCost(2307), stxn)) } // ensure v2 fields error on pre v2 logicsig version @@ -367,23 +357,22 @@ func TestBackwardCompatGlobalFields(t *testing.T) { ops := testProg(t, text, AssemblerMaxVersion) - ep, _, _ := makeSampleEnvWithVersion(1) - ep.TxnGroup[0].Lsig.Logic = ops.Program + stxn := makeSampleTxn() + stxn.Txn.RekeyTo = basics.Address{} // would mess up minavmversion + stxn.Lsig.Logic = ops.Program + ep := defaultSigParamsWithVersion(1, stxn) _, err := EvalSignature(0, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "greater than protocol supported version") + require.ErrorContains(t, err, "greater than protocol supported version") // check opcodes failures ep.TxnGroup[0].Lsig.Logic[0] = 1 // set version to 1 _, err = EvalSignature(0, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid global field") + require.ErrorContains(t, err, "invalid global field") // check opcodes failures ep.TxnGroup[0].Lsig.Logic[0] = 0 // set version to 0 _, err = EvalSignature(0, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid global field") + require.ErrorContains(t, err, "invalid global field") } } @@ -430,25 +419,22 @@ func TestBackwardCompatTxnFields(t *testing.T) { require.NoError(t, err) } - ep, _, _ := makeSampleEnvWithVersion(1) + ep := defaultSigParamsWithVersion(1) ep.TxnGroup[0].Lsig.Logic = ops.Program // check failure with version check _, err = EvalSignature(0, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "greater than protocol supported version") + require.ErrorContains(t, err, "greater than protocol supported version") // check opcodes failures ops.Program[0] = 1 // set version to 1 _, err = EvalSignature(0, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid txn field") + require.ErrorContains(t, err, "invalid txn field") // check opcodes failures ops.Program[0] = 0 // set version to 0 _, err = EvalSignature(0, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid txn field") + require.ErrorContains(t, err, "invalid txn field") } } } @@ -480,7 +466,7 @@ func TestBackwardCompatAssemble(t *testing.T) { v := v t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { t.Parallel() - testLogic(t, source, v, defaultEvalParams()) + testLogic(t, source, v, nil) }) } } diff --git a/data/transactions/logic/blackbox_test.go b/data/transactions/logic/blackbox_test.go index 79029f2c38..55e7bde4f6 100644 --- a/data/transactions/logic/blackbox_test.go +++ b/data/transactions/logic/blackbox_test.go @@ -36,12 +36,6 @@ func TestNewAppEvalParams(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - params := []config.ConsensusParams{ - {Application: true, MaxAppProgramCost: 700}, - config.Consensus[protocol.ConsensusV29], - config.Consensus[protocol.ConsensusFuture], - } - // Create some sample transactions. The main reason this a blackbox test // (_test package) is to have access to txntest. payment := txntest.Txn{ @@ -78,13 +72,18 @@ func TestNewAppEvalParams(t *testing.T) { {[]transactions.SignedTxnWithAD{appcall1, payment, appcall2}, 2}, } + params := []config.ConsensusParams{ + {Application: true, MaxAppProgramCost: 700}, + config.Consensus[protocol.ConsensusV29], + config.Consensus[protocol.ConsensusFuture], + } for i, param := range params { param := param for j, testCase := range cases { i, j, param, testCase := i, j, param, testCase t.Run(fmt.Sprintf("i=%d,j=%d", i, j), func(t *testing.T) { t.Parallel() - ep := logic.NewEvalParams(testCase.group, ¶m, nil) + ep := logic.NewAppEvalParams(testCase.group, ¶m, nil) require.NotNil(t, ep) require.Equal(t, ep.TxnGroup, testCase.group) require.Equal(t, *ep.Proto, param) diff --git a/data/transactions/logic/debugger_eval_test.go b/data/transactions/logic/debugger_eval_test.go index 4e6b37b859..d99452f103 100644 --- a/data/transactions/logic/debugger_eval_test.go +++ b/data/transactions/logic/debugger_eval_test.go @@ -171,7 +171,7 @@ func TestDebuggerLogicSigEval(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { t.Parallel() testDbg := testDebugger{} - ep := DefaultEvalParams() + ep := DefaultSigParams() ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg) TestLogic(t, testCase.program, AssemblerMaxVersion, ep, testCase.evalProblems...) @@ -191,7 +191,7 @@ func TestDebuggerTopLeveLAppEval(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { t.Parallel() testDbg := testDebugger{} - ep := DefaultEvalParams() + ep := DefaultAppParams() ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg) TestApp(t, testCase.program, ep, testCase.evalProblems...) @@ -291,7 +291,7 @@ func TestCallStackUpdate(t *testing.T) { } testDbg := testDebugger{} - ep := DefaultEvalParams() + ep := DefaultSigParams() ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg) TestLogic(t, TestCallStackProgram, AssemblerMaxVersion, ep) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 3d64c5fbc2..2044301702 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -48,9 +48,6 @@ import ( "github.com/algorand/go-algorand/protocol" ) -// evalMaxVersion is the max version we can interpret and run -const evalMaxVersion = LogicVersion - // The constants below control opcode evaluation and MAY NOT be changed without // gating them by version. Old programs need to retain their old behavior. @@ -181,13 +178,13 @@ func stackValueFromTealValue(tv basics.TealValue) (sv stackValue, err error) { return } -// ComputeMinAvmVersion calculates the minimum safe AVM version that may be +// computeMinAvmVersion calculates the minimum safe AVM version that may be // used by a transaction in this group. It is important to prevent // newly-introduced transaction fields from breaking assumptions made by older // versions of the AVM. If one of the transactions in a group will execute a TEAL // program whose version predates a given field, that field must not be set // anywhere in the transaction group, or the group will be rejected. -func ComputeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 { +func computeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 { var minVersion uint64 for _, txn := range group { if !txn.Txn.RekeyTo.IsZero() { @@ -276,6 +273,8 @@ func RuntimeEvalConstants() EvalConstants { // EvalParams contains data that comes into condition evaluation. type EvalParams struct { + runMode RunMode + Proto *config.ConsensusParams Trace *strings.Builder @@ -292,10 +291,9 @@ type EvalParams struct { // optional tracer Tracer EvalTracer - // MinAvmVersion is the minimum allowed AVM version of this program. - // The program must reject if its version is less than this version. If - // MinAvmVersion is nil, we will compute it ourselves - MinAvmVersion *uint64 + // minAvmVersion is the minimum allowed AVM version of a program to be + // evaluated in TxnGroup. + minAvmVersion uint64 // Amount "overpaid" by the transactions of the group. Often 0. When // positive, it can be spent by inner transactions. Shared across a group's @@ -308,6 +306,9 @@ type EvalParams struct { // Total pool of app call budget in a group transaction (nil before budget pooling enabled) PooledApplicationBudget *int + // Total pool of logicsig budget in a group transaction (nil before lsig pooling enabled) + PooledLogicSigBudget *int + // Total allowable inner txns in a group transaction (nil before inner pooling enabled) pooledAllowedInners *int @@ -355,58 +356,75 @@ func copyWithClearAD(txgroup []transactions.SignedTxnWithAD) []transactions.Sign return copy } -// NewEvalParams creates an EvalParams to use while evaluating a top-level txgroup -func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.ConsensusParams, specials *transactions.SpecialAddresses) *EvalParams { - apps := 0 +// NewSigEvalParams creates an EvalParams to be used while evaluating a group's logicsigs +func NewSigEvalParams(txgroup []transactions.SignedTxn, proto *config.ConsensusParams, ls LedgerForSignature) *EvalParams { + lsigs := 0 for _, tx := range txgroup { - if tx.Txn.Type == protocol.ApplicationCallTx { - apps++ + if !tx.Lsig.Blank() { + lsigs++ } } - // Make a simpler EvalParams that is good enough to evaluate LogicSigs. - if apps == 0 { - return &EvalParams{ - TxnGroup: txgroup, - Proto: proto, - Specials: specials, + var pooledLogicBudget *int + if lsigs > 0 { // don't allocate if no lsigs + if proto.EnableLogicSigCostPooling { + pooledLogicBudget = new(int) + *pooledLogicBudget = len(txgroup) * int(proto.LogicSigMaxCost) } } - minAvmVersion := ComputeMinAvmVersion(txgroup) + withADs := transactions.WrapSignedTxnsWithAD(txgroup) + return &EvalParams{ + runMode: ModeSig, + TxnGroup: withADs, + Proto: proto, + minAvmVersion: computeMinAvmVersion(withADs), + SigLedger: ls, + PooledLogicSigBudget: pooledLogicBudget, + } +} + +// NewAppEvalParams creates an EvalParams to use while evaluating a top-level txgroup. +func NewAppEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.ConsensusParams, specials *transactions.SpecialAddresses) *EvalParams { + apps := 0 + for _, tx := range txgroup { + if tx.Txn.Type == protocol.ApplicationCallTx { + apps++ + } + } var pooledApplicationBudget *int var pooledAllowedInners *int + var credit *uint64 - credit := feeCredit(txgroup, proto.MinTxnFee) + if apps > 0 { // none of these allocations needed if no apps + credit = new(uint64) + *credit = feeCredit(txgroup, proto.MinTxnFee) - if proto.EnableAppCostPooling { - pooledApplicationBudget = new(int) - *pooledApplicationBudget = apps * proto.MaxAppProgramCost - } + if proto.EnableAppCostPooling { + pooledApplicationBudget = new(int) + *pooledApplicationBudget = apps * proto.MaxAppProgramCost + } - if proto.EnableInnerTransactionPooling { - pooledAllowedInners = new(int) - *pooledAllowedInners = proto.MaxTxGroupSize * proto.MaxInnerTransactions + if proto.EnableInnerTransactionPooling { + pooledAllowedInners = new(int) + *pooledAllowedInners = proto.MaxTxGroupSize * proto.MaxInnerTransactions + } } ep := &EvalParams{ + runMode: ModeApp, TxnGroup: copyWithClearAD(txgroup), Proto: proto, Specials: specials, pastScratch: make([]*scratchSpace, len(txgroup)), - MinAvmVersion: &minAvmVersion, - FeeCredit: &credit, + minAvmVersion: computeMinAvmVersion(txgroup), + FeeCredit: credit, PooledApplicationBudget: pooledApplicationBudget, pooledAllowedInners: pooledAllowedInners, appAddrCache: make(map[basics.AppIndex]basics.Address), EvalConstants: RuntimeEvalConstants(), } - // resources are computed after ep is constructed because app addresses are - // calculated there, and we'd like to use the caching mechanism built into - // the EvalParams. Perhaps we can make the computation even lazier, so it is - // only computed if needed. - ep.available = ep.computeAvailability() return ep } @@ -444,12 +462,12 @@ func feeCredit(txgroup []transactions.SignedTxnWithAD, minFee uint64) uint64 { // NewInnerEvalParams creates an EvalParams to be used while evaluating an inner group txgroup func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) *EvalParams { - minAvmVersion := ComputeMinAvmVersion(txg) + minAvmVersion := computeMinAvmVersion(txg) // Can't happen currently, since earliest inner callable version is higher // than any minimum imposed otherwise. But is correct to inherit a stronger // restriction from above, in case of future restriction. - if minAvmVersion < *caller.MinAvmVersion { - minAvmVersion = *caller.MinAvmVersion + if minAvmVersion < caller.minAvmVersion { + minAvmVersion = caller.minAvmVersion } // Unlike NewEvalParams, do not add fee credit here. opTxSubmit has already done so. @@ -463,6 +481,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) } ep := &EvalParams{ + runMode: ModeApp, Proto: caller.Proto, Trace: caller.Trace, TxnGroup: txg, @@ -471,7 +490,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) SigLedger: caller.SigLedger, Ledger: caller.Ledger, Tracer: caller.Tracer, - MinAvmVersion: &minAvmVersion, + minAvmVersion: minAvmVersion, FeeCredit: caller.FeeCredit, Specials: caller.Specials, PooledApplicationBudget: caller.PooledApplicationBudget, @@ -534,19 +553,25 @@ func (ep *EvalParams) log() logging.Logger { // package. For example, after a acfg transaction is processed, the AD created // by the acfg is added to the EvalParams this way. func (ep *EvalParams) RecordAD(gi int, ad transactions.ApplyData) { - if ep.available == nil { - // This is a simplified ep. It won't be used for app evaluation, and - // shares the TxnGroup memory with the caller. Don't touch anything! - return + if ep.runMode == ModeSig { + // We should not be touching a signature mode EvalParams as it shares + // memory with its caller. LogicSigs are supposed to be stateless! + panic("RecordAD called in signature mode") } ep.TxnGroup[gi].ApplyData = ad if aid := ad.ConfigAsset; aid != 0 { + if ep.available == nil { // here, and below, we may need to make `ep.available` + ep.available = ep.computeAvailability() + } if ep.available.createdAsas == nil { ep.available.createdAsas = make(map[basics.AssetIndex]struct{}) } ep.available.createdAsas[aid] = struct{}{} } if aid := ad.ApplicationID; aid != 0 { + if ep.available == nil { + ep.available = ep.computeAvailability() + } if ep.available.createdApps == nil { ep.available.createdApps = make(map[basics.AppIndex]struct{}) } @@ -927,6 +952,9 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam if aid == 0 { return false, nil, errors.New("0 appId in contract eval") } + if params.runMode != ModeApp { + return false, nil, fmt.Errorf("attempt to evaluate a contract with %s mode EvalParams", params.runMode) + } cx := EvalContext{ EvalParams: params, runMode: ModeApp, @@ -941,6 +969,10 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam } } + if cx.EvalParams.available == nil { + cx.EvalParams.available = cx.EvalParams.computeAvailability() + } + // If this is a creation... if cx.txn.Txn.ApplicationID == 0 { // make any "0 index" box refs available now that we have an appID. @@ -1030,6 +1062,9 @@ func EvalSignatureFull(gi int, params *EvalParams) (bool, *EvalContext, error) { if params.SigLedger == nil { return false, nil, errors.New("no sig ledger in signature eval") } + if params.runMode != ModeSig { + return false, nil, fmt.Errorf("attempt to evaluate a signature with %s mode EvalParams", params.runMode) + } cx := EvalContext{ EvalParams: params, runMode: ModeSig, @@ -1071,21 +1106,16 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) { } }() - // Avoid returning for any reason until after cx.debugState is setup. That - // require cx to be minimally setup, too. - - version, vlen, verr := versionCheck(program, cx.EvalParams) - // defer verr check until after cx and debugState is setup - - cx.version = version - cx.pc = vlen // 16 is chosen to avoid growth for small programs, and so that repeated // doublings lead to a number just a bit above 1000, the max stack height. cx.Stack = make([]stackValue, 0, 16) - cx.program = program cx.txn.EvalDelta.GlobalDelta = basics.StateDelta{} cx.txn.EvalDelta.LocalDeltas = make(map[uint64]basics.StateDelta) + // We get the error here, but defer reporting so that the Tracer can be + // called with an basically initialized cx. + verr := cx.begin(program) + if cx.Tracer != nil { cx.Tracer.BeforeProgram(cx) @@ -1184,20 +1214,16 @@ func check(program []byte, params *EvalParams, mode RunMode) (err error) { return errLogicSigNotSupported } - version, vlen, err := versionCheck(program, params) - if err != nil { - return err - } - var cx EvalContext - cx.version = version - cx.pc = vlen cx.EvalParams = params cx.runMode = mode - cx.program = program cx.branchTargets = make([]bool, len(program)+1) // teal v2 allowed jumping to the end of the prog cx.instructionStarts = make([]bool, len(program)+1) + if err := cx.begin(program); err != nil { + return err + } + maxCost := cx.remainingBudget() staticCost := 0 for cx.pc < len(cx.program) { @@ -1207,7 +1233,7 @@ func check(program []byte, params *EvalParams, mode RunMode) (err error) { return fmt.Errorf("pc=%3d %w", cx.pc, err) } staticCost += stepCost - if version < backBranchEnabledVersion && staticCost > maxCost { + if cx.version < backBranchEnabledVersion && staticCost > maxCost { return fmt.Errorf("pc=%3d static cost budget of %d exceeded", cx.pc, maxCost) } if cx.pc <= prevpc { @@ -1221,26 +1247,31 @@ func check(program []byte, params *EvalParams, mode RunMode) (err error) { return nil } -func versionCheck(program []byte, params *EvalParams) (uint64, int, error) { +func (cx *EvalContext) begin(program []byte) error { + cx.program = program + version, vlen, err := transactions.ProgramVersion(program) if err != nil { - return 0, 0, err + return err } - if version > evalMaxVersion { - return 0, 0, fmt.Errorf("program version %d greater than max supported version %d", version, evalMaxVersion) + if version > LogicVersion { + return fmt.Errorf("program version %d greater than max supported version %d", version, LogicVersion) } - if version > params.Proto.LogicSigVersion { - return 0, 0, fmt.Errorf("program version %d greater than protocol supported version %d", version, params.Proto.LogicSigVersion) + if version > cx.Proto.LogicSigVersion { + return fmt.Errorf("program version %d greater than protocol supported version %d", version, cx.Proto.LogicSigVersion) } - - if params.MinAvmVersion == nil { - minVersion := ComputeMinAvmVersion(params.TxnGroup) - params.MinAvmVersion = &minVersion + if err != nil { + return err } - if version < *params.MinAvmVersion { - return 0, 0, fmt.Errorf("program version must be >= %d for this transaction group, but have version %d", *params.MinAvmVersion, version) + + cx.version = version + cx.pc = vlen + + if cx.version < cx.EvalParams.minAvmVersion { + return fmt.Errorf("program version must be >= %d for this transaction group, but have version %d", + cx.minAvmVersion, cx.version) } - return version, vlen, nil + return nil } func opCompat(expected, got avmType) bool { @@ -1280,6 +1311,9 @@ func (cx *EvalContext) AppID() basics.AppIndex { func (cx *EvalContext) remainingBudget() int { if cx.runMode == ModeSig { + if cx.PooledLogicSigBudget != nil { + return *cx.PooledLogicSigBudget + } return int(cx.Proto.LogicSigMaxCost) - cx.cost } @@ -1347,23 +1381,22 @@ func (cx *EvalContext) step() error { return fmt.Errorf("%3d %s returned 0 cost", cx.pc, spec.Name) } } - cx.cost += opcost - if cx.PooledApplicationBudget != nil { - *cx.PooledApplicationBudget -= opcost - } - if cx.remainingBudget() < 0 { - // We're not going to execute the instruction, so give the cost back. - // This only matters if this is an inner ClearState - the caller should - // not be over debited. (Normally, failure causes total txtree failure.) - cx.cost -= opcost - if cx.PooledApplicationBudget != nil { - *cx.PooledApplicationBudget += opcost - } + if opcost > cx.remainingBudget() { return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d", cx.pc, spec.Name, cx.cost) } + cx.cost += opcost + // At most one of these pooled budgets will be non-nil, perhaps we could + // collapse to one variable, but there are some complex callers trying to + // set up big budgets for debugging runs that would have to be looked at. + switch { + case cx.PooledApplicationBudget != nil: + *cx.PooledApplicationBudget -= opcost + case cx.PooledLogicSigBudget != nil: + *cx.PooledLogicSigBudget -= opcost + } preheight := len(cx.Stack) err := spec.op(cx) @@ -1889,11 +1922,11 @@ func opShiftRight(cx *EvalContext) error { func opSqrt(cx *EvalContext) error { /* - It would not be safe to use math.Sqrt, because we would have to - convert our u64 to an f64, but f64 cannot represent all u64s exactly. + It would not be safe to use math.Sqrt, because we would have to convert our + u64 to an f64, but f64 cannot represent all u64s exactly. - This algorithm comes from Jack W. Crenshaw's 1998 article in Embedded: - http://www.embedded.com/electronics-blogs/programmer-s-toolbox/4219659/Integer-Square-Roots + This algorithm comes from Jack W. Crenshaw's 1998 article in Embedded: + http://www.embedded.com/electronics-blogs/programmer-s-toolbox/4219659/Integer-Square-Roots */ last := len(cx.Stack) - 1 diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index f33fce3501..06fee09c13 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -592,7 +592,6 @@ func TestNumInnerShallow(t *testing.T) { ep, tx, ledger := MakeSampleEnv() ep.Proto.EnableInnerTransactionPooling = false - ep.Reset() ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAccount(appAddr(888), 1000000) TestApp(t, pay+";int 1", ep) @@ -2754,20 +2753,17 @@ func TestNumInnerDeep(t *testing.T) { itxn_submit ` - tx := txntest.Txn{ - Type: protocol.ApplicationCallTx, - ApplicationID: 888, - ForeignApps: []basics.AppIndex{basics.AppIndex(222)}, - }.SignedTxnWithAD() - require.Equal(t, 888, int(tx.Txn.ApplicationID)) - ledger := NewLedger(nil) + ep, tx, ledger := MakeSampleEnv() + + tx.Type = protocol.ApplicationCallTx + tx.ApplicationID = 888 + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} - pay3 := TestProg(t, pay+pay+pay+"int 1;", AssemblerMaxVersion).Program - ledger.NewApp(tx.Txn.Receiver, 222, basics.AppParams{ - ApprovalProgram: pay3, + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: TestProg(t, pay+pay+pay+"int 1;", AssemblerMaxVersion).Program, }) - ledger.NewApp(tx.Txn.Receiver, 888, basics.AppParams{}) + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAccount(appAddr(888), 1_000_000) callpay3 := `itxn_begin @@ -2775,10 +2771,6 @@ int appl; itxn_field TypeEnum int 222; itxn_field ApplicationID itxn_submit ` - txg := []transactions.SignedTxnWithAD{tx} - ep := NewEvalParams(txg, MakeTestProto(), &transactions.SpecialAddresses{}) - ep.Ledger = ledger - ep.SigLedger = ledger TestApp(t, callpay3+"int 1", ep, "insufficient balance") // inner contract needs money ledger.NewAccount(appAddr(222), 1_000_000) diff --git a/data/transactions/logic/evalBench_test.go b/data/transactions/logic/evalBench_test.go index 240da71bdf..b9f61f0fa7 100644 --- a/data/transactions/logic/evalBench_test.go +++ b/data/transactions/logic/evalBench_test.go @@ -33,16 +33,11 @@ func BenchmarkCheckSignature(b *testing.B) { ops, err := logic.AssembleString(txntest.TmLsig) require.NoError(b, err) stxns := []transactions.SignedTxn{{Txn: txns[3].Txn(), Lsig: transactions.LogicSig{Logic: ops.Program}}} - txgroup := transactions.WrapSignedTxnsWithAD(stxns) - ep := logic.EvalParams{ - Proto: &proto, - TxnGroup: txgroup, - SigLedger: &logic.NoHeaderLedger{}, - } + ep := logic.NewSigEvalParams(stxns, &proto, &logic.NoHeaderLedger{}) b.ResetTimer() for i := 0; i < b.N; i++ { - err = logic.CheckSignature(0, &ep) + err = logic.CheckSignature(0, ep) require.NoError(b, err) } } diff --git a/data/transactions/logic/evalCrypto_test.go b/data/transactions/logic/evalCrypto_test.go index 766fe66baa..e4b22e4975 100644 --- a/data/transactions/logic/evalCrypto_test.go +++ b/data/transactions/logic/evalCrypto_test.go @@ -113,10 +113,12 @@ func TestVrfVerify(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, _, _ := makeSampleEnv() + ep := defaultAppParams() testApp(t, notrack("int 1; int 2; int 3; vrf_verify VrfAlgorand"), ep, "arg 0 wanted") testApp(t, notrack("byte 0x1122; int 2; int 3; vrf_verify VrfAlgorand"), ep, "arg 1 wanted") testApp(t, notrack("byte 0x1122; byte 0x2233; int 3; vrf_verify VrfAlgorand"), ep, "arg 2 wanted") + + ep = defaultSigParams() testLogic(t, "byte 0x1122; byte 0x2233; byte 0x3344; vrf_verify VrfAlgorand", LogicVersion, ep, "vrf proof wrong size") // 80 byte proof testLogic(t, "byte 0x1122; int 80; bzero; byte 0x3344; vrf_verify VrfAlgorand", LogicVersion, ep, "vrf pubkey wrong size") @@ -210,18 +212,18 @@ ed25519verify`, pkStr), v) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program txn.Lsig.Args = [][]byte{data[:], sig[:]} - testLogicBytes(t, ops.Program, defaultEvalParams(txn)) + testLogicBytes(t, ops.Program, defaultSigParams(txn)) // short sig will fail txn.Lsig.Args[1] = sig[1:] - testLogicBytes(t, ops.Program, defaultEvalParams(txn), "invalid signature") + testLogicBytes(t, ops.Program, defaultSigParams(txn), "invalid signature") // flip a bit and it should not pass msg1 := "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" data1, err := hex.DecodeString(msg1) require.NoError(t, err) txn.Lsig.Args = [][]byte{data1, sig[:]} - testLogicBytes(t, ops.Program, defaultEvalParams(txn), "REJECT") + testLogicBytes(t, ops.Program, defaultSigParams(txn), "REJECT") }) } } @@ -250,18 +252,18 @@ ed25519verify_bare`, pkStr), v) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program txn.Lsig.Args = [][]byte{data[:], sig[:]} - testLogicBytes(t, ops.Program, defaultEvalParams(txn)) + testLogicBytes(t, ops.Program, defaultSigParams(txn)) // short sig will fail txn.Lsig.Args[1] = sig[1:] - testLogicBytes(t, ops.Program, defaultEvalParams(txn), "invalid signature") + testLogicBytes(t, ops.Program, defaultSigParams(txn), "invalid signature") // flip a bit and it should not pass msg1 := "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" data1, err := hex.DecodeString(msg1) require.NoError(t, err) txn.Lsig.Args = [][]byte{data1, sig[:]} - testLogicBytes(t, ops.Program, defaultEvalParams(txn), "REJECT") + testLogicBytes(t, ops.Program, defaultSigParams(txn), "REJECT") }) } } @@ -460,7 +462,7 @@ ecdsa_verify Secp256k1`, hex.EncodeToString(r), hex.EncodeToString(s), hex.Encod ops := testProg(t, source, 5) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - pass, err := EvalSignature(0, defaultEvalParamsWithVersion(5, txn)) + pass, err := EvalSignature(0, defaultSigParamsWithVersion(5, txn)) require.NoError(t, err) require.True(t, pass) } @@ -568,7 +570,7 @@ ecdsa_verify Secp256r1`, hex.EncodeToString(r), hex.EncodeToString(s), hex.Encod ops := testProg(t, source, fidoVersion) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - pass, err := EvalSignature(0, defaultEvalParamsWithVersion(fidoVersion, txn)) + pass, err := EvalSignature(0, defaultSigParamsWithVersion(fidoVersion, txn)) require.NoError(t, err) require.True(t, pass) } @@ -707,7 +709,7 @@ ed25519verify`, pkStr), AssemblerMaxVersion) var txn transactions.SignedTxn txn.Lsig.Logic = programs[i] txn.Lsig.Args = [][]byte{data[i][:], signatures[i][:]} - ep := defaultEvalParams(txn) + ep := defaultSigParams(txn) pass, err := EvalSignature(0, ep) if !pass { b.Log(hex.EncodeToString(programs[i])) @@ -792,7 +794,7 @@ func benchmarkEcdsa(b *testing.B, source string, curve EcdsaCurve) { var txn transactions.SignedTxn txn.Lsig.Logic = data[i].programs txn.Lsig.Args = [][]byte{data[i].msg[:], data[i].r, data[i].s, data[i].x, data[i].y, data[i].pk, {uint8(data[i].v)}} - ep := defaultEvalParams(txn) + ep := defaultSigParams(txn) pass, err := EvalSignature(0, ep) if !pass { b.Log(hex.EncodeToString(data[i].programs)) @@ -915,7 +917,7 @@ func benchmarkBn256(b *testing.B, source string) { var txn transactions.SignedTxn txn.Lsig.Logic = data[i].programs txn.Lsig.Args = [][]byte{data[i].a, data[i].k, data[i].g1, data[i].g2} - ep := defaultEvalParams(txn) + ep := defaultSigParams(txn) pass, err := EvalSignature(0, ep) if !pass { b.Log(hex.EncodeToString(data[i].programs)) diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 17759ef677..0bebf9d267 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/protocol" @@ -50,18 +51,12 @@ func makeSampleEnv() (*EvalParams, *transactions.Transaction, *Ledger) { } func makeSampleEnvWithVersion(version uint64) (*EvalParams, *transactions.Transaction, *Ledger) { - // We'd usually like an app in the group, so that the ep created is - // "complete". But to keep as many old tests working as possible, if - // version < appsEnabledVersion, don't put an appl txn in it. - firstTxn := makeSampleTxn() - if version >= appsEnabledVersion { - firstTxn.Txn.Type = protocol.ApplicationCallTx - } - // avoid putting in a RekeyTo field if version < rekeyingEnabledVersion - if version < rekeyingEnabledVersion { - firstTxn.Txn.RekeyTo = basics.Address{} + if version < appsEnabledVersion { + panic("makeSampleEnv is for apps, but you've asked for a version before apps work") } - ep := defaultEvalParamsWithVersion(version, makeSampleTxnGroup(firstTxn)...) + firstTxn := makeSampleTxn() + firstTxn.Txn.Type = protocol.ApplicationCallTx + ep := defaultAppParamsWithVersion(version, makeSampleTxnGroup(firstTxn)...) ledger := NewLedger(nil) ep.SigLedger = ledger ep.Ledger = ledger @@ -265,8 +260,8 @@ log tx.SelectionPK[:], tx.Note, } - ep.TxnGroup[0].Txn.ApplicationID = 100 - ep.TxnGroup[0].Txn.ForeignAssets = []basics.AssetIndex{500} // needed since v4 + tx.ApplicationID = 100 + tx.ForeignAssets = []basics.AssetIndex{500} // needed since v4 params := basics.AssetParams{ Total: 1000, Decimals: 2, @@ -287,7 +282,9 @@ log ledger.NewAsset(tx.Sender, 5, params) if mode == ModeSig { + ep.runMode = ModeSig testLogic(t, test, AssemblerMaxVersion, ep) + ep.runMode = ModeApp } else { testApp(t, test, ep) } @@ -296,8 +293,8 @@ log // check err opcode work in both modes source := "err" - testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "err opcode executed") - testApp(t, source, defaultEvalParams(), "err opcode executed") + testLogic(t, source, AssemblerMaxVersion, nil, "err opcode executed") + testApp(t, source, nil, "err opcode executed") // check that ed25519verify and arg is not allowed in stateful mode between v2-v4 disallowedV4 := []string{ @@ -310,7 +307,7 @@ log } for _, source := range disallowedV4 { ops := testProg(t, source, 4) - testAppBytes(t, ops.Program, defaultEvalParams(), + testAppBytes(t, ops.Program, nil, "not allowed in current mode", "not allowed in current mode") } @@ -324,7 +321,7 @@ log } for _, source := range disallowed { ops := testProg(t, source, AssemblerMaxVersion) - testAppBytes(t, ops.Program, defaultEvalParams(), + testAppBytes(t, ops.Program, nil, "not allowed in current mode", "not allowed in current mode") } @@ -363,7 +360,7 @@ log if v < introduced { continue } - testLogic(t, source, v, defaultEvalParamsWithVersion(v), + testLogic(t, source, v, defaultSigParamsWithVersion(v), "not allowed in current mode", "not allowed in current mode") } } @@ -374,6 +371,26 @@ log require.True(t, modeAny.Any()) } +func TestApplicationsDisallowOldTeal(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + const source = "int 1" + + txn := makeSampleTxn() + txn.Txn.Type = protocol.ApplicationCallTx + txn.Txn.RekeyTo = basics.Address{} + ep := defaultAppParams(txn) + + for v := uint64(0); v < appsEnabledVersion; v++ { + ops := testProg(t, source, v) + e := fmt.Sprintf("program version must be >= %d", appsEnabledVersion) + testAppBytes(t, ops.Program, ep, e, e) + } + + testApp(t, source, ep) +} + func TestBalance(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -403,13 +420,14 @@ func TestBalance(t *testing.T) { }) } -func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, version uint64, ledger *Ledger, +func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, opt protoOpt, ledger *Ledger, expected ...expect) *EvalParams { t.Helper() + proto := makeTestProto(opt) codes := make([][]byte, len(programs)) for i, program := range programs { if program != "" { - codes[i] = testProg(t, program, version).Program + codes[i] = testProg(t, program, proto.LogicSigVersion).Program } } if txgroup == nil { @@ -421,7 +439,7 @@ func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, txgroup = append(txgroup, sample) } } - ep := NewEvalParams(transactions.WrapSignedTxnsWithAD(txgroup), makeTestProtoV(version), &transactions.SpecialAddresses{}) + ep := NewAppEvalParams(transactions.WrapSignedTxnsWithAD(txgroup), proto, &transactions.SpecialAddresses{}) if ledger == nil { ledger = NewLedger(nil) } @@ -461,13 +479,20 @@ func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ... func testApp(t *testing.T, program string, ep *EvalParams, problems ...string) transactions.EvalDelta { t.Helper() + if ep == nil { + ep = defaultAppParamsWithVersion(LogicVersion) + } ops := testProg(t, program, ep.Proto.LogicSigVersion) return testAppBytes(t, ops.Program, ep, problems...) } func testAppBytes(t *testing.T, program []byte, ep *EvalParams, problems ...string) transactions.EvalDelta { t.Helper() - ep.reset() + if ep == nil { + ep = defaultAppParamsWithVersion(LogicVersion) + } else { + ep.reset() + } aid := ep.TxnGroup[0].Txn.ApplicationID if aid == 0 { aid = basics.AppIndex(888) @@ -496,15 +521,13 @@ func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep * require.Fail(t, "Misused testApp: %d problems", len(problems)) } - sb := &strings.Builder{} - ep.Trace = sb + ep.Trace = &strings.Builder{} err := CheckContract(program, ep) if checkProblem == "" { - require.NoError(t, err, sb.String()) + require.NoError(t, err, "Error in CheckContract %v", ep.Trace) } else { - require.Error(t, err, "Check\n%s\nExpected: %v", sb, checkProblem) - require.Contains(t, err.Error(), checkProblem, sb.String()) + require.ErrorContains(t, err, checkProblem, "Wrong error in CheckContract %v", ep.Trace) } // We continue on to check Eval() of things that failed Check() because it's @@ -519,18 +542,17 @@ func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep * pass, err := EvalApp(program, gi, aid, ep) delta := ep.TxnGroup[gi].EvalDelta if evalProblem == "" { - require.NoError(t, err, "Eval%s\nExpected: PASS", sb) - require.True(t, pass, "Eval%s\nExpected: PASS", sb) + require.NoError(t, err, "Eval\n%sExpected: PASS", ep.Trace) + require.True(t, pass, "Eval\n%sExpected: PASS", ep.Trace) return delta } // There is an evalProblem to check. REJECT is special and only means that // the app didn't accept. Maybe it's an error, maybe it's just !pass. if evalProblem == "REJECT" { - require.True(t, err != nil || !pass, "Eval%s\nExpected: REJECT", sb) + require.True(t, err != nil || !pass, "Eval%s\nExpected: REJECT", ep.Trace) } else { - require.Error(t, err, "Eval\n%s\nExpected: %v", sb, evalProblem) - require.Contains(t, err.Error(), evalProblem) + require.ErrorContains(t, err, evalProblem, "Wrong error in EvalContract %v", ep.Trace) } return delta } @@ -1022,9 +1044,9 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) } txn := makeSampleAppl(888) - pre := defaultEvalParamsWithVersion(directRefEnabledVersion-1, txn) + pre := defaultAppParamsWithVersion(directRefEnabledVersion-1, txn) require.GreaterOrEqual(t, version, uint64(directRefEnabledVersion)) - now := defaultEvalParamsWithVersion(version, txn) + now := defaultAppParamsWithVersion(version, txn) ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, @@ -1529,7 +1551,7 @@ intc_1 var txn transactions.SignedTxn txn.Txn.Type = protocol.ApplicationCallTx txn.Txn.ApplicationID = 100 - ep := defaultEvalParams(txn) + ep := defaultAppParams(txn) err := CheckContract(ops.Program, ep) require.NoError(t, err) @@ -2565,8 +2587,8 @@ func TestEnumFieldErrors(t *testing.T) { // nolint:paralleltest // manipulates t txnFieldSpecs[Amount] = origSpec }() - testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "Amount expected field type is []byte but got uint64") - testApp(t, source, defaultEvalParams(), "Amount expected field type is []byte but got uint64") + testLogic(t, source, AssemblerMaxVersion, nil, "Amount expected field type is []byte but got uint64") + testApp(t, source, nil, "Amount expected field type is []byte but got uint64") source = `global MinTxnFee` @@ -2578,8 +2600,8 @@ func TestEnumFieldErrors(t *testing.T) { // nolint:paralleltest // manipulates t globalFieldSpecs[MinTxnFee] = origMinTxnFs }() - testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "MinTxnFee expected field type is []byte but got uint64") - testApp(t, source, defaultEvalParams(), "MinTxnFee expected field type is []byte but got uint64") + testLogic(t, source, AssemblerMaxVersion, nil, "MinTxnFee expected field type is []byte but got uint64") + testApp(t, source, nil, "MinTxnFee expected field type is []byte but got uint64") ep, tx, ledger := makeSampleEnv() ledger.NewAccount(tx.Sender, 1) @@ -2799,26 +2821,27 @@ func TestReturnTypes(t *testing.T) { sb.WriteString(cmd + "\n") ops := testProg(t, sb.String(), AssemblerMaxVersion) - ep, tx, ledger := makeSampleEnv() - - tx.Type = protocol.ApplicationCallTx - tx.ApplicationID = 300 - tx.ForeignApps = []basics.AppIndex{tx.ApplicationID} - tx.ForeignAssets = []basics.AssetIndex{400} - tx.Boxes = []transactions.BoxRef{{ - Name: []byte("3456"), - }} - ep.TxnGroup[0].Lsig.Args = [][]byte{ + tx0 := makeSampleTxn() + tx0.Txn.Type = protocol.ApplicationCallTx + tx0.Txn.ApplicationID = 300 + tx0.Txn.ForeignApps = []basics.AppIndex{300} + tx0.Txn.ForeignAssets = []basics.AssetIndex{400} + tx0.Txn.Boxes = []transactions.BoxRef{{Name: []byte("3456")}} + tx0.Lsig.Args = [][]byte{ []byte("aoeu"), []byte("aoeu"), []byte("aoeu2"), []byte("aoeu3"), } + tx0.Lsig.Logic = ops.Program // We are going to run with GroupIndex=1, so make tx1 interesting too (so - // txn can look at things) - ep.TxnGroup[1] = ep.TxnGroup[0] + // `txn` opcode can look at things) + tx1 := tx0 + + sep, aep := defaultEvalParams(tx0, tx1) + ledger := aep.Ledger.(*Ledger) - ep.pastScratch[0] = &scratchSpace{} // for gload + tx := tx0.Txn ledger.NewAccount(tx.Sender, 1) params := basics.AssetParams{ Total: 1000, @@ -2842,39 +2865,34 @@ func TestReturnTypes(t *testing.T) { ledger.NewLocal(tx.Receiver, 300, string(key), algoValue) ledger.NewAccount(appAddr(300), 1000000) - ep.reset() // for Trace and budget isolation - ep.pastScratch[0] = &scratchSpace{} // for gload // these allows the box_* opcodes that to work ledger.CreateBox(300, "3456", 10) - ep.ioBudget = 50 - - cx := EvalContext{ - EvalParams: ep, - runMode: m, - groupIndex: 1, - txn: &ep.TxnGroup[1], - appID: 300, - } - // These set conditions for some ops that examine the group. - // This convinces them all to work. Revisit. - cx.TxnGroup[0].ConfigAsset = 100 + // We are running gi=1, but we never ran gi=0. Set things up as + // if we did, so they can be accessed with gtxn, gload, gaid + aep.pastScratch[0] = &scratchSpace{} + aep.TxnGroup[0].ConfigAsset = 100 - // These little programs need not pass. Since the returned stack - // is checked for typing, we can't get hung up on whether it is - // exactly one positive int. But if it fails for any *other* - // reason, we're not doing a good test. - _, err = eval(ops.Program, &cx) + var cx *EvalContext + if m == ModeApp { + _, cx, err = EvalContract(ops.Program, 1, 300, aep) + } else { + _, cx, err = EvalSignatureFull(1, sep) + } + // These little programs need not pass. We are just trying to + // examine cx.Stack for proper types/size after executing the + // opcode. But if it fails for any *other* reason, we're not + // doing a good test. if err != nil { // Allow the kinds of errors we expect, but fail for stuff // that indicates the opcode itself failed. reason := err.Error() - if reason != "stack finished with bytes not int" && - !strings.HasPrefix(reason, "stack len is") { - require.NoError(t, err, "%s: %s\n%s", name, err, ep.Trace) + if !strings.Contains(reason, "stack finished with bytes not int") && + !strings.Contains(reason, "stack len is") { + require.NoError(t, err, "%s: %s\n%s", name, err, cx.Trace) } } - require.Len(t, cx.Stack, len(spec.Return.Types), "%s", ep.Trace) + require.Len(t, cx.Stack, len(spec.Return.Types), "%s", cx.Trace) for i := 0; i < len(spec.Return.Types); i++ { stackType := cx.Stack[i].stackType() retType := spec.Return.Types[i] @@ -2901,18 +2919,147 @@ func TestTxnEffects(t *testing.T) { testApp(t, "byte 0x32; log; gtxn 0 LastLog; byte 0x32; ==", ep, "txn effects can only be read from past txns") // Look at the logs of tx 0 - testApps(t, []string{"", "byte 0x32; log; gtxn 0 LastLog; byte 0x; =="}, nil, AssemblerMaxVersion, nil) - testApps(t, []string{"byte 0x33; log; int 1", "gtxn 0 LastLog; byte 0x33; =="}, nil, AssemblerMaxVersion, nil) - testApps(t, []string{"byte 0x33; dup; log; log; int 1", "gtxn 0 NumLogs; int 2; =="}, nil, AssemblerMaxVersion, nil) - testApps(t, []string{"byte 0x37; log; int 1", "gtxn 0 Logs 0; byte 0x37; =="}, nil, AssemblerMaxVersion, nil) - testApps(t, []string{"byte 0x37; log; int 1", "int 0; gtxnas 0 Logs; byte 0x37; =="}, nil, AssemblerMaxVersion, nil) + testApps(t, []string{"", "byte 0x32; log; gtxn 0 LastLog; byte 0x; =="}, nil, nil, nil) + testApps(t, []string{"byte 0x33; log; int 1", "gtxn 0 LastLog; byte 0x33; =="}, nil, nil, nil) + testApps(t, []string{"byte 0x33; dup; log; log; int 1", "gtxn 0 NumLogs; int 2; =="}, nil, nil, nil) + testApps(t, []string{"byte 0x37; log; int 1", "gtxn 0 Logs 0; byte 0x37; =="}, nil, nil, nil) + testApps(t, []string{"byte 0x37; log; int 1", "int 0; gtxnas 0 Logs; byte 0x37; =="}, nil, nil, nil) // Look past the logs of tx 0 - testApps(t, []string{"byte 0x37; log; int 1", "gtxna 0 Logs 1; byte 0x37; =="}, nil, AssemblerMaxVersion, nil, + testApps(t, []string{"byte 0x37; log; int 1", "gtxna 0 Logs 1; byte 0x37; =="}, nil, nil, nil, exp(1, "invalid Logs index 1")) - testApps(t, []string{"byte 0x37; log; int 1", "int 6; gtxnas 0 Logs; byte 0x37; =="}, nil, AssemblerMaxVersion, nil, + testApps(t, []string{"byte 0x37; log; int 1", "int 6; gtxnas 0 Logs; byte 0x37; =="}, nil, nil, nil, exp(1, "invalid Logs index 6")) } +func TestLog(t *testing.T) { + partitiontest.PartitionTest(t) + + t.Parallel() + var txn transactions.SignedTxn + txn.Txn.Type = protocol.ApplicationCallTx + ledger := NewLedger(nil) + ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{}) + ep := defaultAppParams(txn) + testCases := []struct { + source string + loglen int + }{ + { + source: `byte "a logging message"; log; int 1`, + loglen: 1, + }, + { + source: `byte "a logging message"; log; byte "a logging message"; log; int 1`, + loglen: 2, + }, + { + source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log; `, maxLogCalls)), + loglen: maxLogCalls, + }, + { + source: `int 1; loop: byte "a logging message"; log; int 1; +; dup; int 30; <=; bnz loop;`, + loglen: 30, + }, + { + source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize)), + loglen: 1, + }, + } + + //track expected number of logs in cx.EvalDelta.Logs + for i, s := range testCases { + delta := testApp(t, s.source, ep) + require.Len(t, delta.Logs, s.loglen) + if i == len(testCases)-1 { + require.Equal(t, strings.Repeat("a", maxLogSize), delta.Logs[0]) + } else { + for _, l := range delta.Logs { + require.Equal(t, "a logging message", l) + } + } + } + + msg := strings.Repeat("a", 400) + failCases := []struct { + source string + errContains string + // For cases where assembly errors, we manually put in the bytes + assembledBytes []byte + }{ + { + source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize+1)), + errContains: fmt.Sprintf("> %d bytes limit", maxLogSize), + }, + { + source: fmt.Sprintf(`byte "%s"; log; byte "%s"; log; byte "%s"; log; int 1`, msg, msg, msg), + errContains: fmt.Sprintf("> %d bytes limit", maxLogSize), + }, + { + source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log; `, maxLogCalls+1)), + errContains: "too many log calls", + }, + { + source: `int 1; loop: byte "a"; log; int 1; +; dup; int 35; <; bnz loop;`, + errContains: "too many log calls", + }, + { + source: fmt.Sprintf(`int 1; loop: byte "%s"; log; int 1; +; dup; int 6; <; bnz loop;`, strings.Repeat(`a`, 400)), + errContains: fmt.Sprintf("> %d bytes limit", maxLogSize), + }, + { + source: `load 0; log`, + errContains: "log arg 0 wanted []byte but got uint64", + assembledBytes: []byte{byte(ep.Proto.LogicSigVersion), 0x34, 0x00, 0xb0}, + }, + } + + for _, c := range failCases { + if c.assembledBytes == nil { + testApp(t, c.source, ep, c.errContains) + } else { + testAppBytes(t, c.assembledBytes, ep, c.errContains) + } + } +} + +func TestGaid(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + check0 := testProg(t, "gaid 0; int 100; ==", 4) + appTxn := makeSampleTxn() + appTxn.Txn.Type = protocol.ApplicationCallTx + targetTxn := makeSampleTxn() + targetTxn.Txn.Type = protocol.AssetConfigTx + ep := defaultAppParams(targetTxn, appTxn, makeSampleTxn()) + + // should fail when no creatable was created + _, err := EvalApp(check0.Program, 1, 888, ep) + require.ErrorContains(t, err, "did not create anything") + + ep.TxnGroup[0].ApplyData.ConfigAsset = 100 + pass, err := EvalApp(check0.Program, 1, 888, ep) + if !pass || err != nil { + t.Log(ep.Trace.String()) + } + require.NoError(t, err) + require.True(t, pass) + + // should fail when accessing future transaction in group + check2 := testProg(t, "gaid 2; int 0; >", 4) + _, err = EvalApp(check2.Program, 1, 888, ep) + require.ErrorContains(t, err, "gaid can't get creatable ID of txn ahead of the current one") + + // should fail when accessing self + _, err = EvalApp(check0.Program, 0, 888, ep) + require.ErrorContains(t, err, "gaid is only for accessing creatable IDs of previous txns") + + // should fail on non-creatable + ep.TxnGroup[0].Txn.Type = protocol.PaymentTx + _, err = EvalApp(check0.Program, 1, 888, ep) + require.ErrorContains(t, err, "can't use gaid on txn that is not an app call nor an asset config txn") + ep.TxnGroup[0].Txn.Type = protocol.AssetConfigTx +} func TestRound(t *testing.T) { partitiontest.PartitionTest(t) @@ -2966,6 +3113,7 @@ func TestBlockSeed(t *testing.T) { // `block` should also work in LogicSigs, to drive home the point, blot out // the normal Ledger + ep.runMode = ModeSig ep.Ledger = nil testLogic(t, "int 0xfffffff0; block BlkTimestamp", randomnessVersion, ep) } @@ -3010,11 +3158,11 @@ func TestPooledAppCallsVerifyOp(t *testing.T) { ledger := NewLedger(nil) call := transactions.SignedTxn{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}} // Simulate test with 2 grouped txn - testApps(t, []string{source, ""}, []transactions.SignedTxn{call, call}, LogicVersion, ledger, + testApps(t, []string{source, ""}, []transactions.SignedTxn{call, call}, nil, ledger, exp(0, "pc=107 dynamic cost budget exceeded, executing ed25519verify: local program cost was 5")) // Simulate test with 3 grouped txn - testApps(t, []string{source, "", ""}, []transactions.SignedTxn{call, call, call}, LogicVersion, ledger) + testApps(t, []string{source, "", ""}, []transactions.SignedTxn{call, call, call}, nil, ledger) } func appAddr(id int) basics.Address { @@ -3042,20 +3190,30 @@ func TestAppInfo(t *testing.T) { testApp(t, source, ep) } -func TestBudget(t *testing.T) { +func TestAppBudget(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - source := ` + source := func(budget int) string { + return fmt.Sprintf(` global OpcodeBudget -int 699 +int %d == assert global OpcodeBudget -int 695 +int %d == -` - testApp(t, source, defaultEvalParams()) +`, budget-1, budget-5) + } + testApp(t, source(700), nil) + + // with pooling a two app call starts with 1400 + testApps(t, []string{source(1400), source(1393)}, nil, nil, nil) + + // without, they get base 700 + testApps(t, []string{source(700), source(700)}, nil, + func(p *config.ConsensusParams) { p.EnableAppCostPooling = false }, nil) + } func TestSelfMutateV8(t *testing.T) { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index b10c022ef9..c7e73c0ba8 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -22,6 +22,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "io" "math" "strconv" "strings" @@ -35,23 +36,31 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "pgregory.net/rapid" ) -// Note that most of the tests use makeTestProto/defaultEvalParams as evaluator version so that -// we check that v1 and v2 programs are compatible with the latest evaluator -func makeTestProto() *config.ConsensusParams { - return makeTestProtoV(LogicVersion) +type protoOpt func(*config.ConsensusParams) + +func protoVer(version uint64) protoOpt { + return func(p *config.ConsensusParams) { + p.LogicSigVersion = version + p.Application = version >= appsEnabledVersion + } } func makeTestProtoV(version uint64) *config.ConsensusParams { - return &config.ConsensusParams{ - LogicSigVersion: version, + return makeTestProto(protoVer(version)) +} + +func makeTestProto(opts ...protoOpt) *config.ConsensusParams { + p := config.ConsensusParams{ + LogicSigVersion: LogicVersion, LogicSigMaxCost: 20000, - Application: version >= appsEnabledVersion, + Application: true, MaxAppProgramCost: 700, MaxAppKeyLen: 64, @@ -101,10 +110,11 @@ func makeTestProtoV(version uint64) *config.ConsensusParams { MaxGlobalSchemaEntries: 30, MaxLocalSchemaEntries: 13, - EnableAppCostPooling: true, - EnableInnerTransactionPooling: true, + EnableAppCostPooling: true, + EnableLogicSigCostPooling: true, - MinInnerApplVersion: 4, + EnableInnerTransactionPooling: true, + MinInnerApplVersion: 4, SupportBecomeNonParticipatingTransactions: true, @@ -113,76 +123,109 @@ func makeTestProtoV(version uint64) *config.ConsensusParams { MaxBoxSize: 1000, BytesPerBoxReference: 100, } + for _, opt := range opts { + if opt != nil { // so some callsites can take one arg and pass it in + opt(&p) + } + } + return &p } -func defaultEvalParams(txns ...transactions.SignedTxn) *EvalParams { - return defaultEvalParamsWithVersion(LogicVersion, txns...) -} - -func benchmarkEvalParams(txn transactions.SignedTxn) *EvalParams { - ep := defaultEvalParams(txn) +func benchmarkSigParams(txns ...transactions.SignedTxn) *EvalParams { + ep := optSigParams(func(p *config.ConsensusParams) { + p.LogicSigMaxCost = 1_000_000_000 + }, txns...) ep.Trace = nil // Tracing would slow down benchmarks - clone := *ep.Proto - bigBudget := 1000 * 1000 * 1000 // Allow long run times - clone.LogicSigMaxCost = uint64(bigBudget) - clone.MaxAppProgramCost = bigBudget - ep.Proto = &clone - ep.PooledApplicationBudget = &bigBudget return ep } -func defaultEvalParamsWithVersion(version uint64, txns ...transactions.SignedTxn) *EvalParams { - empty := false +func defaultSigParams(txns ...transactions.SignedTxn) *EvalParams { + return optSigParams(nil, txns...) +} +func defaultSigParamsWithVersion(version uint64, txns ...transactions.SignedTxn) *EvalParams { + return optSigParams(protoVer(version), txns...) +} +func optSigParams(opt protoOpt, txns ...transactions.SignedTxn) *EvalParams { if len(txns) == 0 { - empty = true - txns = []transactions.SignedTxn{{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}}} + // We need a transaction to exist, because we'll be stuffing the + // logicsig into it in order to test them. + txns = make([]transactions.SignedTxn, 1) } - ep := NewEvalParams(transactions.WrapSignedTxnsWithAD(txns), makeTestProtoV(version), &transactions.SpecialAddresses{}) + // Make it non-Blank so NewSigEval does not short-circuit (but try to avoid + // manipulating txns if they were actually supplied with other sigs.) + if txns[0].Sig.Blank() && txns[0].Msig.Blank() && txns[0].Lsig.Blank() { + txns[0].Lsig.Logic = []byte{LogicVersion + 1} // make sure it fails if used + } + + ep := NewSigEvalParams(txns, makeTestProto(opt), &NoHeaderLedger{}) ep.Trace = &strings.Builder{} - ep.SigLedger = NewLedger(nil) - if empty { - // We made an app type in order to get a full ep, but that sets MinTealVersion=2 - ep.TxnGroup[0].Txn.Type = "" // set it back - ep.MinAvmVersion = nil // will recalculate in eval() + return ep +} + +func defaultAppParams(txns ...transactions.SignedTxn) *EvalParams { + return defaultAppParamsWithVersion(LogicVersion, txns...) +} +func defaultAppParamsWithVersion(version uint64, txns ...transactions.SignedTxn) *EvalParams { + if len(txns) == 0 { + // Convince NewAppEvalParams not to return nil + txns = []transactions.SignedTxn{{ + Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}, + }} + } + 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 } return ep } -// `supportsAppEval` is test helper method for disambiguating whe `EvalParams` is suitable for logicsig vs app evaluations. -func (ep *EvalParams) supportsAppEval() bool { - return ep.available != nil +func defaultEvalParams(txns ...transactions.SignedTxn) (sig *EvalParams, app *EvalParams) { + return defaultEvalParamsWithVersion(LogicVersion, txns...) +} +func defaultEvalParamsWithVersion(version uint64, txns ...transactions.SignedTxn) (sig *EvalParams, app *EvalParams) { + sig = defaultSigParamsWithVersion(version, txns...) + app = defaultAppParamsWithVersion(version, txns...) + // Let's share ledgers for easier testing and let sigs use it for block access + if app != nil { + sig.SigLedger = app.SigLedger + } + return sig, app } // reset puts an ep back into its original state. This is in *_test.go because // no real code should ever need this. EvalParams should be created to evaluate // a group, and then thrown away. func (ep *EvalParams) reset() { - if ep.Proto.EnableAppCostPooling { - budget := ep.Proto.MaxAppProgramCost - ep.PooledApplicationBudget = &budget - } - if ep.Proto.EnableInnerTransactionPooling { - inners := ep.Proto.MaxTxGroupSize * ep.Proto.MaxInnerTransactions - ep.pooledAllowedInners = &inners - } - ep.pastScratch = make([]*scratchSpace, ep.Proto.MaxTxGroupSize) - for i := range ep.TxnGroup { - ep.TxnGroup[i].ApplyData = transactions.ApplyData{} - } - if ep.available != nil { - available := NewEvalParams(ep.TxnGroup, ep.Proto, ep.Specials).available - if available != nil { - ep.available = available + switch ep.runMode { + case ModeSig: + if ep.Proto.EnableLogicSigCostPooling { + budget := int(ep.Proto.LogicSigMaxCost) * len(ep.TxnGroup) + ep.PooledLogicSigBudget = &budget } - ep.available.dirtyBytes = 0 - } - ep.readBudgetChecked = false - ep.appAddrCache = make(map[basics.AppIndex]basics.Address) - if ep.Trace != nil { - ep.Trace = &strings.Builder{} + case ModeApp: + if ep.Proto.EnableAppCostPooling { + budget := ep.Proto.MaxAppProgramCost + ep.PooledApplicationBudget = &budget + } + if ep.Proto.EnableInnerTransactionPooling { + inners := ep.Proto.MaxTxGroupSize * ep.Proto.MaxInnerTransactions + ep.pooledAllowedInners = &inners + } + ep.pastScratch = make([]*scratchSpace, len(ep.TxnGroup)) + for i := range ep.TxnGroup { + ep.TxnGroup[i].ApplyData = transactions.ApplyData{} + } + ep.available = nil + ep.readBudgetChecked = false + ep.appAddrCache = make(map[basics.AppIndex]basics.Address) + if ep.Trace != nil { + ep.Trace = &strings.Builder{} + } + ep.txidCache = nil + ep.innerTxidCache = nil } - ep.txidCache = nil - ep.innerTxidCache = nil } func TestTooManyArgs(t *testing.T) { @@ -196,7 +239,7 @@ func TestTooManyArgs(t *testing.T) { txn.Lsig.Logic = ops.Program args := [transactions.EvalMaxArgs + 1][]byte{} txn.Lsig.Args = args[:] - pass, err := EvalSignature(0, defaultEvalParams(txn)) + pass, err := EvalSignature(0, defaultSigParams(txn)) require.Error(t, err) require.False(t, pass) }) @@ -207,23 +250,22 @@ func TestEmptyProgram(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testLogicBytes(t, nil, defaultEvalParams(), "invalid", "invalid program (empty)") + testLogicBytes(t, nil, nil, "invalid", "invalid program (empty)") } -// TestMinAvmVersionParamEval tests eval/check reading the MinAvmVersion from the param +// TestMinAvmVersionParamEval tests eval/check reading the minAvmVersion from the param func TestMinAvmVersionParamEvalCheckSignature(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - params := defaultEvalParams() - version2 := uint64(rekeyingEnabledVersion) - params.MinAvmVersion = &version2 + params := defaultSigParams() + params.minAvmVersion = uint64(rekeyingEnabledVersion) program := make([]byte, binary.MaxVarintLen64) // set the program version to 1 binary.PutUvarint(program, 1) verErr := fmt.Sprintf("program version must be >= %d", appsEnabledVersion) - testAppBytes(t, program, params, verErr, verErr) + testLogicBytes(t, program, params, verErr, verErr) } func TestTxnFieldToTealValue(t *testing.T) { @@ -279,6 +321,10 @@ func TestTxnFirstValidTime(t *testing.T) { t.Parallel() ep, tx, ledger := makeSampleEnv() + // This is an unusual test that needs a ledger + // even though it's testing signatures. So it's convenient to use + // makeSampleEnv and then change the mode on the ep. + ep.runMode = ModeSig // By default, test ledger uses an oddball round, ask it what round it's // going to use and prep fv, lv accordingly. @@ -339,8 +385,8 @@ func TestWrongProtoVersion(t *testing.T) { for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, "int 1", v) - ep := defaultEvalParamsWithVersion(0) - testAppBytes(t, ops.Program, ep, "LogicSig not supported", "LogicSig not supported") + ep := defaultSigParamsWithVersion(0) + testLogicBytes(t, ops.Program, ep, "LogicSig not supported", "LogicSig not supported") }) } } @@ -413,7 +459,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program txn.Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")} - ep := defaultEvalParams(txn) + ep := defaultSigParams(txn) err := CheckSignature(0, ep) require.NoError(t, err) pass, cx, err := EvalSignatureFull(0, ep) @@ -436,8 +482,7 @@ end: `, v) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - ep := defaultEvalParams(txn) - err := CheckSignature(0, ep) + err := CheckSignature(0, defaultSigParams(txn)) require.NoError(t, err) }) } @@ -448,7 +493,7 @@ return `, v) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - ep := defaultEvalParams(txn) + ep := defaultSigParams(txn) err := CheckSignature(0, ep) require.NoError(t, err) }) @@ -459,8 +504,7 @@ return pushint := OpsByName[LogicVersion]["pushint"] var txn transactions.SignedTxn txn.Lsig.Logic = []byte{LogicVersion, pushint.Opcode, 0x01} - ep := defaultEvalParams(txn) - err := CheckSignature(0, ep) + err := CheckSignature(0, defaultSigParams(txn)) require.NoError(t, err) } @@ -515,7 +559,7 @@ func TestTLHC(t *testing.T) { txn.Lsig.Args = [][]byte{secret} txn.Txn.FirstValid = 999999 block := bookkeeping.Block{} - ep := defaultEvalParams(txn) + ep := defaultSigParams(txn) err := CheckSignature(0, ep) if err != nil { t.Log(hex.EncodeToString(ops.Program)) @@ -534,7 +578,7 @@ func TestTLHC(t *testing.T) { txn.Txn.Receiver = a2 txn.Txn.CloseRemainderTo = a2 - ep = defaultEvalParams(txn) + ep = defaultSigParams(txn) pass, err = EvalSignature(0, ep) if !pass { t.Log(hex.EncodeToString(ops.Program)) @@ -546,7 +590,7 @@ func TestTLHC(t *testing.T) { txn.Txn.Receiver = a2 txn.Txn.CloseRemainderTo = a2 txn.Txn.FirstValid = 1 - ep = defaultEvalParams(txn) + ep = defaultSigParams(txn) pass, err = EvalSignature(0, ep) if pass { t.Log(hex.EncodeToString(ops.Program)) @@ -558,7 +602,7 @@ func TestTLHC(t *testing.T) { txn.Txn.Receiver = a1 txn.Txn.CloseRemainderTo = a1 txn.Txn.FirstValid = 999999 - ep = defaultEvalParams(txn) + ep = defaultSigParams(txn) pass, err = EvalSignature(0, ep) if !pass { t.Log(hex.EncodeToString(ops.Program)) @@ -570,7 +614,7 @@ func TestTLHC(t *testing.T) { // wrong answer txn.Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849a")} block.BlockHeader.Round = 1 - ep = defaultEvalParams(txn) + ep = defaultSigParams(txn) pass, err = EvalSignature(0, ep) if pass { t.Log(hex.EncodeToString(ops.Program)) @@ -1008,7 +1052,7 @@ func TestTxnBadField(t *testing.T) { t.Parallel() program := []byte{0x01, 0x31, 0x7f} - testLogicBytes(t, program, defaultEvalParams(), "invalid txn field") + testLogicBytes(t, program, nil, "invalid txn field") // TODO: Check should know the type stack was wrong // test txn does not accept ApplicationArgs and Accounts @@ -1021,7 +1065,7 @@ func TestTxnBadField(t *testing.T) { ops := testProg(t, source, AssemblerMaxVersion) require.Equal(t, txnaOpcode, ops.Program[1]) ops.Program[1] = txnOpcode - testLogicBytes(t, ops.Program, defaultEvalParams(), fmt.Sprintf("invalid txn field %s", field)) + testLogicBytes(t, ops.Program, nil, fmt.Sprintf("invalid txn field %s", field)) } } @@ -1030,7 +1074,7 @@ func TestGtxnBadIndex(t *testing.T) { t.Parallel() program := []byte{0x01, 0x33, 0x1, 0x01} - testLogicBytes(t, program, defaultEvalParams(), "txn index 1") + testLogicBytes(t, program, nil, "txn index 1") } func TestGtxnBadField(t *testing.T) { @@ -1039,7 +1083,7 @@ func TestGtxnBadField(t *testing.T) { t.Parallel() program := []byte{0x01, 0x33, 0x0, 127} // TODO: Check should know the type stack was wrong - testLogicBytes(t, program, defaultEvalParams(), "invalid txn field TxnField(127)") + testLogicBytes(t, program, nil, "invalid txn field TxnField(127)") // test gtxn does not accept ApplicationArgs and Accounts txnOpcode := OpsByName[LogicVersion]["txn"].Opcode @@ -1051,7 +1095,7 @@ func TestGtxnBadField(t *testing.T) { ops := testProg(t, source, AssemblerMaxVersion) require.Equal(t, txnaOpcode, ops.Program[1]) ops.Program[1] = txnOpcode - testLogicBytes(t, ops.Program, defaultEvalParams(), fmt.Sprintf("invalid txn field %s", field)) + testLogicBytes(t, ops.Program, nil, fmt.Sprintf("invalid txn field %s", field)) } } @@ -1060,7 +1104,7 @@ func TestGlobalBadField(t *testing.T) { t.Parallel() program := []byte{0x01, 0x32, 127} - testLogicBytes(t, program, defaultEvalParams(), "invalid global field") + testLogicBytes(t, program, nil, "invalid global field") } func TestArg(t *testing.T) { @@ -1083,7 +1127,7 @@ func TestArg(t *testing.T) { []byte("aoeu4"), } ops := testProg(t, source, v) - testLogicBytes(t, ops.Program, defaultEvalParams(txn)) + testLogicBytes(t, ops.Program, defaultSigParams(txn)) }) } } @@ -1232,7 +1276,7 @@ func TestGlobal(t *testing.T) { } appcall.Txn.Group = crypto.Digest{0x07, 0x06} - ep := defaultEvalParams(appcall) + ep := defaultAppParams(appcall) ep.Ledger = ledger testApp(t, tests[v].program, ep) }) @@ -1277,11 +1321,11 @@ int %s txn := transactions.SignedTxn{} txn.Txn.Type = tt if v < appsEnabledVersion && tt == protocol.ApplicationCallTx { - testLogicBytes(t, ops.Program, defaultEvalParams(txn), + testLogicBytes(t, ops.Program, defaultSigParams(txn), "program version must be", "program version must be") return } - testLogicBytes(t, ops.Program, defaultEvalParams(txn)) + testLogicBytes(t, ops.Program, defaultSigParams(txn)) }) } }) @@ -1857,7 +1901,6 @@ func TestTxn(t *testing.T) { } txn.Txn.ApprovalProgram = ops.Program txn.Txn.ClearStateProgram = clearOps.Program - txn.Lsig.Logic = ops.Program txn.Txn.ExtraProgramPages = 2 // RekeyTo not allowed in v1 if v < rekeyingEnabledVersion { @@ -1880,25 +1923,26 @@ func TestTxn(t *testing.T) { clearProgramHash[:], } // Since we test GroupIndex ==3, we need a larger group - ep := defaultEvalParams(txn, txn, txn, txn) - ep.TxnGroup[2].EvalDelta.Logs = []string{"x", "prefilled"} + sep, aep := defaultEvalParams(txn, txn, txn, txn) if v < txnEffectsVersion { - testLogicFull(t, ops.Program, 3, ep) + testLogicFull(t, ops.Program, 3, sep) } else { // Starting in txnEffectsVersion, there are fields we can't access in Logic mode - testLogicFull(t, ops.Program, 3, ep, "not allowed in current mode") + testLogicFull(t, ops.Program, 3, sep, "not allowed in current mode") // And the early tests use "arg" a lot - not allowed in stateful. So remove those tests. lastArg := strings.Index(source, "arg 10\n==\n&&") require.NotEqual(t, -1, lastArg) + source = source[lastArg+12:] - appSafe := "int 1" + strings.Replace(source[lastArg+12:], `txn Sender + aep.TxnGroup[2].EvalDelta.Logs = []string{"x", "prefilled"} // allows gtxn 2 NumLogs + appSafe := "int 1" + strings.Replace(source, `txn Sender int 0 args == assert`, "", 1) ops := testProg(t, appSafe, v) - testAppFull(t, ops.Program, 3, basics.AppIndex(888), ep) + testAppFull(t, ops.Program, 3, basics.AppIndex(888), aep) } }) } @@ -1949,7 +1993,7 @@ return ` ops := testProg(t, cachedTxnProg, 2) - ep, _, _ := makeSampleEnv() + ep := defaultSigParams(makeSampleTxnGroup()...) txid0 := ep.TxnGroup[0].ID() txid1 := ep.TxnGroup[1].ID() ep.TxnGroup[0].Lsig.Args = [][]byte{ @@ -1959,50 +2003,6 @@ return testLogicBytes(t, ops.Program, ep) } -func TestGaid(t *testing.T) { - partitiontest.PartitionTest(t) - - t.Parallel() - check0 := testProg(t, "gaid 0; int 100; ==", 4) - appTxn := makeSampleTxn() - appTxn.Txn.Type = protocol.ApplicationCallTx - targetTxn := makeSampleTxn() - targetTxn.Txn.Type = protocol.AssetConfigTx - ep := defaultEvalParams(targetTxn, appTxn, makeSampleTxn()) - ep.Ledger = NewLedger(nil) - - // should fail when no creatable was created - _, err := EvalApp(check0.Program, 1, 888, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "did not create anything") - - ep.TxnGroup[0].ApplyData.ConfigAsset = 100 - pass, err := EvalApp(check0.Program, 1, 888, ep) - if !pass || err != nil { - t.Log(ep.Trace.String()) - } - require.NoError(t, err) - require.True(t, pass) - - // should fail when accessing future transaction in group - check2 := testProg(t, "gaid 2; int 0; >", 4) - _, err = EvalApp(check2.Program, 1, 888, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "gaid can't get creatable ID of txn ahead of the current one") - - // should fail when accessing self - _, err = EvalApp(check0.Program, 0, 888, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "gaid is only for accessing creatable IDs of previous txns") - - // should fail on non-creatable - ep.TxnGroup[0].Txn.Type = protocol.PaymentTx - _, err = EvalApp(check0.Program, 1, 888, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "can't use gaid on txn that is not an app call nor an asset config txn") - ep.TxnGroup[0].Txn.Type = protocol.AssetConfigTx -} - func TestGtxn(t *testing.T) { partitiontest.PartitionTest(t) @@ -2128,7 +2128,7 @@ gtxn 0 Sender txn.Txn.SelectionPK[:], txn.Txn.Note, } - ep := defaultEvalParams(makeSampleTxnGroup(txn)...) + ep := defaultSigParams(makeSampleTxnGroup(txn)...) testLogic(t, source, v, ep) if v >= 3 { gtxnsProg := strings.ReplaceAll(source, "gtxn 0", "int 0; gtxns") @@ -2151,9 +2151,15 @@ func testLogic(t *testing.T, program string, v uint64, ep *EvalParams, problems func testLogicBytes(t *testing.T, program []byte, ep *EvalParams, problems ...string) { t.Helper() + if ep == nil { + ep = defaultSigParams() + } else { + ep.reset() + } testLogicFull(t, program, 0, ep, problems...) } +// testLogicFull is the lowest-level so it does not create an ep or reset it. func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problems ...string) { t.Helper() @@ -2170,16 +2176,14 @@ func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problem require.Fail(t, "Misused testLogic: %d problems", len(problems)) } - sb := &strings.Builder{} - ep.Trace = sb + ep.Trace = &strings.Builder{} - ep.TxnGroup[0].Lsig.Logic = program + ep.TxnGroup[gi].Lsig.Logic = program err := CheckSignature(gi, ep) if checkProblem == "" { - require.NoError(t, err, sb.String()) + require.NoError(t, err, "Error in CheckSignature %v", ep.Trace) } else { - require.Error(t, err, "Check\n%s\nExpected: %v", sb, checkProblem) - require.Contains(t, err.Error(), checkProblem) + require.ErrorContains(t, err, checkProblem, "Wrong error in CheckSignature %v", ep.Trace) } // We continue on to check Eval() of things that failed Check() because it's @@ -2189,21 +2193,45 @@ func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problem pass, err := EvalSignature(gi, ep) if evalProblem == "" { - require.NoError(t, err, "Eval%s\nExpected: PASS", sb) - assert.True(t, pass, "Eval%s\nExpected: PASS", sb) + require.NoError(t, err, "Eval\n%sExpected: PASS", ep.Trace) + assert.True(t, pass, "Eval\n%sExpected: PASS", ep.Trace) return } // There is an evalProblem to check. REJECT is special and only means that // the app didn't accept. Maybe it's an error, maybe it's just !pass. if evalProblem == "REJECT" { - require.True(t, err != nil || !pass, "Eval%s\nExpected: REJECT", sb) + require.True(t, err != nil || !pass, "Eval\n%sExpected: REJECT", ep.Trace) } else { - require.Error(t, err, "Eval%s\nExpected: %v", sb, evalProblem) - require.Contains(t, err.Error(), evalProblem) + require.ErrorContains(t, err, evalProblem, "Wrong error in EvalSignature %v", ep.Trace) } } +func testLogics(t *testing.T, programs []string, txgroup []transactions.SignedTxn, opt protoOpt, expected ...expect) *EvalParams { + t.Helper() + proto := makeTestProto(opt) + + if txgroup == nil { + for range programs { + txgroup = append(txgroup, makeSampleTxn()) + } + } + // Place the logicsig code first, so NewSigEvalParams calcs budget + for i, program := range programs { + if program != "" { + code := testProg(t, program, proto.LogicSigVersion).Program + txgroup[i].Lsig.Logic = code + } + } + ep := NewSigEvalParams(txgroup, proto, &NoHeaderLedger{}) + for i, program := range programs { + if program != "" { + testLogicFull(t, txgroup[i].Lsig.Logic, i, ep) + } + } + return ep +} + func TestTxna(t *testing.T) { partitiontest.PartitionTest(t) @@ -2217,7 +2245,7 @@ txna ApplicationArgs 0 txn.Txn.Accounts = make([]basics.Address, 1) txn.Txn.Accounts[0] = txn.Txn.Sender txn.Txn.ApplicationArgs = [][]byte{txn.Txn.Sender[:]} - ep := defaultEvalParams(txn) + ep := defaultSigParams(txn) testLogicBytes(t, ops.Program, ep) // modify txn field @@ -2251,8 +2279,7 @@ txn Sender ops2 := testProg(t, source, AssemblerMaxVersion) var txn2 transactions.SignedTxn copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) - ep2 := defaultEvalParams(txn2) - testLogicBytes(t, ops2.Program, ep2) + testLogicBytes(t, ops2.Program, defaultSigParams(txn2)) // check gtxna source = `gtxna 0 Accounts 1 @@ -2292,8 +2319,7 @@ txn Sender ops3 := testProg(t, source, AssemblerMaxVersion) var txn3 transactions.SignedTxn copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) - ep3 := defaultEvalParams(txn3) - testLogicBytes(t, ops3.Program, ep3) + testLogicBytes(t, ops3.Program, defaultSigParams(txn3)) } // check empty values in ApplicationArgs and Account @@ -2311,10 +2337,10 @@ int 0 var txn transactions.SignedTxn txn.Txn.ApplicationArgs = make([][]byte, 1) txn.Txn.ApplicationArgs[0] = []byte("") - testLogicBytes(t, ops.Program, defaultEvalParams(txn)) + testLogicBytes(t, ops.Program, defaultSigParams(txn)) txn.Txn.ApplicationArgs[0] = nil - testLogicBytes(t, ops.Program, defaultEvalParams(txn)) + testLogicBytes(t, ops.Program, defaultSigParams(txn)) source2 := `txna Accounts 1 global ZeroAddress @@ -2325,10 +2351,10 @@ global ZeroAddress var txn2 transactions.SignedTxn txn2.Txn.Accounts = make([]basics.Address, 1) txn2.Txn.Accounts[0] = basics.Address{} - testLogicBytes(t, ops.Program, defaultEvalParams(txn2)) + testLogicBytes(t, ops.Program, defaultSigParams(txn2)) txn2.Txn.Accounts = make([]basics.Address, 1) - testLogicBytes(t, ops.Program, defaultEvalParams(txn2)) + testLogicBytes(t, ops.Program, defaultSigParams(txn2)) } func TestTxnBigPrograms(t *testing.T) { @@ -2354,14 +2380,14 @@ int 1 for i := range txn.Txn.ApprovalProgram { txn.Txn.ApprovalProgram[i] = byte(i % 7) } - testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(txn)) + testLogic(t, source, AssemblerMaxVersion, defaultSigParams(txn)) - testLogic(t, `txna ApprovalProgramPages 2`, AssemblerMaxVersion, defaultEvalParams(txn), + testLogic(t, `txna ApprovalProgramPages 2`, AssemblerMaxVersion, defaultSigParams(txn), "invalid ApprovalProgramPages index") // ClearStateProgram is not in the txn at all - testLogic(t, `txn NumClearStateProgramPages; !`, AssemblerMaxVersion, defaultEvalParams(txn)) - testLogic(t, `txna ClearStateProgramPages 0`, AssemblerMaxVersion, defaultEvalParams(txn), + testLogic(t, `txn NumClearStateProgramPages; !`, AssemblerMaxVersion, defaultSigParams(txn)) + testLogic(t, `txna ClearStateProgramPages 0`, AssemblerMaxVersion, defaultSigParams(txn), "invalid ClearStateProgramPages index") } @@ -2381,7 +2407,7 @@ txnas ApplicationArgs txn.Txn.Accounts = make([]basics.Address, 1) txn.Txn.Accounts[0] = txn.Txn.Sender txn.Txn.ApplicationArgs = [][]byte{txn.Txn.Sender[:]} - ep := defaultEvalParams(txn) + ep := defaultSigParams(txn) testLogicBytes(t, ops.Program, ep) // check special case: Account 0 == Sender @@ -2394,7 +2420,7 @@ txn Sender ops = testProg(t, source, AssemblerMaxVersion) var txn2 transactions.SignedTxn copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) - testLogicBytes(t, ops.Program, defaultEvalParams(txn2)) + testLogicBytes(t, ops.Program, defaultSigParams(txn2)) // check gtxnas source = `int 1 @@ -2414,7 +2440,7 @@ txn Sender ops = testProg(t, source, AssemblerMaxVersion) var txn3 transactions.SignedTxn copy(txn3.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) - testLogicBytes(t, ops.Program, defaultEvalParams(txn3)) + testLogicBytes(t, ops.Program, defaultSigParams(txn3)) // check gtxnsas source = `int 0 @@ -2780,9 +2806,9 @@ func TestGload(t *testing.T) { } if testCase.errContains != "" { - testApps(t, sources, txgroup, LogicVersion, nil, exp(testCase.errTxn, testCase.errContains)) + testApps(t, sources, txgroup, nil, nil, exp(testCase.errTxn, testCase.errContains)) } else { - testApps(t, sources, txgroup, LogicVersion, nil) + testApps(t, sources, txgroup, nil, nil) } }) } @@ -2826,15 +2852,12 @@ func TestGload(t *testing.T) { }, } - ep := defaultEvalParams(failCase.firstTxn, appcall) - ep.SigLedger = NewLedger(nil) - program := testProg(t, "gload 0 0", AssemblerMaxVersion).Program switch failCase.runMode { case ModeApp: - testAppBytes(t, program, ep, failCase.errContains) + testAppBytes(t, program, defaultAppParams(failCase.firstTxn, appcall), failCase.errContains) default: - testLogicBytes(t, program, ep, failCase.errContains, failCase.errContains) + testLogicBytes(t, program, defaultSigParams(failCase.firstTxn, appcall), failCase.errContains, failCase.errContains) } }) } @@ -2888,7 +2911,7 @@ int 1 txgroup[j].Txn.Type = protocol.ApplicationCallTx } - testApps(t, sources, txgroup, LogicVersion, nil) + testApps(t, sources, txgroup, nil, nil) } const testCompareProgramText = `int 35 @@ -2977,19 +3000,19 @@ func TestSlowLogic(t *testing.T) { // v1overspend fails (on v1) ops := testProg(t, v1overspend, 1) // We should never Eval this after it fails Check(), but nice to see it also fails. - testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(1), + testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(1), "static cost", "dynamic cost") // v2overspend passes Check, even on v2 proto, because the old low cost is "grandfathered" ops = testProg(t, v2overspend, 1) - testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(2)) + testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(2)) // even the shorter, v2overspend, fails when compiled as v2 code ops = testProg(t, v2overspend, 2) - testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(2), + testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(2), "static cost", "dynamic cost") // in v4 cost is still 134, but only matters in Eval, not Check, so both fail there - ep4 := defaultEvalParamsWithVersion(4) + ep4 := defaultSigParamsWithVersion(4) ops = testProg(t, v1overspend, 4) testLogicBytes(t, ops.Program, ep4, "dynamic cost") @@ -2997,6 +3020,31 @@ func TestSlowLogic(t *testing.T) { testLogicBytes(t, ops.Program, ep4, "dynamic cost") } +func TestSigBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + source := func(budget int) string { + return fmt.Sprintf(` +global OpcodeBudget +int %d +== +assert +global OpcodeBudget +int %d +== +`, budget-1, budget-5) + } + testLogic(t, source(20000), LogicVersion, nil) + + testLogics(t, []string{source(40000), source(39993)}, nil, nil) + + testLogics(t, []string{source(60000), source(59993), ""}, nil, nil) + + testLogics(t, []string{source(20000), source(20000)}, nil, + func(p *config.ConsensusParams) { p.EnableLogicSigCostPooling = false }) +} + func isNotPanic(t *testing.T, err error) { if err == nil { return @@ -3014,7 +3062,7 @@ func TestStackUnderflow(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `int 1`, v) ops.Program = append(ops.Program, 0x08) // + - testLogicBytes(t, ops.Program, defaultEvalParams(), "stack underflow") + testLogicBytes(t, ops.Program, nil, "stack underflow") }) } } @@ -3027,7 +3075,7 @@ func TestWrongStackTypeRuntime(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `int 1`, v) ops.Program = append(ops.Program, 0x01, 0x15) // sha256, len - testLogicBytes(t, ops.Program, defaultEvalParams(), "sha256 arg 0 wanted") + testLogicBytes(t, ops.Program, nil, "sha256 arg 0 wanted") }) } } @@ -3040,7 +3088,7 @@ func TestEqMismatch(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `byte 0x1234; int 1`, v) ops.Program = append(ops.Program, 0x12) // == - testLogicBytes(t, ops.Program, defaultEvalParams(), "cannot compare") + testLogicBytes(t, ops.Program, nil, "cannot compare") // TODO: Check should know the type stack was wrong }) } @@ -3054,7 +3102,7 @@ func TestNeqMismatch(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `byte 0x1234; int 1`, v) ops.Program = append(ops.Program, 0x13) // != - testLogicBytes(t, ops.Program, defaultEvalParams(), "cannot compare") + testLogicBytes(t, ops.Program, nil, "cannot compare") }) } } @@ -3067,7 +3115,7 @@ func TestWrongStackTypeRuntime2(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `byte 0x1234; int 1`, v) ops.Program = append(ops.Program, 0x08) // + - testLogicBytes(t, ops.Program, defaultEvalParams(), "+ arg 0 wanted") + testLogicBytes(t, ops.Program, nil, "+ arg 0 wanted") }) } } @@ -3085,7 +3133,7 @@ func TestIllegalOp(t *testing.T) { break } } - testLogicBytes(t, ops.Program, defaultEvalParams(), "illegal opcode", "illegal opcode") + testLogicBytes(t, ops.Program, nil, "illegal opcode", "illegal opcode") }) } } @@ -3103,7 +3151,7 @@ int 1 `, v) // cut two last bytes - intc_1 and last byte of bnz ops.Program = ops.Program[:len(ops.Program)-2] - testLogicBytes(t, ops.Program, defaultEvalParams(), + testLogicBytes(t, ops.Program, nil, "bnz program ends short", "bnz program ends short") }) } @@ -3118,7 +3166,7 @@ intc 0 intc 0 bnz done done:`, 2) - testLogicBytes(t, ops.Program, defaultEvalParams()) + testLogicBytes(t, ops.Program, nil) } func TestShortBytecblock(t *testing.T) { @@ -3133,8 +3181,7 @@ func TestShortBytecblock(t *testing.T) { for i := 2; i < len(fullops.Program); i++ { program := fullops.Program[:i] t.Run(hex.EncodeToString(program), func(t *testing.T) { - testLogicBytes(t, program, defaultEvalParams(), - "bytes list", "bytes list") + testLogicBytes(t, program, nil, "bytes list", "bytes list") }) } }) @@ -3157,7 +3204,7 @@ func TestShortBytecblock2(t *testing.T) { t.Parallel() program, err := hex.DecodeString(src) require.NoError(t, err) - testLogicBytes(t, program, defaultEvalParams(), "const bytes list", "const bytes list") + testLogicBytes(t, program, nil, "const bytes list", "const bytes list") }) } } @@ -3218,6 +3265,11 @@ func withPanicOpcode(t *testing.T, version uint64, panicDuringCheck bool, f func func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode partitiontest.PartitionTest(t) + // These tests would generate a lot of log noise which shows up if *other* + // tests fail. So it's pretty annoying to run `go test` on the whole + // package. `logSink` swallows log messages. + logSink := logging.NewLogger() + logSink.SetOutput(io.Discard) for v := uint64(1); v <= AssemblerMaxVersion; v++ { v := v t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode @@ -3225,9 +3277,10 @@ func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode ops := testProg(t, `int 1`, v) ops.Program = append(ops.Program, opcode) - params := defaultEvalParams() - params.TxnGroup[0].Lsig.Logic = ops.Program - err := CheckSignature(0, params) + ep := defaultSigParams() + ep.logger = logSink + ep.TxnGroup[0].Lsig.Logic = ops.Program + err := CheckSignature(0, ep) var pe panicError require.ErrorAs(t, err, &pe) require.Equal(t, panicString, pe.PanicValue) @@ -3235,11 +3288,12 @@ func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - params = defaultEvalParams(txn) - pass, err := EvalSignature(0, params) + ep = defaultSigParams(txn) + ep.logger = logSink + pass, err := EvalSignature(0, ep) if pass { t.Log(hex.EncodeToString(ops.Program)) - t.Log(params.Trace.String()) + t.Log(ep.Trace.String()) } require.False(t, pass) require.ErrorAs(t, err, &pe) @@ -3252,9 +3306,9 @@ func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode Type: protocol.ApplicationCallTx, }, } - params = defaultEvalParams(txn) - params.Ledger = NewLedger(nil) - pass, err = EvalApp(ops.Program, 0, 1, params) + ep := defaultAppParams(txn) + ep.logger = logSink + pass, err = EvalApp(ops.Program, 0, 1, ep) require.False(t, pass) require.ErrorAs(t, err, &pe) require.Equal(t, panicString, pe.PanicValue) @@ -3270,8 +3324,8 @@ func TestProgramTooNew(t *testing.T) { t.Parallel() var program [12]byte - vlen := binary.PutUvarint(program[:], evalMaxVersion+1) - testLogicBytes(t, program[:vlen], defaultEvalParams(), + vlen := binary.PutUvarint(program[:], LogicVersion+1) + testLogicBytes(t, program[:vlen], nil, "greater than max supported", "greater than max supported") } @@ -3281,7 +3335,7 @@ func TestInvalidVersion(t *testing.T) { t.Parallel() program, err := hex.DecodeString("ffffffffffffffffffffffff") require.NoError(t, err) - testLogicBytes(t, program, defaultEvalParams(), "invalid version", "invalid version") + testLogicBytes(t, program, nil, "invalid version", "invalid version") } func TestProgramProtoForbidden(t *testing.T) { @@ -3289,11 +3343,8 @@ func TestProgramProtoForbidden(t *testing.T) { t.Parallel() var program [12]byte - vlen := binary.PutUvarint(program[:], evalMaxVersion) - ep := defaultEvalParams() - ep.Proto = &config.ConsensusParams{ - LogicSigVersion: evalMaxVersion - 1, - } + vlen := binary.PutUvarint(program[:], LogicVersion) + ep := defaultSigParamsWithVersion(LogicVersion - 1) testLogicBytes(t, program[:vlen], ep, "greater than protocol", "greater than protocol") } @@ -3315,16 +3366,16 @@ int 1`, v) require.Equal(t, ops.Program, canonicalProgramBytes) ops.Program[7] = 3 // clobber the branch offset to be in the middle of the bytecblock // Since Eval() doesn't know the jump is bad, we reject "by luck" - testLogicBytes(t, ops.Program, defaultEvalParams(), "aligned", "REJECT") + testLogicBytes(t, ops.Program, nil, "aligned", "REJECT") // back branches are checked differently, so test misaligned back branch ops.Program[6] = 0xff // Clobber the two bytes of offset with 0xff 0xff = -1 ops.Program[7] = 0xff // That jumps into the offset itself (pc + 3 -1) if v < backBranchEnabledVersion { - testLogicBytes(t, ops.Program, defaultEvalParams(), "negative branch", "negative branch") + testLogicBytes(t, ops.Program, nil, "negative branch", "negative branch") } else { // Again, if we were ever to Eval(), we would not know it's wrong. But we reject here "by luck" - testLogicBytes(t, ops.Program, defaultEvalParams(), "back branch target", "REJECT") + testLogicBytes(t, ops.Program, nil, "back branch target", "REJECT") } }) } @@ -3347,8 +3398,7 @@ int 1`, v) require.NoError(t, err) require.Equal(t, ops.Program, canonicalProgramBytes) ops.Program[7] = 200 // clobber the branch offset to be beyond the end of the program - testLogicBytes(t, ops.Program, defaultEvalParams(), - "outside of program", "outside of program") + testLogicBytes(t, ops.Program, nil, "outside of program", "outside of program") }) } } @@ -3371,7 +3421,7 @@ int 1`, v) require.NoError(t, err) require.Equal(t, ops.Program, canonicalProgramBytes) ops.Program[6] = 0x70 // clobber hi byte of branch offset - testLogicBytes(t, ops.Program, defaultEvalParams(), "outside", "outside") + testLogicBytes(t, ops.Program, nil, "outside", "outside") }) } branches := []string{ @@ -3393,8 +3443,7 @@ intc_1 require.NoError(t, err) ops.Program[7] = 0xf0 // clobber the branch offset - highly negative ops.Program[8] = 0xff // clobber the branch offset - testLogicBytes(t, ops.Program, defaultEvalParams(), - "outside of program", "outside of program") + testLogicBytes(t, ops.Program, nil, "outside of program", "outside of program") }) } } @@ -3683,10 +3732,10 @@ func evalLoop(b *testing.B, runs int, program []byte) { for i := 0; i < runs; i++ { var txn transactions.SignedTxn txn.Lsig.Logic = program - pass, err := EvalSignature(0, benchmarkEvalParams(txn)) + pass, err := EvalSignature(0, benchmarkSigParams(txn)) if !pass { // rerun to trace it. tracing messes up timing too much - ep := benchmarkEvalParams(txn) + ep := benchmarkSigParams(txn) ep.Trace = &strings.Builder{} pass, err = EvalSignature(0, ep) b.Log(ep.Trace.String()) @@ -3998,7 +4047,7 @@ func BenchmarkCheckx5(b *testing.B) { for _, program := range programs { var txn transactions.SignedTxn txn.Lsig.Logic = program - err := CheckSignature(0, defaultEvalParams(txn)) + err := CheckSignature(0, defaultSigParams(txn)) if err != nil { require.NoError(b, err) } @@ -4102,17 +4151,15 @@ pop txn.Lsig.Logic = ops.Program txn.Txn.ApplicationArgs = [][]byte{[]byte("test")} - ep := defaultEvalParams(txn) - testLogicBytes(t, ops.Program, ep) + testLogicBytes(t, ops.Program, defaultSigParams(txn)) - ep = defaultEvalParamsWithVersion(1, txn) - testLogicBytes(t, ops.Program, ep, + testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(1, txn), "greater than protocol supported version 1", "greater than protocol supported version 1") // hack the version and fail on illegal opcode ops.Program[0] = 0x1 - ep = defaultEvalParamsWithVersion(1, txn) - testLogicBytes(t, ops.Program, ep, "illegal opcode 0x36", "illegal opcode 0x36") // txna + testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(1, txn), + "illegal opcode 0x36", "illegal opcode 0x36") // txna } func TestStackOverflow(t *testing.T) { @@ -4208,26 +4255,6 @@ func TestArgType(t *testing.T) { require.Equal(t, avmUint64, sv.avmType()) } -func TestApplicationsDisallowOldTeal(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - const source = "int 1" - - txn := makeSampleTxn() - txn.Txn.Type = protocol.ApplicationCallTx - txn.Txn.RekeyTo = basics.Address{} - ep := defaultEvalParams(txn) - - for v := uint64(0); v < appsEnabledVersion; v++ { - ops := testProg(t, source, v) - e := fmt.Sprintf("program version must be >= %d", appsEnabledVersion) - testAppBytes(t, ops.Program, ep, e, e) - } - - testApp(t, source, ep) -} - func TestAnyRekeyToOrApplicationRaisesMinAvmVersion(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -4272,29 +4299,28 @@ func TestAnyRekeyToOrApplicationRaisesMinAvmVersion(t *testing.T) { ci, cse := ci, cse t.Run(fmt.Sprintf("ci=%d", ci), func(t *testing.T) { t.Parallel() - ep := defaultEvalParams(cse.group...) + sep, aep := defaultEvalParams(cse.group...) // Computed MinAvmVersion should be == validFromVersion - calc := ComputeMinAvmVersion(ep.TxnGroup) + calc := computeMinAvmVersion(sep.TxnGroup) + require.Equal(t, calc, cse.validFromVersion) + + calc = computeMinAvmVersion(aep.TxnGroup) require.Equal(t, calc, cse.validFromVersion) // Should fail for all versions < validFromVersion expected := fmt.Sprintf("program version must be >= %d", cse.validFromVersion) for v := uint64(0); v < cse.validFromVersion; v++ { ops := testProg(t, source, v) - if ep.supportsAppEval() { - testAppBytes(t, ops.Program, ep, expected, expected) - } - testLogicBytes(t, ops.Program, ep, expected, expected) + testAppBytes(t, ops.Program, aep, expected, expected) + testLogicBytes(t, ops.Program, sep, expected, expected) } // Should succeed for all versions >= validFromVersion for v := cse.validFromVersion; v <= AssemblerMaxVersion; v++ { ops := testProg(t, source, v) - if ep.supportsAppEval() { - testAppBytes(t, ops.Program, ep) - } - testLogicBytes(t, ops.Program, ep) + testAppBytes(t, ops.Program, aep) + testLogicBytes(t, ops.Program, sep) } }) } @@ -4339,7 +4365,7 @@ func TestAllowedOpcodesV2(t *testing.T) { "gtxn": true, } - ep := defaultEvalParamsWithVersion(2) + sep, aep := defaultEvalParamsWithVersion(2) cnt := 0 for _, spec := range OpSpecs { @@ -4349,9 +4375,9 @@ func TestAllowedOpcodesV2(t *testing.T) { require.Contains(t, source, spec.Name) ops := testProg(t, source, 2) // all opcodes allowed in stateful mode so use CheckStateful/EvalContract - err := CheckContract(ops.Program, ep) + err := CheckContract(ops.Program, aep) require.NoError(t, err, source) - _, err = EvalApp(ops.Program, 0, 0, ep) + _, err = EvalApp(ops.Program, 0, 0, aep) if spec.Name != "return" { // "return" opcode always succeeds so ignore it require.Error(t, err, source) @@ -4360,8 +4386,11 @@ func TestAllowedOpcodesV2(t *testing.T) { for v := byte(0); v <= 1; v++ { ops.Program[0] = v - testLogicBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode") - testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode") + testLogicBytes(t, ops.Program, sep, "illegal opcode", "illegal opcode") + // let the program run even though minAvmVersion would ban it, + // so we can have this sanity check + aep.minAvmVersion = uint64(v) + testAppBytes(t, ops.Program, aep, "illegal opcode", "illegal opcode") } cnt++ } @@ -4372,7 +4401,6 @@ func TestAllowedOpcodesV2(t *testing.T) { // check all v3 opcodes: allowed in v3 and not allowed before func TestAllowedOpcodesV3(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() // all tests are expected to fail in evaluation @@ -4392,7 +4420,7 @@ func TestAllowedOpcodesV3(t *testing.T) { "pushbytes": `pushbytes "stringsfail?"`, } - ep := defaultEvalParamsWithVersion(3) + sep, aep := defaultEvalParamsWithVersion(3) cnt := 0 for _, spec := range OpSpecs { @@ -4402,12 +4430,15 @@ func TestAllowedOpcodesV3(t *testing.T) { require.Contains(t, source, spec.Name) ops := testProg(t, source, 3) // all opcodes allowed in stateful mode so use CheckStateful/EvalContract - testAppBytes(t, ops.Program, ep, "REJECT") + testAppBytes(t, ops.Program, aep, "REJECT") - for v := byte(0); v <= 1; v++ { + for v := byte(0); v <= 2; v++ { ops.Program[0] = v - testLogicBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode") - testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode") + testLogicBytes(t, ops.Program, sep, "illegal opcode", "illegal opcode") + // let the program run even though minAvmVersion would ban it, + // so we can have this sanity check + aep.minAvmVersion = uint64(v) + testAppBytes(t, ops.Program, aep, "illegal opcode", "illegal opcode") } cnt++ } @@ -4437,9 +4468,8 @@ func TestRekeyFailsOnOldVersion(t *testing.T) { ops := testProg(t, "int 1", v) var txn transactions.SignedTxn txn.Txn.RekeyTo = basics.Address{1, 2, 3, 4} - ep := defaultEvalParams(txn) e := fmt.Sprintf("program version must be >= %d", rekeyingEnabledVersion) - testLogicBytes(t, ops.Program, ep, e, e) + testLogicBytes(t, ops.Program, defaultSigParams(txn), e, e) }) } } @@ -4478,13 +4508,13 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval t.Helper() var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - ep := defaultEvalParamsWithVersion(lv, txn) + ep := defaultSigParamsWithVersion(lv, txn) err := CheckSignature(0, ep) if err != nil { t.Log(ep.Trace.String()) } require.NoError(t, err) - ep = defaultEvalParamsWithVersion(lv, txn) + ep = defaultSigParamsWithVersion(lv, txn) pass, err := EvalSignature(0, ep) ok := tester(t, pass, err) if !ok { @@ -5061,116 +5091,6 @@ func TestBytesConversions(t *testing.T) { testAccepts(t, "byte 0x0011; byte 0x10; b+; btoi; int 0x21; ==", 4) } -func TestLog(t *testing.T) { - partitiontest.PartitionTest(t) - - t.Parallel() - var txn transactions.SignedTxn - txn.Txn.Type = protocol.ApplicationCallTx - ledger := NewLedger(nil) - ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{}) - ep := defaultEvalParams(txn) - ep.Proto = makeTestProtoV(LogicVersion) - ep.Ledger = ledger - testCases := []struct { - source string - loglen int - }{ - { - source: `byte "a logging message"; log; int 1`, - loglen: 1, - }, - { - source: `byte "a logging message"; log; byte "a logging message"; log; int 1`, - loglen: 2, - }, - { - source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log; `, maxLogCalls)), - loglen: maxLogCalls, - }, - { - source: `int 1; loop: byte "a logging message"; log; int 1; +; dup; int 30; <=; bnz loop;`, - loglen: 30, - }, - { - source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize)), - loglen: 1, - }, - } - - //track expected number of logs in cx.EvalDelta.Logs - for i, s := range testCases { - delta := testApp(t, s.source, ep) - require.Len(t, delta.Logs, s.loglen) - if i == len(testCases)-1 { - require.Equal(t, strings.Repeat("a", maxLogSize), delta.Logs[0]) - } else { - for _, l := range delta.Logs { - require.Equal(t, "a logging message", l) - } - } - } - - msg := strings.Repeat("a", 400) - failCases := []struct { - source string - runMode RunMode - errContains string - // For cases where assembly errors, we manually put in the bytes - assembledBytes []byte - }{ - { - source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize+1)), - errContains: fmt.Sprintf("> %d bytes limit", maxLogSize), - runMode: ModeApp, - }, - { - source: fmt.Sprintf(`byte "%s"; log; byte "%s"; log; byte "%s"; log; int 1`, msg, msg, msg), - errContains: fmt.Sprintf("> %d bytes limit", maxLogSize), - runMode: ModeApp, - }, - { - source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log; `, maxLogCalls+1)), - errContains: "too many log calls", - runMode: ModeApp, - }, - { - source: `int 1; loop: byte "a"; log; int 1; +; dup; int 35; <; bnz loop;`, - errContains: "too many log calls", - runMode: ModeApp, - }, - { - source: fmt.Sprintf(`int 1; loop: byte "%s"; log; int 1; +; dup; int 6; <; bnz loop;`, strings.Repeat(`a`, 400)), - errContains: fmt.Sprintf("> %d bytes limit", maxLogSize), - runMode: ModeApp, - }, - { - source: `load 0; log`, - errContains: "log arg 0 wanted []byte but got uint64", - runMode: ModeApp, - assembledBytes: []byte{byte(ep.Proto.LogicSigVersion), 0x34, 0x00, 0xb0}, - }, - { - source: `byte "a logging message"; log; int 1`, - errContains: "log not allowed in current mode", - runMode: ModeSig, - }, - } - - for _, c := range failCases { - switch c.runMode { - case ModeApp: - if c.assembledBytes == nil { - testApp(t, c.source, ep, c.errContains) - } else { - testAppBytes(t, c.assembledBytes, ep, c.errContains) - } - default: - testLogic(t, c.source, AssemblerMaxVersion, ep, c.errContains, c.errContains) - } - } -} - func TestPcDetails(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -5425,13 +5345,6 @@ func TestOpJSONRef(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - var txn transactions.SignedTxn - txn.Txn.Type = protocol.ApplicationCallTx - ledger := NewLedger(nil) - ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{}) - ep := defaultEvalParams(txn) - ep.Ledger = ledger - ep.SigLedger = ledger testCases := []struct { source string previousVersErrors []expect @@ -5601,15 +5514,7 @@ func TestOpJSONRef(t *testing.T) { } ops := testProg(t, s.source, AssemblerMaxVersion) - err := CheckContract(ops.Program, ep) - require.NoError(t, err, s) - - pass, _, err := EvalContract(ops.Program, 0, 888, ep) - require.NoError(t, err) - require.True(t, pass) - - // reset pooled budget for new "app call" - *ep.PooledApplicationBudget = ep.Proto.MaxAppProgramCost + testLogicBytes(t, ops.Program, defaultSigParams()) } failedCases := []struct { @@ -5813,16 +5718,7 @@ func TestOpJSONRef(t *testing.T) { } ops := testProg(t, s.source, AssemblerMaxVersion) - - err := CheckContract(ops.Program, ep) - require.NoError(t, err, s) - - pass, _, err := EvalContract(ops.Program, 0, 888, ep) - require.False(t, pass) - require.ErrorContains(t, err, s.error) - - // reset pooled budget for new "app call" - *ep.PooledApplicationBudget = ep.Proto.MaxAppProgramCost + testLogicBytes(t, ops.Program, defaultSigParams(), s.error) } } diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go index 1a21d9ff15..34ae9d9457 100644 --- a/data/transactions/logic/export_test.go +++ b/data/transactions/logic/export_test.go @@ -16,7 +16,12 @@ package logic -import "github.com/algorand/go-algorand/data/basics" +import ( + "testing" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" +) // Export for testing only. See // https://medium.com/@robiplus/golang-trick-export-for-test-aa16cbd7b8cd for a @@ -24,10 +29,6 @@ import "github.com/algorand/go-algorand/data/basics" // we export some extra things to make testing easier there. But we do it in a // _test.go file, so they are only exported during testing. -func (ep *EvalParams) Reset() { - ep.reset() -} - // Inefficient (hashing), just a testing convenience func (l *Ledger) CreateBox(app basics.AppIndex, name string, size uint64) { l.NewBox(app, name, make([]byte, size), app.Address()) @@ -40,23 +41,31 @@ func (l *Ledger) DelBoxes(app basics.AppIndex, names ...string) { } } -var DefaultEvalParams = defaultEvalParams +var DefaultSigParams = defaultSigParams +var DefaultAppParams = defaultAppParams var Exp = exp var MakeSampleEnv = makeSampleEnv var MakeSampleEnvWithVersion = makeSampleEnvWithVersion var MakeSampleTxn = makeSampleTxn var MakeSampleTxnGroup = makeSampleTxnGroup var MakeTestProto = makeTestProto -var MakeTestProtoV = makeTestProtoV var NoTrack = notrack var TestLogic = testLogic var TestApp = testApp var TestAppBytes = testAppBytes -var TestApps = testApps var TestLogicRange = testLogicRange var TestProg = testProg var WithPanicOpcode = withPanicOpcode +// TryApps exports "testApps" while accepting a simple uint64. Annoying, we +// can't export call this "TestApps" because it looks like a Test function with +// the wrong signature. But we can get that effect with the alias below. +func TryApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, ver uint64, ledger *Ledger, expected ...expect) *EvalParams { + return testApps(t, programs, txgroup, protoVer(ver), ledger, expected...) +} + +var TestApps = TryApps + const CreatedResourcesVersion = createdResourcesVersion const AssemblerNoVersion = assemblerNoVersion const FirstTestID = firstTestID diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index bedb079079..f4736f6214 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -41,7 +41,6 @@ func TestGlobalFieldsVersions(t *testing.T) { } require.Greater(t, len(fields), 1) - ledger := NewLedger(nil) for _, field := range fields { text := fmt.Sprintf("global %s", field.field.String()) // check assembler fails if version before introduction @@ -54,22 +53,16 @@ func TestGlobalFieldsVersions(t *testing.T) { ops := testProg(t, text, AssemblerMaxVersion) // check on a version before the field version - preLogicVersion := field.version - 1 - proto := makeTestProtoV(preLogicVersion) - if preLogicVersion < appsEnabledVersion { - require.False(t, proto.Application) - } - ep := defaultEvalParams() - ep.Proto = proto - ep.Ledger = ledger + preVersion := field.version - 1 + ep := defaultSigParamsWithVersion(preVersion) - // check failure with version check - _, err := EvalApp(ops.Program, 0, 888, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "greater than protocol supported version") + // check failure from whole program version check + testLogicBytes(t, ops.Program, ep, + "greater than protocol supported version", + "greater than protocol supported version") // check opcodes failures - ops.Program[0] = byte(preLogicVersion) // set version + ops.Program[0] = byte(preVersion) // set version testLogicBytes(t, ops.Program, ep, "invalid global field") // check opcodes failures on 0 version @@ -101,7 +94,6 @@ func TestTxnFieldVersions(t *testing.T) { } txnaVersion := uint64(appsEnabledVersion) - ledger := NewLedger(nil) txn := makeSampleTxn() // We'll reject too early if we have a nonzero RekeyTo, because that // field must be zero for every txn in the group if this is an old @@ -132,25 +124,18 @@ func TestTxnFieldVersions(t *testing.T) { ops := testProg(t, text, AssemblerMaxVersion) - preLogicVersion := fs.version - 1 - proto := makeTestProtoV(preLogicVersion) - if preLogicVersion < appsEnabledVersion { - require.False(t, proto.Application) - } - ep := defaultEvalParams() - ep.Proto = proto - ep.Ledger = ledger - ep.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup) + preVersion := fs.version - 1 + ep := defaultSigParamsWithVersion(preVersion, txgroup...) // check failure with version check testLogicBytes(t, ops.Program, ep, "greater than protocol supported version", "greater than protocol supported version") // check opcodes failures - ops.Program[0] = byte(preLogicVersion) // set version + ops.Program[0] = byte(preVersion) // set version checkErr := "" evalErr := "invalid txn field" - if txnaMode && preLogicVersion < txnaVersion { + if txnaMode && preVersion < txnaVersion { checkErr = "illegal opcode" evalErr = "illegal opcode" } diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index f0454be6b1..1c97be80a8 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -885,7 +885,7 @@ func init() { } // Start from v2 and higher, // copy lower version opcodes and overwrite matching version - for v := uint64(2); v <= evalMaxVersion; v++ { + for v := uint64(2); v <= LogicVersion; v++ { // Copy opcodes from lower version OpsByName[v] = maps.Clone(OpsByName[v-1]) for op, oi := range opsByOpcode[v-1] { diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go index bd5e5e17c3..b796a8c4c6 100644 --- a/data/transactions/logic/resources_test.go +++ b/data/transactions/logic/resources_test.go @@ -24,7 +24,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/data/transactions/logic" + . "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" @@ -67,10 +67,10 @@ func TestAppSharing(t *testing.T) { getSchema := "int 500; app_params_get AppGlobalNumByteSlice; !; assert; pop; int 1" // In v8, the first tx can read app params of 500, because it's in its // foreign array, but the second can't - logic.TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 8, nil, - logic.Exp(1, "unavailable App 500")) + TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 8, nil, + Exp(1, "unavailable App 500")) // In v9, the second can, because the first can. - logic.TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 9, nil) + TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 9, nil) getLocalEx := `txn Sender; int 500; byte "some-key"; app_local_get_ex; pop; pop; int 1` @@ -78,52 +78,52 @@ func TestAppSharing(t *testing.T) { // reading the locals for a different account. // app_local_get* requires the address and the app exist, else the program fails - logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 8, nil, - logic.Exp(0, "no account")) + TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 8, nil, + Exp(0, "no account")) - _, _, ledger := logic.MakeSampleEnv() + _, _, ledger := MakeSampleEnv() ledger.NewAccount(appl0.Sender, 100_000) ledger.NewAccount(appl1.Sender, 100_000) ledger.NewApp(appl0.Sender, 500, basics.AppParams{}) ledger.NewLocals(appl0.Sender, 500) // opt in // Now txn0 passes, but txn1 has an error because it can't see app 500 - logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 9, ledger, - logic.Exp(1, "unavailable Local State")) + TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "unavailable Local State")) // But it's ok in appl2, because appl2 uses the same Sender, even though the // foreign-app is not repeated in appl2 because the holding being accessed // is the one from tx0. - logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl2), 9, ledger) - logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing - logic.Exp(1, "unavailable App 500")) + TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl2), 9, ledger) + TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing + Exp(1, "unavailable App 500")) // Checking if an account is opted in has pretty much the same rules optInCheck500 := "txn Sender; int 500; app_opted_in" // app_opted_in requires the address and the app exist, else the program fails - logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, nil, // nil ledger, no account - logic.Exp(0, "no account: "+appl0.Sender.String())) + TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, nil, // nil ledger, no account + Exp(0, "no account: "+appl0.Sender.String())) // Now txn0 passes, but txn1 has an error because it can't see app 500 locals for appl1.Sender - logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, ledger, - logic.Exp(1, "unavailable Local State "+appl1.Sender.String())) + TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "unavailable Local State "+appl1.Sender.String())) // But it's ok in appl2, because appl2 uses the same Sender, even though the // foreign-app is not repeated in appl2 because the holding being accessed // is the one from tx0. - logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 9, ledger) - logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing - logic.Exp(1, "unavailable App 500")) + TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 9, ledger) + TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing + Exp(1, "unavailable App 500")) // Confirm sharing applies to the app id called in tx0, not just foreign app array optInCheck900 := "txn Sender; int 900; app_opted_in; !" // we did not opt any senders into 900 // as above, appl1 can't see the local state, but appl2 can b/c sender is same as appl0 - logic.TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl1), 9, ledger, - logic.Exp(1, "unavailable Local State "+appl1.Sender.String())) - logic.TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 9, ledger) - logic.TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 8, ledger, // v8=no sharing - logic.Exp(1, "unavailable App 900")) + TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "unavailable Local State "+appl1.Sender.String())) + TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 9, ledger) + TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 8, ledger, // v8=no sharing + Exp(1, "unavailable App 900")) // Now, confirm that *setting* a local state in tx1 that was made available // in tx0 works. The extra check here is that the change is recorded @@ -134,14 +134,14 @@ func TestAppSharing(t *testing.T) { sources := []string{noop, putLocal} appl1.ApplicationArgs = [][]byte{appl0.Sender[:]} // tx1 will try to modify local state exposed in tx0 // appl0.Sender is available, but 901's local state for it isn't (only 900 is, since 900 was called in tx0) - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, - logic.Exp(1, "unavailable Local State "+appl0.Sender.String())) + TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "unavailable Local State "+appl0.Sender.String())) // Add 901 to tx0's ForeignApps, and it works appl0.ForeignApps = append(appl0.ForeignApps, 901) // well, it will after we opt in - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, - logic.Exp(1, "account "+appl0.Sender.String()+" is not opted into 901")) + TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, + Exp(1, "account "+appl0.Sender.String()+" is not opted into 901")) ledger.NewLocals(appl0.Sender, 901) // opt in - ep := logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) + ep := TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) require.Len(t, ep.TxnGroup, 2) ed := ep.TxnGroup[1].ApplyData.EvalDelta require.Equal(t, map[uint64]basics.StateDelta{ @@ -157,38 +157,38 @@ func TestAppSharing(t *testing.T) { // when running all three, appl2 can't read the locals of app in tx0 and addr in tx1 sources = []string{"", "", "gtxn 1 Sender; gtxn 0 Applications 0; byte 0xAA; app_local_get_ex"} - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, - logic.Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized + TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, + Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized // same test of account in array of tx1 rather than Sender appl1.Accounts = []basics.Address{{7, 7}} sources = []string{"", "", "gtxn 1 Accounts 1; gtxn 0 Applications 0; byte 0xAA; app_local_get_ex"} - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, - logic.Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized + TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, + Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized // try to do a put on local state of the account in tx1, but tx0 ought not have access to that local state ledger.NewAccount(pay1.Receiver, 200_000) ledger.NewLocals(pay1.Receiver, 900) // opt in sources = []string{`gtxn 1 Receiver; byte "key"; byte "val"; app_local_put; int 1`} - logic.TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger, - logic.Exp(0, "unavailable Local State "+pay1.Receiver.String())) + TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger, + Exp(0, "unavailable Local State "+pay1.Receiver.String())) // same for app_local_del sources = []string{`gtxn 1 Receiver; byte "key"; app_local_del; int 1`} - logic.TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger, - logic.Exp(0, "unavailable Local State "+pay1.Receiver.String())) + TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger, + Exp(0, "unavailable Local State "+pay1.Receiver.String())) // now, use an app call in tx1, with 900 in the foreign apps, so the local state is available appl1.ForeignApps = append(appl1.ForeignApps, 900) ledger.NewLocals(appl1.Sender, 900) // opt in sources = []string{`gtxn 1 Sender; byte "key"; byte "val"; app_local_put; int 1`} - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account - logic.Exp(0, "invalid Account reference "+appl1.Sender.String())) + TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) + TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account + Exp(0, "invalid Account reference "+appl1.Sender.String())) // same for app_local_del sources = []string{`gtxn 1 Sender; byte "key"; app_local_del; int 1`} - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account - logic.Exp(0, "invalid Account reference "+appl1.Sender.String())) + TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) + TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account + Exp(0, "invalid Account reference "+appl1.Sender.String())) } // TestBetterLocalErrors confirms that we get specific errors about the missing @@ -199,7 +199,7 @@ func TestBetterLocalErrors(t *testing.T) { joe := basics.Address{9, 9, 9} - ep, tx, ledger := logic.MakeSampleEnv() + ep, tx, ledger := MakeSampleEnv() ledger.NewAccount(joe, 5000000) ledger.NewApp(joe, 500, basics.AppParams{}) ledger.NewLocals(joe, 500) @@ -215,19 +215,19 @@ pop; pop; int 1 binary.BigEndian.PutUint64(app, 500) tx.ApplicationArgs = [][]byte{joe[:], app} - logic.TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String()+", unavailable App 500") + TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String()+", unavailable App 500") tx.Accounts = []basics.Address{joe} - logic.TestApp(t, getLocalEx, ep, "unavailable App 500") + TestApp(t, getLocalEx, ep, "unavailable App 500") tx.ForeignApps = []basics.AppIndex{500} - logic.TestApp(t, getLocalEx, ep) + TestApp(t, getLocalEx, ep) binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 500) - logic.TestApp(t, getLocalEx, ep) + TestApp(t, getLocalEx, ep) binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 501) - logic.TestApp(t, getLocalEx, ep, "unavailable App 501") + TestApp(t, getLocalEx, ep, "unavailable App 501") binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 500) tx.Accounts = []basics.Address{} - logic.TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String()) + TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String()) } // TestAssetSharing confirms that as of v9, assets can be accessed across @@ -258,32 +258,32 @@ func TestAssetSharing(t *testing.T) { // In v8, the first tx can read asset 400, because it's in its foreign array, // but the second can't - logic.TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 8, nil, - logic.Exp(1, "unavailable Asset 400")) + TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 8, nil, + Exp(1, "unavailable Asset 400")) // In v9, the second can, because the first can. - logic.TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 9, nil) + TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 9, nil) getBalance := "txn Sender; int 400; asset_holding_get AssetBalance; pop; pop; int 1" // In contrast, here there's no help from v9, because the second tx is // reading a holding for a different account. - logic.TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 8, nil, - logic.Exp(1, "unavailable Asset 400")) - logic.TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 9, nil, - logic.Exp(1, "unavailable Holding")) + TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 8, nil, + Exp(1, "unavailable Asset 400")) + TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 9, nil, + Exp(1, "unavailable Holding")) // But it's ok in appl2, because the same account is used, even though the // foreign-asset is not repeated in appl2. - logic.TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl2), 9, nil) + TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl2), 9, nil) // when running all three, appl2 can't read the holding of asset in tx0 and addr in tx1 sources := []string{"", "", "gtxn 1 Sender; gtxn 0 Assets 0; asset_holding_get AssetBalance"} - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, - logic.Exp(2, "unavailable Holding")) // note that the error message is for Holding, not specialized + TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, + Exp(2, "unavailable Holding")) // note that the error message is for Holding, not specialized // same test of account in array of tx1 rather than Sender appl1.Accounts = []basics.Address{{7, 7}} sources = []string{"", "", "gtxn 1 Accounts 1; gtxn 0 Assets 0; asset_holding_get AssetBalance"} - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, - logic.Exp(2, "unavailable Holding")) // note that the error message is for Holding, not specialized + TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil, + Exp(2, "unavailable Holding")) // note that the error message is for Holding, not specialized } // TestBetterHoldingErrors confirms that we get specific errors about the missing @@ -294,7 +294,7 @@ func TestBetterHoldingErrors(t *testing.T) { joe := basics.Address{9, 9, 9} - ep, tx, ledger := logic.MakeSampleEnv() + ep, tx, ledger := MakeSampleEnv() ledger.NewAccount(joe, 5000000) ledger.NewAsset(joe, 200, basics.AssetParams{}) // as creator, joe will also be opted in @@ -309,17 +309,17 @@ pop; pop; int 1 binary.BigEndian.PutUint64(asa, 200) tx.ApplicationArgs = [][]byte{joe[:], asa} - logic.TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String()+", unavailable Asset 200") + TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String()+", unavailable Asset 200") tx.Accounts = []basics.Address{joe} - logic.TestApp(t, getHoldingBalance, ep, "unavailable Asset 200") + TestApp(t, getHoldingBalance, ep, "unavailable Asset 200") tx.ForeignAssets = []basics.AssetIndex{200} - logic.TestApp(t, getHoldingBalance, ep) + TestApp(t, getHoldingBalance, ep) binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 0) // slot=0 is same (200) - logic.TestApp(t, getHoldingBalance, ep) + TestApp(t, getHoldingBalance, ep) binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 200) tx.Accounts = []basics.Address{} - logic.TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String()) + TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String()) } // TestAccountPassing checks that the current app account and foreign app's @@ -329,9 +329,9 @@ func TestAccountPassing(t *testing.T) { t.Parallel() // appAddressVersion=7 - logic.TestLogicRange(t, 7, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 7, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { t.Parallel() - accept := logic.TestProg(t, "int 1", 6) + accept := TestProg(t, "int 1", 6) alice := basics.Address{1, 1, 1, 1, 1} ledger.NewApp(alice, 4, basics.AppParams{ ApprovalProgram: accept.Program, @@ -346,12 +346,12 @@ int 1` tx.ForeignApps = []basics.AppIndex{4} ledger.NewAccount(appAddr(888), 50_000) // First show that we're not just letting anything get passed in - logic.TestApp(t, fmt.Sprintf(callWithAccount, "int 32; bzero; byte 0x07; b|"), ep, + TestApp(t, fmt.Sprintf(callWithAccount, "int 32; bzero; byte 0x07; b|"), ep, "unavailable Account AAAAA") // Now show we can pass our own address - logic.TestApp(t, fmt.Sprintf(callWithAccount, "global CurrentApplicationAddress"), ep) + TestApp(t, fmt.Sprintf(callWithAccount, "global CurrentApplicationAddress"), ep) // Or the address of one of our ForeignApps - logic.TestApp(t, fmt.Sprintf(callWithAccount, "addr "+basics.AppIndex(4).Address().String()), ep) + TestApp(t, fmt.Sprintf(callWithAccount, "addr "+basics.AppIndex(4).Address().String()), ep) }) } @@ -360,7 +360,7 @@ func TestOtherTxSharing(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - _, _, ledger := logic.MakeSampleEnv() + _, _, ledger := MakeSampleEnv() senderAcct := basics.Address{1, 2, 3, 4, 5, 6, 1} ledger.NewAccount(senderAcct, 2001) @@ -418,13 +418,13 @@ func TestOtherTxSharing(t *testing.T) { } for _, send := range []txntest.Txn{keyreg, pay, acfg, axfer, afrz} { - logic.TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 9, ledger) - logic.TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 9, ledger) + TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 9, ledger) + TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 9, ledger) - logic.TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 8, ledger, - logic.Exp(1, "invalid Account reference")) - logic.TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 8, ledger, - logic.Exp(0, "invalid Account reference")) + TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 8, ledger, + Exp(1, "invalid Account reference")) + TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 8, ledger, + Exp(0, "invalid Account reference")) } holdingAccess := ` @@ -436,82 +436,82 @@ func TestOtherTxSharing(t *testing.T) { t.Run("keyreg", func(t *testing.T) { // nolint:paralleltest // shares `ledger` appl.ApplicationArgs = [][]byte{senderAcct[:], {200}} - logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &appl), 9, ledger, - logic.Exp(1, "unavailable Asset 200")) + TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &appl), 9, ledger, + Exp(1, "unavailable Asset 200")) withRef := appl withRef.ForeignAssets = []basics.AssetIndex{200} - logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &withRef), 9, ledger, - logic.Exp(1, "unavailable Holding "+senderAcct.String())) + TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &withRef), 9, ledger, + Exp(1, "unavailable Holding "+senderAcct.String())) }) t.Run("pay", func(t *testing.T) { // nolint:paralleltest // shares `ledger` // The receiver is available for algo balance reading appl.ApplicationArgs = [][]byte{receiverAcct[:]} - logic.TestApps(t, []string{"", receiverBalance}, txntest.Group(&pay, &appl), 9, ledger) + TestApps(t, []string{"", receiverBalance}, txntest.Group(&pay, &appl), 9, ledger) // The other account is not (it's not even in the pay txn) appl.ApplicationArgs = [][]byte{otherAcct[:]} - logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&pay, &appl), 9, ledger, - logic.Exp(1, "invalid Account reference "+otherAcct.String())) + TestApps(t, []string{"", otherBalance}, txntest.Group(&pay, &appl), 9, ledger, + Exp(1, "invalid Account reference "+otherAcct.String())) // The other account becomes accessible because used in CloseRemainderTo withClose := pay withClose.CloseRemainderTo = otherAcct - logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&withClose, &appl), 9, ledger) + TestApps(t, []string{"", otherBalance}, txntest.Group(&withClose, &appl), 9, ledger) }) t.Run("acfg", func(t *testing.T) { // nolint:paralleltest // shares `ledger` // The other account is not available even though it's all the extra addresses appl.ApplicationArgs = [][]byte{otherAcct[:]} - logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&acfg, &appl), 9, ledger, - logic.Exp(1, "invalid Account reference "+otherAcct.String())) + TestApps(t, []string{"", otherBalance}, txntest.Group(&acfg, &appl), 9, ledger, + Exp(1, "invalid Account reference "+otherAcct.String())) }) t.Run("axfer", func(t *testing.T) { // nolint:paralleltest // shares `ledger` // The receiver is also available for algo balance reading appl.ApplicationArgs = [][]byte{receiverAcct[:]} - logic.TestApps(t, []string{"", receiverBalance}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", receiverBalance}, txntest.Group(&axfer, &appl), 9, ledger) // as is the "other" (AssetSender) appl.ApplicationArgs = [][]byte{otherAcct[:]} - logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", otherBalance}, txntest.Group(&axfer, &appl), 9, ledger) // sender holding is available appl.ApplicationArgs = [][]byte{senderAcct[:], {byte(axfer.XferAsset)}} - logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger) // receiver holding is available appl.ApplicationArgs = [][]byte{receiverAcct[:], {byte(axfer.XferAsset)}} - logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger) // asset sender (other) account is available appl.ApplicationArgs = [][]byte{otherAcct[:], {byte(axfer.XferAsset)}} - logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger) // AssetCloseTo holding becomes available when set appl.ApplicationArgs = [][]byte{other2Acct[:], {byte(axfer.XferAsset)}} - logic.TestApps(t, []string{"", other2Balance}, txntest.Group(&axfer, &appl), 9, ledger, - logic.Exp(1, "invalid Account reference "+other2Acct.String())) - logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger, - logic.Exp(1, "unavailable Account "+other2Acct.String())) + TestApps(t, []string{"", other2Balance}, txntest.Group(&axfer, &appl), 9, ledger, + Exp(1, "invalid Account reference "+other2Acct.String())) + TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger, + Exp(1, "unavailable Account "+other2Acct.String())) withClose := axfer withClose.AssetCloseTo = other2Acct appl.ApplicationArgs = [][]byte{other2Acct[:], {byte(axfer.XferAsset)}} - logic.TestApps(t, []string{"", other2Balance}, txntest.Group(&withClose, &appl), 9, ledger) - logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&withClose, &appl), 9, ledger) + TestApps(t, []string{"", other2Balance}, txntest.Group(&withClose, &appl), 9, ledger) + TestApps(t, []string{"", holdingAccess}, txntest.Group(&withClose, &appl), 9, ledger) }) t.Run("afrz", func(t *testing.T) { // nolint:paralleltest // shares `ledger` // The other account is available (for algo and asset) appl.ApplicationArgs = [][]byte{otherAcct[:], {byte(afrz.FreezeAsset)}} - logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&afrz, &appl), 9, ledger) - logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&afrz, &appl), 9, ledger) + TestApps(t, []string{"", otherBalance}, txntest.Group(&afrz, &appl), 9, ledger) + TestApps(t, []string{"", holdingAccess}, txntest.Group(&afrz, &appl), 9, ledger) // The sender holding is _not_ (because the freezeaccount's holding is irrelevant to afrz) appl.ApplicationArgs = [][]byte{senderAcct[:], {byte(afrz.FreezeAsset)}} - logic.TestApps(t, []string{"", senderBalance}, txntest.Group(&afrz, &appl), 9, ledger) - logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&afrz, &appl), 9, ledger, - logic.Exp(1, "unavailable Holding "+senderAcct.String())) + TestApps(t, []string{"", senderBalance}, txntest.Group(&afrz, &appl), 9, ledger) + TestApps(t, []string{"", holdingAccess}, txntest.Group(&afrz, &appl), 9, ledger, + Exp(1, "unavailable Holding "+senderAcct.String())) }) } @@ -520,7 +520,7 @@ func TestSharedInnerTxns(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - _, _, ledger := logic.MakeSampleEnv() + _, _, ledger := MakeSampleEnv() const asa1 = 201 const asa2 = 202 @@ -584,14 +584,14 @@ int 1 // appl has no foreign ref to senderAcct, but can still inner pay it appl.ApplicationArgs = [][]byte{senderAcct[:]} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 9, ledger) - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 8, ledger, - logic.Exp(1, "unavailable Account "+senderAcct.String())) + TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 9, ledger) + TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 8, ledger, + Exp(1, "unavailable Account "+senderAcct.String())) // confirm you can't just pay _anybody_. receiverAcct is not in use at all. appl.ApplicationArgs = [][]byte{receiverAcct[:]} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 9, ledger, - logic.Exp(1, "unavailable Account "+receiverAcct.String())) + TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 9, ledger, + Exp(1, "unavailable Account "+receiverAcct.String())) }) t.Run("pay", func(t *testing.T) { // nolint:paralleltest // shares `ledger` @@ -603,19 +603,19 @@ int 1 // appl has no foreign ref to senderAcct or receiverAcct, but can still inner pay them appl.ApplicationArgs = [][]byte{senderAcct[:]} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger) - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 8, ledger, - logic.Exp(1, "unavailable Account "+senderAcct.String())) + TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger) + TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 8, ledger, + Exp(1, "unavailable Account "+senderAcct.String())) appl.ApplicationArgs = [][]byte{receiverAcct[:]} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger) - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 8, ledger, - logic.Exp(1, "unavailable Account "+receiverAcct.String())) + TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger) + TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 8, ledger, + Exp(1, "unavailable Account "+receiverAcct.String())) // confirm you can't just pay _anybody_. otherAcct is not in use at all. appl.ApplicationArgs = [][]byte{otherAcct[:]} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger, - logic.Exp(1, "unavailable Account "+otherAcct.String())) + TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger, + Exp(1, "unavailable Account "+otherAcct.String())) }) t.Run("axfer", func(t *testing.T) { // nolint:paralleltest // shares `ledger` @@ -629,47 +629,47 @@ int 1 // appl can pay the axfer sender appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 9, ledger) - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 8, ledger, - logic.Exp(1, "unavailable Account "+senderAcct.String())) + TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 8, ledger, + Exp(1, "unavailable Account "+senderAcct.String())) // but can't axfer to sender, because appAcct doesn't have holding access for the asa - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, - logic.Exp(1, "unavailable Holding")) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, + Exp(1, "unavailable Holding")) // and to the receiver appl.ApplicationArgs = [][]byte{receiverAcct[:], {asa1}} - logic.TestApps(t, []string{payToArg}, txntest.Group(&appl, &axfer), 9, ledger) - logic.TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &axfer), 9, ledger, - logic.Exp(0, "unavailable Holding")) + TestApps(t, []string{payToArg}, txntest.Group(&appl, &axfer), 9, ledger) + TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &axfer), 9, ledger, + Exp(0, "unavailable Holding")) // and to the clawback appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 9, ledger) - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, - logic.Exp(1, "unavailable Holding")) + TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, + Exp(1, "unavailable Holding")) // Those axfers become possible by adding the asa to the appl's ForeignAssets appl.ForeignAssets = []basics.AssetIndex{asa1} appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}} - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger) appl.ApplicationArgs = [][]byte{receiverAcct[:], {asa1}} - logic.TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &axfer), 9, ledger) + TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &axfer), 9, ledger) appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}} - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger) // but can't axfer a different asset appl.ApplicationArgs = [][]byte{senderAcct[:], {asa2}} - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, - logic.Exp(1, fmt.Sprintf("unavailable Asset %d", asa2))) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, + Exp(1, fmt.Sprintf("unavailable Asset %d", asa2))) // or correct asset to an unknown address appl.ApplicationArgs = [][]byte{unusedAcct[:], {asa1}} - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, - logic.Exp(1, "unavailable Account")) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, + Exp(1, "unavailable Account")) // appl can acfg the asset from tx0 (which requires asset available, not holding) appl.ApplicationArgs = [][]byte{{asa1}} - logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger) + TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger) appl.ApplicationArgs = [][]byte{{asa2}} // but not asa2 - logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger, - logic.Exp(1, fmt.Sprintf("unavailable Asset %d", asa2))) + TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger, + Exp(1, fmt.Sprintf("unavailable Asset %d", asa2))) // Now, confirm that access to account from a pay in one tx, and asa // from another don't allow inner axfer in the third (because there's no @@ -682,16 +682,16 @@ int 1 } // the asset is acfg-able appl.ApplicationArgs = [][]byte{{asa1}} - logic.TestApps(t, []string{"", "", acfgArg}, txntest.Group(&pay, &axfer, &appl), 9, ledger) - logic.TestApps(t, []string{"", "", acfgArg}, txntest.Group(&axfer, &pay, &appl), 9, ledger) + TestApps(t, []string{"", "", acfgArg}, txntest.Group(&pay, &axfer, &appl), 9, ledger) + TestApps(t, []string{"", "", acfgArg}, txntest.Group(&axfer, &pay, &appl), 9, ledger) // payAcct (the pay sender) is payable appl.ApplicationArgs = [][]byte{payAcct[:]} - logic.TestApps(t, []string{"", "", payToArg}, txntest.Group(&axfer, &pay, &appl), 9, ledger) + TestApps(t, []string{"", "", payToArg}, txntest.Group(&axfer, &pay, &appl), 9, ledger) // but the cross-product is not available, so no axfer (opting in first, to prevent that error) ledger.NewHolding(payAcct, asa1, 1, false) appl.ApplicationArgs = [][]byte{payAcct[:], {asa1}} - logic.TestApps(t, []string{"", "", axferToArgs}, txntest.Group(&axfer, &pay, &appl), 9, ledger, - logic.Exp(2, "unavailable Holding "+payAcct.String())) + TestApps(t, []string{"", "", axferToArgs}, txntest.Group(&axfer, &pay, &appl), 9, ledger, + Exp(2, "unavailable Holding "+payAcct.String())) }) t.Run("afrz", func(t *testing.T) { // nolint:paralleltest // shares `ledger` @@ -705,48 +705,48 @@ int 1 // appl can pay to the sender & freeze account appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger) + TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger) appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger) + TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger) // can't axfer to the afrz sender because appAcct holding is not available from afrz appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}} - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, - logic.Exp(1, "unavailable Holding "+appAcct.String())) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, + Exp(1, "unavailable Holding "+appAcct.String())) appl.ForeignAssets = []basics.AssetIndex{asa1} // _still_ can't axfer to sender because afrz sender's holding does NOT // become available (not note that complaint is now about that account) - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, - logic.Exp(1, "unavailable Holding "+senderAcct.String())) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, + Exp(1, "unavailable Holding "+senderAcct.String())) // and not to the receiver which isn't in afrz appl.ApplicationArgs = [][]byte{receiverAcct[:], {asa1}} - logic.TestApps(t, []string{payToArg}, txntest.Group(&appl, &afrz), 9, ledger, - logic.Exp(0, "unavailable Account "+receiverAcct.String())) - logic.TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &afrz), 9, ledger, - logic.Exp(0, "unavailable Account "+receiverAcct.String())) + TestApps(t, []string{payToArg}, txntest.Group(&appl, &afrz), 9, ledger, + Exp(0, "unavailable Account "+receiverAcct.String())) + TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &afrz), 9, ledger, + Exp(0, "unavailable Account "+receiverAcct.String())) // otherAcct is the afrz target, it's holding and account are available appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger) - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger) + TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger) // but still can't axfer a different asset appl.ApplicationArgs = [][]byte{otherAcct[:], {asa2}} - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, - logic.Exp(1, fmt.Sprintf("unavailable Asset %d", asa2))) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, + Exp(1, fmt.Sprintf("unavailable Asset %d", asa2))) appl.ForeignAssets = []basics.AssetIndex{asa2} // once added to appl's foreign array, the appl still lacks access to other's holding - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, - logic.Exp(1, "unavailable Holding "+otherAcct.String())) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger, + Exp(1, "unavailable Holding "+otherAcct.String())) // appl can acfg the asset from tx0 (which requires asset available, not holding) appl.ForeignAssets = []basics.AssetIndex{} appl.ApplicationArgs = [][]byte{{asa1}} - logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&afrz, &appl), 9, ledger) + TestApps(t, []string{"", acfgArg}, txntest.Group(&afrz, &appl), 9, ledger) appl.ApplicationArgs = [][]byte{{asa2}} // but not asa2 - logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&afrz, &appl), 9, ledger, - logic.Exp(1, fmt.Sprintf("unavailable Asset %d", asa2))) + TestApps(t, []string{"", acfgArg}, txntest.Group(&afrz, &appl), 9, ledger, + Exp(1, fmt.Sprintf("unavailable Asset %d", asa2))) }) @@ -762,40 +762,40 @@ int 1 // appl can pay to the otherAcct because it was in tx0 appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}} - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&appl0, &appl), 9, ledger) - logic.TestApps(t, []string{"", payToArg}, txntest.Group(&appl0, &appl), 8, ledger, // version 8 does not get sharing - logic.Exp(1, "unavailable Account "+otherAcct.String())) + TestApps(t, []string{"", payToArg}, txntest.Group(&appl0, &appl), 9, ledger) + TestApps(t, []string{"", payToArg}, txntest.Group(&appl0, &appl), 8, ledger, // version 8 does not get sharing + Exp(1, "unavailable Account "+otherAcct.String())) // appl can (almost) axfer asa1 to the otherAcct because both are in tx0 - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, - logic.Exp(1, "axfer Sender: unavailable Holding")) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, + Exp(1, "axfer Sender: unavailable Holding")) // but it can't take access it's OWN asa1, unless added to ForeignAssets appl.ForeignAssets = []basics.AssetIndex{asa1} - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger) // but it can't use 202 at all. notice the error is more direct that // above, as the problem is not the axfer Sender, only, it's that 202 // can't be used at all. appl.ApplicationArgs = [][]byte{otherAcct[:], {asa2}} - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, - logic.Exp(1, "unavailable Asset 202")) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, + Exp(1, "unavailable Asset 202")) // And adding asa2 does not fix this problem, because the other x 202 holding is unavailable appl.ForeignAssets = []basics.AssetIndex{asa2} - logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, - logic.Exp(1, "axfer AssetReceiver: unavailable Holding "+otherAcct.String()+" x 202")) + TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, + Exp(1, "axfer AssetReceiver: unavailable Holding "+otherAcct.String()+" x 202")) // Now, conduct similar tests, but with the apps performing the // pays/axfers invoked from an outer app. Use various versions to check // cross version sharing. // add v8 and v9 versions of the pay app to the ledger for inner calling - payToArgV8 := logic.TestProg(t, payToArg, 8) + payToArgV8 := TestProg(t, payToArg, 8) ledger.NewApp(senderAcct, 88, basics.AppParams{ApprovalProgram: payToArgV8.Program}) ledger.NewAccount(appAddr(88), 1_000_000) - payToArgV9 := logic.TestProg(t, payToArg, 9) + payToArgV9 := TestProg(t, payToArg, 9) ledger.NewApp(senderAcct, 99, basics.AppParams{ApprovalProgram: payToArgV9.Program}) ledger.NewAccount(appAddr(99), 1_000_000) - approvalV8 := logic.TestProg(t, "int 1", 8) + approvalV8 := TestProg(t, "int 1", 8) ledger.NewApp(senderAcct, 11, basics.AppParams{ApprovalProgram: approvalV8.Program}) innerCallTemplate := ` @@ -813,28 +813,28 @@ int 1 appl.ForeignApps = []basics.AppIndex{11, 88, 99} appl.ApplicationArgs = [][]byte{{99}, otherAcct[:], {asa1}} - logic.TestApps(t, []string{"", innerCall}, txntest.Group(&appl0, &appl), 9, ledger) + TestApps(t, []string{"", innerCall}, txntest.Group(&appl0, &appl), 9, ledger) // when the inner program is v8, it can't perform the pay appl.ApplicationArgs = [][]byte{{88}, otherAcct[:], {asa1}} - logic.TestApps(t, []string{"", innerCall}, txntest.Group(&appl0, &appl), 9, ledger, - logic.Exp(1, "unavailable Account "+otherAcct.String())) + TestApps(t, []string{"", innerCall}, txntest.Group(&appl0, &appl), 9, ledger, + Exp(1, "unavailable Account "+otherAcct.String())) // unless the caller passes in the account, but it can't pass the // account because that also would give the called app access to the // passed account's local state (which isn't available to the caller) innerCallWithAccount := fmt.Sprintf(innerCallTemplate, "addr "+otherAcct.String()+"; itxn_field Accounts") - logic.TestApps(t, []string{"", innerCallWithAccount}, txntest.Group(&appl0, &appl), 9, ledger, - logic.Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String())) + TestApps(t, []string{"", innerCallWithAccount}, txntest.Group(&appl0, &appl), 9, ledger, + Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String())) // the caller can't fix by passing 88 as a foreign app, because doing so // is not much different than the current situation: 88 is being called, // it's already available. innerCallWithBoth := fmt.Sprintf(innerCallTemplate, "addr "+otherAcct.String()+"; itxn_field Accounts; int 88; itxn_field Applications") - logic.TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger, - logic.Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String())) + TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger, + Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String())) // the caller *can* do it if it originally had access to that 88 holding. appl0.ForeignApps = []basics.AppIndex{88} - logic.TestApps(t, []string{"", innerCallWithAccount}, txntest.Group(&appl0, &appl), 9, ledger) + TestApps(t, []string{"", innerCallWithAccount}, txntest.Group(&appl0, &appl), 9, ledger) // here we confirm that even if we try calling another app, we still // can't pass in `other` and 88, because that would give the app access @@ -842,8 +842,8 @@ int 1 // of the foreign arrays, not just the accounts against called app id) appl.ApplicationArgs = [][]byte{{11}, otherAcct[:], {asa1}} appl0.ForeignApps = []basics.AppIndex{11} - logic.TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger, - logic.Exp(1, "appl ForeignApps: unavailable Local State "+otherAcct.String())) + TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger, + Exp(1, "appl ForeignApps: unavailable Local State "+otherAcct.String())) }) @@ -856,7 +856,7 @@ func TestAccessMyLocals(t *testing.T) { t.Parallel() // start at 3, needs assert - logic.TestLogicRange(t, 3, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { sender := basics.Address{1, 2, 3, 4} ledger.NewAccount(sender, 1_000_000) // we don't really process transactions in these tests, so despite the @@ -894,13 +894,13 @@ func TestAccessMyLocals(t *testing.T) { app_local_del int 1 ` - logic.TestApp(t, source, ep) + TestApp(t, source, ep) if ep.Proto.LogicSigVersion >= 4 { // confirm "txn Sender" also works source = strings.ReplaceAll(source, "int 0\n", "txn Sender\n") - logic.TestApp(t, source, ep) + TestApp(t, source, ep) } - logic.TestApp(t, "int 0; int 0; app_opted_in", ep) + TestApp(t, "int 0; int 0; app_opted_in", ep) }) } diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go index 9e5f9564b3..5d44cbe2f1 100644 --- a/data/transactions/logic/tracer_test.go +++ b/data/transactions/logic/tracer_test.go @@ -105,7 +105,7 @@ func TestLogicSigEvalWithTracer(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { t.Parallel() mock := mocktracer.Tracer{} - ep := DefaultEvalParams() + ep := DefaultSigParams() ep.Tracer = &mock TestLogic(t, testCase.program, AssemblerMaxVersion, ep, testCase.evalProblems...) @@ -123,7 +123,7 @@ func TestTopLevelAppEvalWithTracer(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { t.Parallel() mock := mocktracer.Tracer{} - ep := DefaultEvalParams() + ep := DefaultAppParams() ep.Tracer = &mock TestApp(t, testCase.program, ep, testCase.evalProblems...) @@ -190,12 +190,14 @@ func TestEvalPanicWithTracer(t *testing.T) { //nolint:paralleltest // Uses WithP t.Run(mode.String(), func(t *testing.T) { //nolint:paralleltest // Uses WithPanicOpcode testCase := getPanicTracerTestCase(mode) mock := mocktracer.Tracer{} - ep := DefaultEvalParams() - ep.Tracer = &mock switch mode { case ModeSig: + ep := DefaultSigParams() + ep.Tracer = &mock TestLogic(t, testCase.program, AssemblerMaxVersion, ep, testCase.evalProblems...) case ModeApp: + ep := DefaultAppParams() + ep.Tracer = &mock TestApp(t, testCase.program, ep, testCase.evalProblems...) default: require.Fail(t, "unknown mode") @@ -225,7 +227,7 @@ func TestEvalWithTracerPanic(t *testing.T) { t.Run(mode.String(), func(t *testing.T) { t.Parallel() tracer := panicTracer{} - ep := DefaultEvalParams() + ep := DefaultSigParams() ep.Tracer = &tracer TestLogic(t, debuggerTestProgramApprove, AssemblerMaxVersion, ep, "panicTracer panics") }) diff --git a/data/transactions/verify/artifact_test.go b/data/transactions/verify/artifact_test.go index 226ac54491..1fa3d6b2d5 100644 --- a/data/transactions/verify/artifact_test.go +++ b/data/transactions/verify/artifact_test.go @@ -43,20 +43,15 @@ func BenchmarkTinyMan(b *testing.B) { require.NotEmpty(b, stxns[1].Lsig.Logic) require.NotEmpty(b, stxns[2].Sig) require.NotEmpty(b, stxns[3].Lsig.Logic) - txgroup := transactions.WrapSignedTxnsWithAD(stxns) b.ResetTimer() for i := 0; i < b.N; i++ { proto := config.Consensus[protocol.ConsensusCurrentVersion] - ep := logic.EvalParams{ - Proto: &proto, - TxnGroup: txgroup, - SigLedger: &logic.NoHeaderLedger{}, - } - pass, err := logic.EvalSignature(1, &ep) + ep := logic.NewSigEvalParams(stxns, &proto, &logic.NoHeaderLedger{}) + pass, err := logic.EvalSignature(1, ep) require.NoError(b, err) require.True(b, pass) - pass, err = logic.EvalSignature(3, &ep) + pass, err = logic.EvalSignature(3, ep) require.NoError(b, err) require.True(b, pass) } diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index 8667d81e88..3d42694bbe 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -59,24 +59,29 @@ const txnPerWorksetThreshold = 32 // - it allows us to linearly scan the input, and process elements only once we're going to queue them into the pool. const concurrentWorksets = 16 -// GroupContext is the set of parameters external to a transaction which -// stateless checks are performed against. +// GroupContext holds values used to evaluate the LogicSigs in a group. The +// first set are the parameters external to a transaction which could +// potentially change the result of LogicSig evaluation. Example: If the +// consensusVersion changes, a rule might change that matters. Certainly this is +// _very_ rare, but we don't want to use the result of a LogicSig evaluation +// across a protocol upgrade boundary. // -// For efficient caching, these parameters should either be constant -// or change slowly over time. -// -// Group data are omitted because they are committed to in the -// transaction and its ID. +// The second set are derived from the first set and from the transaction +// data. They are stored here only for efficiency, not for correctness, so they +// are not checked in Equal() type GroupContext struct { + // These fields determine whether a logicsig must be re-evaluated. specAddrs transactions.SpecialAddresses consensusVersion protocol.ConsensusVersion - consensusParams config.ConsensusParams - minAvmVersion uint64 - signedGroupTxns []transactions.SignedTxn - ledger logic.LedgerForSignature + + // These fields just hold useful data that ought not be recomputed (unless the above changes) + consensusParams config.ConsensusParams + signedGroupTxns []transactions.SignedTxn + evalParams *logic.EvalParams } var errTxGroupInvalidFee = errors.New("txgroup fee requirement overflow") +var errTxGroupInsuffientLsigBudget = errors.New("txgroup lsig expectations exceed available budget") var errTxnSigHasNoSig = errors.New("signedtxn has no sig") var errTxnSigNotWellFormed = errors.New("signedtxn should only have one of Sig or Msig or LogicSig") var errRekeyingNotSupported = errors.New("nonempty AuthAddr but rekeying is not supported") @@ -125,9 +130,8 @@ func (e *TxGroupError) Unwrap() error { return e.err } -// PrepareGroupContext prepares a verification group parameter object for a given transaction -// group. -func PrepareGroupContext(group []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature) (*GroupContext, error) { +// PrepareGroupContext prepares a GroupCtx for a given transaction group. +func PrepareGroupContext(group []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, evalTracer logic.EvalTracer) (*GroupContext, error) { if len(group) == 0 { return nil, nil } @@ -135,6 +139,9 @@ func PrepareGroupContext(group []transactions.SignedTxn, contextHdr *bookkeeping if !ok { return nil, protocol.Error(contextHdr.CurrentProtocol) } + + ep := logic.NewSigEvalParams(group, &consensusParams, ledger) + ep.Tracer = evalTracer return &GroupContext{ specAddrs: transactions.SpecialAddresses{ FeeSink: contextHdr.FeeSink, @@ -142,23 +149,21 @@ func PrepareGroupContext(group []transactions.SignedTxn, contextHdr *bookkeeping }, consensusVersion: contextHdr.CurrentProtocol, consensusParams: consensusParams, - minAvmVersion: logic.ComputeMinAvmVersion(transactions.WrapSignedTxnsWithAD(group)), signedGroupTxns: group, - ledger: ledger, + evalParams: ep, }, nil } // Equal compares two group contexts to see if they would represent the same verification context for a given transaction. func (g *GroupContext) Equal(other *GroupContext) bool { return g.specAddrs == other.specAddrs && - g.consensusVersion == other.consensusVersion && - g.minAvmVersion == other.minAvmVersion + g.consensusVersion == other.consensusVersion } // txnBatchPrep verifies a SignedTxn having no obviously inconsistent data. // Block-assembly time checks of LogicSig and accounting rules may still block the txn. // It is the caller responsibility to call batchVerifier.Verify(). -func txnBatchPrep(gi int, groupCtx *GroupContext, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError { +func txnBatchPrep(gi int, groupCtx *GroupContext, verifier *crypto.BatchVerifier) *TxGroupError { s := &groupCtx.signedGroupTxns[gi] if !groupCtx.consensusParams.SupportRekeying && (s.AuthAddr != basics.Address{}) { return &TxGroupError{err: errRekeyingNotSupported, GroupIndex: gi, Reason: TxGroupErrorReasonGeneric} @@ -168,7 +173,7 @@ func txnBatchPrep(gi int, groupCtx *GroupContext, verifier *crypto.BatchVerifier return &TxGroupError{err: err, GroupIndex: gi, Reason: TxGroupErrorReasonNotWellFormed} } - return stxnCoreChecks(gi, groupCtx, verifier, evalTracer) + return stxnCoreChecks(gi, groupCtx, verifier) } // TxnGroup verifies a []SignedTxn as being signed and having no obviously inconsistent data. @@ -202,7 +207,7 @@ func txnGroup(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader // txnGroupBatchPrep verifies a []SignedTxn having no obviously inconsistent data. // it is the caller responsibility to call batchVerifier.Verify() func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) (*GroupContext, error) { - groupCtx, err := PrepareGroupContext(stxs, contextHdr, ledger) + groupCtx, err := PrepareGroupContext(stxs, contextHdr, ledger, evalTracer) if err != nil { return nil, err } @@ -210,7 +215,7 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.Bl minFeeCount := uint64(0) feesPaid := uint64(0) for i, stxn := range stxs { - prepErr := txnBatchPrep(i, groupCtx, verifier, evalTracer) + prepErr := txnBatchPrep(i, groupCtx, verifier) if prepErr != nil { // re-wrap the error with more details prepErr.err = fmt.Errorf("transaction %+v invalid : %w", stxn, prepErr.err) @@ -282,7 +287,7 @@ func checkTxnSigTypeCounts(s *transactions.SignedTxn, groupIndex int) (sigType s } // stxnCoreChecks runs signatures validity checks and enqueues signature into batchVerifier for verification. -func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError { +func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) *TxGroupError { s := &groupCtx.signedGroupTxns[gi] sigType, err := checkTxnSigTypeCounts(s, gi) if err != nil { @@ -308,7 +313,7 @@ func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchV return nil case logicSig: - if err := logicSigVerify(gi, groupCtx, evalTracer); err != nil { + if err := logicSigVerify(gi, groupCtx); err != nil { return &TxGroupError{err: err, GroupIndex: gi, Reason: TxGroupErrorReasonLogicSigFailed} } return nil @@ -360,14 +365,7 @@ func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier return errors.New("LogicSig.Logic too long") } - txngroup := transactions.WrapSignedTxnsWithAD(groupCtx.signedGroupTxns) - ep := logic.EvalParams{ - Proto: &groupCtx.consensusParams, - TxnGroup: txngroup, - MinAvmVersion: &groupCtx.minAvmVersion, - SigLedger: groupCtx.ledger, // won't be needed for CheckSignature - } - err := logic.CheckSignature(gi, &ep) + err := logic.CheckSignature(gi, groupCtx.evalParams) if err != nil { return err } @@ -415,20 +413,13 @@ func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier } // logicSigVerify checks that the signature is valid, executing the program. -func logicSigVerify(gi int, groupCtx *GroupContext, evalTracer logic.EvalTracer) error { +func logicSigVerify(gi int, groupCtx *GroupContext) error { err := LogicSigSanityCheck(gi, groupCtx) if err != nil { return err } - ep := logic.EvalParams{ - Proto: &groupCtx.consensusParams, - TxnGroup: transactions.WrapSignedTxnsWithAD(groupCtx.signedGroupTxns), - MinAvmVersion: &groupCtx.minAvmVersion, - SigLedger: groupCtx.ledger, - Tracer: evalTracer, - } - pass, cx, err := logic.EvalSignatureFull(gi, &ep) + pass, cx, err := logic.EvalSignatureFull(gi, groupCtx.evalParams) if err != nil { logicErrTotal.Inc(nil) return fmt.Errorf("transaction %v: %w", groupCtx.signedGroupTxns[gi].ID(), err) diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index 7443711139..9c7d001470 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -62,7 +62,7 @@ var spec = transactions.SpecialAddresses{ func verifyTxn(gi int, groupCtx *GroupContext) error { batchVerifier := crypto.MakeBatchVerifier() - if err := txnBatchPrep(gi, groupCtx, batchVerifier, nil); err != nil { + if err := txnBatchPrep(gi, groupCtx, batchVerifier); err != nil { return err } return batchVerifier.Verify() @@ -208,7 +208,7 @@ func TestSignedPayment(t *testing.T) { payments, stxns, secrets, addrs := generateTestObjects(1, 1, 0, 0) payment, stxn, secret, addr := payments[0], stxns[0], secrets[0], addrs[0] - groupCtx, err := PrepareGroupContext(stxns, blockHeader, nil) + groupCtx, err := PrepareGroupContext(stxns, blockHeader, nil, nil) require.NoError(t, err) require.NoError(t, payment.WellFormed(spec, proto), "generateTestObjects generated an invalid payment") require.NoError(t, verifyTxn(0, groupCtx), "generateTestObjects generated a bad signedtxn") @@ -230,7 +230,7 @@ func TestTxnValidationEncodeDecode(t *testing.T) { _, signed, _, _ := generateTestObjects(100, 50, 0, 0) for _, txn := range signed { - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil, nil) require.NoError(t, err) if verifyTxn(0, groupCtx) != nil { t.Errorf("signed transaction %#v did not verify", txn) @@ -250,7 +250,7 @@ func TestTxnValidationEmptySig(t *testing.T) { _, signed, _, _ := generateTestObjects(100, 50, 0, 0) for _, txn := range signed { - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil, nil) require.NoError(t, err) if verifyTxn(0, groupCtx) != nil { t.Errorf("signed transaction %#v did not verify", txn) @@ -292,7 +292,7 @@ func TestTxnValidationStateProof(t *testing.T) { }, } - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, blockHeader, nil) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, blockHeader, nil, nil) require.NoError(t, err) err = verifyTxn(0, groupCtx) @@ -352,7 +352,7 @@ func TestDecodeNil(t *testing.T) { err := protocol.Decode(nilEncoding, &st) if err == nil { // This used to panic when run on a zero value of SignedTxn. - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{st}, blockHeader, nil) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{st}, blockHeader, nil, nil) require.NoError(t, err) verifyTxn(0, groupCtx) } @@ -1069,7 +1069,7 @@ func BenchmarkTxn(b *testing.B) { b.ResetTimer() for _, txnGroup := range txnGroups { - groupCtx, err := PrepareGroupContext(txnGroup, &blk.BlockHeader, nil) + groupCtx, err := PrepareGroupContext(txnGroup, &blk.BlockHeader, nil, nil) require.NoError(b, err) for i := range txnGroup { err := verifyTxn(i, groupCtx) diff --git a/data/transactions/verify/verifiedTxnCache.go b/data/transactions/verify/verifiedTxnCache.go index efa4701506..184ed273e6 100644 --- a/data/transactions/verify/verifiedTxnCache.go +++ b/data/transactions/verify/verifiedTxnCache.go @@ -22,7 +22,6 @@ import ( "github.com/algorand/go-deadlock" "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/protocol" ) @@ -73,7 +72,7 @@ type VerifiedTransactionCache interface { type verifiedTransactionCache struct { // Number of entries in each map (bucket). entriesPerBucket int - // bucketsLock is the lock for syncornizing the access to the cache + // bucketsLock is the lock for synchronizing access to the cache bucketsLock deadlock.Mutex // buckets is the circular cache buckets buffer buckets []map[transactions.Txid]*GroupContext @@ -127,7 +126,6 @@ func (v *verifiedTransactionCache) GetUnverifiedTransactionGroups(txnGroups [][] for txnGroupIndex := 0; txnGroupIndex < len(txnGroups); txnGroupIndex++ { signedTxnGroup := txnGroups[txnGroupIndex] verifiedTxn := 0 - groupCtx.minAvmVersion = logic.ComputeMinAvmVersion(transactions.WrapSignedTxnsWithAD(signedTxnGroup)) baseBucket := v.base for txnIdx := 0; txnIdx < len(signedTxnGroup); txnIdx++ { diff --git a/data/transactions/verify/verifiedTxnCache_test.go b/data/transactions/verify/verifiedTxnCache_test.go index b90eb66efa..880780f94d 100644 --- a/data/transactions/verify/verifiedTxnCache_test.go +++ b/data/transactions/verify/verifiedTxnCache_test.go @@ -34,7 +34,7 @@ func TestAddingToCache(t *testing.T) { impl := icache.(*verifiedTransactionCache) _, signedTxn, secrets, addrs := generateTestObjects(10, 5, 0, 50) txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) - groupCtx, err := PrepareGroupContext(txnGroups[0], blockHeader, nil) + groupCtx, err := PrepareGroupContext(txnGroups[0], blockHeader, nil, nil) require.NoError(t, err) impl.Add(txnGroups[0], groupCtx) // make it was added. @@ -55,7 +55,7 @@ func TestBucketCycling(t *testing.T) { _, signedTxn, _, _ := generateTestObjects(entriesPerBucket*bucketCount*2, bucketCount, 0, 0) require.Equal(t, entriesPerBucket*bucketCount*2, len(signedTxn)) - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{signedTxn[0]}, blockHeader, nil) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{signedTxn[0]}, blockHeader, nil, nil) require.NoError(t, err) // fill up the cache with entries. @@ -92,7 +92,7 @@ func TestGetUnverifiedTransactionGroups50(t *testing.T) { if i%2 == 0 { expectedUnverifiedGroups = append(expectedUnverifiedGroups, txnGroups[i]) } else { - groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil) + groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil, nil) impl.Add(txnGroups[i], groupCtx) } } @@ -116,7 +116,7 @@ func BenchmarkGetUnverifiedTransactionGroups50(b *testing.B) { if i%2 == 1 { queryTxnGroups = append(queryTxnGroups, txnGroups[i]) } else { - groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil) + groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil, nil) impl.Add(txnGroups[i], groupCtx) } } @@ -145,7 +145,7 @@ func TestUpdatePinned(t *testing.T) { // insert some entries. for i := 0; i < len(txnGroups); i++ { - groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil) + groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil, nil) impl.Add(txnGroups[i], groupCtx) } @@ -174,7 +174,7 @@ func TestPinningTransactions(t *testing.T) { // insert half of the entries. for i := 0; i < len(txnGroups)/2; i++ { - groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil) + groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil, nil) impl.Add(txnGroups[i], groupCtx) } diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go index 0e6a1ff9a2..5d829a386b 100644 --- a/ledger/apply/application_test.go +++ b/ledger/apply/application_test.go @@ -380,8 +380,7 @@ func TestAppCallAddressByIndex(t *testing.T) { a.Equal(sender, addr) addr, err = ac.AddressByIndex(1, sender) - a.Error(err) - a.Contains(err.Error(), "invalid Account reference 1") + a.ErrorContains(err, "invalid Account reference 1") a.Zero(len(ac.Accounts)) acc0 := getRandomAddress(a) @@ -391,8 +390,7 @@ func TestAppCallAddressByIndex(t *testing.T) { a.Equal(acc0, addr) addr, err = ac.AddressByIndex(2, sender) - a.Error(err) - a.Contains(err.Error(), "invalid Account reference 2") + a.ErrorContains(err, "invalid Account reference 2") } func TestAppCallCheckPrograms(t *testing.T) { @@ -401,37 +399,33 @@ func TestAppCallCheckPrograms(t *testing.T) { a := require.New(t) var ac transactions.ApplicationCallTxnFields - var ep logic.EvalParams // This check is for static costs. v26 is last with static cost checking proto := config.Consensus[protocol.ConsensusV26] - ep.Proto = &proto + ep := logic.NewAppEvalParams(nil, &proto, nil) proto.MaxAppProgramCost = 1 - err := checkPrograms(&ac, &ep) - a.Error(err) - a.Contains(err.Error(), "check failed on ApprovalProgram") + err := checkPrograms(&ac, ep) + a.ErrorContains(err, "check failed on ApprovalProgram") program := []byte{2, 0x20, 1, 1, 0x22} // version, intcb, int 1 ac.ApprovalProgram = program ac.ClearStateProgram = program - err = checkPrograms(&ac, &ep) - a.Error(err) - a.Contains(err.Error(), "check failed on ApprovalProgram") + err = checkPrograms(&ac, ep) + a.ErrorContains(err, "check failed on ApprovalProgram") proto.MaxAppProgramCost = 10 - err = checkPrograms(&ac, &ep) + err = checkPrograms(&ac, ep) a.NoError(err) ac.ClearStateProgram = append(ac.ClearStateProgram, program...) ac.ClearStateProgram = append(ac.ClearStateProgram, program...) ac.ClearStateProgram = append(ac.ClearStateProgram, program...) - err = checkPrograms(&ac, &ep) - a.Error(err) - a.Contains(err.Error(), "check failed on ClearStateProgram") + err = checkPrograms(&ac, ep) + a.ErrorContains(err, "check failed on ClearStateProgram") ac.ClearStateProgram = program - err = checkPrograms(&ac, &ep) + err = checkPrograms(&ac, ep) a.NoError(err) } @@ -488,13 +482,15 @@ func TestAppCallApplyCreate(t *testing.T) { h := transactions.Header{ Sender: sender, } - var ep logic.EvalParams - var txnCounter uint64 = 1 b := newTestBalances() + b.SetProto(protocol.ConsensusFuture) + proto := b.ConsensusParams() + ep := logic.NewAppEvalParams(nil, &proto, nil) - err := ApplicationCall(ac, h, b, nil, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "ApplicationCall cannot have nil ApplyData") + var txnCounter uint64 = 1 + + err := ApplicationCall(ac, h, b, nil, 0, ep, txnCounter) + a.ErrorContains(err, "ApplicationCall cannot have nil ApplyData") a.Zero(b.put) a.Zero(b.putAppParams) @@ -502,16 +498,11 @@ func TestAppCallApplyCreate(t *testing.T) { b.balances[creator] = basics.AccountData{} var ad *transactions.ApplyData = &transactions.ApplyData{} - b.SetProto(protocol.ConsensusFuture) - proto := b.ConsensusParams() - ep.Proto = &proto - // this test will succeed in creating the app, but then fail // because the mock balances doesn't update the creators table // so it will think the app doesn't exist - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "only ClearState is supported") + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) + a.ErrorContains(err, "only ClearState is supported") a.Equal(1, b.put) a.Equal(1, b.putAppParams) @@ -528,9 +519,8 @@ func TestAppCallApplyCreate(t *testing.T) { cp.AppParams = maps.Clone(saved.AppParams) cp.AppLocalStates = maps.Clone(saved.AppLocalStates) b.balances[creator] = cp - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "already found app with index") + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) + a.ErrorContains(err, "already found app with index") a.Equal(uint64(0), uint64(b.allocatedAppIdx)) a.Zero(b.put) a.Zero(b.putAppParams) @@ -547,7 +537,7 @@ func TestAppCallApplyCreate(t *testing.T) { b.balances[creator] = cp ac.GlobalStateSchema = basics.StateSchema{NumUint: 1} - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(appIdx, b.allocatedAppIdx) a.Equal(1, b.put) @@ -566,7 +556,7 @@ func TestAppCallApplyCreate(t *testing.T) { txnCounter++ appIdx = basics.AppIndex(txnCounter + 1) b.appCreators[appIdx] = creator - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) br = b.putBalances[creator] a.Equal(uint32(1), br.AppParams[appIdx].ExtraProgramPages) @@ -592,23 +582,22 @@ func TestAppCallApplyCreateOptIn(t *testing.T) { h := transactions.Header{ Sender: sender, } - var ep logic.EvalParams + b := newTestBalancesPass() + b.SetProto(protocol.ConsensusFuture) + proto := b.ConsensusParams() + ep := logic.NewAppEvalParams(nil, &proto, nil) var txnCounter uint64 = 1 appIdx := basics.AppIndex(txnCounter + 1) var ad *transactions.ApplyData = &transactions.ApplyData{} - b := newTestBalancesPass() b.balances = make(map[basics.Address]basics.AccountData) b.balances[creator] = basics.AccountData{} - b.SetProto(protocol.ConsensusFuture) - proto := b.ConsensusParams() - ep.Proto = &proto b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator} gd := map[string]basics.ValueDelta{"uint": {Action: basics.SetUintAction, Uint: 1}} b.delta = transactions.EvalDelta{GlobalDelta: gd} - err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(appIdx, b.allocatedAppIdx) br := b.balances[creator] @@ -650,8 +639,7 @@ func TestAppCallOptIn(t *testing.T) { ad.AppLocalStates[appIdx] = basics.AppLocalState{} b.balances = map[basics.Address]basics.AccountData{sender: ad} err = optInApplication(b, sender, appIdx, params) - a.Error(err) - a.Contains(err.Error(), "has already opted in to app") + a.ErrorContains(err, "has already opted in to app") a.Zero(b.put) a.Zero(b.putAppLocalState) @@ -749,14 +737,13 @@ func TestAppCallClearState(t *testing.T) { var txnCounter uint64 = 1 appIdx := basics.AppIndex(txnCounter + 1) b := newTestBalances() - var ep logic.EvalParams + b.SetProto(protocol.ConsensusFuture) + proto := b.ConsensusParams() + ep := logic.NewAppEvalParams(nil, &proto, nil) ad := &transactions.ApplyData{} b.appCreators = make(map[basics.AppIndex]basics.Address) b.balances = make(map[basics.Address]basics.AccountData, 2) - b.SetProto(protocol.ConsensusFuture) - proto := b.ConsensusParams() - ep.Proto = &proto ac := transactions.ApplicationCallTxnFields{ ApplicationID: appIdx, @@ -782,9 +769,8 @@ func TestAppCallClearState(t *testing.T) { b.pass = true // check app not exist and not opted in b.balances[sender] = basics.AccountData{} - err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "is not currently opted in to app") + err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) + a.ErrorContains(err, "is not currently opted in to app") a.Zero(b.put) a.Zero(b.putAppLocalState) a.Zero(b.deleteAppLocalState) @@ -793,7 +779,7 @@ func TestAppCallClearState(t *testing.T) { b.balances[sender] = basics.AccountData{ AppLocalStates: map[basics.AppIndex]basics.AppLocalState{appIdx: {}}, } - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(1, b.put) a.Zero(b.putAppLocalState) @@ -813,7 +799,7 @@ func TestAppCallClearState(t *testing.T) { appIdx: {Schema: basics.StateSchema{NumUint: 10}}, }, } - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(1, b.put) a.Zero(b.putAppLocalState) @@ -841,7 +827,7 @@ func TestAppCallClearState(t *testing.T) { // one put: to opt out b.pass = false b.delta = transactions.EvalDelta{GlobalDelta: nil} - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(1, b.put) a.Zero(b.putAppLocalState) @@ -858,7 +844,7 @@ func TestAppCallClearState(t *testing.T) { b.pass = true b.delta = transactions.EvalDelta{GlobalDelta: nil} b.err = logic.EvalError{Err: fmt.Errorf("test error")} - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(1, b.put) a.Zero(b.putAppLocalState) @@ -875,7 +861,7 @@ func TestAppCallClearState(t *testing.T) { b.pass = true b.delta = transactions.EvalDelta{GlobalDelta: nil} b.err = fmt.Errorf("test error") - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.Error(err) br = b.putBalances[sender] a.Zero(len(br.AppLocalStates)) @@ -890,7 +876,7 @@ func TestAppCallClearState(t *testing.T) { b.err = nil gd := basics.StateDelta{"uint": {Action: basics.SetUintAction, Uint: 1}} b.delta = transactions.EvalDelta{GlobalDelta: gd} - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(1, b.put) a.Zero(b.putAppLocalState) @@ -905,7 +891,7 @@ func TestAppCallClearState(t *testing.T) { b.err = nil logs := []string{"a"} b.delta = transactions.EvalDelta{Logs: []string{"a"}} - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta) } @@ -953,8 +939,7 @@ func TestAppCallApplyCloseOut(t *testing.T) { b.pass = false err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "transaction rejected by ApprovalProgram") + a.ErrorContains(err, "transaction rejected by ApprovalProgram") a.Zero(b.put) a.Zero(b.putAppLocalState) a.Zero(b.deleteAppLocalState) @@ -966,8 +951,7 @@ func TestAppCallApplyCloseOut(t *testing.T) { b.pass = true b.balances[sender] = basics.AccountData{} err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "is not opted in to any app") + a.ErrorContains(err, "is not opted in to any app") a.Zero(b.put) a.Zero(b.putAppLocalState) a.Zero(b.deleteAppLocalState) @@ -1032,9 +1016,11 @@ func TestAppCallApplyUpdate(t *testing.T) { h := transactions.Header{ Sender: sender, } - var ep logic.EvalParams var ad *transactions.ApplyData = &transactions.ApplyData{} b := newTestBalances() + b.SetProto(protocol.ConsensusV28) + proto := b.ConsensusParams() + ep := logic.NewAppEvalParams(nil, &proto, nil) b.balances = make(map[basics.Address]basics.AccountData) cbr := basics.AccountData{ @@ -1046,14 +1032,9 @@ func TestAppCallApplyUpdate(t *testing.T) { b.balances[creator] = cp b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator} - b.SetProto(protocol.ConsensusV28) - proto := b.ConsensusParams() - ep.Proto = &proto - b.pass = false - err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "transaction rejected by ApprovalProgram") + err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) + a.ErrorContains(err, "transaction rejected by ApprovalProgram") a.Zero(b.put) a.Zero(b.putAppParams) br := b.balances[creator] @@ -1063,7 +1044,7 @@ func TestAppCallApplyUpdate(t *testing.T) { // check updating on empty sender's balance record - happy case b.pass = true b.balances[sender] = basics.AccountData{} - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Zero(b.put) a.Equal(1, b.putAppParams) @@ -1103,7 +1084,7 @@ func TestAppCallApplyUpdate(t *testing.T) { logs := []string{"a"} b.delta = transactions.EvalDelta{Logs: []string{"a"}} - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta) @@ -1128,7 +1109,7 @@ func TestAppCallApplyUpdate(t *testing.T) { } b.pass = true - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.Error(err) a.Contains(err.Error(), fmt.Sprintf("updateApplication %s program too long", test.name)) } @@ -1144,7 +1125,7 @@ func TestAppCallApplyUpdate(t *testing.T) { ClearStateProgram: []byte{2}, } b.pass = true - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) // check extraProgramPages is used and long sum rejected @@ -1155,9 +1136,8 @@ func TestAppCallApplyUpdate(t *testing.T) { ClearStateProgram: appr, } b.pass = true - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "updateApplication app programs too long") + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) + a.ErrorContains(err, "updateApplication app programs too long") } @@ -1209,8 +1189,7 @@ func TestAppCallApplyDelete(t *testing.T) { b.pass = false err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "transaction rejected by ApprovalProgram") + a.ErrorContains(err, "transaction rejected by ApprovalProgram") a.Zero(b.put) a.Zero(b.putAppParams) a.Zero(b.deleteAppParams) @@ -1300,17 +1279,17 @@ func TestAppCallApplyCreateClearState(t *testing.T) { h := transactions.Header{ Sender: sender, } - var ep logic.EvalParams var txnCounter uint64 = 1 appIdx := basics.AppIndex(txnCounter + 1) var ad *transactions.ApplyData = &transactions.ApplyData{} b := newTestBalancesPass() + b.SetProto(protocol.ConsensusFuture) + proto := b.ConsensusParams() + ep := logic.NewAppEvalParams(nil, &proto, nil) b.balances = make(map[basics.Address]basics.AccountData) b.balances[creator] = basics.AccountData{} b.SetProto(protocol.ConsensusFuture) - proto := b.ConsensusParams() - ep.Proto = &proto b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator} @@ -1319,9 +1298,8 @@ func TestAppCallApplyCreateClearState(t *testing.T) { b.delta = transactions.EvalDelta{GlobalDelta: gd} // check creation on empty balance record - err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) - a.Error(err) - a.Contains(err.Error(), "not currently opted in") + err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) + a.ErrorContains(err, "not currently opted in") a.Equal(appIdx, b.allocatedAppIdx) a.Equal(transactions.EvalDelta{}, ad.EvalDelta) br := b.balances[creator] @@ -1350,17 +1328,17 @@ func TestAppCallApplyCreateDelete(t *testing.T) { h := transactions.Header{ Sender: sender, } - var ep logic.EvalParams var txnCounter uint64 = 1 appIdx := basics.AppIndex(txnCounter + 1) var ad *transactions.ApplyData = &transactions.ApplyData{} b := newTestBalancesPass() + b.SetProto(protocol.ConsensusFuture) + proto := b.ConsensusParams() + ep := logic.NewAppEvalParams(nil, &proto, nil) b.balances = make(map[basics.Address]basics.AccountData) b.balances[creator] = basics.AccountData{} b.SetProto(protocol.ConsensusFuture) - proto := b.ConsensusParams() - ep.Proto = &proto b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator} @@ -1369,7 +1347,7 @@ func TestAppCallApplyCreateDelete(t *testing.T) { b.delta = transactions.EvalDelta{GlobalDelta: gd} // check creation on empty balance record - err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(appIdx, b.allocatedAppIdx) a.Equal(transactions.EvalDelta{GlobalDelta: gd}, ad.EvalDelta) @@ -1378,7 +1356,7 @@ func TestAppCallApplyCreateDelete(t *testing.T) { logs := []string{"a"} b.delta = transactions.EvalDelta{Logs: []string{"a"}} - err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) + err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter) a.NoError(err) a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta) diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index fcebc8ffe1..f0dde2b399 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -958,7 +958,7 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit cow := eval.state.child(len(txgroup)) defer cow.recycle() - evalParams := logic.NewEvalParams(txgroup, &eval.proto, &eval.specials) + evalParams := logic.NewAppEvalParams(txgroup, &eval.proto, &eval.specials) evalParams.Tracer = eval.Tracer if eval.Tracer != nil { diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 63d46bf531..0228357b53 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -1205,7 +1205,7 @@ func TestLogicSigOverBudget(t *testing.T) { ` + strings.Repeat(`byte "a" keccak256 pop -`, 200) + `int 1`) +`, 310) + `int 1`) require.NoError(t, err) program := logic.Program(op.Program) lsigAddr := basics.Address(crypto.HashObj(&program)) @@ -1261,7 +1261,7 @@ int 1`, ApplyData: expectedAppCallAD, }, AppBudgetConsumed: 0, - LogicSigBudgetConsumed: 19934, + LogicSigBudgetConsumed: 39998, }, }, FailedAt: expectedFailedAt, diff --git a/test/scripts/e2e_subs/app-group.py b/test/scripts/e2e_subs/app-group.py index 738e1cb794..7ad92b92c6 100755 --- a/test/scripts/e2e_subs/app-group.py +++ b/test/scripts/e2e_subs/app-group.py @@ -74,4 +74,5 @@ txinfo, err = goal.app_call(joe, app_id, accounts=[goal.account]) assert not err, err +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/app-inner-calls-csp.py b/test/scripts/e2e_subs/app-inner-calls-csp.py index e5e7d32a9d..75d8e0f6f7 100755 --- a/test/scripts/e2e_subs/app-inner-calls-csp.py +++ b/test/scripts/e2e_subs/app-inner-calls-csp.py @@ -154,4 +154,5 @@ _, err = goal.app_call(joe, app1ID, app_args=[0x01], foreign_apps=[int(app2ID), int(app3ID)]) assert not err, err +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/app-inner-calls.py b/test/scripts/e2e_subs/app-inner-calls.py index 02010708e3..3268ef87de 100755 --- a/test/scripts/e2e_subs/app-inner-calls.py +++ b/test/scripts/e2e_subs/app-inner-calls.py @@ -145,4 +145,5 @@ assert 10 == goal.balance(app_account, asa_id) +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/app-rekey.py b/test/scripts/e2e_subs/app-rekey.py index 94bfcd22a5..efa1accca7 100755 --- a/test/scripts/e2e_subs/app-rekey.py +++ b/test/scripts/e2e_subs/app-rekey.py @@ -78,4 +78,5 @@ assert not err, err assert goal.balance(joe) == joeb+7, goal.balance(joe) +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/example.py b/test/scripts/e2e_subs/example.py index cc7acda2c1..4852ac9d76 100755 --- a/test/scripts/e2e_subs/example.py +++ b/test/scripts/e2e_subs/example.py @@ -19,7 +19,7 @@ pay = goal.pay(goal.account, receiver=joe, amt=10000) # under min balance txid, err = goal.send(pay, confirm=False) # errors early -assert err +assert "balance 10000 below min 100000" in str(err), err pay = goal.pay(goal.account, receiver=joe, amt=500_000) txinfo, err = goal.send(pay) diff --git a/test/scripts/e2e_subs/goal/goal.py b/test/scripts/e2e_subs/goal/goal.py index 969fc2053b..d36241fd78 100755 --- a/test/scripts/e2e_subs/goal/goal.py +++ b/test/scripts/e2e_subs/goal/goal.py @@ -144,10 +144,10 @@ def sign(self, tx): self.open_wallet(self.wallet_name) return self.kmd.sign_transaction(self.handle, "", tx) - def sign_with_program(self, tx, program, delegator=None): + def sign_with_program(self, tx, program, args=None, delegator=None): if delegator: raise Exception("haven't implemented delgated logicsig yet") - return txn.LogicSigTransaction(tx, txn.LogicSig(program)) + return txn.LogicSigTransaction(tx, txn.LogicSig(program, args)) def send(self, tx, confirm=True): try: diff --git a/test/scripts/e2e_subs/hdr-access.py b/test/scripts/e2e_subs/hdr-access.py index 4da7856a34..bb6c0ad650 100755 --- a/test/scripts/e2e_subs/hdr-access.py +++ b/test/scripts/e2e_subs/hdr-access.py @@ -91,4 +91,5 @@ assert "round 0 is not available" in str(err), err assert "outside [1-" in str(err), err # confirms that we can look back to 1 +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/lsig-budget.py b/test/scripts/e2e_subs/lsig-budget.py new file mode 100755 index 0000000000..8cab2644ab --- /dev/null +++ b/test/scripts/e2e_subs/lsig-budget.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +import os +import sys +from goal import Goal +import algosdk.logic as logic + +from datetime import datetime + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} start {stamp}") + +goal = Goal(sys.argv[1], autosend=True) + +# This lsig runs keccak256 (cost=130) the number of times in its first +# logicsig arg, then accepts. In effect, it accepts if it has enough +# budget to run the number of hashes requested. The program consumes 6 +# opcodes in the base case (args[0] == 0), and 137 for each loop. +run_hashes = """ +#pragma version 6 + arg 0 + btoi +loop: + dup + bz end + byte 0x0102030405060708 + keccak256 + pop + int 1 + - + b loop +end: + pop // the 0 loop variable + int 1 +""" + +code = goal.assemble(run_hashes) +escrow = goal.logic_address(code) + +# Fund the lsig's escrow account +_, err = goal.pay(goal.account, escrow, amt=1_000_000) +assert not err, err + +# Construct a transaction that uses the lsig. Can't send, because we +# have to fill in the lsig (and args) +tx = goal.pay(escrow, escrow, amt=0, note=b'5', send=False) +# 5 loops is fine (20k budget) +stx = goal.sign_with_program(tx, code, [(5).to_bytes(8, "big")]) +txinfo, err = goal.send(stx) +assert not err, err + +# 145 loops is fine 6+145*137 < 20k budget +tx = goal.pay(escrow, escrow, amt=0, note=b'145', send=False) +stx = goal.sign_with_program(tx, code, [(145).to_bytes(8, "big")]) +txinfo, err = goal.send(stx) +assert not err, err + +# 146 is not 6+146*137 = 20,008 (20k budget) +tx = goal.pay(escrow, escrow, amt=0, note=b'146', send=False) +stx = goal.sign_with_program(tx, code, [(146).to_bytes(8, "big")]) +txinfo, err = goal.send(stx) +assert "dynamic cost budget exceeded, executing keccak256" in str(err) + +# Now, try pooling across multiple logicsigs 39988/137 = 291.xxx +tx0 = goal.pay(escrow, escrow, amt=0, note=b'200', send=False) +tx1 = goal.pay(escrow, escrow, amt=0, note=b'91', send=False) +stx0 = goal.sign_with_program(tx0, code, [(200).to_bytes(8, "big")]) +stx1 = goal.sign_with_program(tx1, code, [(91).to_bytes(8, "big")]) +txinfo, err = goal.send_group([stx0, stx1]) +assert not err, err + +# order doesn't matter +tx0.group = None +tx1.group = None +txinfo, err = goal.send_group([stx1, stx0]) # rearrange +assert not err, err + +# 292 is too much +tx0 = goal.pay(escrow, escrow, amt=0, note=b'200', send=False) +tx1 = goal.pay(escrow, escrow, amt=0, note=b'92', send=False) +stx0 = goal.sign_with_program(tx0, code, [(200).to_bytes(8, "big")]) +stx1 = goal.sign_with_program(tx1, code, [(92).to_bytes(8, "big")]) +txinfo, err = goal.send_group([stx0, stx1]) +assert "dynamic cost budget exceeded, executing keccak256" in str(err) + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/shared-resources.py b/test/scripts/e2e_subs/shared-resources.py index d4c1c95945..b5a93f96cf 100755 --- a/test/scripts/e2e_subs/shared-resources.py +++ b/test/scripts/e2e_subs/shared-resources.py @@ -94,4 +94,5 @@ assert grp2_info["local-state-delta"][0]["address"] == goal.account assert grp2_info["local-state-delta"][0]["delta"][0]["value"]["uint"] == 70 +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") print(f"{os.path.basename(sys.argv[0])} OK {stamp}") From 1bf7a214788a66be8b28649103b600bb71e2822a Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 31 Jul 2023 09:37:15 -0400 Subject: [PATCH 14/66] Revert "ledger: increase locks granularity in lookupWithoutRewards" (#5620) --- ledger/acctupdates.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 77e76039c9..bb495f3f8f 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1327,21 +1327,15 @@ func (au *accountUpdates) lookupWithoutRewards(rnd basics.Round, addr basics.Add return } - deltas := au.deltas[:offset] rewardsVersion = au.versions[offset] rewardsLevel = au.roundTotals[offset].RewardsLevel // check if we've had this address modified in the past rounds. ( i.e. if it's in the deltas ) macct, indeltas := au.accounts[addr] - if synchronized { - au.accountsMu.RUnlock() - needUnlock = false - } - if indeltas { // Check if this is the most recent round, in which case, we can // use a cache of the most recent account state. - if offset == uint64(currentDeltaLen) { + if offset == uint64(len(au.deltas)) { return macct.data, rnd, rewardsVersion, rewardsLevel, nil } // the account appears in the deltas, but we don't know if it appears in the @@ -1349,7 +1343,7 @@ func (au *accountUpdates) lookupWithoutRewards(rnd basics.Round, addr basics.Add // backwards to ensure that later updates take priority if present. for offset > 0 { offset-- - d, ok := deltas[offset].Accts.GetData(addr) + d, ok := au.deltas[offset].Accts.GetData(addr) if ok { // the returned validThrough here is not optimal, but it still correct. We could get a more accurate value by scanning // the deltas forward, but this would be time consuming loop, which might not pay off. @@ -1364,10 +1358,6 @@ func (au *accountUpdates) lookupWithoutRewards(rnd basics.Round, addr basics.Add rnd = currentDbRound + basics.Round(currentDeltaLen) } - if synchronized { - au.accountsMu.RLock() - needUnlock = true - } // check the baseAccounts - if macct, has := au.baseAccounts.read(addr); has { // we don't technically need this, since it's already in the baseAccounts, however, writing this over From d82200a8472d20868192ceac8981a872e9b2aa0c Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Tue, 1 Aug 2023 10:55:13 -0700 Subject: [PATCH 15/66] p2p: multiaddr dns bootstrapping utils (#5575) --- cmd/algons/commands.go | 1 + cmd/algons/dnsaddrCmd.go | 65 ++++++++++ go.mod | 1 + go.sum | 4 + network/p2p/dnsaddr/resolve.go | 68 +++++++++++ network/p2p/dnsaddr/resolveController.go | 64 ++++++++++ network/p2p/dnsaddr/resolveController_test.go | 45 +++++++ network/p2p/dnsaddr/resolve_test.go | 111 ++++++++++++++++++ tools/block-generator/go.mod | 13 ++ tools/block-generator/go.sum | 36 ++++++ tools/network/resolveController.go | 25 ++++ 11 files changed, 433 insertions(+) create mode 100644 cmd/algons/dnsaddrCmd.go create mode 100644 network/p2p/dnsaddr/resolve.go create mode 100644 network/p2p/dnsaddr/resolveController.go create mode 100644 network/p2p/dnsaddr/resolveController_test.go create mode 100644 network/p2p/dnsaddr/resolve_test.go diff --git a/cmd/algons/commands.go b/cmd/algons/commands.go index 15469dd2b5..3e010951dc 100644 --- a/cmd/algons/commands.go +++ b/cmd/algons/commands.go @@ -25,6 +25,7 @@ import ( func init() { rootCmd.AddCommand(dnsCmd) + rootCmd.AddCommand(dnsaddrCmd) } var rootCmd = &cobra.Command{ diff --git a/cmd/algons/dnsaddrCmd.go b/cmd/algons/dnsaddrCmd.go new file mode 100644 index 0000000000..1df13cecfe --- /dev/null +++ b/cmd/algons/dnsaddrCmd.go @@ -0,0 +1,65 @@ +// 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 main + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/algorand/go-algorand/network/p2p/dnsaddr" +) + +var ( + dnsaddrDomain string + secure bool +) + +func init() { + dnsaddrCmd.AddCommand(dnsaddrTreeCmd) + + dnsaddrTreeCmd.Flags().StringVarP(&dnsaddrDomain, "domain", "d", "", "Top level domain") + dnsaddrTreeCmd.MarkFlagRequired("domain") + dnsaddrTreeCmd.Flags().BoolVarP(&secure, "secure", "s", true, "Enable dnssec") +} + +var dnsaddrCmd = &cobra.Command{ + Use: "dnsaddr", + Short: "Get, Set, and List Dnsaddr entries", + Long: "Get, Set, and List Dnsaddr entries", + Run: func(cmd *cobra.Command, args []string) { + // Fall back + cmd.HelpFunc()(cmd, args) + }, +} + +var dnsaddrTreeCmd = &cobra.Command{ + Use: "tree", + Short: "Recursively resolves and lists the dnsaddr entries of the given domain", + Long: "Recursively resolves and lists the dnsaddr entries of the given domain", + Run: func(cmd *cobra.Command, args []string) { + controller := dnsaddr.NewMultiaddrDNSResolveController(secure, "") + addrs, err := dnsaddr.MultiaddrsFromResolver(dnsaddrDomain, controller) + if err != nil { + fmt.Printf("%s\n", err.Error()) + return + } + for _, addr := range addrs { + fmt.Printf("%s\n", addr.String()) + } + }, +} diff --git a/go.mod b/go.mod index a2694e4a1a..b63246f90b 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/mattn/go-sqlite3 v1.10.0 github.com/miekg/dns v1.1.55 github.com/multiformats/go-multiaddr v0.10.1 + github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/olivere/elastic v6.2.14+incompatible github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.3.0 diff --git a/go.sum b/go.sum index e6d91c2726..8cb249f338 100644 --- a/go.sum +++ b/go.sum @@ -523,8 +523,11 @@ github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYg github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= +github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= github.com/multiformats/go-multiaddr v0.10.1 h1:HghtFrWyZEPrpTvgAMFJi6gFdgHfs2cb0pyfDsk+lqU= github.com/multiformats/go-multiaddr v0.10.1/go.mod h1:jLEZsA61rwWNZQTHHnqq2HNa+4os/Hz54eqiRnsRqYQ= +github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= +github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= @@ -536,6 +539,7 @@ github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7B github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/network/p2p/dnsaddr/resolve.go b/network/p2p/dnsaddr/resolve.go new file mode 100644 index 0000000000..ad9f4e8b42 --- /dev/null +++ b/network/p2p/dnsaddr/resolve.go @@ -0,0 +1,68 @@ +// 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 dnsaddr + +import ( + "context" + "errors" + "fmt" + + "github.com/multiformats/go-multiaddr" +) + +func isDnsaddr(maddr multiaddr.Multiaddr) bool { + first, _ := multiaddr.SplitFirst(maddr) + return first.Protocol().Code == multiaddr.P_DNSADDR +} + +// MultiaddrsFromResolver attempts to recurse through dnsaddrs starting at domain. +// Any further dnsaddrs will be looked up until all TXT records have been fetched, +// and the full list of resulting Multiaddrs is returned. +// It uses the MultiaddrDNSResolveController to cycle through DNS resolvers on failure. +func MultiaddrsFromResolver(domain string, controller *MultiaddrDNSResolveController) ([]multiaddr.Multiaddr, error) { + resolver := controller.Resolver() + if resolver == nil { + return nil, errors.New("passed controller has no resolvers MultiaddrsFromResolver") + } + dnsaddr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/dnsaddr/%s", domain)) + if err != nil { + return nil, fmt.Errorf("unable to construct multiaddr for %s : %v", domain, err) + } + var resolved []multiaddr.Multiaddr + var toResolve = []multiaddr.Multiaddr{dnsaddr} + for resolver != nil && len(toResolve) > 0 { + curr := toResolve[0] + maddrs, resolveErr := resolver.Resolve(context.Background(), curr) + if resolveErr != nil { + resolver = controller.NextResolver() + // If we errored, and have exhausted all resolvers, just return + if resolver == nil { + return resolved, resolveErr + } + continue + } + for _, maddr := range maddrs { + if isDnsaddr(maddr) { + toResolve = append(toResolve, maddr) + } else { + resolved = append(resolved, maddr) + } + } + toResolve = toResolve[1:] + } + return resolved, nil +} diff --git a/network/p2p/dnsaddr/resolveController.go b/network/p2p/dnsaddr/resolveController.go new file mode 100644 index 0000000000..7fb920ecb3 --- /dev/null +++ b/network/p2p/dnsaddr/resolveController.go @@ -0,0 +1,64 @@ +// 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 dnsaddr + +import ( + madns "github.com/multiformats/go-multiaddr-dns" + + log "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/tools/network" +) + +// MultiaddrDNSResolveController returns a madns.Resolver, cycling through underlying net.Resolvers +type MultiaddrDNSResolveController struct { + resolver *madns.Resolver + nextResolvers []func() *madns.Resolver + controller network.ResolveController +} + +// NewMultiaddrDNSResolveController constructs a MultiaddrDNSResolveController +func NewMultiaddrDNSResolveController(secure bool, fallbackDNSResolverAddress string) *MultiaddrDNSResolveController { + controller := network.NewResolveController(secure, fallbackDNSResolverAddress, log.Base()) + nextResolvers := []func() *madns.Resolver{controller.SystemDnsaddrResolver} + if fallbackDNSResolverAddress != "" { + nextResolvers = append(nextResolvers, controller.FallbackDnsaddrResolver) + } + return &MultiaddrDNSResolveController{ + resolver: nil, + nextResolvers: append(nextResolvers, controller.DefaultDnsaddrResolver), + controller: controller, + } +} + +// NextResolver applies the nextResolvers functions in order and returns the most recent result +func (c *MultiaddrDNSResolveController) NextResolver() *madns.Resolver { + if len(c.nextResolvers) == 0 { + c.resolver = nil + } else { + c.resolver = c.nextResolvers[0]() + c.nextResolvers = c.nextResolvers[1:] + } + return c.resolver +} + +// Resolver returns the current resolver, invokes NextResolver if the resolver is nil +func (c *MultiaddrDNSResolveController) Resolver() *madns.Resolver { + if c.resolver == nil { + c.resolver = c.NextResolver() + } + return c.resolver +} diff --git a/network/p2p/dnsaddr/resolveController_test.go b/network/p2p/dnsaddr/resolveController_test.go new file mode 100644 index 0000000000..c097240ac6 --- /dev/null +++ b/network/p2p/dnsaddr/resolveController_test.go @@ -0,0 +1,45 @@ +// 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 dnsaddr + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestDnsAddrResolveController(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + dnsaddrCont := NewMultiaddrDNSResolveController(true, "127.0.0.1") + + // Assert that the dnsaddr resolver cycles through the dns resolvers properly + assert.Equal(t, dnsaddrCont.controller.SystemDnsaddrResolver(), dnsaddrCont.Resolver()) + assert.Equal(t, dnsaddrCont.controller.FallbackDnsaddrResolver(), dnsaddrCont.NextResolver()) + assert.Equal(t, dnsaddrCont.controller.DefaultDnsaddrResolver(), dnsaddrCont.NextResolver()) + // It should return nil once all the resolvers have been tried + assert.Nil(t, dnsaddrCont.NextResolver()) + assert.Nil(t, dnsaddrCont.NextResolver()) + + // It should not include fallback if none was specified + dnsaddrCont = NewMultiaddrDNSResolveController(true, "") + assert.Equal(t, 2, len(dnsaddrCont.nextResolvers)) + +} diff --git a/network/p2p/dnsaddr/resolve_test.go b/network/p2p/dnsaddr/resolve_test.go new file mode 100644 index 0000000000..df564d5e92 --- /dev/null +++ b/network/p2p/dnsaddr/resolve_test.go @@ -0,0 +1,111 @@ +// 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 dnsaddr + +import ( + "context" + "fmt" + "net" + "testing" + + "github.com/multiformats/go-multiaddr" + madns "github.com/multiformats/go-multiaddr-dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/tools/network" +) + +func TestIsDnsaddr(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + testcases := []struct { + name string + addr string + expected bool + }{ + {name: "DnsAddr", addr: "/dnsaddr/foobar.com", expected: true}, + {name: "DnsAddrWithPeerId", addr: "/dnsaddr/foobar.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", expected: true}, + {name: "DnsAddrWithIPPeerId", addr: "/dnsaddr/foobar.com/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", expected: true}, + {name: "Dns4Addr", addr: "/dns4/foobar.com/", expected: false}, + {name: "Dns6Addr", addr: "/dns6/foobar.com/", expected: false}, + {name: "Dns4AddrWithPeerId", addr: "/dns4/foobar.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", expected: false}, + } + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + maddr, err := multiaddr.NewMultiaddr(testcase.addr) + require.NoError(t, err) + require.Equal(t, testcase.expected, isDnsaddr(maddr)) + }) + } +} + +func TestMultiaddrsFromResolver(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + dnsaddrCont := NewMultiaddrDNSResolveController(false, "") + + // Fail on bad dnsaddr domain + maddrs, err := MultiaddrsFromResolver("/bogus/foobar", dnsaddrCont) + assert.Empty(t, maddrs) + assert.ErrorContains(t, err, fmt.Sprintf("unable to construct multiaddr for %s", "/bogus/foobar")) + + // Success on a dnsaddr that needs to resolve recursively + maddrs, err = MultiaddrsFromResolver("bootstrap.libp2p.io", dnsaddrCont) + assert.NoError(t, err) + assert.NotEmpty(t, maddrs) + // bootstrap.libp2p.io's dnsaddr record contains 4 more dnsaddrs to resolve + assert.Greater(t, len(maddrs), 4) +} + +type failureResolver struct{} + +func (f *failureResolver) LookupIPAddr(context.Context, string) ([]net.IPAddr, error) { + return nil, fmt.Errorf("always errors") +} +func (f *failureResolver) LookupTXT(context.Context, string) ([]string, error) { + return nil, fmt.Errorf("always errors") +} + +func TestMultiaddrsFromResolverDnsFailure(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + dnsaddrCont := &MultiaddrDNSResolveController{ + resolver: nil, + nextResolvers: nil, + } + + // Fail on no resolver + maddrs, err := MultiaddrsFromResolver("", dnsaddrCont) + assert.Empty(t, maddrs) + assert.ErrorContains(t, err, fmt.Sprintf("passed controller has no resolvers MultiaddrsFromResolver")) + + resolver, _ := madns.NewResolver(madns.WithDefaultResolver(&failureResolver{})) + dnsaddrCont = &MultiaddrDNSResolveController{ + resolver: resolver, + nextResolvers: nil, + controller: network.ResolveController{}, + } + // Fail on resolver error + maddrs, err = MultiaddrsFromResolver("bootstrap.libp2p.io", dnsaddrCont) + assert.Empty(t, maddrs) + assert.ErrorContains(t, err, "always errors") +} diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 0d30c99192..e3036565d1 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -32,17 +32,29 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/ipfs/go-cid v0.4.1 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-sqlite3 v1.10.0 // indirect github.com/miekg/dns v1.1.55 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multiaddr v0.10.1 // indirect + github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect github.com/olivere/elastic v6.2.14+incompatible // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect @@ -51,4 +63,5 @@ require ( golang.org/x/sys v0.10.0 // indirect golang.org/x/tools v0.11.0 // indirect gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 // indirect + lukechampine.com/blake3 v1.2.1 // indirect ) diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index d3326e54a2..7c2b241889 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -42,10 +42,14 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -54,11 +58,36 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr v0.10.1 h1:HghtFrWyZEPrpTvgAMFJi6gFdgHfs2cb0pyfDsk+lqU= +github.com/multiformats/go-multiaddr v0.10.1/go.mod h1:jLEZsA61rwWNZQTHHnqq2HNa+4os/Hz54eqiRnsRqYQ= +github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= +github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/olivere/elastic v6.2.14+incompatible h1:k+KadwNP/dkXE0/eu+T6otk1+5fe0tEpPyQJ4XVm5i8= github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= @@ -71,6 +100,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -83,6 +114,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= @@ -107,9 +139,11 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -133,4 +167,6 @@ gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009/go.mod h1:O0bY1e/ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw= diff --git a/tools/network/resolveController.go b/tools/network/resolveController.go index a36914f21b..b9b75fb7b7 100644 --- a/tools/network/resolveController.go +++ b/tools/network/resolveController.go @@ -20,6 +20,8 @@ import ( "net" "time" + madns "github.com/multiformats/go-multiaddr-dns" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/tools/network/dnssec" ) @@ -85,3 +87,26 @@ func (c *ResolveController) DefaultResolver() ResolverIf { } return &Resolver{} } + +// SystemDnsaddrResolver returns the dnsaddr resolver that uses OS-defined DNS server +func (c *ResolveController) SystemDnsaddrResolver() *madns.Resolver { + resolver := c.SystemResolver() + // ignore errors here since we madns.WithDefaultResolver can't error + mResv, _ := madns.NewResolver(madns.WithDefaultResolver(resolver)) + return mResv +} + +// FallbackDnsaddrResolver returns the dnsaddr resolver w/ fallback DNS address +func (c *ResolveController) FallbackDnsaddrResolver() *madns.Resolver { + resolver := c.FallbackResolver() + // ignore errors here since we madns.WithDefaultResolver can't error + mResv, _ := madns.NewResolver(madns.WithDefaultResolver(resolver)) + return mResv +} + +// DefaultDnsaddrResolver returns the dnsaddr resolver w/ default DNS address +func (c *ResolveController) DefaultDnsaddrResolver() *madns.Resolver { + resolver := c.DefaultResolver() + mResv, _ := madns.NewResolver(madns.WithDefaultResolver(resolver)) + return mResv +} From 8c87fa54fa8d012845ddaca85cfbb9760fd279ff Mon Sep 17 00:00:00 2001 From: Shant Karakashian <55754073+algonautshant@users.noreply.github.com> Date: Wed, 2 Aug 2023 10:10:20 -0400 Subject: [PATCH 16/66] ledger: remove redundant block header cache (#5540) --- cmd/tealdbg/localLedger.go | 4 - daemon/algod/api/server/v2/dryrun.go | 4 - data/ledger_test.go | 27 ++-- data/transactions/logic/eval.go | 11 +- data/transactions/logic/eval_test.go | 14 +- data/transactions/logic/ledger_test.go | 4 +- data/transactions/verify/txn_test.go | 3 - ledger/acctupdates.go | 14 +- ledger/acctupdates_test.go | 13 ++ ledger/blockHeaderCache.go | 86 ----------- ledger/blockHeaderCache_test.go | 94 ------------ ledger/eval/applications.go | 5 - ledger/eval/cow.go | 5 - ledger/eval/eval.go | 5 - ledger/eval/eval_test.go | 8 - .../prefetcher/prefetcher_alignment_test.go | 4 - ledger/evalindexer.go | 7 +- ledger/evalindexer_test.go | 4 +- ledger/ledger.go | 50 ++---- ledger/ledger_test.go | 48 +----- ledger/roundlru.go | 142 ------------------ ledger/roundlru_test.go | 95 ------------ ledger/tracker.go | 2 + ledger/txtail.go | 36 +++-- stateproof/abstractions.go | 1 + 25 files changed, 100 insertions(+), 586 deletions(-) delete mode 100644 ledger/blockHeaderCache.go delete mode 100644 ledger/blockHeaderCache_test.go delete mode 100644 ledger/roundlru.go delete mode 100644 ledger/roundlru_test.go 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/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/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/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/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" From ebd655a94d8c2457aea50bec0d250e96291d426f Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 2 Aug 2023 09:24:22 -0500 Subject: [PATCH 17/66] tools: test on CI regardless of modded file (#5621) --- .github/workflows/tools.yml | 3 --- tools/block-generator/go.mod | 2 +- tools/x-repo-types/Makefile | 25 +++++++++++++++++++------ tools/x-repo-types/go.mod | 2 +- 4 files changed, 21 insertions(+), 11 deletions(-) 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/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/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 ) From ba17c2c7291e20feb1396c4c52ab886e3be8d1e9 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 2 Aug 2023 12:00:02 -0400 Subject: [PATCH 18/66] tools: additional generator lifecycle logging. (#5627) --- tools/block-generator/runner/run.go | 36 ++++++++++++------- .../runner/template/conduit.yml.tmpl | 2 +- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 626a4493a6..e1d96ba957 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -43,6 +43,8 @@ import ( //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 +109,7 @@ func Run(args Args) error { } runnerArgs := args runnerArgs.Path = path - fmt.Printf("Running test for configuration '%s'\n", path) + fmt.Printf("%sRunning test for configuration '%s'\n", pad, path) return runnerArgs.run(reportDirectory) }) if err != nil { @@ -155,6 +157,7 @@ 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) @@ -162,28 +165,30 @@ func (r *Args) run(reportDirectory string) error { generatorShutdownFunc, _ := startGenerator(r.Path, nextRound, r.GenesisFile, r.RunnerVerbose, algodNet, blockMiddleware) defer func() { // Shutdown generator. + fmt.Println("Shutting down generator...") 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: logfile, + 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 +201,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 +231,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 +369,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) } @@ -445,7 +456,7 @@ func startGenerator(configFile string, dbround uint64, genesisFile string, verbo // 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 +475,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 +483,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 +499,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" From 0b7958ab96828eb7a7d49ed3aeaabf17a0ee02e5 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 2 Aug 2023 18:00:53 -0400 Subject: [PATCH 19/66] tools: write block generator ledger output to a file (#5630) --- ledger/lrukv.go | 2 +- ledger/lrukv_test.go | 7 ++-- ledger/lruresources.go | 2 +- ledger/lruresources_test.go | 7 ++-- tools/block-generator/generator/generate.go | 15 +++++--- .../generator/generate_test.go | 35 ++++++++++--------- .../generator/generator_ledger.go | 8 ++--- .../generator/generator_types.go | 4 ++- tools/block-generator/generator/server.go | 7 ++-- tools/block-generator/runner/run.go | 27 +++++++++----- 10 files changed, 69 insertions(+), 45 deletions(-) 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/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/runner/run.go b/tools/block-generator/runner/run.go index e1d96ba957..31c83dc91b 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -35,9 +35,11 @@ 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 @@ -109,7 +111,9 @@ func Run(args Args) error { } runnerArgs := args runnerArgs.Path = path - fmt.Printf("%sRunning test for configuration '%s'\n", pad, path) + fmt.Println("----------------------------------------") + fmt.Printf("%sRunning test for configuration: %s\n", pad, info.Name()) + fmt.Println("----------------------------------------") return runnerArgs.run(reportDirectory) }) if err != nil { @@ -123,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 { @@ -162,10 +167,10 @@ func (r *Args) run(reportDirectory string) error { // 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.Println("Shutting down generator...") + fmt.Printf("%sShutting down generator...\n", pad) if err := generatorShutdownFunc(); err != nil { fmt.Printf("failed to shutdown generator: %s\n", err) } @@ -184,7 +189,7 @@ func (r *Args) run(reportDirectory string) error { defer f.Close() conduitConfig := config{ LogLevel: r.ConduitLogLevel, - LogFile: logfile, + LogFile: conduitlogfile, MetricsPort: fmt.Sprintf(":%d", r.MetricsPort), AlgodNet: algodNet, PostgresConnectionString: r.PostgresConnectionString, @@ -449,9 +454,15 @@ 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() { From 5d5ca32d517a4b88767add3189a4199d1fe1b9e0 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 2 Aug 2023 18:03:04 -0400 Subject: [PATCH 20/66] api: describe WaitForBlock timeout behavior (#5587) --- daemon/algod/api/algod.oas2.json | 5 +- daemon/algod/api/algod.oas3.yml | 4 +- .../nonparticipating/public/routes.go | 53 ++++++------- daemon/algod/api/server/v2/handlers.go | 5 +- .../algod/api/server/v2/test/handlers_test.go | 75 ++++++++++++++++++- 5 files changed, 107 insertions(+), 35 deletions(-) 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/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) { From ea9efcdbe16eec4760a23a8a1b4e868009285c5a Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 2 Aug 2023 18:04:56 -0400 Subject: [PATCH 21/66] metrics: add counters to broadcastSignedTxGroup (#5588) --- node/node.go | 11 +++++++++++ util/metrics/metrics.go | 5 +++++ 2 files changed, 16 insertions(+) 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/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"} ) From 8b19c6242e8b064af7fe3bb8ce3a45d7fdaac208 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:44:55 -0400 Subject: [PATCH 22/66] libgoal: set FirstValid to LastRound to prevent early tnxs (#5622) --- libgoal/libgoal.go | 12 +++++++++- libgoal/libgoal_test.go | 22 +++++++++---------- .../features/transactions/asset_test.go | 2 +- .../features/transactions/lease_test.go | 4 ++-- test/scripts/e2e_subs/e2e-teal.sh | 3 ++- test/scripts/e2e_subs/limit-swap-test.sh | 2 +- 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 65621e6108..2bc924bac9 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -528,7 +528,17 @@ func computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxn } if firstValid == 0 { - firstValid = lastRound + 1 + // current node might be a bit ahead of the network, and to prevent sibling nodes from rejecting the transaction + // because it's FirstValid is greater than their pending block evaluator. + // For example, a node just added block 100 and immediately sending a new transaction. + // The other side is lagging behind by 100ms and its LastRound is 99 so its transaction pools accepts txns for rounds 100+. + // This means the node client have to set FirstValid to 100 or below. + if lastRound > 0 { + firstValid = lastRound + } else { + // there is no practical sense to set FirstValid to 0, so we set it to 1 + firstValid = 1 + } } if validRounds != 0 { diff --git a/libgoal/libgoal_test.go b/libgoal/libgoal_test.go index 87df50ad92..b2e5d22051 100644 --- a/libgoal/libgoal_test.go +++ b/libgoal/libgoal_test.go @@ -38,7 +38,7 @@ func TestValidRounds(t *testing.T) { validRounds = 0 fv, lv, err := computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) a.NoError(err) - a.Equal(lastRound+1, fv) + a.Equal(lastRound, fv) a.Equal(fv+maxTxnLife, lv) firstValid = 0 @@ -46,34 +46,34 @@ func TestValidRounds(t *testing.T) { validRounds = maxTxnLife + 1 fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) a.NoError(err) - a.Equal(lastRound+1, fv) + a.Equal(lastRound, fv) a.Equal(fv+maxTxnLife, lv) firstValid = 0 lastValid = 0 validRounds = maxTxnLife + 2 - fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) + _, _, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) a.Error(err) a.Equal("cannot construct transaction: txn validity period 1001 is greater than protocol max txn lifetime 1000", err.Error()) firstValid = 0 lastValid = 1 validRounds = 2 - fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) + _, _, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) a.Error(err) a.Equal("cannot construct transaction: ambiguous input: lastValid = 1, validRounds = 2", err.Error()) firstValid = 2 lastValid = 1 validRounds = 0 - fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) + _, _, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) a.Error(err) a.Equal("cannot construct transaction: txn would first be valid on round 2 which is after last valid round 1", err.Error()) firstValid = 1 lastValid = maxTxnLife + 2 validRounds = 0 - fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) + _, _, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) a.Error(err) a.Equal("cannot construct transaction: txn validity period ( 1 to 1002 ) is greater than protocol max txn lifetime 1000", err.Error()) @@ -90,7 +90,7 @@ func TestValidRounds(t *testing.T) { validRounds = 0 fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) a.NoError(err) - a.Equal(lastRound+1, fv) + a.Equal(lastRound, fv) a.Equal(lastRound+1, lv) firstValid = 0 @@ -98,16 +98,16 @@ func TestValidRounds(t *testing.T) { validRounds = 1 fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) a.NoError(err) - a.Equal(lastRound+1, fv) - a.Equal(lastRound+1, lv) + a.Equal(lastRound, fv) + a.Equal(lastRound, lv) firstValid = 0 lastValid = 0 validRounds = maxTxnLife fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife) a.NoError(err) - a.Equal(lastRound+1, fv) - a.Equal(lastRound+maxTxnLife, lv) + a.Equal(lastRound, fv) + a.Equal(lastRound+maxTxnLife-1, lv) firstValid = 1 lastValid = 0 diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index 7715f01f41..d53c352126 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -80,7 +80,7 @@ func TestAssetValidRounds(t *testing.T) { validRounds = cparams.MaxTxnLife + 1 firstValid, lastValid, lastRound, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) a.NoError(err) - a.Equal(lastRound+1, firstValid) + a.True(firstValid == 1 || firstValid == lastRound) a.Equal(firstValid+cparams.MaxTxnLife, lastValid) firstValid = 0 diff --git a/test/e2e-go/features/transactions/lease_test.go b/test/e2e-go/features/transactions/lease_test.go index 7dbb061900..093ec2273b 100644 --- a/test/e2e-go/features/transactions/lease_test.go +++ b/test/e2e-go/features/transactions/lease_test.go @@ -136,11 +136,11 @@ func TestLeaseRegressionFaultyFirstValidCheckOld_2f3880f7(t *testing.T) { a.True(confirmed, "lease txn confirmed") bal1, _ := fixture.GetBalanceAndRound(account1) - bal2, _ := fixture.GetBalanceAndRound(account2) + bal2, curRound := fixture.GetBalanceAndRound(account2) a.Equal(bal1, uint64(1000000)) a.Equal(bal2, uint64(0)) - tx2, err := client.ConstructPayment(account0, account2, 0, 2000000, nil, "", lease, 0, 0) + tx2, err := client.ConstructPayment(account0, account2, 0, 2000000, nil, "", lease, basics.Round(curRound)+1, 0) a.NoError(err) stx2, err := client.SignTransactionWithWallet(wh, nil, tx2) diff --git a/test/scripts/e2e_subs/e2e-teal.sh b/test/scripts/e2e_subs/e2e-teal.sh index 06caa5efbe..48da729512 100755 --- a/test/scripts/e2e_subs/e2e-teal.sh +++ b/test/scripts/e2e_subs/e2e-teal.sh @@ -71,7 +71,8 @@ while [ $CROUND -lt $TIMEOUT_ROUND ]; do CROUND=$(goal node status | grep 'Last committed block:'|awk '{ print $4 }') done -${gcmd} clerk send --from-program ${TEMPDIR}/tlhc.teal --to ${ACCOUNT} --close-to ${ACCOUNT} --amount 1 --argb64 AA== +# send txn that valid right after the TIMEOUT_ROUND +${gcmd} clerk send --firstvalid $((${TIMEOUT_ROUND} + 1)) --from-program ${TEMPDIR}/tlhc.teal --to ${ACCOUNT} --close-to ${ACCOUNT} --amount 1 --argb64 AA== cat >${TEMPDIR}/true.teal< Date: Fri, 4 Aug 2023 12:45:28 -0400 Subject: [PATCH 23/66] tests: fix e2e TestAssetValidRounds after libgoal adjustment (#5632) --- test/e2e-go/features/transactions/asset_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index d53c352126..2b38e7d515 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -163,7 +163,7 @@ func TestAssetValidRounds(t *testing.T) { // ledger may advance between SuggestedParams and FillUnsignedTxTemplate calls // expect validity sequence var firstValidRange, lastValidRange []uint64 - for i := lastRoundBefore + 1; i <= lastRoundAfter+1; i++ { + for i := lastRoundBefore; i <= lastRoundAfter+1; i++ { firstValidRange = append(firstValidRange, i) lastValidRange = append(lastValidRange, i+cparams.MaxTxnLife) } From 72765c1afc38b5001ad35f76894f0ed00442f50c Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:37:23 -0400 Subject: [PATCH 24/66] network: make GossipNode more independent from wsNetwork implementation (#5634) --- agreement/fuzzer/networkFacade_test.go | 2 +- agreement/gossip/network_test.go | 2 +- network/msgCompressor.go | 2 +- network/multiplexer.go | 13 +- network/multiplexer_test.go | 7 +- network/netidentity.go | 16 +-- network/netidentity_test.go | 3 +- network/wsNetwork.go | 168 +++++++++++++++---------- network/wsNetwork_test.go | 38 +++--- network/wsPeer.go | 97 ++++++++------ 10 files changed, 201 insertions(+), 147 deletions(-) diff --git a/agreement/fuzzer/networkFacade_test.go b/agreement/fuzzer/networkFacade_test.go index d00ec4e7c1..6db2d88c2e 100644 --- a/agreement/fuzzer/networkFacade_test.go +++ b/agreement/fuzzer/networkFacade_test.go @@ -77,7 +77,7 @@ func MakeNetworkFacade(fuzzer *Fuzzer, nodeID int) *NetworkFacade { n := &NetworkFacade{ fuzzer: fuzzer, nodeID: nodeID, - mux: network.MakeMultiplexer(fuzzer.log), + mux: network.MakeMultiplexer(), clocks: make(map[int]chan time.Time), eventsQueues: make(map[string]int), eventsQueuesCh: make(chan int, 1000), diff --git a/agreement/gossip/network_test.go b/agreement/gossip/network_test.go index bd4f4baf38..8584dc8d26 100644 --- a/agreement/gossip/network_test.go +++ b/agreement/gossip/network_test.go @@ -323,7 +323,7 @@ func makewhiteholeNetwork(domain *whiteholeDomain) *whiteholeNetwork { w := &whiteholeNetwork{ peer: atomic.AddUint32(&domain.peerIdx, 1), lastMsgRead: uint32(len(domain.messages)), - mux: network.MakeMultiplexer(domain.log), + mux: network.MakeMultiplexer(), domain: domain, disconnected: make(map[uint32]bool), } diff --git a/network/msgCompressor.go b/network/msgCompressor.go index a46f37f2c2..28f835832f 100644 --- a/network/msgCompressor.go +++ b/network/msgCompressor.go @@ -144,7 +144,7 @@ func (c *wsPeerMsgDataConverter) convert(tag protocol.Tag, data []byte) ([]byte, func makeWsPeerMsgDataConverter(wp *wsPeer) *wsPeerMsgDataConverter { c := wsPeerMsgDataConverter{ - log: wp.net.log, + log: wp.log, origin: wp.originAddress, } diff --git a/network/multiplexer.go b/network/multiplexer.go index fe5b3dcf42..ddcf1845d8 100644 --- a/network/multiplexer.go +++ b/network/multiplexer.go @@ -17,24 +17,19 @@ package network import ( + "fmt" "sync/atomic" - - "github.com/algorand/go-algorand/logging" ) // Multiplexer is a message handler that sorts incoming messages by Tag and passes // them along to the relevant message handler for that type of message. type Multiplexer struct { msgHandlers atomic.Value // stores map[Tag]MessageHandler, an immutable map. - - log logging.Logger } // MakeMultiplexer creates an empty Multiplexer -func MakeMultiplexer(log logging.Logger) *Multiplexer { - m := &Multiplexer{ - log: log, - } +func MakeMultiplexer() *Multiplexer { + m := &Multiplexer{} m.ClearHandlers([]Tag{}) // allocate the map return m } @@ -78,7 +73,7 @@ func (m *Multiplexer) RegisterHandlers(dispatch []TaggedMessageHandler) { } for _, v := range dispatch { if _, has := mp[v.Tag]; has { - m.log.Panicf("Already registered a handler for tag %v", v.Tag) + panic(fmt.Sprintf("Already registered a handler for tag %v", v.Tag)) } mp[v.Tag] = v.MessageHandler } diff --git a/network/multiplexer_test.go b/network/multiplexer_test.go index 1d0215b909..c9d17dacbc 100644 --- a/network/multiplexer_test.go +++ b/network/multiplexer_test.go @@ -22,7 +22,6 @@ import ( "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -57,7 +56,7 @@ func (th *testHandler) SawMsg(msg IncomingMessage) bool { func TestMultiplexer(t *testing.T) { partitiontest.PartitionTest(t) - m := MakeMultiplexer(logging.TestingLog(t)) + m := MakeMultiplexer() handler := &testHandler{} // Handler shouldn't be called before it is registered @@ -78,7 +77,7 @@ func TestMultiplexer(t *testing.T) { panicked = true } }() - m := MakeMultiplexer(logging.TestingLog(t)) + m := MakeMultiplexer() m.RegisterHandlers([]TaggedMessageHandler{{protocol.TxnTag, handler}, {protocol.TxnTag, handler}}) }() @@ -90,7 +89,7 @@ func TestMultiplexer(t *testing.T) { panicked = true } }() - m := MakeMultiplexer(logging.TestingLog(t)) + m := MakeMultiplexer() m.RegisterHandlers([]TaggedMessageHandler{{protocol.TxnTag, handler}}) m.RegisterHandlers([]TaggedMessageHandler{{protocol.TxnTag, handler}}) diff --git a/network/netidentity.go b/network/netidentity.go index 9fce21fcb0..6414c5e897 100644 --- a/network/netidentity.go +++ b/network/netidentity.go @@ -325,6 +325,8 @@ func (i identityVerificationMessageSigned) Verify(key crypto.PublicKey) bool { // sender's claimed identity and the challenge that was assigned to it. If the identity is available, // the peer is loaded into the identity tracker. Otherwise, we ask the network to disconnect the peer. func identityVerificationHandler(message IncomingMessage) OutgoingMessage { + wn := message.Net.(*WebsocketNetwork) + peer := message.Sender.(*wsPeer) // avoid doing work (crypto and potentially taking a lock) if the peer is already verified if atomic.LoadUint32(&peer.identityVerified) == 1 { @@ -335,27 +337,27 @@ func identityVerificationHandler(message IncomingMessage) OutgoingMessage { err := protocol.Decode(message.Data, &msg) if err != nil { networkPeerIdentityError.Inc(nil) - peer.net.log.With("err", err).With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification could not be decoded, disconnecting") + peer.log.With("err", err).With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification could not be decoded, disconnecting") return OutgoingMessage{Action: Disconnect, reason: disconnectBadIdentityData} } if peer.identityChallenge != msg.Msg.ResponseChallenge { networkPeerIdentityError.Inc(nil) - peer.net.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification challenge does not match, disconnecting") + peer.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification challenge does not match, disconnecting") return OutgoingMessage{Action: Disconnect, reason: disconnectBadIdentityData} } if !msg.Verify(peer.identity) { networkPeerIdentityError.Inc(nil) - peer.net.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification is incorrectly signed, disconnecting") + peer.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification is incorrectly signed, disconnecting") return OutgoingMessage{Action: Disconnect, reason: disconnectBadIdentityData} } atomic.StoreUint32(&peer.identityVerified, 1) // if the identity could not be claimed by this peer, it means the identity is in use - peer.net.peersLock.Lock() - ok := peer.net.identityTracker.setIdentity(peer) - peer.net.peersLock.Unlock() + wn.peersLock.Lock() + ok := wn.identityTracker.setIdentity(peer) + wn.peersLock.Unlock() if !ok { networkPeerIdentityDisconnect.Inc(nil) - peer.net.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity already in use, disconnecting") + peer.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity already in use, disconnecting") return OutgoingMessage{Action: Disconnect, reason: disconnectDuplicateConnection} } return OutgoingMessage{} diff --git a/network/netidentity_test.go b/network/netidentity_test.go index f3c72e3e8c..13731aaaeb 100644 --- a/network/netidentity_test.go +++ b/network/netidentity_test.go @@ -356,11 +356,12 @@ func TestIdentityTrackerSetIdentity(t *testing.T) { } // Just tests that if a peer is already verified, it just returns OutgoingMessage{} -func TestHandlerGuard(t *testing.T) { +func TestIdentityTrackerHandlerGuard(t *testing.T) { partitiontest.PartitionTest(t) p := wsPeer{identityVerified: uint32(1)} msg := IncomingMessage{ Sender: &p, + Net: &WebsocketNetwork{}, } require.Equal(t, OutgoingMessage{}, identityVerificationHandler(msg)) } diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 56314f14b9..07902bde79 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -173,12 +173,9 @@ const ( type GossipNode interface { Address() (string, bool) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error - BroadcastArray(ctx context.Context, tag []protocol.Tag, data [][]byte, wait bool, except Peer) error Relay(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error - RelayArray(ctx context.Context, tag []protocol.Tag, data [][]byte, wait bool, except Peer) error Disconnect(badnode Peer) - DisconnectPeers() - Ready() chan struct{} + DisconnectPeers() // only used by testing // RegisterHTTPHandler path accepts gorilla/mux path annotations RegisterHTTPHandler(path string, handler http.Handler) @@ -226,12 +223,8 @@ type GossipNode interface { // SubstituteGenesisID substitutes the "{genesisID}" with their network-specific genesisID. SubstituteGenesisID(rawURL string) string - // GetPeerData returns a value stored by SetPeerData - GetPeerData(peer Peer, key string) interface{} - - // SetPeerData attaches a piece of data to a peer. - // Other services inside go-algorand may attach data to a peer that gets garbage collected when the peer is closed. - SetPeerData(peer Peer, key string, value interface{}) + // called from wsPeer to report that it has closed + peerRemoteClose(peer *wsPeer, reason disconnectReason) } // IncomingMessage represents a message arriving from some peer in our p2p network @@ -354,12 +347,8 @@ type WebsocketNetwork struct { log logging.Logger - readBuffer chan IncomingMessage - wg sync.WaitGroup - handlers Multiplexer - ctx context.Context ctxCancel context.CancelFunc @@ -367,8 +356,8 @@ type WebsocketNetwork struct { peers []*wsPeer peersChangeCounter int32 // peersChangeCounter is an atomic variable that increases on each change to the peers. It helps avoiding taking the peersLock when checking if the peers list was modified. - broadcastQueueHighPrio chan broadcastRequest - broadcastQueueBulk chan broadcastRequest + broadcaster msgBroadcaster + handler msgHandler phonebook Phonebook @@ -408,9 +397,6 @@ type WebsocketNetwork struct { // wsMaxHeaderBytes is the maximum accepted size of the header prior to upgrading to websocket connection. wsMaxHeaderBytes int64 - // slowWritingPeerMonitorInterval defines the interval between two consecutive tests for slow peer writing - slowWritingPeerMonitorInterval time.Duration - requestsTracker *RequestTracker requestsLogger *RequestLogger @@ -491,6 +477,43 @@ type broadcastRequest struct { ctx context.Context } +// msgBroadcaster contains the logic for preparing data for broadcast, managing broadcast priorities +// and queues. It provides a goroutine (broadcastThread) for reading from those queues and scheduling +// broadcasts to peers managed by networkPeerManager. +type msgBroadcaster struct { + ctx context.Context + log logging.Logger + config config.Local + broadcastQueueHighPrio chan broadcastRequest + broadcastQueueBulk chan broadcastRequest + // slowWritingPeerMonitorInterval defines the interval between two consecutive tests for slow peer writing + slowWritingPeerMonitorInterval time.Duration +} + +// msgHandler contains the logic for handling incoming messages and managing a readBuffer. It provides +// a goroutine (messageHandlerThread) for reading incoming messages and calling handlers. +type msgHandler struct { + ctx context.Context + log logging.Logger + config config.Local + readBuffer chan IncomingMessage + Multiplexer +} + +// networkPeerManager provides the network functionality needed by msgBroadcaster and msgHandler for managing +// peer connectivity, and also sending messages. +type networkPeerManager interface { + // used by msgBroadcaster + peerSnapshot(dest []*wsPeer) ([]*wsPeer, int32) + checkSlowWritingPeers() + getPeersChangeCounter() int32 + + // used by msgHandler + Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error + disconnectThread(badnode Peer, reason disconnectReason) + checkPeersConnectivity() +} + // Address returns a string and whether that is a 'final' address or guessed. // Part of GossipNode interface func (wn *WebsocketNetwork) Address() (string, bool) { @@ -528,18 +551,17 @@ func (wn *WebsocketNetwork) Broadcast(ctx context.Context, tag protocol.Tag, dat dataArray[0] = data tagArray := make([]protocol.Tag, 1, 1) tagArray[0] = tag - return wn.BroadcastArray(ctx, tagArray, dataArray, wait, except) + return wn.broadcaster.BroadcastArray(ctx, tagArray, dataArray, wait, except) } // BroadcastArray sends an array of messages. // If except is not nil then we will not send it to that neighboring Peer. // if wait is true then the call blocks until the packet has actually been sent to all neighbors. // TODO: add `priority` argument so that we don't have to guess it based on tag -func (wn *WebsocketNetwork) BroadcastArray(ctx context.Context, tags []protocol.Tag, data [][]byte, wait bool, except Peer) error { +func (wn *msgBroadcaster) BroadcastArray(ctx context.Context, tags []protocol.Tag, data [][]byte, wait bool, except Peer) error { if wn.config.DisableNetworking { return nil } - if len(tags) != len(data) { return errBcastInvalidArray } @@ -598,7 +620,7 @@ func (wn *WebsocketNetwork) Relay(ctx context.Context, tag protocol.Tag, data [] // RelayArray relays array of messages func (wn *WebsocketNetwork) RelayArray(ctx context.Context, tags []protocol.Tag, data [][]byte, wait bool, except Peer) error { if wn.relayMessages { - return wn.BroadcastArray(ctx, tags, data, wait, except) + return wn.broadcaster.BroadcastArray(ctx, tags, data, wait, except) } return nil } @@ -691,14 +713,14 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer { var addrs []string addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole) for _, addr := range addrs { - peerCore := makePeerCore(wn, addr, wn.GetRoundTripper(), "" /*origin address*/) + peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) outPeers = append(outPeers, &peerCore) } case PeersPhonebookArchivalNodes: var addrs []string addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole) for _, addr := range addrs { - peerCore := makePeerCore(wn, addr, wn.GetRoundTripper(), "" /*origin address*/) + peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) outPeers = append(outPeers, &peerCore) } case PeersPhonebookArchivers: @@ -706,7 +728,7 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer { var addrs []string addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchiverRole) for _, addr := range addrs { - peerCore := makePeerCore(wn, addr, wn.GetRoundTripper(), "" /*origin address*/) + peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) outPeers = append(outPeers, &peerCore) } case PeersConnectedIn: @@ -784,16 +806,21 @@ func (wn *WebsocketNetwork) setup() { wn.identityTracker = NewIdentityTracker() - wn.broadcastQueueHighPrio = make(chan broadcastRequest, wn.outgoingMessagesBufferSize) - wn.broadcastQueueBulk = make(chan broadcastRequest, 100) + wn.broadcaster = msgBroadcaster{ + ctx: wn.ctx, + log: wn.log, + config: wn.config, + broadcastQueueHighPrio: make(chan broadcastRequest, wn.outgoingMessagesBufferSize), + broadcastQueueBulk: make(chan broadcastRequest, 100), + } + if wn.broadcaster.slowWritingPeerMonitorInterval == 0 { + wn.broadcaster.slowWritingPeerMonitorInterval = slowWritingPeerMonitorInterval + } wn.meshUpdateRequests = make(chan meshRequest, 5) wn.readyChan = make(chan struct{}) wn.tryConnectAddrs = make(map[string]int64) wn.eventualReadyDelay = time.Minute wn.prioTracker = newPrioTracker(wn) - if wn.slowWritingPeerMonitorInterval == 0 { - wn.slowWritingPeerMonitorInterval = slowWritingPeerMonitorInterval - } readBufferLen := wn.config.IncomingConnectionsLimit + wn.config.GossipFanout if readBufferLen < 100 { @@ -802,7 +829,12 @@ func (wn *WebsocketNetwork) setup() { if readBufferLen > 10000 { readBufferLen = 10000 } - wn.readBuffer = make(chan IncomingMessage, readBufferLen) + wn.handler = msgHandler{ + ctx: wn.ctx, + log: wn.log, + config: wn.config, + readBuffer: make(chan IncomingMessage, readBufferLen), + } var rbytes [10]byte crypto.RandBytes(rbytes[:]) @@ -813,7 +845,6 @@ func (wn *WebsocketNetwork) setup() { } wn.connPerfMonitor = makeConnectionPerformanceMonitor([]Tag{protocol.AgreementVoteTag, protocol.TxnTag}) wn.lastNetworkAdvance = time.Now().UTC() - wn.handlers.log = wn.log // set our supported versions if wn.config.NetworkProtocolVersion != "" { @@ -904,10 +935,10 @@ func (wn *WebsocketNetwork) Start() { for i := 0; i < incomingThreads; i++ { wn.wg.Add(1) // We pass the peersConnectivityCheckTicker.C here so that we don't need to syncronize the access to the ticker's data structure. - go wn.messageHandlerThread(wn.peersConnectivityCheckTicker.C) + go wn.handler.messageHandlerThread(&wn.wg, wn.peersConnectivityCheckTicker.C, wn) } wn.wg.Add(1) - go wn.broadcastThread() + go wn.broadcaster.broadcastThread(&wn.wg, wn) if wn.prioScheme != nil { wn.wg.Add(1) go wn.prioWeightRefresh() @@ -950,7 +981,7 @@ func (wn *WebsocketNetwork) innerStop() { // Stop closes network connections and stops threads. // Stop blocks until all activity on this node is done. func (wn *WebsocketNetwork) Stop() { - wn.handlers.ClearHandlers([]Tag{}) + wn.handler.ClearHandlers([]Tag{}) // if we have a working ticker, just stop it and clear it out. The access to this variable is safe since the Start()/Stop() are synced by the // caller, and the WebsocketNetwork doesn't access wn.peersConnectivityCheckTicker directly. @@ -987,13 +1018,13 @@ func (wn *WebsocketNetwork) Stop() { // RegisterHandlers registers the set of given message handlers. func (wn *WebsocketNetwork) RegisterHandlers(dispatch []TaggedMessageHandler) { - wn.handlers.RegisterHandlers(dispatch) + wn.handler.RegisterHandlers(dispatch) } // ClearHandlers deregisters all the existing message handlers. func (wn *WebsocketNetwork) ClearHandlers() { // exclude the internal handlers. These would get cleared out when Stop is called. - wn.handlers.ClearHandlers([]Tag{protocol.PingTag, protocol.PingReplyTag, protocol.NetPrioResponseTag}) + wn.handler.ClearHandlers([]Tag{protocol.PingTag, protocol.PingReplyTag, protocol.NetPrioResponseTag}) } func (wn *WebsocketNetwork) setHeaders(header http.Header) { @@ -1233,8 +1264,8 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt } peer := &wsPeer{ - wsPeerCore: makePeerCore(wn, trackedRequest.otherPublicAddr, wn.GetRoundTripper(), trackedRequest.remoteHost), - conn: conn, + wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.otherPublicAddr, wn.GetRoundTripper(), trackedRequest.remoteHost), + conn: wsPeerWebsocketConnImpl{conn}, outgoing: false, InstanceName: trackedRequest.otherInstanceName, incomingMsgFilter: wn.incomingMsgFilter, @@ -1281,8 +1312,8 @@ func (wn *WebsocketNetwork) maybeSendMessagesOfInterest(peer *wsPeer, messagesOf } } -func (wn *WebsocketNetwork) messageHandlerThread(peersConnectivityCheckCh <-chan time.Time) { - defer wn.wg.Done() +func (wn *msgHandler) messageHandlerThread(wg *sync.WaitGroup, peersConnectivityCheckCh <-chan time.Time, net networkPeerManager) { + defer wg.Done() for { select { @@ -1298,13 +1329,13 @@ func (wn *WebsocketNetwork) messageHandlerThread(peersConnectivityCheckCh <-chan } } if wn.config.EnableOutgoingNetworkMessageFiltering && len(msg.Data) >= messageFilterSize { - wn.sendFilterMessage(msg) + wn.sendFilterMessage(msg, net) } //wn.log.Debugf("msg handling %#v [%d]byte", msg.Tag, len(msg.Data)) start := time.Now() // now, send to global handlers - outmsg := wn.handlers.Handle(msg) + outmsg := wn.Handle(msg) handled := time.Now() bufferNanos := start.UnixNano() - msg.Received networkIncomingBufferMicros.AddUint64(uint64(bufferNanos/1000), nil) @@ -1312,14 +1343,14 @@ func (wn *WebsocketNetwork) messageHandlerThread(peersConnectivityCheckCh <-chan networkHandleMicros.AddUint64(uint64(handleTime.Nanoseconds()/1000), nil) switch outmsg.Action { case Disconnect: - wn.wg.Add(1) + wg.Add(1) reason := disconnectBadData if outmsg.reason != disconnectReasonNone { reason = outmsg.reason } - go wn.disconnectThread(msg.Sender, reason) + go net.disconnectThread(msg.Sender, reason) case Broadcast: - err := wn.Broadcast(wn.ctx, msg.Tag, msg.Data, false, msg.Sender) + err := net.Broadcast(wn.ctx, msg.Tag, msg.Data, false, msg.Sender) if err != nil && err != errBcastQFull { wn.log.Warnf("WebsocketNetwork.messageHandlerThread: WebsocketNetwork.Broadcast returned unexpected error %v", err) } @@ -1332,7 +1363,7 @@ func (wn *WebsocketNetwork) messageHandlerThread(peersConnectivityCheckCh <-chan } case <-peersConnectivityCheckCh: // go over the peers and ensure we have some type of communication going on. - wn.checkPeersConnectivity() + net.checkPeersConnectivity() } } } @@ -1372,25 +1403,25 @@ func (wn *WebsocketNetwork) checkSlowWritingPeers() { } } -func (wn *WebsocketNetwork) sendFilterMessage(msg IncomingMessage) { +func (wn *msgHandler) sendFilterMessage(msg IncomingMessage, net networkPeerManager) { digest := generateMessageDigest(msg.Tag, msg.Data) //wn.log.Debugf("send filter %s(%d) %v", msg.Tag, len(msg.Data), digest) - err := wn.Broadcast(context.Background(), protocol.MsgDigestSkipTag, digest[:], false, msg.Sender) + err := net.Broadcast(context.Background(), protocol.MsgDigestSkipTag, digest[:], false, msg.Sender) if err != nil && err != errBcastQFull { wn.log.Warnf("WebsocketNetwork.sendFilterMessage: WebsocketNetwork.Broadcast returned unexpected error %v", err) } } -func (wn *WebsocketNetwork) broadcastThread() { - defer wn.wg.Done() +func (wn *msgBroadcaster) broadcastThread(wg *sync.WaitGroup, net networkPeerManager) { + defer wg.Done() slowWritingPeerCheckTicker := time.NewTicker(wn.slowWritingPeerMonitorInterval) defer slowWritingPeerCheckTicker.Stop() - peers, lastPeersChangeCounter := wn.peerSnapshot([]*wsPeer{}) + peers, lastPeersChangeCounter := net.peerSnapshot([]*wsPeer{}) // updatePeers update the peers list if their peer change counter has changed. updatePeers := func() { - if curPeersChangeCounter := atomic.LoadInt32(&wn.peersChangeCounter); curPeersChangeCounter != lastPeersChangeCounter { - peers, lastPeersChangeCounter = wn.peerSnapshot(peers) + if curPeersChangeCounter := net.getPeersChangeCounter(); curPeersChangeCounter != lastPeersChangeCounter { + peers, lastPeersChangeCounter = net.peerSnapshot(peers) } } @@ -1481,7 +1512,7 @@ func (wn *WebsocketNetwork) broadcastThread() { } wn.innerBroadcast(request, true, peers) case <-slowWritingPeerCheckTicker.C: - wn.checkSlowWritingPeers() + net.checkSlowWritingPeers() continue case request := <-wn.broadcastQueueBulk: // check if peers need to be updated, since we've been waiting a while. @@ -1516,13 +1547,16 @@ func (wn *WebsocketNetwork) peerSnapshot(dest []*wsPeer) ([]*wsPeer, int32) { dest = make([]*wsPeer, len(wn.peers)) } copy(dest, wn.peers) - peerChangeCounter := atomic.LoadInt32(&wn.peersChangeCounter) - return dest, peerChangeCounter + return dest, wn.getPeersChangeCounter() +} + +func (wn *WebsocketNetwork) getPeersChangeCounter() int32 { + return atomic.LoadInt32(&wn.peersChangeCounter) } // preparePeerData prepares batches of data for sending. // It performs optional zstd compression for proposal massages -func (wn *WebsocketNetwork) preparePeerData(request broadcastRequest, prio bool, peers []*wsPeer) ([][]byte, [][]byte, []crypto.Digest, bool) { +func (wn *msgBroadcaster) preparePeerData(request broadcastRequest, prio bool, peers []*wsPeer) ([][]byte, [][]byte, []crypto.Digest, bool) { // determine if there is a payload proposal and peers supporting compressed payloads wantCompression := false containsPrioPPTag := false @@ -1572,7 +1606,7 @@ func (wn *WebsocketNetwork) preparePeerData(request broadcastRequest, prio bool, } // prio is set if the broadcast is a high-priority broadcast. -func (wn *WebsocketNetwork) innerBroadcast(request broadcastRequest, prio bool, peers []*wsPeer) { +func (wn *msgBroadcaster) innerBroadcast(request broadcastRequest, prio bool, peers []*wsPeer) { if request.done != nil { defer close(request.done) } @@ -1946,7 +1980,7 @@ func (wn *WebsocketNetwork) getPeerConnectionTelemetryDetails(now time.Time, pee connDetail.TCP = *tcpInfo } if peer.outgoing { - connDetail.Address = justHost(peer.conn.RemoteAddr().String()) + connDetail.Address = justHost(peer.conn.RemoteAddrString()) connDetail.Endpoint = peer.GetAddress() connDetail.MessageDelay = peer.peerMessageDelay connectionDetails.OutgoingPeers = append(connectionDetails.OutgoingPeers, connDetail) @@ -2357,8 +2391,8 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { } peer := &wsPeer{ - wsPeerCore: makePeerCore(wn, addr, wn.GetRoundTripper(), "" /* origin */), - conn: conn, + wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /* origin */), + conn: wsPeerWebsocketConnImpl{conn}, outgoing: true, incomingMsgFilter: wn.incomingMsgFilter, createTime: time.Now(), @@ -2491,9 +2525,9 @@ func (wn *WebsocketNetwork) removePeer(peer *wsPeer, reason disconnectReason) { peerAddr := peer.OriginAddress() // we might be able to get addr out of conn, or it might be closed if peerAddr == "" && peer.conn != nil { - paddr := peer.conn.RemoteAddr() - if paddr != nil { - peerAddr = justHost(paddr.String()) + paddr := peer.conn.RemoteAddrString() + if paddr != "" { + peerAddr = justHost(paddr) } } if peerAddr == "" { @@ -2550,7 +2584,7 @@ func (wn *WebsocketNetwork) addPeer(peer *wsPeer) { // guard against peers which are closed or closing if atomic.LoadInt32(&peer.didSignalClose) == 1 { networkPeerAlreadyClosed.Inc(nil) - wn.log.Debugf("peer closing %s", peer.conn.RemoteAddr().String()) + wn.log.Debugf("peer closing %s", peer.conn.RemoteAddrString()) return } // simple duplicate *pointer* check. should never trigger given the callers to addPeer diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 05992ec8d6..ac59b40b29 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -543,7 +543,7 @@ func TestWebsocketNetworkArray(t *testing.T) { tags := []protocol.Tag{protocol.TxnTag, protocol.TxnTag, protocol.TxnTag} data := [][]byte{[]byte("foo"), []byte("bar"), []byte("algo")} - netA.BroadcastArray(context.Background(), tags, data, false, nil) + netA.broadcaster.BroadcastArray(context.Background(), tags, data, false, nil) select { case <-counterDone: @@ -571,7 +571,7 @@ func TestWebsocketNetworkCancel(t *testing.T) { cancel() // try calling BroadcastArray - netA.BroadcastArray(ctx, tags, data, true, nil) + netA.broadcaster.BroadcastArray(ctx, tags, data, true, nil) select { case <-counterDone: @@ -583,7 +583,7 @@ func TestWebsocketNetworkCancel(t *testing.T) { // try calling innerBroadcast request := broadcastRequest{tags: tags, data: data, enqueueTime: time.Now(), ctx: ctx} peers, _ := netA.peerSnapshot([]*wsPeer{}) - netA.innerBroadcast(request, true, peers) + netA.broadcaster.innerBroadcast(request, true, peers) select { case <-counterDone: @@ -760,9 +760,11 @@ func TestAddrToGossipAddr(t *testing.T) { type nopConn struct{} func (nc *nopConn) RemoteAddr() net.Addr { return nil } +func (nc *nopConn) RemoteAddrString() string { return "" } func (nc *nopConn) NextReader() (int, io.Reader, error) { return 0, nil, nil } func (nc *nopConn) WriteMessage(int, []byte) error { return nil } func (nc *nopConn) WriteControl(int, []byte, time.Time) error { return nil } +func (nc *nopConn) CloseWithMessage([]byte, time.Time) error { return nil } func (nc *nopConn) SetReadLimit(limit int64) {} func (nc *nopConn) CloseWithoutFlush() error { return nil } func (nc *nopConn) SetPingHandler(h func(appData string) error) {} @@ -799,7 +801,7 @@ func TestSlowHandlers(t *testing.T) { // start slow handler calls that will block all handler threads for i := 0; i < incomingThreads; i++ { data := []byte{byte(i)} - node.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: slowTag, Data: data, Net: node} + node.handler.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: slowTag, Data: data, Net: node} ipi++ } defer slowCounter.Broadcast() @@ -807,7 +809,7 @@ func TestSlowHandlers(t *testing.T) { // start fast handler calls that won't get to run for i := 0; i < incomingThreads; i++ { data := []byte{byte(i)} - node.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: fastTag, Data: data, Net: node} + node.handler.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: fastTag, Data: data, Net: node} ipi++ } ok := false @@ -890,7 +892,7 @@ func TestFloodingPeer(t *testing.T) { } select { - case node.readBuffer <- IncomingMessage{Sender: &injectionPeers[myIpi], Tag: slowTag, Data: data, Net: node, processing: processed}: + case node.handler.readBuffer <- IncomingMessage{Sender: &injectionPeers[myIpi], Tag: slowTag, Data: data, Net: node, processing: processed}: case <-ctx.Done(): return } @@ -912,7 +914,7 @@ func TestFloodingPeer(t *testing.T) { fastCounterDone := fastCounter.done for ipi < len(injectionPeers) { data := []byte{byte(ipi)} - node.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: fastTag, Data: data, Net: node} + node.handler.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: fastTag, Data: data, Net: node} numFast++ ipi++ } @@ -2176,7 +2178,7 @@ func BenchmarkWebsocketNetworkBasic(t *testing.B) { networkBroadcastsDropped.WriteMetric(&buf, "") t.Errorf( "a out queue=%d, metric: %s", - len(netA.broadcastQueueBulk), + len(netA.broadcaster.broadcastQueueBulk), buf.String(), ) return @@ -2450,9 +2452,9 @@ func (wn *WebsocketNetwork) broadcastWithTimestamp(tag protocol.Tag, data []byte tagArr[0] = tag request := broadcastRequest{tags: tagArr, data: msgArr, enqueueTime: when, ctx: context.Background()} - broadcastQueue := wn.broadcastQueueBulk + broadcastQueue := wn.broadcaster.broadcastQueueBulk if highPriorityTag(tagArr) { - broadcastQueue = wn.broadcastQueueHighPrio + broadcastQueue = wn.broadcaster.broadcastQueueHighPrio } // no wait select { @@ -2508,13 +2510,13 @@ func TestSlowPeerDisconnection(t *testing.T) { log := logging.TestingLog(t) log.SetLevel(logging.Info) wn := &WebsocketNetwork{ - log: log, - config: defaultConfig, - phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, - slowWritingPeerMonitorInterval: time.Millisecond * 50, + log: log, + config: defaultConfig, + phonebook: MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, } + wn.broadcaster.slowWritingPeerMonitorInterval = time.Millisecond * 50 wn.setup() wn.eventualReadyDelay = time.Second wn.messagesOfInterest = nil // clear this before starting the network so that we won't be sending a MOI upon connection. @@ -3705,7 +3707,7 @@ func TestPreparePeerData(t *testing.T) { peers := []*wsPeer{} wn := WebsocketNetwork{} - data, comp, digests, seenPrioPPTag := wn.preparePeerData(req, false, peers) + data, comp, digests, seenPrioPPTag := wn.broadcaster.preparePeerData(req, false, peers) require.NotEmpty(t, data) require.Empty(t, comp) require.NotEmpty(t, digests) @@ -3725,7 +3727,7 @@ func TestPreparePeerData(t *testing.T) { features: pfCompressedProposal, } peers = []*wsPeer{&peer1, &peer2} - data, comp, digests, seenPrioPPTag = wn.preparePeerData(req, true, peers) + data, comp, digests, seenPrioPPTag = wn.broadcaster.preparePeerData(req, true, peers) require.NotEmpty(t, data) require.NotEmpty(t, comp) require.NotEmpty(t, digests) diff --git a/network/wsPeer.go b/network/wsPeer.go index 1c9aad3db9..a717eef60c 100644 --- a/network/wsPeer.go +++ b/network/wsPeer.go @@ -36,6 +36,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/metrics" @@ -119,17 +120,31 @@ var defaultSendMessageTags = map[protocol.Tag]bool{ // interface allows substituting debug implementation for *websocket.Conn type wsPeerWebsocketConn interface { - RemoteAddr() net.Addr + RemoteAddrString() string NextReader() (int, io.Reader, error) WriteMessage(int, []byte) error - WriteControl(int, []byte, time.Time) error + CloseWithMessage([]byte, time.Time) error SetReadLimit(int64) CloseWithoutFlush() error - SetPingHandler(h func(appData string) error) - SetPongHandler(h func(appData string) error) wrappedConn } +type wsPeerWebsocketConnImpl struct { + *websocket.Conn +} + +func (c wsPeerWebsocketConnImpl) RemoteAddrString() string { + addr := c.RemoteAddr() + if addr == nil { + return "" + } + return addr.String() +} + +func (c wsPeerWebsocketConnImpl) CloseWithMessage(msg []byte, deadline time.Time) error { + return c.WriteControl(websocket.CloseMessage, msg, deadline) +} + type wrappedConn interface { UnderlyingConn() net.Conn } @@ -145,7 +160,10 @@ type sendMessage struct { // wsPeerCore also works for non-connected peers we want to do HTTP GET from type wsPeerCore struct { - net *WebsocketNetwork + net GossipNode + netCtx context.Context + log logging.Logger + readBuffer chan<- IncomingMessage rootURL string originAddress string // incoming connection remote host client http.Client @@ -325,9 +343,12 @@ type TCPInfoUnicastPeer interface { } // Create a wsPeerCore object -func makePeerCore(net *WebsocketNetwork, rootURL string, roundTripper http.RoundTripper, originAddress string) wsPeerCore { +func makePeerCore(ctx context.Context, net GossipNode, log logging.Logger, readBuffer chan<- IncomingMessage, rootURL string, roundTripper http.RoundTripper, originAddress string) wsPeerCore { return wsPeerCore{ net: net, + netCtx: ctx, + log: log, + readBuffer: readBuffer, rootURL: rootURL, originAddress: originAddress, client: http.Client{Transport: roundTripper}, @@ -419,7 +440,7 @@ func (wp *wsPeer) Respond(ctx context.Context, reqMsg IncomingMessage, outMsg Ou if outMsg.OnRelease != nil { outMsg.OnRelease() } - wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String()) + wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString()) return case <-ctx.Done(): if outMsg.OnRelease != nil { @@ -432,7 +453,7 @@ func (wp *wsPeer) Respond(ctx context.Context, reqMsg IncomingMessage, outMsg Ou // setup values not trivially assigned func (wp *wsPeer) init(config config.Local, sendBufferLength int) { - wp.net.log.Debugf("wsPeer init outgoing=%v %#v", wp.outgoing, wp.rootURL) + wp.log.Debugf("wsPeer init outgoing=%v %#v", wp.outgoing, wp.rootURL) wp.closing = make(chan struct{}) wp.sendBufferHighPrio = make(chan sendMessages, sendBufferLength) wp.sendBufferBulk = make(chan sendMessages, sendBufferLength) @@ -468,7 +489,7 @@ func (wp *wsPeer) reportReadErr(err error) { // only report error if we haven't already closed the peer if atomic.LoadInt32(&wp.didInnerClose) == 0 { _, _, line, _ := runtime.Caller(1) - wp.net.log.Warnf("peer[%s] line=%d read err: %s", wp.conn.RemoteAddr().String(), line, err) + wp.log.Warnf("peer[%s] line=%d read err: %s", wp.conn.RemoteAddrString(), line, err) networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "reader err"}) } } @@ -506,7 +527,7 @@ func (wp *wsPeer) readLoop() { return } if mtype != websocket.BinaryMessage { - wp.net.log.Errorf("peer sent non websocket-binary message: %#v", mtype) + wp.log.Errorf("peer sent non websocket-binary message: %#v", mtype) networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "protocol"}) return } @@ -524,7 +545,7 @@ func (wp *wsPeer) readLoop() { // This peers has sent us more responses than we have requested. This is a protocol violation and we should disconnect. if atomic.LoadInt64(&wp.outstandingTopicRequests) < 0 { - wp.net.log.Errorf("wsPeer readloop: peer %s sent TS response without a request", wp.conn.RemoteAddr().String()) + wp.log.Errorf("wsPeer readloop: peer %s sent TS response without a request", wp.conn.RemoteAddrString()) networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "unrequestedTS"}) cleanupCloseError = disconnectUnexpectedTopicResp return @@ -533,11 +554,11 @@ func (wp *wsPeer) readLoop() { // Peer sent us a response to a request we made but we've already timed out -- discard n, err = io.Copy(io.Discard, reader) if err != nil { - wp.net.log.Infof("wsPeer readloop: could not discard timed-out TS message from %s : %s", wp.conn.RemoteAddr().String(), err) + wp.log.Infof("wsPeer readloop: could not discard timed-out TS message from %s : %s", wp.conn.RemoteAddrString(), err) wp.reportReadErr(err) return } - wp.net.log.Warnf("wsPeer readLoop: received a TS response for a stale request from %s. %d bytes discarded", wp.conn.RemoteAddr().String(), n) + wp.log.Warnf("wsPeer readLoop: received a TS response for a stale request from %s. %d bytes discarded", wp.conn.RemoteAddrString(), n) continue } @@ -586,18 +607,18 @@ func (wp *wsPeer) readLoop() { atomic.AddInt64(&wp.outstandingTopicRequests, -1) topics, err := UnmarshallTopics(msg.Data) if err != nil { - wp.net.log.Warnf("wsPeer readLoop: could not read the message from: %s %s", wp.conn.RemoteAddr().String(), err) + wp.log.Warnf("wsPeer readLoop: could not read the message from: %s %s", wp.conn.RemoteAddrString(), err) continue } requestHash, found := topics.GetValue(requestHashKey) if !found { - wp.net.log.Warnf("wsPeer readLoop: message from %s is missing the %s", wp.conn.RemoteAddr().String(), requestHashKey) + wp.log.Warnf("wsPeer readLoop: message from %s is missing the %s", wp.conn.RemoteAddrString(), requestHashKey) continue } hashKey, _ := binary.Uvarint(requestHash) channel, found := wp.getAndRemoveResponseChannel(hashKey) if !found { - wp.net.log.Warnf("wsPeer readLoop: received a message response from %s for a stale request", wp.conn.RemoteAddr().String()) + wp.log.Warnf("wsPeer readLoop: received a message response from %s for a stale request", wp.conn.RemoteAddrString()) continue } @@ -605,7 +626,7 @@ func (wp *wsPeer) readLoop() { case channel <- &Response{Topics: topics}: // do nothing. writing was successful. default: - wp.net.log.Warn("wsPeer readLoop: channel blocked. Could not pass the response to the requester", wp.conn.RemoteAddr().String()) + wp.log.Warn("wsPeer readLoop: channel blocked. Could not pass the response to the requester", wp.conn.RemoteAddrString()) } continue case protocol.MsgDigestSkipTag: @@ -629,28 +650,28 @@ func (wp *wsPeer) readLoop() { } if len(msg.Data) > 0 && wp.incomingMsgFilter != nil && dedupSafeTag(msg.Tag) { if wp.incomingMsgFilter.CheckIncomingMessage(msg.Tag, msg.Data, true, true) { - //wp.net.log.Debugf("dropped incoming duplicate %s(%d)", msg.Tag, len(msg.Data)) + //wp.log.Debugf("dropped incoming duplicate %s(%d)", msg.Tag, len(msg.Data)) duplicateNetworkMessageReceivedTotal.Inc(nil) duplicateNetworkMessageReceivedBytesTotal.AddUint64(uint64(len(msg.Data)+len(msg.Tag)), nil) // drop message, skip adding it to queue continue } } - //wp.net.log.Debugf("got msg %d bytes from %s", len(msg.Data), wp.conn.RemoteAddr().String()) + //wp.log.Debugf("got msg %d bytes from %s", len(msg.Data), wp.conn.RemoteAddrString()) // Wait for a previous message from this peer to be processed, // to achieve fairness in wp.net.readBuffer. select { case <-wp.processed: case <-wp.closing: - wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String()) + wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString()) return } select { - case wp.net.readBuffer <- msg: + case wp.readBuffer <- msg: case <-wp.closing: - wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String()) + wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString()) return } } @@ -662,7 +683,7 @@ func (wp *wsPeer) handleMessageOfInterest(msg IncomingMessage) (close bool, reas // decode the message, and ensure it's a valid message. msgTagsMap, err := unmarshallMessageOfInterest(msg.Data) if err != nil { - wp.net.log.Warnf("wsPeer handleMessageOfInterest: could not unmarshall message from: %s %v", wp.conn.RemoteAddr().String(), err) + wp.log.Warnf("wsPeer handleMessageOfInterest: could not unmarshall message from: %s %v", wp.conn.RemoteAddrString(), err) return true, disconnectBadData } msgs := make([]sendMessage, 1, 1) @@ -681,7 +702,7 @@ func (wp *wsPeer) handleMessageOfInterest(msg IncomingMessage) (close bool, reas case wp.sendBufferHighPrio <- sm: return case <-wp.closing: - wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String()) + wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString()) return true, disconnectReasonNone default: } @@ -690,7 +711,7 @@ func (wp *wsPeer) handleMessageOfInterest(msg IncomingMessage) (close bool, reas case wp.sendBufferHighPrio <- sm: case wp.sendBufferBulk <- sm: case <-wp.closing: - wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String()) + wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString()) return true, disconnectReasonNone } return @@ -707,12 +728,12 @@ func (wp *wsPeer) handleFilterMessage(msg IncomingMessage) { return } if len(msg.Data) != crypto.DigestSize { - wp.net.log.Warnf("bad filter message size %d", len(msg.Data)) + wp.log.Warnf("bad filter message size %d", len(msg.Data)) return } var digest crypto.Digest copy(digest[:], msg.Data) - //wp.net.log.Debugf("add filter %v", digest) + //wp.log.Debugf("add filter %v", digest) has := wp.outgoingMsgFilter.CheckDigest(digest, true, true) if has { // Count that this peer has sent us duplicate filter messages: this means it received the same @@ -745,7 +766,7 @@ func (wp *wsPeer) writeLoopSend(msgs sendMessages) disconnectReason { func (wp *wsPeer) writeLoopSendMsg(msg sendMessage) disconnectReason { if len(msg.data) > MaxMessageLength { - wp.net.log.Errorf("trying to send a message longer than we would receive: %d > %d tag=%s", len(msg.data), MaxMessageLength, string(msg.data[0:2])) + wp.log.Errorf("trying to send a message longer than we would receive: %d > %d tag=%s", len(msg.data), MaxMessageLength, string(msg.data[0:2])) // just drop it, don't break the connection return disconnectReasonNone } @@ -766,7 +787,7 @@ func (wp *wsPeer) writeLoopSendMsg(msg sendMessage) disconnectReason { now := time.Now() msgWaitDuration := now.Sub(msg.enqueued) if msgWaitDuration > maxMessageQueueDuration { - wp.net.log.Warnf("peer stale enqueued message %dms", msgWaitDuration.Nanoseconds()/1000000) + wp.log.Warnf("peer stale enqueued message %dms", msgWaitDuration.Nanoseconds()/1000000) networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "stale message"}) return disconnectStaleWrite } @@ -776,7 +797,7 @@ func (wp *wsPeer) writeLoopSendMsg(msg sendMessage) disconnectReason { err := wp.conn.WriteMessage(websocket.BinaryMessage, msg.data) if err != nil { if atomic.LoadInt32(&wp.didInnerClose) == 0 { - wp.net.log.Warn("peer write error ", err) + wp.log.Warn("peer write error ", err) networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "write err"}) } return disconnectWriteError @@ -842,7 +863,7 @@ func (wp *wsPeer) writeNonBlockMsgs(ctx context.Context, data [][]byte, highPrio includeIndices := make([]int, 0, len(data)) for i := range data { if wp.outgoingMsgFilter != nil && len(data[i]) > messageFilterSize && wp.outgoingMsgFilter.CheckDigest(digest[i], false, false) { - //wp.net.log.Debugf("msg drop as outbound dup %s(%d) %v", string(data[:2]), len(data)-2, digest) + //wp.log.Debugf("msg drop as outbound dup %s(%d) %v", string(data[:2]), len(data)-2, digest) // peer has notified us it doesn't need this message outgoingNetworkMessageFilteredOutTotal.Inc(nil) outgoingNetworkMessageFilteredOutBytesTotal.AddUint64(uint64(len(data)), nil) @@ -926,13 +947,13 @@ func (wp *wsPeer) Close(deadline time.Time) { atomic.StoreInt32(&wp.didSignalClose, 1) if atomic.CompareAndSwapInt32(&wp.didInnerClose, 0, 1) { close(wp.closing) - err := wp.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), deadline) + err := wp.conn.CloseWithMessage(websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), deadline) if err != nil { - wp.net.log.Infof("failed to write CloseMessage to connection for %s", wp.conn.RemoteAddr().String()) + wp.log.Infof("failed to write CloseMessage to connection for %s", wp.conn.RemoteAddrString()) } err = wp.conn.CloseWithoutFlush() if err != nil { - wp.net.log.Infof("failed to CloseWithoutFlush to connection for %s", wp.conn.RemoteAddr().String()) + wp.log.Infof("failed to CloseWithoutFlush to connection for %s", wp.conn.RemoteAddrString()) } } @@ -1019,7 +1040,7 @@ func (wp *wsPeer) Request(ctx context.Context, tag Tag, topics Topics) (resp *Re case wp.sendBufferBulk <- sendMessages{msgs: msg}: atomic.AddInt64(&wp.outstandingTopicRequests, 1) case <-wp.closing: - e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddr().String()) + e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddrString()) return case <-ctx.Done(): return resp, ctx.Err() @@ -1030,7 +1051,7 @@ func (wp *wsPeer) Request(ctx context.Context, tag Tag, topics Topics) (resp *Re case resp = <-responseChannel: return resp, nil case <-wp.closing: - e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddr().String()) + e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddrString()) return case <-ctx.Done(): return resp, ctx.Err() @@ -1077,9 +1098,9 @@ func (wp *wsPeer) setPeerData(key string, value interface{}) { } func (wp *wsPeer) sendMessagesOfInterest(messagesOfInterestGeneration uint32, messagesOfInterestEnc []byte) { - err := wp.Unicast(wp.net.ctx, messagesOfInterestEnc, protocol.MsgOfInterestTag) + err := wp.Unicast(wp.netCtx, messagesOfInterestEnc, protocol.MsgOfInterestTag) if err != nil { - wp.net.log.Errorf("ws send msgOfInterest: %v", err) + wp.log.Errorf("ws send msgOfInterest: %v", err) } else { atomic.StoreUint32(&wp.messagesOfInterestGeneration, messagesOfInterestGeneration) } From 259eb32cdd5d1686ca76e1ae7c92627bf47e2927 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 7 Aug 2023 11:57:10 -0400 Subject: [PATCH 25/66] tools: add mutex profile to heapWatch (#5638) --- test/heapwatch/heapWatch.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/heapwatch/heapWatch.py b/test/heapwatch/heapWatch.py index 22a5a0e3a3..68af9cd3b8 100644 --- a/test/heapwatch/heapWatch.py +++ b/test/heapwatch/heapWatch.py @@ -165,6 +165,12 @@ def get_heap_snapshot(self, snapshot_name=None, outdir=None): def get_goroutine_snapshot(self, snapshot_name=None, outdir=None): return self.get_pprof_snapshot('goroutine', snapshot_name, outdir) + def get_mutex_snapshot(self, snapshot_name=None, outdir=None): + return self.get_pprof_snapshot('mutex', snapshot_name, outdir) + + def get_block_snapshot(self, snapshot_name=None, outdir=None): + return self.get_pprof_snapshot('block', snapshot_name, outdir) + def get_cpu_profile(self, snapshot_name=None, outdir=None, seconds=90): seconds = int(seconds) return self.get_pprof_snapshot('profile?seconds={}'.format(seconds), snapshot_name, outdir, timeout=seconds+20) @@ -352,6 +358,12 @@ def do_snap(self, now, get_cpu=False, fraction=False): if self.args.goroutine: for ad in self.they: ad.get_goroutine_snapshot(snapshot_name, outdir=self.args.out) + if self.args.mutex: + for ad in self.they: + ad.get_mutex_snapshot(snapshot_name, outdir=self.args.out) + if self.args.block: + for ad in self.they: + ad.get_block_snapshot(snapshot_name, outdir=self.args.out) if self.args.metrics: threads = [] for ad in self.they: @@ -427,7 +439,9 @@ def main(): ap = argparse.ArgumentParser() ap.add_argument('data_dirs', nargs='*', help='list paths to algorand datadirs to grab heap profile from') ap.add_argument('--no-heap', dest='heaps', default=True, action='store_false', help='disable heap snapshot capture') + ap.add_argument('--block', default=False, action='store_true', help='also capture goroutines block profile') ap.add_argument('--goroutine', default=False, action='store_true', help='also capture goroutine profile') + ap.add_argument('--mutex', default=False, action='store_true', help='also capture mutex profile') ap.add_argument('--metrics', default=False, action='store_true', help='also capture /metrics counts') ap.add_argument('--blockinfo', default=False, action='store_true', help='also capture block header info') ap.add_argument('--period', default=None, help='seconds between automatically capturing') @@ -452,6 +466,12 @@ def main(): else: logging.basicConfig(level=logging.INFO) + if args.block: + print('Ensure algod is compiled with `runtime.SetBlockProfileRate()` set') + + if args.mutex: + print('Ensure algod is compiled with `runtime.SetMutexProfileFraction()` set') + for nre in args.tf_name_re: try: # do re.compile just to check From 5008a8c4147cea40d4014365eafb90d6012ca501 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:46:35 -0400 Subject: [PATCH 26/66] metrics: add counters for ledger locks trackerMu and accountsMu (#5635) --- ledger/acctupdates.go | 11 +++++++++++ ledger/ledger.go | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 0408034ae2..e4895ad715 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -327,6 +327,8 @@ func (au *accountUpdates) close() { // flushCaches flushes any pending data in caches so that it is fully available during future lookups. func (au *accountUpdates) flushCaches() { + t0 := time.Now() + ledgerAccountsMuLockCount.Inc(nil) au.accountsMu.Lock() au.baseAccounts.flushPendingWrites() @@ -334,6 +336,7 @@ func (au *accountUpdates) flushCaches() { au.baseKVs.flushPendingWrites() au.accountsMu.Unlock() + ledgerAccountsMuLockMicros.AddMicrosecondsSince(t0, nil) } func (au *accountUpdates) LookupResource(rnd basics.Round, addr basics.Address, aidx basics.CreatableIndex, ctype basics.CreatableType) (ledgercore.AccountResource, basics.Round, error) { @@ -675,9 +678,12 @@ func (au *accountUpdates) consecutiveVersion(offset uint64) uint64 { // newBlock is the accountUpdates implementation of the ledgerTracker interface. This is the "external" facing function // which invokes the internal implementation after taking the lock. func (au *accountUpdates) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) { + t0 := time.Now() + ledgerAccountsMuLockCount.Inc(nil) au.accountsMu.Lock() au.newBlockImpl(blk, delta) au.accountsMu.Unlock() + ledgerAccountsMuLockMicros.AddMicrosecondsSince(t0, nil) au.accountsReadCond.Broadcast() } @@ -1629,6 +1635,8 @@ func (au *accountUpdates) postCommit(ctx context.Context, dcc *deferredCommitCon dcc.stats.MemoryUpdatesDuration = time.Duration(time.Now().UnixNano()) } + t0 := time.Now() + ledgerAccountsMuLockCount.Inc(nil) au.accountsMu.Lock() // Drop reference counts to modified accounts, and evict them // from in-memory cache when no references remain. @@ -1735,6 +1743,7 @@ func (au *accountUpdates) postCommit(ctx context.Context, dcc *deferredCommitCon au.cachedDBRound = newBase au.accountsMu.Unlock() + ledgerAccountsMuLockMicros.AddMicrosecondsSince(t0, nil) if dcc.updateStats { dcc.stats.MemoryUpdatesDuration = time.Duration(time.Now().UnixNano()) - dcc.stats.MemoryUpdatesDuration @@ -1889,3 +1898,5 @@ var ledgerGeneratecatchpointCount = metrics.NewCounter("ledger_generatecatchpoin var ledgerGeneratecatchpointMicros = metrics.NewCounter("ledger_generatecatchpoint_micros", "µs spent") var ledgerVacuumCount = metrics.NewCounter("ledger_vacuum_count", "calls") var ledgerVacuumMicros = metrics.NewCounter("ledger_vacuum_micros", "µs spent") +var ledgerAccountsMuLockCount = metrics.NewCounter("ledger_lock_accountsmu_count", "calls") +var ledgerAccountsMuLockMicros = metrics.NewCounter("ledger_lock_accountsmu_micros", "µs spent") diff --git a/ledger/ledger.go b/ledger/ledger.go index 6c02951a92..1b49ea5ba2 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -432,8 +432,13 @@ func (l *Ledger) UnregisterVotersCommitListener() { // written to disk. Returns the minimum block number that must be kept // in the database. func (l *Ledger) notifyCommit(r basics.Round) basics.Round { + t0 := time.Now() l.trackerMu.Lock() - defer l.trackerMu.Unlock() + ledgerTrackerMuLockCount.Inc(nil) + defer func() { + l.trackerMu.Unlock() + ledgerTrackerMuLockMicros.AddMicrosecondsSince(t0, nil) + }() minToSave := l.trackers.committedUpTo(r) if l.archival { @@ -704,8 +709,13 @@ func (l *Ledger) AddBlock(blk bookkeeping.Block, cert agreement.Certificate) err // behaves like AddBlock. func (l *Ledger) AddValidatedBlock(vb ledgercore.ValidatedBlock, cert agreement.Certificate) error { // Grab the tracker lock first, to ensure newBlock() is notified before committedUpTo(). + t0 := time.Now() l.trackerMu.Lock() - defer l.trackerMu.Unlock() + ledgerTrackerMuLockCount.Inc(nil) + defer func() { + l.trackerMu.Unlock() + ledgerTrackerMuLockMicros.AddMicrosecondsSince(t0, nil) + }() blk := vb.Block() err := l.blockQ.putBlock(blk, cert) @@ -866,3 +876,5 @@ var ledgerInitblocksdbCount = metrics.NewCounter("ledger_initblocksdb_count", "c var ledgerInitblocksdbMicros = metrics.NewCounter("ledger_initblocksdb_micros", "µs spent") var ledgerVerifygenhashCount = metrics.NewCounter("ledger_verifygenhash_count", "calls") var ledgerVerifygenhashMicros = metrics.NewCounter("ledger_verifygenhash_micros", "µs spent") +var ledgerTrackerMuLockCount = metrics.NewCounter("ledger_lock_trackermu_count", "calls") +var ledgerTrackerMuLockMicros = metrics.NewCounter("ledger_lock_trackermu_micros", "µs spent") From 95e3504e61499a1046ffbbd4c1c7615aca9c6366 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Mon, 7 Aug 2023 16:52:38 -0400 Subject: [PATCH 27/66] tools: use CommandContext to cancel conduit process. (#5636) --- tools/block-generator/runner/run.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 31c83dc91b..3b22315292 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -111,9 +111,9 @@ func Run(args Args) error { } runnerArgs := args runnerArgs.Path = path - fmt.Println("----------------------------------------") + fmt.Printf("%s----------------------------------------------------------------------\n", pad) fmt.Printf("%sRunning test for configuration: %s\n", pad, info.Name()) - fmt.Println("----------------------------------------") + fmt.Printf("%s----------------------------------------------------------------------\n", pad) return runnerArgs.run(reportDirectory) }) if err != nil { @@ -151,6 +151,7 @@ func (r *Args) run(reportDirectory string) error { var nextRound uint64 var err error if r.ResetDB { + fmt.Printf("%sPostgreSQL resetting.\n", pad) if err = util.EmptyDB(r.PostgresConnectionString); err != nil { return fmt.Errorf("emptyDB err: %w", err) } @@ -174,6 +175,7 @@ func (r *Args) run(reportDirectory string) error { if err := generatorShutdownFunc(); err != nil { fmt.Printf("failed to shutdown generator: %s\n", err) } + fmt.Printf("%sGenerator shutdown complete\n", pad) }() // create conduit config from template @@ -210,6 +212,7 @@ func (r *Args) run(reportDirectory string) error { if sdErr := conduitShutdownFunc(); sdErr != nil { fmt.Printf("failed to shutdown Conduit: %s\n", sdErr) } + fmt.Printf("%sConduit shutdown complete\n", pad) }() // Create the report file @@ -487,11 +490,14 @@ func startGenerator(ledgerLogFile, configFile string, dbround uint64, genesisFil // startConduit starts the conduit binary. func startConduit(dataDir string, conduitBinary string, round uint64) (func() error, error) { fmt.Printf("%sConduit starting with data directory: %s\n", pad, dataDir) - cmd := exec.Command( + ctx, cf := context.WithCancel(context.Background()) + cmd := exec.CommandContext( + ctx, conduitBinary, "-r", strconv.FormatUint(round, 10), "-d", dataDir, ) + cmd.WaitDelay = 5 * time.Second var stdout bytes.Buffer cmd.Stdout = &stdout @@ -503,12 +509,7 @@ func startConduit(dataDir string, conduitBinary string, round uint64) (func() er // conduit doesn't have health check endpoint. so, no health check for now return func() error { - if err := cmd.Process.Signal(os.Interrupt); err != nil { - fmt.Printf("failed to interrupt conduit process: %s\n", err) - if err := cmd.Process.Kill(); err != nil { - return fmt.Errorf("failed to kill conduit process: %w", err) - } - } + cf() if err := cmd.Wait(); err != nil { fmt.Printf("%sConduit exiting: %s\n", pad, err) } From 865e0d5103eff27561a42af0b9d414b9a0829acf Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Tue, 8 Aug 2023 09:29:46 -0700 Subject: [PATCH 28/66] phonebook: Persist initial phonebook peers; remove unused ExtendPeerList (#5615) --- network/phonebook.go | 45 ++++++++------ network/phonebook_test.go | 125 +++++++++----------------------------- network/wsNetwork.go | 2 +- network/wsNetwork_test.go | 34 +++++++++++ 4 files changed, 91 insertions(+), 115 deletions(-) diff --git a/network/phonebook.go b/network/phonebook.go index ad07a2b5f5..0ad7be1a0c 100644 --- a/network/phonebook.go +++ b/network/phonebook.go @@ -68,8 +68,9 @@ type Phonebook interface { // matching entries don't change ReplacePeerList(dnsAddresses []string, networkName string, role PhoneBookEntryRoles) - // ExtendPeerList adds unique addresses to this set of addresses - ExtendPeerList(more []string, networkName string, role PhoneBookEntryRoles) + // AddPersistentPeers stores addresses of peers which are persistent. + // i.e. they won't be replaced by ReplacePeerList calls + AddPersistentPeers(dnsAddresses []string, networkName string, role PhoneBookEntryRoles) } // addressData: holds the information associated with each phonebook address. @@ -86,14 +87,18 @@ type addressData struct { // role is the role that this address serves. role PhoneBookEntryRoles + + // persistent is set true for peers whose record should not be removed for the peer list + persistent bool } // makePhonebookEntryData creates a new addressData entry for provided network name and role. -func makePhonebookEntryData(networkName string, role PhoneBookEntryRoles) addressData { +func makePhonebookEntryData(networkName string, role PhoneBookEntryRoles, persistent bool) addressData { pbData := addressData{ networkNames: make(map[string]bool), recentConnectionTimes: make([]time.Time, 0), role: role, + persistent: persistent, } pbData.networkNames[networkName] = true return pbData @@ -165,7 +170,7 @@ func (e *phonebookImpl) ReplacePeerList(addressesThey []string, networkName stri // prepare a map of items we'd like to remove. removeItems := make(map[string]bool, 0) for k, pbd := range e.data { - if pbd.networkNames[networkName] && pbd.role == role { + if pbd.networkNames[networkName] && pbd.role == role && !pbd.persistent { removeItems[k] = true } } @@ -180,7 +185,7 @@ func (e *phonebookImpl) ReplacePeerList(addressesThey []string, networkName stri delete(removeItems, addr) } else { // we don't have this item. add it. - e.data[addr] = makePhonebookEntryData(networkName, role) + e.data[addr] = makePhonebookEntryData(networkName, role, false) } } @@ -190,6 +195,23 @@ func (e *phonebookImpl) ReplacePeerList(addressesThey []string, networkName stri } } +func (e *phonebookImpl) AddPersistentPeers(dnsAddresses []string, networkName string, role PhoneBookEntryRoles) { + e.lock.Lock() + defer e.lock.Unlock() + + for _, addr := range dnsAddresses { + if pbData, has := e.data[addr]; has { + // we already have this. + // Make sure the persistence field is set to true + pbData.persistent = true + + } else { + // we don't have this item. add it. + e.data[addr] = makePhonebookEntryData(networkName, role, true) + } + } +} + func (e *phonebookImpl) UpdateRetryAfter(addr string, retryAfter time.Time) { e.lock.Lock() defer e.lock.Unlock() @@ -316,19 +338,6 @@ func (e *phonebookImpl) GetAddresses(n int, role PhoneBookEntryRoles) []string { return shuffleSelect(e.filterRetryTime(time.Now(), role), n) } -// ExtendPeerList adds unique addresses to this set of addresses -func (e *phonebookImpl) ExtendPeerList(more []string, networkName string, role PhoneBookEntryRoles) { - e.lock.Lock() - defer e.lock.Unlock() - for _, addr := range more { - if pbEntry, has := e.data[addr]; has { - pbEntry.networkNames[networkName] = true - continue - } - e.data[addr] = makePhonebookEntryData(networkName, role) - } -} - // Length returns the number of addrs contained func (e *phonebookImpl) Length() int { e.lock.RLock() diff --git a/network/phonebook_test.go b/network/phonebook_test.go index 64f0d7c03a..4fdad53c42 100644 --- a/network/phonebook_test.go +++ b/network/phonebook_test.go @@ -17,14 +17,10 @@ package network import ( - "fmt" - "math/rand" - "sync" "testing" "time" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -92,7 +88,7 @@ func TestArrayPhonebookAll(t *testing.T) { set := []string{"a", "b", "c", "d", "e"} ph := MakePhonebook(1, 1).(*phonebookImpl) for _, e := range set { - ph.data[e] = makePhonebookEntryData("", PhoneBookEntryRelayRole) + ph.data[e] = makePhonebookEntryData("", PhoneBookEntryRelayRole, false) } testPhonebookAll(t, set, ph) } @@ -103,7 +99,7 @@ func TestArrayPhonebookUniform1(t *testing.T) { set := []string{"a", "b", "c", "d", "e"} ph := MakePhonebook(1, 1).(*phonebookImpl) for _, e := range set { - ph.data[e] = makePhonebookEntryData("", PhoneBookEntryRelayRole) + ph.data[e] = makePhonebookEntryData("", PhoneBookEntryRelayRole, false) } testPhonebookUniform(t, set, ph, 1) } @@ -114,91 +110,39 @@ func TestArrayPhonebookUniform3(t *testing.T) { set := []string{"a", "b", "c", "d", "e"} ph := MakePhonebook(1, 1).(*phonebookImpl) for _, e := range set { - ph.data[e] = makePhonebookEntryData("", PhoneBookEntryRelayRole) + ph.data[e] = makePhonebookEntryData("", PhoneBookEntryRelayRole, false) } testPhonebookUniform(t, set, ph, 3) } -// TestPhonebookExtension tests for extending different phonebooks with -// addresses. -func TestPhonebookExtension(t *testing.T) { - partitiontest.PartitionTest(t) - - setA := []string{"a"} - moreB := []string{"b"} - ph := MakePhonebook(1, 1).(*phonebookImpl) - ph.ReplacePeerList(setA, "default", PhoneBookEntryRelayRole) - ph.ExtendPeerList(moreB, "default", PhoneBookEntryRelayRole) - ph.ExtendPeerList(setA, "other", PhoneBookEntryRelayRole) - assert.Equal(t, 2, ph.Length()) - assert.Equal(t, true, ph.data["a"].networkNames["default"]) - assert.Equal(t, true, ph.data["a"].networkNames["other"]) - assert.Equal(t, true, ph.data["b"].networkNames["default"]) - assert.Equal(t, false, ph.data["b"].networkNames["other"]) -} - -func extenderThread(th *phonebookImpl, more []string, wg *sync.WaitGroup, repetitions int) { - defer wg.Done() - for i := 0; i <= repetitions; i++ { - start := rand.Intn(len(more)) - end := rand.Intn(len(more)-start) + start - th.ExtendPeerList(more[start:end], "default", PhoneBookEntryRelayRole) - } - th.ExtendPeerList(more, "default", PhoneBookEntryRelayRole) -} - -func TestThreadsafePhonebookExtension(t *testing.T) { - partitiontest.PartitionTest(t) - - set := []string{"a", "b", "c", "d", "e"} - more := []string{"f", "g", "h", "i", "j"} - ph := MakePhonebook(1, 1).(*phonebookImpl) - ph.ReplacePeerList(set, "default", PhoneBookEntryRelayRole) - wg := sync.WaitGroup{} - wg.Add(5) - for ti := 0; ti < 5; ti++ { - go extenderThread(ph, more, &wg, 1000) - } - wg.Wait() - - assert.Equal(t, 10, ph.Length()) -} - -func threadTestThreadsafePhonebookExtensionLong(wg *sync.WaitGroup, ph *phonebookImpl, setSize, repetitions int) { - set := make([]string, setSize) - for i := range set { - set[i] = fmt.Sprintf("%06d", i) - } - rand.Shuffle(len(set), func(i, j int) { t := set[i]; set[i] = set[j]; set[j] = t }) - extenderThread(ph, set, wg, repetitions) -} - -func TestThreadsafePhonebookExtensionLong(t *testing.T) { +func TestMultiPhonebook(t *testing.T) { partitiontest.PartitionTest(t) - if testing.Short() { - t.SkipNow() - return + set := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} + pha := make([]string, 0) + for _, e := range set[:5] { + pha = append(pha, e) } - ph := MakePhonebook(1, 1).(*phonebookImpl) - wg := sync.WaitGroup{} - const threads = 5 - const setSize = 1000 - const repetitions = 100 - wg.Add(threads) - for i := 0; i < threads; i++ { - go threadTestThreadsafePhonebookExtensionLong(&wg, ph, setSize, repetitions) + phb := make([]string, 0) + for _, e := range set[5:] { + phb = append(phb, e) } + mp := MakePhonebook(1, 1*time.Millisecond) + mp.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole) + mp.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole) - wg.Wait() - - assert.Equal(t, setSize, ph.Length()) + testPhonebookAll(t, set, mp) + testPhonebookUniform(t, set, mp, 1) + testPhonebookUniform(t, set, mp, 3) } -func TestMultiPhonebook(t *testing.T) { +// TestMultiPhonebookPersistentPeers validates that the peers added via Phonebook.AddPersistentPeers +// are not replaced when Phonebook.ReplacePeerList is called +func TestMultiPhonebookPersistentPeers(t *testing.T) { partitiontest.PartitionTest(t) - set := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} + persistentPeers := []string{"a"} + set := []string{"b", "c", "d", "e", "f", "g", "h", "i", "j", "k"} pha := make([]string, 0) for _, e := range set[:5] { pha = append(pha, e) @@ -208,12 +152,16 @@ func TestMultiPhonebook(t *testing.T) { phb = append(phb, e) } mp := MakePhonebook(1, 1*time.Millisecond) + mp.AddPersistentPeers(persistentPeers, "pha", PhoneBookEntryRelayRole) + mp.AddPersistentPeers(persistentPeers, "phb", PhoneBookEntryRelayRole) mp.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole) mp.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole) - testPhonebookAll(t, set, mp) - testPhonebookUniform(t, set, mp, 1) - testPhonebookUniform(t, set, mp, 3) + testPhonebookAll(t, append(set, persistentPeers...), mp) + allAddresses := mp.GetAddresses(len(set)+len(persistentPeers), PhoneBookEntryRelayRole) + for _, pp := range persistentPeers { + require.Contains(t, allAddresses, pp) + } } func TestMultiPhonebookDuplicateFiltering(t *testing.T) { @@ -388,21 +336,6 @@ func TestWaitAndAddConnectionTimeShortWindow(t *testing.T) { require.Equal(t, 1, len(phBookData)) } -func BenchmarkThreadsafePhonebook(b *testing.B) { - ph := MakePhonebook(1, 1).(*phonebookImpl) - threads := 5 - if b.N < threads { - threads = b.N - } - wg := sync.WaitGroup{} - wg.Add(threads) - repetitions := b.N / threads - for t := 0; t < threads; t++ { - go threadTestThreadsafePhonebookExtensionLong(&wg, ph, 1000, repetitions) - } - wg.Wait() -} - // TestPhonebookRoles tests that the filtering by roles for different // phonebooks entries works as expected. func TestPhonebookRoles(t *testing.T) { diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 07902bde79..cb54ffc246 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -2483,7 +2483,7 @@ func (wn *WebsocketNetwork) SetPeerData(peer Peer, key string, value interface{} func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, nodeInfo NodeInfo) (wn *WebsocketNetwork, err error) { phonebook := MakePhonebook(config.ConnectionsRateLimitingCount, time.Duration(config.ConnectionsRateLimitingWindowSeconds)*time.Second) - phonebook.ReplacePeerList(phonebookAddresses, string(networkID), PhoneBookEntryRelayRole) + phonebook.AddPersistentPeers(phonebookAddresses, string(networkID), PhoneBookEntryRelayRole) wn = &WebsocketNetwork{ log: log, config: config, diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index ac59b40b29..48ad81727d 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -4308,6 +4308,40 @@ func TestUpdatePhonebookAddresses(t *testing.T) { }) } +func TestUpdatePhonebookAddressesPersistentPeers(t *testing.T) { + partitiontest.PartitionTest(t) + + rapid.Check(t, func(t1 *rapid.T) { + nw := makeTestWebsocketNode(t) + // Generate a new set of relay domains + // Dont overlap with archive nodes previously specified, duplicates between them not stored in phonebook as of this writing + relayDomainsGen := rapid.SliceOfN(rapidgen.DomainOf(253, 63, "", nil), 0, 200) + relayDomains := relayDomainsGen.Draw(t1, "relayDomains") + + var persistentPeers []string + // Add an initial set of relay domains as Persistent Peers in the Phonebook, + persistentPeers = rapid.SliceOfN(rapidgen.DomainOf(253, 63, "", relayDomains), 0, 200).Draw(t1, "") + nw.phonebook.AddPersistentPeers(persistentPeers, string(nw.NetworkID), PhoneBookEntryRelayRole) + + // run updatePhonebookAddresses + nw.updatePhonebookAddresses(relayDomains, nil) + + // Check that entries are in fact in phonebook less any duplicates + dedupedRelayDomains := removeDuplicateStr(relayDomains, false) + require.Equal(t, 0, len(relayDomains)-len(dedupedRelayDomains)) + + relayPeers := nw.GetPeers(PeersPhonebookRelays) + require.Equal(t, len(dedupedRelayDomains)+len(persistentPeers), len(relayPeers)) + + relayAddrs := make([]string, 0, len(relayPeers)) + for _, peer := range relayPeers { + relayAddrs = append(relayAddrs, peer.(HTTPPeer).GetAddress()) + } + + require.ElementsMatch(t, append(dedupedRelayDomains, persistentPeers...), relayAddrs) + }) +} + func removeDuplicateStr(strSlice []string, lowerCase bool) []string { allKeys := make(map[string]bool) var dedupStrSlice = make([]string, 0) From 39ad943e84d453f43405632d120ac3c1274f6a49 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:16:46 -0400 Subject: [PATCH 29/66] rest api: Allow fast track transaction broadcasting via txHandler (#5535) --- cmd/pingpong/runCmd.go | 5 + daemon/algod/api/algod.oas2.json | 59 +++ daemon/algod/api/algod.oas3.yml | 71 ++++ daemon/algod/api/client/restClient.go | 7 + .../api/server/v2/generated/data/routes.go | 4 +- .../v2/generated/experimental/routes.go | 370 +++++++++--------- .../nonparticipating/private/routes.go | 4 +- .../nonparticipating/public/routes.go | 28 +- .../generated/participating/private/routes.go | 4 +- .../generated/participating/public/routes.go | 16 +- daemon/algod/api/server/v2/handlers.go | 18 + .../algod/api/server/v2/test/handlers_test.go | 32 +- daemon/algod/api/server/v2/test/helpers.go | 10 +- data/txHandler.go | 21 + libgoal/transactions.go | 13 + netdeploy/networkTemplate.go | 1 + node/follower_node.go | 5 + node/node.go | 6 + shared/pingpong/config.go | 1 + shared/pingpong/pingpong.go | 9 +- .../recipes/scenario1s/net.json | 124 +++--- .../recipes/scenario1s/node.json | 4 +- .../recipes/scenario1s/nonPartNode.json | 2 +- .../recipes/scenario1s/relay.json | 2 +- 24 files changed, 537 insertions(+), 279 deletions(-) diff --git a/cmd/pingpong/runCmd.go b/cmd/pingpong/runCmd.go index ae1db4106d..d141be2608 100644 --- a/cmd/pingpong/runCmd.go +++ b/cmd/pingpong/runCmd.go @@ -91,6 +91,7 @@ var generatedAccountsOffset uint64 var generatedAccountSampleMethod string var configPath string var latencyPath string +var asyncSending bool func init() { rootCmd.AddCommand(runCmd) @@ -132,6 +133,7 @@ func init() { runCmd.Flags().BoolVar(&randomLease, "randomlease", false, "set the lease to contain a random value") runCmd.Flags().BoolVar(&rekey, "rekey", false, "Create RekeyTo transactions. Requires groupsize=2 and any of random flags exc random dst") runCmd.Flags().Uint32Var(&duration, "duration", 0, "The number of seconds to run the pingpong test, forever if 0") + runCmd.Flags().BoolVar(&asyncSending, "async", false, "Use async sending mode") runCmd.Flags().Uint32Var(&nftAsaPerSecond, "nftasapersecond", 0, "The number of NFT-style ASAs to create per second") runCmd.Flags().StringVar(&pidFile, "pidfile", "", "path to write process id of this pingpong") runCmd.Flags().StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to `file`") @@ -294,6 +296,9 @@ var runCmd = &cobra.Command{ if duration > 0 { cfg.MaxRuntime = time.Duration(uint32(duration)) * time.Second } + if asyncSending { + cfg.AsyncSending = true + } if randomNote { cfg.RandomNote = true } diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 02e2349de3..ccac08f51c 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1236,6 +1236,65 @@ } } }, + "/v2/transactions/async": { + "post": { + "tags": [ + "experimental" + ], + "consumes": [ + "application/x-binary" + ], + "schemes": [ + "http" + ], + "summary": "Fast track for broadcasting a raw transaction or transaction group to the network through the tx handler without performing most of the checks and reporting detailed errors. Should be only used for development and performance testing.", + "operationId": "RawTransactionAsync", + "parameters": [ + { + "description": "The byte encoded signed transaction to broadcast to network", + "name": "rawtxn", + "in": "body", + "required": true, + "schema": { + "type": "string", + "format": "binary" + } + } + ], + "responses": { + "200": { + "type": "object" + }, + "400": { + "description": "Bad Request - Malformed Algorand transaction ", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "503": { + "description": "Service Temporarily Unavailable", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + } + }, "/v2/transactions/simulate": { "post": { "tags": [ diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 5b784af1ac..7c68a95ba5 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -6064,6 +6064,77 @@ "x-codegen-request-body-name": "rawtxn" } }, + "/v2/transactions/async": { + "post": { + "operationId": "RawTransactionAsync", + "requestBody": { + "content": { + "application/x-binary": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "The byte encoded signed transaction to broadcast to network", + "required": true + }, + "responses": { + "200": { + "content": {} + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Bad Request - Malformed Algorand transaction " + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Service Temporarily Unavailable" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Fast track for broadcasting a raw transaction or transaction group to the network through the tx handler without performing most of the checks and reporting detailed errors. Should be only used for development and performance testing.", + "tags": [ + "experimental" + ], + "x-codegen-request-body-name": "rawtxn" + } + }, "/v2/transactions/params": { "get": { "operationId": "TransactionParams", diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index 0b3abecc02..0448f650c4 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -47,6 +47,7 @@ const ( // rawRequestPaths is a set of paths where the body should not be urlencoded var rawRequestPaths = map[string]bool{ "/v2/transactions": true, + "/v2/transactions/async": true, "/v2/teal/dryrun": true, "/v2/teal/compile": true, "/v2/participation": true, @@ -527,6 +528,12 @@ func (client RestClient) SendRawTransaction(txn transactions.SignedTxn) (respons return } +// SendRawTransactionAsync gets a SignedTxn and broadcasts it to the network +func (client RestClient) SendRawTransactionAsync(txn transactions.SignedTxn) (response model.PostTransactionsResponse, err error) { + err = client.post(&response, "/v2/transactions/async", nil, protocol.Encode(&txn), true) + return +} + // SendRawTransactionGroup gets a SignedTxn group and broadcasts it to the network func (client RestClient) SendRawTransactionGroup(txgroup []transactions.SignedTxn) error { // response is not terribly useful: it's the txid of the first transaction, diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index af7c75e2d9..e61ec6d674 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -295,8 +295,8 @@ var swaggerSpec = []string{ "3pbRBT+rVMr9/WIPs/DZAcd4xUWXV0QFFp+/mZaV1z8wkO24ACN80SnUG5xQ3Ir1uuFI4cyjD0e01/vK", "Znx493dxv7/gMpznzo5T6AzXpQDdUAGXw4SN/+QC/99wAco8y2lf58xCWZr47FuFZ58eW3w4uqRHsIl8", "oOqVw079fPq+W0C2oySYdW0LdRP1RZM5vfcMdQdf+rv39+kNFzZbKu1D0bHAz7CzBV6e+ryTvV/bVE+D", - "L5i/KvoxdvBM/nra1E9Lfuyro6mvXh0baRR8xMLn1jQVm3qQQzZGnjfvHH/C6hyeebaWi+enpxjeuVbG", - "ns4+zN/3rBrxx3cNSYR03LNKi2vM7vXuw/8LAAD//9Mad+9M2AAA", + "L5i/KvoxdvBM/nrKvRKR+tbUVkt+7Kuqqa9eVRtpFPzHwufWbBWbgZB7NgagN+8c78LKHZ6xtlaN56en", + "GPq5Vsaezj7M3/csHvHHdw25hFTds0qLa8z89e7D/wsAAP//ZTlZG2jYAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index 47db461039..ec504b55e3 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -22,6 +22,9 @@ type ServerInterface interface { // Returns OK if experimental API is enabled. // (GET /v2/experimental) ExperimentalCheck(ctx echo.Context) error + // Fast track for broadcasting a raw transaction or transaction group to the network through the tx handler without performing most of the checks and reporting detailed errors. Should be only used for development and performance testing. + // (POST /v2/transactions/async) + RawTransactionAsync(ctx echo.Context) error } // ServerInterfaceWrapper converts echo contexts to parameters. @@ -40,6 +43,17 @@ func (w *ServerInterfaceWrapper) ExperimentalCheck(ctx echo.Context) error { return err } +// RawTransactionAsync converts echo context to params. +func (w *ServerInterfaceWrapper) RawTransactionAsync(ctx echo.Context) error { + var err error + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.RawTransactionAsync(ctx) + return err +} + // This is a simple interface which specifies echo.Route addition functions which // are present on both echo.Echo and echo.Group, since we want to allow using // either of them for path registration @@ -69,191 +83,197 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL } router.GET(baseURL+"/v2/experimental", wrapper.ExperimentalCheck, m...) + router.POST(baseURL+"/v2/transactions/async", wrapper.RawTransactionAsync, m...) } // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "1ZtnzgAA", + "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", + "/94hkwcT0wAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 08f34c9b08..43a971be7f 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -312,8 +312,8 @@ var swaggerSpec = []string{ "lSpwv1JCimNMlL/p/7yRafTH/jqKTnXp2M/HH9v1WFsI0svKZOqWMgtHr0wsOsZzV6EEzcW16mkU8wM0", "0f/sJ5ewKN+gjVxkwDhmUlWVaWwDtnPtF1q/3tgRmF46M/lCSJwAzfA4C5Xi4YFPgoZUyQw13s717CD7", "UWXQv57xAv5nBeWmuYEdjJNpiz87Ao8Uvnnwdddnp3f7kT8+F9BbV584XNnzzt/Ht1wYe4m7MHzEaL+z", - "AZ4fu5ybnV+bNFe9L5i7K/gxdG6N/npc146Lfuyq4rGvThUdaOT94/znxiwXmrmQJGoD1/sPdmexMomj", - "lsZqc3p8jKGtS6XN8eRu+rFj0Qk/fqg306cirzf17sPd/wsAAP//2NTAeUjZAAA=", + "AZ4fu5ybnV+bNFe9L5i7K/gxdG6N/nrM29Te1sR9+a/ox66aHvvq1NSBRt53zn9uTHahCQzJpTZ+vf9g", + "dx2rljhKaiw6p8fHGPa6VNocT+6mHzvWnvDjh3qjfZryesPvPtz9vwAAAP//CoQBZ2TZAAA=", } // 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 5ad094a2aa..88d2296319 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -946,20 +946,20 @@ var swaggerSpec = []string{ "BaexpREw7sr2tpnI5AYZn9zIkvfzvG/XkJYGuPAk30fvPF7JwVo3LlkzmJbzOTZt7tzRmaUBjscE/0is", "0C53KBfcj4Ls4FUjz+umnreH63KXIBv8vq+3+AC3g/INXmYsC8o3/soXEsWWZW5xaPvdHZbR2sq63agE", "vI51vr8+r/Zr7/ILfLdO1DZ/t2ghl1QRu7+QkZJnLo+pU397zYdXL7FDn695zaa3Viqx642szs07RET4", - "XW4mkCtSgEz0mtsD1ezqbut825M7uWtW+9cQGzb9HHoYbLdmdc0QDiQ9ZMDXUHwEnUnqxLxGvxL0WvSn", - "sYRtSuybBw0e6QzfjCGpXSrujhTyglCS5gxvUAVXWpapfssp3tEEC5t040u8M7qfvz33r8SvCSO3eG6o", - "t5xiIFF1cxPlczOIXFN8B+DZqCrnc1CGV4ZEMgN4y91bjJOSG0tLzMiSpVIkNinWnCGjn0zsm0u6ITOs", - "RSLIHyAFmRrJHuy69RcrzfLcBbSYaYiYveVUkxyo0uRHZrisGc4XQqjCykBfCnlRYSHetWIOHBRTSdz5", - "8r19io0h3PK9kw8dlvZxXdD9djtCeNhZ1gv56QuMQ8M6yjlTuo6B6MB+a/ffS8aTKJGdL4C4kLA2bZH7", - "WL3NEdCD5uWQXsBbbiScFgS5OtVXI4f2LU/nLNrT0aKaxka0LoP8WgeZeAfhMiTCZO6uVv5EaaIBHfjb", - "S9x4Wxm/tfd7XqM0RC7wzDztEcj2qWsk1vOSMxIajrBWaRr3xnkD5D9vE/p3N2MvejQezGLsDthlV81W", - "UYg3v+FjQnPB57YiorEgBe4T40WpMcj7Jp10sKJ5IlYgJctADVwpE/zbFc1/qj77MB7BGtJES5pCYr0G", - "Q7F2br6xdLpLkAYN85ZLyBjVkG9IISGFzNb+YorUxvbEVk8g6YLyOcpcKcr5wr5mx7kECVVvMWPftoeI", - "115Z88TWgevCeEKsozIslQs0XUR6taBkMga1pwRb2mKIyRxhBVjls8+CHo96NWSD1FUd12aR0+QPA8R/", - "Q5AH+KknPkRZ1DtqvaPWj0atsfKDiLpZywdg8RVuyw07i2662OYt+p4+SiXeu3L2f/Zy9p4DKUKJpA2t", - "P95HjSrCNLnEYkNTIEbwlOjzdu3WnYWM+WvBUXdVKZXrApouKOOuUk2VLYBwaNepWPvWiDfiLrTMDP2E", - "Bh2QlpLpDdoJtGC/XYD5/zujaCuQK29ClDIfHY8WWhfHR0e5SGm+EEofjT6Mw2eq9fBdBf97r/0Xkq2M", - "RfPh3Yf/PwAA///0dvnw8YIBAA==", + "XW4mkCtSgEz0mtsD1ezqbut825M7uWtW+9cQGzb9HHoYbLdmdc0QDiQ9ZMDXUHwEnUnqxLxGvxLaTBJs", + "PEOPRn+KS9jCxL550MCSzvDN+JLa3eLuTyEvCCVpzvB2VXClZZnqt5zi/U2wsEk39sQ7qvt533P/SvwK", + "MXLD54Z6yykGGVW3OlEeOIPIFcZ3AJ7FqnI+B2X4aEhAM4C33L3FOCm5scLEjCxZKkViE2bN+TK6y8S+", + "uaQbMsM6JYL8AVKQqZH6wa5bX7LSLM9dsIuZhojZW041yYEqTX5khgOb4XyRhCrkDPSlkBcVFuIdLebA", + "QTGVxB0z39un2DTCLd87ANGZaR/Xxd5vt1uEh51lvZCfvsAYNayxnDOl6/iIDuy3dje+ZDyJEtn5AogL", + "F2vTFrmPld0cAT1oXhzpBbzlRvppQZDjU301cmjfAHXOoj0dLappbETrosivdZD5dxAuQyJM5u7a5U+U", + "QhrQgb/ZxI23VfNbe7/nFUtD5ALPzNMegWyfuiZjPS85A6LhJGuVrXFvnDdA/vM2qH93M7akR+PBrMnu", + "gF121WwjhXjzGz4mNBd8bqslGutS4D4xXpQaA8Bv0oEHK5onYgVSsgzUwJUywb9d0fyn6rMP4xGsIU20", + "pCkk1qMwFGvn5htLp7sEadBMb7mEjFEN+YYUElLIbF0wpkhtiE9sZQWSLiifo8yVopwv7Gt2nEuQUPUd", + "M7Zve4h4XZY1T2yNuC6MJ8Q6McMyukDTRaSPC0omY2x7SrBlL4aY0xFWgBVA+6zr8ahXQzZIXdUxbxY5", + "Tf4wQPw3BHmAn3riQ5RMvaPWO2r9aNQaK02IqJu1/AMWX+G23LAj6aYLcd6iX+qjVOm9K3X/Zy917zmQ", + "IpRI2tD64z3WqCJMk0ssRDQFYgRPif5w14rdWciY2xYcdVexUrkOoemCMu6q2FSZBAiHdl2MtW+beCOu", + "RMvM0Ido0AFpKZneoJ1AC/bbBZj/vzOKtgK58iZEKfPR8WihdXF8dJSLlOYLofTR6MM4fKZaD99V8L/3", + "2n8h2cpYNB/effj/AwAA///DhipwDYMBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 77ebb098ce..42821accca 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -342,8 +342,8 @@ var swaggerSpec = []string{ "U+SbpPZBuYf54M6ehzTCqUID3A5BmaKjfNLjeycbP9R/UvoOBbRDwaIP5KnZR/MfLOIPFnE7FvEdJA4j", "nlrPNBJEd5w+NJVhoMt80S+qj48coXldch056B4yc5zhiN648TG4xsdW6pK4Ip2OSwZbQX4MiQ28Wz3v", "D5b3B8v792F5Z4cZTVcwubVmdAm7Da8afcisa1uo6+idA2EhH6ShHdh9rE3/79NrLmy2VNqnR8Kik8PO", - "Fnh56nOh935t048OvmBO1ejHOOgo+etpU9M3+bH/RJL66p8IRhqFuIXwuX0ujZ8fkbU3D49v3jm2jBXj", - "PNdvX9Oenp5iypG1MvZ09mH+vvfSFn9815DA++au8KTw4d2H/xcAAP//NCrlReDiAAA=", + "Fnh56nOh935t048OvmBO1ejHOOgo+esp7xq2uy8koSxr8mP/+ST11T8fjDQKMQ3hc/uUGj9NIttvHiXf", + "vHMsG6vJ+RuhfWl7enqK6UjWytjT2Yf5+94rXPzxXUMe75t7xJPJh3cf/l8AAAD//zxPRzD84gAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 1b23d98397..c23a84bffe 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -364,14 +364,14 @@ var swaggerSpec = []string{ "Pl/153OVzDt/H68p00Z+cZH1WK+o31kDzY9dGs3Or03mqt4XTMcV/NiReEphA5jayuZbur5s+X5KGwHy", "TKCBYIgXbpIZ48ggQgbW2PXsx7720mNbl0uwXlz+aTQiHmpBZlLQLKUKy+C4hLM9tfXjLVWjbsDKeeTh", "C8FES0A/SNsc9aO9ryE47hj5L9iXoHocyuHK2gN/Z5mpB9EzmhEf8ZaQVzQ3Gw4ZOXOSeQsbv7e88/kF", - "lM8sUXwyEeCZP3yKUAxMbeluMh4JFmSGHnPfGwXPMIAF8MSxoGQmsq0vhijpWm9s1EyXuR3XVS2jH+/A", - "SPjPbRncZxD8Yof7Yof7Yqn5Yof7srtf7HAj7XBfrFRfrFT/K61Uh5imYmKmM80MS5tYMYi25rW6HW0y", - "s9Usvh2zy3Qtk/WLCDJ9RMgl5r2i5paAFUiaY6FlFSSyK9AFEiN/ITu94kkLEutoaCb+qvmv9fC8qk5O", - "HgM5ud/tozTL85A39/uivIufbNbsb8nV5GrSG0lCIVaQ2bCpMDOQ7bV32P+vHvenXkoxjBFc0hXUAcJE", - "VfM5S5lFeS74gtCFaLyTDd8mXOAXkAY4m5iVMD11yY+ZImuzeFe3qZ3AqC259yWA82YL977od8gl/phv", - "CO/Al/x/G/OM/79aSr9FnO2tGOnOsXtc9QtX+RRc5bPzlT/6G2lgPvwfKWY+OXnyh11QaGx+LTT5Hj3v", - "byeO1bXwYvlpbypo+TB6b+5rvHdDb1i8RWs/2HfvzUWABczdBds4d54eH2MGzKVQ+nhirr+242f48X0N", - "s69YOiklW2EBlPcf/18AAAD//6Cbm1hv8QAA", + "lM8sUXwyEeCZP3yKUAxMbeluMh4JFmSGHnPfGwXPMIAF8MSxoGQmsq0vhijpWm9s1EyXuR3T9j3QthH6", + "woTRj3dgQPznthruMxZ+sdF9sdF9seJ8sdF92d0vNrqRNrovFqwvFqz/lRasQ8xWMTHTmW2GpU2sJkRb", + "81q9jzZZ22oW347nZbqWyfoFBpk+IuQSc2JRc0vACiTNsQizCpLcFegeiVHBkJ1e8aQFiXVCNBN/1fzX", + "en9eVScnj4Gc3O/2UZrlecib+31R3sVPNqP2t+RqcjXpjSShECvIbEhVmDXI9to77P9Xj/tTL90Yxg8u", + "6Qrq4GGiqvmcpcyiPBd8QehCNJ7Lhm8TLvALSAOcTdpKmJ66xMhMkbVZvKvp1E5u1Jbc+xLAebOFe1/7", + "O+QSf+g3hHfgK/+/jXni/18tpd8iBvdWjHTn2D2u+oWrfAqu8tn5yh/9/TQwLf6PFDOfnDz5wy4oNES/", + "Fpp8j175txPH6jp5sdy1NxW0fIi9N/c1nr2hpyzeorWP7Lv35iLA4ubugm0cP0+PjzE75lIofTwx11/b", + "KTT8+L6G2VcznZSSrbA4yvuP/y8AAP//nFHTVIvxAAA=", } // 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 cd9fbbbd4a..ff7edac6f3 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -105,6 +105,7 @@ type NodeInterface interface { GenesisID() string GenesisHash() crypto.Digest BroadcastSignedTxGroup(txgroup []transactions.SignedTxn) error + AsyncBroadcastSignedTxGroup(txgroup []transactions.SignedTxn) error Simulate(request simulation.Request) (result simulation.Result, err error) GetPendingTransaction(txID transactions.Txid) (res node.TxnWithStatus, found bool) GetPendingTxnsFromPool() ([]transactions.SignedTxn, error) @@ -930,6 +931,23 @@ func (v2 *Handlers) RawTransaction(ctx echo.Context) error { return ctx.JSON(http.StatusOK, model.PostTransactionsResponse{TxId: txid.String()}) } +// RawTransactionAsync broadcasts a raw transaction to the network without ensuring it is accepted by transaction pool. +// (POST /v2/transactions/async) +func (v2 *Handlers) RawTransactionAsync(ctx echo.Context) error { + if !v2.Node.Config().EnableExperimentalAPI { + return ctx.String(http.StatusNotFound, "/transactions/async was not enabled in the configuration file by setting the EnableExperimentalAPI to true") + } + txgroup, err := decodeTxGroup(ctx.Request().Body, config.MaxTxGroupSize) + if err != nil { + return badRequest(ctx, err, err.Error(), v2.Log) + } + err = v2.Node.AsyncBroadcastSignedTxGroup(txgroup) + if err != nil { + return serviceUnavailable(ctx, err, err.Error(), v2.Log) + } + return ctx.NoContent(http.StatusOK) +} + // PreEncodedSimulateTxnResult mirrors model.SimulateTransactionResult type PreEncodedSimulateTxnResult struct { Txn PreEncodedTxInfo `codec:"txn-result"` diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index de8a011458..2a690e9840 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -27,6 +27,7 @@ import ( "net" "net/http" "net/http/httptest" + "reflect" "strings" "testing" "time" @@ -793,13 +794,13 @@ func TestPendingTransactionsByAddress(t *testing.T) { pendingTransactionsByAddressTest(t, -1, "json", 400) } -func prepareTransactionTest(t *testing.T, txnToUse int, txnPrep func(transactions.SignedTxn) []byte) (handler v2.Handlers, c echo.Context, rec *httptest.ResponseRecorder, releasefunc func()) { +func prepareTransactionTest(t *testing.T, txnToUse int, txnPrep func(transactions.SignedTxn) []byte, cfg config.Local) (handler v2.Handlers, c echo.Context, rec *httptest.ResponseRecorder, releasefunc func()) { numAccounts := 5 numTransactions := 5 offlineAccounts := true mockLedger, _, _, stxns, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) + mockNode := makeMockNodeWithConfig(mockLedger, t.Name(), nil, cannedStatusReportGolden, false, cfg) handler = v2.Handlers{ Node: mockNode, @@ -819,13 +820,18 @@ func prepareTransactionTest(t *testing.T, txnToUse int, txnPrep func(transaction return } -func postTransactionTest(t *testing.T, txnToUse, expectedCode int) { +func postTransactionTest(t *testing.T, txnToUse int, expectedCode int, method string, enableExperimental bool) { txnPrep := func(stxn transactions.SignedTxn) []byte { return protocol.Encode(&stxn) } - handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, txnPrep) + cfg := config.GetDefaultLocal() + cfg.EnableExperimentalAPI = enableExperimental + handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, txnPrep, cfg) defer releasefunc() - err := handler.RawTransaction(c) + results := reflect.ValueOf(&handler).MethodByName(method).Call([]reflect.Value{reflect.ValueOf(c)}) + require.Equal(t, 1, len(results)) + // if the method returns nil, the cast would fail so use type assertion test + err, _ := results[0].Interface().(error) require.NoError(t, err) require.Equal(t, expectedCode, rec.Code) } @@ -834,8 +840,18 @@ func TestPostTransaction(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - postTransactionTest(t, -1, 400) - postTransactionTest(t, 0, 200) + postTransactionTest(t, -1, 400, "RawTransaction", false) + postTransactionTest(t, 0, 200, "RawTransaction", false) +} + +func TestPostTransactionAsync(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + postTransactionTest(t, -1, 404, "RawTransactionAsync", false) + postTransactionTest(t, 0, 404, "RawTransactionAsync", false) + postTransactionTest(t, -1, 400, "RawTransactionAsync", true) + postTransactionTest(t, 0, 200, "RawTransactionAsync", true) } func simulateTransactionTest(t *testing.T, txnToUse int, format string, expectedCode int) { @@ -849,7 +865,7 @@ func simulateTransactionTest(t *testing.T, txnToUse int, format string, expected } return protocol.EncodeReflect(&request) } - handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, txnPrep) + handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, txnPrep, config.GetDefaultLocal()) defer releasefunc() err := handler.SimulateTransaction(c, model.SimulateTransactionParams{Format: (*model.SimulateTransactionParamsFormat)(&format)}) require.NoError(t, err) diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index aef8051484..aedcede912 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -137,10 +137,14 @@ func (m *mockNode) AppendParticipationKeys(id account.ParticipationID, keys acco } func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error, status node.StatusReport, devMode bool) *mockNode { + return makeMockNodeWithConfig(ledger, genesisID, nodeError, status, devMode, config.GetDefaultLocal()) +} + +func makeMockNodeWithConfig(ledger v2.LedgerForAPI, genesisID string, nodeError error, status node.StatusReport, devMode bool, cfg config.Local) *mockNode { return &mockNode{ ledger: ledger, genesisID: genesisID, - config: config.GetDefaultLocal(), + config: cfg, err: nodeError, usertxns: map[basics.Address][]node.TxnWithStatus{}, status: status, @@ -166,6 +170,10 @@ func (m *mockNode) BroadcastSignedTxGroup(txgroup []transactions.SignedTxn) erro return m.err } +func (m *mockNode) AsyncBroadcastSignedTxGroup(txgroup []transactions.SignedTxn) error { + return m.err +} + func (m *mockNode) Simulate(request simulation.Request) (simulation.Result, error) { simulator := simulation.MakeSimulator(m.ledger.(*data.Ledger), m.config.EnableDeveloperAPI) return simulator.Simulate(request) diff --git a/data/txHandler.go b/data/txHandler.go index 74f9b07b77..a5f44a1d07 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -665,6 +665,27 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net return network.OutgoingMessage{Action: network.Ignore} } +var errBackLogFullLocal = errors.New("backlog full") + +// LocalTransaction is a special shortcut handler for local transactions and intended to be used +// for performance testing and debugging purposes only since it does not have congestion control +// and duplicates detection. +func (handler *TxHandler) LocalTransaction(txgroup []transactions.SignedTxn) error { + select { + case handler.backlogQueue <- &txBacklogMsg{ + rawmsg: &network.IncomingMessage{}, + unverifiedTxGroup: txgroup, + rawmsgDataHash: nil, + unverifiedTxGroupHash: nil, + capguard: nil, + }: + default: + transactionMessagesDroppedFromBacklog.Inc(nil) + return errBackLogFullLocal + } + return nil +} + // checkAlreadyCommitted test to see if the given transaction ( in the txBacklogMsg ) was already committed, and // whether it would qualify as a candidate for the transaction pool. // diff --git a/libgoal/transactions.go b/libgoal/transactions.go index 5ff9e3bbaf..97ac2be994 100644 --- a/libgoal/transactions.go +++ b/libgoal/transactions.go @@ -172,6 +172,19 @@ func (c *Client) BroadcastTransaction(stx transactions.SignedTxn) (txid string, return resp.TxId, nil } +// BroadcastTransactionAsync broadcasts a signed transaction to the network by appending it into tx handler queue. +func (c *Client) BroadcastTransactionAsync(stx transactions.SignedTxn) error { + algod, err := c.ensureAlgodClient() + if err != nil { + return err + } + _, err = algod.SendRawTransactionAsync(stx) + if err != nil { + return err + } + return nil +} + // BroadcastTransactionGroup broadcasts a signed transaction group to the network using algod func (c *Client) BroadcastTransactionGroup(txgroup []transactions.SignedTxn) error { algod, err := c.ensureAlgodClient() diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go index 2af5d79309..7d8b12bf92 100644 --- a/netdeploy/networkTemplate.go +++ b/netdeploy/networkTemplate.go @@ -297,6 +297,7 @@ func createConfigFile(node remote.NodeConfigGoal, configFile string, numNodes in cfg.DNSBootstrapID = "" cfg.EnableProfiler = true cfg.EnableRuntimeMetrics = true + cfg.EnableExperimentalAPI = true if relaysCount == 0 { cfg.DisableNetworking = true } diff --git a/node/follower_node.go b/node/follower_node.go index 5ac3ca6c00..e044333d42 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -233,6 +233,11 @@ func (node *AlgorandFollowerNode) BroadcastSignedTxGroup(_ []transactions.Signed return fmt.Errorf("cannot broadcast txns in sync mode") } +// AsyncBroadcastSignedTxGroup errors in follower mode +func (node *AlgorandFollowerNode) AsyncBroadcastSignedTxGroup(_ []transactions.SignedTxn) (err error) { + return fmt.Errorf("cannot broadcast txns in sync mode") +} + // BroadcastInternalSignedTxGroup errors in follower mode func (node *AlgorandFollowerNode) BroadcastInternalSignedTxGroup(_ []transactions.SignedTxn) (err error) { return fmt.Errorf("cannot broadcast internal signed txn group in sync mode") diff --git a/node/node.go b/node/node.go index 87ffd3e2a4..477c1b794c 100644 --- a/node/node.go +++ b/node/node.go @@ -485,6 +485,12 @@ func (node *AlgorandFullNode) BroadcastSignedTxGroup(txgroup []transactions.Sign return node.broadcastSignedTxGroup(txgroup) } +// AsyncBroadcastSignedTxGroup feeds a raw transaction group directly to the transaction pool. +// This method is intended to be used for performance testing and debugging purposes only. +func (node *AlgorandFullNode) AsyncBroadcastSignedTxGroup(txgroup []transactions.SignedTxn) (err error) { + return node.txHandler.LocalTransaction(txgroup) +} + // BroadcastInternalSignedTxGroup broadcasts a transaction group that has already been signed. // It is originated internally, and in DevMode, it will not advance the round. func (node *AlgorandFullNode) BroadcastInternalSignedTxGroup(txgroup []transactions.SignedTxn) (err error) { diff --git a/shared/pingpong/config.go b/shared/pingpong/config.go index 32f5fab7e9..6d3057e9bb 100644 --- a/shared/pingpong/config.go +++ b/shared/pingpong/config.go @@ -74,6 +74,7 @@ type PpConfig struct { AppLocalKeys uint32 Rekey bool MaxRuntime time.Duration + AsyncSending bool // asset spam; make lots of NFT ASAs NftAsaPerSecond uint32 // e.g. 100 diff --git a/shared/pingpong/pingpong.go b/shared/pingpong/pingpong.go index 112105679a..a35fd451a4 100644 --- a/shared/pingpong/pingpong.go +++ b/shared/pingpong/pingpong.go @@ -815,7 +815,14 @@ func (pps *WorkerState) sendFromTo( sentCount++ pps.schedule(1) var txid string - txid, sendErr = client.BroadcastTransaction(stxn) + if pps.cfg.AsyncSending { + sendErr = client.BroadcastTransactionAsync(stxn) + if sendErr == nil { + txid = stxn.Txn.ID().String() + } + } else { + txid, sendErr = client.BroadcastTransaction(stxn) + } pps.recordTxidSent(txid, sendErr) } else { // Generate txn group diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/net.json b/test/testdata/deployednettemplates/recipes/scenario1s/net.json index 10b453281e..a93573426d 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/net.json +++ b/test/testdata/deployednettemplates/recipes/scenario1s/net.json @@ -17,7 +17,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -38,7 +38,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -59,7 +59,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -80,7 +80,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -101,7 +101,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -122,7 +122,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -143,7 +143,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -164,7 +164,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -188,7 +188,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -206,13 +206,13 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": true, + "EnableTelemetry": false, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": true, + "EnableMetrics": false, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -236,7 +236,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -254,13 +254,13 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": true, + "EnableTelemetry": false, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": true, + "EnableMetrics": false, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -284,7 +284,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -308,7 +308,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -332,7 +332,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -356,7 +356,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -380,7 +380,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -404,7 +404,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -422,13 +422,13 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": true, + "EnableTelemetry": false, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": true, + "EnableMetrics": false, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -452,7 +452,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -476,7 +476,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -494,13 +494,13 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": false, + "EnableTelemetry": true, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": false, + "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "EnableBlockStats": true, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" } ] }, @@ -518,13 +518,13 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": true, + "EnableTelemetry": false, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": true, + "EnableMetrics": false, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -548,7 +548,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" } ] }, @@ -566,13 +566,13 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": false, + "EnableTelemetry": true, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": false, + "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "EnableBlockStats": true, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" } ] }, @@ -596,7 +596,7 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" } ] }, @@ -614,13 +614,13 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": false, + "EnableTelemetry": true, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": false, + "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "EnableBlockStats": true, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" } ] }, @@ -638,13 +638,13 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": false, + "EnableTelemetry": true, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": false, + "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "EnableBlockStats": true, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" } ] }, @@ -666,7 +666,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] }, @@ -688,7 +688,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] }, @@ -710,7 +710,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] }, @@ -732,7 +732,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] }, @@ -754,7 +754,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] }, @@ -776,7 +776,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] }, @@ -798,7 +798,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] }, @@ -820,7 +820,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] }, @@ -842,7 +842,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] }, @@ -864,7 +864,7 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } ] } diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/node.json b/test/testdata/deployednettemplates/recipes/scenario1s/node.json index f6edee9681..3b4cb78771 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/node.json +++ b/test/testdata/deployednettemplates/recipes/scenario1s/node.json @@ -6,7 +6,7 @@ "TelemetryURI": "{{TelemetryURI}}", "EnableMetrics": false, "MetricsURI": "{{MetricsURI}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}", + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}", "AltConfigs": [ { "APIEndpoint": "{{APIEndpoint}}", @@ -16,7 +16,7 @@ "TelemetryURI": "{{TelemetryURI}}", "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }", + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }", "FractionApply": 0.2 } ] diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/nonPartNode.json b/test/testdata/deployednettemplates/recipes/scenario1s/nonPartNode.json index 48f4536841..3f1245f7dd 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/nonPartNode.json +++ b/test/testdata/deployednettemplates/recipes/scenario1s/nonPartNode.json @@ -1,5 +1,5 @@ { "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" } diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/relay.json b/test/testdata/deployednettemplates/recipes/scenario1s/relay.json index b6a7422dd6..327cf0b188 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/relay.json +++ b/test/testdata/deployednettemplates/recipes/scenario1s/relay.json @@ -8,5 +8,5 @@ "TelemetryURI": "{{TelemetryURI}}", "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" } From 0e651fdbb18082325e437b00b1c5e3ae260b3d45 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:50:50 -0400 Subject: [PATCH 30/66] ledger: move accountUpdatesLedgerEvaluator to tracker.go (#5644) --- ledger/acctupdates.go | 96 -------------------------------------- ledger/acctupdates_test.go | 13 ------ ledger/tracker.go | 95 +++++++++++++++++++++++++++++++++++++ ledger/tracker_test.go | 13 ++++++ 4 files changed, 108 insertions(+), 109 deletions(-) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index e4895ad715..cf89d770f5 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -29,10 +29,8 @@ import ( "github.com/algorand/go-deadlock" "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/logging" @@ -717,100 +715,6 @@ func (r *readCloseSizer) Size() (int64, error) { // functions below this line are all internal functions -// accountUpdatesLedgerEvaluator is a "ledger emulator" which is used *only* by initializeCaches, as a way to shortcut -// the locks taken by the real ledger object when making requests that are being served by the accountUpdates. -// Using this struct allow us to take the tracker lock *before* calling the loadFromDisk, and having the operation complete -// without taking any locks. Note that it's not only the locks performance that is gained : by having the loadFrom disk -// not requiring any external locks, we can safely take a trackers lock on the ledger during reloadLedger, which ensures -// that even during catchpoint catchup mode switch, we're still correctly protected by a mutex. -type accountUpdatesLedgerEvaluator struct { - // au is the associated accountUpdates structure which invoking the trackerEvalVerified function, passing this structure as input. - // the accountUpdatesLedgerEvaluator would access the underlying accountUpdates function directly, bypassing the balances mutex lock. - au *accountUpdates - // ao is onlineAccounts for voters access - ao *onlineAccounts - // 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. - prevHeader bookkeeping.BlockHeader -} - -func (aul *accountUpdatesLedgerEvaluator) FlushCaches() {} - -// GenesisHash returns the genesis hash -func (aul *accountUpdatesLedgerEvaluator) GenesisHash() crypto.Digest { - return aul.au.ledger.GenesisHash() -} - -// GenesisProto returns the genesis consensus params -func (aul *accountUpdatesLedgerEvaluator) GenesisProto() config.ConsensusParams { - return aul.au.ledger.GenesisProto() -} - -// VotersForStateProof returns the top online accounts at round rnd. -func (aul *accountUpdatesLedgerEvaluator) VotersForStateProof(rnd basics.Round) (voters *ledgercore.VotersForRound, err error) { - return aul.ao.voters.VotersForStateProof(rnd) -} - -func (aul *accountUpdatesLedgerEvaluator) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { - // Since state proof transaction is not being verified (we only apply the change) during replay, we don't need to implement this function at the moment. - return nil, fmt.Errorf("accountUpdatesLedgerEvaluator: GetStateProofVerificationContext, needed for state proof verification, is not implemented in accountUpdatesLedgerEvaluator") -} - -// BlockHdr returns the header of the given round. When the evaluator is running, it's only referring to the previous header, which is what we -// are providing here. Any attempt to access a different header would get denied. -func (aul *accountUpdatesLedgerEvaluator) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { - if r == aul.prevHeader.Round { - return aul.prevHeader, nil - } - hdr, ok := aul.tail.blockHeader(r) - if ok { - 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 -func (aul *accountUpdatesLedgerEvaluator) LatestTotals() (basics.Round, ledgercore.AccountTotals, error) { - return aul.au.latestTotalsImpl() -} - -// CheckDup test to see if the given transaction id/lease already exists. It's not needed by the accountUpdatesLedgerEvaluator and implemented as a stub. -func (aul *accountUpdatesLedgerEvaluator) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { - // this is a non-issue since this call will never be made on non-validating evaluation - return fmt.Errorf("accountUpdatesLedgerEvaluator: tried to check for dup during accountUpdates initialization ") -} - -// LookupWithoutRewards returns the account balance for a given address at a given round, without the reward -func (aul *accountUpdatesLedgerEvaluator) LookupWithoutRewards(rnd basics.Round, addr basics.Address) (ledgercore.AccountData, basics.Round, error) { - data, validThrough, _, _, err := aul.au.lookupWithoutRewards(rnd, addr, false /*don't sync*/) - if err != nil { - return ledgercore.AccountData{}, 0, err - } - - return data, validThrough, err -} - -func (aul *accountUpdatesLedgerEvaluator) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) { - r, _, err := aul.au.lookupResource(rnd, addr, basics.CreatableIndex(aidx), basics.AppCreatable, false /* don't sync */) - return ledgercore.AppResource{AppParams: r.AppParams, AppLocalState: r.AppLocalState}, err -} - -func (aul *accountUpdatesLedgerEvaluator) LookupAsset(rnd basics.Round, addr basics.Address, aidx basics.AssetIndex) (ledgercore.AssetResource, error) { - r, _, err := aul.au.lookupResource(rnd, addr, basics.CreatableIndex(aidx), basics.AssetCreatable, false /* don't sync */) - return ledgercore.AssetResource{AssetParams: r.AssetParams, AssetHolding: r.AssetHolding}, err -} - -func (aul *accountUpdatesLedgerEvaluator) LookupKv(rnd basics.Round, key string) ([]byte, error) { - return aul.au.lookupKv(rnd, key, false /* don't sync */) -} - -// GetCreatorForRound returns the asset/app creator for a given asset/app index at a given round -func (aul *accountUpdatesLedgerEvaluator) GetCreatorForRound(rnd basics.Round, cidx basics.CreatableIndex, ctype basics.CreatableType) (creator basics.Address, ok bool, err error) { - return aul.au.getCreatorForRound(rnd, cidx, ctype, false /* don't sync */) -} - // latestTotalsImpl returns the totals of all accounts for the most recent round, as well as the round number func (au *accountUpdates) latestTotalsImpl() (basics.Round, ledgercore.AccountTotals, error) { offset := len(au.deltas) diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 658cb71330..d938b0d530 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -2711,16 +2711,3 @@ 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/tracker.go b/ledger/tracker.go index 8cfe71aff0..5975a91409 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -28,6 +28,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" @@ -779,3 +780,97 @@ func (tr *trackerRegistry) getDbRound() basics.Round { tr.mu.RUnlock() return dbRound } + +// accountUpdatesLedgerEvaluator is a "ledger emulator" which is used *only* by initializeCaches, as a way to shortcut +// the locks taken by the real ledger object when making requests that are being served by the accountUpdates. +// Using this struct allow us to take the tracker lock *before* calling the loadFromDisk, and having the operation complete +// without taking any locks. Note that it's not only the locks performance that is gained : by having the loadFrom disk +// not requiring any external locks, we can safely take a trackers lock on the ledger during reloadLedger, which ensures +// that even during catchpoint catchup mode switch, we're still correctly protected by a mutex. +type accountUpdatesLedgerEvaluator struct { + // au is the associated accountUpdates structure which invoking the trackerEvalVerified function, passing this structure as input. + // the accountUpdatesLedgerEvaluator would access the underlying accountUpdates function directly, bypassing the balances mutex lock. + au *accountUpdates + // ao is onlineAccounts for voters access + ao *onlineAccounts + // 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. + prevHeader bookkeeping.BlockHeader +} + +func (aul *accountUpdatesLedgerEvaluator) FlushCaches() {} + +// GenesisHash returns the genesis hash +func (aul *accountUpdatesLedgerEvaluator) GenesisHash() crypto.Digest { + return aul.au.ledger.GenesisHash() +} + +// GenesisProto returns the genesis consensus params +func (aul *accountUpdatesLedgerEvaluator) GenesisProto() config.ConsensusParams { + return aul.au.ledger.GenesisProto() +} + +// VotersForStateProof returns the top online accounts at round rnd. +func (aul *accountUpdatesLedgerEvaluator) VotersForStateProof(rnd basics.Round) (voters *ledgercore.VotersForRound, err error) { + return aul.ao.voters.VotersForStateProof(rnd) +} + +func (aul *accountUpdatesLedgerEvaluator) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { + // Since state proof transaction is not being verified (we only apply the change) during replay, we don't need to implement this function at the moment. + return nil, fmt.Errorf("accountUpdatesLedgerEvaluator: GetStateProofVerificationContext, needed for state proof verification, is not implemented in accountUpdatesLedgerEvaluator") +} + +// BlockHdr returns the header of the given round. When the evaluator is running, it's only referring to the previous header, which is what we +// are providing here. Any attempt to access a different header would get denied. +func (aul *accountUpdatesLedgerEvaluator) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { + if r == aul.prevHeader.Round { + return aul.prevHeader, nil + } + hdr, ok := aul.tail.blockHeader(r) + if ok { + 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 +func (aul *accountUpdatesLedgerEvaluator) LatestTotals() (basics.Round, ledgercore.AccountTotals, error) { + return aul.au.latestTotalsImpl() +} + +// CheckDup test to see if the given transaction id/lease already exists. It's not needed by the accountUpdatesLedgerEvaluator and implemented as a stub. +func (aul *accountUpdatesLedgerEvaluator) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { + // this is a non-issue since this call will never be made on non-validating evaluation + return fmt.Errorf("accountUpdatesLedgerEvaluator: tried to check for dup during accountUpdates initialization ") +} + +// LookupWithoutRewards returns the account balance for a given address at a given round, without the reward +func (aul *accountUpdatesLedgerEvaluator) LookupWithoutRewards(rnd basics.Round, addr basics.Address) (ledgercore.AccountData, basics.Round, error) { + data, validThrough, _, _, err := aul.au.lookupWithoutRewards(rnd, addr, false /*don't sync*/) + if err != nil { + return ledgercore.AccountData{}, 0, err + } + + return data, validThrough, err +} + +func (aul *accountUpdatesLedgerEvaluator) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) { + r, _, err := aul.au.lookupResource(rnd, addr, basics.CreatableIndex(aidx), basics.AppCreatable, false /* don't sync */) + return ledgercore.AppResource{AppParams: r.AppParams, AppLocalState: r.AppLocalState}, err +} + +func (aul *accountUpdatesLedgerEvaluator) LookupAsset(rnd basics.Round, addr basics.Address, aidx basics.AssetIndex) (ledgercore.AssetResource, error) { + r, _, err := aul.au.lookupResource(rnd, addr, basics.CreatableIndex(aidx), basics.AssetCreatable, false /* don't sync */) + return ledgercore.AssetResource{AssetParams: r.AssetParams, AssetHolding: r.AssetHolding}, err +} + +func (aul *accountUpdatesLedgerEvaluator) LookupKv(rnd basics.Round, key string) ([]byte, error) { + return aul.au.lookupKv(rnd, key, false /* don't sync */) +} + +// GetCreatorForRound returns the asset/app creator for a given asset/app index at a given round +func (aul *accountUpdatesLedgerEvaluator) GetCreatorForRound(rnd basics.Round, cidx basics.CreatableIndex, ctype basics.CreatableType) (creator basics.Address, ok bool, err error) { + return aul.au.getCreatorForRound(rnd, cidx, ctype, false /* don't sync */) +} diff --git a/ledger/tracker_test.go b/ledger/tracker_test.go index 09c7401b87..5ab2f63c99 100644 --- a/ledger/tracker_test.go +++ b/ledger/tracker_test.go @@ -301,3 +301,16 @@ func TestTrackerDbRoundDataRace(t *testing.T) { stallingTracker.cancelTasks = true close(stallingTracker.produceReleaseLock) } + +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) +} From 4dc3f92701d33f3a6ef9546e18c04e6f9b2e6b1c Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Tue, 8 Aug 2023 18:46:07 -0400 Subject: [PATCH 31/66] build: slightly more readable "make" output (#5646) --- Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9afd53a357..760dd42084 100644 --- a/Makefile +++ b/Makefile @@ -212,9 +212,11 @@ build: buildsrc buildsrc-special # get around a bug in go build where it will fail # to cache binaries from time to time on empty NFS # dirs -buildsrc: check-go-version crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a node_exporter NONGO_BIN - mkdir -p "${GOCACHE}" && \ - touch "${GOCACHE}"/file.txt && \ +${GOCACHE}/file.txt: + mkdir -p "${GOCACHE}" + touch "${GOCACHE}"/file.txt + +buildsrc: check-go-version crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a node_exporter NONGO_BIN ${GOCACHE}/file.txt go install $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... buildsrc-special: From 867aa176336bbe6850f8ae99f07792e40ee68c03 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:42:05 -0400 Subject: [PATCH 32/66] algod: Add API Endpoint to fetch TxIDs from block (#5611) Co-authored-by: Zeph Grunschlag --- daemon/algod/api/algod.oas2.json | 76 +++ daemon/algod/api/algod.oas3.yml | 112 ++++ .../api/server/v2/generated/data/routes.go | 309 +++++----- .../v2/generated/experimental/routes.go | 366 ++++++------ .../api/server/v2/generated/model/types.go | 6 + .../nonparticipating/private/routes.go | 369 ++++++------ .../nonparticipating/public/routes.go | 539 +++++++++--------- .../generated/participating/private/routes.go | 371 ++++++------ .../generated/participating/public/routes.go | 335 +++++------ daemon/algod/api/server/v2/handlers.go | 35 +- .../algod/api/server/v2/test/handlers_test.go | 32 ++ test/scripts/e2e_subs/e2e-logs.sh | 13 +- 12 files changed, 1428 insertions(+), 1135 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index ccac08f51c..1d19f676ee 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -578,6 +578,64 @@ } ] }, + "/v2/blocks/{round}/txids": { + "get": { + "tags": [ + "public", + "nonparticipating" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "summary": "Get the top level transaction IDs for the block on the given round.", + "operationId": "GetBlockTxids", + "parameters": [ + { + "minimum": 0, + "type": "integer", + "description": "The round from which to fetch block transaction IDs.", + "name": "round", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/BlockTxidsResponse" + }, + "400": { + "description": "Bad Request - Non integer number", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "404": { + "description": "Non existing block", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + } + }, "/v2/blocks/{round}/hash": { "get": { "tags": [ @@ -4350,6 +4408,24 @@ } } }, + "BlockTxidsResponse": { + "description": "Top level transaction IDs in a block.", + "schema": { + "type": "object", + "required": [ + "blockTxids" + ], + "properties": { + "blockTxids": { + "description": "Block transaction IDs.", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, "BlockHashResponse": { "description": "Hash of a block header.", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 7c68a95ba5..26aa89e477 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -350,6 +350,28 @@ }, "description": "Encoded block object." }, + "BlockTxidsResponse": { + "content": { + "application/json": { + "schema": { + "properties": { + "blockTxids": { + "description": "Block transaction IDs.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "blockTxids" + ], + "type": "object" + } + } + }, + "description": "Top level transaction IDs in a block." + }, "BoxResponse": { "content": { "application/json": { @@ -3817,6 +3839,96 @@ ] } }, + "/v2/blocks/{round}/txids": { + "get": { + "operationId": "GetBlockTxids", + "parameters": [ + { + "description": "The round from which to fetch block transaction IDs.", + "in": "path", + "name": "round", + "required": true, + "schema": { + "minimum": 0, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "blockTxids": { + "description": "Block transaction IDs.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "blockTxids" + ], + "type": "object" + } + } + }, + "description": "Top level transaction IDs in a block." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Bad Request - Non integer number" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Non existing block" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Get the top level transaction IDs for the block on the given round.", + "tags": [ + "public", + "nonparticipating" + ] + } + }, "/v2/catchup/{catchpoint}": { "delete": { "description": "Given a catchpoint, it aborts catching up to this catchpoint", diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index e61ec6d674..0732a0ad39 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -143,160 +143,161 @@ var swaggerSpec = []string{ "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", + "jXnB3n34fwEAAP//3kEf9pfZAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index ec504b55e3..e0002e7864 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -90,190 +90,190 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/94hkwcT0wAA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index ae20c28961..27df588342 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -954,6 +954,12 @@ type BlockResponse struct { Cert *map[string]interface{} `json:"cert,omitempty"` } +// BlockTxidsResponse defines model for BlockTxidsResponse. +type BlockTxidsResponse struct { + // BlockTxids Block transaction IDs. + BlockTxids []string `json:"blockTxids"` +} + // BoxResponse Box name and its content. type BoxResponse = Box diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 43a971be7f..61b67806f7 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -130,190 +130,191 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "BYUfP9Qb7ZOY1xt+++H2/wUAAP//q8e4jZPaAAA=", } // 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 88d2296319..c5544d410c 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -54,6 +54,9 @@ type ServerInterface interface { // Get a proof for a transaction in a block. // (GET /v2/blocks/{round}/transactions/{txid}/proof) GetTransactionProof(ctx echo.Context, round uint64, txid string, params GetTransactionProofParams) error + // Get the top level transaction IDs for the block on the given round. + // (GET /v2/blocks/{round}/txids) + GetBlockTxids(ctx echo.Context, round uint64) error // Get a LedgerStateDelta object for a given transaction group // (GET /v2/deltas/txn/group/{id}) GetLedgerStateDeltaForTransactionGroup(ctx echo.Context, id string, params GetLedgerStateDeltaForTransactionGroupParams) error @@ -402,6 +405,24 @@ func (w *ServerInterfaceWrapper) GetTransactionProof(ctx echo.Context) error { return err } +// GetBlockTxids converts echo context to params. +func (w *ServerInterfaceWrapper) GetBlockTxids(ctx echo.Context) error { + var err error + // ------------- Path parameter "round" ------------- + var round uint64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "round", runtime.ParamLocationPath, ctx.Param("round"), &round) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter round: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetBlockTxids(ctx, round) + return err +} + // GetLedgerStateDeltaForTransactionGroup converts echo context to params. func (w *ServerInterfaceWrapper) GetLedgerStateDeltaForTransactionGroup(ctx echo.Context) error { var err error @@ -682,6 +703,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/v2/blocks/:round/hash", wrapper.GetBlockHash, m...) router.GET(baseURL+"/v2/blocks/:round/lightheader/proof", wrapper.GetLightBlockHeaderProof, m...) router.GET(baseURL+"/v2/blocks/:round/transactions/:txid/proof", wrapper.GetTransactionProof, m...) + router.GET(baseURL+"/v2/blocks/:round/txids", wrapper.GetBlockTxids, m...) router.GET(baseURL+"/v2/deltas/txn/group/:id", wrapper.GetLedgerStateDeltaForTransactionGroup, m...) router.GET(baseURL+"/v2/deltas/:round", wrapper.GetLedgerStateDelta, m...) router.GET(baseURL+"/v2/deltas/:round/txn/group", wrapper.GetTransactionGroupLedgerStateDeltasForRound, m...) @@ -702,264 +724,265 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "2n8h2cpYNB/effj/AwAA///DhipwDYMBAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 42821accca..5aec05ef28 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -159,191 +159,192 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index c23a84bffe..b649bd8897 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -205,173 +205,174 @@ var swaggerSpec = []string{ "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", + "UzJGJbryAAA=", } // 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 ff7edac6f3..563990166e 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -630,7 +630,7 @@ func (v2 *Handlers) GetBlock(ctx echo.Context, round uint64, params model.GetBlo } ledger := v2.Node.LedgerForAPI() - block, _, err := ledger.BlockCert(basics.Round(round)) + block, err := ledger.Block(basics.Round(round)) if err != nil { switch err.(type) { case ledgercore.ErrNoEntry: @@ -655,11 +655,40 @@ func (v2 *Handlers) GetBlock(ctx echo.Context, round uint64, params model.GetBlo return ctx.Blob(http.StatusOK, contentType, data) } +// GetBlockTxids gets all top level TxIDs in a block for the given round. +// (GET /v2/blocks/{round}/txids) +func (v2 *Handlers) GetBlockTxids(ctx echo.Context, round uint64) error { + ledger := v2.Node.LedgerForAPI() + block, err := ledger.Block(basics.Round(round)) + if err != nil { + switch err.(type) { + case ledgercore.ErrNoEntry: + return notFound(ctx, err, errFailedLookingUpLedger, v2.Log) + default: + return internalError(ctx, err, errFailedLookingUpLedger, v2.Log) + } + } + + txns, err := block.DecodePaysetFlat() + if err != nil { + return internalError(ctx, err, "decoding transactions", v2.Log) + } + + txids := make([]string, 0, len(txns)) + for ids := range txns { + txids = append(txids, txns[ids].ID().String()) + } + + response := model.BlockTxidsResponse{BlockTxids: txids} + + return ctx.JSON(http.StatusOK, response) +} + // GetBlockHash gets the block hash for the given round. // (GET /v2/blocks/{round}/hash) func (v2 *Handlers) GetBlockHash(ctx echo.Context, round uint64) error { ledger := v2.Node.LedgerForAPI() - block, _, err := ledger.BlockCert(basics.Round(round)) + block, err := ledger.Block(basics.Round(round)) if err != nil { switch err.(type) { case ledgercore.ErrNoEntry: @@ -688,7 +717,7 @@ func (v2 *Handlers) GetTransactionProof(ctx echo.Context, round uint64, txid str } ledger := v2.Node.LedgerForAPI() - block, _, err := ledger.BlockCert(basics.Round(round)) + block, err := ledger.Block(basics.Round(round)) if err != nil { return internalError(ctx, err, errFailedLookingUpLedger, v2.Log) } diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 2a690e9840..d59d9c971c 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -342,6 +342,38 @@ func addBlockHelper(t *testing.T) (v2.Handlers, echo.Context, *httptest.Response return handler, c, rec, stx, releasefunc } +func TestGetBlockTxids(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + handler, c, rec, stx, releasefunc := addBlockHelper(t) + defer releasefunc() + + var response model.BlockTxidsResponse + err := handler.GetBlockTxids(c, 0) + require.NoError(t, err) + require.Equal(t, 200, rec.Code) + data := rec.Body.Bytes() + err = protocol.DecodeJSON(data, &response) + require.NoError(t, err) + require.Equal(t, 0, len(response.BlockTxids)) + + c, rec = newReq(t) + err = handler.GetBlockTxids(c, 2) + require.NoError(t, err) + require.Equal(t, 404, rec.Code) + + c, rec = newReq(t) + err = handler.GetBlockTxids(c, 1) + require.NoError(t, err) + require.Equal(t, 200, rec.Code) + data = rec.Body.Bytes() + err = protocol.DecodeJSON(data, &response) + require.NoError(t, err) + require.Equal(t, 1, len(response.BlockTxids)) + require.Equal(t, stx.ID().String(), response.BlockTxids[0]) +} + func TestGetBlockHash(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() diff --git a/test/scripts/e2e_subs/e2e-logs.sh b/test/scripts/e2e_subs/e2e-logs.sh index 7d40a62e1f..67df088f46 100755 --- a/test/scripts/e2e_subs/e2e-logs.sh +++ b/test/scripts/e2e_subs/e2e-logs.sh @@ -17,6 +17,11 @@ function app_txid { grep -o -E 'txid [A-Z0-9]{52}' | cut -c 6- | head -1 } +function commit_round { + # Get the round that the app transaction was confirmed + grep -o -E 'committed in round [0-9]+' | cut -d ' ' -f 4 +} + set -e set -x set -o pipefail @@ -32,7 +37,13 @@ ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') EXP=(B C D E F G H I J K L M N O P Q R S T U V W X Y Z \[ \\ \] ^ _ \` a b ) # app create -TXID=$(${gcmd} app create --creator "${ACCOUNT}" --approval-prog=${TEAL}/logs.teal --global-byteslices 4 --global-ints 0 --local-byteslices 0 --local-ints 1 --clear-prog=${TEAL}/approve-all.teal | app_txid) +response=$(${gcmd} app create --creator "${ACCOUNT}" --approval-prog=${TEAL}/logs.teal --global-byteslices 4 --global-ints 0 --local-byteslices 0 --local-ints 1 --clear-prog=${TEAL}/approve-all.teal) +TXID=$(echo "$response" | app_txid) +ROUND=$(echo "$response" | commit_round) + +# check that txid was confirmed +call_and_verify "Should contain TxID." "/v2/blocks/$ROUND/txids" 200 $TXID + response=$(rest "/v2/transactions/pending/$TXID") # log len [ "$(echo "$response" | jq '.logs | length')" = 32 ] From 208335fbd82b185f2fd25bde876d35e6c83aa87c Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 9 Aug 2023 16:05:35 -0400 Subject: [PATCH 33/66] tools: precompile block-generator teal code (#5642) --- tools/block-generator/generator/daemon.go | 4 +- tools/block-generator/generator/generate.go | 38 ++++++++++++++++++- .../generator/make_transactions.go | 6 +-- tools/block-generator/generator/server.go | 4 +- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/tools/block-generator/generator/daemon.go b/tools/block-generator/generator/daemon.go index 36c49cac51..fb4f52bab3 100644 --- a/tools/block-generator/generator/daemon.go +++ b/tools/block-generator/generator/daemon.go @@ -31,13 +31,14 @@ func init() { var configFile string var port uint64 + var verbose bool DaemonCmd = &cobra.Command{ Use: "daemon", Short: "Start the generator daemon in standalone mode.", Run: func(cmd *cobra.Command, args []string) { addr := fmt.Sprintf(":%d", port) - srv, _ := MakeServer(configFile, addr) + srv, _ := MakeServer(configFile, addr, verbose) err := srv.ListenAndServe() if err != nil { panic(err) @@ -47,6 +48,7 @@ func init() { DaemonCmd.Flags().StringVarP(&configFile, "config", "c", "", "Specify the block configuration yaml file.") DaemonCmd.Flags().Uint64VarP(&port, "port", "p", 4010, "Port to start the server at.") + DaemonCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "If set the runner will print debugging information from the generator and ledger.") DaemonCmd.MarkFlagRequired("config") } diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index 3a899a33b4..22246e047b 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -32,25 +32,49 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" txn "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/rpcs" + "github.com/algorand/go-algorand/tools/block-generator/util" ) // ---- templates ---- //go:embed teal/poap_boxes.teal var approvalBoxes string +var approvalBoxesBytes interface{} //go:embed teal/poap_clear.teal var clearBoxes string +var clearBoxesBytes interface{} //go:embed teal/swap_amm.teal var approvalSwap string +var approvalSwapBytes interface{} //go:embed teal/swap_clear.teal var clearSwap string +var clearSwapBytes interface{} + +func init() { + prog, err := logic.AssembleString(approvalBoxes) + util.MaybeFail(err, "failed to assemble approval program") + approvalBoxesBytes = prog.Program + + prog, err = logic.AssembleString(clearBoxes) + util.MaybeFail(err, "failed to assemble clear program") + clearBoxesBytes = prog.Program + + prog, err = logic.AssembleString(approvalSwap) + util.MaybeFail(err, "failed to assemble approvalSwap program") + approvalSwapBytes = prog.Program + + prog, err = logic.AssembleString(clearSwap) + util.MaybeFail(err, "failed to assemble clearSwap program") + clearSwapBytes = prog.Program +} // ---- constructors ---- @@ -305,8 +329,8 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { } return nil } - // round == nextRound case + // round == nextRound case err := g.startRound() if err != nil { return err @@ -321,6 +345,15 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { // we'll write genesis block / offset round for non-empty database cert.Block, _, _ = g.ledger.BlockCert(basics.Round(round - g.roundOffset)) } else { + start := time.Now() + var generated, evaluated, validated time.Time + if g.verbose { + defer func() { + fmt.Printf("block generation stats txn generation (%s), ledger eval (%s), ledger add block (%s)\n", + generated.Sub(start), evaluated.Sub(generated), validated.Sub(evaluated)) + }() + } + g.setBlockHeader(&cert) intra := uint64(0) @@ -337,6 +370,7 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { intra += numTxns } + generated = time.Now() vBlock, ledgerTxnCount, err := g.evaluateBlock(cert.Block.BlockHeader, txGroupsAD, int(intra)) if err != nil { @@ -345,11 +379,13 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { if ledgerTxnCount != g.txnCounter+intra { return fmt.Errorf("evaluateBlock() txn count mismatches theoretical intra: %d != %d", ledgerTxnCount, g.txnCounter+intra) } + evaluated = time.Now() err = g.ledger.AddValidatedBlock(*vBlock, cert.Certificate) if err != nil { return fmt.Errorf("failed to add validated block: %w", err) } + validated = time.Now() cert.Block.Payset = vBlock.Block().Payset diff --git a/tools/block-generator/generator/make_transactions.go b/tools/block-generator/generator/make_transactions.go index e733d4339b..6caffe25b7 100644 --- a/tools/block-generator/generator/make_transactions.go +++ b/tools/block-generator/generator/make_transactions.go @@ -123,11 +123,11 @@ func (g *generator) makeAssetAcceptanceTxn(header txn.Header, index uint64) txn. // ---- application transactions ---- func (g *generator) makeAppCreateTxn(kind appKind, sender basics.Address, round, intra uint64, futureAppId uint64) []txn.SignedTxn { - var approval, clear string + var approval, clear interface{} if kind == appKindSwap { - approval, clear = approvalSwap, clearSwap + approval, clear = approvalSwapBytes, clearSwapBytes } else { - approval, clear = approvalBoxes, clearBoxes + approval, clear = approvalBoxesBytes, clearBoxesBytes } createTxn := g.makeTestTxn(sender, round, intra) diff --git a/tools/block-generator/generator/server.go b/tools/block-generator/generator/server.go index 4de0d08322..edfe470f3d 100644 --- a/tools/block-generator/generator/server.go +++ b/tools/block-generator/generator/server.go @@ -29,11 +29,11 @@ import ( ) // MakeServer configures http handlers. Returns the http server. -func MakeServer(configFile string, addr string) (*http.Server, Generator) { +func MakeServer(configFile string, addr string, verbose bool) (*http.Server, Generator) { noOp := func(next http.Handler) http.Handler { return next } - return MakeServerWithMiddleware(nil, 0, "", configFile, false, addr, noOp) + return MakeServerWithMiddleware(nil, 0, "", configFile, verbose, addr, noOp) } // BlocksMiddleware is a middleware for the blocks endpoint. From 1088a2aad7e6452f054fe834b5f43ff016953374 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 9 Aug 2023 13:05:47 -0700 Subject: [PATCH 34/66] Simulate: Allow unnamed foreign resource access (#5366) --- cmd/goal/clerk.go | 28 +- daemon/algod/api/algod.oas2.json | 130 +- daemon/algod/api/algod.oas3.yml | 131 +- .../api/server/v2/generated/data/routes.go | 378 ++-- .../v2/generated/experimental/routes.go | 378 ++-- .../api/server/v2/generated/model/types.go | 65 +- .../nonparticipating/private/routes.go | 380 ++-- .../nonparticipating/public/routes.go | 529 ++--- .../generated/participating/private/routes.go | 384 ++-- .../generated/participating/public/routes.go | 402 ++-- daemon/algod/api/server/v2/handlers.go | 31 +- daemon/algod/api/server/v2/utils.go | 94 +- data/basics/overflow.go | 8 + data/transactions/logic/box.go | 54 +- data/transactions/logic/eval.go | 97 +- data/transactions/logic/evalStateful_test.go | 620 +++-- data/transactions/logic/eval_test.go | 1 + data/transactions/logic/opcodes.go | 11 +- data/transactions/logic/resources.go | 32 +- ledger/simulation/resources.go | 607 +++++ ledger/simulation/resources_test.go | 298 +++ ledger/simulation/simulation_eval_test.go | 1996 +++++++++++++++++ ledger/simulation/simulator.go | 27 +- ledger/simulation/testing/utils.go | 165 +- ledger/simulation/trace.go | 31 +- ledger/simulation/tracer.go | 64 +- test/e2e-go/restAPI/restClient_test.go | 267 ++- test/scripts/e2e_subs/e2e-app-simulate.sh | 54 + .../tealprogs/unnamed-resource-access.teal | 60 + 29 files changed, 5843 insertions(+), 1479 deletions(-) create mode 100644 ledger/simulation/resources.go create mode 100644 ledger/simulation/resources_test.go create mode 100644 test/scripts/e2e_subs/tealprogs/unnamed-resource-access.teal diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 80689635bc..b7e2a0f414 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -74,10 +74,11 @@ var ( simulateAllowMoreOpcodeBudget bool simulateExtraOpcodeBudget uint64 - simulateFullTrace bool - simulateEnableRequestTrace bool - simulateStackChange bool - simulateScratchChange bool + simulateFullTrace bool + simulateEnableRequestTrace bool + simulateStackChange bool + simulateScratchChange bool + simulateAllowUnnamedResources bool ) func init() { @@ -171,6 +172,7 @@ func init() { simulateCmd.Flags().BoolVar(&simulateEnableRequestTrace, "trace", false, "Enable simulation time execution trace of app calls") simulateCmd.Flags().BoolVar(&simulateStackChange, "stack", false, "Report stack change during simulation time") simulateCmd.Flags().BoolVar(&simulateScratchChange, "scratch", false, "Report scratch change during simulation time") + simulateCmd.Flags().BoolVar(&simulateAllowUnnamedResources, "allow-unnamed-resources", false, "Allow access to unnamed resources during simulation") } var clerkCmd = &cobra.Command{ @@ -1281,10 +1283,11 @@ var simulateCmd = &cobra.Command{ Txns: txgroup, }, }, - AllowEmptySignatures: simulateAllowEmptySignatures, - AllowMoreLogging: simulateAllowMoreLogging, - ExtraOpcodeBudget: simulateExtraOpcodeBudget, - ExecTraceConfig: traceCmdOptionToSimulateTraceConfigModel(), + AllowEmptySignatures: simulateAllowEmptySignatures, + AllowMoreLogging: simulateAllowMoreLogging, + AllowUnnamedResources: simulateAllowUnnamedResources, + ExtraOpcodeBudget: simulateExtraOpcodeBudget, + ExecTraceConfig: traceCmdOptionToSimulateTraceConfigModel(), } err := writeFile(requestOutFilename, protocol.EncodeJSON(simulateRequest), 0600) if err != nil { @@ -1305,10 +1308,11 @@ var simulateCmd = &cobra.Command{ Txns: txgroup, }, }, - AllowEmptySignatures: simulateAllowEmptySignatures, - AllowMoreLogging: simulateAllowMoreLogging, - ExtraOpcodeBudget: simulateExtraOpcodeBudget, - ExecTraceConfig: traceCmdOptionToSimulateTraceConfigModel(), + AllowEmptySignatures: simulateAllowEmptySignatures, + AllowMoreLogging: simulateAllowMoreLogging, + AllowUnnamedResources: simulateAllowUnnamedResources, + ExtraOpcodeBudget: simulateExtraOpcodeBudget, + ExecTraceConfig: traceCmdOptionToSimulateTraceConfigModel(), } simulateResponse, responseErr = client.SimulateTransactions(simulateRequest) } else { diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 1d19f676ee..e4c8f8bbe8 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -3093,6 +3093,46 @@ } } }, + "AssetHoldingReference": { + "description": "References an asset held by an account.", + "type": "object", + "required": [ + "account", + "asset" + ], + "properties": { + "account": { + "description": "Address of the account holding the asset.", + "type": "string", + "x-algorand-format": "Address" + }, + "asset": { + "description": "Asset ID of the holding.", + "type": "integer", + "x-algorand-format": "uint64" + } + } + }, + "ApplicationLocalReference": { + "description": "References an account's local state for an application.", + "type": "object", + "required": [ + "account", + "app" + ], + "properties": { + "account": { + "description": "Address of the account with the local state.", + "type": "string", + "x-algorand-format": "Address" + }, + "app": { + "description": "Application ID of the local state application.", + "type": "integer", + "x-algorand-format": "uint64" + } + } + }, "ApplicationStateSchema": { "description": "Specifies maximums on the number of each type that may be stored.", "type": "object", @@ -3590,13 +3630,17 @@ } }, "allow-empty-signatures": { - "description": "Allow transactions without signatures to be simulated as if they had correct signatures.", + "description": "Allows transactions without signatures to be simulated as if they had correct signatures.", "type": "boolean" }, "allow-more-logging": { "description": "Lifts limits on log opcode usage during simulation.", "type": "boolean" }, + "allow-unnamed-resources": { + "description": "Allows access to unnamed resources during simulation.", + "type": "boolean" + }, "extra-opcode-budget": { "description": "Applies extra opcode budget during simulation for each transaction group.", "type": "integer" @@ -3682,6 +3726,25 @@ } } }, + "BoxReference": { + "description": "References a box of an application.", + "type": "object", + "required": [ + "app", + "name" + ], + "properties": { + "app": { + "description": "Application ID which this box belongs to", + "type": "integer" + }, + "name": { + "description": "Base64 encoded box name", + "type": "string", + "format": "byte" + } + } + }, "KvDelta": { "description": "A single Delta containing the key, the previous value and the current value for a single round.", "type": "object", @@ -3874,6 +3937,9 @@ "app-budget-consumed": { "description": "Total budget consumed during execution of app calls in the transaction group.", "type": "integer" + }, + "unnamed-resources-accessed": { + "$ref": "#/definitions/SimulateUnnamedResourcesAccessed" } } }, @@ -3897,6 +3963,9 @@ }, "exec-trace": { "$ref": "#/definitions/SimulationTransactionExecTrace" + }, + "unnamed-resources-accessed": { + "$ref": "#/definitions/SimulateUnnamedResourcesAccessed" } } }, @@ -3988,6 +4057,10 @@ "description": "If true, transactions without signatures are allowed and simulated as if they were properly signed.", "type": "boolean" }, + "allow-unnamed-resources": { + "description": "If true, allows access to unnamed resources during simulation.", + "type": "boolean" + }, "max-log-calls": { "description": "The maximum log calls one can make during simulation", "type": "integer" @@ -4090,6 +4163,61 @@ } } } + }, + "SimulateUnnamedResourcesAccessed": { + "description": "These are resources that were accessed by this group that would normally have caused failure, but were allowed in simulation. Depending on where this object is in the response, the unnamed resources it contains may or may not qualify for group resource sharing. If this is a field in SimulateTransactionGroupResult, the resources do qualify, but if this is a field in SimulateTransactionResult, they do not qualify. In order to make this group valid for actual submission, resources that qualify for group sharing can be made available by any transaction of the group; otherwise, resources must be placed in the same transaction which accessed them.", + "type": "object", + "properties": { + "accounts": { + "description": "The unnamed accounts that were referenced. The order of this array is arbitrary.", + "type": "array", + "items": { + "type": "string", + "x-algorand-format": "Address" + } + }, + "assets": { + "description": "The unnamed assets that were referenced. The order of this array is arbitrary.", + "type": "array", + "items": { + "type": "integer", + "x-algorand-format": "uint64" + } + }, + "apps": { + "description": "The unnamed applications that were referenced. The order of this array is arbitrary.", + "type": "array", + "items": { + "type": "integer", + "x-algorand-format": "uint64" + } + }, + "boxes": { + "description": "The unnamed boxes that were referenced. The order of this array is arbitrary.", + "type": "array", + "items": { + "$ref": "#/definitions/BoxReference" + } + }, + "extra-box-refs": { + "description": "The number of extra box references used to increase the IO budget. This is in addition to the references defined in the input transaction group and any referenced to unnamed boxes.", + "type": "integer" + }, + "asset-holdings": { + "description": "The unnamed asset holdings that were referenced. The order of this array is arbitrary.", + "type": "array", + "items": { + "$ref": "#/definitions/AssetHoldingReference" + } + }, + "app-locals": { + "description": "The unnamed application local states that were referenced. The order of this array is arbitrary.", + "type": "array", + "items": { + "$ref": "#/definitions/ApplicationLocalReference" + } + } + } } }, "parameters": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 26aa89e477..01183b62b9 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -1217,6 +1217,26 @@ ], "type": "object" }, + "ApplicationLocalReference": { + "description": "References an account's local state for an application.", + "properties": { + "account": { + "description": "Address of the account with the local state.", + "type": "string", + "x-algorand-format": "Address" + }, + "app": { + "description": "Application ID of the local state application.", + "type": "integer", + "x-algorand-format": "uint64" + } + }, + "required": [ + "account", + "app" + ], + "type": "object" + }, "ApplicationLocalState": { "description": "Stores local state associated with an application.", "properties": { @@ -1340,6 +1360,26 @@ ], "type": "object" }, + "AssetHoldingReference": { + "description": "References an asset held by an account.", + "properties": { + "account": { + "description": "Address of the account holding the asset.", + "type": "string", + "x-algorand-format": "Address" + }, + "asset": { + "description": "Asset ID of the holding.", + "type": "integer", + "x-algorand-format": "uint64" + } + }, + "required": [ + "account", + "asset" + ], + "type": "object" + }, "AssetParams": { "description": "AssetParams specifies the parameters for an asset.\n\n\\[apar\\] when part of an AssetConfig transaction.\n\nDefinition:\ndata/transactions/asset.go : AssetParams", "properties": { @@ -1488,6 +1528,26 @@ ], "type": "object" }, + "BoxReference": { + "description": "References a box of an application.", + "properties": { + "app": { + "description": "Application ID which this box belongs to", + "type": "integer" + }, + "name": { + "description": "Base64 encoded box name", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + } + }, + "required": [ + "app", + "name" + ], + "type": "object" + }, "BuildVersion": { "properties": { "branch": { @@ -1977,13 +2037,17 @@ "description": "Request type for simulation endpoint.", "properties": { "allow-empty-signatures": { - "description": "Allow transactions without signatures to be simulated as if they had correct signatures.", + "description": "Allows transactions without signatures to be simulated as if they had correct signatures.", "type": "boolean" }, "allow-more-logging": { "description": "Lifts limits on log opcode usage during simulation.", "type": "boolean" }, + "allow-unnamed-resources": { + "description": "Allows access to unnamed resources during simulation.", + "type": "boolean" + }, "exec-trace-config": { "$ref": "#/components/schemas/SimulateTraceConfig" }, @@ -2069,6 +2133,9 @@ "$ref": "#/components/schemas/SimulateTransactionResult" }, "type": "array" + }, + "unnamed-resources-accessed": { + "$ref": "#/components/schemas/SimulateUnnamedResourcesAccessed" } }, "required": [ @@ -2092,6 +2159,9 @@ }, "txn-result": { "$ref": "#/components/schemas/PendingTransactionResponse" + }, + "unnamed-resources-accessed": { + "$ref": "#/components/schemas/SimulateUnnamedResourcesAccessed" } }, "required": [ @@ -2099,6 +2169,61 @@ ], "type": "object" }, + "SimulateUnnamedResourcesAccessed": { + "description": "These are resources that were accessed by this group that would normally have caused failure, but were allowed in simulation. Depending on where this object is in the response, the unnamed resources it contains may or may not qualify for group resource sharing. If this is a field in SimulateTransactionGroupResult, the resources do qualify, but if this is a field in SimulateTransactionResult, they do not qualify. In order to make this group valid for actual submission, resources that qualify for group sharing can be made available by any transaction of the group; otherwise, resources must be placed in the same transaction which accessed them.", + "properties": { + "accounts": { + "description": "The unnamed accounts that were referenced. The order of this array is arbitrary.", + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "app-locals": { + "description": "The unnamed application local states that were referenced. The order of this array is arbitrary.", + "items": { + "$ref": "#/components/schemas/ApplicationLocalReference" + }, + "type": "array" + }, + "apps": { + "description": "The unnamed applications that were referenced. The order of this array is arbitrary.", + "items": { + "type": "integer", + "x-algorand-format": "uint64" + }, + "type": "array" + }, + "asset-holdings": { + "description": "The unnamed asset holdings that were referenced. The order of this array is arbitrary.", + "items": { + "$ref": "#/components/schemas/AssetHoldingReference" + }, + "type": "array" + }, + "assets": { + "description": "The unnamed assets that were referenced. The order of this array is arbitrary.", + "items": { + "type": "integer", + "x-algorand-format": "uint64" + }, + "type": "array" + }, + "boxes": { + "description": "The unnamed boxes that were referenced. The order of this array is arbitrary.", + "items": { + "$ref": "#/components/schemas/BoxReference" + }, + "type": "array" + }, + "extra-box-refs": { + "description": "The number of extra box references used to increase the IO budget. This is in addition to the references defined in the input transaction group and any referenced to unnamed boxes.", + "type": "integer" + } + }, + "type": "object" + }, "SimulationEvalOverrides": { "description": "The set of parameters and limits override during simulation. If this set of parameters is present, then evaluation parameters may differ from standard evaluation in certain ways.", "properties": { @@ -2106,6 +2231,10 @@ "description": "If true, transactions without signatures are allowed and simulated as if they were properly signed.", "type": "boolean" }, + "allow-unnamed-resources": { + "description": "If true, allows access to unnamed resources during simulation.", + "type": "boolean" + }, "extra-opcode-budget": { "description": "The extra opcode budget added to each transaction group during simulation", "type": "integer" diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 0732a0ad39..d7429328dc 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -114,190 +114,200 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "jXnB3n34fwEAAP//3kEf9pfZAAA=", + "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", + "dx/+bwAAAP//oCDbo8rnAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index e0002e7864..8efeb17bb2 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -90,190 +90,200 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index 27df588342..68249b1c67 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -280,6 +280,15 @@ type Application struct { Params ApplicationParams `json:"params"` } +// ApplicationLocalReference References an account's local state for an application. +type ApplicationLocalReference struct { + // Account Address of the account with the local state. + Account string `json:"account"` + + // App Application ID of the local state application. + App uint64 `json:"app"` +} + // ApplicationLocalState Stores local state associated with an application. type ApplicationLocalState struct { // Id The application which this local state is for. @@ -354,6 +363,15 @@ type AssetHolding struct { IsFrozen bool `json:"is-frozen"` } +// AssetHoldingReference References an asset held by an account. +type AssetHoldingReference struct { + // Account Address of the account holding the asset. + Account string `json:"account"` + + // Asset Asset ID of the holding. + Asset uint64 `json:"asset"` +} + // AssetParams AssetParams specifies the parameters for an asset. // // \[apar\] when part of an AssetConfig transaction. @@ -437,6 +455,15 @@ type BoxDescriptor struct { Name []byte `json:"name"` } +// BoxReference References a box of an application. +type BoxReference struct { + // App Application ID which this box belongs to + App uint64 `json:"app"` + + // Name Base64 encoded box name + Name []byte `json:"name"` +} + // BuildVersion defines model for BuildVersion. type BuildVersion struct { Branch string `json:"branch"` @@ -653,12 +680,15 @@ type ScratchChange struct { // SimulateRequest Request type for simulation endpoint. type SimulateRequest struct { - // AllowEmptySignatures Allow transactions without signatures to be simulated as if they had correct signatures. + // AllowEmptySignatures Allows transactions without signatures to be simulated as if they had correct signatures. AllowEmptySignatures *bool `json:"allow-empty-signatures,omitempty"` // AllowMoreLogging Lifts limits on log opcode usage during simulation. AllowMoreLogging *bool `json:"allow-more-logging,omitempty"` + // AllowUnnamedResources Allows access to unnamed resources during simulation. + AllowUnnamedResources *bool `json:"allow-unnamed-resources,omitempty"` + // ExecTraceConfig An object that configures simulation execution trace. ExecTraceConfig *SimulateTraceConfig `json:"exec-trace-config,omitempty"` @@ -703,6 +733,9 @@ type SimulateTransactionGroupResult struct { // TxnResults Simulation result for individual transactions TxnResults []SimulateTransactionResult `json:"txn-results"` + + // UnnamedResourcesAccessed These are resources that were accessed by this group that would normally have caused failure, but were allowed in simulation. Depending on where this object is in the response, the unnamed resources it contains may or may not qualify for group resource sharing. If this is a field in SimulateTransactionGroupResult, the resources do qualify, but if this is a field in SimulateTransactionResult, they do not qualify. In order to make this group valid for actual submission, resources that qualify for group sharing can be made available by any transaction of the group; otherwise, resources must be placed in the same transaction which accessed them. + UnnamedResourcesAccessed *SimulateUnnamedResourcesAccessed `json:"unnamed-resources-accessed,omitempty"` } // SimulateTransactionResult Simulation result for an individual transaction @@ -718,6 +751,33 @@ type SimulateTransactionResult struct { // TxnResult Details about a pending transaction. If the transaction was recently confirmed, includes confirmation details like the round and reward details. TxnResult PendingTransactionResponse `json:"txn-result"` + + // UnnamedResourcesAccessed These are resources that were accessed by this group that would normally have caused failure, but were allowed in simulation. Depending on where this object is in the response, the unnamed resources it contains may or may not qualify for group resource sharing. If this is a field in SimulateTransactionGroupResult, the resources do qualify, but if this is a field in SimulateTransactionResult, they do not qualify. In order to make this group valid for actual submission, resources that qualify for group sharing can be made available by any transaction of the group; otherwise, resources must be placed in the same transaction which accessed them. + UnnamedResourcesAccessed *SimulateUnnamedResourcesAccessed `json:"unnamed-resources-accessed,omitempty"` +} + +// SimulateUnnamedResourcesAccessed These are resources that were accessed by this group that would normally have caused failure, but were allowed in simulation. Depending on where this object is in the response, the unnamed resources it contains may or may not qualify for group resource sharing. If this is a field in SimulateTransactionGroupResult, the resources do qualify, but if this is a field in SimulateTransactionResult, they do not qualify. In order to make this group valid for actual submission, resources that qualify for group sharing can be made available by any transaction of the group; otherwise, resources must be placed in the same transaction which accessed them. +type SimulateUnnamedResourcesAccessed struct { + // Accounts The unnamed accounts that were referenced. The order of this array is arbitrary. + Accounts *[]string `json:"accounts,omitempty"` + + // AppLocals The unnamed application local states that were referenced. The order of this array is arbitrary. + AppLocals *[]ApplicationLocalReference `json:"app-locals,omitempty"` + + // Apps The unnamed applications that were referenced. The order of this array is arbitrary. + Apps *[]uint64 `json:"apps,omitempty"` + + // AssetHoldings The unnamed asset holdings that were referenced. The order of this array is arbitrary. + AssetHoldings *[]AssetHoldingReference `json:"asset-holdings,omitempty"` + + // Assets The unnamed assets that were referenced. The order of this array is arbitrary. + Assets *[]uint64 `json:"assets,omitempty"` + + // Boxes The unnamed boxes that were referenced. The order of this array is arbitrary. + Boxes *[]BoxReference `json:"boxes,omitempty"` + + // ExtraBoxRefs The number of extra box references used to increase the IO budget. This is in addition to the references defined in the input transaction group and any referenced to unnamed boxes. + ExtraBoxRefs *uint64 `json:"extra-box-refs,omitempty"` } // SimulationEvalOverrides The set of parameters and limits override during simulation. If this set of parameters is present, then evaluation parameters may differ from standard evaluation in certain ways. @@ -725,6 +785,9 @@ type SimulationEvalOverrides struct { // AllowEmptySignatures If true, transactions without signatures are allowed and simulated as if they were properly signed. AllowEmptySignatures *bool `json:"allow-empty-signatures,omitempty"` + // AllowUnnamedResources If true, allows access to unnamed resources during simulation. + AllowUnnamedResources *bool `json:"allow-unnamed-resources,omitempty"` + // ExtraOpcodeBudget The extra opcode budget added to each transaction group during simulation ExtraOpcodeBudget *uint64 `json:"extra-opcode-budget,omitempty"` diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 61b67806f7..94c2b17f3e 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -130,191 +130,201 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "BYUfP9Qb7ZOY1xt+++H2/wUAAP//q8e4jZPaAAA=", + "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", + "+L7Z6FAEqNnwj+8//t8AAAD//5R90cnG6AAA", } // 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 c5544d410c..6f12f9a396 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -724,265 +724,276 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 5aec05ef28..46da1c3e3d 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -158,193 +158,203 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "3zXk8b65RzyZfHj34f8HAAD//w2Fuvhe8gAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index b649bd8897..1be6755019 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,202 +177,212 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "UzJGJbryAAA=", + "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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 563990166e..4b50e84f95 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -979,19 +979,21 @@ func (v2 *Handlers) RawTransactionAsync(ctx echo.Context) error { // PreEncodedSimulateTxnResult mirrors model.SimulateTransactionResult type PreEncodedSimulateTxnResult struct { - Txn PreEncodedTxInfo `codec:"txn-result"` - AppBudgetConsumed *uint64 `codec:"app-budget-consumed,omitempty"` - LogicSigBudgetConsumed *uint64 `codec:"logic-sig-budget-consumed,omitempty"` - TransactionTrace *model.SimulationTransactionExecTrace `codec:"exec-trace,omitempty"` + Txn PreEncodedTxInfo `codec:"txn-result"` + AppBudgetConsumed *uint64 `codec:"app-budget-consumed,omitempty"` + LogicSigBudgetConsumed *uint64 `codec:"logic-sig-budget-consumed,omitempty"` + TransactionTrace *model.SimulationTransactionExecTrace `codec:"exec-trace,omitempty"` + UnnamedResourcesAccessed *model.SimulateUnnamedResourcesAccessed `codec:"unnamed-resources-accessed,omitempty"` } // PreEncodedSimulateTxnGroupResult mirrors model.SimulateTransactionGroupResult type PreEncodedSimulateTxnGroupResult struct { - AppBudgetAdded *uint64 `codec:"app-budget-added,omitempty"` - AppBudgetConsumed *uint64 `codec:"app-budget-consumed,omitempty"` - FailedAt *[]uint64 `codec:"failed-at,omitempty"` - FailureMessage *string `codec:"failure-message,omitempty"` - Txns []PreEncodedSimulateTxnResult `codec:"txn-results"` + AppBudgetAdded *uint64 `codec:"app-budget-added,omitempty"` + AppBudgetConsumed *uint64 `codec:"app-budget-consumed,omitempty"` + FailedAt *[]uint64 `codec:"failed-at,omitempty"` + FailureMessage *string `codec:"failure-message,omitempty"` + UnnamedResourcesAccessed *model.SimulateUnnamedResourcesAccessed `codec:"unnamed-resources-accessed,omitempty"` + Txns []PreEncodedSimulateTxnResult `codec:"txn-results"` } // PreEncodedSimulateResponse mirrors model.SimulateResponse @@ -1010,11 +1012,12 @@ type PreEncodedSimulateRequestTransactionGroup struct { // PreEncodedSimulateRequest mirrors model.SimulateRequest type PreEncodedSimulateRequest struct { - TxnGroups []PreEncodedSimulateRequestTransactionGroup `codec:"txn-groups"` - AllowEmptySignatures bool `codec:"allow-empty-signatures,omitempty"` - AllowMoreLogging bool `codec:"allow-more-logging,omitempty"` - ExtraOpcodeBudget uint64 `codec:"extra-opcode-budget,omitempty"` - ExecTraceConfig simulation.ExecTraceConfig `codec:"exec-trace-config,omitempty"` + TxnGroups []PreEncodedSimulateRequestTransactionGroup `codec:"txn-groups"` + AllowEmptySignatures bool `codec:"allow-empty-signatures,omitempty"` + AllowMoreLogging bool `codec:"allow-more-logging,omitempty"` + AllowUnnamedResources bool `codec:"allow-unnamed-resources,omitempty"` + ExtraOpcodeBudget uint64 `codec:"extra-opcode-budget,omitempty"` + ExecTraceConfig simulation.ExecTraceConfig `codec:"exec-trace-config,omitempty"` } // SimulateTransaction simulates broadcasting a raw transaction to the network, returning relevant simulation results. diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index f476afa21a..d1da2895d1 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -26,11 +26,14 @@ import ( "github.com/algorand/go-codec/codec" "github.com/labstack/echo/v4" + "golang.org/x/exp/maps" "golang.org/x/exp/slices" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/simulation" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/node" @@ -67,6 +70,29 @@ func notImplemented(ctx echo.Context, internal error, external string, log loggi return returnError(ctx, http.StatusNotImplemented, internal, external, log) } +func convertSlice[X any, Y any](input []X, fn func(X) Y) []Y { + output := make([]Y, len(input)) + for i := range input { + output[i] = fn(input[i]) + } + return output +} + +func uint64Slice[T ~uint64](s []T) []uint64 { + return convertSlice(s, func(t T) uint64 { return uint64(t) }) +} + +func stringSlice[T fmt.Stringer](s []T) []string { + return convertSlice(s, func(t T) string { return t.String() }) +} + +func sliceOrNil[T any](s []T) *[]T { + if len(s) == 0 { + return nil + } + return &s +} + func addrOrNil(addr basics.Address) *string { if addr.IsZero() { return nil @@ -428,10 +454,41 @@ func convertTxnTrace(txnTrace *simulation.TransactionTrace) *model.SimulationTra func convertTxnResult(txnResult simulation.TxnResult) PreEncodedSimulateTxnResult { return PreEncodedSimulateTxnResult{ - Txn: ConvertInnerTxn(&txnResult.Txn), - AppBudgetConsumed: omitEmpty(txnResult.AppBudgetConsumed), - LogicSigBudgetConsumed: omitEmpty(txnResult.LogicSigBudgetConsumed), - TransactionTrace: convertTxnTrace(txnResult.Trace), + Txn: ConvertInnerTxn(&txnResult.Txn), + AppBudgetConsumed: omitEmpty(txnResult.AppBudgetConsumed), + LogicSigBudgetConsumed: omitEmpty(txnResult.LogicSigBudgetConsumed), + TransactionTrace: convertTxnTrace(txnResult.Trace), + UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnResult.UnnamedResourcesAccessed), + } +} + +func convertUnnamedResourcesAccessed(resources *simulation.ResourceTracker) *model.SimulateUnnamedResourcesAccessed { + if resources == nil { + return nil + } + return &model.SimulateUnnamedResourcesAccessed{ + Accounts: sliceOrNil(stringSlice(maps.Keys(resources.Accounts))), + Assets: sliceOrNil(uint64Slice(maps.Keys(resources.Assets))), + Apps: sliceOrNil(uint64Slice(maps.Keys(resources.Apps))), + Boxes: sliceOrNil(convertSlice(maps.Keys(resources.Boxes), func(box logic.BoxRef) model.BoxReference { + return model.BoxReference{ + App: uint64(box.App), + Name: []byte(box.Name), + } + })), + ExtraBoxRefs: omitEmpty(uint64(resources.NumEmptyBoxRefs)), + AssetHoldings: sliceOrNil(convertSlice(maps.Keys(resources.AssetHoldings), func(holding ledgercore.AccountAsset) model.AssetHoldingReference { + return model.AssetHoldingReference{ + Account: holding.Address.String(), + Asset: uint64(holding.Asset), + } + })), + AppLocals: sliceOrNil(convertSlice(maps.Keys(resources.AppLocals), func(local ledgercore.AccountApp) model.ApplicationLocalReference { + return model.ApplicationLocalReference{ + Account: local.Address.String(), + App: uint64(local.App), + } + })), } } @@ -442,10 +499,11 @@ func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) PreEncodedS } encoded := PreEncodedSimulateTxnGroupResult{ - Txns: txnResults, - FailureMessage: omitEmpty(txnGroupResult.FailureMessage), - AppBudgetAdded: omitEmpty(txnGroupResult.AppBudgetAdded), - AppBudgetConsumed: omitEmpty(txnGroupResult.AppBudgetConsumed), + Txns: txnResults, + FailureMessage: omitEmpty(txnGroupResult.FailureMessage), + AppBudgetAdded: omitEmpty(txnGroupResult.AppBudgetAdded), + AppBudgetConsumed: omitEmpty(txnGroupResult.AppBudgetConsumed), + UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnGroupResult.UnnamedResourcesAccessed), } if len(txnGroupResult.FailedAt) > 0 { @@ -460,10 +518,11 @@ func convertSimulationResult(result simulation.Result) PreEncodedSimulateRespons var evalOverrides *model.SimulationEvalOverrides if result.EvalOverrides != (simulation.ResultEvalOverrides{}) { evalOverrides = &model.SimulationEvalOverrides{ - AllowEmptySignatures: omitEmpty(result.EvalOverrides.AllowEmptySignatures), - MaxLogSize: result.EvalOverrides.MaxLogSize, - MaxLogCalls: result.EvalOverrides.MaxLogCalls, - ExtraOpcodeBudget: omitEmpty(result.EvalOverrides.ExtraOpcodeBudget), + AllowEmptySignatures: omitEmpty(result.EvalOverrides.AllowEmptySignatures), + AllowUnnamedResources: omitEmpty(result.EvalOverrides.AllowUnnamedResources), + MaxLogSize: result.EvalOverrides.MaxLogSize, + MaxLogCalls: result.EvalOverrides.MaxLogCalls, + ExtraOpcodeBudget: omitEmpty(result.EvalOverrides.ExtraOpcodeBudget), } } @@ -488,11 +547,12 @@ func convertSimulationRequest(request PreEncodedSimulateRequest) simulation.Requ txnGroups[i] = txnGroup.Txns } return simulation.Request{ - TxnGroups: txnGroups, - AllowEmptySignatures: request.AllowEmptySignatures, - AllowMoreLogging: request.AllowMoreLogging, - ExtraOpcodeBudget: request.ExtraOpcodeBudget, - TraceConfig: request.ExecTraceConfig, + TxnGroups: txnGroups, + AllowEmptySignatures: request.AllowEmptySignatures, + AllowMoreLogging: request.AllowMoreLogging, + AllowUnnamedResources: request.AllowUnnamedResources, + ExtraOpcodeBudget: request.ExtraOpcodeBudget, + TraceConfig: request.ExecTraceConfig, } } diff --git a/data/basics/overflow.go b/data/basics/overflow.go index 45ec018a7b..d119ba3a9c 100644 --- a/data/basics/overflow.go +++ b/data/basics/overflow.go @@ -152,3 +152,11 @@ func Muldiv(a uint64, b uint64, c uint64) (res uint64, overflow bool) { quo, _ := bits.Div64(hi, lo, c) return quo, false } + +// DivCeil provides `math.Ceil` semantics using integer division. The technique +// avoids slower floating point operations as suggested in https://stackoverflow.com/a/2745086. +// +// The method assumes both numbers are positive and does _not_ check for divide-by-zero. +func DivCeil[T constraints.Integer](numerator, denominator T) T { + return (numerator + denominator - 1) / denominator +} diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index 02b4d8cd2d..ad371c6a1f 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -23,19 +23,29 @@ import ( "github.com/algorand/go-algorand/data/transactions" ) +// BoxOperation is an enum of box operation types +type BoxOperation int + const ( - boxCreate = iota - boxRead - boxWrite - boxDelete + // BoxCreateOperation creates a box + BoxCreateOperation BoxOperation = iota + // BoxReadOperation reads a box + BoxReadOperation + // BoxWriteOperation writes to a box + BoxWriteOperation + // BoxDeleteOperation deletes a box + BoxDeleteOperation ) -func (cx *EvalContext) availableBox(name string, operation int, createSize uint64) ([]byte, bool, error) { +func (cx *EvalContext) availableBox(name string, operation BoxOperation, createSize uint64) ([]byte, bool, error) { if cx.txn.Txn.OnCompletion == transactions.ClearStateOC { return nil, false, fmt.Errorf("boxes may not be accessed from ClearState program") } - dirty, ok := cx.available.boxes[boxRef{cx.appID, name}] + dirty, ok := cx.available.boxes[BoxRef{cx.appID, name}] + if !ok && cx.UnnamedResources != nil { + ok = cx.UnnamedResources.AvailableBox(cx.appID, name, operation, createSize) + } if !ok { return nil, false, fmt.Errorf("invalid Box reference %#x", name) } @@ -50,7 +60,7 @@ func (cx *EvalContext) availableBox(name string, operation int, createSize uint6 } switch operation { - case boxCreate: + case BoxCreateOperation: if exists { if createSize != uint64(len(content)) { return nil, false, fmt.Errorf("box size mismatch %d %d", uint64(len(content)), createSize) @@ -58,11 +68,11 @@ func (cx *EvalContext) availableBox(name string, operation int, createSize uint6 // Since it exists, we have no dirty work to do. The weird case of // box_put, which seems like a combination of create and write, is // properly handled because already used boxWrite to declare the - // intent to write (and tracky dirtiness). + // intent to write (and track dirtiness). return content, exists, nil } fallthrough // If it doesn't exist, a create is like write - case boxWrite: + case BoxWriteOperation: writeSize := createSize if exists { writeSize = uint64(len(content)) @@ -71,15 +81,15 @@ func (cx *EvalContext) availableBox(name string, operation int, createSize uint6 cx.available.dirtyBytes += writeSize } dirty = true - case boxDelete: + case BoxDeleteOperation: if dirty { cx.available.dirtyBytes -= uint64(len(content)) } dirty = false - case boxRead: + case BoxReadOperation: /* nothing to do */ } - cx.available.boxes[boxRef{cx.appID, name}] = dirty + cx.available.boxes[BoxRef{cx.appID, name}] = dirty if cx.available.dirtyBytes > cx.ioBudget { return nil, false, fmt.Errorf("write budget (%d) exceeded %d", cx.ioBudget, cx.available.dirtyBytes) @@ -115,12 +125,12 @@ func opBoxCreate(cx *EvalContext) error { if err != nil { return err } - _, exists, err := cx.availableBox(name, boxCreate, size) + _, exists, err := cx.availableBox(name, BoxCreateOperation, size) if err != nil { return err } if !exists { - appAddr := cx.getApplicationAddress(cx.appID) + appAddr := cx.GetApplicationAddress(cx.appID) err = cx.Ledger.NewBox(cx.appID, name, make([]byte, size), appAddr) if err != nil { return err @@ -145,7 +155,7 @@ func opBoxExtract(cx *EvalContext) error { if err != nil { return err } - contents, exists, err := cx.availableBox(name, boxRead, 0) + contents, exists, err := cx.availableBox(name, BoxReadOperation, 0) if err != nil { return err } @@ -173,7 +183,7 @@ func opBoxReplace(cx *EvalContext) error { return err } - contents, exists, err := cx.availableBox(name, boxWrite, 0 /* size is already known */) + contents, exists, err := cx.availableBox(name, BoxWriteOperation, 0 /* size is already known */) if err != nil { return err } @@ -197,12 +207,12 @@ func opBoxDel(cx *EvalContext) error { if err != nil { return err } - _, exists, err := cx.availableBox(name, boxDelete, 0) + _, exists, err := cx.availableBox(name, BoxDeleteOperation, 0) if err != nil { return err } if exists { - appAddr := cx.getApplicationAddress(cx.appID) + appAddr := cx.GetApplicationAddress(cx.appID) _, err := cx.Ledger.DelBox(cx.appID, name, appAddr) if err != nil { return err @@ -220,7 +230,7 @@ func opBoxLen(cx *EvalContext) error { if err != nil { return err } - contents, exists, err := cx.availableBox(name, boxRead, 0) + contents, exists, err := cx.availableBox(name, BoxReadOperation, 0) if err != nil { return err } @@ -238,7 +248,7 @@ func opBoxGet(cx *EvalContext) error { if err != nil { return err } - contents, exists, err := cx.availableBox(name, boxRead, 0) + contents, exists, err := cx.availableBox(name, BoxReadOperation, 0) if err != nil { return err } @@ -263,7 +273,7 @@ func opBoxPut(cx *EvalContext) error { } // This boxWrite usage requires the size, because the box may not exist. - contents, exists, err := cx.availableBox(name, boxWrite, uint64(len(value))) + contents, exists, err := cx.availableBox(name, BoxWriteOperation, uint64(len(value))) if err != nil { return err } @@ -279,6 +289,6 @@ func opBoxPut(cx *EvalContext) error { } /* The box did not exist, so create it. */ - appAddr := cx.getApplicationAddress(cx.appID) + appAddr := cx.GetApplicationAddress(cx.appID) return cx.Ledger.NewBox(cx.appID, name, value, appAddr) } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index f1d6983836..4dc1290a49 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -247,10 +247,21 @@ type LedgerForLogic interface { Counter() uint64 } -// boxRef is the "hydrated" form of a BoxRef - it has the actual app id, not an index -type boxRef struct { - app basics.AppIndex - name string +// BoxRef is the "hydrated" form of a transactions.BoxRef - it has the actual app id, not an index +type BoxRef struct { + App basics.AppIndex + Name string +} + +// UnnamedResourcePolicy is an interface that defines the policy for allowing unnamed resources. +// This should only be used during simulation or debugging. +type UnnamedResourcePolicy interface { + AvailableAccount(addr basics.Address) bool + AvailableAsset(asset basics.AssetIndex) bool + AvailableApp(app basics.AppIndex) bool + AllowsHolding(addr basics.Address, asset basics.AssetIndex) bool + AllowsLocal(addr basics.Address, app basics.AppIndex) bool + AvailableBox(app basics.AppIndex, name string, operation BoxOperation, createSize uint64) bool } // EvalConstants contains constant parameters that are used by opcodes during evaluation (including both real-execution and simulation). @@ -260,6 +271,10 @@ type EvalConstants struct { // MaxLogCalls is the limit of total log calls during a program execution MaxLogCalls uint64 + + // UnnamedResources, if provided, allows resources to be used without being named according to + // this policy. + UnnamedResources UnnamedResourcePolicy } // RuntimeEvalConstants gives a set of const params used in normal runtime of opcodes @@ -325,6 +340,11 @@ type EvalParams struct { // readBudgetChecked allows us to only check the read budget once readBudgetChecked bool + // SurplusReadBudget is the number of bytes from the IO budget that were not used for reading + // in boxes before evaluation began. In other words, the txn group could have read in + // SurplusReadBudget more box bytes, but did not. + SurplusReadBudget uint64 + EvalConstants // Caching these here means the hashes can be shared across the TxnGroup @@ -346,6 +366,21 @@ func (ep *EvalParams) GetCaller() *EvalContext { return ep.caller } +// GetIOBudget returns the current IO budget for the group. +func (ep *EvalParams) GetIOBudget() uint64 { + return ep.ioBudget +} + +// SetIOBudget sets the IO budget for the group. +func (ep *EvalParams) SetIOBudget(ioBudget uint64) { + ep.ioBudget = ioBudget +} + +// BoxDirtyBytes returns the number of bytes that have been written to boxes +func (ep *EvalParams) BoxDirtyBytes() uint64 { + return ep.available.dirtyBytes +} + func copyWithClearAD(txgroup []transactions.SignedTxnWithAD) []transactions.SignedTxnWithAD { copy := make([]transactions.SignedTxnWithAD, len(txgroup)) for i := range txgroup { @@ -434,7 +469,7 @@ func (ep *EvalParams) computeAvailability() *resources { sharedApps: make(map[basics.AppIndex]struct{}), sharedHoldings: make(map[ledgercore.AccountAsset]struct{}), sharedLocals: make(map[ledgercore.AccountApp]struct{}), - boxes: make(map[boxRef]bool), + boxes: make(map[BoxRef]bool), } for i := range ep.TxnGroup { available.fill(&ep.TxnGroup[i].Txn, ep) @@ -652,6 +687,11 @@ func (cx *EvalContext) RunMode() RunMode { return cx.runMode } +// ProgramVersion returns the AVM version of the current program. +func (cx *EvalContext) ProgramVersion() uint64 { + return cx.version +} + // PC returns the program counter of the current application being evaluated func (cx *EvalContext) PC() int { return cx.pc } @@ -977,7 +1017,7 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam // make any "0 index" box refs available now that we have an appID. for _, br := range cx.txn.Txn.Boxes { if br.Index == 0 { - cx.EvalParams.available.boxes[boxRef{cx.appID, string(br.Name)}] = false + cx.EvalParams.available.boxes[BoxRef{cx.appID, string(br.Name)}] = false } } // and add the appID to `createdApps` @@ -999,12 +1039,12 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam used := uint64(0) for br := range cx.available.boxes { - if len(br.name) == 0 { + if len(br.Name) == 0 { // 0 length names are not allowed for actual created boxes, but // may have been used to add I/O budget. continue } - box, ok, err := cx.Ledger.GetBox(br.app, br.name) + box, ok, err := cx.Ledger.GetBox(br.App, br.Name) if err != nil { return false, nil, err } @@ -1024,6 +1064,7 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam } } cx.readBudgetChecked = true + cx.SurplusReadBudget = cx.ioBudget - used } if cx.Trace != nil && cx.caller != nil { @@ -3063,9 +3104,9 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t case ClearStateProgram: sv.Bytes = nilToEmpty(txn.ClearStateProgram) case NumApprovalProgramPages: - sv.Uint = uint64(divCeil(len(txn.ApprovalProgram), maxStringSize)) + sv.Uint = uint64(basics.DivCeil(len(txn.ApprovalProgram), maxStringSize)) case ApprovalProgramPages: - pageCount := divCeil(len(txn.ApprovalProgram), maxStringSize) + pageCount := basics.DivCeil(len(txn.ApprovalProgram), maxStringSize) if arrayFieldIdx >= uint64(pageCount) { return sv, fmt.Errorf("invalid ApprovalProgramPages index %d", arrayFieldIdx) } @@ -3076,9 +3117,9 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t } sv.Bytes = txn.ApprovalProgram[first:last] case NumClearStateProgramPages: - sv.Uint = uint64(divCeil(len(txn.ClearStateProgram), maxStringSize)) + sv.Uint = uint64(basics.DivCeil(len(txn.ClearStateProgram), maxStringSize)) case ClearStateProgramPages: - pageCount := divCeil(len(txn.ClearStateProgram), maxStringSize) + pageCount := basics.DivCeil(len(txn.ClearStateProgram), maxStringSize) if arrayFieldIdx >= uint64(pageCount) { return sv, fmt.Errorf("invalid ClearStateProgramPages index %d", arrayFieldIdx) } @@ -3523,8 +3564,8 @@ func (cx *EvalContext) getLatestTimestamp() (uint64, error) { return uint64(ts), nil } -// getApplicationAddress memoizes app.Address() across a tx group's evaluation -func (ep *EvalParams) getApplicationAddress(app basics.AppIndex) basics.Address { +// GetApplicationAddress memoizes app.Address() across a tx group's evaluation +func (ep *EvalParams) GetApplicationAddress(app basics.AppIndex) basics.Address { /* Do not instantiate the cache here, that would mask a programming error. The cache must be instantiated at EvalParams construction time, so that proper sharing with inner EvalParams can work. */ @@ -3568,7 +3609,7 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er case CurrentApplicationID: sv.Uint = uint64(cx.appID) case CurrentApplicationAddress: - addr := cx.getApplicationAddress(cx.appID) + addr := cx.GetApplicationAddress(cx.appID) sv.Bytes = addr[:] case CreatorAddress: sv.Bytes, err = cx.getCreatorAddress() @@ -3584,7 +3625,7 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er } case CallerApplicationAddress: if cx.caller != nil { - addr := cx.caller.getApplicationAddress(cx.caller.appID) + addr := cx.caller.GetApplicationAddress(cx.caller.appID) sv.Bytes = addr[:] } else { sv.Bytes = zeroAddress[:] @@ -4352,7 +4393,7 @@ func (cx *EvalContext) availableAccount(addr basics.Address) bool { // Allow an address for an app that was created in group if cx.version >= createdResourcesVersion { for appID := range cx.available.createdApps { - createdAddress := cx.getApplicationAddress(appID) + createdAddress := cx.GetApplicationAddress(appID) if addr == createdAddress { return true } @@ -4369,14 +4410,18 @@ func (cx *EvalContext) availableAccount(addr basics.Address) bool { // Allow an address for an app that was provided in the foreign apps array. if cx.version >= appAddressAvailableVersion { for _, appID := range cx.txn.Txn.ForeignApps { - foreignAddress := cx.getApplicationAddress(appID) + foreignAddress := cx.GetApplicationAddress(appID) if addr == foreignAddress { return true } } } - if cx.getApplicationAddress(cx.appID) == addr { + if cx.GetApplicationAddress(cx.appID) == addr { + return true + } + + if cx.UnnamedResources != nil && cx.UnnamedResources.AvailableAccount(addr) { return true } @@ -5140,8 +5185,8 @@ func authorizedSender(cx *EvalContext, addr basics.Address) error { if err != nil { return err } - if cx.getApplicationAddress(cx.appID) != authorizer { - return fmt.Errorf("app %d (addr %s) unauthorized %s", cx.appID, cx.getApplicationAddress(cx.appID), authorizer) + if cx.GetApplicationAddress(cx.appID) != authorizer { + return fmt.Errorf("app %d (addr %s) unauthorized %s", cx.appID, cx.GetApplicationAddress(cx.appID), authorizer) } return nil } @@ -5149,7 +5194,7 @@ func authorizedSender(cx *EvalContext, addr basics.Address) error { // addInnerTxn appends a fresh SignedTxn to subtxns, populated with reasonable // defaults. func addInnerTxn(cx *EvalContext) error { - addr := cx.getApplicationAddress(cx.appID) + addr := cx.GetApplicationAddress(cx.appID) // For compatibility with v5, in which failures only occurred in the submit, // we only fail here if we are already over the max inner limit. Thus this @@ -5253,6 +5298,10 @@ func (cx *EvalContext) availableAsset(aid basics.AssetIndex) bool { } } + if aid > lastForbiddenResource && cx.UnnamedResources != nil && cx.UnnamedResources.AvailableAsset(aid) { + return true + } + return false } @@ -5297,6 +5346,10 @@ func (cx *EvalContext) availableApp(aid basics.AppIndex) bool { } } + if aid > lastForbiddenResource && cx.UnnamedResources != nil && cx.UnnamedResources.AvailableApp(aid) { + return true + } + return false } diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 0bebf9d267..ab7167c7c8 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/protocol" @@ -1228,72 +1229,83 @@ func TestAssetDisambiguation(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - // It would be nice to start at 2, when apps were added, but `assert` is - // very convenient for testing, and nothing important changed from 2 to - // 3. (Between directRefEnabledVersion=4, so that change is a big deal.) - testLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { - ledger.NewAsset(tx.Sender, 1, basics.AssetParams{AssetName: "one", Total: 1}) - ledger.NewAsset(tx.Sender, 255, basics.AssetParams{AssetName: "twenty", Total: 255}) - ledger.NewAsset(tx.Sender, 256, basics.AssetParams{AssetName: "thirty", Total: 256}) - tx.ForeignAssets = []basics.AssetIndex{255, 256} - // Since 1 is not available, 1 must mean the 1th asset slot = 256 - testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) + // Make sure we don't treat slot indexes as asset IDs when + // ep.UnnamedResources is not nil. + for _, unnamedResources := range []bool{false, true} { + unnamedResources := unnamedResources + t.Run(fmt.Sprintf("unnamedResources=%v", unnamedResources), func(t *testing.T) { + t.Parallel() + // It would be nice to start at 2, when apps were added, but `assert` is + // very convenient for testing, and nothing important changed from 2 to + // 3. (Between directRefEnabledVersion=4, so that change is a big deal.) + testLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + if unnamedResources { + ep.UnnamedResources = &mockUnnamedResourcePolicy{allowEverything: true} + } + ledger.NewAsset(tx.Sender, 1, basics.AssetParams{AssetName: "one", Total: 1}) + ledger.NewAsset(tx.Sender, 255, basics.AssetParams{AssetName: "twenty", Total: 255}) + ledger.NewAsset(tx.Sender, 256, basics.AssetParams{AssetName: "thirty", Total: 256}) + tx.ForeignAssets = []basics.AssetIndex{255, 256} + // Since 1 is not available, 1 must mean the 1th asset slot = 256 + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) - if ep.Proto.LogicSigVersion < directRefEnabledVersion { - // in v3, the asset argument is always treated as an ID, so this is asset 1 - testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep) - } else { - testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep) - } + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the asset argument is always treated as an ID, so this is asset 1 + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep) + } else { + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep) + } - tx.ForeignAssets = []basics.AssetIndex{1, 256} - if ep.Proto.LogicSigVersion < directRefEnabledVersion { - // There's no direct use of assets IDs, so 1 is still the 1th slot (256) - testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) - } else { - // Since 1 IS available, 1 means the assetid=1, not the 1th slot - testApp(t, `int 1; asset_params_get AssetName; assert; byte "one"; ==`, ep) - } - testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep) + tx.ForeignAssets = []basics.AssetIndex{1, 256} + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // There's no direct use of assets IDs, so 1 is still the 1th slot (256) + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) + } else { + // Since 1 IS available, 1 means the assetid=1, not the 1th slot + testApp(t, `int 1; asset_params_get AssetName; assert; byte "one"; ==`, ep) + } + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep) - ep.Proto.AppForbidLowResources = true - tx.ForeignAssets = []basics.AssetIndex{255, 256} - // Since 1 is not available, 1 must mean the 1th asset slot = 256 - testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) - if ep.Proto.LogicSigVersion < directRefEnabledVersion { - // in v3, the asset argument is always treated as an ID, so this is asset 1 - testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep, - "low Asset lookup 1") - } else { - testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep) - } + ep.Proto.AppForbidLowResources = true + tx.ForeignAssets = []basics.AssetIndex{255, 256} + // Since 1 is not available, 1 must mean the 1th asset slot = 256 + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the asset argument is always treated as an ID, so this is asset 1 + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep, + "low Asset lookup 1") + } else { + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep) + } - // but now if that resolution led to a number below 255, boom - tx.ForeignAssets = []basics.AssetIndex{256, 255} - testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep, - "low Asset lookup 255") - if ep.Proto.LogicSigVersion < directRefEnabledVersion { - // in v3, the asset argument is always treated as an ID, so this is asset 1 - testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 30; ==`, ep, - "low Asset lookup 1") - } else { - testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 30; ==`, ep, - "low Asset lookup 255") - } + // but now if that resolution led to a number below 255, boom + tx.ForeignAssets = []basics.AssetIndex{256, 255} + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep, + "low Asset lookup 255") + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the asset argument is always treated as an ID, so this is asset 1 + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 30; ==`, ep, + "low Asset lookup 1") + } else { + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 30; ==`, ep, + "low Asset lookup 255") + } - tx.ForeignAssets = []basics.AssetIndex{1, 256} - if ep.Proto.LogicSigVersion < directRefEnabledVersion { - // in v3, the asset argument is always a slot, so this is asset 256 - testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) - } else { - // Since 1 IS available, 1 means the assetid=1, not the 1th slot - testApp(t, `int 1; asset_params_get AssetName; assert; byte "one"; ==`, ep, - "low Asset lookup 1") - } - // pre v4 and the availability rule come to the same conclusion: treat the 1 as an ID - testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep, - "low Asset lookup 1") - }) + tx.ForeignAssets = []basics.AssetIndex{1, 256} + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the asset argument is always a slot, so this is asset 256 + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) + } else { + // Since 1 IS available, 1 means the assetid=1, not the 1th slot + testApp(t, `int 1; asset_params_get AssetName; assert; byte "one"; ==`, ep, + "low Asset lookup 1") + } + // pre v4 and the availability rule come to the same conclusion: treat the 1 as an ID + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep, + "low Asset lookup 1") + }) + }) + } } // TestAppDisambiguation ensures we have a consistent interpretation of low @@ -1304,83 +1316,94 @@ func TestAppDisambiguation(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - // It would be nice to start at 2, when apps were added, but `assert` is - // very convenient for testing, and nothing important changed from 2 to - // 3. (But directRefEnabledVersion=4, so that change is a big deal.) - testLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { - // make apps with identifiable properties, so we can tell what we get - makeIdentifiableApp := func(appID uint64) { - ledger.NewApp(tx.Sender, basics.AppIndex(appID), basics.AppParams{ - GlobalState: map[string]basics.TealValue{"a": { - Type: basics.TealUintType, - Uint: appID, - }}, - ExtraProgramPages: uint32(appID), - }) - ledger.NewLocals(tx.Sender, appID) - ledger.NewLocal(tx.Sender, appID, "x", basics.TealValue{Type: basics.TealUintType, Uint: appID * 10}) - } - makeIdentifiableApp(1) - makeIdentifiableApp(20) - makeIdentifiableApp(256) - - tx.ForeignApps = []basics.AppIndex{20, 256} - // Since 1 is not available, 1 must mean the first app slot = 20 (recall, 0 mean "this app") - if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages - testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 20; ==`, ep) - } - testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 20; ==`, ep) - if ep.Proto.LogicSigVersion < directRefEnabledVersion { - // in v3, the app argument is always treated as an ID. - testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 10; ==`, ep) - } else { - testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep) - } + // Make sure we don't treat slot indexes as app IDs when + // ep.UnnamedResources is true. + for _, unnamedResources := range []bool{false, true} { + unnamedResources := unnamedResources + t.Run(fmt.Sprintf("unnamedResources=%v", unnamedResources), func(t *testing.T) { + t.Parallel() + // It would be nice to start at 2, when apps were added, but `assert` is + // very convenient for testing, and nothing important changed from 2 to + // 3. (But directRefEnabledVersion=4, so that change is a big deal.) + testLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + if unnamedResources { + ep.UnnamedResources = &mockUnnamedResourcePolicy{allowEverything: true} + } + // make apps with identifiable properties, so we can tell what we get + makeIdentifiableApp := func(appID uint64) { + ledger.NewApp(tx.Sender, basics.AppIndex(appID), basics.AppParams{ + GlobalState: map[string]basics.TealValue{"a": { + Type: basics.TealUintType, + Uint: appID, + }}, + ExtraProgramPages: uint32(appID), + }) + ledger.NewLocals(tx.Sender, appID) + ledger.NewLocal(tx.Sender, appID, "x", basics.TealValue{Type: basics.TealUintType, Uint: appID * 10}) + } + makeIdentifiableApp(1) + makeIdentifiableApp(20) + makeIdentifiableApp(256) + + tx.ForeignApps = []basics.AppIndex{20, 256} + // Since 1 is not available, 1 must mean the first app slot = 20 (recall, 0 mean "this app") + if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages + testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 20; ==`, ep) + } + testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 20; ==`, ep) + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the app argument is always treated as an ID. + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 10; ==`, ep) + } else { + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep) + } - // Make 1 available, so now 1 means the appid=1, not the 1th slot - tx.ForeignApps = []basics.AppIndex{1, 256} - if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages - testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 1; ==`, ep) - } - testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 1; ==`, ep) - testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 10; ==`, ep) - - // same tests, but as of AppForbidLowResources, using 1 is forbidden - ep.Proto.AppForbidLowResources = true - - // repeat the first tests, they are using 20 and 256 directly, which are too low - tx.ForeignApps = []basics.AppIndex{20, 256} - if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages - testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 20; ==`, ep, - "low App lookup 20") - testApp(t, `int 2; app_params_get AppExtraProgramPages; assert; int 256; ==`, ep) - } - testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 20; ==`, ep, - "low App lookup 20") - testApp(t, `int 2; byte "a"; app_global_get_ex; assert; int 256; ==`, ep) - if ep.Proto.LogicSigVersion < directRefEnabledVersion { - // in v3, the app argument is always treated as an ID. - testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep, - "low App lookup 1") - testApp(t, `int 0; int 2; byte "x"; app_local_get_ex; assert; int 2560; ==`, ep, - "low App lookup 2") - } else { - testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep, - "low App lookup 20") - testApp(t, `int 0; int 2; byte "x"; app_local_get_ex; assert; int 2560; ==`, ep) - } + // Make 1 available, so now 1 means the appid=1, not the 1th slot + tx.ForeignApps = []basics.AppIndex{1, 256} + if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages + testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 1; ==`, ep) + } + testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 1; ==`, ep) + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 10; ==`, ep) + + // same tests, but as of AppForbidLowResources, using 1 is forbidden + ep.Proto.AppForbidLowResources = true + + // repeat the first tests, they are using 20 and 256 directly, which are too low + tx.ForeignApps = []basics.AppIndex{20, 256} + if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages + testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 20; ==`, ep, + "low App lookup 20") + testApp(t, `int 2; app_params_get AppExtraProgramPages; assert; int 256; ==`, ep) + } + testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 20; ==`, ep, + "low App lookup 20") + testApp(t, `int 2; byte "a"; app_global_get_ex; assert; int 256; ==`, ep) + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the app argument is always treated as an ID. + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep, + "low App lookup 1") + testApp(t, `int 0; int 2; byte "x"; app_local_get_ex; assert; int 2560; ==`, ep, + "low App lookup 2") + } else { + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep, + "low App lookup 20") + testApp(t, `int 0; int 2; byte "x"; app_local_get_ex; assert; int 2560; ==`, ep) + } - // repeat the second tests, which are using 1, which is too low - tx.ForeignApps = []basics.AppIndex{1, 256} - if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages - testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 1; ==`, ep, - "low App lookup 1") - } - testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 1; ==`, ep, - "low App lookup 1") - testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 10; ==`, ep, - "low App lookup 1") - }) + // repeat the second tests, which are using 1, which is too low + tx.ForeignApps = []basics.AppIndex{1, 256} + if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages + testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 1; ==`, ep, + "low App lookup 1") + } + testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 1; ==`, ep, + "low App lookup 1") + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 10; ==`, ep, + "low App lookup 1") + }) + }) + } } func TestAppParams(t *testing.T) { @@ -2575,6 +2598,331 @@ int 1 }) } +type unnamedResourcePolicyEvent struct { + eventType string + args []interface{} +} + +func availableAccountEvent(addr basics.Address) unnamedResourcePolicyEvent { + return unnamedResourcePolicyEvent{ + eventType: "AvailableAccount", + args: []interface{}{addr}, + } +} + +func availableAssetEvent(aid basics.AssetIndex) unnamedResourcePolicyEvent { + return unnamedResourcePolicyEvent{ + eventType: "AvailableAsset", + args: []interface{}{aid}, + } +} + +func availableAppEvent(aid basics.AppIndex) unnamedResourcePolicyEvent { + return unnamedResourcePolicyEvent{ + eventType: "AvailableApp", + args: []interface{}{aid}, + } +} + +func allowsHoldingEvent(addr basics.Address, aid basics.AssetIndex) unnamedResourcePolicyEvent { + return unnamedResourcePolicyEvent{ + eventType: "AllowsHolding", + args: []interface{}{addr, aid}, + } +} + +func allowsLocalEvent(addr basics.Address, aid basics.AppIndex) unnamedResourcePolicyEvent { + return unnamedResourcePolicyEvent{ + eventType: "AllowsLocal", + args: []interface{}{addr, aid}, + } +} + +func availableBoxEvent(app basics.AppIndex, name string, operation BoxOperation, createSize uint64) unnamedResourcePolicyEvent { + return unnamedResourcePolicyEvent{ + eventType: "AvailableBox", + args: []interface{}{app, name, operation, createSize}, + } +} + +type mockUnnamedResourcePolicy struct { + allowEverything bool + events []unnamedResourcePolicyEvent +} + +func (p *mockUnnamedResourcePolicy) String() string { + if p == nil { + return "no policy" + } + return fmt.Sprintf("allowEverything=%t", p.allowEverything) +} + +func (p *mockUnnamedResourcePolicy) AvailableAccount(addr basics.Address) bool { + p.events = append(p.events, availableAccountEvent(addr)) + return p.allowEverything +} + +func (p *mockUnnamedResourcePolicy) AvailableAsset(aid basics.AssetIndex) bool { + p.events = append(p.events, availableAssetEvent(aid)) + return p.allowEverything +} + +func (p *mockUnnamedResourcePolicy) AvailableApp(aid basics.AppIndex) bool { + p.events = append(p.events, availableAppEvent(aid)) + return p.allowEverything +} + +func (p *mockUnnamedResourcePolicy) AllowsHolding(addr basics.Address, aid basics.AssetIndex) bool { + p.events = append(p.events, allowsHoldingEvent(addr, aid)) + return p.allowEverything +} + +func (p *mockUnnamedResourcePolicy) AllowsLocal(addr basics.Address, aid basics.AppIndex) bool { + p.events = append(p.events, allowsLocalEvent(addr, aid)) + return p.allowEverything +} + +func (p *mockUnnamedResourcePolicy) AvailableBox(app basics.AppIndex, name string, operation BoxOperation, createSize uint64) bool { + p.events = append(p.events, availableBoxEvent(app, name, operation, createSize)) + return p.allowEverything +} + +func TestUnnamedResourceAccess(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + testcases := []struct { + policy *mockUnnamedResourcePolicy + allowsUnnamedResources bool + }{ + {nil, false}, + {&mockUnnamedResourcePolicy{allowEverything: false}, false}, + {&mockUnnamedResourcePolicy{allowEverything: true}, true}, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.policy.String(), func(t *testing.T) { + t.Parallel() + // start at 4 for directRefEnabledVersion + testLogicRange(t, 4, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + tx.Accounts = nil + tx.ForeignApps = nil + tx.ForeignAssets = nil + tx.Boxes = []transactions.BoxRef{{}} // provide write budget, but not access + + if tc.policy != nil { + tc.policy.events = nil + ep.UnnamedResources = tc.policy + } + + var otherAccount basics.Address + crypto.RandBytes(otherAccount[:]) + + ledger.NewAccount(otherAccount, 1) + ledger.NewApp(tx.Sender, 500, basics.AppParams{}) + ledger.NewGlobal(500, "global key", basics.TealValue{ + Type: basics.TealBytesType, + Bytes: "global value", + }) + + ledger.NewLocals(otherAccount, 500) + ledger.NewLocal(otherAccount, 500, "local key", basics.TealValue{ + Type: basics.TealBytesType, + Bytes: "local value", + }) + + ledger.NewAsset(tx.Sender, 501, basics.AssetParams{Total: 501}) + ledger.NewHolding(otherAccount, 501, 2, false) + + ledger.NewApp(tx.Sender, tx.ApplicationID, basics.AppParams{}) + err := ledger.NewBox(tx.ApplicationID, "box key", []byte("box value"), tx.ApplicationID.Address()) + require.NoError(t, err) + + // Unaccessible account + source := fmt.Sprintf("addr %s; balance; int 1; ==", otherAccount) + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + expectedEvents := []unnamedResourcePolicyEvent{availableAccountEvent(otherAccount)} + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + testApp(t, source, ep, fmt.Sprintf("invalid Account reference %s", otherAccount)) + } + + // Unaccessible app + source = `int 500; byte "global key"; app_global_get_ex; assert; byte "global value"; ==` + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + expectedEvents := []unnamedResourcePolicyEvent{availableAppEvent(500)} + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + testApp(t, source, ep, "unavailable App 500") + } + if ep.Proto.LogicSigVersion >= 5 { + // app_params_get introduced + source = "int 500; app_params_get AppCreator; assert; txn Sender; ==" + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + expectedEvents := []unnamedResourcePolicyEvent{availableAppEvent(500)} + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + testApp(t, source, ep, "unavailable App 500") + } + } + if ep.Proto.LogicSigVersion >= 6 { + // inner app calls introduced + source = "itxn_begin; int 500; itxn_field ApplicationID; int 1" + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + expectedEvents := []unnamedResourcePolicyEvent{availableAppEvent(500)} + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + testApp(t, source, ep, "unavailable App 500") + } + } + + // Unaccessible app local + source = fmt.Sprintf(`addr %s; int 500; byte "local key"; app_local_get_ex; assert; byte "local value"; ==`, otherAccount) + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + var expectedEvents []unnamedResourcePolicyEvent + if ep.Proto.LogicSigVersion < 9 { + // before resource sharing + expectedEvents = []unnamedResourcePolicyEvent{ + availableAccountEvent(otherAccount), + availableAppEvent(500), + } + } else { + // after resource sharing + expectedEvents = []unnamedResourcePolicyEvent{ + availableAppEvent(500), + availableAppEvent(500), + availableAccountEvent(otherAccount), + allowsLocalEvent(otherAccount, 500), + } + // The duplicate app events above are actually expected. This is because + // EvalContext.localsReference calls resolveApp, then allowsLocals, + // which calls resolveApp again. + } + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + problem := "unavailable Account %s" + if ep.Proto.LogicSigVersion < 9 { + // Message is difference before sharedResourcesVersion + problem = "invalid Account reference %s" + } + testApp(t, source, ep, fmt.Sprintf(problem, otherAccount)) + } + + // Unaccessible asset + source = "int 501; asset_params_get AssetTotal; assert; int 501; ==" + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + expectedEvents := []unnamedResourcePolicyEvent{availableAssetEvent(501)} + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + testApp(t, source, ep, "unavailable Asset 501") + } + if ep.Proto.LogicSigVersion >= 5 { + // inner calls introduced + source = "itxn_begin; int 501; itxn_field XferAsset; int 1" + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + expectedEvents := []unnamedResourcePolicyEvent{availableAssetEvent(501)} + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + testApp(t, source, ep, "unavailable Asset 501") + } + } + + // Unaccessible asset holding + source = fmt.Sprintf(`addr %s; int 501; asset_holding_get AssetBalance; assert; int 2; ==`, otherAccount) + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + var expectedEvents []unnamedResourcePolicyEvent + if ep.Proto.LogicSigVersion < 9 { + // before resource sharing + expectedEvents = []unnamedResourcePolicyEvent{ + availableAccountEvent(otherAccount), + availableAssetEvent(501), + } + } else { + // after resource sharing + expectedEvents = []unnamedResourcePolicyEvent{ + availableAssetEvent(501), + availableAccountEvent(otherAccount), + availableAssetEvent(501), + allowsHoldingEvent(otherAccount, 501), + } + // The duplicate asset events above are actually expected. This is + // because EvalContext.holdingReference calls resolveAsset, then + // allowsHolding, which calls resolveAsset again. + } + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + problem := "unavailable Account %s" + if ep.Proto.LogicSigVersion < 9 { + // Message is different before sharedResourcesVersion + problem = "invalid Account reference %s" + } + testApp(t, source, ep, fmt.Sprintf(problem, otherAccount)) + } + + // Unaccessible box + if ep.Proto.LogicSigVersion >= 8 { + // Boxes introduced + source = `byte "box key"; box_get; assert; byte "box value"; ==` + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + expectedEvents := []unnamedResourcePolicyEvent{availableBoxEvent(tx.ApplicationID, "box key", BoxReadOperation, 0)} + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + testApp(t, source, ep, fmt.Sprintf("invalid Box reference %#x", "box key")) + } + source = `byte "new box"; int 1; box_create` + if tc.allowsUnnamedResources { + testApp(t, source, ep) + if tc.policy != nil { + expectedEvents := []unnamedResourcePolicyEvent{availableBoxEvent(tx.ApplicationID, "new box", BoxCreateOperation, 1)} + assert.Equal(t, expectedEvents, tc.policy.events) + tc.policy.events = nil + } + } else { + testApp(t, source, ep, fmt.Sprintf("invalid Box reference %#x", "new box")) + } + } + }) + }) + } +} + func TestEnumFieldErrors(t *testing.T) { // nolint:paralleltest // manipulates txnFieldSpecs partitiontest.PartitionTest(t) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index ee049d9d9c..2947fe6d4d 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -95,6 +95,7 @@ func makeTestProto(opts ...protoOpt) *config.ConsensusParams { EnableFeePooling: true, // Chosen to be different from one another and from normal proto + MaxAppBoxReferences: 2, MaxAppTxnAccounts: 3, MaxAppTxnForeignApps: 5, MaxAppTxnForeignAssets: 6, diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 1c97be80a8..0dd9bf8dfa 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -22,6 +22,7 @@ import ( "strconv" "strings" + "github.com/algorand/go-algorand/data/basics" "golang.org/x/exp/maps" ) @@ -85,18 +86,12 @@ type linearCost struct { depth int } -// divCeil provides `math.Ceil` semantics using integer division. The technique avoids slower floating point operations as suggested in https://stackoverflow.com/a/2745086. -// The method does _not_ check for divide-by-zero. -func divCeil(numerator int, denominator int) int { - return (numerator + denominator - 1) / denominator -} - func (lc *linearCost) compute(stack []stackValue) int { cost := lc.baseCost if lc.chunkCost != 0 && lc.chunkSize != 0 { - // Uses divCeil rather than (count/chunkSize) to match how Ethereum discretizes hashing costs. + // Uses basics.DivCeil rather than (count/chunkSize) to match how Ethereum discretizes hashing costs. count := len(stack[len(stack)-1-lc.depth].Bytes) - cost += lc.chunkCost * divCeil(count, lc.chunkSize) + cost += lc.chunkCost * basics.DivCeil(count, lc.chunkSize) } return cost } diff --git a/data/transactions/logic/resources.go b/data/transactions/logic/resources.go index 00045100ec..c407af7044 100644 --- a/data/transactions/logic/resources.go +++ b/data/transactions/logic/resources.go @@ -53,7 +53,7 @@ type resources struct { // of the box - has it been modified in this txngroup? If yes, the size of // the box counts against the group writeBudget. So delete is NOT a dirtying // operation. - boxes map[boxRef]bool + boxes map[BoxRef]bool // dirtyBytes maintains a running count of the number of dirty bytes in `boxes` dirtyBytes uint64 @@ -173,15 +173,21 @@ func (cx *EvalContext) allowsHolding(addr basics.Address, ai basics.AssetIndex) } // If the address was "created" by making its app in this group, then allow for available assets. for created := range r.createdApps { - if cx.getApplicationAddress(created) == addr { + if cx.GetApplicationAddress(created) == addr { return cx.availableAsset(ai) } } // If the current txn is a creation, the new appID won't be in r.createdApps // yet, but it should get the same special treatment. - if cx.txn.Txn.ApplicationID == 0 && cx.getApplicationAddress(cx.appID) == addr { + if cx.txn.Txn.ApplicationID == 0 && cx.GetApplicationAddress(cx.appID) == addr { return cx.availableAsset(ai) } + if cx.UnnamedResources != nil { + // Ensure that the account and asset are available before consulting cx.UnnamedResources.AllowsHolding. + // This way cx.UnnamedResources.AllowsHolding only needs to make a decision about the asset holding + // being available, not about the component resources. + return cx.availableAccount(addr) && cx.availableAsset(ai) && cx.UnnamedResources.AllowsHolding(addr, ai) + } return false } @@ -201,13 +207,19 @@ func (cx *EvalContext) allowsLocals(addr basics.Address, ai basics.AppIndex) boo // All locals of created app accounts are available for created := range r.createdApps { - if cx.getApplicationAddress(created) == addr { + if cx.GetApplicationAddress(created) == addr { return cx.availableApp(ai) } } - if cx.txn.Txn.ApplicationID == 0 && cx.getApplicationAddress(cx.appID) == addr { + if cx.txn.Txn.ApplicationID == 0 && cx.GetApplicationAddress(cx.appID) == addr { return cx.availableApp(ai) } + if cx.UnnamedResources != nil { + // Ensure that the account and app are available before consulting cx.UnnamedResources.AllowsLocal. + // This way cx.UnnamedResources.AllowsLocal only needs to make a decision about the app local + // being available, not about the component resources. + return cx.availableApp(ai) && cx.availableAccount(addr) && cx.UnnamedResources.AllowsLocal(addr, ai) + } return false } @@ -279,11 +291,11 @@ func (r *resources) fillApplicationCall(ep *EvalParams, hdr *transactions.Header // apps available, because that is already handled by looking at // `createdApps`. if id := tx.ApplicationID; id != 0 { - txAccounts = append(txAccounts, ep.getApplicationAddress(id)) + txAccounts = append(txAccounts, ep.GetApplicationAddress(id)) r.sharedApps[id] = struct{}{} } for _, id := range tx.ForeignApps { - txAccounts = append(txAccounts, ep.getApplicationAddress(id)) + txAccounts = append(txAccounts, ep.GetApplicationAddress(id)) r.sharedApps[id] = struct{}{} } for _, address := range txAccounts { @@ -317,7 +329,7 @@ func (r *resources) fillApplicationCall(ep *EvalParams, hdr *transactions.Header // now than after returning a nil. app = tx.ForeignApps[br.Index-1] // shift for the 0=this convention } - r.boxes[boxRef{app, string(br.Name)}] = false + r.boxes[BoxRef{app, string(br.Name)}] = false } } @@ -335,10 +347,10 @@ func (cx *EvalContext) allowsApplicationCall(hdr *transactions.Header, tx *trans txAccounts = append(txAccounts, hdr.Sender) txAccounts = append(txAccounts, tx.Accounts...) if id := tx.ApplicationID; id != 0 { - txAccounts = append(txAccounts, cx.getApplicationAddress(id)) + txAccounts = append(txAccounts, cx.GetApplicationAddress(id)) } for _, id := range tx.ForeignApps { - txAccounts = append(txAccounts, cx.getApplicationAddress(id)) + txAccounts = append(txAccounts, cx.GetApplicationAddress(id)) } for _, address := range txAccounts { for _, id := range tx.ForeignAssets { diff --git a/ledger/simulation/resources.go b/ledger/simulation/resources.go new file mode 100644 index 0000000000..ff4d907273 --- /dev/null +++ b/ledger/simulation/resources.go @@ -0,0 +1,607 @@ +// 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 simulation + +import ( + "fmt" + "math" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/protocol" +) + +// ResourceTracker calculates the additional resources that a transaction or group could use, and +// it tracks any referenced unnamed resources that fit within those limits. +type ResourceTracker struct { + Accounts map[basics.Address]struct{} + MaxAccounts int + + Assets map[basics.AssetIndex]struct{} + MaxAssets int + + Apps map[basics.AppIndex]struct{} + MaxApps int + + // The map value is the size of the box loaded from the ledger prior to any writes. This is used + // to track the box read budget. + Boxes map[logic.BoxRef]uint64 + MaxBoxes int + NumEmptyBoxRefs int + maxWriteBudget uint64 + + MaxTotalRefs int + + AssetHoldings map[ledgercore.AccountAsset]struct{} + AppLocals map[ledgercore.AccountApp]struct{} + MaxCrossProductReferences int +} + +func makeTxnResourceTracker(txn *transactions.Transaction, proto *config.ConsensusParams) ResourceTracker { + if txn.Type != protocol.ApplicationCallTx { + return ResourceTracker{} + } + return ResourceTracker{ + // Use MaxAppTxnAccounts + MaxAppTxnForeignApps for the account limit because app references + // also make their accounts available, and since we can't know if an unknown account is an + // app account, we assume it is. + MaxAccounts: proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps - len(txn.Accounts) - len(txn.ForeignApps), + MaxAssets: proto.MaxAppTxnForeignAssets - len(txn.ForeignAssets), + MaxApps: proto.MaxAppTxnForeignApps - len(txn.ForeignApps), + MaxBoxes: proto.MaxAppBoxReferences - len(txn.Boxes), + MaxTotalRefs: proto.MaxAppTotalTxnReferences - len(txn.Accounts) - len(txn.ForeignAssets) - len(txn.ForeignApps) - len(txn.Boxes), + } +} + +func makeGlobalResourceTracker(perTxnResources []ResourceTracker, nonAppCalls int, proto *config.ConsensusParams) ResourceTracker { + // Calculate the maximum number of cross-product resources that can be accessed by one app call + // under normal circumstances. This is calculated using the case of an app call with a full set + // of foreign apps. Including the app being called, there are (MaxAppTxnForeignApps + 1) apps, + // crossed with (MaxAppTxnForeignAssets + 2) accounts (the called app's account, the sender's + // account, and the foreign app accounts). We then subtract out the app local of sender's + // account and the called app, and each app local of an app and its own account, or + // (MaxAppTxnForeignApps + 2) references. So we end up with: + // + // (MaxAppTxnForeignApps + 1) * (MaxAppTxnForeignApps + 2) - (MaxAppTxnForeignApps + 2) + // <=> MaxAppTxnForeignApps^2 + 3*MaxAppTxnForeignApps + 2 - MaxAppTxnForeignApps - 2 + // <=> MaxAppTxnForeignApps^2 + 2*MaxAppTxnForeignApps + // <=> MaxAppTxnForeignApps * (MaxAppTxnForeignApps + 2) + maxCrossProductsPerAppCall := proto.MaxAppTxnForeignApps * (proto.MaxAppTxnForeignApps + 2) + unusedTxns := proto.MaxTxGroupSize - len(perTxnResources) + globalResources := ResourceTracker{ + MaxCrossProductReferences: maxCrossProductsPerAppCall * (proto.MaxTxGroupSize - nonAppCalls), + // If there are fewer than MaxTxGroupSize transactions, then we can make more resources + // available as if the remaining transactions were empty app calls. + MaxAccounts: unusedTxns * (proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps), + MaxAssets: unusedTxns * proto.MaxAppTxnForeignAssets, + MaxApps: unusedTxns * proto.MaxAppTxnForeignApps, + MaxBoxes: unusedTxns * proto.MaxAppBoxReferences, + MaxTotalRefs: unusedTxns * proto.MaxAppTotalTxnReferences, + } + for i := range perTxnResources { + globalResources.MaxAccounts += perTxnResources[i].MaxAccounts + globalResources.MaxAssets += perTxnResources[i].MaxAssets + globalResources.MaxApps += perTxnResources[i].MaxApps + globalResources.MaxBoxes += perTxnResources[i].MaxBoxes + globalResources.MaxTotalRefs += perTxnResources[i].MaxTotalRefs + } + return globalResources +} + +func (a *ResourceTracker) removePrivateFields() { + a.maxWriteBudget = 0 +} + +// HasResources returns true if the tracker has any resources. +func (a *ResourceTracker) HasResources() bool { + return len(a.Accounts) != 0 || len(a.Assets) != 0 || len(a.Apps) != 0 || len(a.Boxes) != 0 || len(a.AssetHoldings) != 0 || len(a.AppLocals) != 0 +} + +func (a *ResourceTracker) hasAccount(addr basics.Address, ep *logic.EvalParams, programVersion uint64) bool { + // nil map lookup is ok + _, ok := a.Accounts[addr] + if ok { + return true + } + if programVersion >= 7 { // appAddressAvailableVersion + for app := range a.Apps { + if ep.GetApplicationAddress(app) == addr { + return true + } + } + } + return false +} + +func (a *ResourceTracker) addAccount(addr basics.Address) bool { + if len(a.Accounts) >= a.MaxAccounts || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + return false + } + if a.Accounts == nil { + a.Accounts = make(map[basics.Address]struct{}) + } + a.Accounts[addr] = struct{}{} + return true +} + +func (a *ResourceTracker) removeAccountSlot() bool { + if len(a.Accounts) >= a.MaxAccounts || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + return false + } + a.MaxAccounts-- + a.MaxTotalRefs-- + return true +} + +func (a *ResourceTracker) hasAsset(aid basics.AssetIndex) bool { + // nil map lookup is ok + _, ok := a.Assets[aid] + return ok +} + +func (a *ResourceTracker) addAsset(aid basics.AssetIndex) bool { + if len(a.Assets) >= a.MaxAssets || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + return false + } + if a.Assets == nil { + a.Assets = make(map[basics.AssetIndex]struct{}) + } + a.Assets[aid] = struct{}{} + return true +} + +func (a *ResourceTracker) removeAssetSlot() bool { + if len(a.Assets) >= a.MaxAssets || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + return false + } + a.MaxAssets-- + a.MaxTotalRefs-- + return true +} + +func (a *ResourceTracker) hasApp(aid basics.AppIndex) bool { + // nil map lookup is ok + _, ok := a.Apps[aid] + return ok +} + +func (a *ResourceTracker) addApp(aid basics.AppIndex, ep *logic.EvalParams, programVersion uint64) bool { + if len(a.Apps) >= a.MaxApps { + return false + } + + if programVersion >= 7 { // appAddressAvailableVersion + appAddr := ep.GetApplicationAddress(aid) + // nil map lookup is ok + _, ok := a.Accounts[appAddr] + if ok { + // remove the account reference, since it will be made available by this app reference + delete(a.Accounts, appAddr) + if len(a.Accounts) == 0 { + a.Accounts = nil + } + } + } + + if len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + return false + } + if a.Apps == nil { + a.Apps = make(map[basics.AppIndex]struct{}) + } + a.Apps[aid] = struct{}{} + return true +} + +func (a *ResourceTracker) removeAppSlot() bool { + if len(a.Apps) >= a.MaxApps || len(a.Accounts)+len(a.Assets)+len(a.Apps)+len(a.Boxes)+a.NumEmptyBoxRefs >= a.MaxTotalRefs { + return false + } + a.MaxApps-- + a.MaxTotalRefs-- + if a.MaxAccounts > 0 { + a.MaxAccounts-- + } + return true +} + +func (a *ResourceTracker) hasBox(app basics.AppIndex, name string) bool { + // nil map lookup is ok + _, ok := a.Boxes[logic.BoxRef{App: app, Name: name}] + return ok +} + +func (a *ResourceTracker) addBox(app basics.AppIndex, name string, readSize, additionalReadBudget, bytesPerBoxRef uint64) bool { + usedReadBudget := basics.AddSaturate(a.usedBoxReadBudget(), readSize) + // Adding bytesPerBoxRef to account for the new IO budget from adding an additional box ref + readBudget := additionalReadBudget + a.boxIOBudget(bytesPerBoxRef) + bytesPerBoxRef + + var emptyRefs int + if usedReadBudget > readBudget { + // We need to allocate more empty box refs to increase the read budget + neededBudget := usedReadBudget - readBudget + emptyRefsU64 := basics.DivCeil(neededBudget, bytesPerBoxRef) + if emptyRefsU64 > math.MaxInt { + // This should never happen, but if we overflow an int with the number of extra pages + // needed, we can't support this request. + return false + } + emptyRefs = int(emptyRefsU64) + } else if a.NumEmptyBoxRefs != 0 { + surplusBudget := readBudget - usedReadBudget + if surplusBudget >= bytesPerBoxRef && readBudget-bytesPerBoxRef >= a.maxWriteBudget { + // If we already have enough read budget, remove one empty ref to be replaced by the new + // named box ref. + emptyRefs = -1 + } + } + + if emptyRefs >= a.MaxBoxes-len(a.Boxes)-a.NumEmptyBoxRefs || emptyRefs >= a.MaxTotalRefs-len(a.Accounts)-len(a.Assets)-len(a.Apps)-len(a.Boxes)-a.NumEmptyBoxRefs { + return false + } + if a.Boxes == nil { + a.Boxes = make(map[logic.BoxRef]uint64) + } + a.Boxes[logic.BoxRef{App: app, Name: name}] = readSize + a.NumEmptyBoxRefs += emptyRefs + return true +} + +func (a *ResourceTracker) addEmptyBoxRefsForWriteBudget(usedWriteBudget, additionalWriteBudget, bytesPerBoxRef uint64) bool { + writeBudget := additionalWriteBudget + a.boxIOBudget(bytesPerBoxRef) + if usedWriteBudget > writeBudget { + // Need to allocate more empty box refs + overspend := usedWriteBudget - writeBudget + extraRefsU64 := basics.DivCeil(overspend, bytesPerBoxRef) + if extraRefsU64 > math.MaxInt { + // This should never happen, but if we overflow an int with the number of extra pages + // needed, we can't support this request. + return false + } + extraRefs := int(extraRefsU64) + if extraRefs > a.MaxBoxes-len(a.Boxes)-a.NumEmptyBoxRefs || extraRefs > a.MaxTotalRefs-len(a.Accounts)-len(a.Assets)-len(a.Apps)-len(a.Boxes)-a.NumEmptyBoxRefs { + return false + } + a.NumEmptyBoxRefs += extraRefs + } + if a.maxWriteBudget < usedWriteBudget { + a.maxWriteBudget = usedWriteBudget + } + return true +} + +func (a *ResourceTracker) boxIOBudget(bytesPerBoxRef uint64) uint64 { + return uint64(len(a.Boxes)+a.NumEmptyBoxRefs) * bytesPerBoxRef +} + +func (a *ResourceTracker) usedBoxReadBudget() uint64 { + var budget uint64 + for _, readSize := range a.Boxes { + budget += readSize + } + return budget +} + +func (a *ResourceTracker) maxPossibleUnnamedBoxes() int { + numBoxes := a.MaxTotalRefs - len(a.Accounts) - len(a.Assets) - len(a.Apps) + if a.MaxBoxes < numBoxes { + numBoxes = a.MaxBoxes + } + return numBoxes +} + +func (a *ResourceTracker) hasHolding(addr basics.Address, aid basics.AssetIndex) bool { + // nil map lookup is ok + _, ok := a.AssetHoldings[ledgercore.AccountAsset{Address: addr, Asset: aid}] + return ok +} + +func (a *ResourceTracker) addHolding(addr basics.Address, aid basics.AssetIndex) bool { + if len(a.AssetHoldings)+len(a.AppLocals) >= a.MaxCrossProductReferences { + return false + } + if a.AssetHoldings == nil { + a.AssetHoldings = make(map[ledgercore.AccountAsset]struct{}) + } + a.AssetHoldings[ledgercore.AccountAsset{Address: addr, Asset: aid}] = struct{}{} + return true +} + +func (a *ResourceTracker) hasLocal(addr basics.Address, aid basics.AppIndex, ep *logic.EvalParams) bool { + if ep.GetApplicationAddress(aid) == addr { + // The app local of an app and its own account is always available, so don't bother recording it. + return true + } + // nil map lookup is ok + _, ok := a.AppLocals[ledgercore.AccountApp{Address: addr, App: aid}] + return ok +} + +func (a *ResourceTracker) addLocal(addr basics.Address, aid basics.AppIndex) bool { + if len(a.AssetHoldings)+len(a.AppLocals) >= a.MaxCrossProductReferences { + return false + } + if a.AppLocals == nil { + a.AppLocals = make(map[ledgercore.AccountApp]struct{}) + } + a.AppLocals[ledgercore.AccountApp{Address: addr, App: aid}] = struct{}{} + return true +} + +// groupResourceTracker calculates the additional resources that a transaction group could use, +// and it tracks any referenced unnamed resources that fit within those limits. +type groupResourceTracker struct { + // globalResources specifies global resources for the entire group. + globalResources ResourceTracker + + // localTxnResources specifies local resources for each transaction in the group. This will only + // be populated if a top-level transaction executes AVM programs prior to v9 (when resource + // sharing was added). + localTxnResources []ResourceTracker + + startingBoxes int +} + +func makeGroupResourceTracker(txns []transactions.SignedTxnWithAD, proto *config.ConsensusParams) groupResourceTracker { + var startingBoxes int + var nonAppCalls int + localTxnResources := make([]ResourceTracker, len(txns)) + for i := range txns { + localTxnResources[i] = makeTxnResourceTracker(&txns[i].Txn, proto) + startingBoxes += len(txns[i].Txn.Boxes) + if txns[i].Txn.Type != protocol.ApplicationCallTx { + nonAppCalls++ + } + } + return groupResourceTracker{ + globalResources: makeGlobalResourceTracker(localTxnResources, nonAppCalls, proto), + localTxnResources: localTxnResources, + startingBoxes: startingBoxes, + } +} + +func (a *groupResourceTracker) hasAccount(addr basics.Address, ep *logic.EvalParams, programVersion uint64, globalSharing bool, gi int) bool { + if globalSharing { + for i := range a.localTxnResources { + if a.localTxnResources[i].hasAccount(addr, ep, programVersion) { + return true + } + } + return a.globalResources.hasAccount(addr, ep, programVersion) + } + return a.localTxnResources[gi].hasAccount(addr, ep, programVersion) +} + +func (a *groupResourceTracker) addAccount(addr basics.Address, globalSharing bool, gi int) bool { + if globalSharing { + return a.globalResources.addAccount(addr) + } + if !a.localTxnResources[gi].addAccount(addr) { + return false + } + if a.globalResources.hasAccount(addr, nil, 0) { + // It's redundant to list a resources in both the global and local tracker, so remove it + // from global. The below call to a.globalResources.removeAccountSlot() will revert the + // changes to a.globalResources.MaxAccounts and a.globalResources.MaxTotalRefs. + delete(a.globalResources.Accounts, addr) + a.globalResources.MaxAccounts++ + a.globalResources.MaxTotalRefs++ + } + // This ensures that the global tracker reduces in size if a resource is assigned locally. + if a.globalResources.removeAccountSlot() { + return true + } + // Undo the local assignment if global is full. + delete(a.localTxnResources[gi].Accounts, addr) + return false +} + +func (a *groupResourceTracker) hasAsset(aid basics.AssetIndex, globalSharing bool, gi int) bool { + if globalSharing { + for i := range a.localTxnResources { + if a.localTxnResources[i].hasAsset(aid) { + return true + } + } + return a.globalResources.hasAsset(aid) + } + return a.localTxnResources[gi].hasAsset(aid) +} + +func (a *groupResourceTracker) addAsset(aid basics.AssetIndex, globalSharing bool, gi int) bool { + if globalSharing { + return a.globalResources.addAsset(aid) + } + if !a.localTxnResources[gi].addAsset(aid) { + return false + } + if a.globalResources.hasAsset(aid) { + // It's redundant to list a resources in both the global and local tracker, so remove it + // from global. The below call to a.globalResources.removeAssetSlot() will revert the + // changes to a.globalResources.MaxAssets and a.globalResources.MaxTotalRefs. + delete(a.globalResources.Assets, aid) + a.globalResources.MaxAssets++ + a.globalResources.MaxTotalRefs++ + } + // This ensures that the global tracker reduces in size if a resource is assigned locally. + if a.globalResources.removeAssetSlot() { + return true + } + // Undo the local assignment if global is full. + delete(a.localTxnResources[gi].Assets, aid) + return false +} + +func (a *groupResourceTracker) hasApp(aid basics.AppIndex, globalSharing bool, gi int) bool { + if globalSharing { + for i := range a.localTxnResources { + if a.localTxnResources[i].hasApp(aid) { + return true + } + } + return a.globalResources.hasApp(aid) + } + return a.localTxnResources[gi].hasApp(aid) +} + +func (a *groupResourceTracker) addApp(aid basics.AppIndex, ep *logic.EvalParams, programVersion uint64, globalSharing bool, gi int) bool { + if globalSharing { + return a.globalResources.addApp(aid, ep, programVersion) + } + if !a.localTxnResources[gi].addApp(aid, ep, programVersion) { + return false + } + if a.globalResources.hasApp(aid) { + // It's redundant to list a resources in both the global and local tracker, so remove it + // from global. The below call to a.globalResources.removeAppSlot() will revert the changes + // to a.globalResources.MaxApps and a.globalResources.MaxTotalRefs. + delete(a.globalResources.Apps, aid) + a.globalResources.MaxApps++ + a.globalResources.MaxTotalRefs++ + } + // This ensures that the global tracker reduces in size if a resource is assigned locally. + if a.globalResources.removeAppSlot() { + return true + } + // Undo the local assignment if global is full. + delete(a.localTxnResources[gi].Apps, aid) + return false +} + +func (a *groupResourceTracker) hasBox(app basics.AppIndex, name string) bool { + // All boxes are global, never consult localTxnResources + return a.globalResources.hasBox(app, name) +} + +func (a *groupResourceTracker) addBox(app basics.AppIndex, name string, readSize, additionalReadBudget, bytesPerBoxRef uint64) bool { + // All boxes are global, never consult localTxnResources + return a.globalResources.addBox(app, name, readSize, additionalReadBudget, bytesPerBoxRef) +} + +func (a *groupResourceTracker) reconcileBoxWriteBudget(used uint64, bytesPerBoxRef uint64) error { + if !a.globalResources.addEmptyBoxRefsForWriteBudget(used, uint64(a.startingBoxes)*bytesPerBoxRef, bytesPerBoxRef) { + return fmt.Errorf("cannot add extra box refs to satisfy write budget of %d bytes", used) + } + return nil +} + +func (a *groupResourceTracker) maxPossibleBoxIOBudget(bytesPerBoxRef uint64) uint64 { + return basics.MulSaturate( + uint64(a.startingBoxes+a.globalResources.maxPossibleUnnamedBoxes()), + bytesPerBoxRef, + ) +} + +func (a *groupResourceTracker) hasHolding(addr basics.Address, aid basics.AssetIndex) bool { + // All cross-products are global, never consult localTxnResources + return a.globalResources.hasHolding(addr, aid) +} + +func (a *groupResourceTracker) addHolding(addr basics.Address, aid basics.AssetIndex) bool { + // All cross-products are global, never consult localTxnResources + return a.globalResources.addHolding(addr, aid) +} + +func (a *groupResourceTracker) hasLocal(addr basics.Address, aid basics.AppIndex, ep *logic.EvalParams) bool { + // All cross-products are global, never consult localTxnResources + return a.globalResources.hasLocal(addr, aid, ep) +} + +func (a *groupResourceTracker) addLocal(addr basics.Address, aid basics.AppIndex) bool { + // All cross-products are global, never consult localTxnResources + return a.globalResources.addLocal(addr, aid) +} + +type resourcePolicy struct { + tracker groupResourceTracker + ep *logic.EvalParams + initialBoxSurplusReadBudget *uint64 + + txnRootIndex int + programVersion uint64 + globalSharing bool +} + +func newResourcePolicy(ep *logic.EvalParams, groupResult *TxnGroupResult) *resourcePolicy { + policy := resourcePolicy{ + tracker: makeGroupResourceTracker(ep.TxnGroup, ep.Proto), + ep: ep, + } + groupResult.UnnamedResourcesAccessed = &policy.tracker.globalResources + for i := range groupResult.Txns { + groupResult.Txns[i].UnnamedResourcesAccessed = &policy.tracker.localTxnResources[i] + } + return &policy +} + +func (p *resourcePolicy) AvailableAccount(addr basics.Address) bool { + if p.tracker.hasAccount(addr, p.ep, p.programVersion, p.globalSharing, p.txnRootIndex) { + return true + } + return p.tracker.addAccount(addr, p.globalSharing, p.txnRootIndex) +} + +func (p *resourcePolicy) AvailableAsset(aid basics.AssetIndex) bool { + if p.tracker.hasAsset(aid, p.globalSharing, p.txnRootIndex) { + return true + } + return p.tracker.addAsset(aid, p.globalSharing, p.txnRootIndex) +} + +func (p *resourcePolicy) AvailableApp(aid basics.AppIndex) bool { + if p.tracker.hasApp(aid, p.globalSharing, p.txnRootIndex) { + return true + } + return p.tracker.addApp(aid, p.ep, p.programVersion, p.globalSharing, p.txnRootIndex) +} + +func (p *resourcePolicy) AllowsHolding(addr basics.Address, aid basics.AssetIndex) bool { + // holdings are only checked if globalSharing is true + if p.tracker.hasHolding(addr, aid) { + return true + } + return p.tracker.addHolding(addr, aid) +} + +func (p *resourcePolicy) AllowsLocal(addr basics.Address, aid basics.AppIndex) bool { + // locals are only checked if globalSharing is true + if p.tracker.hasLocal(addr, aid, p.ep) { + return true + } + return p.tracker.addLocal(addr, aid) +} + +func (p *resourcePolicy) AvailableBox(app basics.AppIndex, name string, operation logic.BoxOperation, createSize uint64) bool { + if p.tracker.hasBox(app, name) { + // We actually never expect this to happen, since the EvalContext remembers each box in + // order to track their dirty bytes, and it won't invoke this method if it's already seen + // the box. + return true + } + box, ok, err := p.ep.Ledger.GetBox(app, name) + if err != nil { + panic(err.Error()) + } + var readSize uint64 + if ok { + readSize = uint64(len(box)) + } + return p.tracker.addBox(app, name, readSize, *p.initialBoxSurplusReadBudget, p.ep.Proto.BytesPerBoxReference) +} diff --git a/ledger/simulation/resources_test.go b/ledger/simulation/resources_test.go new file mode 100644 index 0000000000..8afcadfa6e --- /dev/null +++ b/ledger/simulation/resources_test.go @@ -0,0 +1,298 @@ +// 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 simulation + +import ( + "testing" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestAppAccounts(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + proto := config.Consensus[protocol.ConsensusFuture] + txns := make([]transactions.SignedTxnWithAD, 1) + txns[0].Txn.Type = protocol.ApplicationCallTx + ep := logic.NewAppEvalParams(txns, &proto, nil) + + appID := basics.AppIndex(12345) + appAccount := appID.Address() + + for _, globalSharing := range []bool{false, true} { + groupAssignment := makeGroupResourceTracker(txns, &proto) + var resources *ResourceTracker + if globalSharing { + resources = &groupAssignment.globalResources + } else { + resources = &groupAssignment.localTxnResources[0] + } + + require.Empty(t, resources.Apps) + require.Empty(t, resources.Accounts) + + require.False(t, groupAssignment.hasApp(appID, globalSharing, 0)) + require.False(t, groupAssignment.hasAccount(appAccount, ep, 7, globalSharing, 0)) + + require.True(t, groupAssignment.addAccount(appAccount, globalSharing, 0)) + + require.Empty(t, resources.Apps) + require.Equal(t, map[basics.Address]struct{}{ + appAccount: {}, + }, resources.Accounts) + + require.False(t, groupAssignment.hasApp(appID, globalSharing, 0)) + require.True(t, groupAssignment.hasAccount(appAccount, ep, 7, globalSharing, 0)) + + require.True(t, groupAssignment.addApp(appID, ep, 7, globalSharing, 0)) + + require.Equal(t, map[basics.AppIndex]struct{}{ + appID: {}, + }, resources.Apps) + require.Empty(t, resources.Accounts) + + require.True(t, groupAssignment.hasApp(appID, globalSharing, 0)) + require.True(t, groupAssignment.hasAccount(appAccount, ep, 7, globalSharing, 0)) + + require.False(t, groupAssignment.hasAccount(appAccount, ep, 6, globalSharing, 0)) + } +} + +func TestGlobalVsLocalResources(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + proto := config.Consensus[protocol.ConsensusFuture] + txns := make([]transactions.SignedTxnWithAD, 3) + txns[0].Txn.Type = protocol.ApplicationCallTx + txns[1].Txn.Type = protocol.ApplicationCallTx + txns[2].Txn.Type = protocol.ApplicationCallTx + ep := logic.NewAppEvalParams(txns, &proto, nil) + + // For this test, we assume only the txn at index 1 supports group sharing. + + t.Run("accounts", func(t *testing.T) { + account1 := basics.Address{1, 1, 1} + account2 := basics.Address{2, 2, 2} + + groupAssignment := makeGroupResourceTracker(txns, &proto) + + require.Empty(t, groupAssignment.globalResources.Accounts) + require.Empty(t, groupAssignment.localTxnResources[0].Accounts) + require.Empty(t, groupAssignment.localTxnResources[1].Accounts) + require.Empty(t, groupAssignment.localTxnResources[2].Accounts) + + require.True(t, groupAssignment.addAccount(account1, false, 0)) + + // account1 should be present in txn 0's local assignment + require.Empty(t, groupAssignment.globalResources.Accounts) + require.Equal(t, map[basics.Address]struct{}{ + account1: {}, + }, groupAssignment.localTxnResources[0].Accounts) + require.Empty(t, groupAssignment.localTxnResources[1].Accounts) + require.Empty(t, groupAssignment.localTxnResources[2].Accounts) + + // Txn 1, which used global resources, can see account1 + require.True(t, groupAssignment.hasAccount(account1, ep, 7, true, 1)) + + require.True(t, groupAssignment.addAccount(account2, true, 1)) + + // account2 should be present in the global assignment + require.Equal(t, map[basics.Address]struct{}{ + account2: {}, + }, groupAssignment.globalResources.Accounts) + require.Equal(t, map[basics.Address]struct{}{ + account1: {}, + }, groupAssignment.localTxnResources[0].Accounts) + require.Empty(t, groupAssignment.localTxnResources[1].Accounts) + require.Empty(t, groupAssignment.localTxnResources[2].Accounts) + + // Txn 2, which does not use global resources, cannot see either account + require.False(t, groupAssignment.hasAccount(account1, ep, 7, false, 2)) + require.False(t, groupAssignment.hasAccount(account2, ep, 7, false, 2)) + + require.True(t, groupAssignment.addAccount(account1, false, 2)) + + // account1 should be present in txn 2's local assignment + require.Equal(t, map[basics.Address]struct{}{ + account2: {}, + }, groupAssignment.globalResources.Accounts) + require.Equal(t, map[basics.Address]struct{}{ + account1: {}, + }, groupAssignment.localTxnResources[0].Accounts) + require.Empty(t, groupAssignment.localTxnResources[1].Accounts) + require.Equal(t, map[basics.Address]struct{}{ + account1: {}, + }, groupAssignment.localTxnResources[2].Accounts) + + require.True(t, groupAssignment.addAccount(account2, false, 2)) + + // account2 gets moved from the global assignment to the local assignment of txn 2 + require.Empty(t, groupAssignment.globalResources.Accounts) + require.Equal(t, map[basics.Address]struct{}{ + account1: {}, + }, groupAssignment.localTxnResources[0].Accounts) + require.Empty(t, groupAssignment.localTxnResources[1].Accounts) + require.Equal(t, map[basics.Address]struct{}{ + account1: {}, + account2: {}, + }, groupAssignment.localTxnResources[2].Accounts) + }) + + t.Run("assets", func(t *testing.T) { + asset1 := basics.AssetIndex(100) + asset2 := basics.AssetIndex(200) + + groupAssignment := makeGroupResourceTracker(txns, &proto) + + require.Empty(t, groupAssignment.globalResources.Assets) + require.Empty(t, groupAssignment.localTxnResources[0].Assets) + require.Empty(t, groupAssignment.localTxnResources[1].Assets) + require.Empty(t, groupAssignment.localTxnResources[2].Assets) + + require.True(t, groupAssignment.addAsset(asset1, false, 0)) + + // asset1 should be present in txn 0's local assignment + require.Empty(t, groupAssignment.globalResources.Assets) + require.Equal(t, map[basics.AssetIndex]struct{}{ + asset1: {}, + }, groupAssignment.localTxnResources[0].Assets) + require.Empty(t, groupAssignment.localTxnResources[1].Assets) + require.Empty(t, groupAssignment.localTxnResources[2].Assets) + + // Txn 1, which used global resources, can see asset1 + require.True(t, groupAssignment.hasAsset(asset1, true, 1)) + + require.True(t, groupAssignment.addAsset(asset2, true, 1)) + + // asset2 should be present in the global assignment + require.Equal(t, map[basics.AssetIndex]struct{}{ + asset2: {}, + }, groupAssignment.globalResources.Assets) + require.Equal(t, map[basics.AssetIndex]struct{}{ + asset1: {}, + }, groupAssignment.localTxnResources[0].Assets) + require.Empty(t, groupAssignment.localTxnResources[1].Assets) + require.Empty(t, groupAssignment.localTxnResources[2].Assets) + + // Txn 2, which does not use global resources, cannot see either asset + require.False(t, groupAssignment.hasAsset(asset1, false, 2)) + require.False(t, groupAssignment.hasAsset(asset2, false, 2)) + + require.True(t, groupAssignment.addAsset(asset1, false, 2)) + + // asset1 should be present in txn 2's local assignment + require.Equal(t, map[basics.AssetIndex]struct{}{ + asset2: {}, + }, groupAssignment.globalResources.Assets) + require.Equal(t, map[basics.AssetIndex]struct{}{ + asset1: {}, + }, groupAssignment.localTxnResources[0].Assets) + require.Empty(t, groupAssignment.localTxnResources[1].Assets) + require.Equal(t, map[basics.AssetIndex]struct{}{ + asset1: {}, + }, groupAssignment.localTxnResources[2].Assets) + + require.True(t, groupAssignment.addAsset(asset2, false, 2)) + + // asset2 gets moved from the global assignment to the local assignment of txn 2 + require.Empty(t, groupAssignment.globalResources.Assets) + require.Equal(t, map[basics.AssetIndex]struct{}{ + asset1: {}, + }, groupAssignment.localTxnResources[0].Assets) + require.Empty(t, groupAssignment.localTxnResources[1].Assets) + require.Equal(t, map[basics.AssetIndex]struct{}{ + asset1: {}, + asset2: {}, + }, groupAssignment.localTxnResources[2].Assets) + }) + + t.Run("apps", func(t *testing.T) { + app1 := basics.AppIndex(100) + app2 := basics.AppIndex(200) + + groupAssignment := makeGroupResourceTracker(txns, &proto) + + require.Empty(t, groupAssignment.globalResources.Apps) + require.Empty(t, groupAssignment.localTxnResources[0].Apps) + require.Empty(t, groupAssignment.localTxnResources[1].Apps) + require.Empty(t, groupAssignment.localTxnResources[2].Apps) + + require.True(t, groupAssignment.addApp(app1, ep, 7, false, 0)) + + // app1 should be present in txn 0's local assignment + require.Empty(t, groupAssignment.globalResources.Apps) + require.Equal(t, map[basics.AppIndex]struct{}{ + app1: {}, + }, groupAssignment.localTxnResources[0].Apps) + require.Empty(t, groupAssignment.localTxnResources[1].Apps) + require.Empty(t, groupAssignment.localTxnResources[2].Apps) + + // Txn 1, which used global resources, can see app1 + require.True(t, groupAssignment.hasApp(app1, true, 1)) + + require.True(t, groupAssignment.addApp(app2, ep, 7, true, 1)) + + // app2 should be present in the global assignment + require.Equal(t, map[basics.AppIndex]struct{}{ + app2: {}, + }, groupAssignment.globalResources.Apps) + require.Equal(t, map[basics.AppIndex]struct{}{ + app1: {}, + }, groupAssignment.localTxnResources[0].Apps) + require.Empty(t, groupAssignment.localTxnResources[1].Apps) + require.Empty(t, groupAssignment.localTxnResources[2].Apps) + + // Txn 2, which does not use global resources, cannot see either app + require.False(t, groupAssignment.hasApp(app1, false, 2)) + require.False(t, groupAssignment.hasApp(app2, false, 2)) + + require.True(t, groupAssignment.addApp(app1, ep, 7, false, 2)) + + // app1 should be present in txn 2's local assignment + require.Equal(t, map[basics.AppIndex]struct{}{ + app2: {}, + }, groupAssignment.globalResources.Apps) + require.Equal(t, map[basics.AppIndex]struct{}{ + app1: {}, + }, groupAssignment.localTxnResources[0].Apps) + require.Empty(t, groupAssignment.localTxnResources[1].Apps) + require.Equal(t, map[basics.AppIndex]struct{}{ + app1: {}, + }, groupAssignment.localTxnResources[2].Apps) + + require.True(t, groupAssignment.addApp(app2, ep, 7, false, 2)) + + // app2 gets moved from the global assignment to the local assignment of txn 2 + require.Empty(t, groupAssignment.globalResources.Apps) + require.Equal(t, map[basics.AppIndex]struct{}{ + app1: {}, + }, groupAssignment.localTxnResources[0].Apps) + require.Empty(t, groupAssignment.localTxnResources[1].Apps) + require.Equal(t, map[basics.AppIndex]struct{}{ + app1: {}, + app2: {}, + }, groupAssignment.localTxnResources[2].Apps) + }) +} diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 0228357b53..f888d0fad2 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -24,12 +24,15 @@ import ( "strings" "testing" + "golang.org/x/exp/slices" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/simulation" simulationtesting "github.com/algorand/go-algorand/ledger/simulation/testing" ledgertesting "github.com/algorand/go-algorand/ledger/testing" @@ -63,6 +66,15 @@ func normalizeEvalDeltas(t *testing.T, actual, expected *transactions.EvalDelta) if len(evalDelta.LocalDeltas) == 0 { evalDelta.LocalDeltas = nil } + if len(evalDelta.SharedAccts) == 0 { + evalDelta.SharedAccts = nil + } + if len(evalDelta.Logs) == 0 { + evalDelta.Logs = nil + } + if len(evalDelta.InnerTxns) == 0 { + evalDelta.InnerTxns = nil + } } // Use assert instead of require here so that we get a more useful error message later assert.Equal(t, len(expected.InnerTxns), len(actual.InnerTxns)) @@ -120,6 +132,8 @@ func validateSimulationResult(t *testing.T, result simulation.Result) { } } +const ignoreAppBudgetConsumed = math.MaxUint64 + func simulationTest(t *testing.T, f func(env simulationtesting.Environment) simulationTestCase) { t.Helper() env := simulationtesting.PrepareSimulatorTest(t) @@ -127,6 +141,10 @@ func simulationTest(t *testing.T, f func(env simulationtesting.Environment) simu testcase := f(env) + runSimulationTestCase(t, env, testcase) +} + +func runSimulationTestCase(t *testing.T, env simulationtesting.Environment, testcase simulationTestCase) { actual, err := simulation.MakeSimulator(env.Ledger, testcase.developerAPI).Simulate(testcase.input) require.NoError(t, err) @@ -142,6 +160,22 @@ func simulationTest(t *testing.T, f func(env simulationtesting.Environment) simu testcase.expected.TxnGroups[i].Txns[j].Txn.SignedTxn = testcase.input.TxnGroups[i][j] } normalizeEvalDeltas(t, &actual.TxnGroups[i].Txns[j].Txn.EvalDelta, &testcase.expected.TxnGroups[i].Txns[j].Txn.EvalDelta) + + if testcase.expected.TxnGroups[i].Txns[j].AppBudgetConsumed == ignoreAppBudgetConsumed { + // This test does not care about the app budget consumed. Replace it with the actual value. + testcase.expected.TxnGroups[i].Txns[j].AppBudgetConsumed = actual.TxnGroups[i].Txns[j].AppBudgetConsumed + } + } + + if testcase.expected.TxnGroups[i].AppBudgetConsumed == ignoreAppBudgetConsumed { + // This test does not care about the app budget consumed. Replace it with the actual value. + // But let's still ensure it's the sum of budgets consumed in this group. + var sum uint64 + for _, txn := range actual.TxnGroups[i].Txns { + sum += txn.AppBudgetConsumed + } + assert.Equal(t, sum, actual.TxnGroups[i].AppBudgetConsumed) + testcase.expected.TxnGroups[i].AppBudgetConsumed = actual.TxnGroups[i].AppBudgetConsumed } } @@ -3579,3 +3613,1965 @@ func TestMockTracerScenarios(t *testing.T) { }) } } + +// TestUnnamedResources tests that app calls can access resources that they otherwise should not be +// able to if AllowUnnamedResources is enabled. Additional tests follow for special cases. +func TestUnnamedResources(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + // Start with directRefEnabledVersion (4), since prior to that all restricted references had to + // be indexes into the foreign arrays, meaning we can't test the unnamed case. + for v := 4; v <= logic.LogicVersion; v++ { + v := v + t.Run(fmt.Sprintf("v%d", v), func(t *testing.T) { + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + + otherAccount := env.Accounts[1] + otherAccountAuthAddr := env.Accounts[2].Addr + env.Rekey(otherAccount.Addr, otherAccountAuthAddr) + + assetCreator := env.Accounts[2].Addr + assetID := env.CreateAsset(assetCreator, basics.AssetParams{Total: 100}) + + assetHolder := env.Accounts[3].Addr + env.OptIntoAsset(assetHolder, assetID) + env.TransferAsset(assetCreator, assetHolder, assetID, 1) + + otherAppCreator := env.Accounts[4].Addr + otherAppID := env.CreateApp(otherAppCreator, simulationtesting.AppParams{ + // Using version 8 because this is the highest version where we check that + // cross-products of resources are available. + ApprovalProgram: "#pragma version 8\nint 1", + ClearStateProgram: "#pragma version 8\nint 1", + }) + + otherAppUser := env.Accounts[5].Addr + env.OptIntoApp(otherAppUser, otherAppID) + + proto := env.TxnInfo.CurrentProtocolParams() + expectedUnnamedResourceGroupAssignment := &simulation.ResourceTracker{ + MaxAccounts: proto.MaxTxGroupSize * (proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps), + MaxAssets: proto.MaxTxGroupSize * proto.MaxAppTxnForeignAssets, + MaxApps: proto.MaxTxGroupSize * proto.MaxAppTxnForeignApps, + MaxBoxes: proto.MaxTxGroupSize * proto.MaxAppBoxReferences, + MaxTotalRefs: proto.MaxTxGroupSize * proto.MaxAppTotalTxnReferences, + MaxCrossProductReferences: proto.MaxTxGroupSize * proto.MaxAppTxnForeignApps * (proto.MaxAppTxnForeignApps + 2), + } + var expectedUnnamedResourceTxnAssignment *simulation.ResourceTracker + var expectedResources *simulation.ResourceTracker + if v < 9 { + // no shared resources + expectedUnnamedResourceTxnAssignment = &simulation.ResourceTracker{ + MaxAccounts: proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps, + MaxAssets: proto.MaxAppTxnForeignAssets, + MaxApps: proto.MaxAppTxnForeignApps, + MaxBoxes: proto.MaxAppBoxReferences, + MaxTotalRefs: proto.MaxAppTotalTxnReferences, + } + expectedResources = expectedUnnamedResourceTxnAssignment + } else { + // shared resources + expectedResources = expectedUnnamedResourceGroupAssignment + } + + var innerCount int + + program := fmt.Sprintf("#pragma version %d\n", v) + + // Do nothing during create + program += "txn ApplicationID; bz end;" + + // Account access + program += fmt.Sprintf("addr %s; balance; int %d; <; assert;", otherAccount.Addr, otherAccount.AcctData.MicroAlgos.Raw) + if v >= 6 { // acct_params_get introduced + program += fmt.Sprintf("addr %s; acct_params_get AcctAuthAddr; assert; addr %s; ==; assert;", otherAccount.Addr, otherAccountAuthAddr) + } + if v >= 5 { // inner txns introduced + program += fmt.Sprintf("itxn_begin; int pay; itxn_field TypeEnum; addr %s; itxn_field Receiver; itxn_submit;", otherAccount.Addr) + innerCount++ + } + expectedResources.Accounts = map[basics.Address]struct{}{ + otherAccount.Addr: {}, + } + + // Asset params access + program += fmt.Sprintf("int %d; asset_params_get AssetTotal; assert; int 100; ==; assert;", assetID) + if v >= 5 { // AssetCreator field introduced + program += fmt.Sprintf("int %d; asset_params_get AssetCreator; assert; addr %s; ==; assert;", assetID, assetCreator) + } + expectedResources.Assets = map[basics.AssetIndex]struct{}{ + assetID: {}, + } + + // Asset holding access + program += fmt.Sprintf("txn Sender; int %d; asset_holding_get AssetBalance; !; assert; !; assert;", assetID) + program += fmt.Sprintf("addr %s; int %d; asset_holding_get AssetBalance; assert; int 99; ==; assert;", assetCreator, assetID) + program += fmt.Sprintf("addr %s; int %d; asset_holding_get AssetBalance; assert; int 1; ==; assert;", assetHolder, assetID) + if v >= 5 { // inner txns introduced + program += fmt.Sprintf("itxn_begin; int axfer; itxn_field TypeEnum; int %d; itxn_field XferAsset; itxn_submit;", assetID) + innerCount++ + } + expectedResources.Accounts[assetCreator] = struct{}{} + expectedResources.Accounts[assetHolder] = struct{}{} + if v >= 9 { + expectedUnnamedResourceGroupAssignment.AssetHoldings = map[ledgercore.AccountAsset]struct{}{ + {Address: sender.Addr, Asset: assetID}: {}, + {Address: assetCreator, Asset: assetID}: {}, + {Address: assetHolder, Asset: assetID}: {}, + {Address: basics.Address{}, Asset: assetID}: {}, + } + } + + // App params access + program += fmt.Sprintf("int %d; byte 0x01; app_global_get_ex; !; assert; !; assert;", otherAppID) + if v >= 5 { // app_params_get introduced + program += fmt.Sprintf("int %d; app_params_get AppCreator; assert; addr %s; ==; assert;", otherAppID, otherAppCreator) + } + expectedResources.Apps = map[basics.AppIndex]struct{}{ + otherAppID: {}, + } + + // App local access + program += fmt.Sprintf("txn Sender; int %d; app_opted_in; !; assert;", otherAppID) + program += fmt.Sprintf("addr %s; int %d; app_opted_in; assert;", otherAppUser, otherAppID) + program += fmt.Sprintf("addr %s; int %d; byte 0x01; app_local_get_ex; !; assert; !; assert;", otherAppUser, otherAppID) + if v >= 6 { // contract to contract itxn calls introduced + program += fmt.Sprintf("itxn_begin; int appl; itxn_field TypeEnum; int %d; itxn_field ApplicationID; itxn_submit;", otherAppID) + innerCount++ + } + expectedResources.Accounts[otherAppUser] = struct{}{} + if v >= 9 { + expectedUnnamedResourceGroupAssignment.AppLocals = map[ledgercore.AccountApp]struct{}{ + {Address: sender.Addr, App: otherAppID}: {}, + {Address: otherAppUser, App: otherAppID}: {}, + {Address: basics.Address{}, App: otherAppID}: {}, + } + } + + // Box access + if v >= 8 { // boxes introduced + program += `byte "A"; int 64; box_create; assert;` + program += `byte "B"; box_len; !; assert; !; assert;` + expectedUnnamedResourceGroupAssignment.Boxes = map[logic.BoxRef]uint64{ + {App: 0, Name: "A"}: 0, + {App: 0, Name: "B"}: 0, + } + } + + program += "end: int 1" + + testAppID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: program, + ClearStateProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + }) + + // Fund the app to cover inner txn fees and min balance increases + env.TransferAlgos(sender.Addr, testAppID.Address(), 1_000_000) + + var holdingsToFix []ledgercore.AccountAsset + for holding := range expectedUnnamedResourceGroupAssignment.AssetHoldings { + if holding.Address.IsZero() { + // replace with app address + holdingsToFix = append(holdingsToFix, holding) + } + } + for _, holding := range holdingsToFix { + delete(expectedUnnamedResourceGroupAssignment.AssetHoldings, holding) + holding.Address = testAppID.Address() + expectedUnnamedResourceGroupAssignment.AssetHoldings[holding] = struct{}{} + } + var localsToFix []ledgercore.AccountApp + for local := range expectedUnnamedResourceGroupAssignment.AppLocals { + if local.Address.IsZero() { + // replace with app address + localsToFix = append(localsToFix, local) + } + } + for _, local := range localsToFix { + delete(expectedUnnamedResourceGroupAssignment.AppLocals, local) + local.Address = testAppID.Address() + expectedUnnamedResourceGroupAssignment.AppLocals[local] = struct{}{} + } + var boxesToFix []logic.BoxRef + for box := range expectedUnnamedResourceGroupAssignment.Boxes { + if box.App == 0 { + // replace with app ID + boxesToFix = append(boxesToFix, box) + } + } + for _, box := range boxesToFix { + value := expectedUnnamedResourceGroupAssignment.Boxes[box] + delete(expectedUnnamedResourceGroupAssignment.Boxes, box) + box.App = testAppID + expectedUnnamedResourceGroupAssignment.Boxes[box] = value + } + + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: testAppID, + }) + stxn := txn.Txn().Sign(sender.Sk) + + if expectedUnnamedResourceTxnAssignment != nil { + localAccounts := len(expectedUnnamedResourceTxnAssignment.Accounts) + localAssets := len(expectedUnnamedResourceTxnAssignment.Assets) + localApps := len(expectedUnnamedResourceTxnAssignment.Apps) + // Skip boxes, they are global only + expectedUnnamedResourceGroupAssignment.MaxAccounts -= localAccounts + localApps + expectedUnnamedResourceGroupAssignment.MaxAssets -= localAssets + expectedUnnamedResourceGroupAssignment.MaxApps -= localApps + expectedUnnamedResourceGroupAssignment.MaxTotalRefs -= localAccounts + localAssets + localApps + + if !expectedUnnamedResourceTxnAssignment.HasResources() { + expectedUnnamedResourceTxnAssignment = nil + } + } + + if !expectedUnnamedResourceGroupAssignment.HasResources() { + expectedUnnamedResourceGroupAssignment = nil + } + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{stxn}}, + AllowUnnamedResources: true, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + InnerTxns: make([]transactions.SignedTxnWithAD, innerCount), + }, + }, + }, + AppBudgetConsumed: ignoreAppBudgetConsumed, + UnnamedResourcesAccessed: expectedUnnamedResourceTxnAssignment, + }, + }, + AppBudgetAdded: 700 + 700*uint64(innerCount), + AppBudgetConsumed: ignoreAppBudgetConsumed, + UnnamedResourcesAccessed: expectedUnnamedResourceGroupAssignment, + }, + }, + EvalOverrides: simulation.ResultEvalOverrides{ + AllowUnnamedResources: true, + }, + }, + } + }) + }) + } +} + +// TestUnnamedResourcesAccountLocalWrite tests app call behavior when writing to an account's local +// state they otherwise shouldn't have access to if AllowUnnamedResources is enabled. +func TestUnnamedResourcesAccountLocalWrite(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + // Start with directRefEnabledVersion (4), since prior to that all restricted references had to + // be indexes into the foreign arrays, meaning we can't test the unnamed case. + for v := 4; v <= logic.LogicVersion; v++ { + v := v + t.Run(fmt.Sprintf("v%d", v), func(t *testing.T) { + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + testAppUser := env.Accounts[1].Addr + + program := fmt.Sprintf(`#pragma version %d +txn ApplicationID +! +txn OnCompletion +int OptIn +== +|| +bnz end // Do nothing during create or opt in + +// App local write to an account we shouldn't be able to +addr %s +byte "key" +byte "value" +app_local_put + +end: +int 1 +`, v, testAppUser) + + testAppID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: program, + ClearStateProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + LocalStateSchema: basics.StateSchema{ + NumByteSlice: 1, + }, + }) + + env.OptIntoApp(testAppUser, testAppID) + + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: testAppID, + }) + stxn := txn.Txn().Sign(sender.Sk) + + proto := env.TxnInfo.CurrentProtocolParams() + expectedUnnamedResourceAssignment := &simulation.ResourceTracker{ + MaxAccounts: proto.MaxTxGroupSize * (proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps), + MaxAssets: proto.MaxTxGroupSize * proto.MaxAppTxnForeignAssets, + MaxApps: proto.MaxTxGroupSize * proto.MaxAppTxnForeignApps, + MaxBoxes: proto.MaxTxGroupSize * proto.MaxAppBoxReferences, + MaxTotalRefs: proto.MaxTxGroupSize * proto.MaxAppTotalTxnReferences, + MaxCrossProductReferences: proto.MaxTxGroupSize * proto.MaxAppTxnForeignApps * (proto.MaxAppTxnForeignApps + 2), + } + var expectedUnnamedResourceTxnAssignment *simulation.ResourceTracker + + var expectedEvalDelta transactions.EvalDelta + var expectedError string + var expectedFailedAt simulation.TxnPath + // Can write to accounts outside of foreign array in sharedResourcesVersion (9), but not before then + if v >= 9 { + expectedEvalDelta = transactions.EvalDelta{ + SharedAccts: []basics.Address{testAppUser}, + LocalDeltas: map[uint64]basics.StateDelta{ + 1: { + "key": basics.ValueDelta{ + Action: basics.SetBytesAction, + Bytes: "value", + }, + }, + }, + } + expectedUnnamedResourceAssignment.Accounts = map[basics.Address]struct{}{ + testAppUser: {}, + } + expectedUnnamedResourceAssignment.AppLocals = map[ledgercore.AccountApp]struct{}{ + {Address: testAppUser, App: testAppID}: {}, + } + } else { + expectedError = fmt.Sprintf("logic eval error: invalid Account reference for mutation %s", testAppUser) + expectedFailedAt = simulation.TxnPath{0} + expectedUnnamedResourceTxnAssignment = &simulation.ResourceTracker{ + MaxAccounts: proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps, + MaxAssets: proto.MaxAppTxnForeignAssets, + MaxApps: proto.MaxAppTxnForeignApps, + MaxBoxes: proto.MaxAppBoxReferences, + MaxTotalRefs: proto.MaxAppTotalTxnReferences, + Accounts: map[basics.Address]struct{}{ + testAppUser: {}, + }, + } + expectedUnnamedResourceAssignment.MaxAccounts-- + expectedUnnamedResourceAssignment.MaxTotalRefs-- + } + + if expectedUnnamedResourceTxnAssignment != nil && !expectedUnnamedResourceTxnAssignment.HasResources() { + expectedUnnamedResourceTxnAssignment = nil + } + + if !expectedUnnamedResourceAssignment.HasResources() { + expectedUnnamedResourceAssignment = nil + } + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{stxn}}, + AllowUnnamedResources: true, + }, + expectedError: expectedError, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: expectedEvalDelta, + }, + }, + AppBudgetConsumed: ignoreAppBudgetConsumed, + UnnamedResourcesAccessed: expectedUnnamedResourceTxnAssignment, + }, + }, + FailedAt: expectedFailedAt, + AppBudgetAdded: 700, + AppBudgetConsumed: ignoreAppBudgetConsumed, + UnnamedResourcesAccessed: expectedUnnamedResourceAssignment, + }, + }, + EvalOverrides: simulation.ResultEvalOverrides{ + AllowUnnamedResources: true, + }, + }, + } + }) + }) + } +} + +// TestUnnamedResourcesCreatedAppsAndAssets tests cross-product availability for newly created apps +// and assets. +func TestUnnamedResourcesCreatedAppsAndAssets(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + // Start with v9, since that's when we first track cross-product references indepdently. + for v := 9; v <= logic.LogicVersion; v++ { + v := v + t.Run(fmt.Sprintf("v%d", v), func(t *testing.T) { + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + otherResourceCreator := env.Accounts[1] + otherAccount := env.Accounts[2].Addr + + otherAssetID := env.CreateAsset(otherResourceCreator.Addr, basics.AssetParams{Total: 100}) + otherAppID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + ClearStateProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + }) + + program := fmt.Sprintf(`#pragma version %d +txn ApplicationID +bz end // Do nothing during create + +gtxn 0 CreatedAssetID +store 0 // new asset + +gtxn 1 CreatedApplicationID +dup +store 1 // new app +app_params_get AppAddress +assert +store 2 // new app account + +addr %s +store 10 // other account + +int %d +store 11 // other asset + +int %d +store 12 // other app + +// Asset holding lookup for newly created asset +load 10 +load 0 +asset_holding_get AssetBalance +! +assert +! +assert + +// App local lookup for newly created app +load 10 +load 1 +app_opted_in +! +assert + +// Asset holding lookup for newly created app account +load 2 +load 11 +asset_holding_get AssetBalance +! +assert +! +assert + +// App local lookup for newly created app account +load 2 +load 12 +app_opted_in +! +assert + +end: +int 1 +`, v, otherAccount, otherAssetID, otherAppID) + + testAppID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: program, + ClearStateProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + }) + + assetCreateTxn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.AssetConfigTx, + Sender: otherResourceCreator.Addr, + AssetParams: basics.AssetParams{ + Total: 1, + }, + }) + appCreateTxn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: otherResourceCreator.Addr, + ApprovalProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + ClearStateProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + }) + appCallTxn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: testAppID, + }) + txntest.Group(&assetCreateTxn, &appCreateTxn, &appCallTxn) + assetCreateStxn := assetCreateTxn.Txn().Sign(otherResourceCreator.Sk) + appCreateStxn := appCreateTxn.Txn().Sign(otherResourceCreator.Sk) + appCallStxn := appCallTxn.Txn().Sign(sender.Sk) + + proto := env.TxnInfo.CurrentProtocolParams() + expectedUnnamedResourceAssignment := simulation.ResourceTracker{ + MaxAccounts: (proto.MaxTxGroupSize - 1) * (proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps), + MaxAssets: (proto.MaxTxGroupSize - 1) * proto.MaxAppTxnForeignAssets, + MaxApps: (proto.MaxTxGroupSize - 1) * proto.MaxAppTxnForeignApps, + MaxBoxes: (proto.MaxTxGroupSize - 1) * proto.MaxAppBoxReferences, + MaxTotalRefs: (proto.MaxTxGroupSize - 1) * proto.MaxAppTotalTxnReferences, + + Accounts: map[basics.Address]struct{}{ + otherAccount: {}, + }, + Assets: map[basics.AssetIndex]struct{}{ + otherAssetID: {}, + }, + Apps: map[basics.AppIndex]struct{}{ + otherAppID: {}, + }, + MaxCrossProductReferences: (proto.MaxTxGroupSize - 1) * proto.MaxAppTxnForeignApps * (proto.MaxAppTxnForeignApps + 2), + // These should remain nil, since cross-product references for newly created + // resources should not be counted against the group's resource limits. + AssetHoldings: nil, + AppLocals: nil, + } + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {assetCreateStxn, appCreateStxn, appCallStxn}, + }, + AllowUnnamedResources: true, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ConfigAsset: basics.AssetIndex(testAppID) + 1, + }, + }, + }, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: testAppID + 2, + }, + }, + AppBudgetConsumed: ignoreAppBudgetConsumed, + }, + { + AppBudgetConsumed: ignoreAppBudgetConsumed, + }, + }, + AppBudgetAdded: 1400, + AppBudgetConsumed: ignoreAppBudgetConsumed, + UnnamedResourcesAccessed: &expectedUnnamedResourceAssignment, + }, + }, + EvalOverrides: simulation.ResultEvalOverrides{ + AllowUnnamedResources: true, + }, + }, + } + }) + }) + } +} + +const boxTestProgram = `#pragma version %d +txn ApplicationID +bz end // Do nothing during create + +byte "create" +byte "delete" +byte "read" +byte "write" +txn ApplicationArgs 0 +match create delete read write +err // Unknown command + +create: +txn ApplicationArgs 1 +txn ApplicationArgs 2 +btoi +box_create +assert +b end + +delete: +txn ApplicationArgs 1 +box_del +assert +b end + +read: +txn ApplicationArgs 1 +box_get +pop +pop +b end + +write: +txn ApplicationArgs 1 +int 0 +txn ApplicationArgs 2 +box_replace + +end: +int 1 +` + +type boxOperation struct { + op logic.BoxOperation + name string + createSize uint64 + contents []byte + otherRefCount int +} + +func (o boxOperation) appArgs() [][]byte { + switch o.op { + case logic.BoxCreateOperation: + return [][]byte{ + []byte("create"), + []byte(o.name), + uint64ToBytes(o.createSize), + } + case logic.BoxReadOperation: + return [][]byte{ + []byte("read"), + []byte(o.name), + } + case logic.BoxWriteOperation: + return [][]byte{ + []byte("write"), + []byte(o.name), + o.contents, + } + case logic.BoxDeleteOperation: + return [][]byte{ + []byte("delete"), + []byte(o.name), + } + default: + panic(fmt.Sprintf("unknown box operation: %v", o.op)) + } +} + +type boxTestResult struct { + Boxes map[logic.BoxRef]uint64 + NumEmptyBoxRefs int + + FailureMessage string + FailingIndex int +} + +func testUnnamedBoxOperations(t *testing.T, env simulationtesting.Environment, app basics.AppIndex, boxOps []boxOperation, expected boxTestResult) { + t.Helper() + + maxGroupSize := env.TxnInfo.CurrentProtocolParams().MaxTxGroupSize + require.LessOrEqual(t, len(boxOps), maxGroupSize) + + otherAssets := 0 + txns := make([]*txntest.Txn, maxGroupSize) + for i, op := range boxOps { + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: env.Accounts[0].Addr, + ApplicationID: app, + ApplicationArgs: op.appArgs(), + ForeignAssets: make([]basics.AssetIndex, op.otherRefCount), + Note: []byte{byte(i)}, // Make each txn unique + }) + txns[i] = &txn + otherAssets += op.otherRefCount + } + for i := len(boxOps); i < maxGroupSize; i++ { + // Fill out the rest of the group with non-app transactions. This reduces the amount of + // unnamed global resources available. + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: env.Accounts[0].Addr, + Receiver: env.Accounts[0].Addr, + Note: []byte{byte(i)}, // Make each txn unique + }) + txns[i] = &txn + } + txntest.Group(txns...) + stxns := make([]transactions.SignedTxn, len(txns)) + for i, txn := range txns { + stxns[i] = txn.Txn().Sign(env.Accounts[0].Sk) + } + + expectedTxnResults := make([]simulation.TxnResult, len(stxns)) + for i := range expectedTxnResults { + expectedTxnResults[i].AppBudgetConsumed = ignoreAppBudgetConsumed + } + + var failedAt simulation.TxnPath + if expected.FailureMessage != "" { + failedAt = simulation.TxnPath{uint64(expected.FailingIndex)} + } + + proto := env.TxnInfo.CurrentProtocolParams() + expectedUnnamedResources := &simulation.ResourceTracker{ + MaxAccounts: len(boxOps) * (proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps), + MaxAssets: len(boxOps)*proto.MaxAppTxnForeignAssets - otherAssets, + MaxApps: len(boxOps) * proto.MaxAppTxnForeignApps, + MaxBoxes: len(boxOps) * proto.MaxAppBoxReferences, + MaxTotalRefs: len(boxOps)*proto.MaxAppTotalTxnReferences - otherAssets, + + Boxes: expected.Boxes, + NumEmptyBoxRefs: expected.NumEmptyBoxRefs, + + MaxCrossProductReferences: len(boxOps) * proto.MaxAppTxnForeignApps * (proto.MaxAppTxnForeignApps + 2), + } + + if !expectedUnnamedResources.HasResources() { + expectedUnnamedResources = nil + } + + testCase := simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{stxns}, + AllowUnnamedResources: true, + }, + expectedError: expected.FailureMessage, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: expectedTxnResults, + AppBudgetAdded: uint64(700 * len(boxOps)), + AppBudgetConsumed: ignoreAppBudgetConsumed, + UnnamedResourcesAccessed: expectedUnnamedResources, + FailedAt: failedAt, + }, + }, + EvalOverrides: simulation.ResultEvalOverrides{ + AllowUnnamedResources: true, + }, + }, + } + runSimulationTestCase(t, env, testCase) +} + +// TestUnnamedResourcesBoxIOBudget tests that the box IO budgets behave properly when +// AllowUnnamedResources is enabled. It does us no good if you can reference unnamed boxes, but the +// IO budget is still restricted based on the predeclared foreign box array. +func TestUnnamedResourcesBoxIOBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + // Boxes introduced in v8 + for v := 8; v <= logic.LogicVersion; v++ { + v := v + t.Run(fmt.Sprintf("v%d", v), func(t *testing.T) { + t.Parallel() + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + + sender := env.Accounts[0] + + appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: fmt.Sprintf(boxTestProgram, v), + ClearStateProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + }) + + proto := env.TxnInfo.CurrentProtocolParams() + + // MBR is needed for boxes. + transferable := env.Accounts[1].AcctData.MicroAlgos.Raw - proto.MinBalance - proto.MinTxnFee + env.TransferAlgos(env.Accounts[1].Addr, appID.Address(), transferable) + + // Set up boxes A, B, C for testing. + // A is a box with a size of exactly BytesPerBoxReference + env.Txn(env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: appID, + ApplicationArgs: [][]byte{ + []byte("create"), + []byte("A"), + uint64ToBytes(proto.BytesPerBoxReference), + }, + Boxes: []transactions.BoxRef{{Name: []byte("A")}}, + }).SignedTxn()) + // B is a box with a size of 1 + env.Txn(env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: appID, + ApplicationArgs: [][]byte{ + []byte("create"), + []byte("B"), + uint64ToBytes(1), + }, + Boxes: []transactions.BoxRef{{Name: []byte("B")}}, + }).SignedTxn()) + // C is a box with a size of 2 * BytesPerBoxReference - 1 + env.Txn(env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: appID, + ApplicationArgs: [][]byte{ + []byte("create"), + []byte("C"), + uint64ToBytes(2*proto.BytesPerBoxReference - 1), + }, + Boxes: []transactions.BoxRef{{Name: []byte("C")}, {}}, + }).SignedTxn()) + + testBoxOps := func(boxOps []boxOperation, expected boxTestResult) { + t.Helper() + testUnnamedBoxOperations(t, env, appID, boxOps, expected) + } + + // Each test below will run against the environment we just set up. They will each run + // in separate simulations, so we can reuse the same environment and not have to worry + // about the effects of one test interfering with another. + + // Reading exisitng boxes + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "A"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + }, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "B"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "B"}: 1, + }, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "C"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, + }, + // We need an additional empty box ref because the size of C exceeds BytesPerBoxReference + NumEmptyBoxRefs: 1, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "A"}, + {op: logic.BoxReadOperation, name: "B"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + {App: appID, Name: "B"}: 1, + }, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "A"}, + {op: logic.BoxReadOperation, name: "C"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, + }, + NumEmptyBoxRefs: 1, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "A"}, + {op: logic.BoxReadOperation, name: "B"}, + {op: logic.BoxReadOperation, name: "C"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + {App: appID, Name: "B"}: 1, + {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, + }, + // No empty box refs needed because we have perfectly reached 3 * BytesPerBoxReference + }) + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "Q"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "Q"}: 0, + }, + }) + + // Creating new boxes + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + }, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 1}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + }, + NumEmptyBoxRefs: 1, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference * 3}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + }, + NumEmptyBoxRefs: 2, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "D", createSize: 1}, + {op: logic.BoxCreateOperation, name: "E", createSize: 1}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + {App: appID, Name: "E"}: 0, + }, + }) + + // Creating new boxes and reading existing ones + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 2}, + {op: logic.BoxReadOperation, name: "A"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + }, + // Since we never write to A, its write budget can cover the extra bytes from writing D, + // so no extra refs needed. + }) + testBoxOps([]boxOperation{ + {op: logic.BoxReadOperation, name: "A"}, + {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 2}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + }, + // The same is true in reverse. + }) + + // Writing to new boxes and existing boxes + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 2}, + {op: logic.BoxWriteOperation, name: "A", contents: []byte{1}}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + }, + NumEmptyBoxRefs: 1, + }) + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "D", createSize: proto.BytesPerBoxReference + 2}, + {op: logic.BoxWriteOperation, name: "B", contents: []byte{1}}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + {App: appID, Name: "B"}: 1, + }, + // Expect 0 empty box refs because the additional box ref from B can cover the extra bytes + // from writing D + }) + + // Writing then deleting + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "D", createSize: 4 * proto.BytesPerBoxReference}, + {op: logic.BoxDeleteOperation, name: "D"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + }, + // Still need 3 empty box refs because we went over the write budget before deletion. + NumEmptyBoxRefs: 3, + }) + + // Writing, deleting, then reading + testBoxOps([]boxOperation{ + {op: logic.BoxCreateOperation, name: "D", createSize: 4 * proto.BytesPerBoxReference}, + {op: logic.BoxDeleteOperation, name: "D"}, + {op: logic.BoxReadOperation, name: "C"}, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "D"}: 0, + {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, + }, + // 1 extra ref from writing D can be used to cover the extra bytes from reading C, + // but the other refs must remain. + NumEmptyBoxRefs: 2, + }) + + // Testing limits + + // Exactly at read budget + testBoxOps([]boxOperation{ + { + op: logic.BoxReadOperation, + name: "A", + otherRefCount: proto.MaxAppBoxReferences - 1, + }, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + }, + }) + + // Over read budget + testBoxOps([]boxOperation{ + { + op: logic.BoxReadOperation, + name: "C", + otherRefCount: proto.MaxAppBoxReferences - 1, + }, + }, boxTestResult{ + FailureMessage: fmt.Sprintf("logic eval error: invalid Box reference %#x", "C"), + FailingIndex: 0, + }) + + // Very close to read budget, but writing another box should still be allowed + testBoxOps([]boxOperation{ + { + op: logic.BoxReadOperation, + name: "C", + otherRefCount: proto.MaxAppBoxReferences - 2, + }, + { + op: logic.BoxCreateOperation, + name: "X", + createSize: 2 * proto.BytesPerBoxReference, + otherRefCount: proto.MaxAppBoxReferences, + }, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "C"}: 2*proto.BytesPerBoxReference - 1, + {App: appID, Name: "X"}: 0, + }, + }) + + // Exactly at write budget + testBoxOps([]boxOperation{ + { + op: logic.BoxCreateOperation, + name: "X", + createSize: proto.BytesPerBoxReference, + otherRefCount: proto.MaxAppBoxReferences - 1, + }, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "X"}: 0, + }, + }) + + // Over write budget + testBoxOps([]boxOperation{ + { + op: logic.BoxCreateOperation, + name: "X", + createSize: proto.BytesPerBoxReference + 1, + otherRefCount: proto.MaxAppBoxReferences - 1, + }, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "X"}: 0, + }, + FailureMessage: fmt.Sprintf("logic eval error: write budget (%d) exceeded %d", proto.BytesPerBoxReference, proto.BytesPerBoxReference+1), + FailingIndex: 0, + }) + + // Exactly at write budget, but reading another box should still be allowed + testBoxOps([]boxOperation{ + { + op: logic.BoxCreateOperation, + name: "X", + createSize: 2 * proto.BytesPerBoxReference, + otherRefCount: proto.MaxAppBoxReferences - 2, + }, + { + op: logic.BoxReadOperation, + name: "A", + otherRefCount: proto.MaxAppBoxReferences, + }, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "X"}: 0, + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + }, + }) + + // No more refs available + testBoxOps([]boxOperation{ + { + op: logic.BoxReadOperation, + name: "A", + otherRefCount: proto.MaxAppBoxReferences - 1, + }, + { + op: logic.BoxReadOperation, + name: "B", + otherRefCount: proto.MaxAppBoxReferences, + }, + }, boxTestResult{ + Boxes: map[logic.BoxRef]uint64{ + {App: appID, Name: "A"}: proto.BytesPerBoxReference, + }, + FailureMessage: fmt.Sprintf("logic eval error: invalid Box reference %#x", "B"), + FailingIndex: 1, + }) + }) + } +} + +const resourceLimitsTestProgramBase = `#pragma version %d +txn ApplicationID +bz end // Do nothing during create + +// Scratch slots: +// 0 - loop counter +// 1 - resource type +// 2 - cross-product app/asset ID +// 3 - cross-product accounts + +loop: +load 0 +txn NumAppArgs +< +bz loop_end +load 0 +txnas ApplicationArgs +dup + +extract 1 0 +swap + +int 0 +getbyte + +store 1 +load 1 +bz account +load 1 +int 1 +== +bnz asset +load 1 +int 2 +== +bnz app +load 1 +int 3 +== +bnz box +load 1 +int 4 +== +bnz asset_holding +load 1 +int 5 +== +bnz app_local +err + +account: +balance +assert +b loop_step + +asset: +btoi +asset_params_get AssetTotal +assert +assert +b loop_step + +app: +btoi +byte 0x01 +app_global_get_ex +! +assert +! +assert +b loop_step + +box: +%s +b loop_step + +asset_holding: +dup +int 0 +extract_uint64 +store 2 +extract 8 0 +store 3 +asset_holding_loop: +load 3 +len +bz asset_holding_loop_end +txn Note +load 3 +dup +extract 1 0 +store 3 +int 0 +getbyte +int 32 +* +int 32 +extract +load 2 +asset_holding_get AssetBalance +! +assert +! +assert +b asset_holding_loop +asset_holding_loop_end: +b loop_step + +app_local: +dup +int 0 +extract_uint64 +store 2 +extract 8 0 +store 3 +app_local_loop: +load 3 +len +bz app_local_loop_end +txn Note +load 3 +dup +extract 1 0 +store 3 +int 0 +getbyte +int 32 +* +int 32 +extract +load 2 +app_opted_in +! +assert +b app_local_loop +app_local_loop_end: + +loop_step: +load 0 +int 1 ++ +store 0 +b loop +loop_end: + +end: +int 1 +` + +func resourceLimitsTestProgram(version int) string { + var boxCode string + if version >= 8 { + // Boxes available + boxCode = "box_len; !; assert; !; assert" + } else { + boxCode = "err" + } + return fmt.Sprintf(resourceLimitsTestProgramBase, version, boxCode) +} + +type unnamedResourceArgument struct { + account basics.Address + asset basics.AssetIndex + app basics.AppIndex + box string + assetHoldingAccounts []basics.Address + appLocalAccounts []basics.Address + + limitExceeded bool +} + +type unnamedResourceArguments []unnamedResourceArgument + +func (resources unnamedResourceArguments) markLimitExceeded() unnamedResourceArguments { + modified := slices.Clone(resources) + modified[len(modified)-1].limitExceeded = true + return modified +} + +func (resources unnamedResourceArguments) addAccounts(accounts ...basics.Address) unnamedResourceArguments { + modified := slices.Clone(resources) + for _, account := range accounts { + modified = append(modified, unnamedResourceArgument{account: account}) + } + return modified +} + +func (resources unnamedResourceArguments) addAssets(assets ...basics.AssetIndex) unnamedResourceArguments { + modified := slices.Clone(resources) + for _, asset := range assets { + modified = append(modified, unnamedResourceArgument{asset: asset}) + } + return modified +} + +func (resources unnamedResourceArguments) addApps(apps ...basics.AppIndex) unnamedResourceArguments { + modified := slices.Clone(resources) + for _, app := range apps { + modified = append(modified, unnamedResourceArgument{app: app}) + } + return modified +} + +func (resources unnamedResourceArguments) addBoxes(boxes ...string) unnamedResourceArguments { + modified := slices.Clone(resources) + for _, box := range boxes { + modified = append(modified, unnamedResourceArgument{box: box}) + } + return modified +} + +func (resources unnamedResourceArguments) addAssetHoldings(asset basics.AssetIndex, accounts ...basics.Address) unnamedResourceArguments { + modified := slices.Clone(resources) + modified = append(modified, unnamedResourceArgument{asset: asset, assetHoldingAccounts: accounts}) + return modified +} + +func (resources unnamedResourceArguments) addAppLocals(app basics.AppIndex, accounts ...basics.Address) unnamedResourceArguments { + modified := slices.Clone(resources) + modified = append(modified, unnamedResourceArgument{app: app, appLocalAccounts: accounts}) + return modified +} + +func (resources unnamedResourceArguments) accounts() []basics.Address { + var accounts []basics.Address + for i := range resources { + if resources[i].limitExceeded { + break + } + if !resources[i].account.IsZero() { + accounts = append(accounts, resources[i].account) + } + // accounts = append(accounts, resources[i].assetHoldingAccounts...) + // accounts = append(accounts, resources[i].appLocalAccounts...) + } + return accounts +} + +func (resources unnamedResourceArguments) assets() []basics.AssetIndex { + var assets []basics.AssetIndex + for i := range resources { + if resources[i].limitExceeded { + break + } + if resources[i].asset != 0 { + assets = append(assets, resources[i].asset) + } + } + return assets +} + +func (resources unnamedResourceArguments) apps() []basics.AppIndex { + var apps []basics.AppIndex + for i := range resources { + if resources[i].limitExceeded { + break + } + if resources[i].app != 0 { + apps = append(apps, resources[i].app) + } + } + return apps +} + +func (resources unnamedResourceArguments) boxes() []string { + var boxes []string + for i := range resources { + if resources[i].limitExceeded { + break + } + if resources[i].box != "" { + boxes = append(boxes, resources[i].box) + } + } + return boxes +} + +func (resources unnamedResourceArguments) assetHoldings() []ledgercore.AccountAsset { + var assetHoldings []ledgercore.AccountAsset + for i := range resources { + if resources[i].limitExceeded { + break + } + for _, account := range resources[i].assetHoldingAccounts { + assetHoldings = append(assetHoldings, ledgercore.AccountAsset{ + Address: account, + Asset: resources[i].asset, + }) + } + } + return assetHoldings +} + +func (resources unnamedResourceArguments) appLocals() []ledgercore.AccountApp { + var appLocals []ledgercore.AccountApp + for i := range resources { + if resources[i].limitExceeded { + break + } + for _, account := range resources[i].appLocalAccounts { + appLocals = append(appLocals, ledgercore.AccountApp{ + Address: account, + App: resources[i].app, + }) + } + } + return appLocals +} + +func (resources unnamedResourceArguments) addToTxn(txn *txntest.Txn) { + encodedArgs := make([][]byte, len(resources)) + crossProductAccounts := make(map[basics.Address]int) + var crossProductAccountsOrder []basics.Address + for i, resource := range resources { + switch { + case len(resource.assetHoldingAccounts) != 0: + encoding := make([]byte, 1+8+len(resource.assetHoldingAccounts)) + encoding[0] = 4 + copy(encoding[1:9], uint64ToBytes(uint64(resource.asset))) + for j, account := range resource.assetHoldingAccounts { + accountIndex, ok := crossProductAccounts[account] + if !ok { + accountIndex = len(crossProductAccounts) + crossProductAccounts[account] = accountIndex + crossProductAccountsOrder = append(crossProductAccountsOrder, account) + } + encoding[9+j] = byte(accountIndex) + } + encodedArgs[i] = encoding + case len(resource.appLocalAccounts) != 0: + encoding := make([]byte, 1+8+len(resource.appLocalAccounts)) + encoding[0] = 5 + copy(encoding[1:9], uint64ToBytes(uint64(resource.app))) + for j, account := range resource.appLocalAccounts { + accountIndex, ok := crossProductAccounts[account] + if !ok { + accountIndex = len(crossProductAccounts) + crossProductAccounts[account] = accountIndex + crossProductAccountsOrder = append(crossProductAccountsOrder, account) + } + encoding[9+j] = byte(accountIndex) + } + encodedArgs[i] = encoding + case !resource.account.IsZero(): + encodedArgs[i] = append([]byte{0}, resource.account[:]...) + case resource.asset != 0: + encodedArgs[i] = append([]byte{1}, uint64ToBytes(uint64(resource.asset))...) + case resource.app != 0: + encodedArgs[i] = append([]byte{2}, uint64ToBytes(uint64(resource.app))...) + case resource.box != "": + encodedArgs[i] = append([]byte{3}, []byte(resource.box)...) + default: + panic(fmt.Sprintf("empty resource at index %d", i)) + } + } + txn.ApplicationArgs = encodedArgs + txn.Note = make([]byte, 32*len(crossProductAccountsOrder)) + for i, account := range crossProductAccountsOrder { + copy(txn.Note[32*i:], account[:]) + } +} + +func mapWithKeys[K comparable, V any](keys []K, defaultValue V) map[K]V { + if keys == nil { + return nil + } + + m := make(map[K]V, len(keys)) + for _, k := range keys { + m[k] = defaultValue + } + return m +} + +func boxNamesToRefs(app basics.AppIndex, names []string) []logic.BoxRef { + if names == nil { + return nil + } + + refs := make([]logic.BoxRef, len(names)) + for i, name := range names { + refs[i] = logic.BoxRef{ + App: app, + Name: name, + } + } + return refs +} + +func testUnnamedResourceLimits(t *testing.T, env simulationtesting.Environment, appVersion int, app basics.AppIndex, resources unnamedResourceArguments, otherTxns []txntest.Txn, extraBudget uint64, expectedError string) { + t.Helper() + maxGroupSize := env.TxnInfo.CurrentProtocolParams().MaxTxGroupSize + txns := make([]*txntest.Txn, maxGroupSize) + appCall := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: env.Accounts[0].Addr, + ApplicationID: app, + }) + resources.addToTxn(&appCall) + txns[0] = &appCall + for i := range otherTxns { + txn := env.TxnInfo.NewTxn(otherTxns[i]) + txns[i+1] = &txn + } + for i := 1 + len(otherTxns); i < maxGroupSize; i++ { + // Fill out the rest of the group with non-app transactions. This reduces the amount of + // unnamed global resources available. + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: env.Accounts[0].Addr, + Receiver: env.Accounts[0].Addr, + Note: []byte{byte(i)}, // Make each txn unique + }) + txns[i] = &txn + } + txntest.Group(txns...) + stxns := make([]transactions.SignedTxn, len(txns)) + for i, txn := range txns { + stxns[i] = txn.Txn().Sign(env.Accounts[0].Sk) + } + + expectedTxnResults := make([]simulation.TxnResult, len(stxns)) + for i := range expectedTxnResults { + expectedTxnResults[i].AppBudgetConsumed = ignoreAppBudgetConsumed + } + + var failedAt simulation.TxnPath + if expectedError != "" { + failedAt = simulation.TxnPath{0} + } + + proto := env.TxnInfo.CurrentProtocolParams() + + expectedGroupResources := &simulation.ResourceTracker{ + MaxAccounts: proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps, + MaxAssets: proto.MaxAppTxnForeignAssets, + MaxApps: proto.MaxAppTxnForeignApps, + MaxBoxes: proto.MaxAppBoxReferences, + MaxTotalRefs: proto.MaxAppTotalTxnReferences, + + Boxes: mapWithKeys(boxNamesToRefs(app, resources.boxes()), uint64(0)), + + MaxCrossProductReferences: proto.MaxAppTxnForeignApps * (proto.MaxAppTxnForeignApps + 2), + } + expectedAccounts := mapWithKeys(resources.accounts(), struct{}{}) + // If present, delete the sender, since it's accessible normally. + delete(expectedAccounts, env.Accounts[0].Addr) + if len(expectedAccounts) == 0 { + expectedAccounts = nil + } + expectedAssets := mapWithKeys(resources.assets(), struct{}{}) + for _, txn := range otherTxns { + delete(expectedAssets, txn.XferAsset) + } + if len(expectedAssets) == 0 { + expectedAssets = nil + } + expectedApps := mapWithKeys(resources.apps(), struct{}{}) + delete(expectedApps, app) + if len(expectedApps) == 0 { + expectedApps = nil + } + if appVersion < 9 { + // No shared resources + localResources := simulation.ResourceTracker{ + MaxAccounts: proto.MaxAppTxnAccounts + proto.MaxAppTxnForeignApps, + MaxAssets: proto.MaxAppTxnForeignAssets, + MaxApps: proto.MaxAppTxnForeignApps, + MaxBoxes: proto.MaxAppBoxReferences, + MaxTotalRefs: proto.MaxAppTotalTxnReferences, + + Accounts: expectedAccounts, + Assets: expectedAssets, + Apps: expectedApps, + } + expectedGroupResources.MaxAccounts -= len(localResources.Accounts) + len(localResources.Apps) + expectedGroupResources.MaxAssets -= len(localResources.Assets) + expectedGroupResources.MaxApps -= len(localResources.Apps) + expectedGroupResources.MaxTotalRefs -= len(localResources.Accounts) + len(localResources.Assets) + len(localResources.Apps) + if localResources.HasResources() { + expectedTxnResults[0].UnnamedResourcesAccessed = &localResources + } + } else { + // Shared resources + expectedGroupResources.Accounts = expectedAccounts + expectedGroupResources.Assets = expectedAssets + expectedGroupResources.Apps = expectedApps + expectedGroupResources.AssetHoldings = mapWithKeys(resources.assetHoldings(), struct{}{}) + expectedGroupResources.AppLocals = mapWithKeys(resources.appLocals(), struct{}{}) + } + + if !expectedGroupResources.HasResources() { + expectedGroupResources = nil + } + + testCase := simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{stxns}, + AllowUnnamedResources: true, + ExtraOpcodeBudget: extraBudget, + }, + expectedError: expectedError, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: expectedTxnResults, + AppBudgetAdded: uint64(700) + extraBudget, + AppBudgetConsumed: ignoreAppBudgetConsumed, + UnnamedResourcesAccessed: expectedGroupResources, + FailedAt: failedAt, + }, + }, + EvalOverrides: simulation.ResultEvalOverrides{ + AllowUnnamedResources: true, + ExtraOpcodeBudget: extraBudget, + }, + }, + } + runSimulationTestCase(t, env, testCase) +} + +func TestUnnamedResourcesLimits(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + // Start with v5, since that introduces the `txnas` opcode, needed for dynamic indexing into app + // args array. + for v := 5; v <= logic.LogicVersion; v++ { + v := v + t.Run(fmt.Sprintf("v%d", v), func(t *testing.T) { + t.Parallel() + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + + proto := env.TxnInfo.CurrentProtocolParams() + + sender := env.Accounts[0] + otherAccounts := make([]basics.Address, len(env.Accounts)-1) + for i := range otherAccounts { + otherAccounts[i] = env.Accounts[i+1].Addr + } + + assetCreator := env.Accounts[1].Addr + assets := make([]basics.AssetIndex, proto.MaxAppTxnForeignAssets+1) + for i := range assets { + assets[i] = env.CreateAsset(assetCreator, basics.AssetParams{Total: 100}) + } + + otherAppCreator := env.Accounts[1].Addr + otherApps := make([]basics.AppIndex, proto.MaxAppTxnForeignApps+1) + for i := range otherApps { + otherApps[i] = env.CreateApp(otherAppCreator, simulationtesting.AppParams{ + // The program version here doesn't matter + ApprovalProgram: "#pragma version 8\nint 1", + ClearStateProgram: "#pragma version 8\nint 1", + }) + } + + boxes := make([]string, proto.MaxAppBoxReferences+1) + for i := range boxes { + boxes[i] = fmt.Sprintf("box%d", i) + } + + appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: resourceLimitsTestProgram(v), + ClearStateProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + }) + + testResourceAccess := func(resources unnamedResourceArguments, extra ...string) { + t.Helper() + var expectedError string + if len(extra) != 0 { + expectedError = extra[0] + } + testUnnamedResourceLimits(t, env, v, appID, resources, nil, 0, expectedError) + } + + // Each test below will run against the environment we just set up. They will each run + // in separate simulations, so we can reuse the same environment and not have to worry + // about the effects of one test interfering with another. + + // Exactly at account limit + testResourceAccess( + unnamedResourceArguments{}.addAccounts(otherAccounts[:proto.MaxAppTotalTxnReferences]...), + ) + // Over account limit + testResourceAccess( + unnamedResourceArguments{}. + addAccounts(otherAccounts[:proto.MaxAppTotalTxnReferences+1]...). + markLimitExceeded(), + fmt.Sprintf("logic eval error: invalid Account reference %s", otherAccounts[proto.MaxAppTotalTxnReferences]), + ) + + // Exactly at asset limit + testResourceAccess( + unnamedResourceArguments{}.addAssets(assets[:proto.MaxAppTxnForeignAssets]...), + ) + // Over asset limit + testResourceAccess( + unnamedResourceArguments{}. + addAssets(assets[:proto.MaxAppTxnForeignAssets+1]...). + markLimitExceeded(), + fmt.Sprintf("logic eval error: unavailable Asset %d", assets[proto.MaxAppTxnForeignAssets]), + ) + + // Exactly at app limit + testResourceAccess( + unnamedResourceArguments{}.addApps(otherApps[:proto.MaxAppTxnForeignApps]...), + ) + // Over app limit + testResourceAccess( + unnamedResourceArguments{}. + addApps(otherApps[:proto.MaxAppTxnForeignApps+1]...). + markLimitExceeded(), + fmt.Sprintf("logic eval error: unavailable App %d", otherApps[proto.MaxAppTxnForeignApps]), + ) + + if v >= 8 { + // Exactly at box limit + testResourceAccess( + unnamedResourceArguments{}.addBoxes(boxes[:proto.MaxAppBoxReferences]...), + ) + // Over box limit + testResourceAccess( + unnamedResourceArguments{}. + addBoxes(boxes[:proto.MaxAppBoxReferences+1]...). + markLimitExceeded(), + fmt.Sprintf("logic eval error: invalid Box reference %#x", boxes[proto.MaxAppBoxReferences]), + ) + } + + numResourceTypes := 3 // accounts, assets, apps + if v >= 8 { + numResourceTypes++ // boxes + } + var atLimit unnamedResourceArguments + for i := 0; i < proto.MaxAppTotalTxnReferences; i++ { + switch i % numResourceTypes { + case 0: + atLimit = atLimit.addAccounts(otherAccounts[i/numResourceTypes]) + case 1: + atLimit = atLimit.addAssets(assets[i/numResourceTypes]) + case 2: + atLimit = atLimit.addApps(otherApps[i/numResourceTypes]) + case 3: + atLimit = atLimit.addBoxes(boxes[i/numResourceTypes]) + default: + panic(fmt.Sprintf("i=%d, numResourceTypes=%d", i, numResourceTypes)) + } + } + // Exactly at limit for total references + testResourceAccess(atLimit) + + // Adding 1 more of any is over the limit + testResourceAccess( + atLimit.addAccounts(otherAccounts[len(otherAccounts)-1]).markLimitExceeded(), + fmt.Sprintf("logic eval error: invalid Account reference %s", otherAccounts[len(otherAccounts)-1]), + ) + testResourceAccess( + atLimit.addAssets(assets[len(assets)-1]).markLimitExceeded(), + fmt.Sprintf("logic eval error: unavailable Asset %d", assets[len(assets)-1]), + ) + testResourceAccess( + atLimit.addApps(otherApps[len(otherApps)-1]).markLimitExceeded(), + fmt.Sprintf("logic eval error: unavailable App %d", otherApps[len(otherApps)-1]), + ) + if v >= 8 { + testResourceAccess( + atLimit.addBoxes(boxes[len(boxes)-1]).markLimitExceeded(), + fmt.Sprintf("logic eval error: invalid Box reference %#x", boxes[len(boxes)-1]), + ) + } + }) + } +} + +// PROBLEM: for newly created assets/apps (and app accounts), cross-product refs with unnamed resources +// SHOULD NOT count against the cross product limit. This is because just including the other resource +// anywhere in the group is enough to grant access. + +func excludingIndex[S []V, V any](slice S, index int) S { + if index == 0 { + return slice[1:] + } + if index == len(slice)-1 { + return slice[:len(slice)-1] + } + return append(slices.Clone(slice[:index]), slice[index+1:]...) +} + +func TestUnnamedResourcesCrossProductLimits(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + // Start with v9, since that's when we first track cross-product references indepdently. + for v := 9; v <= logic.LogicVersion; v++ { + v := v + t.Run(fmt.Sprintf("v%d", v), func(t *testing.T) { + t.Parallel() + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + + proto := env.TxnInfo.CurrentProtocolParams() + + sender := env.Accounts[0] + otherAccounts := make([]basics.Address, proto.MaxTxGroupSize) + for i := range otherAccounts { + otherAccounts[i][0] = byte(i + 1) + } + + assets := make([]basics.AssetIndex, proto.MaxTxGroupSize-1) + for i := range assets { + assets[i] = env.CreateAsset(sender.Addr, basics.AssetParams{Total: 100}) + } + + otherApps := make([]basics.AppIndex, proto.MaxAppTxnForeignApps) + for i := range otherApps { + otherApps[i] = env.CreateApp(sender.Addr, simulationtesting.AppParams{ + // The program version here doesn't matter + ApprovalProgram: "#pragma version 8\nint 1", + ClearStateProgram: "#pragma version 8\nint 1", + }) + } + otherAppAccounts := make([]basics.Address, len(otherApps)) + for i := range otherAppAccounts { + otherAppAccounts[i] = otherApps[i].Address() + } + + appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: resourceLimitsTestProgram(v), + ClearStateProgram: fmt.Sprintf("#pragma version %d\n int 1", v), + }) + + otherAccounts[len(otherAccounts)-1] = appID.Address() + + assetFillingTxns := make([]txntest.Txn, proto.MaxTxGroupSize-1) + for i := range assetFillingTxns { + assetFillingTxns[i] = txntest.Txn{ + Type: protocol.AssetTransferTx, + XferAsset: assets[i], + Sender: sender.Addr, + AssetReceiver: otherAccounts[i], + } + } + + testResourceAccess := func(resources unnamedResourceArguments, extra ...string) { + t.Helper() + var expectedError string + if len(extra) != 0 { + expectedError = extra[0] + } + testUnnamedResourceLimits(t, env, v, appID, resources, assetFillingTxns, 2000, expectedError) + } + + // Each test below will run against the environment we just set up. They will each run + // in separate simulations, so we can reuse the same environment and not have to worry + // about the effects of one test interfering with another. + + maxCrossProducts := proto.MaxAppTxnForeignApps * (proto.MaxAppTxnForeignApps + 2) + + var atAssetHoldingLimit unnamedResourceArguments + var assetHoldingLimitIndex int + for i := range assets { + accounts := excludingIndex(otherAccounts, i) + end := false + if (i+1)*(proto.MaxTxGroupSize-1) >= maxCrossProducts { + remaining := maxCrossProducts - i*(proto.MaxTxGroupSize-1) + accounts = accounts[:remaining] + assetHoldingLimitIndex = i + 1 + end = true + } + atAssetHoldingLimit = atAssetHoldingLimit.addAssetHoldings(assets[i], accounts...) + if end { + break + } + } + + var atAppLocalLimit unnamedResourceArguments + for i := range otherApps { + accounts := []basics.Address{sender.Addr, appID.Address()} + accounts = append(accounts, excludingIndex(otherAppAccounts, i)...) + atAppLocalLimit = atAppLocalLimit.addAppLocals(otherApps[i], accounts...) + } + atAppLocalLimit = atAppLocalLimit.addAppLocals(appID, otherAppAccounts...) + + // Hitting the limit with a combined number of asset holdings and app locals. We reuse + // most of atAppLocalLimit, but remove the last app locals and replace them with asset + // holdings. + atCombinedLimit := atAppLocalLimit[:len(atAppLocalLimit)-1] + atCombinedLimit = atCombinedLimit.addAssetHoldings(assets[0], otherAccounts[1:proto.MaxAppTxnForeignApps+1]...) + + // Exactly at asset holding limit + testResourceAccess(atAssetHoldingLimit) + + // Exactly at app local limit + testResourceAccess(atAppLocalLimit) + + // Exactly at total cross-product limit with both resource types + testResourceAccess(atCombinedLimit) + + // Over asset holding limit + testResourceAccess( + atAssetHoldingLimit. + addAssetHoldings(assets[assetHoldingLimitIndex], otherAccounts[0]). + markLimitExceeded(), + fmt.Sprintf("logic eval error: unavailable Holding %s x %d", otherAccounts[0], assets[assetHoldingLimitIndex]), + ) + + // Over app local limit + testResourceAccess( + atAppLocalLimit. + addAppLocals(appID, otherAccounts[0]). + markLimitExceeded(), + fmt.Sprintf("logic eval error: unavailable Local State %s x %d", otherAccounts[0], appID), + ) + + // Over total cross-product limit with asset holding + testResourceAccess( + atCombinedLimit. + addAssetHoldings(assets[1], otherAccounts[0]). + markLimitExceeded(), + fmt.Sprintf("logic eval error: unavailable Holding %s x %d", otherAccounts[0], assets[1]), + ) + + // Over total cross-product limit with app local + testResourceAccess( + atCombinedLimit. + addAppLocals(appID, otherAccounts[0]). + markLimitExceeded(), + fmt.Sprintf("logic eval error: unavailable Local State %s x %d", otherAccounts[0], appID), + ) + }) + } +} diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go index d11cf845b2..9283eb83d7 100644 --- a/ledger/simulation/simulator.go +++ b/ledger/simulation/simulator.go @@ -39,11 +39,12 @@ type simulatorLedger struct { // Request packs simulation related txn-group(s), and configurations that are overlapping the ones in real transactions. type Request struct { - TxnGroups [][]transactions.SignedTxn - AllowEmptySignatures bool - AllowMoreLogging bool - ExtraOpcodeBudget uint64 - TraceConfig ExecTraceConfig + TxnGroups [][]transactions.SignedTxn + AllowEmptySignatures bool + AllowMoreLogging bool + AllowUnnamedResources bool + ExtraOpcodeBudget uint64 + TraceConfig ExecTraceConfig } // Latest is part of the LedgerForSimulator interface. @@ -243,6 +244,22 @@ func (s Simulator) Simulate(simulateRequest Request) (Result, error) { } } + if simulatorTracer.result.TxnGroups[0].UnnamedResourcesAccessed != nil { + // Remove private fields for easier test comparison + simulatorTracer.result.TxnGroups[0].UnnamedResourcesAccessed.removePrivateFields() + if !simulatorTracer.result.TxnGroups[0].UnnamedResourcesAccessed.HasResources() { + simulatorTracer.result.TxnGroups[0].UnnamedResourcesAccessed = nil + } + for i := range simulatorTracer.result.TxnGroups[0].Txns { + txnResult := &simulatorTracer.result.TxnGroups[0].Txns[i] + txnResult.UnnamedResourcesAccessed.removePrivateFields() + if !txnResult.UnnamedResourcesAccessed.HasResources() { + // Clean up any unused local resource assignments + txnResult.UnnamedResourcesAccessed = nil + } + } + } + simulatorTracer.result.Block = block // Update total cost by aggregating individual txn costs diff --git a/ledger/simulation/testing/utils.go b/ledger/simulation/testing/utils.go index 0ff0d529fc..0d02ce8415 100644 --- a/ledger/simulation/testing/utils.go +++ b/ledger/simulation/testing/utils.go @@ -30,6 +30,8 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/ledger" + "github.com/algorand/go-algorand/ledger/eval" + "github.com/algorand/go-algorand/ledger/ledgercore" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" @@ -67,17 +69,11 @@ func (info TxnInfo) NewTxn(txn txntest.Txn) txntest.Txn { return txn } -// InnerTxn sets network- and parent-specific values to the given inner transaction. This is only -// useful for creating an expected inner transaction to compare against. -func (info TxnInfo) InnerTxn(parent transactions.SignedTxn, inner txntest.Txn) txntest.Txn { - inner.FirstValid = parent.Txn.FirstValid - inner.LastValid = parent.Txn.LastValid - inner.FillDefaults(info.CurrentProtocolParams()) - return inner -} - -// Environment contains the ledger and testing environment for transaction simulations +// Environment contains the ledger and testing environment for transaction simulations. It also +// provides convenience methods to execute transactions against the ledger prior to simulation. This +// allows you to create specific a ledger state before running a simulation. type Environment struct { + t *testing.T Ledger *data.Ledger // Accounts is a list of all accounts in the ledger, excluding the fee sink and rewards pool Accounts []Account @@ -91,8 +87,154 @@ func (env *Environment) Close() { env.Ledger.Close() } +// nextBlock begins evaluation of a new block, after ledger creation or endBlock() +func (env *Environment) nextBlock() *eval.BlockEvaluator { + env.t.Helper() + rnd := env.Ledger.Latest() + hdr, err := env.Ledger.BlockHdr(rnd) + require.NoError(env.t, err) + + nextHdr := bookkeeping.MakeBlock(hdr).BlockHeader + evaluator, err := env.Ledger.StartEvaluator(nextHdr, 0, 0, nil) + require.NoError(env.t, err) + return evaluator +} + +// endBlock completes the block being created, returns the ValidatedBlock for inspection +func (env *Environment) endBlock(evaluator *eval.BlockEvaluator) *ledgercore.ValidatedBlock { + env.t.Helper() + validatedBlock, err := evaluator.GenerateBlock() + require.NoError(env.t, err) + err = env.Ledger.AddValidatedBlock(*validatedBlock, agreement.Certificate{}) + require.NoError(env.t, err) + return validatedBlock +} + +// Txn creates and executes a new block with the given transaction and returns its ApplyData +func (env *Environment) Txn(txn transactions.SignedTxn) transactions.ApplyData { + env.t.Helper() + + evaluator := env.nextBlock() + err := evaluator.Transaction(txn, transactions.ApplyData{}) + require.NoError(env.t, err) + newBlock := env.endBlock(evaluator).Block() + + require.Len(env.t, newBlock.Payset, 1) + + env.TxnInfo.LatestHeader = newBlock.BlockHeader + + return newBlock.Payset[0].ApplyData +} + +// CreateAsset creates an asset with the given parameters and returns its ID +func (env *Environment) CreateAsset(creator basics.Address, params basics.AssetParams) basics.AssetIndex { + env.t.Helper() + + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.AssetConfigTx, + Sender: creator, + AssetParams: params, + }) + + ad := env.Txn(txn.SignedTxn()) + require.NotZero(env.t, ad.ConfigAsset) + + return ad.ConfigAsset +} + +// AppParams mirrors basics.AppParams, but allows the approval and clear state programs to have the +// same values that txntest.Txn accepts +type AppParams struct { + ApprovalProgram interface{} + ClearStateProgram interface{} + GlobalState basics.TealKeyValue + LocalStateSchema basics.StateSchema + GlobalStateSchema basics.StateSchema + ExtraProgramPages uint32 +} + +// CreateApp creates an application with the given parameters and returns its ID +func (env *Environment) CreateApp(creator basics.Address, params AppParams) basics.AppIndex { + env.t.Helper() + + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: creator, + ApprovalProgram: params.ApprovalProgram, + ClearStateProgram: params.ClearStateProgram, + GlobalStateSchema: params.GlobalStateSchema, + LocalStateSchema: params.LocalStateSchema, + ExtraProgramPages: params.ExtraProgramPages, + }) + + ad := env.Txn(txn.SignedTxn()) + require.NotZero(env.t, ad.ApplicationID) + + return ad.ApplicationID +} + +// TransferAlgos transfers the given amount of Algos from one account to another +func (env *Environment) TransferAlgos(from, to basics.Address, amount uint64) { + env.t.Helper() + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: from, + Receiver: to, + Amount: amount, + }) + env.Txn(txn.SignedTxn()) +} + +// TransferAsset transfers the given amount of an asset from one account to another +func (env *Environment) TransferAsset(from, to basics.Address, assetID basics.AssetIndex, amount uint64) { + env.t.Helper() + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.AssetTransferTx, + Sender: from, + AssetReceiver: to, + XferAsset: assetID, + AssetAmount: amount, + }) + env.Txn(txn.SignedTxn()) +} + +// OptIntoAsset opts the given account into the given asset +func (env *Environment) OptIntoAsset(address basics.Address, assetID basics.AssetIndex) { + env.t.Helper() + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.AssetTransferTx, + Sender: address, + AssetReceiver: address, + XferAsset: assetID, + }) + env.Txn(txn.SignedTxn()) +} + +// OptIntoApp opts the given account into the given application +func (env *Environment) OptIntoApp(address basics.Address, appID basics.AppIndex) { + env.t.Helper() + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: address, + ApplicationID: appID, + OnCompletion: transactions.OptInOC, + }) + env.Txn(txn.SignedTxn()) +} + +// Rekey rekeys the given account to the given authorizer +func (env *Environment) Rekey(account, rekeyTo basics.Address) { + env.t.Helper() + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.KeyRegistrationTx, + Sender: account, + RekeyTo: rekeyTo, + }) + env.Txn(txn.SignedTxn()) +} + // PrepareSimulatorTest creates an environment to test transaction simulations. The caller is -// responsible for calling Close() on the returned environment. +// responsible for calling Close() on the returned Environment. func PrepareSimulatorTest(t *testing.T) Environment { genesisInitState, keys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 100) @@ -152,6 +294,7 @@ func PrepareSimulatorTest(t *testing.T) Environment { } return Environment{ + t: t, Ledger: ledger, Accounts: accounts, FeeSinkAccount: feeSinkAccount, diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index 5748360972..ab3ba732fa 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -36,6 +36,14 @@ type TxnResult struct { AppBudgetConsumed uint64 LogicSigBudgetConsumed uint64 Trace *TransactionTrace + + // UnnamedResourcesAccessed is present if all of the following are true: + // * AllowUnnamedResources is true + // * The transaction cannot use shared resources (pre-v9 program) + // * The transaction accessed unnamed resources. + // + // In that case, it will be populated with the unnamed resources accessed by this transaction. + UnnamedResourcesAccessed *ResourceTracker } // TxnGroupResult contains the simulation result for a single transaction group @@ -50,6 +58,15 @@ type TxnGroupResult struct { AppBudgetAdded uint64 // AppBudgetConsumed is the total opcode cost used for this group AppBudgetConsumed uint64 + + // UnnamedResourcesAccessed will be present if AllowUnnamedResources is true. In that case, it + // will be populated with the unnamed resources accessed by this transaction group from + // transactions which can benefit from shared resources (v9 or higher programs). + // + // Any unnamed resources accessed from transactions which cannot benefit from shared resources + // will be placed in the corresponding `UnnamedResourcesAccessed` field in the appropriate + // TxnResult struct. + UnnamedResourcesAccessed *ResourceTracker } func makeTxnGroupResult(txgroup []transactions.SignedTxn) TxnGroupResult { @@ -67,10 +84,11 @@ const ResultLatestVersion = uint64(2) // ResultEvalOverrides contains the limits and parameters during a call to Simulator.Simulate type ResultEvalOverrides struct { - AllowEmptySignatures bool - MaxLogCalls *uint64 - MaxLogSize *uint64 - ExtraOpcodeBudget uint64 + AllowEmptySignatures bool + AllowUnnamedResources bool + MaxLogCalls *uint64 + MaxLogSize *uint64 + ExtraOpcodeBudget uint64 } // LogBytesLimit hardcode limit of how much bytes one can log per transaction during simulation (with AllowMoreLogging) @@ -172,8 +190,9 @@ func makeSimulationResult(lastRound basics.Round, request Request, developerAPI } resultEvalConstants := ResultEvalOverrides{ - AllowEmptySignatures: request.AllowEmptySignatures, - ExtraOpcodeBudget: request.ExtraOpcodeBudget, + AllowEmptySignatures: request.AllowEmptySignatures, + ExtraOpcodeBudget: request.ExtraOpcodeBudget, + AllowUnnamedResources: request.AllowUnnamedResources, }.AllowMoreLogging(request.AllowMoreLogging) if err := validateSimulateRequest(request, developerAPI); err != nil { diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index b56e13e42e..22406fed1e 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -57,11 +57,6 @@ func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledg tracer.relativeCursor = tracer.relativeCursor[:top] } -func (tracer *cursorEvalTracer) relativeGroupIndex() int { - top := len(tracer.relativeCursor) - 1 - return tracer.relativeCursor[top] -} - func (tracer *cursorEvalTracer) absolutePath() TxnPath { path := make(TxnPath, len(tracer.relativeCursor)) for i, relativeGroupIndex := range tracer.relativeCursor { @@ -84,6 +79,8 @@ type evalTracer struct { result *Result failedAt TxnPath + unnamedResourcePolicy *resourcePolicy + // execTraceStack keeps track of the call stack: // from top level transaction to the current inner txn that contains latest TransactionTrace. // NOTE: execTraceStack is used only for PC/Stack/Storage exposure. @@ -110,6 +107,7 @@ func makeEvalTracer(lastRound basics.Round, request Request, developerAPI bool) return &evalTracer{result: &result}, nil } +// handleError is responsible for setting the failedAt field properly. func (tracer *evalTracer) handleError(evalError error) { if evalError != nil && tracer.failedAt == nil { tracer.failedAt = tracer.absolutePath() @@ -167,13 +165,23 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { *ep.PooledApplicationBudget += int(tracer.result.EvalOverrides.ExtraOpcodeBudget) } - // Override runtime related constraints against ep, before entering txn group - ep.EvalConstants = tracer.result.EvalOverrides.LogicEvalConstants() + if ep.GetCaller() == nil { + // Override runtime related constraints against ep, before entering txn group + ep.EvalConstants = tracer.result.EvalOverrides.LogicEvalConstants() + if tracer.result.EvalOverrides.AllowUnnamedResources { + tracer.unnamedResourcePolicy = newResourcePolicy(ep, &tracer.result.TxnGroups[0]) + ep.EvalConstants.UnnamedResources = tracer.unnamedResourcePolicy + } + } } func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { tracer.handleError(evalError) tracer.cursorEvalTracer.AfterTxnGroup(ep, deltas, evalError) + + if ep.GetCaller() == nil && tracer.unnamedResourcePolicy != nil { + tracer.unnamedResourcePolicy = nil + } } func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { @@ -228,6 +236,9 @@ func (tracer *evalTracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) { // In both case, we need to add to transaction trace to the stack tracer.execTraceStack = append(tracer.execTraceStack, txnTraceStackElem) } + if ep.GetCaller() == nil && tracer.unnamedResourcePolicy != nil { + tracer.unnamedResourcePolicy.txnRootIndex = groupIndex + } tracer.cursorEvalTracer.BeforeTxn(ep, groupIndex) } @@ -367,11 +378,19 @@ func (tracer *evalTracer) AfterOpcode(cx *logic.EvalContext, evalError error) { } } - if cx.RunMode() != logic.ModeApp { - // do nothing for LogicSig ops - return + if cx.RunMode() == logic.ModeApp { + tracer.handleError(evalError) + if evalError == nil && tracer.unnamedResourcePolicy != nil { + if err := tracer.unnamedResourcePolicy.tracker.reconcileBoxWriteBudget(cx.BoxDirtyBytes(), cx.Proto.BytesPerBoxReference); err != nil { + // This should never happen, since we limit the IO budget to tracer.unnamedResourcePolicy.assignment.maxPossibleBoxIOBudget + // (as shown below), so we should never have to reconcile an unachievable budget. + panic(err.Error()) + } + + // Update box budget. It will decrease if an additional non-box resource has been accessed. + cx.SetIOBudget(tracer.unnamedResourcePolicy.tracker.maxPossibleBoxIOBudget(cx.Proto.BytesPerBoxReference)) + } } - tracer.handleError(evalError) } func (tracer *evalTracer) BeforeProgram(cx *logic.EvalContext) { @@ -379,19 +398,38 @@ func (tracer *evalTracer) BeforeProgram(cx *logic.EvalContext) { // Before Program, activated for logic sig, happens before txn group execution // we should create trace object for this txn result - if cx.RunMode() != logic.ModeApp { + if cx.RunMode() == logic.ModeSig { if tracer.result.ReturnTrace() { tracer.result.TxnGroups[0].Txns[groupIndex].Trace = &TransactionTrace{} traceRef := tracer.result.TxnGroups[0].Txns[groupIndex].Trace traceRef.programTraceRef = &traceRef.LogicSigTrace } } + + if cx.RunMode() == logic.ModeApp && tracer.unnamedResourcePolicy != nil { + globalSharing := false + for iter := cx; iter != nil; iter = iter.GetCaller() { + if iter.ProgramVersion() >= 9 { + // If some caller in the app callstack allows global sharing, global resources can + // be accessed here. Otherwise the top-level txn must declare all resources locally. + globalSharing = true + break + } + } + tracer.unnamedResourcePolicy.globalSharing = globalSharing + tracer.unnamedResourcePolicy.programVersion = cx.ProgramVersion() + if tracer.unnamedResourcePolicy.initialBoxSurplusReadBudget == nil { + s := cx.SurplusReadBudget + tracer.unnamedResourcePolicy.initialBoxSurplusReadBudget = &s + } + cx.SetIOBudget(tracer.unnamedResourcePolicy.tracker.maxPossibleBoxIOBudget(cx.Proto.BytesPerBoxReference)) + } } func (tracer *evalTracer) AfterProgram(cx *logic.EvalContext, evalError error) { groupIndex := cx.GroupIndex() - if cx.RunMode() != logic.ModeApp { + if cx.RunMode() == logic.ModeSig { // Report cost for LogicSig program and exit tracer.result.TxnGroups[0].Txns[groupIndex].LogicSigBudgetConsumed = uint64(cx.Cost()) if tracer.result.ReturnTrace() { diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 5d14f64d2f..fc21250269 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -956,7 +956,7 @@ return a.NoError(err) a.NotNil(submittedAppCreateTxn.ApplicationIndex) createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex) - a.Greater(uint64(createdAppID), uint64(0)) + a.NotZero(createdAppID) // fund app account appFundTxn, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, createdAppID.Address().String(), 0, 1_000_000, nil, "", 0, 0) @@ -988,7 +988,7 @@ return a.Nil(innerTxn.ApplicationIndex) a.NotNil(innerTxn.AssetIndex) createdAssetID := *innerTxn.AssetIndex - a.Greater(createdAssetID, uint64(0)) + a.NotZero(createdAssetID) createdAssetInfo, err := testClient.AssetInformation(createdAssetID) a.NoError(err) @@ -1236,8 +1236,10 @@ end: int 1 ` ops, err := logic.AssembleString(prog) + a.NoError(err) approval := ops.Program ops, err = logic.AssembleString("#pragma version 8\nint 1") + a.NoError(err) clearState := ops.Program gl := basics.StateSchema{} @@ -1262,7 +1264,7 @@ end: a.NoError(err) a.NotNil(submittedAppCreateTxn.ApplicationIndex) createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex) - a.Greater(uint64(createdAppID), uint64(0)) + a.NotZero(createdAppID) // fund app account appFundTxn, err := testClient.SendPaymentFromWallet( @@ -1870,7 +1872,7 @@ int 1` // get app ID a.NotNil(submittedAppCreateTxn.ApplicationIndex) createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex) - a.Greater(uint64(createdAppID), uint64(0)) + a.NotZero(createdAppID) // fund app account appFundTxn, err := testClient.SendPaymentFromWallet( @@ -1998,7 +2000,7 @@ int 1` // get app ID a.NotNil(submittedAppCreateTxn.ApplicationIndex) createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex) - a.Greater(uint64(createdAppID), uint64(0)) + a.NotZero(createdAppID) // fund app account appFundTxn, err := testClient.SendPaymentFromWallet( @@ -3202,3 +3204,258 @@ func TestSimulateScratchSlotChange(t *testing.T) { } a.Equal(expectedTraceSecondTxn, resp.TxnGroups[0].Txns[1].TransactionTrace) } + +func TestSimulateWithUnnamedResources(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + a := require.New(fixtures.SynchronizedTest(t)) + var localFixture fixtures.RestClientFixture + localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json")) + defer localFixture.Shutdown() + + testClient := localFixture.LibGoalClient + + _, err := testClient.WaitForRound(1) + a.NoError(err) + + wh, err := testClient.GetUnencryptedWalletHandle() + a.NoError(err) + addresses, err := testClient.ListAddresses(wh) + a.NoError(err) + _, senderAddress := getMaxBalAddr(t, testClient, addresses) + a.NotEmpty(senderAddress, "no addr with funds") + a.NoError(err) + + otherAddress := getDestAddr(t, testClient, nil, senderAddress, wh) + + // fund otherAddress + txn, err := testClient.SendPaymentFromWallet( + wh, nil, senderAddress, otherAddress, + 0, 1_000_000, nil, "", 0, 0, + ) + a.NoError(err) + txID := txn.ID().String() + _, err = waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second) + a.NoError(err) + + // create asset + txn, err = testClient.MakeUnsignedAssetCreateTx(100, false, "", "", "", "", "", "", "", nil, 0) + a.NoError(err) + txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn) + a.NoError(err) + // sign and broadcast + txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn) + a.NoError(err) + confirmedTxn, err := waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second) + a.NoError(err) + // get asset ID + a.NotNil(confirmedTxn.AssetIndex) + assetID := *confirmedTxn.AssetIndex + a.NotZero(assetID) + + // opt-in to asset + txn, err = testClient.MakeUnsignedAssetSendTx(assetID, 0, otherAddress, "", "") + a.NoError(err) + txn, err = testClient.FillUnsignedTxTemplate(otherAddress, 0, 0, 0, txn) + a.NoError(err) + // sign and broadcast + txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn) + a.NoError(err) + _, err = waitForTransaction(t, testClient, otherAddress, txID, 30*time.Second) + a.NoError(err) + + // transfer asset + txn, err = testClient.MakeUnsignedAssetSendTx(assetID, 1, otherAddress, "", "") + a.NoError(err) + txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn) + a.NoError(err) + // sign and broadcast + txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn) + a.NoError(err) + _, err = waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second) + a.NoError(err) + + ops, err := logic.AssembleString("#pragma version 9\n int 1") + a.NoError(err) + alwaysApprove := ops.Program + + gl := basics.StateSchema{} + lc := basics.StateSchema{} + + // create app + txn, err = testClient.MakeUnsignedAppCreateTx(transactions.OptInOC, alwaysApprove, alwaysApprove, gl, lc, nil, nil, nil, nil, nil, 0) + a.NoError(err) + txn, err = testClient.FillUnsignedTxTemplate(otherAddress, 0, 0, 0, txn) + a.NoError(err) + // sign and broadcast + txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn) + a.NoError(err) + confirmedTxn, err = waitForTransaction(t, testClient, otherAddress, txID, 30*time.Second) + a.NoError(err) + // get app ID + a.NotNil(confirmedTxn.ApplicationIndex) + otherAppID := basics.AppIndex(*confirmedTxn.ApplicationIndex) + a.NotZero(otherAppID) + + prog := fmt.Sprintf(`#pragma version 9 +txn ApplicationID +bz end + +addr %s // otherAddress +store 0 + +int %d // assetID +store 1 + +int %d // otherAppID +store 2 + +// Account access +load 0 // otherAddress +balance +assert + +// Asset params access +load 1 // assetID +asset_params_get AssetTotal +assert +int 100 +== +assert + +// Asset holding access +load 0 // otherAddress +load 1 // assetID +asset_holding_get AssetBalance +assert +int 1 +== +assert + +// App params access +load 2 // otherAppID +app_params_get AppCreator +assert +load 0 // otherAddress +== +assert + +// App local access +load 0 // otherAddress +load 2 // otherAppID +app_opted_in +assert + +// Box access +byte "A" +int 1025 +box_create +assert + +end: +int 1 +`, otherAddress, assetID, otherAppID) + + ops, err = logic.AssembleString(prog) + a.NoError(err) + approval := ops.Program + + // create app + txn, err = testClient.MakeUnsignedAppCreateTx(transactions.NoOpOC, approval, alwaysApprove, gl, lc, nil, nil, nil, nil, nil, 0) + a.NoError(err) + txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn) + a.NoError(err) + // sign and broadcast + txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn) + a.NoError(err) + confirmedTxn, err = waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second) + a.NoError(err) + // get app ID + a.NotNil(confirmedTxn.ApplicationIndex) + testAppID := basics.AppIndex(*confirmedTxn.ApplicationIndex) + a.NotZero(testAppID) + + // fund app account + txn, err = testClient.SendPaymentFromWallet( + wh, nil, senderAddress, testAppID.Address().String(), + 0, 1_000_000, nil, "", 0, 0, + ) + a.NoError(err) + txID = txn.ID().String() + _, err = waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second) + a.NoError(err) + + // construct app call + txn, err = testClient.MakeUnsignedAppNoOpTx( + uint64(testAppID), nil, nil, nil, nil, nil, + ) + a.NoError(err) + txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn) + a.NoError(err) + stxn, err := testClient.SignTransactionWithWallet(wh, nil, txn) + a.NoError(err) + + // Cannot access these resources by default + resp, err := testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: []transactions.SignedTxn{stxn}, + }, + }, + AllowUnnamedResources: false, + }) + a.NoError(err) + a.Contains(*resp.TxnGroups[0].FailureMessage, "logic eval error: invalid Account reference "+otherAddress) + a.Equal([]uint64{0}, *resp.TxnGroups[0].FailedAt) + + // It should work with AllowUnnamedResources=true + resp, err = testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: []transactions.SignedTxn{stxn}, + }, + }, + AllowUnnamedResources: true, + }) + a.NoError(err) + + expectedUnnamedGroupResources := model.SimulateUnnamedResourcesAccessed{ + Accounts: &[]string{otherAddress}, + Assets: &[]uint64{assetID}, + Apps: &[]uint64{uint64(otherAppID)}, + Boxes: &[]model.BoxReference{{App: uint64(testAppID), Name: []byte("A")}}, + ExtraBoxRefs: toPtr[uint64](1), + AssetHoldings: &[]model.AssetHoldingReference{ + {Account: otherAddress, Asset: assetID}, + }, + AppLocals: &[]model.ApplicationLocalReference{ + {Account: otherAddress, App: uint64(otherAppID)}, + }, + } + + budgetAdded, budgetUsed := uint64(700), uint64(40) + allowUnnamedResources := true + + expectedResult := v2.PreEncodedSimulateResponse{ + Version: 2, + LastRound: resp.LastRound, + EvalOverrides: &model.SimulationEvalOverrides{ + AllowUnnamedResources: &allowUnnamedResources, + }, + TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{ + { + Txns: []v2.PreEncodedSimulateTxnResult{ + { + Txn: v2.PreEncodedTxInfo{Txn: stxn}, + AppBudgetConsumed: &budgetUsed, + }, + }, + AppBudgetAdded: &budgetAdded, + AppBudgetConsumed: &budgetUsed, + UnnamedResourcesAccessed: &expectedUnnamedGroupResources, + }, + }, + } + a.Equal(expectedResult, resp) +} diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index 8dbcc849cf..71f9a22b04 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -431,3 +431,57 @@ if [[ $(echo "$SCRATCH_STORE_UNIT" | jq '."stack-pop-count"') != 1 ]]; then fi # WE DON'T TEST IN DETAILS ABOUT SCRATCH AND TRACE IN E2E SCRIPT TESTS, SEE RESTCLIENT TEST FOR DETAILS + +############################################## +# TEST ALLOW UNNAMED RESOURCES IN SIMULATION # +############################################## + +printf '#pragma version 9\nint 1' > "${TEMPDIR}/simple-v9.teal" + +RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${DIR}/tealprogs/unnamed-resource-access.teal" --clear-prog "${TEMPDIR}/simple-v9.teal" 2>&1 || true) +EXPSUCCESS='Created app with app index' +if [[ $RES != *"${EXPSUCCESS}"* ]]; then + date '+app-simulate-test FAIL the app creation for unnamed resource access should succeed %Y%m%d_%H%M%S' + false +fi + +OTHERAPPID=$APPID +APPID=$(echo "$RES" | grep Created | awk '{ print $6 }') +APPADDR=$(${gcmd} app info --app-id $APPID | grep "Application account" | awk '{ print $3 }') + +${gcmd} clerk send --from $ACCOUNT --to $APPADDR --amount 200000 + +OTHERADDR=$(${gcmd} app info --app-id $OTHERAPPID | grep "Application account" | awk '{ print $3 }') +${gcmd} clerk send --from $ACCOUNT --to $OTHERADDR --amount 100000 + +ASSETID=$(${gcmd} asset create --creator ${ACCOUNT} --total 100 | grep Created | awk '{ print $6 }') + +# Simulation with default settings should fail +${gcmd} app call --app-id $APPID --from $ACCOUNT --app-arg "addr:$OTHERADDR" --app-arg "int:$ASSETID" --app-arg "int:$OTHERAPPID" 2>&1 -o "${TEMPDIR}/unnamed-resource-access.tx" +${gcmd} clerk sign -i "${TEMPDIR}/unnamed-resource-access.tx" -o "${TEMPDIR}/unnamed-resource-access.stx" +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/unnamed-resource-access.stx") + +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the app call without allow unnamed resources should fail %Y%m%d_%H%M%S' + false +fi + +EXPECTED_FAILURE="logic eval error: invalid Account reference $OTHERADDR" + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."failure-message"') != *"${EXPECTED_FAILURE}"* ]]; then + date '+app-simulate-test FAIL the app call without allow unnamed resources should fail with the expected error %Y%m%d_%H%M%S' + false +fi + +# Simulation with --allow-unnamed-resources should succeed +RES=$(${gcmd} clerk simulate --allow-unnamed-resources -t "${TEMPDIR}/unnamed-resource-access.stx") + +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the app call with allow unnamed resources should pass %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."eval-overrides"."allow-unnamed-resources"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the app call with allow unnamed resources have the correct eval-overrides %Y%m%d_%H%M%S' + false +fi diff --git a/test/scripts/e2e_subs/tealprogs/unnamed-resource-access.teal b/test/scripts/e2e_subs/tealprogs/unnamed-resource-access.teal new file mode 100644 index 0000000000..df552c5820 --- /dev/null +++ b/test/scripts/e2e_subs/tealprogs/unnamed-resource-access.teal @@ -0,0 +1,60 @@ +#pragma version 9 +txn ApplicationID +bz end + +txn ApplicationArgs 0 // otherAddress +store 0 + +txn ApplicationArgs 1 // assetID +btoi +store 1 + +txn ApplicationArgs 2 // otherAppID +btoi +store 2 + +// Account access +load 0 // otherAddress +balance +assert + +// Asset params access +load 1 // assetID +asset_params_get AssetTotal +assert +int 100 +== +assert + +// Asset holding access +load 0 // otherAddress +load 1 // assetID +asset_holding_get AssetBalance +! +assert +! +assert + +// App params access +load 2 // otherAppID +app_params_get AppCreator +assert +txn Sender +== +assert + +// App local access +load 0 // otherAddress +load 2 // otherAppID +app_opted_in +! +assert + +// Box access +byte "A" +int 64 +box_create +assert + +end: +int 1 From a36464a3bd157e800636b588f34aa665e697d6e6 Mon Sep 17 00:00:00 2001 From: Ignacio Corderi Date: Wed, 9 Aug 2023 22:13:39 +0200 Subject: [PATCH 35/66] ledger: add pebbledb support (#5606) --- daemon/algod/server.go | 14 + go.mod | 4 +- go.sum | 11 +- ledger/ledger.go | 6 +- .../trackerdb/pebbledbdriver/pebbledriver.go | 586 ++++++++++++++++ .../store/trackerdb/pebbledbdriver/testing.go | 41 ++ .../testsuite/accounts_ext_kv_test.go | 15 +- .../trackerdb/testsuite/pebbledb_test.go | 44 ++ tools/block-generator/go.mod | 19 + tools/block-generator/go.sum | 627 ++++++++++++++++++ 10 files changed, 1358 insertions(+), 9 deletions(-) create mode 100644 ledger/store/trackerdb/pebbledbdriver/pebbledriver.go create mode 100644 ledger/store/trackerdb/pebbledbdriver/testing.go create mode 100644 ledger/store/trackerdb/testsuite/pebbledb_test.go diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 9f702a038c..037e1e1e48 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -130,6 +130,20 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes if err != nil { return fmt.Errorf("Initialize() err: %w", err) } + // TODO: remove this after making pebble support official + // and integrate the value into ReservedFDs config parameter. + if cfg.StorageEngine == "pebbledb" { + fdRequired = ot.Add(fdRequired, 1000) + if ot.Overflowed { + return errors.New( + "Initialize() overflowed when adding up fdRequired and 1000 needed for pebbledb") + } + err = util.SetFdSoftLimit(fdRequired) + if err != nil { + return fmt.Errorf("Initialize() failed to set FD limit for pebbledb backend, err: %w", err) + } + } + if cfg.IsGossipServer() { var ot basics.OverflowTracker fdRequired = ot.Add(fdRequired, uint64(cfg.IncomingConnectionsLimit)) diff --git a/go.mod b/go.mod index b63246f90b..6c88c25d5c 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/algorand/sortition v1.0.0 github.com/algorand/websocket v1.4.6 github.com/aws/aws-sdk-go v1.33.0 + github.com/cockroachdb/pebble v0.0.0-20230807162746-af8c5f279001 github.com/consensys/gnark-crypto v0.7.0 github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c github.com/dchest/siphash v1.2.1 @@ -52,9 +53,9 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cockroachdb/errors v1.8.1 // indirect github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect - github.com/cockroachdb/pebble v0.0.0-20230227185959-8285e8dd5c08 // indirect github.com/cockroachdb/redact v1.0.8 // indirect github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect @@ -64,7 +65,6 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect diff --git a/go.sum b/go.sum index 8cb249f338..935d69f151 100644 --- a/go.sum +++ b/go.sum @@ -132,18 +132,20 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4= -github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/cockroachdb/pebble v0.0.0-20230227185959-8285e8dd5c08 h1:acwj2RLsc4NK34fzBUQp5oy4oN+XTbRsEZVCX4puE+U= -github.com/cockroachdb/pebble v0.0.0-20230227185959-8285e8dd5c08/go.mod h1:9lRMC4XN3/BLPtIp6kAKwIaHu369NOf2rMucPzipz50= +github.com/cockroachdb/pebble v0.0.0-20230807162746-af8c5f279001 h1:+woucWZrHqZbpUr61pxhUnF7NSmUmrg9zT0z3Xz8U1w= +github.com/cockroachdb/pebble v0.0.0-20230807162746-af8c5f279001/go.mod h1:FN5O47SBEz5+kO9fG8UTR64g2WS1u5ZFCgTvxGjoSks= github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= +github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6 h1:DJK8W/iB+s/qkTtmXSrHA49lp5O3OsR7E6z4byOLy34= +github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/consensys/gnark-crypto v0.7.0 h1:rwdy8+ssmLYRqKp+ryRRgQJl/rCq2uv+n83cOydm5UE= github.com/consensys/gnark-crypto v0.7.0/go.mod h1:KPSuJzyxkJA8xZ/+CV47tyqkr9MmpZA3PXivK4VPrVg= @@ -293,8 +295,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/ledger/ledger.go b/ledger/ledger.go index 1b49ea5ba2..350b649240 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -38,6 +38,7 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/blockdb" "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/ledger/store/trackerdb/pebbledbdriver" "github.com/algorand/go-algorand/ledger/store/trackerdb/sqlitedriver" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" @@ -296,9 +297,12 @@ func openLedgerDB(dbPathPrefix string, dbMem bool, cfg config.Local, log logging go func() { var lerr error switch cfg.StorageEngine { + case "pebbledb": + dir := dbPathPrefix + "/tracker.pebble" + trackerDBs, lerr = pebbledbdriver.Open(dir, dbMem, config.Consensus[protocol.ConsensusCurrentVersion], log) + // anything else will initialize a sqlite engine. case "sqlite": fallthrough - // anything else will initialize a sqlite engine. default: file := dbPathPrefix + ".tracker.sqlite" trackerDBs, lerr = sqlitedriver.Open(file, dbMem, log) diff --git a/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go b/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go new file mode 100644 index 0000000000..ebef106db2 --- /dev/null +++ b/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go @@ -0,0 +1,586 @@ +// 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 pebbledbdriver + +import ( + "context" + "io" + "runtime" + "time" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/ledger/store/trackerdb/generickv" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/util/db" + "github.com/cockroachdb/pebble" + "github.com/cockroachdb/pebble/bloom" + "github.com/cockroachdb/pebble/vfs" +) + +const ( + // minCache is the minimum amount of memory in megabytes to allocate to pebble + // read and write caching, split half and half. + minCache = 16 + + // minHandles is the minimum number of files handles to allocate to the open + // database files. + minHandles = 16 +) + +type trackerStore struct { + kvs kvstore + proto config.ConsensusParams + // use the generickv implementations + trackerdb.Reader + trackerdb.Writer + trackerdb.Catchpoint +} + +// Open opens a Pebble db database +func Open(dbdir string, inMem bool, proto config.ConsensusParams, log logging.Logger) (trackerdb.Store, error) { + cache := 128 // this divided by 2 and by memTableLimit = 1GB /(2 * 16) = 32MB per memtable + handles := 1000 + + // Ensure we have some minimal caching and file guarantees + if cache < minCache { + cache = minCache + } + if handles < minHandles { + handles = minHandles + } + + // The max memtable size is limited by the uint32 offsets stored in + // internal/arenaskl.node, DeferredBatchOp, and flushableBatchEntry. + // Taken from https://github.com/cockroachdb/pebble/blob/master/open.go#L38 + maxMemTableSize := 4<<30 - 1 // Capped by 4 GB + + memTableLimit := 2 // default: 2 + memTableSize := cache * 1024 * 1024 / 2 / memTableLimit + if memTableSize > maxMemTableSize { + memTableSize = maxMemTableSize + } + + // configure pebbledb + opts := &pebble.Options{ + // logging + Logger: log, + + // Pebble has a single combined cache area and the write + // buffers are taken from this too. Assign all available + // memory allowance for cache. + Cache: pebble.NewCache(int64(cache * 1024 * 1024)), // default: 8 MB + MaxOpenFiles: handles, // default: 1000 + + // The size of memory table(as well as the write buffer). + // Note, there may have more than two memory tables in the system. + MemTableSize: memTableSize, // default: 4MB + + // MemTableStopWritesThreshold places a hard limit on the size + // of the existent MemTables(including the frozen one). + // Note, this must be the number of tables not the size of all memtables + // according to https://github.com/cockroachdb/pebble/blob/master/options.go#L738-L742 + // and to https://github.com/cockroachdb/pebble/blob/master/db.go#L1892-L1903. + MemTableStopWritesThreshold: memTableLimit, // default: 2 + + // Sync sstables periodically in order to smooth out writes to disk. This + // option does not provide any persistency guarantee, but is used to avoid + // latency spikes if the OS automatically decides to write out a large chunk + // of dirty filesystem buffers. This option only controls SSTable syncs; WAL + // syncs are controlled by WALBytesPerSync. + BytesPerSync: 512 * 1024, // default: 512 KB + + // WALBytesPerSync sets the number of bytes to write to a WAL before calling + // Sync on it in the background. Just like with BytesPerSync above, this + // helps smooth out disk write latencies, and avoids cases where the OS + // writes a lot of buffered data to disk at once. However, this is less + // necessary with WALs, as many write operations already pass in + // Sync = true. + // + // The default value is 0, i.e. no background syncing. This matches the + // default behaviour in RocksDB. + WALBytesPerSync: 0, // default: 0 + + // The default compaction concurrency(1 thread), + // Here use all available CPUs for faster compaction. + MaxConcurrentCompactions: func() int { return runtime.NumCPU() }, // default: 1 + + // The count of L0 files necessary to trigger an L0 compaction. + L0CompactionFileThreshold: 500, // default: 500 + + // The amount of L0 read-amplification necessary to trigger an L0 compaction + L0CompactionThreshold: 4, // default: 4 + + // Hard limit on L0 read-amplification, computed as the number of L0 + // sublevels. Writes are stopped when this threshold is reached. + L0StopWritesThreshold: 12, // default: 12 + + // The maximum number of bytes for LBase. The base level is the level which + // L0 is compacted into. The base level is determined dynamically based on + // the existing data in the LSM. The maximum number of bytes for other levels + // is computed dynamically based on the base level's maximum size. When the + // maximum number of bytes for a level is exceeded, compaction is requested. + LBaseMaxBytes: 64 * 1024 * 1024, // default: 64 MB + + // Per-level options. Options for at least one level must be specified. The + // options for the last level are used for all subsequent levels. + Levels: make([]pebble.LevelOptions, 7), + } + + // Disable seek compaction explicitly. Check https://github.com/ethereum/go-ethereum/pull/20130 + // for more details. + opts.Experimental.ReadSamplingMultiplier = -1 + + // The target file size for the level. + // WARNING: unclear if this can be changed during the lifetime of the db + // if it can be changed, it might make things slower for a time + opts.Levels[0].TargetFileSize = 2 * 1024 * 1024 // default: 4 MB + + // configure the levels + for i := 0; i < len(opts.Levels); i++ { + l := &opts.Levels[i] + // BlockSize is the target uncompressed size in bytes of each table block. + // WARNING: unclear if this can be changed during the lifetime of the db + // if it can be changed, it might make things slower for a time + l.BlockSize = 4 * 1024 // default: 4 KB + + // IndexBlockSize is the target uncompressed size in bytes of each index + // block. When the index block size is larger than this target, two-level + // indexes are automatically enabled. Setting this option to a large value + // (such as math.MaxInt32) disables the automatic creation of two-level + // indexes. + // + // The default value is the value of BlockSize. + // WARNING: unclear if this can be changed during the lifetime of the db + // if it can be changed, it might make things slower for a time + l.IndexBlockSize = l.BlockSize + + // FilterPolicy defines a filter algorithm (such as a Bloom filter) that can + // reduce disk reads for Get calls. + // + // One such implementation is bloom.FilterPolicy(10) from the pebble/bloom + // package. + // + // The default value means to use no filter. + // WARNING: unclear if this can be changed during the lifetime of the db + // if it can be changed, it might make things slower for a time + l.FilterPolicy = bloom.FilterPolicy(10) + + // FilterType defines whether an existing filter policy is applied at a + // block-level or table-level. Block-level filters use less memory to create, + // but are slower to access as a check for the key in the index must first be + // performed to locate the filter block. A table-level filter will require + // memory proportional to the number of keys in an sstable to create, but + // avoids the index lookup when determining if a key is present. Table-level + // filters should be preferred except under constrained memory situations. + // WARNING: unclear if this can be changed during the lifetime of the db + // if it can be changed, it might make things slower for a time + l.FilterType = pebble.TableFilter + + // Compression defines the per-block compression to use. + // WARNING: unclear if this can be changed during the lifetime of the db + // if it can be changed, it might make things slower for a time + l.Compression = pebble.SnappyCompression // default: SnappyCompression + + if i > 0 { + // The target file size for the level. + // WARNING: unclear if this can be changed during the lifetime of the db + // if it can be changed, it might make things slower for a time + l.TargetFileSize = opts.Levels[i-1].TargetFileSize + } + } + + if inMem { + opts.FS = vfs.NewMem() + } + db, err := pebble.Open(dbdir+".pebbledb", opts) + if err != nil { + return nil, err + } + // no fsync + wo := &pebble.WriteOptions{Sync: false} + kvs := kvstore{Pdb: db, wo: wo} + var store trackerdb.Store + store = &trackerStore{ + kvs, + proto, + generickv.MakeReader(&kvs, proto), + generickv.MakeWriter(store, &kvs, &kvs), + generickv.MakeCatchpoint(), + } + return store, nil +} + +// IsSharedCacheConnection implements trackerdb.Store +func (s *trackerStore) IsSharedCacheConnection() bool { + return false +} + +// SetSynchronousMode implements trackerdb.Store +func (s *trackerStore) SetSynchronousMode(ctx context.Context, mode db.SynchronousMode, fullfsync bool) (err error) { + // TODO + return nil +} + +// RunMigrations implements trackerdb.Store +func (s *trackerStore) RunMigrations(ctx context.Context, params trackerdb.Params, log logging.Logger, targetVersion int32) (mgr trackerdb.InitParams, err error) { + // create a anonym struct that impls the interface for the migration runner + db := struct { + *trackerStore + *kvstore + }{s, &s.kvs} + return generickv.RunMigrations(ctx, db, params, targetVersion) +} + +// Batch implements trackerdb.Store +func (s *trackerStore) Batch(fn trackerdb.BatchFn) (err error) { + return s.BatchContext(context.Background(), fn) +} + +// BatchContext implements trackerdb.Store +func (s *trackerStore) BatchContext(ctx context.Context, fn trackerdb.BatchFn) (err error) { + handle, err := s.BeginBatch(ctx) + if err != nil { + return + } + defer handle.Close() + + // run the batch + err = fn(ctx, handle) + if err != nil { + return + } + + // commit the batch + err = handle.Commit() + if err != nil { + return + } + + return err +} + +// BeginBatch implements trackerdb.Store +func (s *trackerStore) BeginBatch(ctx context.Context) (trackerdb.Batch, error) { + scope := batchScope{store: s, wb: s.kvs.Pdb.NewBatch(), wo: s.kvs.wo, db: s.kvs.Pdb} + + return &struct { + batchScope + trackerdb.Writer + }{scope, generickv.MakeWriter(s, &scope, &s.kvs)}, nil +} + +// Snapshot implements trackerdb.Store +func (s *trackerStore) Snapshot(fn trackerdb.SnapshotFn) (err error) { + return s.SnapshotContext(context.Background(), fn) +} + +// SnapshotContext implements trackerdb.Store +func (s *trackerStore) SnapshotContext(ctx context.Context, fn trackerdb.SnapshotFn) (err error) { + handle, err := s.BeginSnapshot(ctx) + if err != nil { + return + } + defer handle.Close() + + // run the snapshot + err = fn(ctx, handle) + if err != nil { + return + } + + return err +} + +// BeginSnapshot implements trackerdb.Store +func (s *trackerStore) BeginSnapshot(ctx context.Context) (trackerdb.Snapshot, error) { + scope := snapshotScope{db: s.kvs.Pdb, snap: s.kvs.Pdb.NewSnapshot()} + return &struct { + snapshotScope + trackerdb.Reader + }{scope, generickv.MakeReader(&scope, s.proto)}, nil +} + +// Transaction implements trackerdb.Store +func (s *trackerStore) Transaction(fn trackerdb.TransactionFn) (err error) { + return s.TransactionContext(context.Background(), fn) +} + +// TransactionContext implements trackerdb.Store +func (s *trackerStore) TransactionContext(ctx context.Context, fn trackerdb.TransactionFn) (err error) { + handle, err := s.BeginTransaction(ctx) + if err != nil { + return + } + defer handle.Close() + + // run the transaction + err = fn(ctx, handle) + if err != nil { + return + } + + // commit the transaction + err = handle.Commit() + if err != nil { + return + } + + return err +} + +// BeginTransaction implements trackerdb.Store +func (s *trackerStore) BeginTransaction(ctx context.Context) (trackerdb.Transaction, error) { + scope := transactionScope{ + store: s, + db: s.kvs.Pdb, + wo: s.kvs.wo, + snap: s.kvs.Pdb.NewSnapshot(), + wb: s.kvs.Pdb.NewBatch(), + } + + return &struct { + transactionScope + trackerdb.Reader + trackerdb.Writer + trackerdb.Catchpoint + }{scope, generickv.MakeReader(&scope, s.proto), generickv.MakeWriter(s, &scope, &scope), generickv.MakeCatchpoint()}, nil +} + +// Vacuum implements trackerdb.Store +func (s *trackerStore) Vacuum(ctx context.Context) (stats db.VacuumStats, err error) { + // TODO + return db.VacuumStats{}, nil +} + +// ResetToV6Test implements trackerdb.Store +func (s *trackerStore) ResetToV6Test(ctx context.Context) error { + // TODO + return nil +} + +// Close implements trackerdb.Store +func (s *trackerStore) Close() { + s.kvs.Pdb.Close() +} + +// +// generic impls +// + +func mapPebbleErrors(err error) error { + switch err { + case pebble.ErrNotFound: + return trackerdb.ErrNotFound + default: + return err + } +} + +type kvstore struct { + Pdb *pebble.DB + wo *pebble.WriteOptions +} + +func (s *kvstore) Set(key, value []byte) error { + return s.Pdb.Set(key, value, s.wo) +} + +func (s *kvstore) Get(key []byte) (value []byte, closer io.Closer, err error) { + value, closer, err = s.Pdb.Get(key) + err = mapPebbleErrors(err) + return +} + +func (s *kvstore) NewIter(low, high []byte, reverse bool) generickv.KvIter { + opts := pebble.IterOptions{LowerBound: low, UpperBound: high} + return newIter(s.Pdb.NewIter(&opts), reverse) +} + +func (s *kvstore) Delete(key []byte) error { + return s.Pdb.Delete(key, s.wo) +} + +func (s *kvstore) DeleteRange(start, end []byte) error { + return s.Pdb.DeleteRange(start, end, s.wo) +} + +type batchScope struct { + // Hack: we should tray to impl without this field + store *trackerStore + db *pebble.DB + wo *pebble.WriteOptions + wb *pebble.Batch +} + +func (bs batchScope) Set(key, value []byte) error { + return bs.wb.Set(key, value, bs.wo) +} + +func (bs batchScope) Delete(key []byte) error { + return bs.wb.Delete(key, bs.wo) +} + +func (bs batchScope) DeleteRange(start, end []byte) error { + return bs.wb.DeleteRange(start, end, bs.wo) +} + +func (bs batchScope) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) { + // noop + return time.Now(), nil +} + +func (bs batchScope) Commit() error { + return bs.wb.Commit(bs.wo) +} + +func (bs batchScope) Close() error { + return bs.wb.Close() +} + +type snapshotScope struct { + db *pebble.DB + snap *pebble.Snapshot +} + +func (ss snapshotScope) Get(key []byte) (value []byte, closer io.Closer, err error) { + value, closer, err = ss.snap.Get(key) + err = mapPebbleErrors(err) + return +} + +func (ss snapshotScope) NewIter(low, high []byte, reverse bool) generickv.KvIter { + opts := pebble.IterOptions{LowerBound: low, UpperBound: high} + return newIter(ss.snap.NewIter(&opts), reverse) +} + +func (ss snapshotScope) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) { + // noop + return time.Now(), nil +} + +func (ss snapshotScope) Close() error { + return ss.snap.Close() +} + +type transactionScope struct { + // Hack: we should tray to impl without this field + store *trackerStore + db *pebble.DB + wo *pebble.WriteOptions + snap *pebble.Snapshot + wb *pebble.Batch +} + +func (txs transactionScope) Set(key, value []byte) error { + return txs.wb.Set(key, value, txs.wo) +} + +func (txs transactionScope) Get(key []byte) (value []byte, closer io.Closer, err error) { + value, closer, err = txs.snap.Get(key) + err = mapPebbleErrors(err) + return +} + +func (txs transactionScope) NewIter(low, high []byte, reverse bool) generickv.KvIter { + opts := pebble.IterOptions{LowerBound: low, UpperBound: high} + return newIter(txs.snap.NewIter(&opts), reverse) +} + +func (txs transactionScope) Delete(key []byte) error { + return txs.wb.Delete(key, txs.wo) +} + +func (txs transactionScope) DeleteRange(start, end []byte) error { + return txs.wb.DeleteRange(start, end, txs.wo) +} + +func (txs *transactionScope) RunMigrations(ctx context.Context, params trackerdb.Params, log logging.Logger, targetVersion int32) (mgr trackerdb.InitParams, err error) { + // create a anonym struct that impls the interface for the migration runner + db := struct { + *trackerStore + *kvstore + }{txs.store, &txs.store.kvs} + return generickv.RunMigrations(ctx, db, params, targetVersion) +} + +func (txs transactionScope) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) { + // noop + return time.Now(), nil +} + +func (txs transactionScope) Commit() error { + return txs.wb.Commit(txs.wo) +} + +func (txs transactionScope) Close() error { + txs.snap.Close() // ignore error + return txs.wb.Close() +} + +type pebbleIter struct { + iter *pebble.Iterator + reverse bool + firstCall bool +} + +func newIter(iter *pebble.Iterator, reverse bool) *pebbleIter { + return &pebbleIter{iter, reverse, true} +} + +func (i *pebbleIter) Next() bool { + if i.firstCall { + i.firstCall = false + if i.reverse { + return i.iter.Last() + } + return i.iter.First() + } + if i.reverse { + return i.iter.Prev() + } + return i.iter.Next() +} +func (i *pebbleIter) Valid() bool { return i.iter.Valid() } +func (i *pebbleIter) Close() { i.iter.Close() } + +func (i *pebbleIter) Key() []byte { + k := i.iter.Key() + ret := make([]byte, len(k)) + copy(ret, k) + return ret +} + +func (i *pebbleIter) Value() ([]byte, error) { + v := i.iter.Value() + ret := make([]byte, len(v)) + copy(ret, v) + return ret, nil +} + +// KeySlice is a zero copy slice only valid until iter.Next() or iter.Close() is called. +func (i *pebbleIter) KeySlice() generickv.Slice { return pebbleSlice(i.iter.Key()) } + +// ValueSlice is a zero copy slice only valid until iter.Next() or iter.Close() is called. +func (i *pebbleIter) ValueSlice() (generickv.Slice, error) { return pebbleSlice(i.iter.Value()), nil } + +type pebbleSlice []byte + +func (s pebbleSlice) Data() []byte { return s } +func (s pebbleSlice) Free() {} +func (s pebbleSlice) Size() int { return len(s) } +func (s pebbleSlice) Exists() bool { return s != nil } diff --git a/ledger/store/trackerdb/pebbledbdriver/testing.go b/ledger/store/trackerdb/pebbledbdriver/testing.go new file mode 100644 index 0000000000..2724eb01fb --- /dev/null +++ b/ledger/store/trackerdb/pebbledbdriver/testing.go @@ -0,0 +1,41 @@ +// 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 pebbledbdriver + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/protocol" + "github.com/stretchr/testify/require" +) + +// OpenForTesting opens a sqlite db file for testing purposes. +func OpenForTesting(t testing.TB, inMemory bool) trackerdb.Store { + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + // create a tmp dir for the db, the testing runtime will clean it up automatically + dir := fmt.Sprintf("%s/db", t.TempDir()) + + db, err := Open(dir, inMemory, proto, logging.TestingLog(t)) + require.NoErrorf(t, err, "Dir : %s\nInMemory: %v", dir, inMemory) + + return db +} diff --git a/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go index 3c17c96b8e..aebabd781f 100644 --- a/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go +++ b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go @@ -177,6 +177,9 @@ func CustomTestOnlineAccountParams(t *customT) { ar, err := t.db.MakeAccountsReader() require.NoError(t, err) + aor, err := t.db.MakeOnlineAccountsOptimizedReader() + require.NoError(t, err) + // generate some test data startRound := basics.Round(0) roundParams := []ledgercore.OnlineRoundParamsData{ @@ -194,7 +197,17 @@ func CustomTestOnlineAccountParams(t *customT) { err = aw.AccountsPutOnlineRoundParams(roundParams, startRound) require.NoError(t, err) - // read round params + // lookup single round params + read1, err := aor.LookupOnlineRoundParams(basics.Round(1)) + require.NoError(t, err) + require.Equal(t, roundParams[1], read1) + + // lookup single round params (not found) + _, err = aor.LookupOnlineRoundParams(basics.Round(9000)) + require.Error(t, err) + require.Equal(t, trackerdb.ErrNotFound, err) + + // read all round params readParams, endRound, err := ar.AccountsOnlineRoundParams() require.NoError(t, err) require.Len(t, readParams, 3) // assert boundries diff --git a/ledger/store/trackerdb/testsuite/pebbledb_test.go b/ledger/store/trackerdb/testsuite/pebbledb_test.go new file mode 100644 index 0000000000..56f614b7e9 --- /dev/null +++ b/ledger/store/trackerdb/testsuite/pebbledb_test.go @@ -0,0 +1,44 @@ +// 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 testsuite + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/ledger/store/trackerdb/pebbledbdriver" + "github.com/algorand/go-algorand/logging" + "github.com/stretchr/testify/require" +) + +func TestPebbleDB(t *testing.T) { + dbFactory := func(proto config.ConsensusParams) dbForTests { + // create a tmp dir for the db, the testing runtime will clean it up automatically + dir := fmt.Sprintf("%s/db", t.TempDir()) + + db, err := pebbledbdriver.Open(dir, false, proto, logging.TestingLog(t)) + require.NoError(t, err) + + seedDb(t, db) + + return db + } + + // run the suite + runGenericTestsWithDB(t, dbFactory) +} diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 6eadc0421a..bc58c9a96b 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -24,10 +24,20 @@ require ( github.com/algorand/sortition v1.0.0 // indirect github.com/algorand/websocket v1.4.6 // indirect github.com/aws/aws-sdk-go v1.33.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cockroachdb/errors v1.8.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect + github.com/cockroachdb/pebble v0.0.0-20230807162746-af8c5f279001 // indirect + github.com/cockroachdb/redact v1.0.8 // indirect + github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6 // indirect github.com/consensys/gnark-crypto v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/dchest/siphash v1.2.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // indirect @@ -35,9 +45,13 @@ require ( github.com/ipfs/go-cid v0.4.1 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/kr/pretty v0.2.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-sqlite3 v1.10.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.55 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect @@ -53,6 +67,10 @@ require ( github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -62,6 +80,7 @@ require ( golang.org/x/net v0.12.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/tools v0.11.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 // indirect lukechampine.com/blake3 v1.2.1 // indirect ) diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index 7c2b241889..7b3b28e77e 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -1,5 +1,52 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= +github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/algorand/avm-abi v0.2.0 h1:bkjsG+BOEcxUcnGSALLosmltE0JZdg+ZisXKx0UDX2k= github.com/algorand/avm-abi v0.2.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= github.com/algorand/falcon v0.1.0 h1:xl832kfZ7hHG6B4p90DQynjfKFGbIUgUOnsRiMZXfAo= @@ -18,12 +65,49 @@ github.com/algorand/sortition v1.0.0 h1:PJiZtdSTBm4nArQrZXBnhlljHXhuyAXRJBqVWowQ github.com/algorand/sortition v1.0.0/go.mod h1:23CZwAbTWPv0bBsq+Php/2J6Y/iXDyzlfcZyepeY5Fo= github.com/algorand/websocket v1.4.6 h1:I0kV4EYwatuUrKtNiwzYYgojgwh6pksDmlqntKG2Woc= github.com/algorand/websocket v1.4.6/go.mod h1:HJmdGzFtnlUQ4nTzZP6WrT29oGYf1t6Ybi64vROcT+M= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go v1.33.0 h1:Bq5Y6VTLbfnJp1IV8EL/qUU5qO1DYHda/zis/sqevkY= github.com/aws/aws-sdk-go v1.33.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= +github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= +github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/cockroachdb/pebble v0.0.0-20230807162746-af8c5f279001 h1:+woucWZrHqZbpUr61pxhUnF7NSmUmrg9zT0z3Xz8U1w= +github.com/cockroachdb/pebble v0.0.0-20230807162746-af8c5f279001/go.mod h1:FN5O47SBEz5+kO9fG8UTR64g2WS1u5ZFCgTvxGjoSks= +github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= +github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= +github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6 h1:DJK8W/iB+s/qkTtmXSrHA49lp5O3OsR7E6z4byOLy34= +github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/consensys/gnark-crypto v0.7.0 h1:rwdy8+ssmLYRqKp+ryRRgQJl/rCq2uv+n83cOydm5UE= github.com/consensys/gnark-crypto v0.7.0/go.mod h1:KPSuJzyxkJA8xZ/+CV47tyqkr9MmpZA3PXivK4VPrVg= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -31,33 +115,197 @@ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= +github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= +github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= +github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= +github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= @@ -65,9 +313,17 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8Rv github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -88,85 +344,456 @@ github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsC github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= +github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olivere/elastic v6.2.14+incompatible h1:k+KadwNP/dkXE0/eu+T6otk1+5fe0tEpPyQJ4XVm5i8= github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 h1:q/fZgS8MMadqFFGa8WL4Oyz+TmjiZfi8UrzWhTl8d5w= gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009/go.mod h1:O0bY1e/dSoxMYZYTHP0SWKxG5EWLEvKR9/cOjWPPMKU= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From fb8adcfb809ed218fc1a3bc6e338b6064286b030 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:17:21 -0400 Subject: [PATCH 36/66] tests: fix close - commit data race in tracker tests (#5619) --- ledger/acctonline_test.go | 3 +-- ledger/acctupdates_test.go | 35 +++++++++++++---------------- ledger/catchpointfilewriter_test.go | 6 ++--- ledger/voters_test.go | 15 +++++-------- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index a8f7a8fca2..1aaf7080c5 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -730,8 +730,7 @@ func TestAcctOnlineRoundParamsCache(t *testing.T) { conf := config.GetDefaultLocal() au, ao := newAcctUpdates(t, ml, conf) - defer au.close() - defer ao.close() + // au and ao are closed via ml.Close() -> ml.trackers.close() // cover 10 genesis blocks rewardLevel := uint64(0) diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index d938b0d530..b900f3e7dd 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -532,8 +532,7 @@ func testAcctUpdates(t *testing.T, conf config.Local) { defer ml.Close() au, ao := newAcctUpdates(t, ml, conf) - defer au.close() - defer ao.close() + // au and ao are closed via ml.Close() -> ml.trackers.close() // cover 10 genesis blocks rewardLevel := uint64(0) @@ -662,9 +661,8 @@ func BenchmarkBalancesChanges(b *testing.B) { conf := config.GetDefaultLocal() maxAcctLookback := conf.MaxAcctLookback - au, ao := newAcctUpdates(b, ml, conf) - defer au.close() - defer ao.close() + au, _ := newAcctUpdates(b, ml, conf) + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() // cover initialRounds genesis blocks rewardLevel := uint64(0) @@ -798,7 +796,7 @@ func testAcctUpdatesUpdatesCorrectness(t *testing.T, cfg config.Local) { } au, _ := newAcctUpdates(t, ml, cfg) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() // cover 10 genesis blocks rewardLevel := uint64(0) @@ -929,7 +927,7 @@ func TestBoxNamesByAppIDs(t *testing.T) { conf := config.GetDefaultLocal() au, _ := newAcctUpdates(t, ml, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() knownCreatables := make(map[basics.CreatableIndex]bool) opts := auNewBlockOpts{ledgercore.AccountDeltas{}, protocol.ConsensusCurrentVersion, protoParams, knownCreatables} @@ -1050,7 +1048,7 @@ func TestKVCache(t *testing.T) { conf := config.GetDefaultLocal() au, _ := newAcctUpdates(t, ml, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() knownCreatables := make(map[basics.CreatableIndex]bool) opts := auNewBlockOpts{ledgercore.AccountDeltas{}, protocol.ConsensusCurrentVersion, protoParams, knownCreatables} @@ -1224,7 +1222,7 @@ func BenchmarkLargeMerkleTrieRebuild(b *testing.B) { cfg := config.GetDefaultLocal() cfg.Archival = true au, _ := newAcctUpdates(b, ml, cfg) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() // at this point, the database was created. We want to fill the accounts data accountsNumber := 6000000 * b.N @@ -1613,7 +1611,7 @@ func TestAcctUpdatesCachesInitialization(t *testing.T) { conf = config.GetDefaultLocal() au, _ = newAcctUpdates(t, ml2, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() // make sure the deltas array end up containing only the most recent 320 rounds. require.Equal(t, int(conf.MaxAcctLookback), len(au.deltas)) @@ -1641,7 +1639,7 @@ func TestAcctUpdatesSplittingConsensusVersionCommits(t *testing.T) { conf := config.GetDefaultLocal() au, _ := newAcctUpdates(t, ml, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() // cover initialRounds genesis blocks rewardLevel := uint64(0) @@ -1746,7 +1744,7 @@ func TestAcctUpdatesSplittingConsensusVersionCommitsBoundary(t *testing.T) { conf := config.GetDefaultLocal() au, _ := newAcctUpdates(t, ml, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() // cover initialRounds genesis blocks rewardLevel := uint64(0) @@ -1881,7 +1879,7 @@ func TestAcctUpdatesResources(t *testing.T) { conf := config.GetDefaultLocal() au, _ := newAcctUpdates(t, ml, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() var addr1 basics.Address var addr2 basics.Address @@ -2088,7 +2086,7 @@ func TestAcctUpdatesLookupLatest(t *testing.T) { conf := config.GetDefaultLocal() au, _ := newAcctUpdates(t, ml, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() for addr, acct := range accts { acctData, validThrough, withoutRewards, err := au.lookupLatest(addr) require.NoError(t, err) @@ -2124,8 +2122,7 @@ func testAcctUpdatesLookupRetry(t *testing.T, assertFn func(au *accountUpdates, defer ml.Close() au, ao := newAcctUpdates(t, ml, conf) - defer au.close() - defer ao.close() + // au and ao are closed via ml.Close() -> ml.trackers.close() // cover 10 genesis blocks rewardLevel := uint64(0) @@ -2367,7 +2364,7 @@ func TestAcctUpdatesLookupLatestCacheRetry(t *testing.T) { conf := config.GetDefaultLocal() au, _ := newAcctUpdates(t, ml, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() var addr1 basics.Address for addr := range accts[0] { @@ -2496,7 +2493,7 @@ func TestAcctUpdatesLookupResources(t *testing.T) { conf := config.GetDefaultLocal() au, _ := newAcctUpdates(t, ml, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() var addr1 basics.Address for addr := range accts[0] { @@ -2576,7 +2573,7 @@ func TestAcctUpdatesLookupStateDelta(t *testing.T) { conf := config.GetDefaultLocal() au, _ := newAcctUpdates(t, ml, conf) - defer au.close() + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() knownCreatables := make(map[basics.CreatableIndex]bool) diff --git a/ledger/catchpointfilewriter_test.go b/ledger/catchpointfilewriter_test.go index da5bc3c555..f378f633a3 100644 --- a/ledger/catchpointfilewriter_test.go +++ b/ledger/catchpointfilewriter_test.go @@ -132,7 +132,7 @@ func verifyStateProofVerificationContextWrite(t *testing.T, data []ledgercore.St au, _ := newAcctUpdates(t, ml, conf) err := au.loadFromDisk(ml, 0) require.NoError(t, err) - au.close() + au.close() // it is OK to close it here - no data race since commitSyncer is not active fileName := filepath.Join(temporaryDirectory, "15.data") mockCommitData := make([]verificationCommitContext, 0) @@ -258,7 +258,7 @@ func TestBasicCatchpointWriter(t *testing.T) { au, _ := newAcctUpdates(t, ml, conf) err := au.loadFromDisk(ml, 0) require.NoError(t, err) - au.close() + au.close() // it is OK to close it here - no data race since commitSyncer is not active fileName := filepath.Join(temporaryDirectory, "15.data") err = ml.trackerDB().Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { @@ -425,7 +425,7 @@ func TestCatchpointReadDatabaseOverflowSingleAccount(t *testing.T) { au, _ := newAcctUpdates(t, ml, conf) err := au.loadFromDisk(ml, 0) require.NoError(t, err) - au.close() + au.close() // it is OK to close it here - no data race since commitSyncer is not active catchpointDataFilePath := filepath.Join(temporaryDirectory, "15.data") err = ml.trackerDB().Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { diff --git a/ledger/voters_test.go b/ledger/voters_test.go index 190997c92d..0ec6d2baad 100644 --- a/ledger/voters_test.go +++ b/ledger/voters_test.go @@ -122,9 +122,8 @@ func TestVoterTrackerDeleteVotersAfterStateproofConfirmed(t *testing.T) { conf := config.GetDefaultLocal() // To cause all blocks to be committed, for easier processing by the voters tracker. conf.MaxAcctLookback = 0 - au, ao := newAcctUpdates(t, ml, conf) - defer au.close() - defer ao.close() + _, ao := newAcctUpdates(t, ml, conf) + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() i := uint64(1) // adding blocks to the voterstracker (in order to pass the numOfIntervals*stateproofInterval we add 1) @@ -177,9 +176,8 @@ func TestLimitVoterTracker(t *testing.T) { conf := config.GetDefaultLocal() // To cause all blocks to be committed, for easier processing by the voters tracker. conf.MaxAcctLookback = 0 - au, ao := newAcctUpdates(t, ml, conf) - defer au.close() - defer ao.close() + _, ao := newAcctUpdates(t, ml, conf) + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() i := uint64(1) @@ -261,9 +259,8 @@ func TestTopNAccountsThatHaveNoMssKeys(t *testing.T) { defer ml.Close() conf := config.GetDefaultLocal() - au, ao := newAcctUpdates(t, ml, conf) - defer au.close() - defer ao.close() + _, ao := newAcctUpdates(t, ml, conf) + // accountUpdates and onlineAccounts are closed via: ml.Close() -> ml.trackers.close() i := uint64(1) for ; i < (intervalForTest)+1; i++ { From d92a3bf0e316aedd3c3c8111dc2bf9d1342aaee7 Mon Sep 17 00:00:00 2001 From: algo-devops-service <80971703+Algo-devops-service@users.noreply.github.com> Date: Thu, 10 Aug 2023 14:10:42 -0400 Subject: [PATCH 37/66] CICD: go-algorand relstable3.17.0-remerge mergeback (#5648) --- config/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/version.go b/config/version.go index c99256a2f4..d1954ab315 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 17 +const VersionMinor = 18 // Version is the type holding our full version information. type Version struct { From 435532d83b6ae97537e634919de0055e3cf6cce4 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 10 Aug 2023 19:52:56 -0400 Subject: [PATCH 38/66] build: upgrade to go1.20.6 (#5577) Co-authored-by: chris erway --- .circleci/config.yml | 3 +-- .github/workflows/reviewdog.yml | 2 +- Dockerfile | 2 +- Makefile | 5 ----- scripts/get_golang_version.sh | 2 +- 5 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d2898da25a..47af20f6b4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -457,7 +457,7 @@ commands: shell: bash.exe command: | choco install -y msys2 pacman make wget --force - choco install -y golang --version=1.20.5 --force + choco install -y golang --version=1.20.6 --force choco install -y python3 --version=3.7.3 --force export msys2='cmd //C RefreshEnv.cmd ' export msys2+='& set MSYS=winsymlinks:nativestrict ' @@ -602,7 +602,6 @@ commands: export PACKAGE_NAMES=$(echo $PACKAGES | tr -d '\n') export PARTITION_TOTAL=${CIRCLE_NODE_TOTAL} export PARTITION_ID=${CIRCLE_NODE_INDEX} - export GOEXPERIMENT="none" gotestsum --format standard-verbose --junitfile << parameters.result_path >>/<< parameters.result_subdir >>/${CIRCLE_NODE_INDEX}/results.xml --jsonfile << parameters.result_path >>/<< parameters.result_subdir >>/${CIRCLE_NODE_INDEX}/testresults.json -- --tags "sqlite_unlock_notify sqlite_omit_load_extension" << parameters.short_test_flag >> -race -timeout 1h -coverprofile=coverage.txt -covermode=atomic -p 1 $PACKAGE_NAMES - store_artifacts: path: << parameters.result_path >> diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index 46c5470734..c2d336194f 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -56,7 +56,7 @@ jobs: uses: actions/cache@v3.3.1 with: path: cicdtmp/golangci-lint/golangci-lint-cgo - key: cicd-golangci-lint-cgo-v0.0.2 + key: cicd-golangci-lint-cgo-v0.0.2-${{ env.GO_VERSION }} - name: Build custom golangci-lint with CGO_ENABLED if: steps.cache-golangci-lint.outputs.cache-hit != 'true' diff --git a/Dockerfile b/Dockerfile index 0c6c370c97..8832ae5bca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:20.04 as builder -ARG GO_VERSION="1.20.5" +ARG GO_VERSION="1.20.6" ARG CHANNEL ARG URL diff --git a/Makefile b/Makefile index 760dd42084..8301771718 100644 --- a/Makefile +++ b/Makefile @@ -69,11 +69,6 @@ ifeq ($(SHORT_PART_PERIOD), 1) export SHORT_PART_PERIOD_FLAG := -s endif -# Disable go experiments during build as of go 1.20.5 due to -# https://github.com/golang/go/issues/60825 -# Likely fix: https://go-review.googlesource.com/c/go/+/503937/6/src/runtime/race_arm64.s -export GOEXPERIMENT=none - GOTAGS := --tags "$(GOTAGSLIST)" GOTRIMPATH := $(shell GOPATH=$(GOPATH) && go help build | grep -q .-trimpath && echo -trimpath) diff --git a/scripts/get_golang_version.sh b/scripts/get_golang_version.sh index 767fb0a49e..331626f188 100755 --- a/scripts/get_golang_version.sh +++ b/scripts/get_golang_version.sh @@ -11,7 +11,7 @@ # Our build task-runner `mule` will refer to this script and will automatically # build a new image whenever the version number has been changed. -BUILD=1.20.5 +BUILD=1.20.6 MIN=1.20 GO_MOD_SUPPORT=1.20 From 4ff2bf3496da7d32dd5ef6bbe26655e68a53bbd5 Mon Sep 17 00:00:00 2001 From: AlgoAxel <113933518+AlgoAxel@users.noreply.github.com> Date: Fri, 11 Aug 2023 12:16:05 -0400 Subject: [PATCH 39/66] ledger: clear Merkle Trie on sqlite IO error (#5568) --- daemon/algod/server.go | 4 + ledger/acctonline.go | 6 +- ledger/acctupdates.go | 6 +- ledger/bulletin.go | 6 +- ledger/catchpointtracker.go | 25 +++- ledger/catchpointtracker_test.go | 90 ++++++++++++++- ledger/metrics.go | 6 +- ledger/notifier.go | 6 +- ledger/spverificationtracker.go | 6 +- ledger/store/trackerdb/interface.go | 14 +++ .../store/trackerdb/sqlitedriver/sql_test.go | 28 +++++ .../trackerdb/sqlitedriver/sqlitedriver.go | 58 +++++++--- ledger/tracker.go | 27 +++-- ledger/tracker_test.go | 109 +++++++++++++++++- ledger/txtail.go | 6 +- logging/log.go | 6 + logging/log_test.go | 20 ++++ logging/testingLogger.go | 14 +++ 18 files changed, 397 insertions(+), 40 deletions(-) diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 037e1e1e48..4af1f1c4e1 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -245,6 +245,10 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes return fmt.Errorf("couldn't initialize the node: %s", err) } s.node = serverNode + + // When a caller to logging uses Fatal, we want to stop the node before os.Exit is called. + logging.RegisterExitHandler(s.Stop) + return nil } diff --git a/ledger/acctonline.go b/ledger/acctonline.go index c73dfa010a..e2760ee0a2 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -349,7 +349,11 @@ func (ao *onlineAccounts) consecutiveVersion(offset uint64) uint64 { return offset } -func (ao *onlineAccounts) handleUnorderedCommitOrError(dcc *deferredCommitContext) { +func (ao *onlineAccounts) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (ao *onlineAccounts) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (ao *onlineAccounts) handleCommitError(dcc *deferredCommitContext) { } func (ao *onlineAccounts) maxBalLookback() uint64 { diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index cf89d770f5..d6876f13e9 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1414,7 +1414,11 @@ func (au *accountUpdates) roundOffset(rnd basics.Round) (offset uint64, err erro return off, nil } -func (au *accountUpdates) handleUnorderedCommitOrError(dcc *deferredCommitContext) { +func (au *accountUpdates) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (au *accountUpdates) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (au *accountUpdates) handleCommitError(dcc *deferredCommitContext) { } // prepareCommit prepares data to write to the database a "chunk" of rounds, and update the cached dbRound accordingly. diff --git a/ledger/bulletin.go b/ledger/bulletin.go index b05848193c..4465a52381 100644 --- a/ledger/bulletin.go +++ b/ledger/bulletin.go @@ -123,7 +123,11 @@ func (b *bulletin) postCommit(ctx context.Context, dcc *deferredCommitContext) { func (b *bulletin) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -func (b *bulletin) handleUnorderedCommitOrError(*deferredCommitContext) { +func (b *bulletin) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (b *bulletin) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (b *bulletin) handleCommitError(dcc *deferredCommitContext) { } func (b *bulletin) produceCommittingTask(committedRound basics.Round, dbRound basics.Round, dcr *deferredCommitRange) *deferredCommitRange { diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index 427494dec1..7e59406025 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -953,10 +953,27 @@ func (ct *catchpointTracker) postCommitUnlocked(ctx context.Context, dcc *deferr } } -// handleUnorderedCommitOrError is a special method for handling deferred commits that are out of order. -// Tracker might update own state in this case. For example, account catchpoint tracker cancels -// scheduled catchpoint writing that deferred commit. -func (ct *catchpointTracker) handleUnorderedCommitOrError(dcc *deferredCommitContext) { +// when the deferred commit is found to be out of order, cancel writing +func (ct *catchpointTracker) handleUnorderedCommit(dcc *deferredCommitContext) { + ct.cancelWrite(dcc) +} + +// if an error is encountered during commit preparation, cancel writing +func (ct *catchpointTracker) handlePrepareCommitError(dcc *deferredCommitContext) { + ct.cancelWrite(dcc) +} + +// if an error is encountered during commit, cancel writing and clear the balances trie +func (ct *catchpointTracker) handleCommitError(dcc *deferredCommitContext) { + // in cases where the commitRound fails, it is not certain that the merkle trie is in a clean state, and should be cleared. + // Specifically, modifications to the trie happen through accountsUpdateBalances, + // which happens before commit to disk. Errors in this tracker, subsequent trackers, or the commit to disk may cause the trie cache to be incorrect, + // affecting the perceived root on subsequent rounds + ct.balancesTrie = nil + ct.cancelWrite(dcc) +} + +func (ct *catchpointTracker) cancelWrite(dcc *deferredCommitContext) { // if the node is configured to generate catchpoint files, we might need to update the catchpointWriting variable. if ct.enableGeneratingCatchpointFiles { // determine if this was a catchpoint round diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index 6669eab4af..d5ed7621e8 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -342,6 +342,88 @@ func createCatchpoint(t *testing.T, ct *catchpointTracker, accountsRound basics. require.NoError(t, err) } +// TestCatchpointCommitErrorHandling exists to confirm that when an error occurs during catchpoint generation, +// the catchpoint tracker will clear the appropriate state - specifically, the balancesTrie will be cleared, +// and the balancesTrie will remain functional if loaded from disk, or if lazily loaded during commitRound +func TestCatchpointCommitErrorHandling(t *testing.T) { + partitiontest.PartitionTest(t) + + temporaryDirectory := t.TempDir() + + accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} + ml := makeMockLedgerForTracker(t, true, 10, protocol.ConsensusCurrentVersion, accts) + defer ml.Close() + + ct := &catchpointTracker{} + conf := config.GetDefaultLocal() + + conf.Archival = true + ct.initialize(conf, ".") + defer ct.close() + ct.dbDirectory = temporaryDirectory + + _, err := trackerDBInitialize(ml, true, ct.dbDirectory) + require.NoError(t, err) + + err = ct.loadFromDisk(ml, ml.Latest()) + require.NoError(t, err) + + txn, err := ml.dbs.BeginTransaction(context.Background()) + require.NoError(t, err) + dcc := deferredCommitContext{ + compactKvDeltas: map[string]modifiedKvValue{"key": {data: []byte("value")}}, + } + + // before commitRound is called, record the trie RootHash + require.NotNil(t, ct.balancesTrie) + root1, err := ct.balancesTrie.RootHash() + require.NoError(t, err) + + ct.commitRound(context.Background(), txn, &dcc) + + txn.Commit() + + // after commitRound is called, confirm the RootHash has changed + root2, err := ct.balancesTrie.RootHash() + require.NoError(t, err) + require.NotEqual(t, root1, root2) + + // demonstrate that handleUnordered does not restore the trie + ct.handleUnorderedCommit(&dcc) + root2a, err := ct.balancesTrie.RootHash() + require.NoError(t, err) + require.Equal(t, root2, root2a) + + // demonstrate that handlePrepareCommitError does not restore the trie + ct.handlePrepareCommitError(&dcc) + root2b, err := ct.balancesTrie.RootHash() + require.NoError(t, err) + require.Equal(t, root2, root2b) + + // now have the ct handle a commit error + ct.handleCommitError(&dcc) + // after error handling, the trie should be nil + require.Nil(t, ct.balancesTrie) + + // after reloading from disk, the trie should be equal to root1 + err = ct.loadFromDisk(ml, ml.Latest()) + require.NoError(t, err) + root3, err := ct.balancesTrie.RootHash() + require.NoError(t, err) + require.Equal(t, root1, root3) + + // also demonstrate that lazy initialization allows a nil trie to go back to root2 immediately after error if the same delta is applied + txn, err = ml.dbs.BeginTransaction(context.Background()) + require.NoError(t, err) + ct.handleCommitError(&dcc) // clear trie + require.Nil(t, ct.balancesTrie) + ct.commitRound(context.Background(), txn, &dcc) + txn.Commit() + root4, err := ct.balancesTrie.RootHash() + require.NoError(t, err) + require.Equal(t, root2, root4) +} + // TestCatchpointFileWithLargeSpVerification makes sure that CatchpointFirstStageInfo.BiggestChunkLen is calculated based on state proof verification contexts // as well as other chunks in the catchpoint files. func TestCatchpointFileWithLargeSpVerification(t *testing.T) { @@ -702,8 +784,12 @@ func (bt *blockingTracker) postCommitUnlocked(ctx context.Context, dcc *deferred } } -// handleUnorderedCommitOrError is not used by the blockingTracker -func (bt *blockingTracker) handleUnorderedCommitOrError(*deferredCommitContext) { +// control functions are not used by the blockingTracker +func (bt *blockingTracker) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (bt *blockingTracker) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (bt *blockingTracker) handleCommitError(dcc *deferredCommitContext) { } // close is not used by the blockingTracker diff --git a/ledger/metrics.go b/ledger/metrics.go index 661cb0c2dc..017960d907 100644 --- a/ledger/metrics.go +++ b/ledger/metrics.go @@ -87,7 +87,11 @@ func (mt *metricsTracker) postCommit(ctx context.Context, dcc *deferredCommitCon func (mt *metricsTracker) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -func (mt *metricsTracker) handleUnorderedCommitOrError(*deferredCommitContext) { +func (mt *metricsTracker) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (mt *metricsTracker) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (mt *metricsTracker) handleCommitError(dcc *deferredCommitContext) { } func (mt *metricsTracker) produceCommittingTask(committedRound basics.Round, dbRound basics.Round, dcr *deferredCommitRange) *deferredCommitRange { diff --git a/ledger/notifier.go b/ledger/notifier.go index d89badce7b..c9ea45e41a 100644 --- a/ledger/notifier.go +++ b/ledger/notifier.go @@ -123,7 +123,11 @@ func (bn *blockNotifier) postCommit(ctx context.Context, dcc *deferredCommitCont func (bn *blockNotifier) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -func (bn *blockNotifier) handleUnorderedCommitOrError(*deferredCommitContext) { +func (bn *blockNotifier) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (bn *blockNotifier) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (bn *blockNotifier) handleCommitError(dcc *deferredCommitContext) { } func (bn *blockNotifier) produceCommittingTask(committedRound basics.Round, dbRound basics.Round, dcr *deferredCommitRange) *deferredCommitRange { diff --git a/ledger/spverificationtracker.go b/ledger/spverificationtracker.go index e3d3c214ea..c34825a9c5 100644 --- a/ledger/spverificationtracker.go +++ b/ledger/spverificationtracker.go @@ -160,7 +160,11 @@ func (spt *spVerificationTracker) postCommit(_ context.Context, dcc *deferredCom func (spt *spVerificationTracker) postCommitUnlocked(context.Context, *deferredCommitContext) { } -func (spt *spVerificationTracker) handleUnorderedCommitOrError(*deferredCommitContext) { +func (spt *spVerificationTracker) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (spt *spVerificationTracker) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (spt *spVerificationTracker) handleCommitError(dcc *deferredCommitContext) { } func (spt *spVerificationTracker) close() { diff --git a/ledger/store/trackerdb/interface.go b/ledger/store/trackerdb/interface.go index 2ddfa2020b..fa52e9fd58 100644 --- a/ledger/store/trackerdb/interface.go +++ b/ledger/store/trackerdb/interface.go @@ -19,6 +19,7 @@ package trackerdb import ( "context" "errors" + "fmt" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -30,6 +31,19 @@ import ( // ErrNotFound is returned when a record is not found. var ErrNotFound = errors.New("trackerdb: not found") +// ErrIoErr is returned when a Disk/IO error is encountered +type ErrIoErr struct { + InnerError error +} + +func (e *ErrIoErr) Error() string { + return fmt.Sprintf("trackerdb: io error: %v", e.InnerError) +} + +func (e *ErrIoErr) Unwrap() error { + return e.InnerError +} + // AccountRef is an opaque ref to an account in the db. type AccountRef interface { AccountRefMarker() diff --git a/ledger/store/trackerdb/sqlitedriver/sql_test.go b/ledger/store/trackerdb/sqlitedriver/sql_test.go index 7e4dff97d8..65e4782b5d 100644 --- a/ledger/store/trackerdb/sqlitedriver/sql_test.go +++ b/ledger/store/trackerdb/sqlitedriver/sql_test.go @@ -19,12 +19,15 @@ package sqlitedriver import ( "context" "database/sql" + "errors" "testing" "github.com/algorand/go-algorand/data/basics" storetesting "github.com/algorand/go-algorand/ledger/store/testing" + "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/require" ) @@ -81,3 +84,28 @@ func TestAccountsDbQueriesCreateClose(t *testing.T) { qs.Close() require.Nil(t, qs.lookupAccountStmt) } + +// TestWrapIOError ensures that SQL ErrIOErr is converted to trackerdb.ErrIoErr +// github.com/mattn/go-sqlite3/blob/master/error.go +// github.com/mattn/go-sqlite3/blob/master/sqlite3.go#L830 +func TestWrapIOError(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // This structure is how sqlite3 returns Errors + err := sqlite3.Error{Code: sqlite3.ErrIoErr} + var trackerIOErr *trackerdb.ErrIoErr + require.ErrorAs(t, wrapIOError(err), &trackerIOErr) + + // ErrNo10 is a sqlite3 error code for ErrIoErr + err = sqlite3.Error{Code: sqlite3.ErrNo(10)} + require.ErrorAs(t, wrapIOError(err), &trackerIOErr) + + err = sqlite3.Error{Code: sqlite3.ErrSchema} + require.False(t, errors.As(wrapIOError(err), &trackerIOErr)) + + // confirm that double wrapping only applies once + err = sqlite3.Error{Code: sqlite3.ErrIoErr} + require.Equal(t, wrapIOError(err), wrapIOError(wrapIOError(err))) + +} diff --git a/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go b/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go index 34f4d363cc..02c290be2d 100644 --- a/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go +++ b/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go @@ -19,6 +19,7 @@ package sqlitedriver import ( "context" "database/sql" + "errors" "testing" "time" @@ -28,6 +29,7 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/db" + "github.com/mattn/go-sqlite3" ) type trackerSQLStore struct { @@ -66,60 +68,60 @@ func (s *trackerSQLStore) Batch(fn trackerdb.BatchFn) (err error) { } func (s *trackerSQLStore) BatchContext(ctx context.Context, fn trackerdb.BatchFn) (err error) { - return s.pair.Wdb.AtomicContext(ctx, func(ctx context.Context, tx *sql.Tx) error { + return wrapIOError(s.pair.Wdb.AtomicContext(ctx, func(ctx context.Context, tx *sql.Tx) error { return fn(ctx, &sqlBatchScope{tx, false, &sqlWriter{tx}}) - }) + })) } func (s *trackerSQLStore) BeginBatch(ctx context.Context) (trackerdb.Batch, error) { handle, err := s.pair.Wdb.Handle.BeginTx(ctx, nil) if err != nil { - return nil, err + return nil, wrapIOError(err) } return &sqlBatchScope{handle, false, &sqlWriter{handle}}, nil } func (s *trackerSQLStore) Snapshot(fn trackerdb.SnapshotFn) (err error) { - return s.SnapshotContext(context.Background(), fn) + return wrapIOError(s.SnapshotContext(context.Background(), fn)) } func (s *trackerSQLStore) SnapshotContext(ctx context.Context, fn trackerdb.SnapshotFn) (err error) { - return s.pair.Rdb.AtomicContext(ctx, func(ctx context.Context, tx *sql.Tx) error { + return wrapIOError(s.pair.Rdb.AtomicContext(ctx, func(ctx context.Context, tx *sql.Tx) error { return fn(ctx, &sqlSnapshotScope{tx, &sqlReader{tx}}) - }) + })) } func (s *trackerSQLStore) BeginSnapshot(ctx context.Context) (trackerdb.Snapshot, error) { handle, err := s.pair.Rdb.Handle.BeginTx(ctx, nil) if err != nil { - return nil, err + return nil, wrapIOError(err) } return &sqlSnapshotScope{handle, &sqlReader{handle}}, nil } func (s *trackerSQLStore) Transaction(fn trackerdb.TransactionFn) (err error) { - return s.TransactionContext(context.Background(), fn) + return wrapIOError(s.TransactionContext(context.Background(), fn)) } func (s *trackerSQLStore) TransactionContext(ctx context.Context, fn trackerdb.TransactionFn) (err error) { - return s.pair.Wdb.AtomicContext(ctx, func(ctx context.Context, tx *sql.Tx) error { + return wrapIOError(s.pair.Wdb.AtomicContext(ctx, func(ctx context.Context, tx *sql.Tx) error { return fn(ctx, &sqlTransactionScope{tx, false, &sqlReader{tx}, &sqlWriter{tx}, &sqlCatchpoint{tx}}) - }) + })) } func (s *trackerSQLStore) BeginTransaction(ctx context.Context) (trackerdb.Transaction, error) { handle, err := s.pair.Wdb.Handle.BeginTx(ctx, nil) if err != nil { - return nil, err + return nil, wrapIOError(err) } return &sqlTransactionScope{handle, false, &sqlReader{handle}, &sqlWriter{handle}, &sqlCatchpoint{handle}}, nil } func (s trackerSQLStore) RunMigrations(ctx context.Context, params trackerdb.Params, log logging.Logger, targetVersion int32) (mgr trackerdb.InitParams, err error) { - err = s.pair.Wdb.AtomicContext(ctx, func(ctx context.Context, tx *sql.Tx) error { + err = wrapIOError(s.pair.Wdb.AtomicContext(ctx, func(ctx context.Context, tx *sql.Tx) error { mgr, err = RunMigrations(ctx, tx, params, log, targetVersion) return err - }) + })) return } @@ -283,7 +285,7 @@ func (bs *sqlBatchScope) ResetTransactionWarnDeadline(ctx context.Context, deadl func (bs *sqlBatchScope) Close() error { if !bs.committed { - return bs.tx.Rollback() + return wrapIOError(bs.tx.Rollback()) } return nil } @@ -291,7 +293,7 @@ func (bs *sqlBatchScope) Close() error { func (bs *sqlBatchScope) Commit() error { err := bs.tx.Commit() if err != nil { - return err + return wrapIOError(err) } bs.committed = true return nil @@ -307,7 +309,7 @@ func (ss *sqlSnapshotScope) ResetTransactionWarnDeadline(ctx context.Context, de } func (ss *sqlSnapshotScope) Close() error { - return ss.tx.Rollback() + return wrapIOError(ss.tx.Rollback()) } type sqlTransactionScope struct { @@ -328,7 +330,7 @@ func (txs *sqlTransactionScope) ResetTransactionWarnDeadline(ctx context.Context func (txs *sqlTransactionScope) Close() error { if !txs.committed { - return txs.tx.Rollback() + return wrapIOError(txs.tx.Rollback()) } return nil } @@ -336,8 +338,28 @@ func (txs *sqlTransactionScope) Close() error { func (txs *sqlTransactionScope) Commit() error { err := txs.tx.Commit() if err != nil { - return err + return wrapIOError(err) } txs.committed = true return nil } + +// wrapIOError allows for SQL IO Errors to be represented as trackerdb.ErrIoErr +// in places which may enconter them. +func wrapIOError(err error) error { + if err == nil { + return nil + } + // if it's already a trackerdb error, don't wrap it again + var alreadyWrapped *trackerdb.ErrIoErr + if errors.As(err, &alreadyWrapped) { + return err + } + var sqliteErr sqlite3.Error + if errors.As(err, &sqliteErr) { + if sqliteErr.Code == sqlite3.ErrIoErr { + return &trackerdb.ErrIoErr{InnerError: err} + } + } + return err +} diff --git a/ledger/tracker.go b/ledger/tracker.go index 5975a91409..c5f3e84781 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -119,11 +119,16 @@ type ledgerTracker interface { // An optional context is provided for long-running operations. postCommitUnlocked(context.Context, *deferredCommitContext) - // handleUnorderedCommitOrError is a special method for handling deferred commits that are out of order - // or to handle errors reported by other trackers while committing a batch. - // Tracker might update own state in this case. For example, account updates tracker cancels + // handleUnorderedCommit is a control method for handling deferred commits that are out of order + // Tracker might update its own state in this case. For example, account updates tracker cancels // scheduled catchpoint writing flag for this batch. - handleUnorderedCommitOrError(*deferredCommitContext) + handleUnorderedCommit(*deferredCommitContext) + // handlePrepareCommitError is a control method for handling self-cleanup or update if any trackers report + // error during the prepare commit phase of commitRound + handlePrepareCommitError(*deferredCommitContext) + // handleCommitError is a control method for handling self-cleanup or update if any trackers report + // error during the commit phase of commitRound + handleCommitError(*deferredCommitContext) // close terminates the tracker, reclaiming any resources // like open database connections or goroutines. close may @@ -512,7 +517,7 @@ func (tr *trackerRegistry) commitRound(dcc *deferredCommitContext) error { if tr.dbRound < dbRound || offset < uint64(tr.dbRound-dbRound) { tr.log.Warnf("out of order deferred commit: offset %d, dbRound %d but current tracker DB round is %d", offset, dbRound, tr.dbRound) for _, lt := range tr.trackers { - lt.handleUnorderedCommitOrError(dcc) + lt.handleUnorderedCommit(dcc) } tr.mu.RUnlock() return nil @@ -546,7 +551,7 @@ func (tr *trackerRegistry) commitRound(dcc *deferredCommitContext) error { } if err != nil { for _, lt := range tr.trackers { - lt.handleUnorderedCommitOrError(dcc) + lt.handlePrepareCommitError(dcc) } tr.mu.RUnlock() return err @@ -574,10 +579,18 @@ func (tr *trackerRegistry) commitRound(dcc *deferredCommitContext) error { ledgerCommitroundMicros.AddMicrosecondsSince(start, nil) if err != nil { + for _, lt := range tr.trackers { - lt.handleUnorderedCommitOrError(dcc) + lt.handleCommitError(dcc) } tr.log.Warnf("unable to advance tracker db snapshot (%d-%d): %v", dbRound, dbRound+basics.Round(offset), err) + + // if the error is an IO error, shut down the node. + var trackerIOErr *trackerdb.ErrIoErr + if errors.As(err, &trackerIOErr) { + tr.log.Fatalf("Fatal IO error during CommitRound, exiting: %v", err) + } + return err } diff --git a/ledger/tracker_test.go b/ledger/tracker_test.go index 5ab2f63c99..87646f24a6 100644 --- a/ledger/tracker_test.go +++ b/ledger/tracker_test.go @@ -23,6 +23,7 @@ import ( "testing" "time" + "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/agreement" @@ -142,6 +143,59 @@ func TestTrackerScheduleCommit(t *testing.T) { a.Equal(expectedOffset, dc.offset) } +type ioErrorTracker struct { +} + +// loadFromDisk is not implemented in the blockingTracker. +func (io *ioErrorTracker) loadFromDisk(ledgerForTracker, basics.Round) error { + return nil +} + +// newBlock is not implemented in the blockingTracker. +func (io *ioErrorTracker) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) { +} + +// committedUpTo in the blockingTracker just stores the committed round. +func (io *ioErrorTracker) committedUpTo(committedRnd basics.Round) (minRound, lookback basics.Round) { + return 0, basics.Round(0) +} + +func (io *ioErrorTracker) produceCommittingTask(committedRound basics.Round, dbRound basics.Round, dcr *deferredCommitRange) *deferredCommitRange { + return dcr +} + +// prepareCommit, is not used by the blockingTracker +func (io *ioErrorTracker) prepareCommit(*deferredCommitContext) error { + return nil +} + +// commitRound is not used by the blockingTracker +func (io *ioErrorTracker) commitRound(context.Context, trackerdb.TransactionScope, *deferredCommitContext) error { + return sqlite3.Error{Code: sqlite3.ErrIoErr} +} + +func (io *ioErrorTracker) postCommit(ctx context.Context, dcc *deferredCommitContext) { +} + +// postCommitUnlocked implements entry/exit blockers, designed for testing. +func (io *ioErrorTracker) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { +} + +// control functions are not used by the blockingTracker +func (io *ioErrorTracker) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (io *ioErrorTracker) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (io *ioErrorTracker) handleCommitError(dcc *deferredCommitContext) { +} + +// close is not used by the blockingTracker +func (io *ioErrorTracker) close() { +} + +func (io *ioErrorTracker) reset() { +} + type producePrepareBlockingTracker struct { produceReleaseLock chan struct{} prepareCommitEntryLock chan struct{} @@ -191,8 +245,12 @@ func (bt *producePrepareBlockingTracker) postCommit(ctx context.Context, dcc *de func (bt *producePrepareBlockingTracker) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -// handleUnorderedCommitOrError is not used by the blockingTracker -func (bt *producePrepareBlockingTracker) handleUnorderedCommitOrError(*deferredCommitContext) { +// control functions are not used by the blockingTracker +func (bt *producePrepareBlockingTracker) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (bt *producePrepareBlockingTracker) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (bt *producePrepareBlockingTracker) handleCommitError(dcc *deferredCommitContext) { } // close is not used by the blockingTracker @@ -302,6 +360,53 @@ func TestTrackerDbRoundDataRace(t *testing.T) { close(stallingTracker.produceReleaseLock) } +func TestCommitRoundIOError(t *testing.T) { + partitiontest.PartitionTest(t) + + a := require.New(t) + + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 1) + const inMem = true + log := logging.TestingLogWithoutFatalExit(t) + log.SetLevel(logging.Warn) + cfg := config.GetDefaultLocal() + ledger, err := OpenLedger(log, t.Name(), inMem, genesisInitState, cfg) + a.NoError(err, "could not open ledger") + defer ledger.Close() + + // flip the flag when the exit handler is called, + // which happens when Fatal logging is called + flag := false + logging.RegisterExitHandler(func() { + flag = true + }) + + io := &ioErrorTracker{} + ledger.trackerMu.Lock() + ledger.trackers.mu.Lock() + ledger.trackers.trackers = append([]ledgerTracker{io}, ledger.trackers.trackers...) + ledger.trackers.mu.Unlock() + ledger.trackerMu.Unlock() + + // create update content which would trigger a commit + targetRound := basics.Round(100) + blk := genesisInitState.Block + for i := basics.Round(0); i < targetRound-1; i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += int64(crypto.RandUint64() % 100 * 1000) + err := ledger.AddBlock(blk, agreement.Certificate{}) + a.NoError(err) + } + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += int64(crypto.RandUint64() % 100 * 1000) + err = ledger.AddBlock(blk, agreement.Certificate{}) + a.NoError(err) + + // confirm that after 100 blocks, the scheduled commit generated an error + // which triggered Fatal logging (and would therefore call any registered exit handlers) + a.True(flag) +} + func TestAccountUpdatesLedgerEvaluatorNoBlockHdr(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/txtail.go b/ledger/txtail.go index 31e44be77a..6f05b47f98 100644 --- a/ledger/txtail.go +++ b/ledger/txtail.go @@ -325,7 +325,11 @@ func (t *txTail) postCommit(ctx context.Context, dcc *deferredCommitContext) { func (t *txTail) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -func (t *txTail) handleUnorderedCommitOrError(*deferredCommitContext) { +func (t *txTail) handleUnorderedCommit(dcc *deferredCommitContext) { +} +func (t *txTail) handlePrepareCommitError(dcc *deferredCommitContext) { +} +func (t *txTail) handleCommitError(dcc *deferredCommitContext) { } func (t *txTail) produceCommittingTask(committedRound basics.Round, dbRound basics.Round, dcr *deferredCommitRange) *deferredCommitRange { diff --git a/logging/log.go b/logging/log.go index ebe600f7f7..d9b79eb9aa 100644 --- a/logging/log.go +++ b/logging/log.go @@ -376,6 +376,12 @@ func NewWrappedLogger(l *logrus.Logger) Logger { return out } +// RegisterExitHandler registers a function to be called on exit by logrus +// Exit handling happens when logrus.Exit is called, which is called by logrus.Fatal +func RegisterExitHandler(handler func()) { + logrus.RegisterExitHandler(handler) +} + func (l logger) EnableTelemetry(cfg TelemetryConfig) (err error) { if l.loggerState.telemetry != nil || (!cfg.Enable && !cfg.SendToLog) { return nil diff --git a/logging/log_test.go b/logging/log_test.go index af62bae912..bf6db060c7 100644 --- a/logging/log_test.go +++ b/logging/log_test.go @@ -119,3 +119,23 @@ func TestSetJSONFormatter(t *testing.T) { a.True(isJSON(bufNewLogger.String())) } + +// This test ensures that handler functions registered to Fatal Logging trigger +// when Fatal logs are emitted. We attach graceful service shutdown to Fatal logging, +// and we want to notice if changes to our logging dependencies change how these handlers are called +func TestFatalExitHandler(t *testing.T) { + partitiontest.PartitionTest(t) + + nl := TestingLogWithoutFatalExit(t) + + // Make an exit handler that sets a flag to demonstrate it was called + flag := false + RegisterExitHandler(func() { + flag = true + }) + nl.Fatal("OH NO") + + // Check that the exit handler was called + require.True(t, flag) + +} diff --git a/logging/testingLogger.go b/logging/testingLogger.go index 6492c7ddd8..0f53e6225b 100644 --- a/logging/testingLogger.go +++ b/logging/testingLogger.go @@ -18,6 +18,8 @@ package logging import ( "testing" + + "github.com/sirupsen/logrus" ) // TestLogWriter is an io.Writer that wraps a testing.T (or a testing.B) -- anything written to it gets logged with t.Log(...) @@ -39,6 +41,18 @@ func (tb TestLogWriter) Write(p []byte) (n int, err error) { return len(p), nil } +// TestingLogWithoutFatalExit is a test-only convenience function to configure logging for testing in situations where Fatal() may be called +// (e.g. in the case of an expected failure) +// Calls to Fatal() will still call any registered exit handlers +func TestingLogWithoutFatalExit(tb testing.TB) Logger { + l := logrus.New() + l.ExitFunc = func(code int) {} + wl := NewWrappedLogger(l) + wl.SetLevel(Debug) + wl.SetOutput(TestLogWriter{tb}) + return wl +} + // TestingLog is a test-only convenience function to configure logging for testing func TestingLog(tb testing.TB) Logger { l := NewLogger() From 24cc4d75ca8194af1b12c54aa1c3d3b374fd9e0d Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Mon, 14 Aug 2023 15:11:26 -0400 Subject: [PATCH 40/66] Build: Remove n-algorand from wsnetwork partial overlap test. (#5660) --- network/wsNetwork_test.go | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 48ad81727d..348f48beed 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -4410,30 +4410,6 @@ type MergeTestDNSInputs struct { func mergePrimarySecondaryRelayAddressListsPartialOverlapTestInputsGen() *rapid.Generator[*MergeTestDNSInputs] { - algorand0Base := rapid.Custom(func(t *rapid.T) *MergeTestDNSInputs { - //unused/satisfying rapid expectation - rapid.String().Draw(t, "algorand0Base") - // .algorand.network?backup=.algorand0.network& - // dedup=.(algorand-|n-.algorand0).network - return &MergeTestDNSInputs{ - dedupExpStr: "((algorand-|n-.algorand0).network)", - primaryDomainSuffix: "algorand-.network", - secondaryDomainSuffix: "n-.algorand0.network", - } - }) - - algorand0Inverse := rapid.Custom(func(t *rapid.T) *MergeTestDNSInputs { - //unused/satisfying rapid expectation - rapid.String().Draw(t, "algorand0Inverse") - // .algorand0.network?backup=.algorand.network& - // dedup=.(algorand-|n-.algorand0).network - return &MergeTestDNSInputs{ - dedupExpStr: "((algorand-|n-.algorand0).network)", - primaryDomainSuffix: "n-.algorand0.network", - secondaryDomainSuffix: "algorand-.network", - } - }) - algorandNetBase := rapid.Custom(func(t *rapid.T) *MergeTestDNSInputs { //unused/satisfying rapid expectation rapid.String().Draw(t, "algorandNetBase") @@ -4458,7 +4434,7 @@ func mergePrimarySecondaryRelayAddressListsPartialOverlapTestInputsGen() *rapid. } }) - return rapid.OneOf(algorand0Base, algorand0Inverse, algorandNetBase, algorandNetInverse) + return rapid.OneOf(algorandNetBase, algorandNetInverse) } func TestMergePrimarySecondaryRelayAddressListsPartialOverlap(t *testing.T) { @@ -4516,7 +4492,7 @@ func TestMergePrimarySecondaryRelayAddressListsNoDedupExp(t *testing.T) { network := supportedNetworkGen().Draw(t1, "network") primaryDomainSuffix := strings.Replace( - `n-.algorand0.network`, "", network, -1) + `algorand-.net`, "", network, -1) // Generate hosts for a primary network domain primaryNetworkDomainGen := rapidgen.DomainWithSuffixAndPort(primaryDomainSuffix, nil) From eae761046789353094c271441b0879522cb1eaf1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 10:09:08 -0400 Subject: [PATCH 41/66] build(deps): bump github.com/libp2p/go-libp2p from 0.29.0 to 0.29.1 (#5647) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .circleci/config.yml | 2 +- Dockerfile | 2 +- go.mod | 5 ++--- go.sum | 10 ++++------ scripts/get_golang_version.sh | 2 +- tools/block-generator/go.sum | 3 +-- tools/x-repo-types/go.sum | 4 ++-- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 47af20f6b4..b9ac9f3faf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -457,7 +457,7 @@ commands: shell: bash.exe command: | choco install -y msys2 pacman make wget --force - choco install -y golang --version=1.20.6 --force + choco install -y golang --version=$(./scripts/get_golang_version.sh) --force choco install -y python3 --version=3.7.3 --force export msys2='cmd //C RefreshEnv.cmd ' export msys2+='& set MSYS=winsymlinks:nativestrict ' diff --git a/Dockerfile b/Dockerfile index 8832ae5bca..468d257360 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:20.04 as builder -ARG GO_VERSION="1.20.6" +ARG GO_VERSION="1.20.7" ARG CHANNEL ARG URL diff --git a/go.mod b/go.mod index 6c88c25d5c..dc78fd6921 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/jmoiron/sqlx v1.2.0 github.com/karalabe/usb v0.0.2 github.com/labstack/echo/v4 v4.9.1 - github.com/libp2p/go-libp2p v0.29.0 + github.com/libp2p/go-libp2p v0.29.1 github.com/mattn/go-sqlite3 v1.10.0 github.com/miekg/dns v1.1.55 github.com/multiformats/go-multiaddr v0.10.1 @@ -96,7 +96,6 @@ require ( github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -119,7 +118,7 @@ require ( golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect golang.org/x/tools v0.11.0 // indirect google.golang.org/protobuf v1.30.0 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect diff --git a/go.sum b/go.sum index 935d69f151..7b9ce20799 100644 --- a/go.sum +++ b/go.sum @@ -453,8 +453,8 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-libp2p v0.29.0 h1:QduJ2XQr/Crg4EnloueWDL0Jj86N3Ezhyyj7XH+XwHI= -github.com/libp2p/go-libp2p v0.29.0/go.mod h1:iNKL7mEnZ9wAss+03IjAwM9ZAQXfVUAPUUmOACQfQ/g= +github.com/libp2p/go-libp2p v0.29.1 h1:yNeg6XgP8gbdc4YSrwiIt5T1TGOrVjH8dzl8h0GIOfQ= +github.com/libp2p/go-libp2p v0.29.1/go.mod h1:20El+LLy3/YhdUYIvGbLnvVJN32nMdqY6KXBENRAfLY= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -548,8 +548,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olivere/elastic v6.2.14+incompatible h1:k+KadwNP/dkXE0/eu+T6otk1+5fe0tEpPyQJ4XVm5i8= github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= @@ -1170,8 +1168,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= diff --git a/scripts/get_golang_version.sh b/scripts/get_golang_version.sh index 331626f188..10bdb8630d 100755 --- a/scripts/get_golang_version.sh +++ b/scripts/get_golang_version.sh @@ -11,7 +11,7 @@ # Our build task-runner `mule` will refer to this script and will automatically # build a new image whenever the version number has been changed. -BUILD=1.20.6 +BUILD=1.20.7 MIN=1.20 GO_MOD_SUPPORT=1.20 diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index 7b3b28e77e..058f75fb93 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -349,7 +349,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olivere/elastic v6.2.14+incompatible h1:k+KadwNP/dkXE0/eu+T6otk1+5fe0tEpPyQJ4XVm5i8= github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= @@ -767,7 +766,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= diff --git a/tools/x-repo-types/go.sum b/tools/x-repo-types/go.sum index 7929646e02..61d48cf887 100644 --- a/tools/x-repo-types/go.sum +++ b/tools/x-repo-types/go.sum @@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -15,6 +15,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 1d1f8ccc8a65496448b0d7b7dbc6b52c3e8ba4f6 Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Tue, 15 Aug 2023 10:39:30 -0400 Subject: [PATCH 42/66] API: Disable API authentication (#5625) --- cmd/algocfg/profileCommand.go | 1 + cmd/algocfg/profileCommand_test.go | 6 + cmd/algod/main.go | 23 ++-- config/localTemplate.go | 5 +- config/local_defaults.go | 3 +- daemon/algod/server.go | 13 +- installer/config.json.example | 3 +- test/e2e-go/restAPI/restClient_test.go | 44 ++++++ test/scripts/e2e_go_tests.sh | 5 +- test/testdata/configs/config-v30.json | 125 ++++++++++++++++++ .../testdata/nettemplates/DisableAPIAuth.json | 25 ++++ 11 files changed, 236 insertions(+), 17 deletions(-) create mode 100644 test/testdata/configs/config-v30.json create mode 100644 test/testdata/nettemplates/DisableAPIAuth.json diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index 6519786e78..e629f2578a 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -44,6 +44,7 @@ var ( cfg.EnableDeveloperAPI = true cfg.MaxAcctLookback = 256 cfg.EnableTxnEvalTracer = true + cfg.DisableAPIAuth = true return cfg }, } diff --git a/cmd/algocfg/profileCommand_test.go b/cmd/algocfg/profileCommand_test.go index a99b65f0b0..dd905fd857 100644 --- a/cmd/algocfg/profileCommand_test.go +++ b/cmd/algocfg/profileCommand_test.go @@ -45,4 +45,10 @@ func Test_getConfigForArg(t *testing.T) { require.True(t, cfg.EnableFollowMode) }) + t.Run("valid config test development", func(t *testing.T) { + t.Parallel() + cfg, err := getConfigForArg("development") + require.NoError(t, err) + require.True(t, cfg.DisableAPIAuth) + }) } diff --git a/cmd/algod/main.go b/cmd/algod/main.go index 47bf4fcaea..c9cdc1fe6b 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -19,7 +19,6 @@ package main import ( "flag" "fmt" - "github.com/algorand/go-algorand/util" "math/rand" "os" "path/filepath" @@ -27,7 +26,6 @@ import ( "strings" "time" - "github.com/algorand/go-deadlock" "github.com/gofrs/flock" "github.com/algorand/go-algorand/config" @@ -39,8 +37,11 @@ import ( "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/protocol" toolsnet "github.com/algorand/go-algorand/tools/network" + "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/metrics" "github.com/algorand/go-algorand/util/tokens" + + "github.com/algorand/go-deadlock" ) var dataDirectory = flag.String("d", "", "Root Algorand daemon data path") @@ -234,15 +235,19 @@ func run() int { Genesis: genesis, } - // Generate a REST API token if one was not provided - apiToken, wroteNewToken, err := tokens.ValidateOrGenerateAPIToken(s.RootPath, tokens.AlgodTokenFilename) + if !cfg.DisableAPIAuth { + // Generate a REST API token if one was not provided + apiToken, wroteNewToken, err2 := tokens.ValidateOrGenerateAPIToken(s.RootPath, tokens.AlgodTokenFilename) - if err != nil { - log.Fatalf("API token error: %v", err) - } + if err2 != nil { + log.Fatalf("API token error: %v", err2) + } - if wroteNewToken { - fmt.Printf("No REST API Token found. Generated token: %s\n", apiToken) + if wroteNewToken { + fmt.Printf("No REST API Token found. Generated token: %s\n", apiToken) + } + } else { + fmt.Printf("Public (non-admin) API authentication disabled. %s not generated\n", tokens.AlgodTokenFilename) } // Generate a admin REST API token if one was not provided diff --git a/config/localTemplate.go b/config/localTemplate.go index f13fc97f1f..aa25417e30 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -41,7 +41,7 @@ type Local struct { // Version tracks the current version of the defaults so we can migrate old -> new // This is specifically important whenever we decide to change the default value // for an existing parameter. This field tag must be updated any time we add a new version. - Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29"` + Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30"` // environmental (may be overridden) // When enabled, stores blocks indefinitely, otherwise, only the most recent blocks @@ -532,6 +532,9 @@ type Local struct { // The private key provided must be an ed25519 private key. // This is only used when P2PEnable is true. If the parameter is not set, it uses the default location. P2PPrivateKeyLocation string `version[29]:""` + + // DisableAPIAuth turns off authentication for public (non-admin) API endpoints. + DisableAPIAuth bool `version[30]:"false"` } // DNSBootstrapArray returns an array of one or more DNS Bootstrap identifiers diff --git a/config/local_defaults.go b/config/local_defaults.go index a3c55c1e1b..1b867ff8e6 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -20,7 +20,7 @@ package config var defaultLocal = Local{ - Version: 29, + Version: 30, AccountUpdatesStatsInterval: 5000000000, AccountsRebuildSynchronousMode: 1, AgreementIncomingBundlesQueueLength: 15, @@ -50,6 +50,7 @@ var defaultLocal = Local{ DNSSecurityFlags: 1, DeadlockDetection: 0, DeadlockDetectionThreshold: 30, + DisableAPIAuth: false, DisableLedgerLRUCache: false, DisableLocalhostConnectionRateLimit: true, DisableNetworking: false, diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 4af1f1c4e1..80d0afeed3 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -290,10 +290,15 @@ func (s *Server) Start() { s.metricServiceStarted = true } - apiToken, err := tokens.GetAndValidateAPIToken(s.RootPath, tokens.AlgodTokenFilename) - if err != nil { - fmt.Printf("APIToken error: %v\n", err) - os.Exit(1) + var apiToken string + var err error + fmt.Printf("API authentication disabled: %v\n", cfg.DisableAPIAuth) + if !cfg.DisableAPIAuth { + apiToken, err = tokens.GetAndValidateAPIToken(s.RootPath, tokens.AlgodTokenFilename) + if err != nil { + fmt.Printf("APIToken error: %v\n", err) + os.Exit(1) + } } adminAPIToken, err := tokens.GetAndValidateAPIToken(s.RootPath, tokens.AlgodAdminTokenFilename) diff --git a/installer/config.json.example b/installer/config.json.example index 8522011ce6..6df0464f17 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -1,5 +1,5 @@ { - "Version": 29, + "Version": 30, "AccountUpdatesStatsInterval": 5000000000, "AccountsRebuildSynchronousMode": 1, "AgreementIncomingBundlesQueueLength": 15, @@ -29,6 +29,7 @@ "DNSSecurityFlags": 1, "DeadlockDetection": 0, "DeadlockDetectionThreshold": 30, + "DisableAPIAuth": false, "DisableLedgerLRUCache": false, "DisableLocalhostConnectionRateLimit": true, "DisableNetworking": false, diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index fc21250269..bc8218120f 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -27,12 +27,14 @@ import ( "math/rand" "net/http" "os" + "path" "path/filepath" "sort" "strings" "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/config" @@ -51,6 +53,7 @@ import ( "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/db" + "github.com/algorand/go-algorand/util/tokens" ) var fixture fixtures.RestClientFixture @@ -3459,3 +3462,44 @@ int 1 } a.Equal(expectedResult, resp) } + +func TestDisabledAPIConfig(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + a := require.New(fixtures.SynchronizedTest(t)) + var localFixture fixtures.RestClientFixture + localFixture.Setup(t, filepath.Join("nettemplates", "DisableAPIAuth.json")) + defer localFixture.Shutdown() + + testClient := localFixture.LibGoalClient + + statusResponse, err := testClient.Status() + a.NoError(err) + a.NotEmpty(statusResponse) + statusResponse2, err := testClient.Status() + a.NoError(err) + a.NotEmpty(statusResponse2) + a.True(statusResponse2.LastRound >= statusResponse.LastRound) + + // Check the public token isn't created when the API authentication is disabled + nc, err := localFixture.GetNodeController("Primary") + _, err = os.Stat(path.Join(nc.GetDataDir(), tokens.AlgodAdminTokenFilename)) + assert.NoError(t, err) + _, err = os.Stat(path.Join(nc.GetDataDir(), tokens.AlgodTokenFilename)) + assert.True(t, os.IsNotExist(err)) + + // check public api works without a token + testClient.WaitForRound(1) + _, err = testClient.Block(1) + assert.NoError(t, err) + // check admin api works with the generated token + _, err = testClient.GetParticipationKeys() + assert.NoError(t, err) + // check admin api doesn't work with an invalid token + algodURL, err := nc.ServerURL() + assert.NoError(t, err) + client := client.MakeRestClient(algodURL, "") + _, err = client.GetParticipationKeys() + assert.Contains(t, err.Error(), "Invalid API Token") +} diff --git a/test/scripts/e2e_go_tests.sh b/test/scripts/e2e_go_tests.sh index b1ce3a3559..e565eabf76 100755 --- a/test/scripts/e2e_go_tests.sh +++ b/test/scripts/e2e_go_tests.sh @@ -21,7 +21,6 @@ echo "GOTESTCOMMAND will be: ${GOTESTCOMMAND}" TESTPATTERNS=() NORACEBUILD="" -export RUN_EXPECT="FALSE" while [ "$1" != "" ]; do case "$1" in -e) @@ -48,6 +47,10 @@ if [[ -n $TESTPATTERNS && -n $RUN_EXPECT ]]; then exit 1 fi +if [ -n $RUN_EXPECT ]; then + RUN_EXPECT="FALSE" +fi + # Anchor our repo root reference location REPO_ROOT="$( cd "$(dirname "$0")" ; pwd -P )"/../.. diff --git a/test/testdata/configs/config-v30.json b/test/testdata/configs/config-v30.json new file mode 100644 index 0000000000..6df0464f17 --- /dev/null +++ b/test/testdata/configs/config-v30.json @@ -0,0 +1,125 @@ +{ + "Version": 30, + "AccountUpdatesStatsInterval": 5000000000, + "AccountsRebuildSynchronousMode": 1, + "AgreementIncomingBundlesQueueLength": 15, + "AgreementIncomingProposalsQueueLength": 50, + "AgreementIncomingVotesQueueLength": 20000, + "AnnounceParticipationKey": true, + "Archival": false, + "BaseLoggerDebugLevel": 4, + "BlockServiceCustomFallbackEndpoints": "", + "BlockServiceMemCap": 500000000, + "BroadcastConnectionsLimit": -1, + "CadaverDirectory": "", + "CadaverSizeTarget": 0, + "CatchpointFileHistoryLength": 365, + "CatchpointInterval": 10000, + "CatchpointTracking": 0, + "CatchupBlockDownloadRetryAttempts": 1000, + "CatchupBlockValidateMode": 0, + "CatchupFailurePeerRefreshRate": 10, + "CatchupGossipBlockFetchTimeoutSec": 4, + "CatchupHTTPBlockFetchTimeoutSec": 4, + "CatchupLedgerDownloadRetryAttempts": 50, + "CatchupParallelBlocks": 16, + "ConnectionsRateLimitingCount": 60, + "ConnectionsRateLimitingWindowSeconds": 1, + "DNSBootstrapID": ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)", + "DNSSecurityFlags": 1, + "DeadlockDetection": 0, + "DeadlockDetectionThreshold": 30, + "DisableAPIAuth": false, + "DisableLedgerLRUCache": false, + "DisableLocalhostConnectionRateLimit": true, + "DisableNetworking": false, + "DisableOutgoingConnectionThrottling": false, + "EnableAccountUpdatesStats": false, + "EnableAgreementReporting": false, + "EnableAgreementTimeMetrics": false, + "EnableAssembleStats": false, + "EnableBlockService": false, + "EnableBlockServiceFallbackToArchiver": true, + "EnableCatchupFromArchiveServers": false, + "EnableDeveloperAPI": false, + "EnableExperimentalAPI": false, + "EnableFollowMode": false, + "EnableGossipBlockService": true, + "EnableIncomingMessageFilter": false, + "EnableLedgerService": false, + "EnableMetricReporting": false, + "EnableOutgoingNetworkMessageFiltering": true, + "EnablePingHandler": true, + "EnableProcessBlockStats": false, + "EnableProfiler": false, + "EnableRequestLogger": false, + "EnableRuntimeMetrics": false, + "EnableTopAccountsReporting": false, + "EnableTxBacklogRateLimiting": false, + "EnableTxnEvalTracer": false, + "EnableUsageLog": false, + "EnableVerbosedTransactionSyncLogging": false, + "EndpointAddress": "127.0.0.1:0", + "FallbackDNSResolverAddress": "", + "ForceFetchTransactions": false, + "ForceRelayMessages": false, + "GossipFanout": 4, + "HeartbeatUpdateInterval": 600, + "IncomingConnectionsLimit": 2400, + "IncomingMessageFilterBucketCount": 5, + "IncomingMessageFilterBucketSize": 512, + "LedgerSynchronousMode": 2, + "LogArchiveMaxAge": "", + "LogArchiveName": "node.archive.log", + "LogSizeLimit": 1073741824, + "MaxAPIBoxPerApplication": 100000, + "MaxAPIResourcesPerAccount": 100000, + "MaxAcctLookback": 4, + "MaxCatchpointDownloadDuration": 43200000000000, + "MaxConnectionsPerIP": 15, + "MinCatchpointFileDownloadBytesPerSecond": 20480, + "NetAddress": "", + "NetworkMessageTraceServer": "", + "NetworkProtocolVersion": "", + "NodeExporterListenAddress": ":9100", + "NodeExporterPath": "./node_exporter", + "OptimizeAccountsDatabaseOnStartup": false, + "OutgoingMessageFilterBucketCount": 3, + "OutgoingMessageFilterBucketSize": 128, + "P2PEnable": false, + "P2PPersistPeerID": true, + "P2PPrivateKeyLocation": "", + "ParticipationKeysRefreshInterval": 60000000000, + "PeerConnectionsUpdateInterval": 3600, + "PeerPingPeriodSeconds": 0, + "PriorityPeers": {}, + "ProposalAssemblyTime": 500000000, + "PublicAddress": "", + "ReconnectTime": 60000000000, + "ReservedFDs": 256, + "RestConnectionsHardLimit": 2048, + "RestConnectionsSoftLimit": 1024, + "RestReadTimeoutSeconds": 15, + "RestWriteTimeoutSeconds": 120, + "RunHosted": false, + "StorageEngine": "sqlite", + "SuggestedFeeBlockHistory": 3, + "SuggestedFeeSlidingWindowSize": 50, + "TLSCertFile": "", + "TLSKeyFile": "", + "TelemetryToLog": true, + "TransactionSyncDataExchangeRate": 0, + "TransactionSyncSignificantMessageThreshold": 0, + "TxBacklogReservedCapacityPerPeer": 20, + "TxBacklogServiceRateWindowSeconds": 10, + "TxBacklogSize": 26000, + "TxIncomingFilterMaxSize": 500000, + "TxIncomingFilteringFlags": 1, + "TxPoolExponentialIncreaseFactor": 2, + "TxPoolSize": 75000, + "TxSyncIntervalSeconds": 60, + "TxSyncServeResponseSize": 1000000, + "TxSyncTimeoutSeconds": 30, + "UseXForwardedForAddressField": "", + "VerifiedTranscationsCacheSize": 150000 +} diff --git a/test/testdata/nettemplates/DisableAPIAuth.json b/test/testdata/nettemplates/DisableAPIAuth.json new file mode 100644 index 0000000000..4d1b859b41 --- /dev/null +++ b/test/testdata/nettemplates/DisableAPIAuth.json @@ -0,0 +1,25 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "ConsensusProtocol": "future", + "LastPartKeyRound": 3000, + "Wallets": [ + { + "Name": "Wallet1", + "Stake": 100, + "Online": true + } + ] + }, + "Nodes": [ + { + "Name": "Primary", + "IsRelay": true, + "ConfigJSONOverride": "{\"DisableAPIAuth\":true}", + "Wallets": [ + { "Name": "Wallet1", + "ParticipationOnly": false } + ] + } + ] +} From 2aac40284da69a0cfec805bc7008d163daa9a80d Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 15 Aug 2023 12:03:38 -0400 Subject: [PATCH 43/66] tools: block-generator locked table retry and additional metrics (#5653) --- tools/block-generator/generator/generate.go | 64 +++------ .../generator/generate_apps.go | 42 +++++- .../generator/generate_test.go | 18 +-- .../generator/generator_ledger.go | 25 +++- .../generator/generator_types.go | 5 +- .../block-generator/runner/reporting_test.go | 94 +++++++++++++ tools/block-generator/runner/run.go | 125 +++++++++++------- 7 files changed, 263 insertions(+), 110 deletions(-) create mode 100644 tools/block-generator/runner/reporting_test.go diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index 22246e047b..e456856c6f 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -17,7 +17,6 @@ package generator import ( - _ "embed" "encoding/json" "errors" "fmt" @@ -32,49 +31,19 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" txn "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/rpcs" - "github.com/algorand/go-algorand/tools/block-generator/util" ) -// ---- templates ---- - -//go:embed teal/poap_boxes.teal -var approvalBoxes string -var approvalBoxesBytes interface{} - -//go:embed teal/poap_clear.teal -var clearBoxes string -var clearBoxesBytes interface{} - -//go:embed teal/swap_amm.teal -var approvalSwap string -var approvalSwapBytes interface{} - -//go:embed teal/swap_clear.teal -var clearSwap string -var clearSwapBytes interface{} - -func init() { - prog, err := logic.AssembleString(approvalBoxes) - util.MaybeFail(err, "failed to assemble approval program") - approvalBoxesBytes = prog.Program - - prog, err = logic.AssembleString(clearBoxes) - util.MaybeFail(err, "failed to assemble clear program") - clearBoxesBytes = prog.Program - - prog, err = logic.AssembleString(approvalSwap) - util.MaybeFail(err, "failed to assemble approvalSwap program") - approvalSwapBytes = prog.Program - - prog, err = logic.AssembleString(clearSwap) - util.MaybeFail(err, "failed to assemble clearSwap program") - clearSwapBytes = prog.Program -} +const ( + BlockTotalSizeBytes = "blocks_total_size_bytes" + CommitWaitTimeMS = "commit_wait_time_ms" + BlockgenGenerateTimeMS = "blockgen_generate_time_ms" + LedgerEvalTimeMS = "ledger_eval_time_ms" + LedgerValidateTimeMS = "ledger_validate_time_ms" +) // ---- constructors ---- @@ -105,10 +74,11 @@ func MakeGenerator(log logging.Logger, dbround uint64, bkGenesis bookkeeping.Gen rewardsResidue: 0, rewardsRate: 0, rewardsRecalculationRound: 0, - reportData: make(map[TxTypeID]TxData), latestData: make(map[TxTypeID]uint64), roundOffset: dbround, } + gen.reportData.Transactions = make(map[TxTypeID]TxData) + gen.reportData.Counters = make(map[string]uint64) gen.feeSink[31] = 1 gen.rewardsPool[31] = 2 @@ -357,7 +327,7 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { g.setBlockHeader(&cert) intra := uint64(0) - txGroupsAD := [][]txn.SignedTxnWithAD{} + var txGroupsAD [][]txn.SignedTxnWithAD for intra < minTxnsForBlock { txGroupAD, numTxns, err := g.generateTxGroup(g.round, intra) if err != nil { @@ -371,8 +341,9 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { intra += numTxns } generated = time.Now() + g.reportData.Counters[BlockgenGenerateTimeMS] += uint64(generated.Sub(start).Milliseconds()) - vBlock, ledgerTxnCount, err := g.evaluateBlock(cert.Block.BlockHeader, txGroupsAD, int(intra)) + vBlock, ledgerTxnCount, commitWaitTime, err := g.evaluateBlock(cert.Block.BlockHeader, txGroupsAD, int(intra)) if err != nil { return fmt.Errorf("failed to evaluate block: %w", err) } @@ -380,12 +351,15 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { return fmt.Errorf("evaluateBlock() txn count mismatches theoretical intra: %d != %d", ledgerTxnCount, g.txnCounter+intra) } evaluated = time.Now() + g.reportData.Counters[LedgerEvalTimeMS] += uint64(evaluated.Sub(generated).Milliseconds()) err = g.ledger.AddValidatedBlock(*vBlock, cert.Certificate) if err != nil { return fmt.Errorf("failed to add validated block: %w", err) } validated = time.Now() + g.reportData.Counters[CommitWaitTimeMS] += uint64(commitWaitTime.Milliseconds()) + g.reportData.Counters[LedgerValidateTimeMS] += uint64((validated.Sub(evaluated) - commitWaitTime).Milliseconds()) cert.Block.Payset = vBlock.Block().Payset @@ -400,6 +374,8 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { // write the msgpack bytes for a block g.latestBlockMsgp = protocol.EncodeMsgp(&cert) + g.reportData.Counters[BlockTotalSizeBytes] += uint64(len(g.latestBlockMsgp)) + _, err = output.Write(g.latestBlockMsgp) if err != nil { return err @@ -812,7 +788,7 @@ func (g *generator) generateAssetTxnInternalHint(txType TxTypeID, round uint64, } if g.balances[senderIndex] < txn.Fee.ToUint64() { - fmt.Printf("\n\nthe sender account does not have enough algos for the transfer. idx %d, asset transaction type %v, num %d\n\n", senderIndex, actual, g.reportData[actual].GenerationCount) + fmt.Printf("\n\nthe sender account does not have enough algos for the transfer. idx %d, asset transaction type %v, num %d\n\n", senderIndex, actual, g.reportData.Transactions[actual].GenerationCount) os.Exit(1) } @@ -835,10 +811,10 @@ func track(id TxTypeID) (TxTypeID, time.Time) { func (g *generator) recordData(id TxTypeID, start time.Time) { g.latestData[id]++ - data := g.reportData[id] + data := g.reportData.Transactions[id] data.GenerationCount += 1 data.GenerationTime += time.Since(start) - g.reportData[id] = data + g.reportData.Transactions[id] = data } // ---- sign transactions ---- diff --git a/tools/block-generator/generator/generate_apps.go b/tools/block-generator/generator/generate_apps.go index 5ecac947c0..8edd28da57 100644 --- a/tools/block-generator/generator/generate_apps.go +++ b/tools/block-generator/generator/generate_apps.go @@ -17,13 +17,53 @@ package generator import ( + _ "embed" "fmt" "math/rand" "time" txn "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/tools/block-generator/util" ) +// ---- templates ---- + +//go:embed teal/poap_boxes.teal +var approvalBoxes string +var approvalBoxesBytes interface{} + +//go:embed teal/poap_clear.teal +var clearBoxes string +var clearBoxesBytes interface{} + +//go:embed teal/swap_amm.teal +var approvalSwap string +var approvalSwapBytes interface{} + +//go:embed teal/swap_clear.teal +var clearSwap string +var clearSwapBytes interface{} + +// Precompile teal programs +func init() { + prog, err := logic.AssembleString(approvalBoxes) + util.MaybeFail(err, "failed to assemble approval program") + approvalBoxesBytes = prog.Program + + prog, err = logic.AssembleString(clearBoxes) + util.MaybeFail(err, "failed to assemble clear program") + clearBoxesBytes = prog.Program + + prog, err = logic.AssembleString(approvalSwap) + util.MaybeFail(err, "failed to assemble approvalSwap program") + approvalSwapBytes = prog.Program + + prog, err = logic.AssembleString(clearSwap) + util.MaybeFail(err, "failed to assemble clearSwap program") + clearSwapBytes = prog.Program +} + // ---- generator app state ---- func (g *generator) resetPendingApps() { @@ -71,7 +111,7 @@ func countEffects(actual TxTypeID) uint64 { func CumulativeEffects(report Report) EffectsReport { effsReport := make(EffectsReport) - for txType, data := range report { + for txType, data := range report.Transactions { rootCount := data.GenerationCount effsReport[string(txType)] += rootCount for _, effect := range effects[txType] { diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go index 29a2613d64..1ce40b0f13 100644 --- a/tools/block-generator/generator/generate_test.go +++ b/tools/block-generator/generator/generate_test.go @@ -674,16 +674,16 @@ func TestRecordData(t *testing.T) { gen := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) id := TxTypeID("test") - data, ok := gen.reportData[id] + data, ok := gen.reportData.Transactions[id] require.False(t, ok) gen.recordData(id, time.Now()) - data, ok = gen.reportData[id] + data, ok = gen.reportData.Transactions[id] require.True(t, ok) require.Equal(t, uint64(1), data.GenerationCount) gen.recordData(id, time.Now()) - data, ok = gen.reportData[id] + data, ok = gen.reportData.Transactions[id] require.True(t, ok) require.Equal(t, uint64(2), data.GenerationCount) } @@ -725,11 +725,13 @@ func TestCumulativeEffects(t *testing.T) { partitiontest.PartitionTest(t) report := Report{ - TxTypeID("app_boxes_optin"): {GenerationCount: uint64(42)}, - TxTypeID("app_boxes_create"): {GenerationCount: uint64(1337)}, - TxTypeID("pay_pay"): {GenerationCount: uint64(999)}, - TxTypeID("asset_optin_total"): {GenerationCount: uint64(13)}, - TxTypeID("app_boxes_call"): {GenerationCount: uint64(413)}, + Transactions: map[TxTypeID]TxData{ + TxTypeID("app_boxes_optin"): {GenerationCount: uint64(42)}, + TxTypeID("app_boxes_create"): {GenerationCount: uint64(1337)}, + TxTypeID("pay_pay"): {GenerationCount: uint64(999)}, + TxTypeID("asset_optin_total"): {GenerationCount: uint64(13)}, + TxTypeID("app_boxes_call"): {GenerationCount: uint64(413)}, + }, } expectedEffectsReport := EffectsReport{ diff --git a/tools/block-generator/generator/generator_ledger.go b/tools/block-generator/generator/generator_ledger.go index 97fed9b344..dabad6ac21 100644 --- a/tools/block-generator/generator/generator_ledger.go +++ b/tools/block-generator/generator/generator_ledger.go @@ -20,6 +20,8 @@ import ( "encoding/binary" "fmt" "os" + "strings" + "time" "github.com/algorand/avm-abi/apps" cconfig "github.com/algorand/go-algorand/config" @@ -167,19 +169,30 @@ func (g *generator) startEvaluator(hdr bookkeeping.BlockHeader, paysetHint int) }) } -func (g *generator) evaluateBlock(hdr bookkeeping.BlockHeader, txGroups [][]txn.SignedTxnWithAD, paysetHint int) (*ledgercore.ValidatedBlock, uint64 /* txnCount */, error) { +func (g *generator) evaluateBlock(hdr bookkeeping.BlockHeader, txGroups [][]txn.SignedTxnWithAD, paysetHint int) (*ledgercore.ValidatedBlock, uint64 /* txnCount */, time.Duration /* commit wait time */, error) { + commitWaitTime := time.Duration(0) + waitDelay := 10 * time.Millisecond eval, err := g.startEvaluator(hdr, paysetHint) if err != nil { - return nil, 0, fmt.Errorf("could not start evaluator: %w", err) + return nil, 0, 0, fmt.Errorf("could not start evaluator: %w", err) } for i, txGroup := range txGroups { - err := eval.TransactionGroup(txGroup) - if err != nil { - return nil, 0, fmt.Errorf("could not evaluate transaction group %d: %w", i, err) + for { + err := eval.TransactionGroup(txGroup) + if err != nil { + if strings.Contains(err.Error(), "database table is locked") { + time.Sleep(waitDelay) + commitWaitTime += waitDelay + // sometimes the database is locked, so we retry + continue + } + return nil, 0, 0, fmt.Errorf("could not evaluate transaction group %d: %w", i, err) + } + break } } lvb, err := eval.GenerateBlock() - return lvb, eval.TestingTxnCounter(), err + return lvb, eval.TestingTxnCounter(), commitWaitTime, err } func countInners(ad txn.ApplyData) int { diff --git a/tools/block-generator/generator/generator_types.go b/tools/block-generator/generator/generator_types.go index 6685ffe7c8..8c16c1bd5b 100644 --- a/tools/block-generator/generator/generator_types.go +++ b/tools/block-generator/generator/generator_types.go @@ -149,7 +149,10 @@ type assetHolding struct { } // Report is the generation report. -type Report map[TxTypeID]TxData +type Report struct { + Counters map[string]uint64 `json:"counters"` + Transactions map[TxTypeID]TxData `json:"transactions"` +} // EffectsReport collates transaction counts caused by a root transaction. type EffectsReport map[string]uint64 diff --git a/tools/block-generator/runner/reporting_test.go b/tools/block-generator/runner/reporting_test.go new file mode 100644 index 0000000000..27af7f52e5 --- /dev/null +++ b/tools/block-generator/runner/reporting_test.go @@ -0,0 +1,94 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package runner + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/tools/block-generator/generator" +) + +func makeDummyData() (time.Time, time.Duration, generator.Report, *MetricsCollector) { + start := time.Now().Add(-10 * time.Minute) + duration := time.Hour + generatorReport := generator.Report{ + Counters: make(map[string]uint64), + Transactions: make(map[generator.TxTypeID]generator.TxData), + } + collector := &MetricsCollector{Data: make([]Entry, 10)} + return start, duration, generatorReport, collector +} + +// makeMetrics creates a set of metrics for testing. +func makeMetrics(start time.Time) *MetricsCollector { + collector := &MetricsCollector{} + for i := 0; i <= 10; i++ { + var data []string + + // should be converted to an average. + data = append(data, fmt.Sprintf("import_time_sec_sum %d", i*100)) + data = append(data, fmt.Sprintf("import_time_sec_count %d", i)) + // should be converted to an average. + data = append(data, fmt.Sprintf("imported_tx_per_block_sum %d", i*100)) + data = append(data, fmt.Sprintf("imported_tx_per_block_count %d", i)) + + data = append(data, fmt.Sprintf("imported_round %d", i)) + collector.Data = append(collector.Data, Entry{ + Timestamp: start.Add(time.Duration(i) * time.Minute), + Data: data, + }) + } + return collector +} + +func TestWriteReport_MissingMetrics(t *testing.T) { + start, duration, generatorReport, collector := makeDummyData() + var builder strings.Builder + err := writeReport(&builder, t.Name(), start, duration, generatorReport, collector) + require.ErrorContains(t, err, "metric incomplete or not found") +} + +func TestWriterReport_Good(t *testing.T) { + start, duration, generatorReport, _ := makeDummyData() + collector := makeMetrics(start) + + generatorReport.Counters[generator.BlockTotalSizeBytes] = 1024 + generatorReport.Counters[generator.BlockgenGenerateTimeMS] = 0 + generatorReport.Counters[generator.CommitWaitTimeMS] = 1000 + generatorReport.Counters[generator.LedgerEvalTimeMS] = 2000 + generatorReport.Counters[generator.LedgerValidateTimeMS] = 3000 + + var builder strings.Builder + err := writeReport(&builder, t.Name(), start, duration, generatorReport, collector) + require.NoError(t, err) + + report := builder.String() + + // both rounds of metrics are reported. + require.Contains(t, report, "final_imported_round:10") + require.Contains(t, report, "early_imported_round:2") + + // counters are reported. + for k, v := range generatorReport.Counters { + require.Contains(t, report, fmt.Sprintf("%s:%d", k, v)) + } +} diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 3b22315292..fc76308b2c 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -19,11 +19,12 @@ package runner import ( "bytes" "context" + "encoding/json" + "io" "sort" // embed conduit template config file _ "embed" - "encoding/json" "fmt" "net/http" "os" @@ -253,12 +254,12 @@ const ( ) // Helper to record metrics. Supports rates (sum/count) and counters. -func recordDataToFile(start time.Time, entry Entry, prefix string, out *os.File) error { +func recordDataToWriter(start time.Time, entry Entry, prefix string, out io.Writer) error { var writeErrors []string var writeErr error record := func(prefix2, name string, t metricType) { key := fmt.Sprintf("%s%s_%s", prefix, prefix2, name) - if err := recordMetricToFile(entry, key, name, t, out); err != nil { + if err := recordMetricToWriter(entry, key, name, t, out); err != nil { writeErr = err writeErrors = append(writeErrors, name) } @@ -287,21 +288,21 @@ func recordDataToFile(start time.Time, entry Entry, prefix string, out *os.File) tps := totalTxn / importTimeS key := "overall_transactions_per_second" msg := fmt.Sprintf("%s_%s:%.2f\n", prefix, key, tps) - if _, err := out.WriteString(msg); err != nil { + if _, err := fmt.Fprintf(out, msg); err != nil { return fmt.Errorf("unable to write metric '%s': %w", key, err) } // Uptime key = "uptime_seconds" msg = fmt.Sprintf("%s_%s:%.2f\n", prefix, key, time.Since(start).Seconds()) - if _, err := out.WriteString(msg); err != nil { + if _, err := fmt.Fprintf(out, msg); err != nil { return fmt.Errorf("unable to write metric '%s': %w", key, err) } return nil } -func recordMetricToFile(entry Entry, outputKey, metricSuffix string, t metricType, out *os.File) error { +func recordMetricToWriter(entry Entry, outputKey, metricSuffix string, t metricType, out io.Writer) error { value, err := getMetric(entry, metricSuffix, t == rate) if err != nil { return err @@ -314,7 +315,7 @@ func recordMetricToFile(entry Entry, outputKey, metricSuffix string, t metricTyp msg = fmt.Sprintf("%s:%.2f\n", outputKey, value) } - if _, err := out.WriteString(msg); err != nil { + if _, err := fmt.Fprintf(out, msg); err != nil { return fmt.Errorf("unable to write metric '%s': %w", outputKey, err) } @@ -371,50 +372,31 @@ func getMetric(entry Entry, suffix string, rateMetric bool) (float64, error) { return 0.0, fmt.Errorf("metric incomplete or not found: %s", suffix) } -// Run the test for 'RunDuration', collect metrics and write them to the 'ReportDirectory' -func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string) error { - collector := &MetricsCollector{MetricsURL: fmt.Sprintf("http://%s/metrics", metricsURL)} - - // Run for r.RunDuration - start := time.Now() - fmt.Printf("%sduration starting now: %s\n", pad, start) - count := 1 - for time.Since(start) < r.RunDuration { - time.Sleep(r.RunDuration / 10) - fmt.Printf("%scollecting metrics (%d)\n", pad, count) - - if err := collector.Collect(AllMetricNames...); err != nil { - return fmt.Errorf("problem collecting metrics (%d / %s): %w", count, time.Since(start), err) +func writeReport(w io.Writer, scenario string, start time.Time, runDuration time.Duration, generatorReport generator.Report, collector *MetricsCollector) error { + write := func(pattern string, parts ...any) error { + str := fmt.Sprintf(pattern, parts...) + if _, err := fmt.Fprintf(w, str); err != nil { + return fmt.Errorf("unable to write '%s': %w", str, err) } - count++ + return nil } - fmt.Printf("%scollecting final metrics\n", pad) - if err := collector.Collect(AllMetricNames...); err != nil { - return fmt.Errorf("problem collecting final metrics (%d / %s): %w", count, time.Since(start), err) + if err := write("scenario:%s\n", scenario); err != nil { + return err } - // write scenario to report - scenario := path.Base(r.Path) - if _, err := report.WriteString(fmt.Sprintf("scenario:%s\n", scenario)); err != nil { - return fmt.Errorf("unable to write scenario to report: %w", err) - } - // Collect results. - durationStr := fmt.Sprintf("test_duration_seconds:%d\ntest_duration_actual_seconds:%f\n", - uint64(r.RunDuration.Seconds()), - time.Since(start).Seconds()) - if _, err := report.WriteString(durationStr); err != nil { - return fmt.Errorf("unable to write duration metric: %w", err) + if err := write("test_duration_seconds:%d\n", uint64(runDuration.Seconds())); err != nil { + return err } - resp, err := http.Get(fmt.Sprintf("http://%s/report", generatorURL)) - if err != nil { - return fmt.Errorf("generator report query failed") + if err := write("test_duration_actual_seconds:%f\n", time.Since(start).Seconds()); err != nil { + return err } - defer resp.Body.Close() - var generatorReport generator.Report - if err = json.NewDecoder(resp.Body).Decode(&generatorReport); err != nil { - return fmt.Errorf("problem decoding generator report: %w", err) + + for metric, value := range generatorReport.Counters { + if err := write("%s:%d\n", metric, value); err != nil { + return err + } } effects := generator.CumulativeEffects(generatorReport) @@ -432,36 +414,79 @@ func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string) txCount := effects[metric] allTxns += txCount str := fmt.Sprintf("transaction_%s_total:%d\n", metric, txCount) - if _, err = report.WriteString(str); err != nil { - return fmt.Errorf("unable to write transaction_count metric: %w", err) + if _, err := fmt.Fprintf(w, str); err != nil { + return fmt.Errorf("unable to write '%s' metric: %w", str, err) } } str := fmt.Sprintf("transaction_%s_total:%d\n", "ALL", allTxns) - if _, err = report.WriteString(str); err != nil { - return fmt.Errorf("unable to write transaction_count metric: %w", err) + if _, err := fmt.Fprintf(w, str); err != nil { + return fmt.Errorf("unable to write '%s' metric: %w", str, err) } // Record a rate from one of the first data points. if len(collector.Data) > 5 { - if err = recordDataToFile(start, collector.Data[2], "early", report); err != nil { + if err := recordDataToWriter(start, collector.Data[2], "early", w); err != nil { return err } } // Also record the final metrics. - if err = recordDataToFile(start, collector.Data[len(collector.Data)-1], "final", report); err != nil { + if err := recordDataToWriter(start, collector.Data[len(collector.Data)-1], "final", w); err != nil { return err } return nil } +// Run the test for 'RunDuration', collect metrics and write report to the report file. +func (r *Args) runTest(w io.Writer, metricsURL string, generatorURL string) error { + collector := &MetricsCollector{MetricsURL: fmt.Sprintf("http://%s/metrics", metricsURL)} + + // Run for r.RunDuration + start := time.Now() + fmt.Printf("%sduration starting now: %s\n", pad, start) + count := 1 + for time.Since(start) < r.RunDuration { + time.Sleep(r.RunDuration / 10) + fmt.Printf("%scollecting metrics (%d)\n", pad, count) + + if err := collector.Collect(AllMetricNames...); err != nil { + return fmt.Errorf("problem collecting metrics (%d / %s): %w", count, time.Since(start), err) + } + count++ + } + + fmt.Printf("%scollecting final metrics\n", pad) + if err := collector.Collect(AllMetricNames...); err != nil { + return fmt.Errorf("problem collecting final metrics (%d / %s): %w", count, time.Since(start), err) + } + + // get generator report + scenario := path.Base(r.Path) + resp, err := http.Get(fmt.Sprintf("http://%s/report", generatorURL)) + if err != nil { + return fmt.Errorf("generator report query failed") + } + defer resp.Body.Close() + var generatorReport generator.Report + if err = json.NewDecoder(resp.Body).Decode(&generatorReport); err != nil { + return fmt.Errorf("problem decoding generator report: %w", err) + } + + // write report to file + err = writeReport(w, scenario, start, r.RunDuration, generatorReport, collector) + if err != nil { + return fmt.Errorf("problem writing report: %w", err) + } + return nil +} + // startGenerator starts the generator server. func startGenerator(ledgerLogFile, configFile string, dbround uint64, genesisFile string, verbose bool, addr string, blockMiddleware func(http.Handler) http.Handler) (func() error, generator.Generator) { f, err := os.OpenFile(ledgerLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) util.MaybeFail(err, "unable to open ledger log file '%s'", ledgerLogFile) log := logging.NewLogger() - log.SetLevel(logging.Warn) + log.SetLevel(logging.Info) log.SetOutput(f) // Start generator. From 3425e8563f9925a5642ae9a9c53235345f4736ff Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:00:08 -0400 Subject: [PATCH 44/66] p2p: in-memory peerstore (#5664) --- go.mod | 4 ---- go.sum | 18 ------------------ network/p2p/peerstore/peerstore.go | 24 +++++++++--------------- network/p2p/peerstore/peerstore_test.go | 15 ++------------- 4 files changed, 11 insertions(+), 50 deletions(-) diff --git a/go.mod b/go.mod index dc78fd6921..2b81add583 100644 --- a/go.mod +++ b/go.mod @@ -25,8 +25,6 @@ require ( github.com/golang/snappy v0.0.4 github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.8.0 - github.com/ipfs/go-datastore v0.6.0 - github.com/ipfs/go-ds-pebble v0.2.4 github.com/jmoiron/sqlx v1.2.0 github.com/karalabe/usb v0.0.2 github.com/labstack/echo/v4 v4.9.1 @@ -66,12 +64,10 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/invopop/yaml v0.1.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect - github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.16.7 // indirect diff --git a/go.sum b/go.sum index 7b9ce20799..89abdaa086 100644 --- a/go.sum +++ b/go.sum @@ -47,7 +47,6 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= @@ -108,7 +107,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -169,11 +167,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5il github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= -github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -354,8 +349,6 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU= -github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= @@ -376,22 +369,12 @@ github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= -github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= -github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= -github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= -github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1Ro= -github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= -github.com/ipfs/go-ds-pebble v0.2.4 h1:Y/Pma4GIIxvenyuFU2oPv6eS3Yh7UebaZ6/xqpBJuds= -github.com/ipfs/go-ds-pebble v0.2.4/go.mod h1:gcsz9feIlROgsWzO81ZlrMpWtK2mQ+b/a8F+advE+Hw= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= -github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= -github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= @@ -656,7 +639,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= diff --git a/network/p2p/peerstore/peerstore.go b/network/p2p/peerstore/peerstore.go index 03cf49d1f1..ee6bf90a65 100644 --- a/network/p2p/peerstore/peerstore.go +++ b/network/p2p/peerstore/peerstore.go @@ -17,33 +17,27 @@ package peerstore import ( - "context" "fmt" - ds "github.com/ipfs/go-datastore" - pebbledb "github.com/ipfs/go-ds-pebble" "github.com/libp2p/go-libp2p/core/peer" libp2p "github.com/libp2p/go-libp2p/core/peerstore" - "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds" + mempstore "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" ) -// PeerStore implements libp2p.Peerstore +// PeerStore implements Peerstore and CertifiedAddrBook. type PeerStore struct { - libp2p.Peerstore + peerStoreCAB } -func initDBStore(path string) (ds.Batching, error) { - store, err := pebbledb.NewDatastore(path, nil) - return store, err +// peerStoreCAB combines the libp2p Peerstore and CertifiedAddrBook interfaces. +type peerStoreCAB interface { + libp2p.Peerstore + libp2p.CertifiedAddrBook } // NewPeerStore creates a new peerstore backed by a datastore. -func NewPeerStore(ctx context.Context, path string, addrInfo []*peer.AddrInfo) (*PeerStore, error) { - datastore, err := initDBStore(path) - if err != nil { - return nil, fmt.Errorf("cannot initialize a peerstore, invalid path for datastore: %w", err) - } - ps, err := pstoreds.NewPeerstore(ctx, datastore, pstoreds.DefaultOpts()) +func NewPeerStore(addrInfo []*peer.AddrInfo) (*PeerStore, error) { + ps, err := mempstore.NewPeerstore() if err != nil { return nil, fmt.Errorf("cannot initialize a peerstore: %w", err) } diff --git a/network/p2p/peerstore/peerstore_test.go b/network/p2p/peerstore/peerstore_test.go index 7219799221..2debcae255 100644 --- a/network/p2p/peerstore/peerstore_test.go +++ b/network/p2p/peerstore/peerstore_test.go @@ -17,7 +17,6 @@ package peerstore import ( - "context" "crypto/rand" "fmt" "testing" @@ -41,8 +40,7 @@ func TestPeerstore(t *testing.T) { } addrInfo, _ := PeerInfoFromAddrs(peerAddrs) - dir := t.TempDir() - ps, err := NewPeerStore(context.Background(), dir, addrInfo) + ps, err := NewPeerStore(addrInfo) require.NoError(t, err) defer ps.Close() @@ -74,17 +72,8 @@ func TestPeerstore(t *testing.T) { require.Equal(t, 8, len(peers)) // remove a peer addr - ps.Peerstore.ClearAddrs(peerIDS[0]) + ps.ClearAddrs(peerIDS[0]) peers = ps.PeersWithAddrs() require.Equal(t, 7, len(peers)) } - -func TestPeerStoreInitErrors(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - // bad datastore path - _, err := NewPeerStore(context.Background(), "//", []*peer.AddrInfo{}) - require.Contains(t, err.Error(), "invalid path for datastore") - -} From 8f840c73eed3c0ae0999b9d52248a8e68f9ba2c1 Mon Sep 17 00:00:00 2001 From: Shant Karakashian <55754073+algonautshant@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:13:47 -0400 Subject: [PATCH 45/66] Ledger: Close the ledger at node shutdown (#5668) --- node/node.go | 1 + 1 file changed, 1 insertion(+) diff --git a/node/node.go b/node/node.go index 477c1b794c..0e10be7be2 100644 --- a/node/node.go +++ b/node/node.go @@ -418,6 +418,7 @@ func (node *AlgorandFullNode) Stop() { node.lowPriorityCryptoVerificationPool.Shutdown() node.cryptoPool.Shutdown() node.cancelCtx() + node.ledger.Close() } // note: unlike the other two functions, this accepts a whole filename From 0c4f253e657bcb2bfebd9292861e143ac2c7c492 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:16:51 -0400 Subject: [PATCH 46/66] tools: metrics files visualizer (#5661) --- test/heapwatch/.gitignore | 2 + test/heapwatch/metrics_delta.py | 27 +++-- test/heapwatch/metrics_viz.py | 195 ++++++++++++++++++++++++++++++++ test/heapwatch/requirements.txt | 6 + 4 files changed, 219 insertions(+), 11 deletions(-) create mode 100644 test/heapwatch/.gitignore create mode 100644 test/heapwatch/metrics_viz.py create mode 100644 test/heapwatch/requirements.txt diff --git a/test/heapwatch/.gitignore b/test/heapwatch/.gitignore new file mode 100644 index 0000000000..c20c2ab731 --- /dev/null +++ b/test/heapwatch/.gitignore @@ -0,0 +1,2 @@ +__pycache__ + diff --git a/test/heapwatch/metrics_delta.py b/test/heapwatch/metrics_delta.py index d50346ec04..420130df91 100644 --- a/test/heapwatch/metrics_delta.py +++ b/test/heapwatch/metrics_delta.py @@ -371,6 +371,21 @@ def process_nick_re(nre, filesByNick, nick_to_tfname, rsum, args, grsum): 'npn': (.7,.7,0), } +def terraform_inventory_ip_not_names(tf_inventory_path): + """return ip to nickname mapping""" + tf_inventory = configparser.ConfigParser(allow_no_value=True) + tf_inventory.read(tf_inventory_path) + ip_to_name = {} + for k, sub in tf_inventory.items(): + if k.startswith('name_'): + for ip in sub: + if ip in ip_to_name: + logger.warning('ip %r already named %r, also got %r', ip, ip_to_name[ip], k) + ip_to_name[ip] = k + #logger.debug('names: %r', sorted(ip_to_name.values())) + #logger.debug('ip to name %r', ip_to_name) + return ip_to_name + def main(): os.environ['TZ'] = 'UTC' time.tzset() @@ -409,17 +424,7 @@ def main(): break nick_to_tfname = {} if tf_inventory_path: - tf_inventory = configparser.ConfigParser(allow_no_value=True) - tf_inventory.read(tf_inventory_path) - ip_to_name = {} - for k, sub in tf_inventory.items(): - if k.startswith('name_'): - for ip in sub: - if ip in ip_to_name: - logger.warning('ip %r already named %r, also got %r', ip, ip_to_name[ip], k) - ip_to_name[ip] = k - #logger.debug('names: %r', sorted(ip_to_name.values())) - #logger.debug('ip to name %r', ip_to_name) + ip_to_name = terraform_inventory_ip_not_names(tf_inventory_path) unfound = [] for ip, name in ip_to_name.items(): found = [] diff --git a/test/heapwatch/metrics_viz.py b/test/heapwatch/metrics_viz.py new file mode 100644 index 0000000000..a2616a2eb6 --- /dev/null +++ b/test/heapwatch/metrics_viz.py @@ -0,0 +1,195 @@ +""" +Tool for metrics files visualization. +Expects metrics files in format ._