Skip to content

Commit

Permalink
core, eth: correct semantics for LeavePoW, EnterPoS
Browse files Browse the repository at this point in the history
  • Loading branch information
MariusVanDerWijden committed Nov 1, 2021
1 parent 6418972 commit 7727d0e
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 153 deletions.
28 changes: 17 additions & 11 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
)
Expand Down Expand Up @@ -55,12 +55,12 @@ var (
//
// The beacon here is a half-functional consensus engine with partial functions which
// is only used for necessary consensus checks. The legacy consensus engine can be any
// engine implements the consensus interface(except the beacon itself).
// engine implements the consensus interface (except the beacon itself).
type Beacon struct {
ethone consensus.Engine // Classic consensus engine used in eth1, e.g. ethash or clique

// transitioned is the flag whether the transition has been triggered.
// It's triggered by receiving the first "POS_CHAINHEAD_SET" message
// It's triggered by receiving the first "ENGINE_FORKCHOICEUPDATED" message
// from the external consensus engine.
transitioned bool
lock sync.RWMutex
Expand Down Expand Up @@ -107,6 +107,7 @@ func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *ty
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
// concurrently. The method returns a quit channel to abort the operations and
// a results channel to retrieve the async verifications.
// VerifyHeaders expect the headers to be ordered (not necessary continuous).
func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
if !beacon.IsPoSHeader(headers[len(headers)-1]) {
return beacon.ethone.VerifyHeaders(chain, headers, seals)
Expand Down Expand Up @@ -181,9 +182,10 @@ func (beacon *Beacon) VerifyUncles(chain consensus.ChainReader, block *types.Blo

// verifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum consensus engine. The difference between the beacon and classic is
// (a) the difficulty, mixhash, nonce, extradata and unclehash are expected
// (a) the difficulty, mixhash, nonce and unclehash are expected
// to be the desired constants
// (b) the timestamp is not verified anymore
// (c) the extradata is limited to 32 bytes
func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header) error {
// Ensure that the header's extra-data section is of a reasonable size
if len(header.Extra) > 32 {
Expand All @@ -203,13 +205,17 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
}
// Verify that the gas limit remains within allowed bounds
diff := int64(parent.GasLimit) - int64(header.GasLimit)
if diff < 0 {
diff *= -1
}
limit := parent.GasLimit / params.GasLimitBoundDivisor
if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit {
return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit)
if !chain.Config().IsLondon(header.Number) {
// Verify BaseFee not present before EIP-1559 fork.
if header.BaseFee != nil {
return fmt.Errorf("invalid baseFee before fork: have %d, expected 'nil'", header.BaseFee)
}
if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil {
return err
}
} else if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil {
// Verify the header's EIP-1559 attributes.
return err
}
// Verify that the block number is parent's +1
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
Expand Down
10 changes: 5 additions & 5 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,10 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// Rewind may have occurred, skip in that case.
if bc.CurrentHeader().Number.Cmp(head.Number()) >= 0 {
reorg, err := bc.forker.Reorg(bc.CurrentFastBlock().Header(), head.Header())
if err != nil || !reorg {
if err != nil {
log.Warn("Reorg failed", "err", err)
return false
} else if !reorg {
return false
}
rawdb.WriteHeadFastBlockHash(bc.db, head.Hash())
Expand Down Expand Up @@ -1184,9 +1187,6 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
// writeBlockWithState writes block, metadata and corresponding state data to the
// database.
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error {
bc.wg.Add(1)
defer bc.wg.Done()

// Calculate the total difficulty of the block
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
if ptd == nil {
Expand Down Expand Up @@ -1436,7 +1436,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
if reorg {
// Switch to import mode if the forker says the reorg is necessary
// and also the block is not on the canonical chain.
// In eth2 the forker always returns True for reorg desision(blindly trust
// In eth2 the forker always returns true for reorg decision (blindly trusting
// the external consensus engine), but in order to prevent the unnecessary
// reorgs when importing known blocks, the special case is handled here.
if bc.GetCanonicalHash(block.NumberU64()) != block.Hash() || block.NumberU64() > current.NumberU64() {
Expand Down
2 changes: 1 addition & 1 deletion core/forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type ForkChoice struct {

// preserve is a helper function used in td fork choice.
// Miners will prefer to choose the local mined block if the
// local td is equal to the extern one. It can nil for light
// local td is equal to the extern one. It can be nil for light
// client
preserve func(header *types.Header) bool
}
Expand Down
13 changes: 0 additions & 13 deletions core/merger.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ type Merger struct {
db ethdb.KeyValueStore
status transitionStatus
leavePoWCalls []func()
enterPoSCalls []func()
lock sync.Mutex
}

Expand All @@ -66,15 +65,6 @@ func (m *Merger) SubscribeLeavePoW(callback func()) {
m.leavePoWCalls = append(m.leavePoWCalls, callback)
}

// SubscribeEnterPoS registers callback so that if the chain leaves
// from the 'transition' stage and enters the PoS stage it can be invoked.
func (m *Merger) SubscribeEnterPoS(callback func()) {
m.lock.Lock()
defer m.lock.Unlock()

m.enterPoSCalls = append(m.enterPoSCalls, callback)
}

// LeavePoW is called whenever the first NewHead message received
// from the consensus-layer.
func (m *Merger) LeavePoW() {
Expand Down Expand Up @@ -111,9 +101,6 @@ func (m *Merger) EnterPoS() {
log.Crit("Failed to encode the transition status", "err", err)
}
rawdb.WriteTransitionStatus(m.db, blob)
for _, call := range m.enterPoSCalls {
call()
}
log.Info("Entered PoS stage")
}

Expand Down
82 changes: 8 additions & 74 deletions eth/catalyst/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"errors"
"fmt"
"math/big"
"os"
"time"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -38,7 +37,6 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
chainParams "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
)
Expand Down Expand Up @@ -196,15 +194,7 @@ func (api *ConsensusAPI) GetPayload(PayloadID hexutil.Uint64) (*ExecutableData,
func (api *ConsensusAPI) ConsensusValidated(params ConsensusValidatedParams) error {
switch params.Status {
case VALID.Status:
// Finalize the transition if it's the first `FinalisedBlock` event.
merger := api.merger()
if !merger.EnteredPoS() {
if err := api.checkTerminalTotalDifficulty(params.BlockHash); err != nil {
return err
}
merger.EnterPoS()
}
return nil //api.setHead(params.BlockHash)
return nil
case INVALID.Status:
// TODO (MariusVanDerWijden) delete the block from the bc
return nil
Expand Down Expand Up @@ -258,6 +248,10 @@ func (api *ConsensusAPI) ExecutePayload(params ExecutableData) (GenericStringRes
if err := api.eth.BlockChain().InsertBlock(block); err != nil {
return INVALID, err
}
merger := api.merger()
if !merger.LeftPoW() {
merger.LeavePoW()
}
return VALID, nil
}

Expand All @@ -277,7 +271,7 @@ func (api *ConsensusAPI) assembleBlock(params AssembleBlockParams) (*ExecutableD
}

if params.Timestamp < parent.Time() {
return nil, fmt.Errorf("child timestamp lower than parent's: %d >= %d", parent.Time(), params.Timestamp)
return nil, fmt.Errorf("child timestamp lower than parent's: %d < %d", params.Timestamp, parent.Time())
}
if now := uint64(time.Now().Unix()); params.Timestamp > now+1 {
diff := time.Duration(params.Timestamp-now) * time.Second
Expand Down Expand Up @@ -468,8 +462,8 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
func (api *ConsensusAPI) setHead(newHead common.Hash) error {
// Trigger the transition if it's the first `NewHead` event.
merger := api.merger()
if !merger.LeftPoW() {
merger.LeavePoW()
if !merger.EnteredPoS() {
merger.EnterPoS()
}
log.Info("Setting head", "head", newHead)
if api.light {
Expand Down Expand Up @@ -508,63 +502,3 @@ func (api *ConsensusAPI) merger() *core.Merger {
}
return api.eth.Merger()
}

// Helper API for the merge f2f

func (api *ConsensusAPI) ExportChain(path string) error {
if api.light {
return errors.New("cannot export chain in light mode")
}
f, err := os.Create(path)
if err != nil {
return err
}
return api.eth.BlockChain().Export(f)
}

func (api *ConsensusAPI) ImportChain(path string) error {
if api.light {
return errors.New("cannot import chain in light mode")
}
f, err := os.Open(path)
if err != nil {
return err
}
for {
var block types.Block
if err := block.DecodeRLP(rlp.NewStream(f, 0)); err != nil {
break
}
if err := api.eth.BlockChain().InsertBlock(&block); err != nil {
return err
}
}
return nil
}

func (api *ConsensusAPI) ExportExecutableData(path string) error {
if api.light {
return errors.New("cannot export chain in light mode")
}
f, err := os.Create(path)
if err != nil {
return err
}
for i := uint64(0); i < api.eth.BlockChain().CurrentBlock().NumberU64(); i++ {
block := api.eth.BlockChain().GetBlockByNumber(i)
exec := BlockToExecutableData(block, common.Hash{})
b, err := exec.MarshalJSON()
if err != nil {
return err
}
if _, err := f.Write(b); err != nil {
return err
}
f.Write([]byte("\n"))
}
return nil
}

func (api *ConsensusAPI) GetHead() (common.Hash, error) {
return api.eth.BlockChain().CurrentBlock().Hash(), nil
}
49 changes: 0 additions & 49 deletions eth/catalyst/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,52 +73,6 @@ func generatePreMergeChain(n int) (*core.Genesis, []*types.Block) {
return genesis, blocks
}

// TODO (MariusVanDerWijden) reenable once engine api is updated to the latest spec
/*
func generateTestChainWithFork(n int, fork int) (*core.Genesis, []*types.Block, []*types.Block) {
if fork >= n {
fork = n - 1
}
db := rawdb.NewMemoryDatabase()
config := &params.ChainConfig{
ChainID: big.NewInt(1337),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
Ethash: new(params.EthashConfig),
}
genesis := &core.Genesis{
Config: config,
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}},
ExtraData: []byte("test genesis"),
Timestamp: 9000,
BaseFee: big.NewInt(params.InitialBaseFee),
}
generate := func(i int, g *core.BlockGen) {
g.OffsetTime(5)
g.SetExtra([]byte("test"))
}
generateFork := func(i int, g *core.BlockGen) {
g.OffsetTime(5)
g.SetExtra([]byte("testF"))
}
gblock := genesis.ToBlock(db)
engine := ethash.NewFaker()
blocks, _ := core.GenerateChain(config, gblock, engine, db, n, generate)
blocks = append([]*types.Block{gblock}, blocks...)
forkedBlocks, _ := core.GenerateChain(config, blocks[fork], engine, db, n-fork, generateFork)
return genesis, blocks, forkedBlocks
}
*/

func TestEth2AssembleBlock(t *testing.T) {
genesis, blocks := generatePreMergeChain(10)
n, ethservice := startEthService(t, genesis, blocks)
Expand Down Expand Up @@ -172,9 +126,6 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
defer n.Close()

api := NewConsensusAPI(ethservice, nil)
if err := api.ConsensusValidated(ConsensusValidatedParams{BlockHash: blocks[5].Hash(), Status: VALID.Status}); err == nil {
t.Errorf("consensus validated before total terminal difficulty should fail")
}

if err := api.ForkchoiceUpdated(ForkChoiceParams{HeadBlockHash: blocks[5].Hash()}); err == nil {
t.Errorf("fork choice updated before total terminal difficulty should fail")
Expand Down

0 comments on commit 7727d0e

Please sign in to comment.