diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index 16e8505731..797966156b 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -244,13 +244,22 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header } // Verify the existence / non-existence of excessBlobGas cancun := chain.Config().IsCancun(header.Number, header.Time) - if !cancun && header.ExcessBlobGas != nil { - return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas) - } - if !cancun && header.BlobGasUsed != nil { - return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed) - } - if cancun { + if !cancun { + switch { + case header.ExcessBlobGas != nil: + return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", *header.ExcessBlobGas) + case header.BlobGasUsed != nil: + return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *header.BlobGasUsed) + case header.ParentBeaconRoot != nil: + return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", *header.ParentBeaconRoot) + } + } else { + if header.ParentBeaconRoot == nil { + return errors.New("header is missing beaconRoot") + } + if *header.ParentBeaconRoot != (common.Hash{}) { + return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected empty", *header.ParentBeaconRoot) + } if err := eip4844.VerifyEIP4844Header(parent, header); err != nil { return err } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index c35370080f..26a6a5ced0 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -394,8 +394,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr header.ExcessBlobGas = &excess header.BlobGasUsed = &used - beaconRoot := common.HexToHash("0xbeac00") - header.ParentBeaconRoot = &beaconRoot + header.ParentBeaconRoot = new(common.Hash) } // Assemble and return the final block for sealing return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)) diff --git a/miner/worker.go b/miner/worker.go index e139c0d73a..8cf34cda8c 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -104,7 +104,7 @@ type worker struct { mu sync.RWMutex // The lock used to protect the coinbase and extra fields coinbase common.Address clock *mockable.Clock // Allows us mock the clock for testing - beaconRoot *common.Hash // TODO: not set anywhere, retained for upstream compatibility and future use + beaconRoot *common.Hash // TODO: set to empty hash, retained for upstream compatibility and future use } func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, clock *mockable.Clock) *worker { @@ -117,6 +117,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus mux: mux, coinbase: config.Etherbase, clock: clock, + beaconRoot: &common.Hash{}, } return worker diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index 1b2119e201..38f2bc29fa 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -264,10 +264,10 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { // Verify the existence / non-existence of excessBlobGas cancun := rules.IsCancun if !cancun && ethHeader.ExcessBlobGas != nil { - return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", ethHeader.ExcessBlobGas) + return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", *ethHeader.ExcessBlobGas) } if !cancun && ethHeader.BlobGasUsed != nil { - return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", ethHeader.BlobGasUsed) + return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *ethHeader.BlobGasUsed) } if cancun && ethHeader.ExcessBlobGas == nil { return errors.New("header is missing excessBlobGas") @@ -275,5 +275,18 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { if cancun && ethHeader.BlobGasUsed == nil { return errors.New("header is missing blobGasUsed") } + if !cancun && ethHeader.ParentBeaconRoot != nil { + return fmt.Errorf("invalid parentBeaconRoot: have %x, expected nil", *ethHeader.ParentBeaconRoot) + } + // TODO: decide what to do after Cancun + // currently we are enforcing it to be empty hash + if cancun { + switch { + case ethHeader.ParentBeaconRoot == nil: + return errors.New("header is missing parentBeaconRoot") + case *ethHeader.ParentBeaconRoot != (common.Hash{}): + return fmt.Errorf("invalid parentBeaconRoot: have %x, expected empty hash", ethHeader.ParentBeaconRoot) + } + } return nil } diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 975aa5b12e..8e08ece40b 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -97,6 +97,8 @@ var ( genesisJSONDurango = `{"config":{"chainId":43111,"homesteadBlock":0,"daoForkBlock":0,"daoForkSupport":true,"eip150Block":0,"eip150Hash":"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0","eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"muirGlacierBlock":0,"apricotPhase1BlockTimestamp":0,"apricotPhase2BlockTimestamp":0,"apricotPhase3BlockTimestamp":0,"apricotPhase4BlockTimestamp":0,"apricotPhase5BlockTimestamp":0,"apricotPhasePre6BlockTimestamp":0,"apricotPhase6BlockTimestamp":0,"apricotPhasePost6BlockTimestamp":0,"banffBlockTimestamp":0,"cortinaBlockTimestamp":0,"durangoBlockTimestamp":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x5f5e100","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"0x99b9DEA54C48Dfea6aA9A4Ca4623633EE04ddbB5":{"balance":"0x56bc75e2d63100000"},"0100000000000000000000000000000000000000":{"code":"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033","balance":"0x0"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}` genesisJSONLatest = genesisJSONDurango + genesisJSONCancun = `{"config":{"chainId":43111,"cancunTime":0,"homesteadBlock":0,"daoForkBlock":0,"daoForkSupport":true,"eip150Block":0,"eip150Hash":"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0","eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"muirGlacierBlock":0,"apricotPhase1BlockTimestamp":0,"apricotPhase2BlockTimestamp":0,"apricotPhase3BlockTimestamp":0,"apricotPhase4BlockTimestamp":0,"apricotPhase5BlockTimestamp":0,"apricotPhasePre6BlockTimestamp":0,"apricotPhase6BlockTimestamp":0,"apricotPhasePost6BlockTimestamp":0,"banffBlockTimestamp":0,"cortinaBlockTimestamp":0,"durangoBlockTimestamp":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x5f5e100","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"0x99b9DEA54C48Dfea6aA9A4Ca4623633EE04ddbB5":{"balance":"0x56bc75e2d63100000"},"0100000000000000000000000000000000000000":{"code":"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033","balance":"0x0"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}` + apricotRulesPhase0 = params.Rules{} apricotRulesPhase1 = params.Rules{IsApricotPhase1: true} apricotRulesPhase2 = params.Rules{IsApricotPhase1: true, IsApricotPhase2: true} @@ -4094,3 +4096,120 @@ func TestSkipChainConfigCheckCompatible(t *testing.T) { require.NoError(t, err) require.NoError(t, reinitVM.Shutdown(context.Background())) } + +func TestParentBeaconRootBlock(t *testing.T) { + tests := []struct { + name string + genesisJSON string + beaconRoot *common.Hash + expectedError bool + errString string + }{ + { + name: "non-empty parent beacon root in Durango", + genesisJSON: genesisJSONDurango, + beaconRoot: &common.Hash{0x01}, + expectedError: true, + // err string wont work because it will also fail with blob gas is non-empty (zeroed) + }, + { + name: "empty parent beacon root in Durango", + genesisJSON: genesisJSONDurango, + beaconRoot: &common.Hash{}, + expectedError: true, + }, + { + name: "nil parent beacon root in Durango", + genesisJSON: genesisJSONDurango, + beaconRoot: nil, + expectedError: false, + }, + { + name: "non-empty parent beacon root in Cancun", + genesisJSON: genesisJSONCancun, + beaconRoot: &common.Hash{0x01}, + expectedError: true, + errString: "expected empty hash", + }, + { + name: "empty parent beacon root in Cancun", + genesisJSON: genesisJSONCancun, + beaconRoot: &common.Hash{}, + expectedError: false, + }, + { + name: "nil parent beacon root in Cancun", + genesisJSON: genesisJSONCancun, + beaconRoot: nil, + expectedError: true, + errString: "header is missing parentBeaconRoot", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + importAmount := uint64(1000000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, test.genesisJSON, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + + defer func() { + if err := vm.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + }() + + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) + } + + <-issuer + + blk, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatalf("Failed to build block with import transaction: %s", err) + } + + // Modify the block to have a parent beacon root + ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + header := types.CopyHeader(ethBlock.Header()) + header.ParentBeaconRoot = test.beaconRoot + parentBeaconEthBlock := types.NewBlockWithExtData( + header, + nil, + nil, + nil, + new(trie.Trie), + ethBlock.ExtData(), + false, + ) + + parentBeaconBlock, err := vm.newBlock(parentBeaconEthBlock) + if err != nil { + t.Fatal(err) + } + + errCheck := func(err error) { + if test.expectedError { + if test.errString != "" { + require.ErrorContains(t, err, test.errString) + } else { + require.Error(t, err) + } + } else { + require.NoError(t, err) + } + } + + _, err = vm.ParseBlock(context.Background(), parentBeaconBlock.Bytes()) + errCheck(err) + err = parentBeaconBlock.Verify(context.Background()) + errCheck(err) + }) + } +}