Skip to content

Commit

Permalink
all: poc implement for reducing block interval
Browse files Browse the repository at this point in the history
params: change unit of ParliaConfig.Period to millisecond from second

consensus/parlia: add a mechanism to update block interval

core/types: use ParentBeaconRoot to represent milliseconds

consensus/parlia: micro adjust initial back off time
  • Loading branch information
buddh0 committed Jan 23, 2025
1 parent 0e571c7 commit 5c8d5b7
Show file tree
Hide file tree
Showing 18 changed files with 118 additions and 58 deletions.
2 changes: 1 addition & 1 deletion common/bidutil/bidutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func BidBetterBefore(parentHeader *types.Header, blockPeriod uint64, delayLeftOv
// BidMustBefore returns the time when the next bid must be received,
// only considering the consensus delay but not bid simulation duration.
func BidMustBefore(parentHeader *types.Header, blockPeriod uint64, delayLeftOver time.Duration) time.Time {
nextHeaderTime := time.Unix(int64(parentHeader.Time+blockPeriod), 0)
nextHeaderTime := time.UnixMilli(int64(parentHeader.TimeInMilliseconds() + blockPeriod))
nextHeaderTime = nextHeaderTime.Add(-delayLeftOver)
return nextHeaderTime
}
4 changes: 4 additions & 0 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ func (beacon *Beacon) Delay(_ consensus.ChainReader, _ *types.Header, _ *time.Du
return nil
}

func (beacon *Beacon) BlockInterval(chain consensus.ChainHeaderReader, header *types.Header) (uint64, error) {
panic("not supported")
}

// Finalize implements consensus.Engine, setting the final state on the header
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, txs *[]*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal, _ *[]*types.Receipt, _ *[]*types.Transaction, _ *uint64, tracer *tracing.Hooks) error {
// Finalize is different with Prepare, it can be used in both block verification.
Expand Down
4 changes: 4 additions & 0 deletions consensus/clique/clique.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,10 @@ func (c *Clique) Delay(chain consensus.ChainReader, header *types.Header, leftOv
return nil
}

func (c *Clique) BlockInterval(chain consensus.ChainHeaderReader, header *types.Header) (uint64, error) {
return c.config.Period * 1000, nil
}

// Seal implements consensus.Engine, attempting to create a sealed block using
// the local signing credentials.
func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
Expand Down
4 changes: 3 additions & 1 deletion consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ type Engine interface {
// Delay returns the max duration the miner can commit txs
Delay(chain ChainReader, header *types.Header, leftOver *time.Duration) *time.Duration

// BlockInterval returns the block interval in milliseconds after given header applied
BlockInterval(chain ChainHeaderReader, header *types.Header) (uint64, error)

// Close terminates any background threads maintained by the consensus engine.
Close() error
}
Expand All @@ -162,6 +165,5 @@ type PoSA interface {
GetFinalizedHeader(chain ChainHeaderReader, header *types.Header) *types.Header
VerifyVote(chain ChainHeaderReader, vote *types.VoteEnvelope) error
IsActiveValidatorAt(chain ChainHeaderReader, header *types.Header, checkVoteKeyFn func(bLSPublicKey *types.BLSPublicKey) bool) bool
BlockInterval() uint64
NextProposalBlock(chain ChainHeaderReader, header *types.Header, proposer common.Address) (uint64, uint64, error)
}
4 changes: 4 additions & 0 deletions consensus/ethash/ethash.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,7 @@ func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API {
func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
panic("ethash (pow) sealing not supported any more")
}

func (ethash *Ethash) BlockInterval(chain consensus.ChainHeaderReader, header *types.Header) (uint64, error) {
panic("not supported")
}
3 changes: 1 addition & 2 deletions consensus/misc/eip1559/eip1559_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ func config() *params.ChainConfig {
config := copyConfig(params.TestChainConfig)
config.Ethash = nil
config.Parlia = &params.ParliaConfig{
Period: 3,
Epoch: 200,
Epoch: 200,
}
config.LondonBlock = big.NewInt(5)
return config
Expand Down
73 changes: 47 additions & 26 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ const (
inMemorySignatures = 4096 // Number of recent block signatures to keep in memory
inMemoryHeaders = 86400 // Number of recent headers to keep in memory for double sign detection,

checkpointInterval = 1024 // Number of blocks after which to save the snapshot to the database
defaultEpochLength = uint64(200) // 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
checkpointInterval = 1024 // Number of blocks after which to save the snapshot to the database
defaultEpochLength = uint64(200) // Default number of blocks of checkpoint to update validatorSet from contract
defaultBlockInterval = uint16(3000) // Default block interval in milliseconds
lorentzBlockInterval = uint16(500) // Block interval starting from the Lorentz hard fork
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
Expand All @@ -66,8 +68,8 @@ const (
validatorBytesLength = common.AddressLength + types.BLSPublicKeyLength
validatorNumberSize = 1 // Fixed number of extra prefix bytes reserved for validator number after Luban

wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers
initialBackOffTime = uint64(1) // second
wiggleTime = uint64(1000) // milliseconds, Random delay (per signer) to allow concurrent signers
defaultInitialBackOffTime = uint64(1000) // milliseconds, Default backoff time for the second validator permitted to produce blocks

systemRewardPercent = 4 // it means 1/2^4 = 1/16 percentage of gas fee incoming will be distributed to system

Expand Down Expand Up @@ -316,10 +318,6 @@ func New(
return c
}

func (p *Parlia) Period() uint64 {
return p.config.Period
}

func (p *Parlia) IsSystemTransaction(tx *types.Transaction, header *types.Header) (bool, error) {
// deploy a contract
if tx.To() == nil {
Expand Down Expand Up @@ -640,8 +638,18 @@ func (p *Parlia) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
}
} else {
if header.ParentBeaconRoot == nil || *header.ParentBeaconRoot != (common.Hash{}) {
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected zero hash", header.ParentBeaconRoot)
if header.ParentBeaconRoot == nil {
return fmt.Errorf("nil parentBeaconRoot after bohr hard fork")
}
lorentz := chain.Config().IsLorentz(header.Number, header.Time)
if !lorentz {
if *header.ParentBeaconRoot != (common.Hash{}) { // remove it once bsc mainnet passed lorentz hard fork for simplicity
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected zero hash", header.ParentBeaconRoot)
}
} else {
if header.Milliseconds() >= 1000 {
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected the last two bytes to represent milliseconds", header.ParentBeaconRoot)
}
}
}

Expand Down Expand Up @@ -779,6 +787,10 @@ func (p *Parlia) snapshot(chain consensus.ChainHeaderReader, number uint64, hash
// new snapshot
snap = newSnapshot(p.config, p.signatures, number, blockHash, validators, voteAddrs, p.ethAPI)

if p.chainConfig.IsLorentz(checkpoint.Number, checkpoint.Time) {
snap.BlockInterval = lorentzBlockInterval
}

// get turnLength from headers and use that for new turnLength
turnLength, err := parseTurnLength(checkpoint, p.chainConfig, p.config)
if err != nil {
Expand Down Expand Up @@ -1094,9 +1106,13 @@ func (p *Parlia) Prepare(chain consensus.ChainHeaderReader, header *types.Header
if parent == nil {
return consensus.ErrUnknownAncestor
}
header.Time = p.blockTimeForRamanujanFork(snap, header, parent)
if header.Time < uint64(time.Now().Unix()) {
header.Time = uint64(time.Now().Unix())
blockTime := p.blockTimeForRamanujanFork(snap, header, parent)
if now := uint64(time.Now().UnixMilli()); blockTime < now {
blockTime = now
}
header.Time = blockTime / 1000 // get seconds
if p.chainConfig.IsLorentz(header.Number, header.Time) {
header.SetMilliseconds(blockTime % 1000)
}

header.Extra = header.Extra[:extraVanity-nextForkHashSize]
Expand Down Expand Up @@ -1539,9 +1555,9 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header, leftOv
}
delay := p.delayForRamanujanFork(snap, header)

if *leftOver >= time.Duration(p.config.Period)*time.Second {
if *leftOver >= time.Duration(snap.BlockInterval)*time.Millisecond {
// ignore invalid leftOver
log.Error("Delay invalid argument", "leftOver", leftOver.String(), "Period", p.config.Period)
log.Error("Delay invalid argument", "leftOver", leftOver.String(), "Period", snap.BlockInterval)
} else if *leftOver >= delay {
delay = time.Duration(0)
return &delay
Expand All @@ -1550,9 +1566,9 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header, leftOv
}

// The blocking time should be no more than half of period when snap.TurnLength == 1
timeForMining := time.Duration(p.config.Period) * time.Second / 2
timeForMining := time.Duration(snap.BlockInterval) * time.Millisecond / 2
if !snap.lastBlockInOneTurn(header.Number.Uint64()) {
timeForMining = time.Duration(p.config.Period) * time.Second * 2 / 3
timeForMining = time.Duration(snap.BlockInterval) * time.Millisecond * 2 / 3
}
if delay > timeForMining {
delay = timeForMining
Expand All @@ -1570,11 +1586,6 @@ func (p *Parlia) Seal(chain consensus.ChainHeaderReader, block *types.Block, res
if number == 0 {
return errUnknownBlock
}
// For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing)
if p.config.Period == 0 && len(block.Transactions()) == 0 {
log.Info("Sealing paused, waiting for transactions")
return nil
}
// Don't hold the val fields for the entire sealing procedure
p.lock.RLock()
val, signFn := p.val, p.signFn
Expand Down Expand Up @@ -2089,7 +2100,10 @@ func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Ad
log.Debug("backOffTime", "blockNumber", header.Number, "in turn validator", val)
return 0
} else {
delay := initialBackOffTime
delay := defaultInitialBackOffTime
if p.chainConfig.IsLorentz(header.Number, header.Time) && snap.lastBlockInOneTurn(header.Number.Uint64()) {
delay = uint64(snap.BlockInterval) / 2
}
validators := snap.validators()
if p.chainConfig.IsPlanck(header.Number) {
counts := snap.countRecents()
Expand Down Expand Up @@ -2159,8 +2173,15 @@ func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Ad
}
}

func (p *Parlia) BlockInterval() uint64 {
return p.config.Period
func (p *Parlia) BlockInterval(chain consensus.ChainHeaderReader, header *types.Header) (uint64, error) {
if header == nil {
return uint64(defaultBlockInterval), errUnknownBlock
}
snap, err := p.snapshot(chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
return uint64(defaultBlockInterval), err
}
return uint64(snap.BlockInterval), nil
}

func (p *Parlia) NextProposalBlock(chain consensus.ChainHeaderReader, header *types.Header, proposer common.Address) (uint64, uint64, error) {
Expand Down
2 changes: 1 addition & 1 deletion consensus/parlia/parlia_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func producerBlockDelay(candidates map[int]bool, height, numOfValidators int) (i
minCandidate = c
}
}
delay := initialBackOffTime + uint64(minDelay)*wiggleTime
delay := defaultInitialBackOffTime + uint64(minDelay)*wiggleTime
return minCandidate, delay
}

Expand Down
4 changes: 2 additions & 2 deletions consensus/parlia/ramanujanfork.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (p *Parlia) delayForRamanujanFork(snap *Snapshot, header *types.Header) tim
}

func (p *Parlia) blockTimeForRamanujanFork(snap *Snapshot, header, parent *types.Header) uint64 {
blockTime := parent.Time + p.config.Period
blockTime := parent.TimeInMilliseconds() + uint64(snap.BlockInterval)
if p.chainConfig.IsRamanujan(header.Number) {
blockTime = blockTime + p.backOffTime(snap, header, p.val)
}
Expand All @@ -36,7 +36,7 @@ func (p *Parlia) blockTimeForRamanujanFork(snap *Snapshot, header, parent *types

func (p *Parlia) blockTimeVerifyForRamanujanFork(snap *Snapshot, header, parent *types.Header) error {
if p.chainConfig.IsRamanujan(header.Number) {
if header.Time < parent.Time+p.config.Period+p.backOffTime(snap, header, header.Coinbase) {
if header.TimeInMilliseconds() < parent.TimeInMilliseconds()+uint64(snap.BlockInterval)+p.backOffTime(snap, header, header.Coinbase) {
return consensus.ErrFutureBlock
}
}
Expand Down
10 changes: 10 additions & 0 deletions consensus/parlia/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type Snapshot struct {

Number uint64 `json:"number"` // Block number where the snapshot was created
Hash common.Hash `json:"hash"` // Block hash where the snapshot was created
BlockInterval uint16 `json:"block_interval"` // Block Interval in milliseconds
TurnLength uint8 `json:"turn_length"` // Length of `turn`, meaning the consecutive number of blocks a validator receives priority for block production
Validators map[common.Address]*ValidatorInfo `json:"validators"` // Set of authorized validators at this moment
Recents map[uint64]common.Address `json:"recents"` // Set of recent validators for spam protections
Expand Down Expand Up @@ -74,6 +75,7 @@ func newSnapshot(
sigCache: sigCache,
Number: number,
Hash: hash,
BlockInterval: defaultBlockInterval,
TurnLength: defaultTurnLength,
Recents: make(map[uint64]common.Address),
RecentForkHashes: make(map[uint64]string),
Expand Down Expand Up @@ -117,6 +119,9 @@ func loadSnapshot(config *params.ParliaConfig, sigCache *lru.ARCCache, db ethdb.
if err := json.Unmarshal(blob, snap); err != nil {
return nil, err
}
if snap.BlockInterval == 0 { // no BlockInterval field in old snapshots
snap.BlockInterval = defaultBlockInterval
}
if snap.TurnLength == 0 { // no TurnLength field in old snapshots
snap.TurnLength = defaultTurnLength
}
Expand Down Expand Up @@ -145,6 +150,7 @@ func (s *Snapshot) copy() *Snapshot {
sigCache: s.sigCache,
Number: s.Number,
Hash: s.Hash,
BlockInterval: s.BlockInterval,
TurnLength: s.TurnLength,
Validators: make(map[common.Address]*ValidatorInfo),
Recents: make(map[uint64]common.Address),
Expand Down Expand Up @@ -308,6 +314,10 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea
}
}
}
// It's better to set it based on IsOnLorentz, but in practice, the effect is the same as using IsLorentz.
if chainConfig.IsLorentz(header.Number, header.Time) {
snap.BlockInterval = lorentzBlockInterval
}
snap.Recents[number] = validator
snap.RecentForkHashes[number] = hex.EncodeToString(header.Extra[extraVanity-nextForkHashSize : extraVanity])
snap.updateAttestation(header, chainConfig, s.config)
Expand Down
2 changes: 1 addition & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engi
header.ParentBeaconRoot = new(common.Hash)
} else {
header.WithdrawalsHash = &types.EmptyWithdrawalsHash
if cm.config.IsBohr(header.Number, header.Time) {
if cm.config.IsBohr(header.Number, header.Time) && !cm.config.IsLorentz(header.Number, header.Time) {
header.ParentBeaconRoot = new(common.Hash)
}
if cm.config.IsPrague(header.Number, header.Time) {
Expand Down
2 changes: 1 addition & 1 deletion core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
// EIP-4788: The parentBeaconBlockRoot of the genesis block is always
// the zero hash. This is because the genesis block does not have a parent
// by definition.
if conf.Parlia == nil || conf.IsBohr(num, g.Timestamp) {
if conf.Parlia == nil || conf.IsBohr(num, g.Timestamp) && !conf.IsLorentz(num, g.Timestamp) {
head.ParentBeaconRoot = new(common.Hash)
}

Expand Down
18 changes: 18 additions & 0 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (

"golang.org/x/crypto/sha3"

"github.com/holiman/uint256"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
Expand Down Expand Up @@ -156,6 +158,22 @@ func (h *Header) Hash() common.Hash {
return rlpHash(h)
}

func (h *Header) SetMilliseconds(milliseconds uint64) {
tmp := common.Hash(uint256.NewInt(milliseconds % 1000).Bytes32())
h.ParentBeaconRoot = &tmp
}

func (h *Header) Milliseconds() uint64 {
if h.ParentBeaconRoot == nil || *h.ParentBeaconRoot == (common.Hash{}) {
return 0
}
return uint256.NewInt(0).SetBytes2(h.ParentBeaconRoot[30:]).Uint64()
}

func (h *Header) TimeInMilliseconds() uint64 {
return h.Time*1000 + h.Milliseconds()
}

var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size())

// Size returns the approximate memory used by all internal contents. It is used
Expand Down
15 changes: 7 additions & 8 deletions core/vote/vote_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/parlia"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader"
Expand Down Expand Up @@ -147,13 +146,13 @@ func (voteManager *VoteManager) loop() {
}

curHead := cHead.Header
if p, ok := voteManager.engine.(*parlia.Parlia); ok {
nextBlockMinedTime := time.Unix(int64((curHead.Time + p.Period())), 0)
timeForBroadcast := 50 * time.Millisecond // enough to broadcast a vote
if time.Now().Add(timeForBroadcast).After(nextBlockMinedTime) {
log.Warn("too late to vote", "Head.Time(Second)", curHead.Time, "Now(Millisecond)", time.Now().UnixMilli())
continue
}
parentHeader := voteManager.chain.GetHeaderByHash(curHead.ParentHash)
blockInterval, _ := voteManager.engine.BlockInterval(voteManager.chain, parentHeader)
nextBlockMinedTime := time.UnixMilli(int64((curHead.TimeInMilliseconds() + blockInterval)))
timeForBroadcast := 50 * time.Millisecond // enough to broadcast a vote
if time.Now().Add(timeForBroadcast).After(nextBlockMinedTime) {
log.Warn("too late to vote", "Head.Time(Second)", curHead.Time, "Now(Millisecond)", time.Now().UnixMilli())
continue
}

// Check if cur validator is within the validatorSet at curHead
Expand Down
2 changes: 2 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,7 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param
func (api *BlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} {
fields := RPCMarshalHeader(header)
fields["totalDifficulty"] = (*hexutil.Big)(api.b.GetTd(ctx, header.Hash()))
// fields["timestampInMilliseconds"] = hexutil.Uint64(header.TimeInMilliseconds())
return fields
}

Expand All @@ -1443,6 +1444,7 @@ func (api *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, i
fields := RPCMarshalBlock(b, inclTx, fullTx, api.b.ChainConfig())
if inclTx {
fields["totalDifficulty"] = (*hexutil.Big)(api.b.GetTd(ctx, b.Hash()))
// fields["timestampInMilliseconds"] = hexutil.Uint64(b.Header().TimeInMilliseconds())
}
return fields, nil
}
Expand Down
3 changes: 2 additions & 1 deletion miner/bid_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,8 @@ func (b *bidSimulator) newBidLoop() {

func (b *bidSimulator) bidBetterBefore(parentHash common.Hash) time.Time {
parentHeader := b.chain.GetHeaderByHash(parentHash)
return bidutil.BidBetterBefore(parentHeader, b.chainConfig.Parlia.Period, b.delayLeftOver, b.config.BidSimulationLeftOver)
blockInterval, _ := b.engine.BlockInterval(b.chain, parentHeader)
return bidutil.BidBetterBefore(parentHeader, blockInterval, b.delayLeftOver, b.config.BidSimulationLeftOver)
}

func (b *bidSimulator) clearLoop() {
Expand Down
Loading

0 comments on commit 5c8d5b7

Please sign in to comment.