diff --git a/nimbus/config.nim b/nimbus/config.nim index fcbaaa00f..73a7dad72 100644 --- a/nimbus/config.nim +++ b/nimbus/config.nim @@ -117,7 +117,6 @@ type ## Protocol flags Eth ## enable eth subprotocol #Snap ## enable snap sub-protocol - Les ## enable les subprotocol RpcFlag* {.pure.} = enum ## RPC flags @@ -373,8 +372,8 @@ type protocols {. desc: "Enable specific set of server protocols (available: Eth, " & - " Les, None.) This will not affect the sync mode" - # " Snap, Les, None.) This will not affect the sync mode" + " None.) This will not affect the sync mode" + # " Snap, None.) This will not affect the sync mode" defaultValue: @[] defaultValueDesc: $ProtocolFlag.Eth name: "protocols" .}: seq[string] @@ -635,7 +634,6 @@ proc getProtocolFlags*(conf: NimbusConf): set[ProtocolFlag] = for item in repeatingList(conf.protocols): case item.toLowerAscii() of "eth": result.incl ProtocolFlag.Eth - of "les": result.incl ProtocolFlag.Les # of "snap": result.incl ProtocolFlag.Snap of "none": noneOk = true else: diff --git a/nimbus/nimbus.nim b/nimbus/nimbus.nim index e6e57ca91..388298087 100644 --- a/nimbus/nimbus.nim +++ b/nimbus/nimbus.nim @@ -28,8 +28,7 @@ import ./core/clique/clique_desc, ./core/clique/clique_sealer, ./sync/protocol, - ./sync/handlers, - ./sync/protocol/les_protocol + ./sync/handlers when defined(evmc_enabled): import transaction/evmc_dynamic_loader @@ -131,8 +130,6 @@ proc setupP2P(nimbus: NimbusNode, conf: NimbusConf, nimbus.ethNode.peerPool, nimbus.chainRef, nimbus.txPool) - of ProtocolFlag.Les: - nimbus.ethNode.addCapability les #of ProtocolFlag.Snap: # nimbus.ethNode.addSnapHandlerCapability( # nimbus.ethNode.peerPool, diff --git a/nimbus/sync/protocol/les/flow_control.nim b/nimbus/sync/protocol/les/flow_control.nim deleted file mode 100644 index 69787d1b5..000000000 --- a/nimbus/sync/protocol/les/flow_control.nim +++ /dev/null @@ -1,509 +0,0 @@ -# Nimbus -# Copyright (c) 2018-2024 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/[tables, sets], - chronicles, chronos, - eth/[rlp, common], - eth/p2p/[rlpx, private/p2p_types], - ./private/les_types - -const - maxSamples = 100000 - rechargingScale = 1000000 - - lesStatsKey = "les.flow_control.stats" - lesStatsVer = 0 - -logScope: - topics = "les flow_control" - -# TODO: move this somewhere -proc pop[A, B](t: var Table[A, B], key: A): B = - result = t[key] - t.del(key) - -when LesTime is SomeInteger: - template `/`(lhs, rhs: LesTime): LesTime = - lhs div rhs - -when defined(testing): - var lesTime* = LesTime(0) - template now(): LesTime = lesTime - template advanceTime(t) = lesTime += LesTime(t) - -else: - import times - let startTime = epochTime() - - proc now(): LesTime = - return LesTime((times.epochTime() - startTime) * 1000.0) - -proc addSample(ra: var StatsRunningAverage; x, y: float64) = - if ra.count >= maxSamples: - let decay = float64(ra.count + 1 - maxSamples) / maxSamples - template applyDecay(x) = x -= x * decay - - applyDecay ra.sumX - applyDecay ra.sumY - applyDecay ra.sumXX - applyDecay ra.sumXY - ra.count = maxSamples - 1 - - inc ra.count - ra.sumX += x - ra.sumY += y - ra.sumXX += x * x - ra.sumXY += x * y - -proc calc(ra: StatsRunningAverage): tuple[m, b: float] = - if ra.count == 0: - return - - let count = float64(ra.count) - let d = count * ra.sumXX - ra.sumX * ra.sumX - if d < 0.001: - return (m: ra.sumY / count, b: 0.0) - - result.m = (count * ra.sumXY - ra.sumX * ra.sumY) / d - result.b = (ra.sumY / count) - (result.m * ra.sumX / count) - -proc currentRequestsCosts*(network: LesNetwork, - les: ProtocolInfo): seq[ReqCostInfo] = - # Make sure the message costs are already initialized - doAssert network.messageStats.len > les.messages[^1].id, - "Have you called `initFlowControl`" - - for msg in les.messages: - var (m, b) = network.messageStats[msg.id].calc() - if m < 0: - b += m - m = 0 - - if b < 0: - b = 0 - - result.add ReqCostInfo(msgId: msg.id, - baseCost: ReqCostInt(b * 2), - reqCost: ReqCostInt(m * 2)) - -proc persistMessageStats*(network: LesNetwork) = - # XXX: Because of the package_visible_types template magic, Nim complains - # when we pass the messageStats expression directly to `encodeList` - let stats = network.messageStats - network.setSetting(lesStatsKey, rlp.encodeList(lesStatsVer, stats)) - -proc loadMessageStats*(network: LesNetwork, - les: ProtocolInfo): bool = - block readFromDB: - var stats = network.getSetting(lesStatsKey) - if stats.len == 0: - notice "LES stats not present in the database" - break readFromDB - - try: - var statsRlp = rlpFromBytes(stats) - if not statsRlp.enterList: - notice "Found a corrupted LES stats record" - break readFromDB - - let version = statsRlp.read(int) - if version != lesStatsVer: - notice "Found an outdated LES stats record" - break readFromDB - - statsRlp >> network.messageStats - if network.messageStats.len <= les.messages[^1].id: - notice "Found an incomplete LES stats record" - break readFromDB - - return true - - except RlpError as e: - error "Error while loading LES message stats", err = e.msg - - newSeq(network.messageStats, les.messages[^1].id + 1) - return false - -proc update(s: var FlowControlState, t: LesTime) = - let dt = max(t - s.lastUpdate, LesTime(0)) - - s.bufValue = min( - s.bufValue + s.minRecharge * dt, - s.bufLimit) - - s.lastUpdate = t - -proc init(s: var FlowControlState, - bufLimit: BufValueInt, minRecharge: int, t: LesTime) = - s.bufValue = bufLimit - s.bufLimit = bufLimit - s.minRecharge = minRecharge - s.lastUpdate = t - -#func canMakeRequest(s: FlowControlState, -# maxCost: ReqCostInt): (LesTime, float64) = -# ## Returns the required waiting time before sending a request and -# ## the estimated buffer level afterwards (as a fraction of the limit) -# const safetyMargin = 50 -# -# var maxCost = min( -# maxCost + safetyMargin * s.minRecharge, -# s.bufLimit) -# -# if s.bufValue >= maxCost: -# result[1] = float64(s.bufValue - maxCost) / float64(s.bufLimit) -# else: -# result[0] = (maxCost - s.bufValue) / s.minRecharge - -func canServeRequest(srv: LesNetwork): bool = - result = srv.reqCount < srv.maxReqCount and - srv.reqCostSum < srv.maxReqCostSum - -proc rechargeReqCost(peer: LesPeer, t: LesTime) = - let dt = t - peer.lastRechargeTime - peer.reqCostVal += peer.reqCostGradient * dt / rechargingScale - peer.lastRechargeTime = t - if peer.isRecharging and t >= peer.rechargingEndsAt: - peer.isRecharging = false - peer.reqCostGradient = 0 - peer.reqCostVal = 0 - -proc updateRechargingParams(peer: LesPeer, network: LesNetwork) = - peer.reqCostGradient = 0 - if peer.reqCount > 0: - peer.reqCostGradient = rechargingScale / network.reqCount - - if peer.isRecharging: - peer.reqCostGradient = (network.rechargingRate * (peer.rechargingPower / - network.totalRechargingPower).int64).int - peer.rechargingEndsAt = peer.lastRechargeTime + - LesTime(peer.reqCostVal * rechargingScale / - -peer.reqCostGradient ) - -proc trackRequests(network: LesNetwork, peer: LesPeer, reqCountChange: int) = - peer.reqCount += reqCountChange - network.reqCount += reqCountChange - - doAssert peer.reqCount >= 0 and network.reqCount >= 0 - - if peer.reqCount == 0: - # All requests have been finished. Start recharging. - peer.isRecharging = true - network.totalRechargingPower += peer.rechargingPower - elif peer.reqCount == reqCountChange and peer.isRecharging: - # `peer.reqCount` must have been 0 for the condition above to hold. - # This is a transition from recharging to serving state. - peer.isRecharging = false - network.totalRechargingPower -= peer.rechargingPower - peer.startReqCostVal = peer.reqCostVal - - updateRechargingParams peer, network - -proc updateFlowControl(network: LesNetwork, t: LesTime) = - while true: - var firstTime = t - for peer in network.peers: - # TODO: perhaps use a bin heap here - if peer.isRecharging and peer.rechargingEndsAt < firstTime: - firstTime = peer.rechargingEndsAt - - let rechargingEndedForSomePeer = firstTime < t - - network.reqCostSum = 0 - for peer in network.peers: - peer.rechargeReqCost firstTime - network.reqCostSum += peer.reqCostVal - - if rechargingEndedForSomePeer: - for peer in network.peers: - if peer.isRecharging: - updateRechargingParams peer, network - else: - network.lastUpdate = t - return - -proc endPendingRequest*(network: LesNetwork, peer: LesPeer, t: LesTime) = - if peer.reqCount > 0: - network.updateFlowControl t - network.trackRequests peer, -1 - network.updateFlowControl t - -proc enlistInFlowControl*(network: LesNetwork, - peer: LesPeer, - peerRechargingPower = 100) = - let t = now() - - doAssert peer.isServer or peer.isClient - # Each Peer must be potential communication partner for us. - # There will be useless peers on the network, but the logic - # should make sure to disconnect them earlier in `onPeerConnected`. - - if peer.isServer: - peer.localFlowState.init network.bufferLimit, network.minRechargingRate, t - peer.pendingReqs = initTable[int, ReqCostInt]() - - if peer.isClient: - peer.remoteFlowState.init network.bufferLimit, network.minRechargingRate, t - peer.lastRechargeTime = t - peer.rechargingEndsAt = t - peer.rechargingPower = peerRechargingPower - - network.updateFlowControl t - -proc delistFromFlowControl*(network: LesNetwork, peer: LesPeer) = - let t = now() - - # XXX: perhaps this is not safe with our reqCount logic. - # The original code may depend on the binarity of the `serving` flag. - network.endPendingRequest peer, t - network.updateFlowControl t - -proc initFlowControl*(network: LesNetwork, les: ProtocolInfo, - maxReqCount, maxReqCostSum, reqCostTarget: int) = - network.rechargingRate = rechargingScale * (rechargingScale / - (100 * rechargingScale / reqCostTarget - rechargingScale)) - network.maxReqCount = maxReqCount - network.maxReqCostSum = maxReqCostSum - - if not network.loadMessageStats(les): - warn "Failed to load persisted LES message stats. " & - "Flow control will be re-initilized." - -#proc canMakeRequest(peer: var LesPeer, maxCost: int): (LesTime, float64) = -# peer.localFlowState.update now() -# return peer.localFlowState.canMakeRequest(maxCost) - -template getRequestCost(peer: LesPeer, localOrRemote: untyped, - msgId, costQuantity: int): ReqCostInt = - let - baseCost = peer.`localOrRemote ReqCosts`[msgId].baseCost - reqCost = peer.`localOrRemote ReqCosts`[msgId].reqCost - - min(baseCost + reqCost * costQuantity, - peer.`localOrRemote FlowState`.bufLimit) - -proc trackOutgoingRequest*(network: LesNetwork, peer: LesPeer, - msgId, reqId, costQuantity: int) = - let maxCost = peer.getRequestCost(local, msgId, costQuantity) - - peer.localFlowState.bufValue -= maxCost - peer.pendingReqsCost += maxCost - peer.pendingReqs[reqId] = peer.pendingReqsCost - -proc trackIncomingResponse*(peer: LesPeer, reqId: int, bv: BufValueInt) = - let bv = min(bv, peer.localFlowState.bufLimit) - if not peer.pendingReqs.hasKey(reqId): - return - - let costsSumAtSending = peer.pendingReqs.pop(reqId) - let costsSumChange = peer.pendingReqsCost - costsSumAtSending - - peer.localFlowState.bufValue = if bv > costsSumChange: bv - costsSumChange - else: 0 - peer.localFlowState.lastUpdate = now() - -proc acceptRequest*(network: LesNetwork, peer: LesPeer, - msgId, costQuantity: int): Future[bool] {.async.} = - let t = now() - let reqCost = peer.getRequestCost(remote, msgId, costQuantity) - - peer.remoteFlowState.update t - network.updateFlowControl t - - while not network.canServeRequest: - await sleepAsync(chronos.milliseconds(10)) - - if peer notin network.peers: - # The peer was disconnected or the network - # was shut down while we waited - return false - - network.trackRequests peer, +1 - network.updateFlowControl network.lastUpdate - - if reqCost > peer.remoteFlowState.bufValue: - error "LES peer sent request too early", - recharge = (reqCost - peer.remoteFlowState.bufValue) * rechargingScale / - peer.remoteFlowState.minRecharge - return false - - return true - -proc bufValueAfterRequest*(network: LesNetwork, peer: LesPeer, - msgId: int, quantity: int): BufValueInt = - let t = now() - let costs = peer.remoteReqCosts[msgId] - var reqCost = costs.baseCost + quantity * costs.reqCost - - peer.remoteFlowState.update t - peer.remoteFlowState.bufValue -= reqCost - - network.endPendingRequest peer, t - - let curReqCost = peer.reqCostVal - if curReqCost < peer.remoteFlowState.bufLimit: - let bv = peer.remoteFlowState.bufLimit - curReqCost - if bv > peer.remoteFlowState.bufValue: - peer.remoteFlowState.bufValue = bv - - network.messageStats[msgId].addSample(float64(quantity), - float64(curReqCost - peer.startReqCostVal)) - - return peer.remoteFlowState.bufValue - -when defined(testing): - import unittest2, random, ../../rlpx - - proc isMax(s: FlowControlState): bool = - s.bufValue == s.bufLimit - - p2pProtocol dummyLes(version = 1, rlpxName = "abc"): - proc a(p: Peer) - proc b(p: Peer) - proc c(p: Peer) - proc d(p: Peer) - proc e(p: Peer) - - template fequals(lhs, rhs: float64, epsilon = 0.0001): bool = - abs(lhs-rhs) < epsilon - - proc tests* = - randomize(3913631) - - suite "les flow control": - suite "running averages": - test "consistent costs": - var s: StatsRunningAverage - for i in 0..100: - s.addSample(5.0, 100.0) - - let (cost, base) = s.calc - - check: - fequals(cost, 100.0) - fequals(base, 0.0) - - test "randomized averages": - proc performTest(qBase, qRandom: int, cBase, cRandom: float64) = - var - s: StatsRunningAverage - expectedFinalCost = cBase + cRandom / 2 - error = expectedFinalCost - - for samples in [100, 1000, 10000]: - for i in 0..samples: - let q = float64(qBase + rand(10)) - s.addSample(q, q * (cBase + rand(cRandom))) - - let (newCost, newBase) = s.calc - # With more samples, our error should decrease, getting - # closer and closer to the average (unless we are already close enough) - let newError = abs(newCost - expectedFinalCost) - # This check fails with Nim-1.6: - # check newError < error - error = newError - - # After enough samples we should be very close the final result - check error < (expectedFinalCost * 0.02) - - performTest(1, 10, 5.0, 100.0) - performTest(1, 4, 200.0, 1000.0) - - suite "buffer value calculations": - type TestReq = object - peer: LesPeer - msgId, quantity: int - accepted: bool - - setup: - var lesNetwork = new LesNetwork - lesNetwork.peers = initHashSet[LesPeer]() - lesNetwork.initFlowControl(dummyLes.protocolInfo, - reqCostTarget = 300, - maxReqCount = 5, - maxReqCostSum = 1000) - - for i in 0 ..< lesNetwork.messageStats.len: - lesNetwork.messageStats[i].addSample(1.0, float(i) * 100.0) - - var client = new LesPeer - client.isClient = true - - var server = new LesPeer - server.isServer = true - - var clientServer = new LesPeer - clientServer.isClient = true - clientServer.isServer = true - - var client2 = new LesPeer - client2.isClient = true - - var client3 = new LesPeer - client3.isClient = true - - var bv: BufValueInt - - template enlist(peer: LesPeer) {.dirty.} = - let reqCosts = currentRequestsCosts(lesNetwork, dummyLes.protocolInfo) - peer.remoteReqCosts = reqCosts - peer.localReqCosts = reqCosts - lesNetwork.peers.incl peer - lesNetwork.enlistInFlowControl peer - - template startReq(p: LesPeer, msg, q: int): TestReq = - var req: TestReq - req.peer = p - req.msgId = msg - req.quantity = q - req.accepted = waitFor lesNetwork.acceptRequest(p, msg, q) - req - - template endReq(req: TestReq): BufValueInt = - bufValueAfterRequest(lesNetwork, req.peer, req.msgId, req.quantity) - - test "single peer recharging": - lesNetwork.bufferLimit = 1000 - lesNetwork.minRechargingRate = 100 - - enlist client - - check: - client.remoteFlowState.isMax - client.rechargingPower > 0 - - advanceTime 100 - - let r1 = client.startReq(0, 100) - check r1.accepted - check client.isRecharging == false - - advanceTime 50 - - let r2 = client.startReq(1, 1) - check r2.accepted - check client.isRecharging == false - - advanceTime 25 - bv = endReq r2 - check client.isRecharging == false - - advanceTime 130 - bv = endReq r1 - check client.isRecharging == true - - advanceTime 300 - lesNetwork.updateFlowControl now() - - check: - client.isRecharging == false - client.remoteFlowState.isMax - diff --git a/nimbus/sync/protocol/les/private/les_types.nim b/nimbus/sync/protocol/les/private/les_types.nim deleted file mode 100644 index be4fdec1e..000000000 --- a/nimbus/sync/protocol/les/private/les_types.nim +++ /dev/null @@ -1,127 +0,0 @@ -# Nimbus -# Copyright (c) 2018-2023 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, tables, sets], - eth/common - -type - AnnounceType* = enum - None, - Simple, - Signed, - Unspecified - - ReqCostInfo* = object - msgId*: int - baseCost*, reqCost*: ReqCostInt - - FlowControlState* = object - bufValue*, bufLimit*: int - minRecharge*: int - lastUpdate*: LesTime - - StatsRunningAverage* = object - sumX*, sumY*, sumXX*, sumXY*: float64 - count*: int - - LesPeer* = ref object of RootRef - isServer*: bool - isClient*: bool - announceType*: AnnounceType - - bestDifficulty*: DifficultyInt - bestBlockHash*: KeccakHash - bestBlockNumber*: BlockNumber - - hasChainSince*: HashOrNum - hasStateSince*: HashOrNum - relaysTransactions*: bool - - # The variables below are used to implement the flow control - # mechanisms of LES from our point of view as a server. - # They describe how much load has been generated by this - # particular peer. - reqCount*: int # How many outstanding requests are there? - # - rechargingPower*: int # Do we give this peer any extra priority - # (implemented as a faster recharning rate) - # 100 is the default. You can go higher and lower. - # - isRecharging*: bool # This is true while the peer is not making - # any requests - # - reqCostGradient*: int # Measures the speed of recharging or accumulating - # "requests cost" at any given moment. - # - reqCostVal*: int # The accumulated "requests cost" - # - rechargingEndsAt*: int # When will recharging end? - # (the buffer of the Peer will be fully restored) - # - lastRechargeTime*: LesTime # When did we last update the recharging parameters - # - startReqCostVal*: int # TODO - - remoteFlowState*: FlowControlState - remoteReqCosts*: seq[ReqCostInfo] - - # The next variables are used to limit ourselves as a client in order to - # not violate the control-flow requirements of the remote LES server. - - pendingReqs*: Table[int, ReqCostInt] - pendingReqsCost*: int - - localFlowState*: FlowControlState - localReqCosts*: seq[ReqCostInfo] - - LesNetwork* = ref object of RootRef - peers*: HashSet[LesPeer] - messageStats*: seq[StatsRunningAverage] - ourAnnounceType*: AnnounceType - - # The fields below are relevant when serving data. - bufferLimit*: int - minRechargingRate*: int - - reqCostSum*, maxReqCostSum*: ReqCostInt - reqCount*, maxReqCount*: int - sumWeigth*: int - - rechargingRate*: int64 - totalRechargedUnits*: int - totalRechargingPower*: int - - lastUpdate*: LesTime - - KeyValuePair* = object - key*: string - value*: Blob - - HandshakeError* = object of CatchableError - - LesTime* = int # this is in milliseconds - BufValueInt* = int - ReqCostInt* = int - -template hash*(peer: LesPeer): Hash = hash(cast[pointer](peer)) - -template areWeServingData*(network: LesNetwork): bool = - network.maxReqCount != 0 - -template areWeRequestingData*(network: LesNetwork): bool = - network.ourAnnounceType != AnnounceType.Unspecified - -proc setSetting*(ctx: LesNetwork, key: string, val: openArray[byte]) = - discard - -proc getSetting*(ctx: LesNetwork, key: string): seq[byte] = - discard - diff --git a/nimbus/sync/protocol/les_protocol.nim b/nimbus/sync/protocol/les_protocol.nim deleted file mode 100644 index 8eb914e2e..000000000 --- a/nimbus/sync/protocol/les_protocol.nim +++ /dev/null @@ -1,524 +0,0 @@ -# Nimbus -# Copyright (c) 2018-2024 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/[times, tables, options, sets, hashes, strutils], - stew/shims/macros, chronicles, chronos, nimcrypto/[keccak, hash], - eth/[rlp, keys, common], - eth/p2p/[rlpx, kademlia, private/p2p_types], - ./les/private/les_types, ./les/flow_control, - ../types - -export - les_types - -type - ProofRequest* = object - blockHash*: KeccakHash - accountKey*: Blob - key*: Blob - fromLevel*: uint - - ContractCodeRequest* = object - blockHash*: KeccakHash - key*: EthAddress - - HelperTrieProofRequest* = object - subType*: uint - sectionIdx*: uint - key*: Blob - fromLevel*: uint - auxReq*: uint - - LesStatus = object - difficulty : DifficultyInt - blockHash : Hash256 - blockNumber: BlockNumber - genesisHash: Hash256 - -const - lesVersion = 2 - maxHeadersFetch = 192 - maxBodiesFetch = 32 - maxReceiptsFetch = 128 - maxCodeFetch = 64 - maxProofsFetch = 64 - maxHeaderProofsFetch = 64 - maxTransactionsFetch = 64 - - # Handshake properties: - # https://github.com/zsfelfoldi/go-ethereum/wiki/Light-Ethereum-Subprotocol-(LES) - keyProtocolVersion = "protocolVersion" - ## P: is 1 for the LPV1 protocol version. - - keyNetworkId = "networkId" - ## P: should be 0 for testnet, 1 for mainnet. - - keyHeadTotalDifficulty = "headTd" - ## P: Total Difficulty of the best chain. - ## Integer, as found in block header. - - keyHeadHash = "headHash" - ## B_32: the hash of the best (i.e. highest TD) known block. - - keyHeadNumber = "headNum" - ## P: the number of the best (i.e. highest TD) known block. - - keyGenesisHash = "genesisHash" - ## B_32: the hash of the Genesis block. - - #keyServeHeaders = "serveHeaders" - # ## (optional, no value) - # ## present if the peer can serve header chain downloads. - - keyServeChainSince = "serveChainSince" - ## P (optional) - ## present if the peer can serve Body/Receipts ODR requests - ## starting from the given block number. - - keyServeStateSince = "serveStateSince" - ## P (optional): - ## present if the peer can serve Proof/Code ODR requests - ## starting from the given block number. - - keyRelaysTransactions = "txRelay" - ## (optional, no value) - ## present if the peer can relay transactions to the ETH network. - - keyFlowControlBL = "flowControl/BL" - keyFlowControlMRC = "flowControl/MRC" - keyFlowControlMRR = "flowControl/MRR" - ## see Client Side Flow Control: - ## https://github.com/zsfelfoldi/go-ethereum/wiki/Client-Side-Flow-Control-model-for-the-LES-protocol - - keyAnnounceType = "announceType" - keyAnnounceSignature = "sign" - -proc getStatus(ctx: LesNetwork): LesStatus = - discard - -proc getBlockBodies(ctx: LesNetwork, hashes: openArray[Hash256]): seq[BlockBody] = - discard - -proc getBlockHeaders(ctx: LesNetwork, req: BlocksRequest): seq[BlockHeader] = - discard - -proc getReceipts(ctx: LesNetwork, hashes: openArray[Hash256]): seq[Receipt] = - discard - -proc getProofs(ctx: LesNetwork, proofs: openArray[ProofRequest]): seq[Blob] = - discard - -proc getContractCodes(ctx: LesNetwork, reqs: openArray[ContractCodeRequest]): seq[Blob] = - discard - -proc getHeaderProofs(ctx: LesNetwork, reqs: openArray[ProofRequest]): seq[Blob] = - discard - -proc getHelperTrieProofs(ctx: LesNetwork, - reqs: openArray[HelperTrieProofRequest], - outNodes: var seq[Blob], outAuxData: var seq[Blob]) = - discard - -proc getTransactionStatus(ctx: LesNetwork, txHash: KeccakHash): TransactionStatusMsg = - discard - -proc addTransactions(ctx: LesNetwork, transactions: openArray[Transaction]) = - discard - -proc initProtocolState(network: LesNetwork, node: EthereumNode) {.gcsafe.} = - network.peers = initHashSet[LesPeer]() - -proc addPeer(network: LesNetwork, peer: LesPeer) = - network.enlistInFlowControl peer - network.peers.incl peer - -proc removePeer(network: LesNetwork, peer: LesPeer) = - network.delistFromFlowControl peer - network.peers.excl peer - -template costQuantity(quantityExpr, max: untyped) {.pragma.} - -proc getCostQuantity(fn: NimNode): tuple[quantityExpr, maxQuantity: NimNode] = - # XXX: `getCustomPragmaVal` doesn't work yet on regular nnkProcDef nodes - # (TODO: file as an issue) - let costQuantity = fn.pragma.findPragma(bindSym"costQuantity") - doAssert costQuantity != nil - - result.quantityExpr = costQuantity[1] - result.maxQuantity= costQuantity[2] - - if result.maxQuantity.kind == nnkExprEqExpr: - result.maxQuantity = result.maxQuantity[1] - -macro outgoingRequestDecorator(n: untyped): untyped = - result = n - #let (costQuantity, maxQuantity) = n.getCostQuantity - let (costQuantity, _) = n.getCostQuantity - - result.body.add quote do: - trackOutgoingRequest(peer.networkState(les), - peer.state(les), - perProtocolMsgId, reqId, `costQuantity`) - #echo "outgoingRequestDecorator: ", result.repr - -macro incomingResponseDecorator(n: untyped): untyped = - result = n - - let trackingCall = quote do: - try: - trackIncomingResponse(peer.state(les), reqId, msg.bufValue) - except KeyError as exc: - raise newException(EthP2PError, exc.msg) - - result.body.insert(n.body.len - 1, trackingCall) - #echo "incomingResponseDecorator: ", result.repr - -macro incomingRequestDecorator(n: untyped): untyped = - result = n - let (costQuantity, maxQuantity) = n.getCostQuantity - - template acceptStep(quantityExpr, maxQuantity) {.dirty.} = - let requestCostQuantity = quantityExpr - if requestCostQuantity > maxQuantity: - await peer.disconnect(BreachOfProtocol) - return - - let lesPeer = peer.state - let lesNetwork = peer.networkState - - if not await acceptRequest(lesNetwork, lesPeer, - perProtocolMsgId, - requestCostQuantity): return - - let zero = result.body[0][0] - zero.insert(1, getAst(acceptStep(costQuantity, maxQuantity))) - #echo "incomingRequestDecorator: ", result.repr - -template updateBV: BufValueInt = - bufValueAfterRequest(lesNetwork, lesPeer, - perProtocolMsgId, requestCostQuantity) - -func getValue(values: openArray[KeyValuePair], - key: string, T: typedesc): Option[T] = - for v in values: - if v.key == key: - return some(rlp.decode(v.value, T)) - -func getRequiredValue(values: openArray[KeyValuePair], - key: string, T: typedesc): T = - for v in values: - if v.key == key: - return rlp.decode(v.value, T) - - raise newException(HandshakeError, - "Required handshake field " & key & " missing") - -p2pProtocol les(version = lesVersion, - peerState = LesPeer, - networkState = LesNetwork, - outgoingRequestDecorator = outgoingRequestDecorator, - incomingRequestDecorator = incomingRequestDecorator, - incomingResponseThunkDecorator = incomingResponseDecorator): - handshake: - proc status(p: Peer, values: openArray[KeyValuePair]) - - onPeerConnected do (peer: Peer): - let - network = peer.network - lesPeer = peer.state - lesNetwork = peer.networkState - status = lesNetwork.getStatus() - - template `=>`(k, v: untyped): untyped = - KeyValuePair(key: k, value: rlp.encode(v)) - - var lesProperties = @[ - keyProtocolVersion => lesVersion, - keyNetworkId => network.networkId, - keyHeadTotalDifficulty => status.difficulty, - keyHeadHash => status.blockHash, - keyHeadNumber => status.blockNumber, - keyGenesisHash => status.genesisHash - ] - - lesPeer.remoteReqCosts = currentRequestsCosts(lesNetwork, les.protocolInfo) - - if lesNetwork.areWeServingData: - lesProperties.add [ - # keyServeHeaders => nil, - keyServeChainSince => 0, - keyServeStateSince => 0, - # keyRelaysTransactions => nil, - keyFlowControlBL => lesNetwork.bufferLimit, - keyFlowControlMRR => lesNetwork.minRechargingRate, - keyFlowControlMRC => lesPeer.remoteReqCosts - ] - - if lesNetwork.areWeRequestingData: - lesProperties.add(keyAnnounceType => lesNetwork.ourAnnounceType) - - let - s = await peer.status(lesProperties, timeout = chronos.seconds(10)) - peerNetworkId = s.values.getRequiredValue(keyNetworkId, NetworkId) - peerGenesisHash = s.values.getRequiredValue(keyGenesisHash, KeccakHash) - peerLesVersion = s.values.getRequiredValue(keyProtocolVersion, uint) - - template requireCompatibility(peerVar, localVar, varName: untyped) = - if localVar != peerVar: - raise newException(HandshakeError, - "Incompatibility detected! $1 mismatch ($2 != $3)" % - [varName, $localVar, $peerVar]) - - requireCompatibility(peerLesVersion, uint(lesVersion), "les version") - requireCompatibility(peerNetworkId, network.networkId, "network id") - requireCompatibility(peerGenesisHash, status.genesisHash, "genesis hash") - - template `:=`(lhs, key) = - lhs = s.values.getRequiredValue(key, type(lhs)) - - lesPeer.bestBlockHash := keyHeadHash - lesPeer.bestBlockNumber := keyHeadNumber - lesPeer.bestDifficulty := keyHeadTotalDifficulty - - let peerAnnounceType = s.values.getValue(keyAnnounceType, AnnounceType) - if peerAnnounceType.isSome: - lesPeer.isClient = true - lesPeer.announceType = peerAnnounceType.get - else: - lesPeer.announceType = AnnounceType.Simple - lesPeer.hasChainSince := keyServeChainSince - lesPeer.hasStateSince := keyServeStateSince - lesPeer.relaysTransactions := keyRelaysTransactions - lesPeer.localFlowState.bufLimit := keyFlowControlBL - lesPeer.localFlowState.minRecharge := keyFlowControlMRR - lesPeer.localReqCosts := keyFlowControlMRC - - lesNetwork.addPeer lesPeer - - onPeerDisconnected do (peer: Peer, reason: DisconnectionReason) {.gcsafe.}: - peer.networkState.removePeer peer.state - - ## Header synchronisation - ## - - proc announce( - peer: Peer, - headHash: KeccakHash, - headNumber: BlockNumber, - headTotalDifficulty: DifficultyInt, - reorgDepth: BlockNumber, - values: openArray[KeyValuePair], - announceType: AnnounceType) = - - if peer.state.announceType == AnnounceType.None: - error "unexpected announce message", peer - return - - if announceType == AnnounceType.Signed: - let signature = values.getValue(keyAnnounceSignature, Blob) - if signature.isNone: - chronicles.error "missing announce signature" - return - let sig = Signature.fromRaw(signature.get).tryGet() - let sigMsg = rlp.encodeList(headHash, headNumber, headTotalDifficulty) - let signerKey = recover(sig, sigMsg).tryGet() - if signerKey.toNodeId != peer.remote.id: - chronicles.error "invalid announce signature" - # TODO: should we disconnect this peer? - return - - # TODO: handle new block - - requestResponse: - proc getBlockHeaders( - peer: Peer, - req: BlocksRequest) {. - costQuantity(req.maxResults.int, max = maxHeadersFetch).} = - - let ctx = peer.networkState() - let headers = ctx.getBlockHeaders(req) - await response.send(updateBV(), headers) - - proc blockHeaders( - peer: Peer, - bufValue: BufValueInt, - blocks: openArray[BlockHeader]) - - ## On-damand data retrieval - ## - - requestResponse: - proc getBlockBodies( - peer: Peer, - blocks: openArray[KeccakHash]) {. - costQuantity(blocks.len, max = maxBodiesFetch), gcsafe.} = - - let ctx = peer.networkState() - let blocks = ctx.getBlockBodies(blocks) - await response.send(updateBV(), blocks) - - proc blockBodies( - peer: Peer, - bufValue: BufValueInt, - bodies: openArray[BlockBody]) - - requestResponse: - proc getReceipts( - peer: Peer, - hashes: openArray[KeccakHash]) - {.costQuantity(hashes.len, max = maxReceiptsFetch).} = - - let ctx = peer.networkState() - let receipts = ctx.getReceipts(hashes) - await response.send(updateBV(), receipts) - - proc receipts( - peer: Peer, - bufValue: BufValueInt, - receipts: openArray[Receipt]) - - requestResponse: - proc getProofs( - peer: Peer, - proofs: openArray[ProofRequest]) {. - costQuantity(proofs.len, max = maxProofsFetch).} = - - let ctx = peer.networkState() - let proofs = ctx.getProofs(proofs) - await response.send(updateBV(), proofs) - - proc proofs( - peer: Peer, - bufValue: BufValueInt, - proofs: openArray[Blob]) - - requestResponse: - proc getContractCodes( - peer: Peer, - reqs: seq[ContractCodeRequest]) {. - costQuantity(reqs.len, max = maxCodeFetch).} = - - let ctx = peer.networkState() - let results = ctx.getContractCodes(reqs) - await response.send(updateBV(), results) - - proc contractCodes( - peer: Peer, - bufValue: BufValueInt, - results: seq[Blob]) - - nextID 15 - - requestResponse: - proc getHeaderProofs( - peer: Peer, - reqs: openArray[ProofRequest]) {. - costQuantity(reqs.len, max = maxHeaderProofsFetch).} = - - let ctx = peer.networkState() - let proofs = ctx.getHeaderProofs(reqs) - await response.send(updateBV(), proofs) - - proc headerProofs( - peer: Peer, - bufValue: BufValueInt, - proofs: openArray[Blob]) - - requestResponse: - proc getHelperTrieProofs( - peer: Peer, - reqs: openArray[HelperTrieProofRequest]) {. - costQuantity(reqs.len, max = maxProofsFetch).} = - - let ctx = peer.networkState() - var nodes, auxData: seq[Blob] - ctx.getHelperTrieProofs(reqs, nodes, auxData) - await response.send(updateBV(), nodes, auxData) - - proc helperTrieProofs( - peer: Peer, - bufValue: BufValueInt, - nodes: seq[Blob], - auxData: seq[Blob]) - - ## Transaction relaying and status retrieval - ## - - requestResponse: - proc sendTxV2( - peer: Peer, - transactions: openArray[Transaction]) {. - costQuantity(transactions.len, max = maxTransactionsFetch).} = - - let ctx = peer.networkState() - - var results: seq[TransactionStatusMsg] - for t in transactions: - let hash = t.rlpHash - var s = ctx.getTransactionStatus(hash) - if s.status == TransactionStatus.Unknown: - ctx.addTransactions([t]) - s = ctx.getTransactionStatus(hash) - - results.add s - - await response.send(updateBV(), results) - - proc getTxStatus( - peer: Peer, - transactions: openArray[Transaction]) {. - costQuantity(transactions.len, max = maxTransactionsFetch).} = - - let ctx = peer.networkState() - - var results: seq[TransactionStatusMsg] - for t in transactions: - results.add ctx.getTransactionStatus(t.rlpHash) - await response.send(updateBV(), results) - - proc txStatus( - peer: Peer, - bufValue: BufValueInt, - transactions: openArray[TransactionStatusMsg]) - -proc configureLes*(node: EthereumNode, - # Client options: - announceType = AnnounceType.Simple, - # Server options. - # The zero default values indicate that the - # LES server will be deactivated. - maxReqCount = 0, - maxReqCostSum = 0, - reqCostTarget = 0) = - - doAssert announceType != AnnounceType.Unspecified or maxReqCount > 0 - - var lesNetwork = node.protocolState(les) - lesNetwork.ourAnnounceType = announceType - initFlowControl(lesNetwork, les.protocolInfo, - maxReqCount, maxReqCostSum, reqCostTarget) - -proc configureLesServer*(node: EthereumNode, - # Client options: - announceType = AnnounceType.Unspecified, - # Server options. - # The zero default values indicate that the - # LES server will be deactivated. - maxReqCount = 0, - maxReqCostSum = 0, - reqCostTarget = 0) = - ## This is similar to `configureLes`, but with default parameter - ## values appropriate for a server. - node.configureLes(announceType, maxReqCount, maxReqCostSum, reqCostTarget) - -proc persistLesMessageStats*(node: EthereumNode) = - persistMessageStats(node.protocolState(les)) - diff --git a/tests/test_configuration.nim b/tests/test_configuration.nim index 2e2f88313..8f04739d0 100644 --- a/tests/test_configuration.nim +++ b/tests/test_configuration.nim @@ -157,19 +157,9 @@ proc configurationMain*() = let flags = conf.getProtocolFlags() check ProtocolFlag.Eth in flags - let aa = makeConfig(@["--protocols:les"]) - let ax = aa.getProtocolFlags() - check ProtocolFlag.Les in ax - - let bb = makeConfig(@["--protocols:eth", "--protocols:les"]) + let bb = makeConfig(@["--protocols:eth"]) let bx = bb.getProtocolFlags() check ProtocolFlag.Eth in bx - check ProtocolFlag.Les in bx - - let cc = makeConfig(@["--protocols:eth,les"]) - let cx = cc.getProtocolFlags() - check ProtocolFlag.Eth in cx - check ProtocolFlag.Les in cx test "bootstrap-node and bootstrap-file": let conf = makeTestConfig()