Skip to content

Commit

Permalink
Impl/fip 0023 (#4569)
Browse files Browse the repository at this point in the history
* Implement FIP-0023 (Break ties between tipsets of equal weight)

* rm unused code
  • Loading branch information
hunjixin authored Oct 12, 2021
1 parent 7bf5e2e commit dac19d7
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 5 deletions.
74 changes: 74 additions & 0 deletions pkg/chainsync/syncer/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,11 +654,85 @@ func (syncer *Syncer) SetHead(ctx context.Context, ts *types.TipSet) error {

// If it is the heaviest update the chainStore.
if heavier {
exceeds, err := syncer.exceedsForkLength(head, ts)
if err != nil {
return err
}
if exceeds {
return nil
}
return syncer.chainStore.SetHead(ctx, ts)
}
return nil
}

// Check if the two tipsets have a fork length above `ForkLengthThreshold`.
// `synced` is the head of the chain we are currently synced to and `external`
// is the incoming tipset potentially belonging to a forked chain. It assumes
// the external chain has already been validated and available in the ChainStore.
// The "fast forward" case is covered in this logic as a valid fork of length 0.
//
// FIXME: We may want to replace some of the logic in `syncFork()` with this.
// `syncFork()` counts the length on both sides of the fork at the moment (we
// need to settle on that) but here we just enforce it on the `synced` side.
func (syncer *Syncer) exceedsForkLength(synced, external *types.TipSet) (bool, error) {
if synced == nil || external == nil {
// FIXME: If `cs.heaviest` is nil we should just bypass the entire
// `MaybeTakeHeavierTipSet` logic (instead of each of the called
// functions having to handle the nil case on their own).
return false, nil
}

var err error
// `forkLength`: number of tipsets we need to walk back from the our `synced`
// chain to the common ancestor with the new `external` head in order to
// adopt the fork.
for forkLength := 0; forkLength < int(constants.ForkLengthThreshold); forkLength++ {
// First walk back as many tipsets in the external chain to match the
// `synced` height to compare them. If we go past the `synced` height
// the subsequent match will fail but it will still be useful to get
// closer to the `synced` head parent's height in the next loop.
for external.Height() > synced.Height() {
if external.Height() == 0 {
// We reached the genesis of the external chain without a match;
// this is considered a fork outside the allowed limit (of "infinite"
// length).
return true, nil
}

external, err = syncer.chainStore.GetTipSet(external.Parents())
if err != nil {
return false, xerrors.Errorf("failed to load parent tipset in external chain: %w", err)
}
}

// Now check if we arrived at the common ancestor.
if synced.Equals(external) {
return false, nil
}

// Now check to see if we've walked back to the checkpoint.
if synced.Key().Equals(syncer.checkPoint) {
return true, nil
}

// If we didn't, go back *one* tipset on the `synced` side (incrementing
// the `forkLength`).
if synced.Height() == 0 {
// Same check as the `external` side, if we reach the start (genesis)
// there is no common ancestor.
return true, nil
}
synced, err = syncer.chainStore.GetTipSet(synced.Parents())
if err != nil {
return false, xerrors.Errorf("failed to load parent tipset in synced chain: %w", err)
}
}

// We traversed the fork length allowed without finding a common ancestor.
return true, nil
}

// TODO: this function effectively accepts unchecked input from the network,
// either validate it here, or ensure that its validated elsewhere (maybe make
// sure the blocksync code checks it?)
Expand Down
1 change: 0 additions & 1 deletion pkg/consensus/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

blockadt "github.com/filecoin-project/specs-actors/actors/util/adt"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/prometheus/common/log"
cbg "github.com/whyrusleeping/cbor-gen"

"github.com/Gurpartap/async"
Expand Down
32 changes: 28 additions & 4 deletions pkg/consensus/chain_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package consensus
import (
"context"
"errors"
logging "github.com/ipfs/go-log/v2"
"math/big"

fbig "github.com/filecoin-project/go-state-types/big"
Expand All @@ -17,6 +18,8 @@ import (
"github.com/filecoin-project/venus/pkg/state"
)

var log = logging.Logger("chain_selector")

// ChainSelector weighs and compares chains.
type ChainSelector struct {
cstore cbor.IpldStore
Expand Down Expand Up @@ -86,10 +89,31 @@ func (c *ChainSelector) IsHeavier(ctx context.Context, a, b *types.TipSet) (bool
if err != nil {
return false, err
}
// Without ties pass along the comparison.
if !aW.Equals(bW) {
return aW.GreaterThan(bW), nil

heavier := aW.GreaterThan(bW)
if aW.Equals(bW) && !a.Equals(b) {
log.Errorw("weight draw", "currTs", a, "ts", b)
heavier = breakWeightTie(a, b)
}

return heavier, nil
}

// true if ts1 wins according to the filecoin tie-break rule
func breakWeightTie(ts1, ts2 *types.TipSet) bool {
s := len(ts1.Blocks())
if s > len(ts2.Blocks()) {
s = len(ts2.Blocks())
}

// blocks are already sorted by ticket
for i := 0; i < s; i++ {
if ts1.Blocks()[i].Ticket.Less(&ts2.Blocks()[i].Ticket) {
log.Infof("weight tie broken in favour of %s", ts1.Key())
return true
}
}

return a.Len() > b.Len(), nil
log.Infof("weight tie left unbroken, default to %s", ts2.Key())
return false
}

0 comments on commit dac19d7

Please sign in to comment.