diff options
Diffstat (limited to 'ledger/internal/eval_test.go')
-rw-r--r-- | ledger/internal/eval_test.go | 211 |
1 files changed, 121 insertions, 90 deletions
diff --git a/ledger/internal/eval_test.go b/ledger/internal/eval_test.go index d8e7cd5fa..9e102e02f 100644 --- a/ledger/internal/eval_test.go +++ b/ledger/internal/eval_test.go @@ -22,7 +22,6 @@ import ( "errors" "fmt" "math/rand" - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -35,8 +34,8 @@ 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/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/verify" - "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/ledger/ledgercore" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/protocol" @@ -73,103 +72,128 @@ func TestBlockEvaluatorFeeSink(t *testing.T) { require.Equal(t, eval.specials.FeeSink, testSinkAddr) } -func TestPrepareEvalParams(t *testing.T) { - partitiontest.PartitionTest(t) +func testEvalAppGroup(t *testing.T, schema basics.StateSchema) (*BlockEvaluator, basics.Address, error) { + genesisInitState, addrs, keys := ledgertesting.Genesis(10) - eval := BlockEvaluator{ - prevHeader: bookkeeping.BlockHeader{ - TimeStamp: 1234, - Round: 2345, - }, + genesisBalances := bookkeeping.GenesisBalances{ + Balances: genesisInitState.Accounts, + FeeSink: testSinkAddr, + RewardsPool: testPoolAddr, + Timestamp: 0, } + l := newTestLedger(t, genesisBalances) - params := []config.ConsensusParams{ - {Application: true, MaxAppProgramCost: 700}, - config.Consensus[protocol.ConsensusV29], - config.Consensus[protocol.ConsensusFuture], - } + blkHeader, err := l.BlockHdr(basics.Round(0)) + require.NoError(t, err) + newBlock := bookkeeping.MakeBlock(blkHeader) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + require.NoError(t, err) + eval.validate = true + eval.generate = false - // Create some sample transactions - payment := txntest.Txn{ - Type: protocol.PaymentTx, - Sender: basics.Address{1, 2, 3, 4}, - Receiver: basics.Address{4, 3, 2, 1}, - Amount: 100, - }.SignedTxnWithAD() - - appcall1 := txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: basics.Address{1, 2, 3, 4}, - ApplicationID: basics.AppIndex(1), - }.SignedTxnWithAD() - - appcall2 := appcall1 - appcall2.SignedTxn.Txn.ApplicationCallTxnFields.ApplicationID = basics.AppIndex(2) - - type evalTestCase struct { - group []transactions.SignedTxnWithAD - - // indicates if prepareAppEvaluators should return a non-nil - // appTealEvaluator for the txn at index i - expected []bool - - numAppCalls int - // Used for checking transitive pointer equality in app calls - // If there are no app calls in the group, it is set to -1 - firstAppCallIndex int + ops, err := logic.AssembleString(`#pragma version 2 + txn ApplicationID + bz create + byte "caller" + txn Sender + app_global_put + b ok +create: + byte "creator" + txn Sender + app_global_put +ok: + int 1`) + require.NoError(t, err, ops.Errors) + approval := ops.Program + ops, err = logic.AssembleString("#pragma version 2\nint 1") + require.NoError(t, err) + clear := ops.Program + + genHash := l.GenesisHash() + header := transactions.Header{ + Sender: addrs[0], + Fee: minFee, + FirstValid: newBlock.Round(), + LastValid: newBlock.Round(), + GenesisHash: genHash, + } + appcall1 := transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: header, + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + GlobalStateSchema: schema, + ApprovalProgram: approval, + ClearStateProgram: clear, + }, } - // Create some groups with these transactions - cases := []evalTestCase{ - {[]transactions.SignedTxnWithAD{payment}, []bool{false}, 0, -1}, - {[]transactions.SignedTxnWithAD{appcall1}, []bool{true}, 1, 0}, - {[]transactions.SignedTxnWithAD{payment, payment}, []bool{false, false}, 0, -1}, - {[]transactions.SignedTxnWithAD{appcall1, payment}, []bool{true, false}, 1, 0}, - {[]transactions.SignedTxnWithAD{payment, appcall1}, []bool{false, true}, 1, 1}, - {[]transactions.SignedTxnWithAD{appcall1, appcall2}, []bool{true, true}, 2, 0}, - {[]transactions.SignedTxnWithAD{appcall1, appcall2, appcall1}, []bool{true, true, true}, 3, 0}, - {[]transactions.SignedTxnWithAD{payment, appcall1, payment}, []bool{false, true, false}, 1, 1}, - {[]transactions.SignedTxnWithAD{appcall1, payment, appcall2}, []bool{true, false, true}, 2, 0}, + appcall2 := transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: header, + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: 1, + }, } - for i, param := range params { - for j, testCase := range cases { - t.Run(fmt.Sprintf("i=%d,j=%d", i, j), func(t *testing.T) { - eval.proto = param - res := eval.prepareEvalParams(testCase.group) - require.Equal(t, len(res), len(testCase.group)) - - // Compute the expected transaction group without ApplyData for - // the test case - expGroupNoAD := make([]transactions.SignedTxn, len(testCase.group)) - for k := range testCase.group { - expGroupNoAD[k] = testCase.group[k].SignedTxn - } - - // Ensure non app calls have a nil evaluator, and that non-nil - // evaluators point to the right transactions and values - for k, present := range testCase.expected { - if present { - require.NotNil(t, res[k]) - require.NotNil(t, res[k].PastSideEffects) - require.Equal(t, res[k].GroupIndex, uint64(k)) - require.Equal(t, res[k].TxnGroup, expGroupNoAD) - require.Equal(t, *res[k].Proto, eval.proto) - require.Equal(t, *res[k].Txn, testCase.group[k].SignedTxn) - require.Equal(t, res[k].MinTealVersion, res[testCase.firstAppCallIndex].MinTealVersion) - require.Equal(t, res[k].PooledApplicationBudget, res[testCase.firstAppCallIndex].PooledApplicationBudget) - if reflect.DeepEqual(param, config.Consensus[protocol.ConsensusV29]) { - require.Equal(t, *res[k].PooledApplicationBudget, uint64(eval.proto.MaxAppProgramCost)) - } else if reflect.DeepEqual(param, config.Consensus[protocol.ConsensusFuture]) { - require.Equal(t, *res[k].PooledApplicationBudget, uint64(eval.proto.MaxAppProgramCost*testCase.numAppCalls)) - } - } else { - require.Nil(t, res[k]) - } - } - }) - } + var group transactions.TxGroup + group.TxGroupHashes = []crypto.Digest{crypto.HashObj(appcall1), crypto.HashObj(appcall2)} + appcall1.Group = crypto.HashObj(group) + appcall2.Group = crypto.HashObj(group) + stxn1 := appcall1.Sign(keys[0]) + stxn2 := appcall2.Sign(keys[0]) + + g := []transactions.SignedTxnWithAD{ + { + SignedTxn: stxn1, + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{GlobalDelta: map[string]basics.ValueDelta{ + "creator": {Action: basics.SetBytesAction, Bytes: string(addrs[0][:])}}, + }, + ApplicationID: 1, + }, + }, + { + SignedTxn: stxn2, + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{GlobalDelta: map[string]basics.ValueDelta{ + "caller": {Action: basics.SetBytesAction, Bytes: string(addrs[0][:])}}, + }}, + }, + } + txgroup := []transactions.SignedTxn{stxn1, stxn2} + err = eval.TestTransactionGroup(txgroup) + if err != nil { + return eval, addrs[0], err } + err = eval.TransactionGroup(g) + return eval, addrs[0], err +} + +// TestEvalAppStateCountsWithTxnGroup ensures txns in a group can't violate app state schema limits +// the test ensures that +// commitToParent -> applyChild copies child's cow state usage counts into parent +// and the usage counts correctly propagated from parent cow to child cow and back +func TestEvalAppStateCountsWithTxnGroup(t *testing.T) { + partitiontest.PartitionTest(t) + + _, _, err := testEvalAppGroup(t, basics.StateSchema{NumByteSlice: 1}) + require.Error(t, err) + require.Contains(t, err.Error(), "store bytes count 2 exceeds schema bytes count 1") +} + +// TestEvalAppAllocStateWithTxnGroup ensures roundCowState.deltas and applyStorageDelta +// produce correct results when a txn group has storage allocate and storage update actions +func TestEvalAppAllocStateWithTxnGroup(t *testing.T) { + partitiontest.PartitionTest(t) + + eval, addr, err := testEvalAppGroup(t, basics.StateSchema{NumByteSlice: 2}) + require.NoError(t, err) + deltas := eval.state.deltas() + ad, _ := deltas.Accts.Get(addr) + state := ad.AppParams[1].GlobalState + require.Equal(t, basics.TealValue{Type: basics.TealBytesType, Bytes: string(addr[:])}, state["caller"]) + require.Equal(t, basics.TealValue{Type: basics.TealBytesType, Bytes: string(addr[:])}, state["creator"]) } func TestCowCompactCert(t *testing.T) { @@ -408,6 +432,7 @@ type evalTestLedger struct { blocks map[basics.Round]bookkeeping.Block roundBalances map[basics.Round]map[basics.Address]basics.AccountData genesisHash crypto.Digest + genesisProto config.ConsensusParams feeSink basics.Address rewardsPool basics.Address latestTotals ledgercore.AccountTotals @@ -436,6 +461,7 @@ func newTestLedger(t testing.TB, balances bookkeeping.GenesisBalances) *evalTest for _, acctData := range balances.Balances { l.latestTotals.AddAccount(proto, acctData, &ot) } + l.genesisProto = proto require.False(t, genBlock.FeeSink.IsZero()) require.False(t, genBlock.RewardsPool.IsZero()) @@ -504,6 +530,11 @@ func (ledger *evalTestLedger) GenesisHash() crypto.Digest { return ledger.genesisHash } +// GenesisProto returns the genesis hash for this ledger. +func (ledger *evalTestLedger) GenesisProto() config.ConsensusParams { + return ledger.genesisProto +} + // Latest returns the latest known block round added to the ledger. func (ledger *evalTestLedger) Latest() basics.Round { return basics.Round(len(ledger.blocks)).SubSaturate(1) |