Skip to content

Commit 275e1f3

Browse files
Add indexerLedgerForEval interface (#2897)
Add a new ledger interface for Indexer that is significantly simpler and allows implementing batching in a more straight-forward way. As a result, the Indexer code doesn't need to know the specifics of go-algorand and doesn't need to implement its own accounts cache for batching.
1 parent 4b67562 commit 275e1f3

File tree

4 files changed

+400
-148
lines changed

4 files changed

+400
-148
lines changed

ledger/eval.go

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,54 +1524,3 @@ func (vb ValidatedBlock) WithSeed(s committee.Seed) ValidatedBlock {
15241524
delta: vb.delta,
15251525
}
15261526
}
1527-
1528-
// GetBlockAddresses returns all addresses referenced in `block`.
1529-
func GetBlockAddresses(block *bookkeeping.Block) map[basics.Address]struct{} {
1530-
// Reserve a reasonable memory size for the map.
1531-
res := make(map[basics.Address]struct{}, len(block.Payset)+2)
1532-
res[block.FeeSink] = struct{}{}
1533-
res[block.RewardsPool] = struct{}{}
1534-
1535-
var refAddresses []basics.Address
1536-
for _, stib := range block.Payset {
1537-
getTxnAddresses(&stib.Txn, &refAddresses)
1538-
for _, address := range refAddresses {
1539-
res[address] = struct{}{}
1540-
}
1541-
}
1542-
1543-
return res
1544-
}
1545-
1546-
// Eval evaluates a block without validation using the given `proto`. Return the state
1547-
// delta and transactions with modified apply data according to `proto`.
1548-
// This function is used by Indexer which modifies `proto` to retrieve the asset
1549-
// close amount for each transaction even when the real consensus parameters do not
1550-
// support it.
1551-
func Eval(l ledgerForEvaluator, blk *bookkeeping.Block, proto config.ConsensusParams) (ledgercore.StateDelta, []transactions.SignedTxnInBlock, error) {
1552-
eval, err := startEvaluator(
1553-
l, blk.BlockHeader, proto, len(blk.Payset), false, false)
1554-
if err != nil {
1555-
return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{}, err
1556-
}
1557-
1558-
paysetgroups, err := blk.DecodePaysetGroups()
1559-
if err != nil {
1560-
return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{}, err
1561-
}
1562-
1563-
for _, group := range paysetgroups {
1564-
err = eval.TransactionGroup(group)
1565-
if err != nil {
1566-
return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{}, err
1567-
}
1568-
}
1569-
1570-
// Finally, process any pending end-of-block state changes.
1571-
err = eval.endOfBlock()
1572-
if err != nil {
1573-
return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{}, err
1574-
}
1575-
1576-
return eval.state.deltas(), eval.block.Payset, nil
1577-
}

ledger/evalIndexer.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Copyright (C) 2019-2021 Algorand, Inc.
2+
// This file is part of go-algorand
3+
//
4+
// go-algorand is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as
6+
// published by the Free Software Foundation, either version 3 of the
7+
// License, or (at your option) any later version.
8+
//
9+
// go-algorand is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package ledger
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
23+
"github.com/algorand/go-algorand/config"
24+
"github.com/algorand/go-algorand/crypto"
25+
"github.com/algorand/go-algorand/data/basics"
26+
"github.com/algorand/go-algorand/data/bookkeeping"
27+
"github.com/algorand/go-algorand/data/transactions"
28+
"github.com/algorand/go-algorand/ledger/ledgercore"
29+
)
30+
31+
// FoundAddress is a wrapper for an address and a boolean.
32+
type FoundAddress struct {
33+
Address basics.Address
34+
Exists bool
35+
}
36+
37+
// A ledger interface that Indexer implements. This is a simplified version of the
38+
// ledgerForEvaluator interface. Certain functions that the evaluator doesn't use
39+
// in the trusting mode are excluded, and the present functions only request data
40+
// at the latest round.
41+
type indexerLedgerForEval interface {
42+
LatestBlockHdr() (bookkeeping.BlockHeader, error)
43+
// The value of the returned map is nil iff the account was not found.
44+
LookupWithoutRewards(map[basics.Address]struct{}) (map[basics.Address]*basics.AccountData, error)
45+
GetAssetCreator(map[basics.AssetIndex]struct{}) (map[basics.AssetIndex]FoundAddress, error)
46+
GetAppCreator(map[basics.AppIndex]struct{}) (map[basics.AppIndex]FoundAddress, error)
47+
Totals() (ledgercore.AccountTotals, error)
48+
}
49+
50+
// Converter between indexerLedgerForEval and ledgerForEvaluator interfaces.
51+
type indexerLedgerConnector struct {
52+
il indexerLedgerForEval
53+
genesisHash crypto.Digest
54+
latestRound basics.Round
55+
}
56+
57+
// BlockHdr is part of ledgerForEvaluator interface.
58+
func (l indexerLedgerConnector) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) {
59+
if round != l.latestRound {
60+
return bookkeeping.BlockHeader{}, fmt.Errorf(
61+
"BlockHdr() evaluator called this function for the wrong round %d, "+
62+
"latest round is %d",
63+
round, l.latestRound)
64+
}
65+
return l.il.LatestBlockHdr()
66+
}
67+
68+
// CheckDup is part of ledgerForEvaluator interface.
69+
func (l indexerLedgerConnector) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, TxLease) error {
70+
// This function is not used by evaluator.
71+
return errors.New("CheckDup() not implemented")
72+
}
73+
74+
// LookupWithoutRewards is part of ledgerForEvaluator interface.
75+
func (l indexerLedgerConnector) LookupWithoutRewards(round basics.Round, address basics.Address) (basics.AccountData, basics.Round, error) {
76+
accountDataMap, err :=
77+
l.il.LookupWithoutRewards(map[basics.Address]struct{}{address: {}})
78+
if err != nil {
79+
return basics.AccountData{}, basics.Round(0), err
80+
}
81+
82+
accountData := accountDataMap[address]
83+
if accountData == nil {
84+
return basics.AccountData{}, round, nil
85+
}
86+
return *accountData, round, nil
87+
}
88+
89+
// GetCreatorForRound is part of ledgerForEvaluator interface.
90+
func (l indexerLedgerConnector) GetCreatorForRound(_ basics.Round, cindex basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) {
91+
var foundAddress FoundAddress
92+
93+
switch ctype {
94+
case basics.AssetCreatable:
95+
foundAddresses, err :=
96+
l.il.GetAssetCreator(map[basics.AssetIndex]struct{}{basics.AssetIndex(cindex): {}})
97+
if err != nil {
98+
return basics.Address{}, false, err
99+
}
100+
foundAddress = foundAddresses[basics.AssetIndex(cindex)]
101+
case basics.AppCreatable:
102+
foundAddresses, err :=
103+
l.il.GetAppCreator(map[basics.AppIndex]struct{}{basics.AppIndex(cindex): {}})
104+
if err != nil {
105+
return basics.Address{}, false, err
106+
}
107+
foundAddress = foundAddresses[basics.AppIndex(cindex)]
108+
default:
109+
return basics.Address{}, false, fmt.Errorf("unknown creatable type %v", ctype)
110+
}
111+
112+
return foundAddress.Address, foundAddress.Exists, nil
113+
}
114+
115+
// GenesisHash is part of ledgerForEvaluator interface.
116+
func (l indexerLedgerConnector) GenesisHash() crypto.Digest {
117+
return l.genesisHash
118+
}
119+
120+
// Totals is part of ledgerForEvaluator interface.
121+
func (l indexerLedgerConnector) Totals(round basics.Round) (ledgercore.AccountTotals, error) {
122+
if round != l.latestRound {
123+
return ledgercore.AccountTotals{}, fmt.Errorf(
124+
"Totals() evaluator called this function for the wrong round %d, "+
125+
"latest round is %d",
126+
round, l.latestRound)
127+
}
128+
return l.il.Totals()
129+
}
130+
131+
// CompactCertVoters is part of ledgerForEvaluator interface.
132+
func (l indexerLedgerConnector) CompactCertVoters(_ basics.Round) (*VotersForRound, error) {
133+
// This function is not used by evaluator.
134+
return nil, errors.New("CompactCertVoters() not implemented")
135+
}
136+
137+
func makeIndexerLedgerConnector(il indexerLedgerForEval, genesisHash crypto.Digest, latestRound basics.Round) indexerLedgerConnector {
138+
return indexerLedgerConnector{
139+
il: il,
140+
genesisHash: genesisHash,
141+
latestRound: latestRound,
142+
}
143+
}
144+
145+
// Returns all addresses referenced in `block`.
146+
func getBlockAddresses(block *bookkeeping.Block) map[basics.Address]struct{} {
147+
// Reserve a reasonable memory size for the map.
148+
res := make(map[basics.Address]struct{}, len(block.Payset)+2)
149+
res[block.FeeSink] = struct{}{}
150+
res[block.RewardsPool] = struct{}{}
151+
152+
var refAddresses []basics.Address
153+
for _, stib := range block.Payset {
154+
getTxnAddresses(&stib.Txn, &refAddresses)
155+
for _, address := range refAddresses {
156+
res[address] = struct{}{}
157+
}
158+
}
159+
160+
return res
161+
}
162+
163+
// EvalForIndexer evaluates a block without validation using the given `proto`.
164+
// Return the state delta and transactions with modified apply data according to `proto`.
165+
// This function is used by Indexer which modifies `proto` to retrieve the asset
166+
// close amount for each transaction even when the real consensus parameters do not
167+
// support it.
168+
func EvalForIndexer(il indexerLedgerForEval, block *bookkeeping.Block, proto config.ConsensusParams) (ledgercore.StateDelta, []transactions.SignedTxnInBlock, error) {
169+
ilc := makeIndexerLedgerConnector(il, block.GenesisHash(), block.Round()-1)
170+
171+
eval, err := startEvaluator(
172+
ilc, block.BlockHeader, proto, len(block.Payset), false, false)
173+
if err != nil {
174+
return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
175+
fmt.Errorf("EvalForIndexer() err: %w", err)
176+
}
177+
178+
// Preload most needed accounts.
179+
{
180+
accountDataMap, err := il.LookupWithoutRewards(getBlockAddresses(block))
181+
if err != nil {
182+
return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
183+
fmt.Errorf("EvalForIndexer() err: %w", err)
184+
}
185+
base := eval.state.lookupParent.(*roundCowBase)
186+
for address, accountData := range accountDataMap {
187+
if accountData == nil {
188+
base.accounts[address] = basics.AccountData{}
189+
} else {
190+
base.accounts[address] = *accountData
191+
}
192+
}
193+
}
194+
195+
paysetgroups, err := block.DecodePaysetGroups()
196+
if err != nil {
197+
return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
198+
fmt.Errorf("EvalForIndexer() err: %w", err)
199+
}
200+
201+
for _, group := range paysetgroups {
202+
err = eval.TransactionGroup(group)
203+
if err != nil {
204+
return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
205+
fmt.Errorf("EvalForIndexer() err: %w", err)
206+
}
207+
}
208+
209+
// Finally, process any pending end-of-block state changes.
210+
err = eval.endOfBlock()
211+
if err != nil {
212+
return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{},
213+
fmt.Errorf("EvalForIndexer() err: %w", err)
214+
}
215+
216+
return eval.state.deltas(), eval.block.Payset, nil
217+
}

0 commit comments

Comments
 (0)