Skip to content

Feat: pass coinbase from consensus node #179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions consensus/clique/clique.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,10 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header
return nil
}

func (c *Clique) StartHook(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB) error {
return nil
}

// Finalize implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given.
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
Expand Down
4 changes: 4 additions & 0 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ type Engine interface {
// rules of a particular engine. The changes are executed inline.
Prepare(chain ChainHeaderReader, header *types.Header) error

// StartHook calling before start apply transactions of block
//StartHook(chain consensus.ChainHeaderReader, header *types.Header, preHeader *types.Header, state *state.StateDB) error
StartHook(chain ChainHeaderReader, header *types.Header, state *state.StateDB) error

// Finalize runs any post-transaction state modifications (e.g. block rewards)
// but does not assemble the block.
//
Expand Down
4 changes: 4 additions & 0 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,10 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H
return nil
}

func (ethash *Ethash) StartHook(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB) error {
return nil
}

// Finalize implements consensus.Engine, accumulating the block and uncle rewards,
// setting the final state on the header
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
Expand Down
74 changes: 65 additions & 9 deletions consensus/l2/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,27 @@ package l2
import (
"errors"
"fmt"
"github.com/morph-l2/go-ethereum/contracts/morphtoken"
"math/big"

"github.com/morph-l2/go-ethereum/common"
"github.com/morph-l2/go-ethereum/consensus"
"github.com/morph-l2/go-ethereum/consensus/misc"
"github.com/morph-l2/go-ethereum/contracts/l2staking"
"github.com/morph-l2/go-ethereum/core"
"github.com/morph-l2/go-ethereum/core/state"
"github.com/morph-l2/go-ethereum/core/types"
"github.com/morph-l2/go-ethereum/core/vm"
"github.com/morph-l2/go-ethereum/params"
"github.com/morph-l2/go-ethereum/rollup/rcfg"
"github.com/morph-l2/go-ethereum/rpc"
"github.com/morph-l2/go-ethereum/trie"
)

var (
l2Difficulty = common.Big0 // The default block difficulty in the l2 consensus
l2Nonce = types.EncodeNonce(0) // The default block nonce in the l2 consensus
l2Difficulty = common.Big0 // The default block difficulty in the l2 consensus
l2Nonce = types.EncodeNonce(0) // The default block nonce in the l2 consensus
rewardEpoch uint64 = 86400
)

// Various error messages to mark blocks invalid. These should be private to
Expand All @@ -29,9 +35,28 @@ var (
errInvalidNonce = errors.New("invalid nonce")
errInvalidUncleHash = errors.New("invalid uncle hash")
errInvalidTimestamp = errors.New("invalid timestamp")
errInvalidCoinbase = errors.New("invalid coinbase")
)

// chain context
type chainContext struct {
Chain consensus.ChainHeaderReader
engine consensus.Engine
}

func (c chainContext) Engine() consensus.Engine {
return c.engine
}

func (c chainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
return c.Chain.GetHeader(hash, number)
}

func (c chainContext) Config() *params.ChainConfig {
return c.Chain.Config()
}
Comment on lines +40 to +56
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing GetHeaderByNumber breaks the core.ChainContext contract

core.NewEVMBlockContext expects the argument to satisfy core.ChainContext, which in Geth also requires
GetHeaderByNumber(uint64) *types.Header.
The current chainContext implements Engine and GetHeader but omits this method, so the code will not compile.

 type chainContext struct {
     Chain  consensus.ChainHeaderReader
     engine consensus.Engine
 }
 
+// GetHeaderByNumber delegates to the underlying reader.
+func (c chainContext) GetHeaderByNumber(number uint64) *types.Header {
+	return c.Chain.GetHeader(common.Hash{}, number) // or cast to a richer interface if available
+}
+
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// chain context
type chainContext struct {
Chain consensus.ChainHeaderReader
engine consensus.Engine
}
func (c chainContext) Engine() consensus.Engine {
return c.engine
}
func (c chainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
return c.Chain.GetHeader(hash, number)
}
func (c chainContext) Config() *params.ChainConfig {
return c.Chain.Config()
}
// chain context
type chainContext struct {
Chain consensus.ChainHeaderReader
engine consensus.Engine
}
// GetHeaderByNumber delegates to the underlying reader.
func (c chainContext) GetHeaderByNumber(number uint64) *types.Header {
return c.Chain.GetHeader(common.Hash{}, number) // or cast to a richer interface if available
}
func (c chainContext) Engine() consensus.Engine {
return c.engine
}
func (c chainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
return c.Chain.GetHeader(hash, number)
}
func (c chainContext) Config() *params.ChainConfig {
return c.Chain.Config()
}


var _ = consensus.Engine(&Consensus{})

type Consensus struct {
ethone consensus.Engine // Original consensus engine used in eth1, e.g. ethash or clique
config *params.ChainConfig
Expand Down Expand Up @@ -142,9 +167,6 @@ func (l2 *Consensus) verifyHeader(chain consensus.ChainHeaderReader, header, par
return errInvalidUncleHash
}

if l2.config.Morph.FeeVaultEnabled() && header.Coinbase != types.EmptyAddress {
return errInvalidCoinbase
}
// Verify the timestamp
if header.Time <= parent.Time {
return errInvalidTimestamp
Expand Down Expand Up @@ -187,9 +209,43 @@ func (l2 *Consensus) Prepare(chain consensus.ChainHeaderReader, header *types.He
header.Nonce = l2Nonce
header.UncleHash = types.EmptyUncleHash
header.Extra = []byte{} // disable extra field filling with bytes
// set coinbase to empty address, if feeVault is enabled
if l2.config.Morph.FeeVaultEnabled() {
header.Coinbase = types.EmptyAddress
return nil
}

// StartHook implements calling before start apply transactions of block
func (l2 *Consensus) StartHook(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB) error {
rewardStarted := state.GetState(rcfg.L2StakingAddress, rcfg.RewardStartedSlot).Big()
if rewardStarted.Cmp(common.Big1) != 0 {
return nil
}
inflationMintedEpochs := state.GetState(rcfg.MorphTokenAddress, rcfg.InflationMintedEpochsSolt).Big().Uint64()
rewardStartTime := state.GetState(rcfg.L2StakingAddress, rcfg.RewardStartTimeSlot).Big().Uint64()
parentHeader := chain.GetHeaderByHash(header.ParentHash)
if parentHeader == nil {
return consensus.ErrUnknownAncestor
}
cx := chainContext{Chain: chain, engine: l2.ethone}
blockContext := core.NewEVMBlockContext(header, cx, l2.config, nil)
Comment on lines +223 to +228
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

GetHeaderByHash does not exist on ChainHeaderReader

consensus.ChainHeaderReader exposes GetHeader(hash, number) but not GetHeaderByHash.
This will cause a compilation error.

-parentHeader := chain.GetHeaderByHash(header.ParentHash)
+parentHeader := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)

If the richer interface is really required, assert the type first:

type headerByHash interface {
	GetHeaderByHash(hash common.Hash) *types.Header
}
var parentHeader *types.Header
if r, ok := chain.(headerByHash); ok {
	parentHeader = r.GetHeaderByHash(header.ParentHash)
} else {
	parentHeader = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
}

// TODO tracer
evm := vm.NewEVM(blockContext, vm.TxContext{}, state, l2.config, vm.Config{Tracer: nil})
stakingCallData, err := l2staking.PacketData(parentHeader.Coinbase)
if err != nil {
return err
}
systemAddress := vm.AccountRef(rcfg.SystemAddress)
_, _, err = evm.Call(systemAddress, rcfg.L2StakingAddress, stakingCallData, params.MaxGasLimit, common.Big0)
if err != nil {
return err
}
if header.Time > rewardStartTime && (header.Time-rewardStartTime)/rewardEpoch > inflationMintedEpochs {
callData, err := morphtoken.PacketData()
if err != nil {
return err
}
_, _, err = evm.Call(systemAddress, rcfg.MorphTokenAddress, callData, params.MaxGasLimit, common.Big0)
if err != nil {
return err
}
}
return nil
}
Expand Down
42 changes: 42 additions & 0 deletions contracts/l2staking/l2staking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package l2staking

import (
"fmt"
"strings"
"sync"

"github.com/morph-l2/go-ethereum/accounts/abi"
"github.com/morph-l2/go-ethereum/common"
)

const jsonData = `[{"inputs":[{"internalType":"address","name":"sequencerAddr","type":"address"}],"name":"recordBlocks","outputs":[],"stateMutability":"nonpayable","type":"function"}]`

var (
l2StakingABI *abi.ABI
loadOnce sync.Once
loadErr error
)

func Abi() (*abi.ABI, error) {
loadOnce.Do(func() {
stakingABI, err := abi.JSON(strings.NewReader(jsonData))
if err != nil {
loadErr = fmt.Errorf("failed to parse ABI: %w", err)
return
}
l2StakingABI = &stakingABI
})
return l2StakingABI, loadErr
}

func PacketData(addr common.Address) ([]byte, error) {
a, err := Abi()
if err != nil {
return nil, fmt.Errorf("failed to get ABI: %w", err)
}
data, err := a.Pack("recordBlocks", addr)
if err != nil {
return nil, fmt.Errorf("failed to pack data: %w", err)
}
return data, nil
}
13 changes: 13 additions & 0 deletions contracts/l2staking/l2staking_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package l2staking

import (
"testing"

"github.com/morph-l2/go-ethereum/common"
"github.com/stretchr/testify/require"
)

func TestPackData(t *testing.T) {
_, err := PacketData(common.HexToAddress("0x01"))
require.NoError(t, err)
}
41 changes: 41 additions & 0 deletions contracts/morphtoken/morph_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package morphtoken

import (
"fmt"
"strings"
"sync"

"github.com/morph-l2/go-ethereum/accounts/abi"
)

const jsonData = `[{"inputs":[],"name":"mintInflations","outputs":[],"stateMutability":"nonpayable","type":"function"}]`

var (
morphTokenABI *abi.ABI
loadOnce sync.Once
loadErr error
)

func Abi() (*abi.ABI, error) {
loadOnce.Do(func() {
tokenABI, err := abi.JSON(strings.NewReader(jsonData))
if err != nil {
loadErr = fmt.Errorf("failed to parse ABI: %w", err)
return
}
morphTokenABI = &tokenABI
})
return morphTokenABI, loadErr
}

func PacketData() ([]byte, error) {
a, err := Abi()
if err != nil {
return nil, fmt.Errorf("failed to get ABI: %w", err)
}
data, err := a.Pack("mintInflations")
if err != nil {
return nil, fmt.Errorf("failed to pack data: %w", err)
}
return data, nil
}
12 changes: 12 additions & 0 deletions contracts/morphtoken/morph_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package morphtoken

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestPackData(t *testing.T) {
_, err := PacketData()
require.NoError(t, err)
}
4 changes: 4 additions & 0 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
blockContext := NewEVMBlockContext(header, p.bc, p.config, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
processorBlockTransactionGauge.Update(int64(block.Transactions().Len()))
err := p.engine.StartHook(p.bc, header, statedb)
if err != nil {
return nil, nil, 0, err
}
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
Expand Down
1 change: 1 addition & 0 deletions core/types/l2trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type BlockTrace struct {
Bytecodes []*BytecodeTrace `json:"codes"`
TxStorageTraces []*StorageTrace `json:"txStorageTraces,omitempty"`
ExecutionResults []*ExecutionResult `json:"executionResults"`
StartHookResult *ExecutionResult `json:"startHookResult,omitempty"`
WithdrawTrieRoot common.Hash `json:"withdraw_trie_root,omitempty"`
SequencerSetVerifyHash common.Hash `json:"sequencer_set_verify_hash,omitempty"`
StartL1QueueIndex uint64 `json:"startL1QueueIndex"`
Expand Down
18 changes: 10 additions & 8 deletions eth/catalyst/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ type assembleBlockParamsMarshaling struct {
//go:generate go run github.com/fjl/gencodec -type AssembleL2BlockParams -field-override assembleL2BlockParamsMarshaling -out gen_l2blockparams.go

type AssembleL2BlockParams struct {
Number uint64 `json:"number" gencodec:"required"`
Transactions [][]byte `json:"transactions"`
Number uint64 `json:"number" gencodec:"required"`
Coinbase common.Address `json:"coinbase"`
Transactions [][]byte `json:"transactions"`
}

// JSON type overrides for assembleL2BlockParams.
Expand Down Expand Up @@ -125,12 +126,13 @@ type executableL2DataMarshaling struct {

// SafeL2Data is the block data which is approved in L1 and considered to be safe
type SafeL2Data struct {
Number uint64 `json:"number" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
BaseFee *big.Int `json:"baseFeePerGas"`
Timestamp uint64 `json:"timestamp" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"`
BatchHash *common.Hash `json:"batchHash"`
Miner common.Address `json:"miner" gencodec:"required"`
Number uint64 `json:"number" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
BaseFee *big.Int `json:"baseFeePerGas"`
Timestamp uint64 `json:"timestamp" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"`
BatchHash *common.Hash `json:"batchHash"`
}

// JSON type overrides for SafeL2Data.
Expand Down
7 changes: 7 additions & 0 deletions eth/catalyst/gen_l2_sd.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions eth/catalyst/gen_l2blockparams.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading