diff --git a/ledger/eval.go b/ledger/eval.go
index 5730a4f223..7a2a6e4634 100644
--- a/ledger/eval.go
+++ b/ledger/eval.go
@@ -1524,54 +1524,3 @@ func (vb ValidatedBlock) WithSeed(s committee.Seed) ValidatedBlock {
delta: vb.delta,
}
}
-
-// GetBlockAddresses returns all addresses referenced in `block`.
-func GetBlockAddresses(block *bookkeeping.Block) map[basics.Address]struct{} {
- // Reserve a reasonable memory size for the map.
- res := make(map[basics.Address]struct{}, len(block.Payset)+2)
- res[block.FeeSink] = struct{}{}
- res[block.RewardsPool] = struct{}{}
-
- var refAddresses []basics.Address
- for _, stib := range block.Payset {
- getTxnAddresses(&stib.Txn, &refAddresses)
- for _, address := range refAddresses {
- res[address] = struct{}{}
- }
- }
-
- return res
-}
-
-// Eval evaluates a block without validation using the given `proto`. Return the state
-// delta and transactions with modified apply data according to `proto`.
-// This function is used by Indexer which modifies `proto` to retrieve the asset
-// close amount for each transaction even when the real consensus parameters do not
-// support it.
-func Eval(l ledgerForEvaluator, blk *bookkeeping.Block, proto config.ConsensusParams) (ledgercore.StateDelta, []transactions.SignedTxnInBlock, error) {
- eval, err := startEvaluator(
- l, blk.BlockHeader, proto, len(blk.Payset), false, false)
- if err != nil {
- return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{}, err
- }
-
- paysetgroups, err := blk.DecodePaysetGroups()
- if err != nil {
- return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{}, err
- }
-
- for _, group := range paysetgroups {
- err = eval.TransactionGroup(group)
- if err != nil {
- return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{}, err
- }
- }
-
- // Finally, process any pending end-of-block state changes.
- err = eval.endOfBlock()
- if err != nil {
- return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{}, err
- }
-
- return eval.state.deltas(), eval.block.Payset, nil
-}
diff --git a/ledger/evalIndexer.go b/ledger/evalIndexer.go
new file mode 100644
index 0000000000..4d2e7dd01d
--- /dev/null
+++ b/ledger/evalIndexer.go
@@ -0,0 +1,217 @@
+// Copyright (C) 2019-2021 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package ledger
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/data/bookkeeping"
+ "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+)
+
+// FoundAddress is a wrapper for an address and a boolean.
+type FoundAddress struct {
+ Address basics.Address
+ Exists bool
+}
+
+// A ledger interface that Indexer implements. This is a simplified version of the
+// ledgerForEvaluator interface. Certain functions that the evaluator doesn't use
+// in the trusting mode are excluded, and the present functions only request data
+// at the latest round.
+type indexerLedgerForEval interface {
+ LatestBlockHdr() (bookkeeping.BlockHeader, error)
+ // The value of the returned map is nil iff the account was not found.
+ LookupWithoutRewards(map[basics.Address]struct{}) (map[basics.Address]*basics.AccountData, error)
+ GetAssetCreator(map[basics.AssetIndex]struct{}) (map[basics.AssetIndex]FoundAddress, error)
+ GetAppCreator(map[basics.AppIndex]struct{}) (map[basics.AppIndex]FoundAddress, error)
+ Totals() (ledgercore.AccountTotals, error)
+}
+
+// Converter between indexerLedgerForEval and ledgerForEvaluator interfaces.
+type indexerLedgerConnector struct {
+ il indexerLedgerForEval
+ genesisHash crypto.Digest
+ latestRound basics.Round
+}
+
+// BlockHdr is part of ledgerForEvaluator interface.
+func (l indexerLedgerConnector) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) {
+ if round != l.latestRound {
+ return bookkeeping.BlockHeader{}, fmt.Errorf(
+ "BlockHdr() evaluator called this function for the wrong round %d, "+
+ "latest round is %d",
+ round, l.latestRound)
+ }
+ return l.il.LatestBlockHdr()
+}
+
+// CheckDup is part of ledgerForEvaluator interface.
+func (l indexerLedgerConnector) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, TxLease) error {
+ // This function is not used by evaluator.
+ return errors.New("CheckDup() not implemented")
+}
+
+// LookupWithoutRewards is part of ledgerForEvaluator interface.
+func (l indexerLedgerConnector) LookupWithoutRewards(round basics.Round, address basics.Address) (basics.AccountData, basics.Round, error) {
+ accountDataMap, err :=
+ l.il.LookupWithoutRewards(map[basics.Address]struct{}{address: {}})
+ if err != nil {
+ return basics.AccountData{}, basics.Round(0), err
+ }
+
+ accountData := accountDataMap[address]
+ if accountData == nil {
+ return basics.AccountData{}, round, nil
+ }
+ return *accountData, round, nil
+}
+
+// GetCreatorForRound is part of ledgerForEvaluator interface.
+func (l indexerLedgerConnector) GetCreatorForRound(_ basics.Round, cindex basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) {
+ var foundAddress FoundAddress
+
+ switch ctype {
+ case basics.AssetCreatable:
+ foundAddresses, err :=
+ l.il.GetAssetCreator(map[basics.AssetIndex]struct{}{basics.AssetIndex(cindex): {}})
+ if err != nil {
+ return basics.Address{}, false, err
+ }
+ foundAddress = foundAddresses[basics.AssetIndex(cindex)]
+ case basics.AppCreatable:
+ foundAddresses, err :=
+ l.il.GetAppCreator(map[basics.AppIndex]struct{}{basics.AppIndex(cindex): {}})
+ if err != nil {
+ return basics.Address{}, false, err
+ }
+ foundAddress = foundAddresses[basics.AppIndex(cindex)]
+ default:
+ return basics.Address{}, false, fmt.Errorf("unknown creatable type %v", ctype)
+ }
+
+ return foundAddress.Address, foundAddress.Exists, nil
+}
+
+// GenesisHash is part of ledgerForEvaluator interface.
+func (l indexerLedgerConnector) GenesisHash() crypto.Digest {
+ return l.genesisHash
+}
+
+// Totals is part of ledgerForEvaluator interface.
+func (l indexerLedgerConnector) Totals(round basics.Round) (ledgercore.AccountTotals, error) {
+ if round != l.latestRound {
+ return ledgercore.AccountTotals{}, fmt.Errorf(
+ "Totals() evaluator called this function for the wrong round %d, "+
+ "latest round is %d",
+ round, l.latestRound)
+ }
+ return l.il.Totals()
+}
+
+// CompactCertVoters is part of ledgerForEvaluator interface.
+func (l indexerLedgerConnector) CompactCertVoters(_ basics.Round) (*VotersForRound, error) {
+ // This function is not used by evaluator.
+ return nil, errors.New("CompactCertVoters() not implemented")
+}
+
+func makeIndexerLedgerConnector(il indexerLedgerForEval, genesisHash crypto.Digest, latestRound basics.Round) indexerLedgerConnector {
+ return indexerLedgerConnector{
+ il: il,
+ genesisHash: genesisHash,
+ latestRound: latestRound,
+ }
+}
+
+// Returns all addresses referenced in `block`.
+func getBlockAddresses(block *bookkeeping.Block) map[basics.Address]struct{} {
+ // Reserve a reasonable memory size for the map.
+ res := make(map[basics.Address]struct{}, len(block.Payset)+2)
+ res[block.FeeSink] = struct{}{}
+ res[block.RewardsPool] = struct{}{}
+
+ var refAddresses []basics.Address
+ for _, stib := range block.Payset {
+ getTxnAddresses(&stib.Txn, &refAddresses)
+ for _, address := range refAddresses {
+ res[address] = struct{}{}
+ }
+ }
+
+ return res
+}
+
+// EvalForIndexer evaluates a block without validation using the given `proto`.
+// Return the state delta and transactions with modified apply data according to `proto`.
+// This function is used by Indexer which modifies `proto` to retrieve the asset
+// close amount for each transaction even when the real consensus parameters do not
+// support it.
+func EvalForIndexer(il indexerLedgerForEval, block *bookkeeping.Block, proto config.ConsensusParams) (ledgercore.StateDelta, []transactions.SignedTxnInBlock, error) {
+ ilc := makeIndexerLedgerConnector(il, block.GenesisHash(), block.Round()-1)
+
+ eval, err := startEvaluator(
+ ilc, block.BlockHeader, proto, len(block.Payset), false, false)
+ if err != nil {
+ return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
+ fmt.Errorf("EvalForIndexer() err: %w", err)
+ }
+
+ // Preload most needed accounts.
+ {
+ accountDataMap, err := il.LookupWithoutRewards(getBlockAddresses(block))
+ if err != nil {
+ return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
+ fmt.Errorf("EvalForIndexer() err: %w", err)
+ }
+ base := eval.state.lookupParent.(*roundCowBase)
+ for address, accountData := range accountDataMap {
+ if accountData == nil {
+ base.accounts[address] = basics.AccountData{}
+ } else {
+ base.accounts[address] = *accountData
+ }
+ }
+ }
+
+ paysetgroups, err := block.DecodePaysetGroups()
+ if err != nil {
+ return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
+ fmt.Errorf("EvalForIndexer() err: %w", err)
+ }
+
+ for _, group := range paysetgroups {
+ err = eval.TransactionGroup(group)
+ if err != nil {
+ return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
+ fmt.Errorf("EvalForIndexer() err: %w", err)
+ }
+ }
+
+ // Finally, process any pending end-of-block state changes.
+ err = eval.endOfBlock()
+ if err != nil {
+ return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
+ fmt.Errorf("EvalForIndexer() err: %w", err)
+ }
+
+ return eval.state.deltas(), eval.block.Payset, nil
+}
diff --git a/ledger/evalIndexer_test.go b/ledger/evalIndexer_test.go
new file mode 100644
index 0000000000..3817156867
--- /dev/null
+++ b/ledger/evalIndexer_test.go
@@ -0,0 +1,183 @@
+// Copyright (C) 2019-2021 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package ledger
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/data/bookkeeping"
+ "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/data/txntest"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/partitiontest"
+)
+
+type indexerLedgerForEvalImpl struct {
+ l *Ledger
+ latestRound basics.Round
+}
+
+func (il indexerLedgerForEvalImpl) LatestBlockHdr() (bookkeeping.BlockHeader, error) {
+ return il.l.BlockHdr(il.latestRound)
+}
+
+// The value of the returned map is nil iff the account was not found.
+func (il indexerLedgerForEvalImpl) LookupWithoutRewards(addresses map[basics.Address]struct{}) (map[basics.Address]*basics.AccountData, error) {
+ res := make(map[basics.Address]*basics.AccountData)
+
+ for address := range addresses {
+ accountData, _, err := il.l.LookupWithoutRewards(il.latestRound, address)
+ if err != nil {
+ return nil, err
+ }
+
+ if accountData.IsZero() {
+ res[address] = nil
+ } else {
+ accountDataCopy := new(basics.AccountData)
+ *accountDataCopy = accountData
+ res[address] = accountDataCopy
+ }
+ }
+
+ return res, nil
+}
+
+func (il indexerLedgerForEvalImpl) GetAssetCreator(map[basics.AssetIndex]struct{}) (map[basics.AssetIndex]FoundAddress, error) {
+ // This function is unused.
+ return nil, errors.New("GetAssetCreator() not implemented")
+}
+
+func (il indexerLedgerForEvalImpl) GetAppCreator(map[basics.AppIndex]struct{}) (map[basics.AppIndex]FoundAddress, error) {
+ // This function is unused.
+ return nil, errors.New("GetAppCreator() not implemented")
+}
+
+func (il indexerLedgerForEvalImpl) Totals() (ledgercore.AccountTotals, error) {
+ return il.l.Totals(il.latestRound)
+}
+
+// Test that overriding the consensus parameters effects the generated apply data.
+func TestEvalForIndexerCustomProtocolParams(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ genesisBalances, addrs, _ := newTestGenesis()
+
+ var genHash crypto.Digest
+ crypto.RandBytes(genHash[:])
+ block, err := bookkeeping.MakeGenesisBlock(protocol.ConsensusV24,
+ genesisBalances, "test", genHash)
+
+ dbName := fmt.Sprintf("%s", t.Name())
+ cfg := config.GetDefaultLocal()
+ cfg.Archival = true
+ l, err := OpenLedger(logging.Base(), dbName, true, InitState{
+ Block: block,
+ Accounts: genesisBalances.Balances,
+ GenesisHash: genHash,
+ }, cfg)
+ require.NoError(t, err)
+ defer l.Close()
+
+ const assetid basics.AssetIndex = 1
+ proto := config.Consensus[protocol.ConsensusV24]
+
+ block = bookkeeping.MakeBlock(block.BlockHeader)
+
+ createTxn := txntest.Txn{
+ Type: "acfg",
+ Sender: addrs[0],
+ GenesisHash: block.GenesisHash(),
+ AssetParams: basics.AssetParams{
+ Total: 200,
+ Decimals: 0,
+ Manager: addrs[0],
+ Reserve: addrs[0],
+ Freeze: addrs[0],
+ Clawback: addrs[0],
+ },
+ }
+ createTxn.FillDefaults(proto)
+ createStib, err := block.BlockHeader.EncodeSignedTxn(
+ createTxn.SignedTxn(), transactions.ApplyData{})
+ require.NoError(t, err)
+
+ optInTxn := txntest.Txn{
+ Type: "axfer",
+ Sender: addrs[1],
+ GenesisHash: block.GenesisHash(),
+ XferAsset: assetid,
+ AssetAmount: 0,
+ AssetReceiver: addrs[1],
+ }
+ optInTxn.FillDefaults(proto)
+ optInStib, err := block.BlockHeader.EncodeSignedTxn(
+ optInTxn.SignedTxn(), transactions.ApplyData{})
+ require.NoError(t, err)
+
+ fundTxn := txntest.Txn{
+ Type: "axfer",
+ Sender: addrs[0],
+ GenesisHash: block.GenesisHash(),
+ XferAsset: assetid,
+ AssetAmount: 100,
+ AssetReceiver: addrs[1],
+ }
+ fundTxn.FillDefaults(proto)
+ fundStib, err := block.BlockHeader.EncodeSignedTxn(
+ fundTxn.SignedTxn(), transactions.ApplyData{})
+ require.NoError(t, err)
+
+ optOutTxn := txntest.Txn{
+ Type: "axfer",
+ Sender: addrs[1],
+ GenesisHash: block.GenesisHash(),
+ XferAsset: assetid,
+ AssetAmount: 30,
+ AssetReceiver: addrs[0],
+ AssetCloseTo: addrs[0],
+ }
+ optOutTxn.FillDefaults(proto)
+ optOutStib, err := block.BlockHeader.EncodeSignedTxn(
+ optOutTxn.SignedTxn(), transactions.ApplyData{})
+ require.NoError(t, err)
+
+ block.Payset = []transactions.SignedTxnInBlock{
+ createStib, optInStib, fundStib, optOutStib,
+ }
+
+ il := indexerLedgerForEvalImpl{
+ l: l,
+ latestRound: 0,
+ }
+ proto.EnableAssetCloseAmount = true
+ _, modifiedTxns, err := EvalForIndexer(il, &block, proto)
+ require.NoError(t, err)
+
+ require.Equal(t, 4, len(modifiedTxns))
+ assert.Equal(t, uint64(70), modifiedTxns[3].AssetClosingAmount)
+}
diff --git a/ledger/eval_test.go b/ledger/eval_test.go
index 9d3598250e..6673709acc 100644
--- a/ledger/eval_test.go
+++ b/ledger/eval_test.go
@@ -1677,103 +1677,6 @@ func TestModifiedAppLocalStates(t *testing.T) {
}
}
-// Test that overriding the consensus parameters effects the generated apply data.
-func TestCustomProtocolParams(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- genesisBalances, addrs, _ := newTestGenesis()
-
- var genHash crypto.Digest
- crypto.RandBytes(genHash[:])
- block, err := bookkeeping.MakeGenesisBlock(protocol.ConsensusV24,
- genesisBalances, "test", genHash)
-
- dbName := fmt.Sprintf("%s", t.Name())
- cfg := config.GetDefaultLocal()
- cfg.Archival = true
- l, err := OpenLedger(logging.Base(), dbName, true, InitState{
- Block: block,
- Accounts: genesisBalances.Balances,
- GenesisHash: genHash,
- }, cfg)
- require.NoError(t, err)
- defer l.Close()
-
- const assetid basics.AssetIndex = 1
- proto := config.Consensus[protocol.ConsensusV24]
-
- block = bookkeeping.MakeBlock(block.BlockHeader)
-
- createTxn := txntest.Txn{
- Type: "acfg",
- Sender: addrs[0],
- GenesisHash: block.GenesisHash(),
- AssetParams: basics.AssetParams{
- Total: 200,
- Decimals: 0,
- Manager: addrs[0],
- Reserve: addrs[0],
- Freeze: addrs[0],
- Clawback: addrs[0],
- },
- }
- createTxn.FillDefaults(proto)
- createStib, err := block.BlockHeader.EncodeSignedTxn(
- createTxn.SignedTxn(), transactions.ApplyData{})
- require.NoError(t, err)
-
- optInTxn := txntest.Txn{
- Type: "axfer",
- Sender: addrs[1],
- GenesisHash: block.GenesisHash(),
- XferAsset: assetid,
- AssetAmount: 0,
- AssetReceiver: addrs[1],
- }
- optInTxn.FillDefaults(proto)
- optInStib, err := block.BlockHeader.EncodeSignedTxn(
- optInTxn.SignedTxn(), transactions.ApplyData{})
- require.NoError(t, err)
-
- fundTxn := txntest.Txn{
- Type: "axfer",
- Sender: addrs[0],
- GenesisHash: block.GenesisHash(),
- XferAsset: assetid,
- AssetAmount: 100,
- AssetReceiver: addrs[1],
- }
- fundTxn.FillDefaults(proto)
- fundStib, err := block.BlockHeader.EncodeSignedTxn(
- fundTxn.SignedTxn(), transactions.ApplyData{})
- require.NoError(t, err)
-
- optOutTxn := txntest.Txn{
- Type: "axfer",
- Sender: addrs[1],
- GenesisHash: block.GenesisHash(),
- XferAsset: assetid,
- AssetAmount: 30,
- AssetReceiver: addrs[0],
- AssetCloseTo: addrs[0],
- }
- optOutTxn.FillDefaults(proto)
- optOutStib, err := block.BlockHeader.EncodeSignedTxn(
- optOutTxn.SignedTxn(), transactions.ApplyData{})
- require.NoError(t, err)
-
- block.Payset = []transactions.SignedTxnInBlock{
- createStib, optInStib, fundStib, optOutStib,
- }
-
- proto.EnableAssetCloseAmount = true
- _, modifiedTxns, err := Eval(l, &block, proto)
- require.NoError(t, err)
-
- require.Equal(t, 4, len(modifiedTxns))
- assert.Equal(t, uint64(70), modifiedTxns[3].AssetClosingAmount)
-}
-
// TestAppInsMinBalance checks that accounts with MaxAppsOptedIn are accepted by block evaluator
// and do not cause any MaximumMinimumBalance problems
func TestAppInsMinBalance(t *testing.T) {