Skip to content

Commit

Permalink
Merge pull request #6378 from onflow/ramtin/evm-move-random-to-block-…
Browse files Browse the repository at this point in the history
…level

[Flow EVM] Set prevrandao value on block level
  • Loading branch information
ramtinms authored Aug 21, 2024
2 parents e51cea8 + 3d6b44c commit 122fdb6
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 24 deletions.
2 changes: 1 addition & 1 deletion engine/execution/state/bootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions fvm/evm/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions fvm/evm/events/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
36 changes: 27 additions & 9 deletions fvm/evm/handler/blockstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand All @@ -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()
Expand Down Expand Up @@ -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
}
Expand Down
12 changes: 5 additions & 7 deletions fvm/evm/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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),
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions fvm/evm/stdlib/contract.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
95 changes: 92 additions & 3 deletions fvm/evm/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -65,6 +68,7 @@ func NewBlock(
height uint64,
timestamp uint64,
totalSupply *big.Int,
prevRandao gethCommon.Hash,
) *Block {
return &Block{
ParentBlockHash: parentBlockHash,
Expand All @@ -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
Expand All @@ -104,6 +116,7 @@ func GenesisBlock(chainID flow.ChainID) *Block {
ReceiptRoot: gethTypes.EmptyRootHash,
TransactionHashRoot: gethTypes.EmptyRootHash,
TotalGasUsed: 0,
PrevRandao: gethCommon.Hash{},
}
}

Expand Down Expand Up @@ -182,14 +195,22 @@ 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(
parentBlockHash gethCommon.Hash,
height uint64,
timestamp uint64,
totalSupply *big.Int,
prevRandao gethCommon.Hash,
) *BlockProposal {
return &BlockProposal{
Block: Block{
Expand All @@ -198,6 +219,7 @@ func NewBlockProposal(
Timestamp: timestamp,
TotalSupply: totalSupply,
ReceiptRoot: gethTypes.EmptyRootHash,
PrevRandao: prevRandao,
},
Receipts: make([]LightReceipt, 0),
TxHashes: make([]gethCommon.Hash, 0),
Expand All @@ -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
}
52 changes: 51 additions & 1 deletion fvm/evm/types/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Loading

0 comments on commit 122fdb6

Please sign in to comment.