Skip to content

Commit

Permalink
BEP-341: Validators can produce consecutive blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanBSC committed May 22, 2024
1 parent d7b9866 commit 3193cf0
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 62 deletions.
13 changes: 13 additions & 0 deletions consensus/parlia/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,19 @@ const validatorSetABIBeforeLuban = `
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getTurnTerm",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
Expand Down
47 changes: 47 additions & 0 deletions consensus/parlia/haberfork.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package parlia

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/systemcontracts"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)

func (p *Parlia) getTurnTerm(header *types.Header) (turnTerm *big.Int, err error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

method := "getTurnTerm"
toAddress := common.HexToAddress(systemcontracts.ValidatorContract)
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))

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

blockNr := rpc.BlockNumberOrHashWithHash(header.Hash(), false)
result, err := p.ethAPI.Call(ctx, ethapi.TransactionArgs{
Gas: &gas,
To: &toAddress,
Data: &msgData,
}, &blockNr, nil, nil)
if err != nil {
return nil, err
}

if err := p.validatorSetABI.UnpackIntoInterface(&turnTerm, method, result); err != nil {
return nil, err
}

return turnTerm, nil
}
115 changes: 91 additions & 24 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,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
defaultTurnTerm = uint64(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.
turnTermSize = 1 // Fixed number of extra-data suffix bytes reserved for turnTerm

validatorBytesLengthBeforeLuban = common.AddressLength
validatorBytesLength = common.AddressLength + types.BLSPublicKeyLength
Expand Down Expand Up @@ -136,6 +138,10 @@ var (
// list of validators different than the one the local node calculated.
errMismatchingEpochValidators = errors.New("mismatching validator list on epoch block")

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

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

Expand Down Expand Up @@ -365,6 +371,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 haber fork: |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Turn Term (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---|
func getValidatorBytesFromHeader(header *types.Header, chainConfig *params.ChainConfig, parliaConfig *params.ParliaConfig) []byte {
if len(header.Extra) <= extraVanity+extraSeal {
return nil
Expand All @@ -381,11 +388,11 @@ func getValidatorBytesFromHeader(header *types.Header, chainConfig *params.Chain
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
if num == 0 || len(header.Extra) < end+extraSeal {
return nil
}
return header.Extra[start:end]
}

Expand All @@ -404,11 +411,14 @@ func getVoteAttestationFromHeader(header *types.Header, chainConfig *params.Chai
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.IsHaber(header.Number, header.Time) {
start += turnTermSize
}
end := len(header.Extra) - extraSeal
if end <= start {
return nil, nil
}
attestationBytes = header.Extra[start:end]
}

Expand Down Expand Up @@ -884,6 +894,33 @@ func (p *Parlia) prepareValidators(header *types.Header) error {
return nil
}

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

parent := chain.GetHeaderByHash(header.ParentHash)
if parent == nil {
return errors.New("parent not found")
}

if p.chainConfig.IsHaber(parent.Number, parent.Time) {
turnTerm, err := p.getTurnTerm(parent)
if err != nil {
return err
}
if turnTerm == nil {
return errors.New("unexpected error when getTurnTerm")
}
header.Extra = append(header.Extra, byte(int(turnTerm.Int64())))
} else {
header.Extra = append(header.Extra, byte(int(defaultTurnTerm)))
}

return nil
}

func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, header *types.Header) error {
if !p.chainConfig.IsLuban(header.Number) || header.Number.Uint64() < 2 {
return nil
Expand Down Expand Up @@ -1015,6 +1052,9 @@ func (p *Parlia) Prepare(chain consensus.ChainHeaderReader, header *types.Header
return err
}

if err := p.prepareTurnTerm(chain, header); err != nil {
return err
}
// add extra seal space
header.Extra = append(header.Extra, make([]byte, extraSeal)...)

Expand Down Expand Up @@ -1065,6 +1105,40 @@ func (p *Parlia) verifyValidators(header *types.Header) error {
return nil
}

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

turnTermFromHeader, err := parseTurnTerm(header, p.chainConfig, p.config)
if err != nil {
return err
}
if turnTermFromHeader != nil {
parent := chain.GetHeaderByHash(header.ParentHash)
if parent == nil {
return errors.New("parent not found")
}

if p.chainConfig.IsHaber(parent.Number, parent.Time) {
turnTermFromContract, err := p.getTurnTerm(parent)
if err != nil {
return err
}
if turnTermFromContract != nil && uint8(turnTermFromContract.Int64()) == *turnTermFromHeader {
return nil
}
} else {
if uint8(defaultTurnTerm) == *turnTermFromHeader {
return nil
}
}
}

return errMismatchingEpochTurnTerm
}

func (p *Parlia) distributeFinalityReward(chain consensus.ChainHeaderReader, state *state.StateDB, header *types.Header,
cx core.ChainContext, txs *[]*types.Transaction, receipts *[]*types.Receipt, systemTxs *[]*types.Transaction,
usedGas *uint64, mining bool) error {
Expand Down Expand Up @@ -1159,6 +1233,10 @@ func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
return err
}

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

cx := chainContext{Chain: chain, parlia: p}

parent := chain.GetHeaderByHash(header.ParentHash)
Expand All @@ -1185,7 +1263,7 @@ func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
}
}
if header.Difficulty.Cmp(diffInTurn) != 0 {
spoiledVal := snap.supposeValidator()
spoiledVal := snap.inturnValidator()
signedRecently := false
if p.chainConfig.IsPlato(header.Number) {
signedRecently = snap.SignRecently(spoiledVal)
Expand Down Expand Up @@ -1276,7 +1354,7 @@ func (p *Parlia) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
if err != nil {
return nil, nil, err
}
spoiledVal := snap.supposeValidator()
spoiledVal := snap.inturnValidator()
signedRecently := false
if p.chainConfig.IsPlato(header.Number) {
signedRecently = snap.SignRecently(spoiledVal)
Expand Down Expand Up @@ -1905,26 +1983,15 @@ func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Ad
delay := initialBackOffTime
validators := snap.validators()
if p.chainConfig.IsPlanck(header.Number) {
// reverse the key/value of snap.Recents to get recentsMap
recentsMap := make(map[common.Address]uint64, len(snap.Recents))
bound := uint64(0)
if n, limit := header.Number.Uint64(), uint64(len(validators)/2+1); n > limit {
bound = n - limit
}
for seen, recent := range snap.Recents {
if seen <= bound {
continue
}
recentsMap[recent] = seen
}
counts := snap.countRecents()

// The backOffTime does not matter when a validator has signed recently.
if _, ok := recentsMap[val]; ok {
if snap.signRecentlyByCounts(val, counts) {
return 0
}

inTurnAddr := validators[(snap.Number+1)%uint64(len(validators))]
if _, ok := recentsMap[inTurnAddr]; ok {
inTurnAddr := snap.inturnValidator()
if snap.signRecentlyByCounts(inTurnAddr, counts) {
log.Debug("in turn validator has recently signed, skip initialBackOffTime",
"inTurnAddr", inTurnAddr)
delay = 0
Expand All @@ -1933,7 +2000,7 @@ func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Ad
// Exclude the recently signed validators
temp := make([]common.Address, 0, len(validators))
for _, addr := range validators {
if _, ok := recentsMap[addr]; ok {
if snap.signRecentlyByCounts(addr, counts) {
continue
}
temp = append(temp, addr)
Expand Down
Loading

0 comments on commit 3193cf0

Please sign in to comment.