159 lines
5.0 KiB
Nim
159 lines
5.0 KiB
Nim
# Nimbus - New sync approach - A fusion of snap, trie, beam and other methods
|
|
#
|
|
# Copyright (c) 2021 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
# http://opensource.org/licenses/MIT)
|
|
# at your option. This file may not be copied, modified, or distributed
|
|
# except according to those terms.
|
|
|
|
import
|
|
std/hashes,
|
|
chronicles,
|
|
chronos,
|
|
eth/[common/eth_types, p2p, p2p/peer_pool, p2p/private/p2p_types],
|
|
stew/keyed_queue,
|
|
"."/[protocol, types],
|
|
./snap/worker
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
logScope:
|
|
topics = "snap-sync"
|
|
|
|
type
|
|
SnapSyncCtx* = ref object of Worker
|
|
chain: AbstractChainDB
|
|
buddies: KeyedQueue[Peer,WorkerBuddy] ## LRU cache with worker descriptors
|
|
pool: PeerPool ## for starting the system
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc nsCtx(sp: WorkerBuddy): SnapSyncCtx =
|
|
sp.ns.SnapSyncCtx
|
|
|
|
proc hash(peer: Peer): Hash =
|
|
## Needed for `buddies` table key comparison
|
|
hash(peer.remote.id)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc workerLoop(sp: WorkerBuddy) {.async.} =
|
|
let ns = sp.nsCtx
|
|
trace "Starting peer worker", peer=sp,
|
|
peers=ns.pool.len, workers=ns.buddies.len, maxWorkers=ns.buddiesMax
|
|
|
|
# Do something, work a bit
|
|
await sp.workerExec
|
|
|
|
# Continue until stopped
|
|
while not sp.ctrl.stopped:
|
|
# Rotate connection table so the most used entry is at the end
|
|
discard sp.nsCtx.buddies.lruFetch(sp.peer)
|
|
|
|
let delayMs = if sp.workerLockedOk: 1000 else: 50
|
|
await sleepAsync(chronos.milliseconds(delayMs))
|
|
|
|
# Do something, work a bit
|
|
await sp.workerExec
|
|
|
|
trace "Peer worker done", peer=sp, ctrlState=sp.ctrl.state,
|
|
peers=ns.pool.len, workers=ns.buddies.len, maxWorkers=ns.buddiesMax
|
|
|
|
|
|
proc onPeerConnected(ns: SnapSyncCtx, peer: Peer) =
|
|
let sp = WorkerBuddy.new(ns, peer)
|
|
|
|
# Check for known entry (which should not exist.)
|
|
if ns.buddies.hasKey(peer):
|
|
trace "Ignoring already registered peer!", peer,
|
|
peers=ns.pool.len, workers=ns.buddies.len, maxWorkers=ns.buddiesMax
|
|
return
|
|
|
|
# Initialise worker for this peer
|
|
if not sp.workerStart():
|
|
trace "Ignoring useless peer", peer,
|
|
peers=ns.pool.len, workers=ns.buddies.len, maxWorkers=ns.buddiesMax
|
|
sp.ctrl.zombie = true
|
|
return
|
|
|
|
# Check for table overflow. An overflow should not happen if the table is
|
|
# as large as the peer connection table.
|
|
if ns.buddiesMax <= ns.buddies.len:
|
|
let leastPeer = ns.buddies.shift.value.data
|
|
if leastPeer.ctrl.zombie:
|
|
trace "Dequeuing zombie peer", leastPeer,
|
|
peers=ns.pool.len, workers=ns.buddies.len, maxWorkers=ns.buddiesMax
|
|
discard
|
|
else:
|
|
trace "Peer table full! Dequeuing least used entry", leastPeer,
|
|
peers=ns.pool.len, workers=ns.buddies.len, maxWorkers=ns.buddiesMax
|
|
leastPeer.workerStop()
|
|
leastPeer.ctrl.zombie = true
|
|
|
|
# Add peer entry
|
|
discard ns.buddies.lruAppend(sp.peer, sp, ns.buddiesMax)
|
|
|
|
# Run worker
|
|
asyncSpawn sp.workerLoop()
|
|
|
|
|
|
proc onPeerDisconnected(ns: SnapSyncCtx, peer: Peer) =
|
|
let rc = ns.buddies.eq(peer)
|
|
if rc.isErr:
|
|
debug "Disconnected from unregistered peer", peer,
|
|
peers=ns.pool.len, workers=ns.buddies.len, maxWorkers=ns.buddiesMax
|
|
return
|
|
let sp = rc.value
|
|
if sp.ctrl.zombie:
|
|
trace "Disconnected zombie peer", peer,
|
|
peers=ns.pool.len, workers=ns.buddies.len, maxWorkers=ns.buddiesMax
|
|
else:
|
|
sp.workerStop()
|
|
ns.buddies.del(peer)
|
|
trace "Disconnected peer", peer,
|
|
peers=ns.pool.len, workers=ns.buddies.len, maxWorkers=ns.buddiesMax
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc new*(T: type SnapSyncCtx; ethNode: EthereumNode; maxPeers: int): T =
|
|
## Constructor
|
|
new result
|
|
let size = max(1,maxPeers)
|
|
result.chain = ethNode.chain
|
|
result.buddies.init(size)
|
|
result.buddiesMax = size
|
|
result.pool = ethNode.peerPool
|
|
|
|
proc start*(ctx: SnapSyncCtx) =
|
|
## Set up syncing. This call should come early.
|
|
var po = PeerObserver(
|
|
onPeerConnected:
|
|
proc(p: Peer) {.gcsafe.} =
|
|
ctx.onPeerConnected(p),
|
|
onPeerDisconnected:
|
|
proc(p: Peer) {.gcsafe.} =
|
|
ctx.onPeerDisconnected(p))
|
|
|
|
# Initialise sub-systems
|
|
ctx.workerSetup(ctx.chain)
|
|
po.setProtocol eth
|
|
ctx.pool.addObserver(ctx, po)
|
|
|
|
proc stop*(ctx: SnapSyncCtx) =
|
|
## Stop syncing
|
|
ctx.pool.delObserver(ctx)
|
|
ctx.workerRelease()
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|