Skip to content

Commit

Permalink
unified across both specs
Browse files Browse the repository at this point in the history
  • Loading branch information
sternhenri committed Apr 18, 2019
1 parent 4615fed commit a2e8244
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 126 deletions.
117 changes: 35 additions & 82 deletions bootstrap.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Filecoin Bootstrapping Routine

This spec describes how to implement the Filecoin bootstrapping protocol, for related systems, see:
This spec describes the Filecoin bootstrapping protocol, it must be read along with:

- [Sync](sync.md) on how Filecoin nodes sync with bootstrap nodes to catch up to the chain and stay synced after the initial bootstrapping has occured. The `Syncer` interface and many of the methods used here are defined in the sync spec.

For related systems, see:

- [Sync](sync.md) on how Filecoin nodes sync with bootstrap nodes to catch up to the chain and stay synced after the initial bootstrapping has occured.
- [Network Protocols](./network-protocols.md) on how Filecoin nodes can communicate with each other, with for instance, an [initial handshake](./network-protocols.md#hello-handshake), or [block syncing](./network-protocols.md#blocksync).
- [Operation](./operation.md) on various operations a functional Filecoin node needs to run, like [DHT routing](./operation.md#dht-for-peer-routing.md).

Expand All @@ -13,14 +16,16 @@ When a Filecoin node first comes online, it must find the latest head for the ch
At its simplest, bootstrapping can be thought of as secure network joining. It must enable a new node to get the latest head/chain that the majority of the network power is mining off and must be both:

- Robust — able to do so in adversarial networks (so long as honest nodes make up the majority of the network).

- Consistent — able to reliably get the canonical chain (even given poor latency or state loss in our initial peer set).

The specific bootstrapping heuristic could be left up to the node implementation, but we provide a simple one here as a candidate construction. This spec is divided as follows:

- Initial Connection — ensuring the node connects to an honest set of initial peers.

- Peer Set Expansion — ensuring that these peers can serve up the network's canonical head reliably in spite of their individual latencies or non-byzantine state loss.
- Chain Selection — choosing the most likely canonical chain thereafter.


Chain Selection is addressed in the [sync spec](./sync.md).

We leave trustless initial peer selection as future work.

Expand Down Expand Up @@ -49,29 +54,21 @@ We will use these new peers to sync with the chain, using the chain heads from o
We assume no long-lived network partitions in Filecoin, meaning these confirmed heads will all be on the same chain (though potentially at different heights). A node should initially connect to 5 trusted nodes to begin bootstrapping.

```go
type Bootstrapper struct {
trustedPeers []PeerID
trustedHeads map[[]Cid]uint64

bootstrapPeers []PeerID

}

func (b *Bootstrapper) InitialConnect(config ConfigFile, s Syncer) {
func (s *Syncer) InitialConnect(config ConfigFile) {
b.trustedPeers = configFile.bootstrapPeers

for trustedPeer := range b.trustedPeers {
for trustedPeer := range s.trustedPeers {
trustedChain := sayHello(trustedPeer)

b.trustedHeads[trustedChain.HeaviestTipSet] = trustedChain.HeaviestTipSetHeight
s.trustedHeads[trustedChain.HeaviestTipSet] = trustedChain.HeaviestTipSetHeight

if trustedChain.GenesisHash != GENESIS {
Fatal("One of your trusted peers is bad.")
}
}

s.genesis = GENESIS
b.ExpandPeerSet(config, s)
s.ExpandPeerSet()
}
```

Expand All @@ -85,88 +82,44 @@ We look for random peers so our peer set makes up a "representative" set of the

In that sense, during the initial bootstrapping, we treat the Filecoin nodes network and the underlying libp2p network as two distinct networks for bootstrapping purposes. We specifically distinguish this bootstrapping phase (meant to help us sync to the chain and which doesn't use the DHT structure to pick nodes) from the node bootstrapping in which Filecoin nodes use the DHT to find nearby nodes with whom to exchange messages.

Typical DHT bootstrapping would mean querying your own ID (to get those nodes closest to you). Instead here, we will successively query random IDs (25 times) round-robinning our trusted nodes as starting points. We'll keep the nodes with IDs closest to our random one in our peer set.
We will successively query random IDs (25 times) round-robinning our trusted nodes as starting points. We'll keep the nodes with IDs closest to our random one in our peer set.

```go
type PeersMessage struct {
PeerSet []PeerID
}

func (b *Bootstrapper) ExpandPeerSet(config ConfigFile, b Bootstrapper, s Syncer) {
func (s *Syncer) ExpandPeerSet() {
dht := getDHT()

// get a random node from each trusted peer until we have 25
trustedPeer := 0
for len(b.bootstrapPeers) < 25 {
wantedPeer := generateRandomNodeID()
// libp2p's GetClosestPeers method returns a channel of k nearest peers, ordered
peers, err := dht.GetClosestPeers(b.trustedPeers[trustedPeer], wantedPeer)

if err != nil {
fail("couldn't get a close peer")
}

newPeer := <-peers
close(peers)

if !b.bootsrapPeers.contains(newPeer) && b.VerifyPeer(newPeer, s)
{
b.bootstrapPeers = append(b.bootstrapPeers, newPeer)
trustedPeerNum := 0
// we'll look for duplicates found sampling the network as a heuristic for interrupting the search if the network is too small
dupCounter := 0
for len(s.peerSet) < BootstrapPeerThreshold && dupCounter < DupThreshold {
trustedPeer := s.trustedPeers[trustedPeerNum]
newPeer := syncer.getRandomPeer(trustedPeer)
// adding a peer to the peer set will trigger syncing if our peer set has grown over the threshold
if !syncer.addPeerToSet(newPeer) {
dupCounter += 1
}

// round robin
trustedPeer += 1
trustedPeer % len(b.trustedPeers)
trustedPeerNum += 1
trustedPeerNum %= len(s.trustedPeers)
}

// if we've reached the threshold, abandon expansion and sync with trusted heads instead
if dupCounter >= DupThreshold {
s.SyncBootstrap(false)
}
}

func (b *Bootsrapper) verifyPeer(id PeerID, s Syncer) bool {
// TODO
}

```

For each of our 25 peers, we perform hello handshakes, and run the following verifications:
For each of our BootstrapPeerThreshold peers, we perform hello handshakes, and run the following verifications:

- Ensure that each of our bootstrapper's `trustedHeads.keys` are included as ancestors of our peers' latestHeads.
- If they are not, include that peer in our syncer's `BadTipsetCache`.
- If they are, add these peers and latestHeads to our syncer's peerSet/peerHeads (with offset initialConnections)
- If they are, add these peers and latestHeads to our syncer's peerSet/peerHeads

Repeat this process requesting new peers until your PeerHeads contains at least 25 valid peers, to be used to select a latest head to mine off of.

If you cannot get to 25 (or don't have 30 to start with) the protocol should fail. This is an unlikely network condition in which an attack may be underway.

## Chain Selection

We now have 25 peers mining off of the same trusted ancestor heads. Disagreements in their chains have to do with the fact that these may not yet be confirmed blocks. We simply want to start mining off of the heaviest of their chains to sync up to and mine off of.

```go
func selectHead(heads map[PeerID]TipSet) TipSet {
headsArr := toArray(heads)
sel := headsArr[0]
for i := 1; i < len(headsArr); i++ {
cur := headsArr[i]

if cur.IsAncestorOf(sel) {
continue
}
if sel.IsAncestorOf(cur) {
sel = cur
continue
}

nca := NearestCommonAncestor(cur, sel)
if sel.Height() - nca.Height() > ForkLengthThreshold {
// TODO: handle this better than refusing to sync
Fatal("Conflict exists in heads set")
}

if cur.Weight() > sel.Weight() {
sel = cur
}
}
return sel
}
```
If the random search yields too many duplicates, the peer set expansion is abandoned, under the assumption that the network is too small to support the peer expansion (this would happen early in the network's life or if the number of peers dipped below the threshold at some point).

The selected chain should be passed to the syncer, and the ‘initial sync’ should begin.
In that case, the protocol will instead try to sync with the trustedPeers directly. In the steady-state, we assume the trustedPeers would reject such syncing requests under normal network conditions.
Loading

0 comments on commit a2e8244

Please sign in to comment.