|
| 1 | +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. |
| 2 | +// See the file LICENSE for licensing terms. |
| 3 | + |
| 4 | +package blockstest |
| 5 | + |
| 6 | +import ( |
| 7 | + "math/big" |
| 8 | + "testing" |
| 9 | + |
| 10 | + "github.com/ava-labs/libevm/common" |
| 11 | + "github.com/ava-labs/libevm/consensus" |
| 12 | + "github.com/ava-labs/libevm/core" |
| 13 | + "github.com/ava-labs/libevm/core/rawdb" |
| 14 | + "github.com/ava-labs/libevm/core/state" |
| 15 | + "github.com/ava-labs/libevm/core/types" |
| 16 | + "github.com/ava-labs/libevm/core/vm" |
| 17 | + "github.com/ava-labs/libevm/params" |
| 18 | + "github.com/holiman/uint256" |
| 19 | + "github.com/stretchr/testify/assert" |
| 20 | + "github.com/stretchr/testify/require" |
| 21 | + |
| 22 | + "github.com/ava-labs/strevm/saetest" |
| 23 | +) |
| 24 | + |
| 25 | +func TestIntegration(t *testing.T) { |
| 26 | + const ( |
| 27 | + numAccounts = 2 |
| 28 | + numBlocks = 3 |
| 29 | + txsPerAccountPerBlock = 3 |
| 30 | + ) |
| 31 | + |
| 32 | + config := params.AllDevChainProtocolChanges |
| 33 | + wallet := saetest.NewUNSAFEWallet(t, numAccounts, types.LatestSigner(config)) |
| 34 | + alloc := saetest.MaxAllocFor(wallet.Addresses()...) |
| 35 | + |
| 36 | + db := rawdb.NewMemoryDatabase() |
| 37 | + |
| 38 | + // Although the point of SAE is to replace [core.BlockChain], it remains the |
| 39 | + // ground truth for correct block and transaction processing. If we were to |
| 40 | + // test [ChainBuilder] with an SAE executor, which is itself going to be |
| 41 | + // tested with the builder, then we would have a circular argument for |
| 42 | + // correctness. |
| 43 | + bc, err := core.NewBlockChain( |
| 44 | + db, nil, |
| 45 | + &core.Genesis{ |
| 46 | + Config: config, |
| 47 | + Alloc: alloc, |
| 48 | + }, |
| 49 | + nil, engine{}, vm.Config{}, |
| 50 | + func(*types.Header) bool { return true }, |
| 51 | + nil, |
| 52 | + ) |
| 53 | + require.NoError(t, err, "core.NewBlockChain()") |
| 54 | + stateProc := core.NewStateProcessor(config, bc, engine{}) |
| 55 | + |
| 56 | + sdb, err := state.New(bc.Genesis().Root(), state.NewDatabase(db), nil) |
| 57 | + require.NoError(t, err, "state.New(%T.Genesis().Root())", bc) |
| 58 | + |
| 59 | + build := NewChainBuilder(NewBlock(t, bc.Genesis(), nil, nil)) |
| 60 | + dest := common.Address{'d', 'e', 's', 't'} |
| 61 | + for i := range numBlocks { |
| 62 | + // Genesis is block 0 |
| 63 | + blockNum := uint64(i + 1) //nolint:gosec // Known to not overflow |
| 64 | + |
| 65 | + var txs types.Transactions |
| 66 | + for range txsPerAccountPerBlock { |
| 67 | + for i := range numAccounts { |
| 68 | + tx := wallet.SetNonceAndSign(t, i, &types.LegacyTx{ |
| 69 | + To: &dest, |
| 70 | + Value: big.NewInt(1), |
| 71 | + Gas: params.TxGas, |
| 72 | + GasPrice: big.NewInt(1), |
| 73 | + }) |
| 74 | + txs = append(txs, tx) |
| 75 | + } |
| 76 | + } |
| 77 | + b := build.NewBlock(t, txs, ModifyHeader(func(h *types.Header) { |
| 78 | + h.GasLimit = 100e6 |
| 79 | + })) |
| 80 | + |
| 81 | + receipts, _, _, err := stateProc.Process(b.EthBlock(), sdb, *bc.GetVMConfig()) |
| 82 | + require.NoError(t, err, "%T.Process(%T.NewBlock().EthBlock()...)", stateProc, build) |
| 83 | + for _, r := range receipts { |
| 84 | + assert.Equal(t, types.ReceiptStatusSuccessful, r.Status, "%T.Status", r) |
| 85 | + assert.Equal(t, blockNum, r.BlockNumber.Uint64(), "%T.BlockNumber", r) |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + t.Run("balance_of_recipient", func(t *testing.T) { |
| 90 | + bal := sdb.GetBalance(dest) |
| 91 | + require.True(t, bal.IsUint64(), "%T.GetBalance(...).IsUint64()", sdb) |
| 92 | + require.Equal(t, uint64(numAccounts*numBlocks*txsPerAccountPerBlock), bal.Uint64()) |
| 93 | + }) |
| 94 | +} |
| 95 | + |
| 96 | +// engine is a fake [consensus.Engine], implementing the minimum number of |
| 97 | +// methods to avoid a panic. |
| 98 | +type engine struct { |
| 99 | + consensus.Engine |
| 100 | +} |
| 101 | + |
| 102 | +func (engine) VerifyHeader(consensus.ChainHeaderReader, *types.Header) error { |
| 103 | + return nil |
| 104 | +} |
| 105 | + |
| 106 | +func (engine) Author(*types.Header) (common.Address, error) { |
| 107 | + return common.Address{'a', 'u', 't', 'h'}, nil |
| 108 | +} |
| 109 | + |
| 110 | +func (engine) Finalize(consensus.ChainHeaderReader, *types.Header, *state.StateDB, []*types.Transaction, []*types.Header, []*types.Withdrawal) { |
| 111 | +} |
| 112 | + |
| 113 | +func TestNewGenesis(t *testing.T) { |
| 114 | + config := params.AllDevChainProtocolChanges |
| 115 | + signer := types.LatestSigner(config) |
| 116 | + wallet := saetest.NewUNSAFEWallet(t, 10, signer) |
| 117 | + alloc := saetest.MaxAllocFor(wallet.Addresses()...) |
| 118 | + |
| 119 | + db := rawdb.NewMemoryDatabase() |
| 120 | + gen := NewGenesis(t, db, config, alloc) |
| 121 | + |
| 122 | + assert.True(t, gen.Executed(), "genesis.Executed()") |
| 123 | + assert.NoError(t, gen.WaitUntilSettled(t.Context()), "genesis.WaitUntilSettled()") |
| 124 | + assert.Equal(t, gen.Hash(), gen.LastSettled().Hash(), "genesis.LastSettled().Hash() is self") |
| 125 | + |
| 126 | + t.Run("alloc", func(t *testing.T) { |
| 127 | + sdb, err := state.New(gen.SettledStateRoot(), state.NewDatabase(db), nil) |
| 128 | + require.NoError(t, err, "state.New(genesis.SettledStateRoot())") |
| 129 | + for i, addr := range wallet.Addresses() { |
| 130 | + want := new(uint256.Int).SetAllOne() |
| 131 | + assert.Truef(t, sdb.GetBalance(addr).Eq(want), "%T.GetBalance(%T.Addresses()[%d]) is max uint256", sdb, wallet, i) |
| 132 | + } |
| 133 | + }) |
| 134 | +} |
0 commit comments