summaryrefslogtreecommitdiff
path: root/ledger/internal/eval_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'ledger/internal/eval_test.go')
-rw-r--r--ledger/internal/eval_test.go211
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)