Skip to content

Commit

Permalink
Merge pull request bnb-chain#58 from node-real/modify_fork_choice
Browse files Browse the repository at this point in the history
modify fork choice to prefer higher justified block number
  • Loading branch information
NathanBSC authored Mar 10, 2023
2 parents 16c912a + 6a55acd commit c9a9990
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 192 deletions.
4 changes: 2 additions & 2 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ type PoSA interface {
EnoughDistance(chain ChainReader, header *types.Header) bool
IsLocalBlock(header *types.Header) bool
AllowLightProcess(chain ChainReader, currentHeader *types.Header) bool
GetJustifiedHeader(chain ChainHeaderReader, header *types.Header) *types.Header
GetFinalizedHeader(chain ChainHeaderReader, header *types.Header, backward uint64) *types.Header
GetJustifiedNumberAndHash(chain ChainHeaderReader, header *types.Header) (uint64, common.Hash, error)
GetFinalizedHeader(chain ChainHeaderReader, header *types.Header) *types.Header
VerifyVote(chain ChainHeaderReader, vote *types.VoteEnvelope) error
IsActiveValidatorAt(chain ChainHeaderReader, header *types.Header) bool
}
68 changes: 29 additions & 39 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ const (

validatorBytesLength = common.AddressLength
validatorBytesLengthAfterBoneh = common.AddressLength + types.BLSPublicKeyLength
validatorNumberSizeAfterBoneh = 1 // Fixed number of extra prefix bytes reserved for validator number
naturallyJustifiedDist = 14 // The distance to naturally justify a block
validatorNumberSizeAfterBoneh = 1 // Fixed number of extra prefix bytes reserved for validator number

wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers
initialBackOffTime = uint64(1) // second
Expand Down Expand Up @@ -428,13 +427,13 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header
// The source block should be the highest justified block.
sourceNumber := attestation.Data.SourceNumber
sourceHash := attestation.Data.SourceHash
justified := p.GetJustifiedHeader(chain, parent)
if justified == nil {
return fmt.Errorf("no justified block found")
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, parent)
if err != nil {
return fmt.Errorf("unexpected error when getting the highest justified number and hash")
}
if sourceNumber != justified.Number.Uint64() || sourceHash != justified.Hash() {
if sourceNumber != justifiedBlockNumber || sourceHash != justifiedBlockHash {
return fmt.Errorf("invalid attestation, source mismatch, expected block: %d, hash: %s; real block: %d, hash: %s",
justified.Number.Uint64(), justified.Hash(), sourceNumber, sourceHash)
justifiedBlockNumber, justifiedBlockHash, sourceNumber, sourceHash)
}

// The snapshot should be the targetNumber-1 block's snapshot.
Expand Down Expand Up @@ -819,14 +818,14 @@ func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, head

// Prepare vote attestation
// Prepare vote data
justified := p.GetJustifiedHeader(chain, parent)
if justified == nil {
return errors.New("highest justified block not found")
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, parent)
if err != nil {
return fmt.Errorf("unexpected error when getting the highest justified number and hash")
}
attestation := &types.VoteAttestation{
Data: &types.VoteData{
SourceNumber: justified.Number.Uint64(),
SourceHash: justified.Hash(),
SourceNumber: justifiedBlockNumber,
SourceHash: justifiedBlockHash,
TargetNumber: parent.Number.Uint64(),
TargetHash: parent.Hash(),
},
Expand Down Expand Up @@ -1184,12 +1183,12 @@ func (p *Parlia) VerifyVote(chain consensus.ChainHeaderReader, vote *types.VoteE
return fmt.Errorf("target number mismatch")
}

justifiedHeader := p.GetJustifiedHeader(chain, header)
if justifiedHeader == nil {
log.Error("failed to get the highest justified header", "headerNumber", header.Number, "headerHash", header.Hash())
return fmt.Errorf("BlockHeader at current voteBlockNumber is nil")
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, header)
if err != nil {
log.Error("failed to get the highest justified number and hash", "headerNumber", header.Number, "headerHash", header.Hash())
return fmt.Errorf("unexpected error when getting the highest justified number and hash")
}
if vote.Data.SourceNumber != justifiedHeader.Number.Uint64() || vote.Data.SourceHash != justifiedHeader.Hash() {
if vote.Data.SourceNumber != justifiedBlockNumber || vote.Data.SourceHash != justifiedBlockHash {
return fmt.Errorf("vote source block mismatch")
}

Expand Down Expand Up @@ -1688,41 +1687,32 @@ func (p *Parlia) applyTransaction(
return nil
}

// GetJustifiedHeader returns highest justified block's header before the specific block,
// the attestation within the specific block will be taken into account.
func (p *Parlia) GetJustifiedHeader(chain consensus.ChainHeaderReader, header *types.Header) *types.Header {
// GetJustifiedNumberAndHash returns the highest justified block's number and hash on the branch including and before `header`
func (p *Parlia) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, header *types.Header) (uint64, common.Hash, error) {
if chain == nil || header == nil {
return nil
return 0, common.Hash{}, fmt.Errorf("illegal chain or header")
}

snap, err := p.snapshot(chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
log.Error("Unexpected error when getting snapshot",
"error", err, "blockNumber", header.Number.Uint64(), "blockHash", header.Hash())
return nil
return 0, common.Hash{}, err
}

// If there is no vote justified block, then return root or naturally justified block.
if snap.Attestation == nil {
if header.Number.Uint64() <= naturallyJustifiedDist {
return chain.GetHeaderByNumber(0)
if p.chainConfig.IsBoneh(header.Number) {
log.Debug("once one attestation generated, attestation of snap would not be nil forever basically")
}
// Return naturally justified block.
return FindAncientHeader(header, naturallyJustifiedDist, chain, nil)
}

// If the latest vote justified block is too far, return naturally justified block.
if snap.Number-snap.Attestation.TargetNumber > naturallyJustifiedDist {
return FindAncientHeader(header, naturallyJustifiedDist, chain, nil)
return 0, chain.GetHeaderByNumber(0).Hash(), nil
}
//Return latest vote justified block.
return chain.GetHeaderByHash(snap.Attestation.TargetHash)
return snap.Attestation.TargetNumber, snap.Attestation.TargetHash, nil
}

// GetFinalizedHeader returns highest finalized block header before the specific block.
// It will first to find vote finalized block within the specific backward blocks, the suggested backward blocks is 21.
// If the vote finalized block not found, return its previous backward block.
func (p *Parlia) GetFinalizedHeader(chain consensus.ChainHeaderReader, header *types.Header, backward uint64) *types.Header {
// GetFinalizedHeader returns highest finalized block header.
// It will find vote finalized block within NaturallyFinalizedDist blocks firstly,
// If the vote finalized block not found, return its naturally finalized block.
func (p *Parlia) GetFinalizedHeader(chain consensus.ChainHeaderReader, header *types.Header) *types.Header {
backward := uint64(types.NaturallyFinalizedDist)
if chain == nil || header == nil {
return nil
}
Expand Down
72 changes: 38 additions & 34 deletions consensus/parlia/parlia_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ import (
"golang.org/x/crypto/sha3"

"github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)

const (
upperLimitOfVoteBlockNumber = 11
)

func TestImpactOfValidatorOutOfService(t *testing.T) {
testCases := []struct {
totalValidators int
Expand Down Expand Up @@ -205,25 +210,25 @@ func (b *MockBlock) IsConflicted(a *MockBlock) bool {
return a.blockHash != b.blockHash
}

// GetJustifiedBlock returns highest justified block,
// not include current block's attestation.
func (b *MockBlock) GetJustifiedBlock() *MockBlock {
if b.blockNumber < 3 {
return GenesisBlock
}

parent := b.parent
for i := 0; i < naturallyJustifiedDist && parent.blockNumber > 0; i++ {
// vote justified
if parent.attestation != 0 {
return parent
// GetJustifiedNumberAndHash returns number and hash of the highest justified block,
// keep same func signature with consensus even if `error` will be nil definitely
func (b *MockBlock) GetJustifiedNumberAndHash() (uint64, common.Hash, error) {
justifiedBlock := GenesisBlock
for curBlock := b; curBlock.blockNumber > 1; curBlock = curBlock.parent {
// justified
if curBlock.attestation != 0 {
justifiedBlock = curBlock.parent
break
}

parent = parent.parent
}

// naturally justified
return parent
return justifiedBlock.blockNumber, justifiedBlock.blockHash, nil
}

func (b *MockBlock) GetJustifiedNumber() uint64 {
justifiedBlockNumber, _, _ := b.GetJustifiedNumberAndHash()
return justifiedBlockNumber
}

// GetFinalizedBlock returns highest finalized block,
Expand All @@ -234,7 +239,7 @@ func (b *MockBlock) GetFinalizedBlock() *MockBlock {
}

if b.attestation != 0 && b.parent.attestation != 0 {
return b.parent
return b.parent.parent
}

return b.parent.GetFinalizedBlock()
Expand Down Expand Up @@ -307,25 +312,25 @@ func (v *MockValidator) Vote(block *MockBlock) bool {
}

// Rule 2: No surround vote
justified := block.GetJustifiedBlock()
for targetNumber := justified.blockNumber + 1; targetNumber < block.blockNumber; targetNumber++ {
justifiedBlockNumber, justifiedBlockHash, _ := block.GetJustifiedNumberAndHash()
for targetNumber := justifiedBlockNumber + 1; targetNumber < block.blockNumber; targetNumber++ {
if vote, ok := v.voteRecords[targetNumber]; ok {
if vote.SourceNumber > justified.blockNumber {
if vote.SourceNumber > justifiedBlockNumber {
return false
}
}
}
for targetNumber := block.blockNumber; targetNumber <= block.blockNumber+naturallyJustifiedDist; targetNumber++ {
for targetNumber := block.blockNumber; targetNumber <= block.blockNumber+upperLimitOfVoteBlockNumber; targetNumber++ {
if vote, ok := v.voteRecords[targetNumber]; ok {
if vote.SourceNumber < justified.blockNumber {
if vote.SourceNumber < justifiedBlockNumber {
return false
}
}
}

v.voteRecords[block.blockNumber] = &types.VoteData{
SourceNumber: justified.blockNumber,
SourceHash: justified.blockHash,
SourceNumber: justifiedBlockNumber,
SourceHash: justifiedBlockHash,
TargetNumber: block.blockNumber,
TargetHash: block.blockHash,
}
Expand All @@ -338,11 +343,11 @@ func (v *MockValidator) InsertBlock(block *MockBlock) {
return
}

// The higher finalized block is the longest chain.
if block.GetFinalizedBlock().blockNumber < v.head.GetFinalizedBlock().blockNumber {
// The higher justified block is the longest chain.
if block.GetJustifiedNumber() < v.head.GetJustifiedNumber() {
return
}
if block.GetFinalizedBlock().blockNumber > v.head.GetFinalizedBlock().blockNumber {
if block.GetJustifiedNumber() > v.head.GetJustifiedNumber() {
v.head = block
return
}
Expand Down Expand Up @@ -435,7 +440,7 @@ func (c *Coordinator) AggregateVotes(bs *BlockSimulator, block *MockBlock) error
count++
}

if count > len(c.validators)*2/3 {
if count >= cmath.CeilDiv(len(c.validators)*2, 3) {
c.attestations[block.blockHash] = attestation
}

Expand Down Expand Up @@ -535,7 +540,7 @@ var simulatorTestcases = []*TestSimulatorParam{
{3, 17, 0x1f0001, 0x1ffff1},
{4, 18, 0x1f0001, 0x1ffff1},
{5, 19, 0x1f0001, 0x1ffff1},
{3, 3, 0x00fffe, 0x00fffe}, // justify block2 and finalize block 1
{3, 3, 0x00fffe, 0x00fffe}, // justify block 2 and finalize block 1
{6, 20, 0x1f0001, 0x1fffff},
{4, 4, 0x00fffe, 0x1fffff},
{5, 5, 0x00fffe, 0x1fffff},
Expand All @@ -545,14 +550,14 @@ var simulatorTestcases = []*TestSimulatorParam{
},
},
{
// 21 validators, all active, naturally justified keep finalized block grow
// 21 validators, all active, the finalized fork can keep grow
validatorsNumber: 21,
cs: []*BlockSimulator{
{1, 1, 0x00fffe, 0x00fffe},
{2, 2, 0x00fffe, 0x00fffe}, // The block 3 will never produce
{1, 14, 0x00fffe, 0x00fffe},
{2, 15, 0x00fffe, 0x00fffe}, // The block 3 will never produce
{1, 0, 0x1f0001, 0x1fffff},
{2, 16, 0x1f0001, 0x1fffff},
{3, 1, 0x1f0001, 0x1fffff},
{3, 1, 0x1f0001, 0x1fffff}, // based block produced by 15
{4, 2, 0x1f0001, 0x1fffff},
{5, 3, 0x1f0001, 0x1fffff},
{6, 4, 0x1f0001, 0x1fffff},
Expand Down Expand Up @@ -580,12 +585,11 @@ func TestSimulateP2P(t *testing.T) {
if err != nil {
t.Fatalf("[Testcase %d] simulate P2P error: %v", index, err)
}

for _, val := range c.validators {
t.Logf("[Testcase %d] validator(%d) head block: %d",
index, val.index, val.head.blockNumber)
t.Logf("[Testcase %d] validator(%d) highest justified block: %d",
index, val.index, val.head.GetJustifiedBlock().blockNumber)
index, val.index, val.head.GetJustifiedNumber())
t.Logf("[Testcase %d] validator(%d) highest finalized block: %d",
index, val.index, val.head.GetFinalizedBlock().blockNumber)
}
Expand Down
Loading

0 comments on commit c9a9990

Please sign in to comment.