Skip to content
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

Bohr #452

Merged
merged 4 commits into from
Jul 25, 2024
Merged

Bohr #452

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
18 changes: 18 additions & 0 deletions consensus/misc/eip4844.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/holiman/uint256"

"github.com/ledgerwatch/erigon-lib/chain"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/common/fixedgas"

"github.com/ledgerwatch/erigon/core/types"
Expand Down Expand Up @@ -89,6 +90,23 @@ func VerifyPresenceOfCancunHeaderFields(header *types.Header) error {
return nil
}

// VerifyPresenceOfCancunHeaderFields checks that the fields introduced in Cancun (EIP-4844, EIP-4788) are present.
func VerifyPresenceOfBohrHeaderFields(header *types.Header) error {
if header.BlobGasUsed == nil {
return fmt.Errorf("header is missing blobGasUsed")
}
if header.ExcessBlobGas == nil {
return fmt.Errorf("header is missing excessBlobGas")
}
if header.ParentBeaconBlockRoot != nil || *header.ParentBeaconBlockRoot != (libcommon.Hash{}) {
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected zero hash", header.ParentBeaconBlockRoot)
}
if header.WithdrawalsHash == nil || *header.WithdrawalsHash != types.EmptyRootHash {
return errors.New("header has wrong WithdrawalsHash")
}
return nil
}

// VerifyAbsenceOfCancunHeaderFields checks that the header doesn't have any fields added in Cancun (EIP-4844, EIP-4788).
func VerifyAbsenceOfCancunHeaderFields(header *types.Header) error {
if header.BlobGasUsed != nil {
Expand Down
13 changes: 13 additions & 0 deletions consensus/parlia/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -2306,6 +2306,19 @@ const validatorSetABI = `
],
"stateMutability": "view"
},
{
"inputs": [],
"name": "getTurnLength",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"type": "function",
"name": "getValidators",
Expand Down
19 changes: 19 additions & 0 deletions consensus/parlia/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,22 @@ func (api *API) GetValidatorsAtHash(hash libcommon.Hash) ([]libcommon.Address, e
}
return snap.validators(), nil
}

func (api *API) GetTurnLength(number *rpc.BlockNumber) (uint8, error) {
// Retrieve the requested block number (or current if none requested)
var header *types.Header
if number == nil || *number == rpc.LatestBlockNumber {
header = api.chain.CurrentHeader()
} else {
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
}
// Ensure we have an actually valid block and return the validators from its snapshot
if header == nil {
return 0, errUnknownBlock
}
snap, err := api.parlia.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil, false /* verify */)
if err != nil || snap.TurnLength == 0 {
return 0, err
}
return snap.TurnLength, nil
}
74 changes: 74 additions & 0 deletions consensus/parlia/bohrFork.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package parlia

import (
"errors"
"math/big"
mrand "math/rand"

"github.com/ledgerwatch/erigon/common/u256"
"github.com/ledgerwatch/erigon/consensus"
"github.com/ledgerwatch/erigon/core/state"
"github.com/ledgerwatch/erigon/core/systemcontracts"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/params"
"github.com/ledgerwatch/log/v3"
)

func (p *Parlia) getTurnLength(chain consensus.ChainHeaderReader, header *types.Header, ibs *state.IntraBlockState) (*uint8, error) {
parent := chain.GetHeaderByHash(header.ParentHash)
if parent == nil {
return nil, errors.New("parent not found")
}

var turnLength uint8
if p.chainConfig.IsBohr(parent.Number.Uint64(), parent.Time) {
turnLengthFromContract, err := p.getTurnLengthFromContract(parent, ibs)
if err != nil {
return nil, err
}
if turnLengthFromContract == nil {
return nil, errors.New("unexpected error when getTurnLengthFromContract")
}
turnLength = uint8(turnLengthFromContract.Int64())
} else {
turnLength = defaultTurnLength
}
log.Debug("getTurnLength", "turnLength", turnLength)

return &turnLength, nil
}

func (p *Parlia) getTurnLengthFromContract(header *types.Header, ibs *state.IntraBlockState) (turnLength *big.Int, err error) {
// mock to get turnLength from the contract
if params.FixedTurnLength >= 1 && params.FixedTurnLength <= 9 {
if params.FixedTurnLength == 2 {
return p.getRandTurnLength(header)
}
return big.NewInt(int64(params.FixedTurnLength)), nil
}

method := "getTurnLength"
data, err := p.validatorSetABI.Pack(method)
if err != nil {
log.Error("Unable to pack tx for getTurnLength", "error", err)
return nil, err
}

// do smart contract call
msgData := Bytes(data)
_, returnData, err := p.systemCall(header.Coinbase, systemcontracts.ValidatorContract, msgData[:], ibs, header, u256.Num0)
if err != nil {
return nil, err
}

err = p.validatorSetABI.UnpackIntoInterface(&turnLength, method, returnData)
return turnLength, err
}

// getRandTurnLength returns a random valid value, used to test switching turn length
func (p *Parlia) getRandTurnLength(header *types.Header) (turnLength *big.Int, err error) {
turnLengths := [8]uint8{1, 3, 4, 5, 6, 7, 8, 9}
r := mrand.New(mrand.NewSource(int64(header.Time)))
lengthIndex := int(r.Int31n(int32(len(turnLengths))))
return big.NewInt(int64(turnLengths[lengthIndex])), nil
}
124 changes: 112 additions & 12 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"sort"
"strings"
Expand Down Expand Up @@ -55,10 +56,12 @@ const (

CheckpointInterval = 1024 // Number of blocks after which to save the snapshot to the database
defaultEpochLength = uint64(100) // Default number of blocks of checkpoint to update validatorSet from contract
defaultTurnLength = uint8(1) // Default consecutive number of blocks a validator receives priority for block production

extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
nextForkHashSize = 4 // Fixed number of extra-data suffix bytes reserved for nextForkHash.
turnLengthSize = 1 // Fixed number of extra-data suffix bytes reserved for turnLength

validatorBytesLengthBeforeLuban = length.Addr
validatorBytesLength = length.Addr + types.BLSPublicKeyLength
Expand Down Expand Up @@ -123,6 +126,10 @@ var (
// invalid list of validators (i.e. non divisible by 20 bytes).
errInvalidSpanValidators = errors.New("invalid validator list on sprint end block")

// errInvalidTurnLength is returned if a block contains an
// invalid length of turn (i.e. no data left after parsing validators).
errInvalidTurnLength = errors.New("invalid turnLength")

// errInvalidMixDigest is returned if a block's mix digest is non-zero.
errInvalidMixDigest = errors.New("non-zero mix digest")

Expand All @@ -133,6 +140,10 @@ var (
// list of validators different than the one the local node calculated.
errMismatchingEpochValidators = errors.New("mismatching validator list on epoch block")

// errMismatchingEpochTurnLength is returned if a sprint block contains a
// turn length different than the one the local node calculated.
errMismatchingEpochTurnLength = errors.New("mismatching turn length on epoch block")

// errInvalidDifficulty is returned if the difficulty of a block is missing.
errInvalidDifficulty = errors.New("invalid difficulty")

Expand Down Expand Up @@ -334,6 +345,7 @@ func (p *Parlia) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*typ
// On Luban fork, we introduce vote attestation into the header's extra field, so extra format is different from before.
// Before Luban fork: |---Extra Vanity---|---Validators Bytes (or Empty)---|---Extra Seal---|
// After Luban fork: |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---|
// After bohr fork: |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Turn Length (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---|
func getValidatorBytesFromHeader(header *types.Header, chainConfig *chain.Config, parliaConfig *chain.ParliaConfig) []byte {
if len(header.Extra) <= extraVanity+extraSeal {
return nil
Expand All @@ -350,11 +362,15 @@ func getValidatorBytesFromHeader(header *types.Header, chainConfig *chain.Config
return nil
}
num := int(header.Extra[extraVanity])
if num == 0 || len(header.Extra) <= extraVanity+extraSeal+num*validatorBytesLength {
return nil
}
start := extraVanity + validatorNumberSize
end := start + num*validatorBytesLength
extraMinLen := end + extraSeal
if chainConfig.IsBohr(header.Number.Uint64(), header.Time) {
extraMinLen += turnLengthSize
}
if num == 0 || len(header.Extra) < extraMinLen {
return nil
}
return header.Extra[start:end]
}

Expand All @@ -373,11 +389,14 @@ func getVoteAttestationFromHeader(header *types.Header, chainConfig *chain.Confi
attestationBytes = header.Extra[extraVanity : len(header.Extra)-extraSeal]
} else {
num := int(header.Extra[extraVanity])
if len(header.Extra) <= extraVanity+extraSeal+validatorNumberSize+num*validatorBytesLength {
return nil, nil
}
start := extraVanity + validatorNumberSize + num*validatorBytesLength
if chainConfig.IsBohr(header.Number.Uint64(), header.Time) {
start += turnLengthSize
}
end := len(header.Extra) - extraSeal
if end <= start {
return nil, nil
}
attestationBytes = header.Extra[start:end]
}

Expand Down Expand Up @@ -565,8 +584,15 @@ func (p *Parlia) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
return err
}
} else {
if err := misc.VerifyPresenceOfCancunHeaderFields(header); err != nil {
return err
bohr := chain.Config().IsBohr(header.Number.Uint64(), header.Time)
if bohr {
if err := misc.VerifyPresenceOfBohrHeaderFields(header); err != nil {
return err
}
} else {
if err := misc.VerifyPresenceOfCancunHeaderFields(header); err != nil {
return err
}
}
}

Expand Down Expand Up @@ -664,7 +690,7 @@ func (p *Parlia) verifySeal(chain consensus.ChainHeaderReader, header *types.Hea
return fmt.Errorf("parlia.verifySeal: headerNum=%d, validator=%x, %w", header.Number.Uint64(), signer.Bytes(), errUnauthorizedValidator)
}

if snap.signedRecently(signer) {
if snap.SignRecently(signer) {
return errRecentlySigned
}

Expand Down Expand Up @@ -715,6 +741,24 @@ func (p *Parlia) prepareValidators(header *types.Header, chain consensus.ChainHe
return nil
}

func (p *Parlia) prepareTurnLength(chain consensus.ChainHeaderReader, header *types.Header, ibs *state.IntraBlockState) error {
if header.Number.Uint64()%p.config.Epoch != 0 ||
!p.chainConfig.IsBohr(header.Number.Uint64(), header.Time) {
return nil
}

turnLength, err := p.getTurnLength(chain, header, ibs)
if err != nil {
return err
}

if turnLength != nil {
header.Extra = append(header.Extra, *turnLength)
}

return nil
}

// snapshot retrieves the authorization snapshot at a given point in time.
// !!! be careful
// the block with `number` and `hash` is just the last element of `parents`,
Expand Down Expand Up @@ -866,6 +910,10 @@ func (p *Parlia) Prepare(chain consensus.ChainHeaderReader, header *types.Header
return err
}

if err := p.prepareTurnLength(chain, header, ibs); err != nil {
return err
}

// add extra seal space
header.Extra = append(header.Extra, make([]byte, extraSeal)...)

Expand Down Expand Up @@ -919,6 +967,30 @@ func (p *Parlia) verifyValidators(header, parentHeader *types.Header, state *sta
return nil
}

func (p *Parlia) verifyTurnLength(chain consensus.ChainHeaderReader, header *types.Header, ibs *state.IntraBlockState) error {
if header.Number.Uint64()%p.config.Epoch != 0 ||
!p.chainConfig.IsBohr(header.Number.Uint64(), header.Time) {
return nil
}

turnLengthFromHeader, err := parseTurnLength(header, p.chainConfig, p.config)
if err != nil {
return err
}
if turnLengthFromHeader != nil {
turnLength, err := p.getTurnLength(chain, header, ibs)
if err != nil {
return err
}
if turnLength != nil && *turnLength == *turnLengthFromHeader {
log.Debug("verifyTurnLength", "turnLength", *turnLength)
return nil
}
}

return errMismatchingEpochTurnLength
}

// Initialize runs any pre-transaction state modifications (e.g. epoch start)
func (p *Parlia) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header,
state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger) {
Expand Down Expand Up @@ -983,6 +1055,10 @@ func (p *Parlia) finalize(header *types.Header, state *state.IntraBlockState, tx
return nil, nil, err
}

if err := p.verifyTurnLength(chain, header, state); err != nil {
return nil, nil, err
}

if p.chainConfig.IsFeynman(header.Number.Uint64(), header.Time) {
systemcontracts.UpgradeBuildInSystemContract(p.chainConfig, header.Number, parentHeader.Time, header.Time, state, logger)
}
Expand All @@ -1001,10 +1077,10 @@ func (p *Parlia) finalize(header *types.Header, state *state.IntraBlockState, tx
}
}
if header.Difficulty.Cmp(diffInTurn) != 0 {
spoiledVal := snap.supposeValidator()
spoiledVal := snap.inturnValidator()
signedRecently := false
if p.chainConfig.IsPlato(header.Number.Uint64()) {
if snap.signedRecently(spoiledVal) {
if snap.SignRecently(spoiledVal) {
signedRecently = true
}
} else {
Expand Down Expand Up @@ -1244,12 +1320,36 @@ func (p *Parlia) Seal(chain consensus.ChainHeaderReader, block *types.Block, res
return nil
}

func encodeSigHeaderWithoutVoteAttestation(w io.Writer, header *types.Header, chainId *big.Int) {
err := rlp.Encode(w, []interface{}{
chainId,
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:extraVanity], // this will panic if extra is too short, should check before calling encodeSigHeaderWithoutVoteAttestation
header.MixDigest,
header.Nonce,
})
if err != nil {
panic("can't encode: " + err.Error())
}
}

// SealHash returns the hash of a block prior to it being sealed.
func (p *Parlia) SealHash(header *types.Header) (hash libcommon.Hash) {
hasher := cryptopool.NewLegacyKeccak256()
defer cryptopool.ReturnToPoolKeccak256(hasher)

types.EncodeSigHeaderWithoutVoteAttestation(hasher, header, p.chainConfig.ChainID)
encodeSigHeaderWithoutVoteAttestation(hasher, header, p.chainConfig.ChainID)
hasher.Sum(hash[:0])
return hash
}
Expand Down
Loading
Loading