diff --git a/engine/execution/state/bootstrap/bootstrap_test.go b/engine/execution/state/bootstrap/bootstrap_test.go index f2ec6d76b67..6fa5ef66470 100644 --- a/engine/execution/state/bootstrap/bootstrap_test.go +++ b/engine/execution/state/bootstrap/bootstrap_test.go @@ -53,7 +53,7 @@ func TestBootstrapLedger(t *testing.T) { } func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) { - expectedStateCommitmentBytes, _ := hex.DecodeString("1383e01cdba9bb1df08d413f89c3a252f1081f2ebb0c58850676194a01cfc4c4") + expectedStateCommitmentBytes, _ := hex.DecodeString("e8b4b48a5c4eb510e28ecc9623271d53ef9915c98d333939b448516fa25e5a8f") expectedStateCommitment, err := flow.ToStateCommitment(expectedStateCommitmentBytes) require.NoError(t, err) diff --git a/fvm/evm/events/events.go b/fvm/evm/events/events.go index b59f34bd41c..edda7c78253 100644 --- a/fvm/evm/events/events.go +++ b/fvm/evm/events/events.go @@ -127,6 +127,7 @@ func (p *blockEvent) ToCadence(chainID flow.ChainID) (cadence.Event, error) { hashToCadenceArrayValue(p.ParentBlockHash), hashToCadenceArrayValue(p.ReceiptRoot), hashToCadenceArrayValue(p.TransactionHashRoot), + hashToCadenceArrayValue(p.PrevRandao), }).WithType(eventType), nil } @@ -139,6 +140,7 @@ type BlockEventPayload struct { ParentBlockHash gethCommon.Hash `cadence:"parentHash"` ReceiptRoot gethCommon.Hash `cadence:"receiptRoot"` TransactionHashRoot gethCommon.Hash `cadence:"transactionHashRoot"` + PrevRandao gethCommon.Hash `cadence:"prevrandao"` } // DecodeBlockEventPayload decodes Cadence event into block event payload. diff --git a/fvm/evm/events/events_test.go b/fvm/evm/events/events_test.go index e30819ca31e..4b5d3420f6b 100644 --- a/fvm/evm/events/events_test.go +++ b/fvm/evm/events/events_test.go @@ -34,6 +34,7 @@ func TestEVMBlockExecutedEventCCFEncodingDecoding(t *testing.T) { ReceiptRoot: gethCommon.Hash{}, TotalGasUsed: 15, TransactionHashRoot: gethCommon.HexToHash("0x70b67ce6710355acf8d69b2ea013d34e212bc4824926c5d26f189c1ca9667246"), + PrevRandao: testutils.RandomCommonHash(t), } event := events.NewBlockEvent(block) @@ -55,6 +56,7 @@ func TestEVMBlockExecutedEventCCFEncodingDecoding(t *testing.T) { assert.Equal(t, bep.ParentBlockHash, block.ParentBlockHash) assert.Equal(t, bep.ReceiptRoot, block.ReceiptRoot) assert.Equal(t, bep.TransactionHashRoot, block.TransactionHashRoot) + assert.Equal(t, bep.PrevRandao, block.PrevRandao) v, err := ccf.Encode(ev) require.NoError(t, err) diff --git a/fvm/evm/handler/blockstore.go b/fvm/evm/handler/blockstore.go index c1dcf77cb49..a8c5975f193 100644 --- a/fvm/evm/handler/blockstore.go +++ b/fvm/evm/handler/blockstore.go @@ -47,7 +47,19 @@ func (bs *BlockStore) BlockProposal() (*types.BlockProposal, error) { if len(data) != 0 { return types.NewBlockProposalFromBytes(data) } + bp, err := bs.constructBlockProposal() + if err != nil { + return nil, err + } + // store block proposal + err = bs.UpdateBlockProposal(bp) + if err != nil { + return nil, err + } + return bp, nil +} +func (bs *BlockStore) constructBlockProposal() (*types.BlockProposal, error) { // if available construct a new one cadenceHeight, err := bs.backend.GetCurrentBlockHeight() if err != nil { @@ -76,12 +88,21 @@ func (bs *BlockStore) BlockProposal() (*types.BlockProposal, error) { // expect timestamps in unix seconds so we convert here timestamp := uint64(cadenceBlock.Timestamp / int64(time.Second)) + // read a random value for block proposal + prevrandao := gethCommon.Hash{} + err = bs.backend.ReadRandom(prevrandao[:]) + if err != nil { + return nil, err + } + blockProposal := types.NewBlockProposal( parentHash, lastExecutedBlock.Height+1, timestamp, lastExecutedBlock.TotalSupply, + prevrandao, ) + return blockProposal, nil } @@ -99,14 +120,6 @@ func (bs *BlockStore) UpdateBlockProposal(bp *types.BlockProposal) error { ) } -func (bs *BlockStore) ResetBlockProposal() error { - return bs.backend.SetValue( - bs.rootAddress[:], - []byte(BlockStoreLatestBlockProposalKey), - nil, - ) -} - // CommitBlockProposal commits the block proposal to the chain func (bs *BlockStore) CommitBlockProposal(bp *types.BlockProposal) error { bp.PopulateRoots() @@ -135,7 +148,12 @@ func (bs *BlockStore) CommitBlockProposal(bp *types.BlockProposal) error { return err } - err = bs.ResetBlockProposal() + // construct a new block proposal and store + newBP, err := bs.constructBlockProposal() + if err != nil { + return err + } + err = bs.UpdateBlockProposal(newBP) if err != nil { return err } diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index dcc86ea56b7..e6c86e16a2e 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -404,7 +404,10 @@ func (h *ContractHandler) run( } // step 9 - emit transaction event - err = h.emitEvent(events.NewTransactionEvent(res, rlpEncodedTx, bp.Height)) + err = h.emitEvent( + events.NewTransactionEvent(res, rlpEncodedTx, bp.Height), + ) + if err != nil { return nil, err } @@ -516,11 +519,6 @@ func (h *ContractHandler) getBlockContext() (types.BlockContext, error) { if err != nil { return types.BlockContext{}, err } - rand := gethCommon.Hash{} - err = h.backend.ReadRandom(rand[:]) - if err != nil { - return types.BlockContext{}, err - } return types.BlockContext{ ChainID: types.EVMChainIDFromFlowChainID(h.flowChainID), @@ -533,7 +531,7 @@ func (h *ContractHandler) getBlockContext() (types.BlockContext, error) { return hash }, ExtraPrecompiledContracts: h.precompiledContracts, - Random: rand, + Random: bp.PrevRandao, Tracer: h.tracer.TxTracer(), TxCountSoFar: uint(len(bp.TxHashes)), TotalGasUsedSoFar: bp.TotalGasUsed, diff --git a/fvm/evm/stdlib/contract.cdc b/fvm/evm/stdlib/contract.cdc index e5a075fcc13..21ed5a0af78 100644 --- a/fvm/evm/stdlib/contract.cdc +++ b/fvm/evm/stdlib/contract.cdc @@ -34,6 +34,8 @@ contract EVM { receiptRoot: [UInt8; 32], // root hash of all the transaction hashes transactionHashRoot: [UInt8; 32], + /// value returned for PREVRANDAO opcode + prevrandao: [UInt8; 32], ) /// Transaction executed event is emitted every time a transaction diff --git a/fvm/evm/types/block.go b/fvm/evm/types/block.go index bd2ab4e8408..458e2961bc3 100644 --- a/fvm/evm/types/block.go +++ b/fvm/evm/types/block.go @@ -44,8 +44,11 @@ type Block struct { // values as node values. Proofs are still compatible but might require an extra hashing step. TransactionHashRoot gethCommon.Hash - // stores gas used by all transactions included in the block. + // TotalGasUsed stores gas used by all transactions included in the block. TotalGasUsed uint64 + + // PrevRandao is the value returned for block.prevrandao opcode + PrevRandao gethCommon.Hash } // ToBytes encodes the block into bytes @@ -65,6 +68,7 @@ func NewBlock( height uint64, timestamp uint64, totalSupply *big.Int, + prevRandao gethCommon.Hash, ) *Block { return &Block{ ParentBlockHash: parentBlockHash, @@ -73,13 +77,21 @@ func NewBlock( TotalSupply: totalSupply, ReceiptRoot: gethTypes.EmptyReceiptsHash, TransactionHashRoot: gethTypes.EmptyRootHash, + PrevRandao: prevRandao, } } // NewBlockFromBytes constructs a new block from encoded data func NewBlockFromBytes(encoded []byte) (*Block, error) { res := &Block{} - return res, gethRLP.DecodeBytes(encoded, res) + err := gethRLP.DecodeBytes(encoded, res) + if err != nil { + res = decodeBlockBreakingChanges(encoded) + if res == nil { + return nil, err + } + } + return res, nil } // GenesisTimestamp returns the block time stamp for EVM genesis block @@ -104,6 +116,7 @@ func GenesisBlock(chainID flow.ChainID) *Block { ReceiptRoot: gethTypes.EmptyRootHash, TransactionHashRoot: gethTypes.EmptyRootHash, TotalGasUsed: 0, + PrevRandao: gethCommon.Hash{}, } } @@ -182,7 +195,14 @@ func (b *BlockProposal) ToBytes() ([]byte, error) { // NewBlockProposalFromBytes constructs a new block proposal from encoded data func NewBlockProposalFromBytes(encoded []byte) (*BlockProposal, error) { res := &BlockProposal{} - return res, gethRLP.DecodeBytes(encoded, res) + err := gethRLP.DecodeBytes(encoded, res) + if err != nil { + res = decodeBlockProposalBreakingChanges(encoded) + if res == nil { + return nil, err + } + } + return res, nil } func NewBlockProposal( @@ -190,6 +210,7 @@ func NewBlockProposal( height uint64, timestamp uint64, totalSupply *big.Int, + prevRandao gethCommon.Hash, ) *BlockProposal { return &BlockProposal{ Block: Block{ @@ -198,6 +219,7 @@ func NewBlockProposal( Timestamp: timestamp, TotalSupply: totalSupply, ReceiptRoot: gethTypes.EmptyRootHash, + PrevRandao: prevRandao, }, Receipts: make([]LightReceipt, 0), TxHashes: make([]gethCommon.Hash, 0), @@ -217,3 +239,70 @@ func (t TransactionHashes) EncodeIndex(index int, buffer *bytes.Buffer) { func (t TransactionHashes) RootHash() gethCommon.Hash { return gethTypes.DeriveSha(t, gethTrie.NewStackTrie(nil)) } + +// Below block type section, defines earlier block types, +// this is being used to decode blocks that were stored +// before block type changes. It allows us to still decode +// a block that would otherwise be invalid if decoded into +// latest version of the above Block type. + +// before adding PrevRandao to the block +type BlockV0 struct { + ParentBlockHash gethCommon.Hash + Height uint64 + Timestamp uint64 + TotalSupply *big.Int + ReceiptRoot gethCommon.Hash + TransactionHashRoot gethCommon.Hash + TotalGasUsed uint64 +} + +type BlockProposalV0 struct { + BlockV0 + Receipts []LightReceipt + TxHashes TransactionHashes +} + +// decodeBlockBreakingChanges will try to decode the bytes into all +// previous versions of block type, if it succeeds it will return the +// migrated block, otherwise it will return nil. +func decodeBlockBreakingChanges(encoded []byte) *Block { + b0 := &BlockV0{} + if err := gethRLP.DecodeBytes(encoded, b0); err == nil { + return &Block{ + ParentBlockHash: b0.ParentBlockHash, + Height: b0.Height, + Timestamp: b0.Timestamp, + TotalSupply: b0.TotalSupply, + ReceiptRoot: b0.ReceiptRoot, + TransactionHashRoot: b0.TransactionHashRoot, + TotalGasUsed: b0.TotalGasUsed, + PrevRandao: gethCommon.Hash{}, + } + } + return nil +} + +// decodeBlockProposalBreakingChanges will try to decode the bytes into all +// previous versions of block proposal type, if it succeeds it will return the +// migrated block, otherwise it will return nil. +func decodeBlockProposalBreakingChanges(encoded []byte) *BlockProposal { + bp0 := &BlockProposalV0{} + if err := gethRLP.DecodeBytes(encoded, bp0); err == nil { + return &BlockProposal{ + Block: Block{ + ParentBlockHash: bp0.ParentBlockHash, + Height: bp0.Height, + Timestamp: bp0.Timestamp, + TotalSupply: bp0.TotalSupply, + ReceiptRoot: bp0.ReceiptRoot, + TransactionHashRoot: bp0.TransactionHashRoot, + TotalGasUsed: bp0.TotalGasUsed, + PrevRandao: gethCommon.Hash{}, + }, + Receipts: bp0.Receipts, + TxHashes: bp0.TxHashes, + } + } + return nil +} diff --git a/fvm/evm/types/block_test.go b/fvm/evm/types/block_test.go index aa9ee1d260c..2ccb0ddcd65 100644 --- a/fvm/evm/types/block_test.go +++ b/fvm/evm/types/block_test.go @@ -6,6 +6,7 @@ import ( gethCommon "github.com/onflow/go-ethereum/common" gethTypes "github.com/onflow/go-ethereum/core/types" + gethRLP "github.com/onflow/go-ethereum/rlp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -53,7 +54,7 @@ func Test_BlockHash(t *testing.T) { } func Test_BlockProposal(t *testing.T) { - bp := NewBlockProposal(gethCommon.Hash{1}, 1, 0, nil) + bp := NewBlockProposal(gethCommon.Hash{1}, 1, 0, nil, gethCommon.Hash{1, 2, 3}) bp.AppendTransaction(nil) require.Empty(t, bp.TxHashes) @@ -76,3 +77,52 @@ func Test_BlockProposal(t *testing.T) { bp.PopulateRoots() require.NotEqual(t, gethTypes.EmptyReceiptsHash, bp.ReceiptRoot) } + +func Test_DecodeHistoricBlocks(t *testing.T) { + bv0 := BlockV0{ + ParentBlockHash: gethCommon.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + Height: 1, + Timestamp: 2, + TotalSupply: big.NewInt(3), + ReceiptRoot: gethCommon.Hash{0x04}, + TransactionHashRoot: gethCommon.Hash{0x05}, + TotalGasUsed: 0, + } + b0, err := gethRLP.EncodeToBytes(bv0) + require.NoError(t, err) + + b := decodeBlockBreakingChanges(b0) + require.Equal(t, b.ParentBlockHash, bv0.ParentBlockHash) + require.Equal(t, b.Height, bv0.Height) + require.Equal(t, b.Timestamp, bv0.Timestamp) + require.Equal(t, b.TotalSupply.Uint64(), bv0.TotalSupply.Uint64()) + require.Equal(t, b.ReceiptRoot, bv0.ReceiptRoot) + require.Equal(t, b.TransactionHashRoot, bv0.TransactionHashRoot) + require.Equal(t, b.TotalGasUsed, bv0.TotalGasUsed) + require.Empty(t, b.PrevRandao) + + bpv0 := BlockProposalV0{ + BlockV0: bv0, + Receipts: []LightReceipt{ + {CumulativeGasUsed: 10}, + {CumulativeGasUsed: 2}, + }, + TxHashes: []gethCommon.Hash{{1, 2}, {3, 4}, {5, 6}}, + } + + bp0, err := gethRLP.EncodeToBytes(bpv0) + require.NoError(t, err) + + bp, err := NewBlockProposalFromBytes(bp0) + require.NoError(t, err) + require.Equal(t, bp.ParentBlockHash, bpv0.ParentBlockHash) + require.Equal(t, bp.Height, bpv0.Height) + require.Equal(t, bp.Timestamp, bpv0.Timestamp) + require.Equal(t, bp.TotalSupply.Uint64(), bpv0.TotalSupply.Uint64()) + require.Equal(t, bp.ReceiptRoot, bpv0.ReceiptRoot) + require.Equal(t, bp.TransactionHashRoot, bpv0.TransactionHashRoot) + require.Equal(t, bp.TotalGasUsed, bpv0.TotalGasUsed) + require.Empty(t, bp.PrevRandao) + require.Len(t, bp.Receipts, 2) + require.Len(t, bp.TxHashes, 3) +} diff --git a/utils/unittest/execution_state.go b/utils/unittest/execution_state.go index ea7278b1e6e..ee6da7b082d 100644 --- a/utils/unittest/execution_state.go +++ b/utils/unittest/execution_state.go @@ -23,7 +23,7 @@ const ServiceAccountPrivateKeySignAlgo = crypto.ECDSAP256 const ServiceAccountPrivateKeyHashAlgo = hash.SHA2_256 // Pre-calculated state commitment with root account with the above private key -const GenesisStateCommitmentHex = "f0eeeadd231fcc1668d4bcc8df8488a35e126600e3c4b88b4120b35e4ea9ee8c" +const GenesisStateCommitmentHex = "ba479ddabd34159a9d6326ea78c659aa6dd71db63791714bdccdbea859ba1b8e" var GenesisStateCommitment flow.StateCommitment @@ -87,10 +87,10 @@ func genesisCommitHexByChainID(chainID flow.ChainID) string { return GenesisStateCommitmentHex } if chainID == flow.Testnet { - return "b41ab049fc2f2f5f419357f2e4dc9d2181a18f419e3b2e96d7f908511d5e0aa1" + return "9485c620254319da8ea93978909d7ed8327e9dd1f4cb9ec74c816919166b5a2c" } if chainID == flow.Sandboxnet { return "e1c08b17f9e5896f03fe28dd37ca396c19b26628161506924fbf785834646ea1" } - return "922fca1ecf717e724328d24a9e3cb147426973405fe0d7ba87f67e0f5347887b" + return "66df5ceb8ca13532a5004bb6014677b0ff72199a5ea42388d5e0947368739c94" }