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

node: new function for canonical chain selection #419

Merged
merged 3 commits into from Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 0 additions & 19 deletions block/header.go
Expand Up @@ -6,7 +6,6 @@
package block

import (
"bytes"
"encoding/binary"
"fmt"
"io"
Expand Down Expand Up @@ -248,24 +247,6 @@ func (h *Header) String() string {
h.body.TxsRootFeatures.Root, h.body.TxsRootFeatures.Features, h.body.StateRoot, h.body.ReceiptsRoot, h.body.Extension.BackerSignaturesRoot, h.body.Extension.TotalQuality, h.body.Signature)
}

// BetterThan return if this block is better than other one.
func (h *Header) BetterThan(other *Header) bool {
s1 := h.TotalScore()
s2 := other.TotalScore()

if s1 > s2 {
return true
}
if s1 < s2 {
return false
}
// total scores are equal

// smaller ID is preferred, since block with smaller ID usually has larger average score.
// also, it's a deterministic decision.
return bytes.Compare(h.ID().Bytes(), other.ID().Bytes()) < 0
}

// Number extract block number from block id.
func Number(blockID thor.Bytes32) uint32 {
// first 4 bytes are over written by block number (big endian).
Expand Down
57 changes: 0 additions & 57 deletions block/header_test.go

This file was deleted.

153 changes: 127 additions & 26 deletions cmd/thor/node/node.go
Expand Up @@ -6,6 +6,7 @@
package node

import (
"bytes"
"context"
"fmt"
"sort"
Expand Down Expand Up @@ -254,7 +255,6 @@ func (n *Node) txStashLoop(ctx context.Context) {
}

func (n *Node) processBlock(blk *block.Block, stats *blockStats) (bool, error) {

// consensus object is not thread-safe
n.consLock.Lock()
startTime := mclock.Now()
Expand Down Expand Up @@ -298,53 +298,154 @@ func (n *Node) commitBlock(stage *state.Stage, newBlock *block.Block, receipts t
n.commitLock.Lock()
defer n.commitLock.Unlock()

var (
prevBest = n.repo.BestBlock()
becomeNewBest = newBlock.Header().BetterThan(prevBest.Header())
awaitLog = func() {}
)
defer awaitLog()

if becomeNewBest && !n.skipLogs && !n.logDBFailed {
done := make(chan struct{})
awaitLog = func() { <-done }
if _, err := stage.Commit(); err != nil {
return nil, nil, errors.Wrap(err, "commit state")
}
if err := n.repo.AddBlock(newBlock, receipts); err != nil {
return nil, nil, errors.Wrap(err, "add block")
}

go func() {
defer close(done)
var prevBest = n.repo.BestBlock()
becomeNewBest, err := n.compare(newBlock.Header(), prevBest.Header())
if err != nil {
return nil, nil, errors.Wrap(err, "compare head")
}

if becomeNewBest {
if !n.skipLogs && !n.logDBFailed {
diff, err := n.repo.NewChain(newBlock.Header().ParentID()).Exclude(
n.repo.NewChain(prevBest.Header().ID()))
if err != nil {
n.logDBFailed = true
log.Warn("failed to write logs", "err", err)
return
}

if err := n.writeLogs(diff, newBlock, receipts); err != nil {
n.logDBFailed = true
log.Warn("failed to write logs", "err", err)
return
}
}()
}
if err := n.repo.SetBestBlockID(newBlock.Header().ID()); err != nil {
return nil, nil, err
}
}

if _, err := stage.Commit(); err != nil {
return nil, nil, errors.Wrap(err, "commit state")
return n.repo.NewChain(prevBest.Header().ID()), n.repo.NewBestChain(), nil
}

// build forks comparing two heads.
func (n *Node) buildFork(b1 *block.Header, b2 *block.Header) (ancestor *block.Header, br1 []thor.Bytes32, br2 []thor.Bytes32, err error) {
c1 := n.repo.NewChain(b1.ID())
c2 := n.repo.NewChain(b2.ID())

br1, err = c1.Exclude(c2)
if err != nil {
return
}
br2, err = c2.Exclude(c1)
if err != nil {
return
}

if err := n.repo.AddBlock(newBlock, receipts); err != nil {
return nil, nil, errors.Wrap(err, "add block")
var ancestorNumber uint32
if len(br1) > 0 {
ancestorNumber = block.Number(br1[0]) - 1
} else {
ancestorNumber = block.Number(br2[0]) - 1
}

if becomeNewBest {
// to wait for log written
awaitLog()
if err := n.repo.SetBestBlockID(newBlock.Header().ID()); err != nil {
return nil, nil, err
ancestor, err = c1.GetBlockHeader(ancestorNumber)
if err != nil {
return
}

return
}

// giving the list of blockID in ascending order, find the latest heavy block.
func (n *Node) findLatestHeavyBlock(ids []thor.Bytes32) (*block.Header, error) {
for i := len(ids) - 1; i >= 0; i-- {
sum, err := n.repo.GetBlockSummary(ids[i])
if err != nil {
return nil, err
}

parent, err := n.repo.GetBlockSummary(sum.Header.ParentID())
if err != nil {
return nil, err
}

if sum.Header.TotalQuality() > parent.Header.TotalQuality() {
return sum.Header, nil
}
}
return nil, nil
}

return n.repo.NewChain(prevBest.Header().ID()), n.repo.NewBestChain(), nil
// compare compares two chains, returns true if a>b.
func (n *Node) compare(b1 *block.Header, b2 *block.Header) (bool, error) {
q1 := b1.TotalQuality()
q2 := b2.TotalQuality()

if q1 > q2 {
return true, nil
}
if q1 < q2 {
return false, nil
}

// total quality are equal, find the latest heavy block on both branches
// later heavy block is better

if q1 > 0 {
// Non-zero quality means blocks are at post VIP-193 stage
ancestor, br1, br2, err := n.buildFork(b1, b2)
if err != nil {
return false, errors.Wrap(err, "build fork")
}
if len(br2) == 0 {
// c1 includes c2
return true, nil
}
if len(br1) == 0 {
// c2 includes c1
return false, nil
}

if q1 > ancestor.TotalQuality() {
h1, err := n.findLatestHeavyBlock(br1)
if err != nil {
return false, err
}

h2, err := n.findLatestHeavyBlock(br2)
if err != nil {
return false, err
}

if h1.Timestamp() > h2.Timestamp() {
return true, nil
}

if h1.Timestamp() < h2.Timestamp() {
return false, nil
}
}
}

s1 := b1.TotalScore()
s2 := b2.TotalScore()
if s1 > s2 {
return true, nil
}
if s2 < s1 {
return false, nil
}
// total scores are equal

// smaller ID is preferred, since block with smaller ID usually has larger average score.
// also, it's a deterministic decision.
return bytes.Compare(b1.ID().Bytes(), b2.ID().Bytes()) < 0, nil
}

func (n *Node) writeLogs(diff []thor.Bytes32, newBlock *block.Block, newReceipts tx.Receipts) error {
Expand Down