Merge devel and resolve the conflicts

This commit is contained in:
Zahary Karadjov 2020-07-08 15:36:03 +03:00
commit 318b225ccd
No known key found for this signature in database
GPG Key ID: C8936F8A3073D609
97 changed files with 2594 additions and 562 deletions

View File

@ -34,7 +34,7 @@ build_script:
test_script:
- mingw32-make -j2 ARCH_OVERRIDE=%PLATFORM% LOG_LEVEL=TRACE
- mingw32-make -j2 ARCH_OVERRIDE=%PLATFORM% LOG_LEVEL=TRACE NIMFLAGS="-d:testnet_servers_image"
- mingw32-make -j2 ARCH_OVERRIDE=%PLATFORM% LOG_LEVEL=TRACE NIMFLAGS="-d:testnet_servers_image" beacon_node
- mingw32-make -j2 ARCH_OVERRIDE=%PLATFORM% DISABLE_TEST_FIXTURES_SCRIPT=1 test
deploy: off

View File

@ -22,25 +22,24 @@ matrix:
- NPROC=2
before_install:
- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib"
- sudo apt-get -q update
- os: linux
arch: arm64
sudo: required
env:
- NPROC=6 # Worth trying more than 2 parallel jobs: https://travis-ci.community/t/no-cache-support-on-arm64/5416/8
# (also used to get a different cache key than the amd64 one)
before_install:
- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib"
- sudo apt-get -q update
- sudo apt-get install -y libpcre3-dev
#- os: linux
#arch: arm64
#sudo: required
#env:
#- NPROC=6 # Worth trying more than 2 parallel jobs: https://travis-ci.community/t/no-cache-support-on-arm64/5416/8
## (also used to get a different cache key than the amd64 one)
#before_install:
#- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib"
#- sudo apt-get -q update
#- sudo apt-get install -y libpcre3-dev
- os: osx
before_install:
- HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 brew install ccache
env:
- NPROC=2
allow_failures:
# ARM64 is a bit buggy: https://travis-ci.community/t/no-output-has-been-received-and-then-build-terminated-on-arm64/8834
- arch: arm64
#allow_failures:
## ARM64 is a bit buggy: https://travis-ci.community/t/no-output-has-been-received-and-then-build-terminated-on-arm64/8834
#- arch: arm64
install:
@ -52,7 +51,6 @@ script:
# Building Nim-1.0.4 takes up to 10 minutes on Travis - the time limit after which jobs are cancelled for having no output
- make -j${NPROC} NIMFLAGS="--parallelBuild:2" V=1 update # to allow a newer Nim version to be detected
- make -j${NPROC} NIMFLAGS="--parallelBuild:2" LOG_LEVEL=TRACE
- make -j${NPROC} NIMFLAGS="--parallelBuild:2 -d:testnet_servers_image" LOG_LEVEL=TRACE
# Compiling "test_all" on ARM64 can take longer than 10 minutes, so make it verbose.
- make -j${NPROC} NIMFLAGS="--parallelBuild:2" DISABLE_TEST_FIXTURES_SCRIPT=1 V=1 test
- make -j${NPROC} NIMFLAGS="--parallelBuild:2 -d:testnet_servers_image" LOG_LEVEL=TRACE beacon_node
- make -j${NPROC} NIMFLAGS="--parallelBuild:2" DISABLE_TEST_FIXTURES_SCRIPT=1 test

View File

@ -243,9 +243,10 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
+ Deposit at MAX_EFFECTIVE_BALANCE balance (32 ETH) OK
+ Deposit over MAX_EFFECTIVE_BALANCE balance (32 ETH) OK
+ Deposit under MAX_EFFECTIVE_BALANCE balance (32 ETH) OK
+ Invalid deposit at MAX_EFFECTIVE_BALANCE balance (32 ETH) OK
+ Validator top-up OK
```
OK: 4/4 Fail: 0/4 Skip: 0/4
OK: 5/5 Fail: 0/5 Skip: 0/5
## [Unit - Spec - Epoch processing] Justification and Finalization [Preset: mainnet]
```diff
+ Rule I - 234 finalization with enough support OK
@ -265,4 +266,4 @@ OK: 8/8 Fail: 0/8 Skip: 0/8
OK: 1/1 Fail: 0/1 Skip: 0/1
---TOTAL---
OK: 155/163 Fail: 0/163 Skip: 8/163
OK: 156/164 Fail: 0/164 Skip: 8/164

2
Jenkinsfile vendored
View File

@ -27,7 +27,7 @@ def runStages() {
sh """#!/bin/bash
set -e
make -j${env.NPROC}
make -j${env.NPROC} LOG_LEVEL=TRACE NIMFLAGS='-d:testnet_servers_image'
make -j${env.NPROC} LOG_LEVEL=TRACE NIMFLAGS='-d:testnet_servers_image' beacon_node
"""
}
},

View File

@ -199,4 +199,21 @@ publish-book: | book
git worktree remove -f tmp-book && \
rm -rf tmp-book
auditors-book:
cd docs/the_auditors_handbook && \
mdbook build
publish-auditors-book: | auditors-book
git worktree add tmp-book gh-pages && \
mkdir -p tmp-book/auditors-book && \
rm -rf tmp-book/auditors-book/* && \
cp -a docs/the_auditors_handbook/book/* tmp-book/auditors-book/ && \
cd tmp-book && \
git add . && { \
git commit -m "make publish-auditors-book" && \
git push origin gh-pages || true; } && \
cd .. && \
git worktree remove -f tmp-book && \
rm -rf tmp-book
endif # "variables.mk" was not included

View File

@ -83,7 +83,7 @@ yourAURmanager -S base-devel pcre-static
Assuming you use [Homebrew](https://brew.sh/) to manage packages:
```sh
brew install pcre
brew install pcre cmake
```
Make sure you have [CMake](https://cmake.org/) installed, to be able to build libunwind (used for [lightweight stack traces](https://github.com/status-im/nim-libbacktrace)).

View File

@ -22,7 +22,7 @@ jobs:
- task: CacheBeta@1
displayName: 'cache official test fixtures'
inputs:
key: jsonTestsCache
key: jsonTestsCacheV1
path: jsonTestsCache
- task: CacheBeta@1
@ -67,7 +67,7 @@ jobs:
mingw32-make -j2 ARCH_OVERRIDE=${PLATFORM} LOG_LEVEL=TRACE
if [[ $PLATFORM == "x64" ]]; then
# everything builds more slowly on 32-bit, since there's no libbacktrace support
mingw32-make -j2 ARCH_OVERRIDE=${PLATFORM} LOG_LEVEL=TRACE NIMFLAGS="-d:testnet_servers_image"
mingw32-make -j2 ARCH_OVERRIDE=${PLATFORM} LOG_LEVEL=TRACE NIMFLAGS="-d:testnet_servers_image" beacon_node
fi
file build/beacon_node
# fail fast

View File

@ -20,9 +20,7 @@ logScope:
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregation-selection
func is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex,
slot_signature: ValidatorSig): bool =
var cache = get_empty_per_epoch_cache()
slot_signature: ValidatorSig, cache: var StateCache): bool =
let
committee = get_beacon_committee(state, slot, index, cache)
modulo = max(1, len(committee) div TARGET_AGGREGATORS_PER_COMMITTEE).uint64
@ -33,7 +31,7 @@ proc aggregate_attestations*(
privkey: ValidatorPrivKey, trailing_distance: uint64): Option[AggregateAndProof] =
doAssert state.slot >= trailing_distance
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/p2p-interface.md#configuration
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#configuration
doAssert trailing_distance <= ATTESTATION_PROPAGATION_SLOT_RANGE
let
@ -49,8 +47,9 @@ proc aggregate_attestations*(
# TODO for testing purposes, refactor this into the condition check
# and just calculation
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregation-selection
if not is_aggregator(state, slot, index, slot_signature):
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregation-selection
var cache = get_empty_per_epoch_cache()
if not is_aggregator(state, slot, index, slot_signature, cache):
return none(AggregateAndProof)
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#attestation-data
@ -67,7 +66,7 @@ proc aggregate_attestations*(
for attestation in getAttestationsForBlock(pool, state):
# getAttestationsForBlock(...) already aggregates
if attestation.data == attestation_data:
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregateandproof
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregateandproof
return some(AggregateAndProof(
aggregator_index: index.uint64,
aggregate: attestation,
@ -160,3 +159,120 @@ proc isValidAttestation*(
return false
true
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#global-topics
proc isValidAggregatedAttestation*(
pool: var AttestationPool,
signedAggregateAndProof: SignedAggregateAndProof,
current_slot: Slot): bool =
let
aggregate_and_proof = signedAggregateAndProof.message
aggregate = aggregate_and_proof.aggregate
# There's some overlap between this and isValidAttestation(), but unclear if
# saving a few lines of code would balance well with losing straightforward,
# spec-based synchronization.
#
# [IGNORE] aggregate.data.slot is within the last
# ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. aggregate.data.slot +
# ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot
if not (aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >=
current_slot and current_slot >= aggregate.data.slot):
debug "isValidAggregatedAttestation: aggregation.data.slot not within ATTESTATION_PROPAGATION_SLOT_RANGE"
return false
# [IGNORE] The valid aggregate attestation defined by
# hash_tree_root(aggregate) has not already been seen (via aggregate gossip,
# within a verified block, or through the creation of an equivalent aggregate
# locally).
#
# This is [IGNORE] and already checked by attestation pool when aggregate is
# added.
# [IGNORE] The aggregate is the first valid aggregate received for the
# aggregator with index aggregate_and_proof.aggregator_index for the epoch
# aggregate.data.target.epoch.
#
# This is [IGNORE] and already effectively checked by attestation pool upon
# attempting to resolve attestations.
# [REJECT] The block being voted for (aggregate.data.beacon_block_root)
# passes validation.
let attestationBlck = pool.blockPool.getRef(aggregate.data.beacon_block_root)
if attestationBlck.isNil:
debug "isValidAggregatedAttestation: block doesn't exist in block pool"
pool.blockPool.addMissing(aggregate.data.beacon_block_root)
return false
# [REJECT] The attestation has participants -- that is,
# len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1.
#
# get_attesting_indices() is:
# committee = get_beacon_committee(state, data.slot, data.index)
# return set(index for i, index in enumerate(committee) if bits[i])
#
# the attestation doesn't have participants is iff either:
# (1) the aggregation bits are all 0; or
# (2) the non-zero aggregation bits don't overlap with extant committee
# members, i.e. they counts don't match.
# But (2) would reflect an invalid aggregation in other ways, so reject it
# either way.
if isZeros(aggregate.aggregation_bits):
debug "isValidAggregatedAttestation: attestation has no or invalid aggregation bits"
return false
# [REJECT] aggregate_and_proof.selection_proof selects the validator as an
# aggregator for the slot -- i.e. is_aggregator(state, aggregate.data.slot,
# aggregate.data.index, aggregate_and_proof.selection_proof) returns True.
# TODO use withEpochState when it works more reliably
pool.blockPool.withState(
pool.blockPool.tmpState,
BlockSlot(blck: attestationBlck, slot: aggregate.data.slot)):
var cache = getEpochCache(blck, state)
if not is_aggregator(
state, aggregate.data.slot, aggregate.data.index.CommitteeIndex,
aggregate_and_proof.selection_proof, cache):
debug "isValidAggregatedAttestation: incorrect aggregator"
return false
# [REJECT] The aggregator's validator index is within the committee -- i.e.
# aggregate_and_proof.aggregator_index in get_beacon_committee(state,
# aggregate.data.slot, aggregate.data.index).
if aggregate_and_proof.aggregator_index.ValidatorIndex notin
get_beacon_committee(
state, aggregate.data.slot, aggregate.data.index.CommitteeIndex, cache):
debug "isValidAggregatedAttestation: aggregator's validator index not in committee"
return false
# [REJECT] The aggregate_and_proof.selection_proof is a valid signature of the
# aggregate.data.slot by the validator with index
# aggregate_and_proof.aggregator_index.
# get_slot_signature(state, aggregate.data.slot, privkey)
if aggregate_and_proof.aggregator_index >= state.validators.len.uint64:
debug "isValidAggregatedAttestation: invalid aggregator_index"
return false
if not verify_slot_signature(
state.fork, state.genesis_validators_root, aggregate.data.slot,
state.validators[aggregate_and_proof.aggregator_index].pubkey,
aggregate_and_proof.selection_proof):
debug "isValidAggregatedAttestation: selection_proof signature verification failed"
return false
# [REJECT] The aggregator signature, signed_aggregate_and_proof.signature, is valid.
if not verify_aggregate_and_proof_signature(
state.fork, state.genesis_validators_root, aggregate_and_proof,
state.validators[aggregate_and_proof.aggregator_index].pubkey,
signed_aggregate_and_proof.signature):
debug "isValidAggregatedAttestation: signed_aggregate_and_proof signature verification failed"
return false
# [REJECT] The signature of aggregate is valid.
if not is_valid_indexed_attestation(
state, get_indexed_attestation(state, aggregate, cache), {}):
debug "isValidAggregatedAttestation: aggregate signature verification failed"
return false
debug "isValidAggregatedAttestation: succeeded"
true

View File

@ -7,7 +7,7 @@
import
# Standard library
algorithm, os, tables, strutils, times, math, terminal, random,
algorithm, os, tables, strutils, times, math, terminal, bearssl, random,
# Nimble packages
stew/[objects, byteutils, endians2], stew/shims/macros,
@ -131,9 +131,11 @@ func enrForkIdFromState(state: BeaconState): ENRForkID =
next_fork_version: forkVer,
next_fork_epoch: FAR_FUTURE_EPOCH)
proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async.} =
proc init*(
T: type BeaconNode, rng: ref BrHmacDrbgContext,
conf: BeaconNodeConf): Future[BeaconNode] {.async.} =
let
netKeys = getPersistentNetKeys(conf)
netKeys = getPersistentNetKeys(rng[], conf)
nickname = if conf.nodeName == "auto": shortForm(netKeys)
else: conf.nodeName
db = BeaconChainDB.init(kvStore SqStoreRef.init(conf.databaseDir, "nbc").tryGet())
@ -227,7 +229,7 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async
enrForkId = enrForkIdFromState(blockPool.headState.data.data)
topicBeaconBlocks = getBeaconBlocksTopic(enrForkId.forkDigest)
topicAggregateAndProofs = getAggregateAndProofsTopic(enrForkId.forkDigest)
network = await createEth2Node(conf, enrForkId)
network = await createEth2Node(rng, conf, enrForkId)
var res = BeaconNode(
nickname: nickname,
@ -763,6 +765,13 @@ proc installAttestationHandlers(node: BeaconNode) =
return false
node.attestationPool.isValidAttestation(attestation, slot, committeeIndex)
proc aggregatedAttestationValidator(
signedAggregateAndProof: SignedAggregateAndProof): bool =
let (afterGenesis, slot) = node.beaconClock.now().toSlot()
if not afterGenesis:
return false
node.attestationPool.isValidAggregatedAttestation(signedAggregateAndProof, slot)
var attestationSubscriptions: seq[Future[void]] = @[]
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#attestations-and-aggregation
@ -776,6 +785,14 @@ proc installAttestationHandlers(node: BeaconNode) =
attestationValidator(attestation, ci)
))
attestationSubscriptions.add(node.network.subscribe(
getAggregateAndProofsTopic(node.forkDigest),
proc(signedAggregateAndProof: SignedAggregateAndProof) =
attestationHandler(signedAggregateAndProof.message.aggregate),
proc(signedAggregateAndProof: SignedAggregateAndProof): bool =
aggregatedAttestationValidator(signedAggregateAndProof)
))
waitFor allFutures(attestationSubscriptions)
proc stop*(node: BeaconNode) =
@ -1027,12 +1044,14 @@ when hasPrompt:
# var t: Thread[ptr Prompt]
# createThread(t, processPromptCommands, addr p)
proc createWalletInteractively(conf: BeaconNodeConf): OutFile {.raises: [Defect].} =
proc createWalletInteractively(
rng: var BrHmacDrbgContext,
conf: BeaconNodeConf): OutFile {.raises: [Defect].} =
if conf.nonInteractive:
fatal "Wallets can be created only in interactive mode"
quit 1
var mnemonic = generateMnemonic()
var mnemonic = generateMnemonic(rng)
defer: keystore_management.burnMem(mnemonic)
template readLine: string =
@ -1099,7 +1118,8 @@ proc createWalletInteractively(conf: BeaconNodeConf): OutFile {.raises: [Defect]
continue
break
let (uuid, walletContent) = KdfPbkdf2.createWalletContent(mnemonic, name)
let (uuid, walletContent) = KdfPbkdf2.createWalletContent(
rng, mnemonic, name)
try:
var outWalletFile: OutFile
@ -1172,6 +1192,10 @@ programMain:
else:
config.runtimePreset = defaultRuntimePreset
# Single RNG instance for the application - will be seeded on construction
# and avoid using system resources (such as urandom) after that
let rng = keys.newRng()
case config.cmd
of createTestnet:
var
@ -1227,7 +1251,7 @@ programMain:
let bootstrapFile = config.outputBootstrapFile.string
if bootstrapFile.len > 0:
let
networkKeys = getPersistentNetKeys(config)
networkKeys = getPersistentNetKeys(rng[], config)
metadata = getPersistentNetMetadata(config)
bootstrapEnr = enr.Record.init(
1, # sequence number
@ -1251,9 +1275,16 @@ programMain:
config.createDumpDirs()
var node = waitFor BeaconNode.init(config)
var node = waitFor BeaconNode.init(rng, config)
ctrlCHandling: status = BeaconNodeStatus.Stopping
## Ctrl+C handling
proc controlCHandler() {.noconv.} =
when defined(windows):
# workaround for https://github.com/nim-lang/Nim/issues/4057
setupForeignThreadGc()
info "Shutting down after having received SIGINT"
status = BeaconNodeStatus.Stopping
setControlCHook(controlCHandler)
when hasPrompt:
initPrompt(node)
@ -1278,6 +1309,7 @@ programMain:
let deposits = generateDeposits(
config.runtimePreset,
rng[],
config.totalDeposits,
config.outValidatorsDir,
config.outSecretsDir)
@ -1310,7 +1342,7 @@ programMain:
of wallets:
case config.walletsCmd:
of WalletsCmd.create:
let walletFile = createWalletInteractively(config)
let walletFile = createWalletInteractively(rng[], config)
of WalletsCmd.list:
# TODO
discard

View File

@ -359,11 +359,14 @@ func putStateCache(
# with the concomitant memory allocator and GC load. Instead, use a
# more memory-intensive (but more conceptually straightforward, and
# faster) strategy to just store, for the most recent slots.
if state.data.slot mod 2 != 0:
return
let stateCacheIndex = dag.getStateCacheIndex(blck.root, state.data.slot)
if stateCacheIndex == -1:
# Could use a deque or similar, but want simpler structure, and the data
# items are small and few.
const MAX_CACHE_SIZE = 32
const MAX_CACHE_SIZE = 16
let cacheLen = dag.cachedStates.len
doAssert cacheLen <= MAX_CACHE_SIZE
@ -644,17 +647,10 @@ template withEpochState*(
## cache is unsafe outside of block.
## TODO async transformations will lead to a race where cache gets updated
## while waiting for future to complete - catch this here somehow?
for ancestor in get_ancestors_in_epoch(blockSlot):
if getStateDataCached(dag, cache, ancestor):
break
template hashedState(): HashedBeaconState {.inject, used.} = cache.data
template state(): BeaconState {.inject, used.} = cache.data.data
template blck(): BlockRef {.inject, used.} = cache.blck
template root(): Eth2Digest {.inject, used.} = cache.data.root
body
# TODO implement the looser constraints allowed by epoch, not precise slot target
# allow expressing preference to opt-in to looser constraints regardless
dag.withState(cache, blockSlot):
body
proc updateStateData*(dag: CandidateChains, state: var StateData, bs: BlockSlot) =
## Rewind or advance state such that it matches the given block and slot -

View File

@ -1,6 +1,6 @@
import
os, strutils, options, json,
chronos, nimcrypto, confutils, web3, stint,
chronos, confutils, web3, stint,
eth/keys
# Compiled version of /scripts/depositContract.v.py in this repo

View File

@ -2,7 +2,7 @@
import
os, sequtils, strutils,
chronicles, stew/shims/net, stew/results, eth/keys, eth/trie/db,
chronicles, stew/shims/net, stew/results, eth/keys, eth/trie/db, bearssl,
eth/p2p/discoveryv5/[enr, protocol, discovery_db, node],
conf
@ -76,7 +76,7 @@ proc new*(T: type Eth2DiscoveryProtocol,
conf: BeaconNodeConf,
ip: Option[ValidIpAddress], tcpPort, udpPort: Port,
pk: PrivateKey,
enrFields: openarray[(string, seq[byte])]):
enrFields: openarray[(string, seq[byte])], rng: ref BrHmacDrbgContext):
T {.raises: [Exception, Defect].} =
# TODO
# Implement more configuration options:
@ -97,4 +97,5 @@ proc new*(T: type Eth2DiscoveryProtocol,
loadBootstrapFile(persistentBootstrapFile, bootstrapEnrs, ourPubKey)
let enrFieldPairs = mapIt(enrFields, toFieldPair(it[0], it[1]))
newProtocol(pk, db, ip, tcpPort, udpPort, enrFieldPairs, bootstrapEnrs)
newProtocol(
pk, db, ip, tcpPort, udpPort, enrFieldPairs, bootstrapEnrs, rng = rng)

View File

@ -4,7 +4,7 @@ import
options as stdOptions,
# Status libs
stew/[varints, base58, base64, endians2, results, byteutils],
stew/[varints, base58, base64, endians2, results, byteutils], bearssl,
stew/shims/net as stewNet,
stew/shims/[macros, tables],
faststreams/[inputs, outputs, buffers], snappy, snappy/framing,
@ -65,6 +65,7 @@ type
connWorkers: seq[Future[void]]
connTable: Table[PeerID, PeerInfo]
forkId: ENRForkID
rng*: ref BrHmacDrbgContext
EthereumNode = Eth2Node # needed for the definitions in p2p_backends_helpers
@ -796,7 +797,7 @@ proc getPersistentNetMetadata*(conf: BeaconNodeConf): Eth2Metadata =
proc init*(T: type Eth2Node, conf: BeaconNodeConf, enrForkId: ENRForkID,
switch: Switch, ip: Option[ValidIpAddress], tcpPort, udpPort: Port,
privKey: keys.PrivateKey): T =
privKey: keys.PrivateKey, rng: ref BrHmacDrbgContext): T =
new result
result.switch = switch
result.wantedPeers = conf.maxPeers
@ -810,7 +811,8 @@ proc init*(T: type Eth2Node, conf: BeaconNodeConf, enrForkId: ENRForkID,
result.forkId = enrForkId
result.discovery = Eth2DiscoveryProtocol.new(
conf, ip, tcpPort, udpPort, privKey,
{"eth2": SSZ.encode(result.forkId), "attnets": SSZ.encode(result.metadata.attnets)})
{"eth2": SSZ.encode(result.forkId), "attnets": SSZ.encode(result.metadata.attnets)},
rng)
newSeq result.protocolStates, allProtocols.len
for proto in allProtocols:
@ -1064,13 +1066,14 @@ proc initAddress*(T: type MultiAddress, str: string): T =
template tcpEndPoint(address, port): auto =
MultiAddress.init(address, tcpProtocol, port)
proc getPersistentNetKeys*(conf: BeaconNodeConf): KeyPair =
proc getPersistentNetKeys*(
rng: var BrHmacDrbgContext, conf: BeaconNodeConf): KeyPair =
let
privKeyPath = conf.dataDir / networkKeyFilename
privKey =
if not fileExists(privKeyPath):
createDir conf.dataDir.string
let key = PrivateKey.random(Secp256k1).tryGet()
let key = PrivateKey.random(Secp256k1, rng).tryGet()
writeFile(privKeyPath, key.getBytes().tryGet())
key
else:
@ -1086,7 +1089,7 @@ func gossipId(data: openArray[byte]): string =
func msgIdProvider(m: messages.Message): string =
gossipId(m.data)
proc createEth2Node*(conf: BeaconNodeConf, enrForkId: ENRForkID): Future[Eth2Node] {.async, gcsafe.} =
proc createEth2Node*(rng: ref BrHmacDrbgContext, conf: BeaconNodeConf, enrForkId: ENRForkID): Future[Eth2Node] {.async, gcsafe.} =
var
(extIp, extTcpPort, extUdpPort) = setupNat(conf)
hostAddress = tcpEndPoint(conf.libp2pAddress, conf.tcpPort)
@ -1096,7 +1099,7 @@ proc createEth2Node*(conf: BeaconNodeConf, enrForkId: ENRForkID): Future[Eth2Nod
info "Initializing networking", hostAddress,
announcedAddresses
let keys = conf.getPersistentNetKeys
let keys = getPersistentNetKeys(rng[], conf)
# TODO nim-libp2p still doesn't have support for announcing addresses
# that are different from the host address (this is relevant when we
# are running behind a NAT).
@ -1104,14 +1107,18 @@ proc createEth2Node*(conf: BeaconNodeConf, enrForkId: ENRForkID): Future[Eth2Nod
triggerSelf = true, gossip = true,
sign = false, verifySignature = false,
transportFlags = {ServerFlags.ReuseAddr},
msgIdProvider = msgIdProvider)
msgIdProvider = msgIdProvider,
secureManagers = [
SecureProtocol.Noise, # Only noise in ETH2!
],
rng = rng)
result = Eth2Node.init(conf, enrForkId, switch,
extIp, extTcpPort, extUdpPort,
keys.seckey.asEthKey)
keys.seckey.asEthKey, rng = rng)
proc getPersistenBootstrapAddr*(conf: BeaconNodeConf,
proc getPersistenBootstrapAddr*(rng: var BrHmacDrbgContext, conf: BeaconNodeConf,
ip: ValidIpAddress, port: Port): EnrResult[enr.Record] =
let pair = getPersistentNetKeys(conf)
let pair = getPersistentNetKeys(rng, conf)
return enr.Record.init(1'u64, # sequence number
pair.seckey.asEthKey,
some(ip), port, port, @[])

View File

@ -593,6 +593,7 @@ proc run(conf: InspectorConf) {.async.} =
var pubsubPeers = newTable[PeerID, PeerInfo]()
var resolveQueue = newAsyncQueue[PeerID](10)
var connectQueue = newAsyncQueue[PeerInfo](10)
let rng = lcrypto.newRng()
let bootnodes = loadBootstrapNodes(conf)
if len(bootnodes) == 0:
@ -667,7 +668,7 @@ proc run(conf: InspectorConf) {.async.} =
bootstrap_fork_digest = forkDigest.get()
forkDigest = argForkDigest
let seckey = lcrypto.PrivateKey.random(PKScheme.Secp256k1).tryGet()
let seckey = lcrypto.PrivateKey.random(PKScheme.Secp256k1, rng[]).tryGet()
# let pubkey = seckey.getKey()
let hostAddress = tryGetMultiAddress(conf.bindAddress)
@ -677,7 +678,7 @@ proc run(conf: InspectorConf) {.async.} =
var switch = newStandardSwitch(some(seckey), hostAddress.get(),
triggerSelf = true, gossip = true,
sign = false, verifySignature = false)
sign = false, verifySignature = false, rng = rng)
if len(conf.topics) > 0:
for item in conf.topics:

View File

@ -1,7 +1,7 @@
import
os, strutils, terminal,
chronicles, chronos, blscurve, nimcrypto, json_serialization, serialization,
web3, stint, eth/common/eth_types, eth/keys, confutils,
stew/byteutils, chronicles, chronos, web3, stint, json_serialization,
serialization, blscurve, eth/common/eth_types, eth/keys, confutils, bearssl,
spec/[datatypes, digest, crypto, keystore], conf, ssz/merkleization, merkle_minimal
export
@ -103,6 +103,7 @@ type
FailedToCreateDepositFile
proc generateDeposits*(preset: RuntimePreset,
rng: var BrHmacDrbgContext,
totalValidators: int,
validatorsDir: string,
secretsDir: string): Result[seq[Deposit], GenerateDepositsError] =
@ -110,13 +111,12 @@ proc generateDeposits*(preset: RuntimePreset,
info "Generating deposits", totalValidators, validatorsDir, secretsDir
for i in 0 ..< totalValidators:
let password = KeyStorePass getRandomBytesOrPanic(32).toHex
let credentials = generateCredentials(password = password)
let password = KeyStorePass getRandomBytes(rng, 32).toHex
let credentials = generateCredentials(rng, password = password)
let
keyName = intToStr(i, 6) & "_" & $(credentials.signingKey.toPubKey)
validatorDir = validatorsDir / keyName
passphraseFile = secretsDir / keyName
depositFile = validatorDir / depositFileName
keystoreFile = validatorDir / keystoreFileName

View File

@ -44,16 +44,6 @@ proc setupMainProc*(logLevel: string) =
stderr.write "Invalid value for --log-level. " & err.msg
quit 1
template ctrlCHandling*(extraCode: untyped) =
## Ctrl+C handling
proc controlCHandler() {.noconv.} =
when defined(windows):
# workaround for https://github.com/nim-lang/Nim/issues/4057
setupForeignThreadGc()
info "Shutting down after having received SIGINT"
extraCode
setControlCHook(controlCHandler)
template makeBannerAndConfig*(clientId: string, ConfType: type): untyped =
let
version = clientId & "\p" & copyrights & "\p\p" &

View File

@ -8,6 +8,9 @@ type
PeerFlags = enum
Acquired, DeleteOnRelease
EventType = enum
NotEmptyEvent, NotFullEvent
PeerItem[T] = object
data: T
peerType: PeerType
@ -62,21 +65,35 @@ iterator pairs*[A, B](pool: PeerPool[A, B]): (B, A) =
for peerId, peerIdx in pool.registry:
yield (peerId, pool.storage[peerIdx.data].data)
proc waitNotEmptyEvent[A, B](pool: PeerPool[A, B],
filter: set[PeerType]) {.async.} =
template incomingEvent(eventType: EventType): AsyncEvent =
case eventType
of EventType.NotEmptyEvent:
pool.incNotEmptyEvent
of EventType.NotFullEvent:
pool.incNotFullEvent
template outgoingEvent(eventType: EventType): AsyncEvent =
case eventType
of EventType.NotEmptyEvent:
pool.outNotEmptyEvent
of EventType.NotFullEvent:
pool.outNotFullEvent
proc waitForEvent[A, B](pool: PeerPool[A, B], eventType: EventType,
filter: set[PeerType]) {.async.} =
if filter == {PeerType.Incoming, PeerType.Outgoing} or filter == {}:
var fut1 = pool.incNotEmptyEvent.wait()
var fut2 = pool.outNotEmptyEvent.wait()
var fut1 = incomingEvent(eventType).wait()
var fut2 = outgoingEvent(eventType).wait()
try:
discard await one(fut1, fut2)
if fut1.finished:
if not(fut2.finished):
fut2.cancel()
pool.incNotEmptyEvent.clear()
incomingEvent(eventType).clear()
else:
if not(fut1.finished):
fut1.cancel()
pool.outNotEmptyEvent.clear()
outgoingEvent(eventType).clear()
except CancelledError:
if not(fut1.finished):
fut1.cancel()
@ -84,20 +101,19 @@ proc waitNotEmptyEvent[A, B](pool: PeerPool[A, B],
fut2.cancel()
raise
elif PeerType.Incoming in filter:
await pool.incNotEmptyEvent.wait()
pool.incNotEmptyEvent.clear()
await incomingEvent(eventType).wait()
incomingEvent(eventType).clear()
elif PeerType.Outgoing in filter:
await pool.outNotEmptyEvent.wait()
pool.outNotEmptyEvent.clear()
await outgoingEvent(eventType).wait()
outgoingEvent(eventType).clear()
proc waitNotEmptyEvent[A, B](pool: PeerPool[A, B],
filter: set[PeerType]): Future[void] =
pool.waitForEvent(EventType.NotEmptyEvent, filter)
proc waitNotFullEvent[A, B](pool: PeerPool[A, B],
peerType: PeerType) {.async.} =
if peerType == PeerType.Incoming:
await pool.incNotFullEvent.wait()
pool.incNotFullEvent.clear()
elif peerType == PeerType.Outgoing:
await pool.outNotFullEvent.wait()
pool.outNotFullEvent.clear()
filter: set[PeerType]): Future[void] =
pool.waitForEvent(EventType.NotFullEvent, filter)
template getItem[A, B](pool: PeerPool[A, B],
filter: set[PeerType]): ptr PeerItem[A] =
@ -336,20 +352,19 @@ proc addPeer*[A, B](pool: PeerPool[A, B],
let peerKey = getKey(peer)
if not(pool.registry.hasKey(peerKey)) and not(peer.getFuture().finished):
if len(pool.registry) >= pool.maxPeersCount:
await pool.waitNotFullEvent(peerType)
while len(pool.registry) >= pool.maxPeersCount:
await pool.waitNotFullEvent({PeerType.Incoming, PeerType.Outgoing})
if peerType == PeerType.Incoming:
if pool.curIncPeersCount >= pool.maxIncPeersCount:
await pool.waitNotFullEvent(peerType)
while pool.curIncPeersCount >= pool.maxIncPeersCount:
await pool.waitNotFullEvent({peerType})
let pindex = pool.addPeerImpl(peer, peerKey, peerType)
inc(pool.curIncPeersCount)
pool.incQueue.push(pindex)
pool.incNotEmptyEvent.fire()
res = true
elif peerType == PeerType.Outgoing:
if pool.curOutPeersCount >= pool.maxOutPeersCount:
await pool.waitNotFullEvent(peerType)
while pool.curOutPeersCount >= pool.maxOutPeersCount:
await pool.waitNotFullEvent({peerType})
let pindex = pool.addPeerImpl(peer, peerKey, peerType)
inc(pool.curOutPeersCount)
pool.outQueue.push(pindex)

View File

@ -53,7 +53,7 @@ func decrease_balance*(
proc process_deposit*(preset: RuntimePreset,
state: var BeaconState,
deposit: Deposit,
flags: UpdateFlags = {}): bool {.nbench.}=
flags: UpdateFlags = {}): Result[void, cstring] {.nbench.}=
# Process an Eth1 deposit, registering a validator or increasing its balance.
# Verify the Merkle branch
@ -64,10 +64,7 @@ proc process_deposit*(preset: RuntimePreset,
state.eth1_deposit_index,
state.eth1_data.deposit_root,
):
notice "Deposit Merkle validation failed",
proof = deposit.proof, deposit_root = state.eth1_data.deposit_root,
deposit_index = state.eth1_deposit_index
return false
return err("process_deposit: deposit Merkle validation failed")
# Deposits must be processed in order
state.eth1_deposit_index += 1
@ -89,7 +86,7 @@ proc process_deposit*(preset: RuntimePreset,
# large number of invalid deposits on Altona
trace "Skipping deposit with invalid signature",
deposit = shortLog(deposit.data)
return true
return ok()
# Add validator and balance entries
state.validators.add(Validator(
@ -107,7 +104,7 @@ proc process_deposit*(preset: RuntimePreset,
# Increase balance by deposit amount
increase_balance(state, index.ValidatorIndex, amount)
true
ok()
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#compute_activation_exit_epoch
func compute_activation_exit_epoch(epoch: Epoch): Epoch =
@ -178,12 +175,12 @@ proc slash_validator*(state: var BeaconState, slashed_index: ValidatorIndex,
validator.effective_balance div MIN_SLASHING_PENALTY_QUOTIENT)
# The rest doesn't make sense without there being any proposer index, so skip
# Apply proposer and whistleblower rewards
let proposer_index = get_beacon_proposer_index(state, cache)
if proposer_index.isNone:
debug "No beacon proposer index and probably no active validators"
return
# Apply proposer and whistleblower rewards
let
# Spec has whistleblower_index as optional param, but it's never used.
whistleblower_index = proposer_index.get
@ -642,7 +639,7 @@ proc check_attestation*(
proc process_attestation*(
state: var BeaconState, attestation: SomeAttestation, flags: UpdateFlags,
stateCache: var StateCache): bool {.nbench.}=
stateCache: var StateCache): Result[void, cstring] {.nbench.} =
# In the spec, attestation validation is mixed with state mutation, so here
# we've split it into two functions so that the validation logic can be
# reused when looking for suitable blocks to include in attestations.
@ -651,8 +648,7 @@ proc process_attestation*(
let proposer_index = get_beacon_proposer_index(state, stateCache)
if proposer_index.isNone:
debug "No beacon proposer index and probably no active validators"
return false
return err("process_attestation: no beacon proposer index and probably no active validators")
if check_attestation(state, attestation, flags, stateCache):
let
@ -679,13 +675,9 @@ proc process_attestation*(
state, attestation.data, attestation.aggregation_bits, stateCache).len
state.previous_epoch_attestations.add(pending_attestation)
true
ok()
else:
trace "process_attestation: check_attestation failed",
attestation = shortLog(attestation),
indices = get_attesting_indices(
state, attestation.data, attestation.aggregation_bits, stateCache).len
false
err("process_attestation: check_attestation failed")
func makeAttestationData*(
state: BeaconState, slot: Slot, committee_index: uint64,

View File

@ -28,7 +28,6 @@ import
./digest,
# Status
stew/[endians2, objects, results, byteutils],
nimcrypto/sysrand,
blscurve,
chronicles,
json_serialization,
@ -175,25 +174,6 @@ func blsFastAggregateVerify*(
fastAggregateVerify(unwrapped, message, signature.blsValue)
proc newKeyPair*(): BlsResult[tuple[pub: ValidatorPubKey, priv: ValidatorPrivKey]] =
## Generates a new public-private keypair
## This requires entropy on the system
# The input-keying-material requires 32 bytes at least for security
# The generation is deterministic and the input-keying-material
# must be protected against side-channel attacks
var ikm: array[32, byte]
if randomBytes(ikm) != 32:
return err "bls: no random bytes"
var
sk: SecretKey
pk: PublicKey
if keyGen(ikm, pk, sk):
ok((ValidatorPubKey(kind: Real, blsValue: pk), ValidatorPrivKey(sk)))
else:
err "bls: cannot generate keypair"
proc toGaugeValue*(hash: Eth2Digest): int64 =
# Only the last 8 bytes are taken into consideration in accordance
# to the ETH2 metrics spec:
@ -362,16 +342,3 @@ func init*(T: typedesc[ValidatorSig], data: array[RawSigSize, byte]): T {.noInit
if v.isErr:
raise (ref ValueError)(msg: $v.error)
return v[]
proc getRandomBytes*(n: Natural): seq[byte]
{.raises: [RandomSourceDepleted, Defect].} =
result = newSeq[byte](n)
if randomBytes(result) != result.len:
raise newException(RandomSourceDepleted, "Failed to generate random bytes")
proc getRandomBytesOrPanic*(output: var openArray[byte]) =
doAssert randomBytes(output) == output.len
proc getRandomBytesOrPanic*(n: Natural): seq[byte] =
result = newSeq[byte](n)
getRandomBytesOrPanic(result)

View File

@ -12,6 +12,55 @@ proc get_v1_beacon_states_root(stateId: string): Eth2Digest
# TODO stateId is part of the REST path
proc get_v1_beacon_states_fork(stateId: string): Fork
# TODO stateId is part of the REST path
proc get_v1_beacon_states_finality_checkpoints(
stateId: string): BeaconStatesFinalityCheckpointsTuple
# TODO stateId is part of the REST path
proc get_v1_beacon_states_stateId_validators(
stateId: string, validatorIds: seq[string],
status: string): seq[BeaconStatesValidatorsTuple]
# TODO stateId and validatorId are part of the REST path
proc get_v1_beacon_states_stateId_validators_validatorId(
stateId: string, validatorId: string): BeaconStatesValidatorsTuple
# TODO stateId and epoch are part of the REST path
proc get_v1_beacon_states_stateId_committees_epoch(stateId: string,
epoch: uint64, index: uint64, slot: uint64): seq[BeaconStatesCommitteesTuple]
proc get_v1_beacon_headers(slot: uint64, parent_root: Eth2Digest): seq[BeaconHeadersTuple]
# TODO blockId is part of the REST path
proc get_v1_beacon_headers_blockId(blockId: string):
tuple[canonical: bool, header: SignedBeaconBlockHeader]
# TODO blockId is part of the REST path
proc get_v1_beacon_blocks_blockId(blockId: string): SignedBeaconBlock
# TODO blockId is part of the REST path
proc get_v1_beacon_blocks_blockId_root(blockId: string): Eth2Digest
# TODO blockId is part of the REST path
proc get_v1_beacon_blocks_blockId_attestations(blockId: string): seq[Attestation]
# TODO POST /v1/beacon/pool/attester_slashings
# TODO GET /v1/beacon/pool/attester_slashings
# TODO POST /v1/beacon/pool/proposer_slashings
# TODO GET /v1/beacon/pool/proposer_slashings
# TODO POST /v1/beacon/pool/voluntary_exits
# TODO GET /v1/beacon/pool/voluntary_exits
# TODO POST /v1/beacon/pool/attestations
# TODO GET /v1/beacon/pool/attestations
proc post_v1_beacon_pool_attestations(attestation: Attestation): bool
proc get_v1_config_fork_schedule(): seq[tuple[epoch: uint64, version: Version]]
# TODO stateId is part of the REST path
proc get_v1_debug_beacon_states_stateId(stateId: string): BeaconState
# TODO: delete old stuff

View File

@ -21,3 +21,23 @@ type
genesis_time: uint64
genesis_validators_root: Eth2Digest
genesis_fork_version: Version
BeaconStatesFinalityCheckpointsTuple* = tuple
previous_justified: Checkpoint
current_justified: Checkpoint
finalized: Checkpoint
BeaconStatesValidatorsTuple* = tuple
validator: Validator
status: string
balance: uint64
BeaconStatesCommitteesTuple* = tuple
index: uint64
slot: uint64
validators: seq[uint64] # each object in the sequence should have an index field...
BeaconHeadersTuple* = tuple
root: Eth2Digest
canonical: bool
header: SignedBeaconBlockHeader

View File

@ -10,19 +10,16 @@ import
# calls that return a bool are actually without a return type in the main REST API
# spec but nim-json-rpc requires that all RPC calls have a return type.
proc post_v1_beacon_pool_attestations(attestation: Attestation): bool
proc get_v1_validator_block(slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig): BeaconBlock
# TODO slot is part of the REST path
proc get_v1_validator_blocks(slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig): BeaconBlock
proc post_v1_validator_block(body: SignedBeaconBlock): bool
proc post_v1_beacon_blocks(body: SignedBeaconBlock): bool
proc get_v1_validator_attestation_data(slot: Slot, committee_index: CommitteeIndex): AttestationData
proc get_v1_validator_attestation(slot: Slot, committee_index: CommitteeIndex): AttestationData
# TODO at the time of writing (10.06.2020) the API specifies this call to have a hash of
# the attestation data instead of the object itself but we also need the slot.. see here:
# https://docs.google.com/spreadsheets/d/1kVIx6GvzVLwNYbcd-Fj8YUlPf4qGrWUlS35uaTnIAVg/edit?disco=AAAAGh7r_fQ
proc get_v1_validator_aggregate_attestation(attestation_data: AttestationData): Attestation
proc get_v1_validator_aggregate_and_proof(attestation_data: AttestationData): Attestation
proc post_v1_validator_aggregate_and_proof(payload: SignedAggregateAndProof): bool

View File

@ -6,10 +6,10 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
json, math, strutils, strformat, typetraits,
json, math, strutils, strformat, typetraits, bearssl,
stew/[results, byteutils, bitseqs, bitops2], stew/shims/macros,
eth/keyfile/uuid, blscurve, json_serialization,
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, sysrand, utils],
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, utils],
./datatypes, ./crypto, ./digest, ./signatures
export
@ -132,6 +132,11 @@ template burnMem*(m: var (SensitiveData|TaintedString)) =
# to make its usage less error-prone.
utils.burnMem(string m)
proc getRandomBytes*(rng: var BrHmacDrbgContext, n: Natural): seq[byte]
{.raises: [Defect].} =
result = newSeq[byte](n)
brHmacDrbgGenerate(rng, result)
macro wordListArray(filename: static string): array[wordListLen, cstring] =
result = newTree(nnkBracket)
var words = slurp(filename).split()
@ -180,14 +185,17 @@ func getSeed*(mnemonic: Mnemonic, password: KeyStorePass): KeySeed =
let salt = "mnemonic-" & password.string
KeySeed sha512.pbkdf2(mnemonic.string, salt, 2048, 64)
proc generateMnemonic*(words: openarray[cstring] = englishWords,
entropyParam: openarray[byte] = @[]): Mnemonic =
proc generateMnemonic*(
rng: var BrHmacDrbgContext,
words: openarray[cstring] = englishWords,
entropyParam: openarray[byte] = @[]): Mnemonic =
# https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
doAssert words.len == wordListLen
var entropy: seq[byte]
if entropyParam.len == 0:
entropy = getRandomBytesOrPanic(32)
setLen(entropy, 32)
brHmacDrbgGenerate(rng, entropy)
else:
doAssert entropyParam.len >= 128 and
entropyParam.len <= 256 and
@ -325,6 +333,7 @@ proc decryptKeystore*(data: KeyStoreContent,
ValidatorPrivKey.fromRaw(? secret)
proc createCryptoField(T: type[KdfParams],
rng: var BrHmacDrbgContext,
secret: openarray[byte],
password = KeyStorePass "",
salt: openarray[byte] = @[],
@ -340,13 +349,13 @@ proc createCryptoField(T: type[KdfParams],
doAssert salt.len == saltSize
@salt
else:
getRandomBytesOrPanic(saltSize)
getRandomBytes(rng, saltSize)
let aesIv = if iv.len > 0:
doAssert iv.len == AES.sizeBlock
@iv
else:
getRandomBytesOrPanic(AES.sizeBlock)
getRandomBytes(rng, AES.sizeBlock)
when T is KdfPbkdf2:
decKey = sha256.pbkdf2(password.string, kdfSalt, pbkdf2Params.c,
@ -374,6 +383,7 @@ proc createCryptoField(T: type[KdfParams],
message: byteutils.toHex(cipherMsg)))
proc encryptKeystore*(T: type[KdfParams],
rng: var BrHmacDrbgContext,
privKey: ValidatorPrivkey,
password = KeyStorePass "",
path = KeyPath "",
@ -382,7 +392,7 @@ proc encryptKeystore*(T: type[KdfParams],
pretty = true): KeyStoreContent =
let
secret = privKey.toRaw[^32..^1]
cryptoField = createCryptoField(T, secret, password, salt, iv)
cryptoField = createCryptoField(T, rng, secret, password, salt, iv)
pubkey = privKey.toPubKey()
uuid = uuidGenerate().expect("Random bytes should be available")
keystore = Keystore[T](
@ -396,6 +406,7 @@ proc encryptKeystore*(T: type[KdfParams],
else: $(%keystore)
proc createWallet*(T: type[KdfParams],
rng: var BrHmacDrbgContext,
mnemonic: Mnemonic,
name = WalletName "",
salt: openarray[byte] = @[],
@ -409,7 +420,7 @@ proc createWallet*(T: type[KdfParams],
# we want the wallet restoration procedure to depend only on the
# mnemonic (the user is asked to treat the mnemonic as a password).
seed = getSeed(mnemonic, KeyStorePass"")
cryptoField = %createCryptoField(T, distinctBase seed, password, salt, iv)
cryptoField = %createCryptoField(T,rng, distinctBase seed, password, salt, iv)
Wallet(
uuid: uuid,
@ -422,6 +433,7 @@ proc createWallet*(T: type[KdfParams],
nextAccount: nextAccount.get(0))
proc createWalletContent*(T: type[KdfParams],
rng: var BrHmacDrbgContext,
mnemonic: Mnemonic,
name = WalletName "",
salt: openarray[byte] = @[],
@ -429,10 +441,12 @@ proc createWalletContent*(T: type[KdfParams],
password = KeyStorePass "",
nextAccount = none(Natural),
pretty = true): (UUID, WalletContent) =
let wallet = createWallet(T, mnemonic, name, salt, iv, password, nextAccount, pretty)
let wallet = createWallet(
T, rng, mnemonic, name, salt, iv, password, nextAccount, pretty)
(wallet.uuid, WalletContent Json.encode(wallet, pretty = pretty))
proc restoreCredentials*(mnemonic: Mnemonic,
proc restoreCredentials*(rng: var BrHmacDrbgContext,
mnemonic: Mnemonic,
password = KeyStorePass ""): Credentials =
let
withdrawalKeyPath = makeKeyPath(0, withdrawalKeyKind)
@ -443,14 +457,15 @@ proc restoreCredentials*(mnemonic: Mnemonic,
Credentials(
mnemonic: mnemonic,
keyStore: encryptKeystore(KdfPbkdf2, signingKey, password, signingKeyPath),
keyStore: encryptKeystore(KdfPbkdf2, rng, signingKey, password, signingKeyPath),
signingKey: signingKey,
withdrawalKey: withdrawalKey)
proc generateCredentials*(entropy: openarray[byte] = @[],
proc generateCredentials*(rng: var BrHmacDrbgContext,
entropy: openarray[byte] = @[],
password = KeyStorePass ""): Credentials =
let mnemonic = generateMnemonic(englishWords, entropy)
restoreCredentials(mnemonic, password)
let mnemonic = generateMnemonic(rng, englishWords, entropy)
restoreCredentials(rng, mnemonic, password)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/deposit-contract.md#withdrawal-credentials
proc makeWithdrawalCredentials*(k: ValidatorPubKey): Eth2Digest =

View File

@ -29,6 +29,18 @@ func get_slot_signature*(
blsSign(privKey, signing_root.data)
func verify_slot_signature*(
fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot,
pubkey: ValidatorPubKey, signature: SomeSig): bool =
withTrust(signature):
let
epoch = compute_epoch_at_slot(slot)
domain = get_domain(
fork, DOMAIN_SELECTION_PROOF, epoch, genesis_validators_root)
signing_root = compute_signing_root(slot, domain)
blsVerify(pubkey, signing_root.data, signature)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#randao-reveal
func get_epoch_signature*(
fork: Fork, genesis_validators_root: Eth2Digest, epoch: Epoch,
@ -87,6 +99,18 @@ func get_aggregate_and_proof_signature*(fork: Fork, genesis_validators_root: Eth
blsSign(privKey, signing_root.data)
func verify_aggregate_and_proof_signature*(fork: Fork, genesis_validators_root: Eth2Digest,
aggregate_and_proof: AggregateAndProof,
pubkey: ValidatorPubKey, signature: SomeSig): bool =
withTrust(signature):
let
epoch = compute_epoch_at_slot(aggregate_and_proof.aggregate.data.slot)
domain = get_domain(
fork, DOMAIN_AGGREGATE_AND_PROOF, epoch, genesis_validators_root)
signing_root = compute_signing_root(aggregate_and_proof, domain)
blsVerify(pubKey, signing_root.data, signature)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregate-signature
func get_attestation_signature*(
fork: Fork, genesis_validators_root: Eth2Digest,

View File

@ -21,8 +21,6 @@
# spec does so also.
# * For indices, we get a mix of uint64, ValidatorIndex and int - this is currently
# swept under the rug with casts
# * Sane error handling is missing in most cases (yay, we'll get the chance to
# debate exceptions again!)
# When updating the code, add TODO sections to mark where there are clear
# improvements to be made - other than that, keep things similar to spec for
# now.
@ -43,39 +41,28 @@ declareGauge beacon_pending_deposits, "Number of pending deposits (state.eth1_da
declareGauge beacon_processed_deposits_total, "Number of total deposits included on chain" # On block
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#block-header
proc process_block_header*(
func process_block_header*(
state: var BeaconState, blck: SomeBeaconBlock, flags: UpdateFlags,
stateCache: var StateCache): bool {.nbench.} =
logScope:
blck = shortLog(blck)
stateCache: var StateCache): Result[void, cstring] {.nbench.} =
# Verify that the slots match
if not (blck.slot == state.slot):
notice "Block header: slot mismatch",
state_slot = shortLog(state.slot)
return false
return err("process_block_header: slot mismatch")
# Verify that the block is newer than latest block header
if not (blck.slot > state.latest_block_header.slot):
debug "Block header: block not newer than latest block header"
return false
return err("process_block_header: block not newer than latest block header")
# Verify that proposer index is the correct index
let proposer_index = get_beacon_proposer_index(state, stateCache)
if proposer_index.isNone:
debug "Block header: proposer missing"
return false
return err("process_block_header: proposer missing")
if not (blck.proposer_index.ValidatorIndex == proposer_index.get):
notice "Block header: proposer index incorrect",
proposer_index = proposer_index.get
return false
return err("process_block_header: proposer index incorrect")
# Verify that the parent matches
if not (blck.parent_root == hash_tree_root(state.latest_block_header)):
notice "Block header: previous block root mismatch",
latest_block_header = state.latest_block_header,
latest_block_header_root = shortLog(hash_tree_root(state.latest_block_header))
return false
return err("process_block_header: previous block root mismatch")
# Cache current block as the new latest block
state.latest_block_header = BeaconBlockHeader(
@ -89,12 +76,11 @@ proc process_block_header*(
# Verify proposer is not slashed
let proposer = state.validators[proposer_index.get]
if proposer.slashed:
notice "Block header: proposer slashed"
return false
return err("process_block_header: proposer slashed")
true
ok()
proc `xor`[T: array](a, b: T): T =
func `xor`[T: array](a, b: T): T =
for i in 0..<result.len:
result[i] = a[i] xor b[i]
@ -152,7 +138,8 @@ func is_slashable_validator(validator: Validator, epoch: Epoch): bool =
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#proposer-slashings
proc process_proposer_slashing*(
state: var BeaconState, proposer_slashing: ProposerSlashing,
flags: UpdateFlags, stateCache: var StateCache): bool {.nbench.}=
flags: UpdateFlags, stateCache: var StateCache):
Result[void, cstring] {.nbench.} =
let
header_1 = proposer_slashing.signed_header_1.message
@ -160,29 +147,24 @@ proc process_proposer_slashing*(
# Not from spec
if header_1.proposer_index.int >= state.validators.len:
notice "Proposer slashing: invalid proposer index"
return false
return err("process_proposer_slashing: invalid proposer index")
# Verify header slots match
if not (header_1.slot == header_2.slot):
notice "Proposer slashing: slot mismatch"
return false
return err("process_proposer_slashing: slot mismatch")
# Verify header proposer indices match
if not (header_1.proposer_index == header_2.proposer_index):
notice "Proposer slashing: proposer indices mismatch"
return false
return err("process_proposer_slashing: proposer indices mismatch")
# Verify the headers are different
if not (header_1 != header_2):
notice "Proposer slashing: headers not different"
return false
return err("process_proposer_slashing: headers not different")
# Verify the proposer is slashable
let proposer = state.validators[header_1.proposer_index]
if not is_slashable_validator(proposer, get_current_epoch(state)):
notice "Proposer slashing: slashed proposer"
return false
return err("process_proposer_slashing: slashed proposer")
# Verify signatures
if skipBlsValidation notin flags:
@ -191,13 +173,11 @@ proc process_proposer_slashing*(
if not verify_block_signature(
state.fork, state.genesis_validators_root, signed_header.message.slot,
signed_header.message, proposer.pubkey, signed_header.signature):
notice "Proposer slashing: invalid signature",
signature_index = i
return false
return err("process_proposer_slashing: invalid signature")
slashValidator(state, header_1.proposer_index.ValidatorIndex, stateCache)
true
ok()
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#is_slashable_attestation_data
func is_slashable_attestation_data(
@ -217,84 +197,72 @@ proc process_attester_slashing*(
attester_slashing: AttesterSlashing,
flags: UpdateFlags,
stateCache: var StateCache
): bool {.nbench.}=
let
attestation_1 = attester_slashing.attestation_1
attestation_2 = attester_slashing.attestation_2
): Result[void, cstring] {.nbench.}=
let
attestation_1 = attester_slashing.attestation_1
attestation_2 = attester_slashing.attestation_2
if not is_slashable_attestation_data(
attestation_1.data, attestation_2.data):
notice "Attester slashing: surround or double vote check failed"
return false
if not is_slashable_attestation_data(
attestation_1.data, attestation_2.data):
return err("Attester slashing: surround or double vote check failed")
if not is_valid_indexed_attestation(state, attestation_1, flags):
notice "Attester slashing: invalid attestation 1"
return false
if not is_valid_indexed_attestation(state, attestation_1, flags):
return err("Attester slashing: invalid attestation 1")
if not is_valid_indexed_attestation(state, attestation_2, flags):
notice "Attester slashing: invalid attestation 2"
return false
if not is_valid_indexed_attestation(state, attestation_2, flags):
return err("Attester slashing: invalid attestation 2")
var slashed_any = false
var slashed_any = false
for index in sorted(toSeq(intersection(
toHashSet(attestation_1.attesting_indices.asSeq),
toHashSet(attestation_2.attesting_indices.asSeq)).items), system.cmp):
if is_slashable_validator(
state.validators[index.int], get_current_epoch(state)):
slash_validator(state, index.ValidatorIndex, stateCache)
slashed_any = true
if not slashed_any:
notice "Attester slashing: Trying to slash participant(s) twice"
return false
return true
for index in sorted(toSeq(intersection(
toHashSet(attestation_1.attesting_indices.asSeq),
toHashSet(attestation_2.attesting_indices.asSeq)).items), system.cmp):
if is_slashable_validator(
state.validators[index.int], get_current_epoch(state)):
slash_validator(state, index.ValidatorIndex, stateCache)
slashed_any = true
if not slashed_any:
return err("Attester slashing: Trying to slash participant(s) twice")
ok()
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.4/specs/core/0_beacon-chain.md#voluntary-exits
proc process_voluntary_exit*(
state: var BeaconState,
signed_voluntary_exit: SignedVoluntaryExit,
flags: UpdateFlags): bool {.nbench.}=
flags: UpdateFlags): Result[void, cstring] {.nbench.} =
let voluntary_exit = signed_voluntary_exit.message
# Not in spec. Check that validator_index is in range
if voluntary_exit.validator_index >= state.validators.len.uint64:
notice "Exit: invalid validator index",
index = voluntary_exit.validator_index,
num_validators = state.validators.len
return false
return err("Exit: invalid validator index")
let validator = state.validators[voluntary_exit.validator_index.int]
# Verify the validator is active
if not is_active_validator(validator, get_current_epoch(state)):
notice "Exit: validator not active"
return false
return err("Exit: validator not active")
# Verify the validator has not yet exited
if validator.exit_epoch != FAR_FUTURE_EPOCH:
notice "Exit: validator has exited"
return false
return err("Exit: validator has exited")
## Exits must specify an epoch when they become valid; they are not valid
## before then
# Exits must specify an epoch when they become valid; they are not valid
# before then
if not (get_current_epoch(state) >= voluntary_exit.epoch):
notice "Exit: exit epoch not passed"
return false
return err("Exit: exit epoch not passed")
# Verify the validator has been active long enough
if not (get_current_epoch(state) >= validator.activation_epoch +
SHARD_COMMITTEE_PERIOD):
notice "Exit: not in validator set long enough"
return false
return err("Exit: not in validator set long enough")
# Verify signature
if skipBlsValidation notin flags:
if not verify_voluntary_exit_signature(
state.fork, state.genesis_validators_root, voluntary_exit,
validator.pubkey, signed_voluntary_exit.signature):
notice "Exit: invalid signature"
return false
return err("Exit: invalid signature")
# Initiate exit
debug "Exit: processing voluntary exit (validator_leaving)",
@ -310,14 +278,14 @@ proc process_voluntary_exit*(
initiate_validator_exit(
state, voluntary_exit.validator_index.ValidatorIndex, cache)
true
ok()
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#operations
proc process_operations(preset: RuntimePreset,
state: var BeaconState,
body: SomeBeaconBlockBody,
flags: UpdateFlags,
stateCache: var StateCache): bool {.nbench.} =
stateCache: var StateCache): Result[void, cstring] {.nbench.} =
# Verify that outstanding deposits are processed up to the maximum number of
# deposits
let
@ -325,34 +293,32 @@ proc process_operations(preset: RuntimePreset,
req_deposits = min(MAX_DEPOSITS,
state.eth1_data.deposit_count.int64 - state.eth1_deposit_index.int64)
if not (num_deposits == req_deposits):
notice "processOperations: incorrect number of deposits",
num_deposits = num_deposits,
req_deposits = req_deposits,
deposit_count = state.eth1_data.deposit_count,
deposit_index = state.eth1_deposit_index
return false
return err("incorrect number of deposits")
template for_ops_cached(operations: auto, fn: auto) =
for operation in operations:
if not fn(state, operation, flags, stateCache):
return false
let res = fn(state, operation, flags, stateCache)
if res.isErr:
return res
template for_ops(operations: auto, fn: auto) =
for operation in operations:
if not fn(state, operation, flags):
return false
let res = fn(state, operation, flags)
if res.isErr:
return res
for_ops_cached(body.proposer_slashings, process_proposer_slashing)
for_ops_cached(body.attester_slashings, process_attester_slashing)
for_ops_cached(body.attestations, process_attestation)
for deposit in body.deposits:
if not process_deposit(preset, state, deposit, flags):
return false
let res = process_deposit(preset, state, deposit, flags)
if res.isErr:
return res
for_ops(body.voluntary_exits, process_voluntary_exit)
true
ok()
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#block-processing
proc process_block*(
@ -379,8 +345,13 @@ proc process_block*(
beacon_previous_live_validators.set(toHashSet(
mapIt(state.previous_epoch_attestations, it.proposerIndex)).len.int64)
if not process_block_header(state, blck, flags, stateCache):
notice "Block header not valid", slot = shortLog(state.slot)
logScope:
blck = shortLog(blck)
let res_block = process_block_header(state, blck, flags, stateCache)
if res_block.isErr:
debug "Block header not valid",
block_header_error = $(res_block.error),
slot = state.slot
return false
if not process_randao(state, blck.body, flags, stateCache):
@ -388,8 +359,14 @@ proc process_block*(
return false
process_eth1_data(state, blck.body)
if not process_operations(preset, state, blck.body, flags, stateCache):
# One could combine this and the default-true, but that's a bit implicit
let res_ops = process_operations(preset, state, blck.body, flags, stateCache)
if res_ops.isErr:
debug "process_operations encountered error",
operation_error = $(res_ops.error),
slot = state.slot,
eth1_deposit_index = state.eth1_deposit_index,
deposit_root = shortLog(state.eth1_data.deposit_root)
return false
true

View File

@ -62,8 +62,6 @@ func get_total_active_balance*(state: BeaconState, cache: var StateCache): Gwei
let
epoch = state.slot.compute_epoch_at_slot
try:
# TODO refactor get_empty_per_epoch_cache() not to be, well, empty, so can
# avoid this ever refilling, and raiseAssert, and get rid of var
if epoch notin cache.shuffled_active_validator_indices:
cache.shuffled_active_validator_indices[epoch] =
get_shuffled_active_validator_indices(state, epoch)
@ -249,9 +247,8 @@ func get_base_reward(state: BeaconState, index: ValidatorIndex,
integer_squareroot(total_balance) div BASE_REWARDS_PER_EPOCH
func get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex,
cache: var StateCache): Gwei =
let total_balance = get_total_active_balance(state, cache)
total_balance: Gwei): Gwei =
# Spec version recalculates get_total_active_balance(state) quadratically
get_base_reward(state, attesting_index, total_balance) div PROPOSER_REWARD_QUOTIENT
func get_finality_delay(state: BeaconState): uint64 =
@ -271,6 +268,7 @@ func get_eligible_validator_indices(state: BeaconState): seq[ValidatorIndex] =
func get_attestation_component_deltas(state: BeaconState,
attestations: seq[PendingAttestation],
total_balance: Gwei,
cache: var StateCache,
): tuple[a: seq[Gwei], b: seq[Gwei]] =
# Helper with shared logic for use by get source, target, and head deltas
@ -279,7 +277,6 @@ func get_attestation_component_deltas(state: BeaconState,
rewards = repeat(0'u64, len(state.validators))
penalties = repeat(0'u64, len(state.validators))
let
total_balance = get_total_active_balance(state, cache)
unslashed_attesting_indices =
get_unslashed_attesting_indices(state, attestations, cache)
attesting_balance = get_total_balance(state, unslashed_attesting_indices)
@ -301,28 +298,36 @@ func get_attestation_component_deltas(state: BeaconState,
(rewards, penalties)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#components-of-attestation-deltas
func get_source_deltas(state: BeaconState, cache: var StateCache):
# These is slightly refactored to calculate total_balance once.
func get_source_deltas(
state: BeaconState, total_balance: Gwei, cache: var StateCache):
tuple[a: seq[Gwei], b: seq[Gwei]] =
# Return attester micro-rewards/penalties for source-vote for each validator.
let matching_source_attestations =
get_matching_source_attestations(state, get_previous_epoch(state))
get_attestation_component_deltas(state, matching_source_attestations, cache)
get_attestation_component_deltas(
state, matching_source_attestations, total_balance, cache)
func get_target_deltas(state: BeaconState, cache: var StateCache):
func get_target_deltas(
state: BeaconState, total_balance: Gwei, cache: var StateCache):
tuple[a: seq[Gwei], b: seq[Gwei]] =
# Return attester micro-rewards/penalties for target-vote for each validator.
let matching_target_attestations =
get_matching_target_attestations(state, get_previous_epoch(state))
get_attestation_component_deltas(state, matching_target_attestations, cache)
get_attestation_component_deltas(
state, matching_target_attestations, total_balance, cache)
func get_head_deltas(state: BeaconState, cache: var StateCache):
func get_head_deltas(
state: BeaconState, total_balance: Gwei, cache: var StateCache):
tuple[a: seq[Gwei], b: seq[Gwei]] =
# Return attester micro-rewards/penalties for head-vote for each validator.
let matching_head_attestations =
get_matching_head_attestations(state, get_previous_epoch(state))
get_attestation_component_deltas(state, matching_head_attestations, cache)
get_attestation_component_deltas(
state, matching_head_attestations, total_balance, cache)
func get_inclusion_delay_deltas(state: BeaconState, cache: var StateCache):
func get_inclusion_delay_deltas(
state: BeaconState, total_balance: Gwei, cache: var StateCache):
seq[Gwei] =
# Return proposer and inclusion delay micro-rewards/penalties for each validator.
var rewards = repeat(0'u64, len(state.validators))
@ -333,7 +338,6 @@ func get_inclusion_delay_deltas(state: BeaconState, cache: var StateCache):
matching_source_attestations,
get_attesting_indices(state, it.data, it.aggregation_bits, cache))
let total_balance = get_total_active_balance(state, cache)
for index in get_unslashed_attesting_indices(state, matching_source_attestations, cache):
# Translation of attestation = min([...])
# Start by filtering the right attestations
@ -355,21 +359,22 @@ func get_inclusion_delay_deltas(state: BeaconState, cache: var StateCache):
if a.inclusion_delay < attestation.inclusion_delay:
attestation = a
# TODO remove duplicate calculation of get_proposer_reward()
rewards[attestation.proposer_index] += get_proposer_reward(state, index, cache)
rewards[attestation.proposer_index] +=
get_proposer_reward(state, index, total_balance)
let max_attester_reward =
get_base_reward(state, index, total_balance) - get_proposer_reward(state, index, cache)
get_base_reward(state, index, total_balance) -
get_proposer_reward(state, index, total_balance)
rewards[index] += Gwei(max_attester_reward div attestation.inclusion_delay)
# No penalties associated with inclusion delay
# Spec constructs both and returns both; this doesn't
rewards
func get_inactivity_penalty_deltas(state: BeaconState, cache: var StateCache):
func get_inactivity_penalty_deltas(
state: BeaconState, total_balance: Gwei, cache: var StateCache):
seq[Gwei] =
# Return inactivity reward/penalty deltas for each validator.
var penalties = repeat(0'u64, len(state.validators))
let total_balance = get_total_active_balance(state, cache) # DO NOT KEEP RECALCULATING THIS
if is_in_inactivity_leak(state):
let
matching_target_attestations =
@ -381,7 +386,7 @@ func get_inactivity_penalty_deltas(state: BeaconState, cache: var StateCache):
let base_reward = get_base_reward(state, index, total_balance)
penalties[index] +=
Gwei(BASE_REWARDS_PER_EPOCH * base_reward -
get_proposer_reward(state, index, cache))
get_proposer_reward(state, index, total_balance))
# matching_target_attesting_indices is a HashSet
if index notin matching_target_attesting_indices:
let effective_balance = state.validators[index].effective_balance
@ -394,14 +399,21 @@ func get_inactivity_penalty_deltas(state: BeaconState, cache: var StateCache):
penalties
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_attestation_deltas
func get_attestation_deltas(state: BeaconState, cache: var StateCache): tuple[a: seq[Gwei], b: seq[Gwei]] =
func get_attestation_deltas(state: BeaconState, cache: var StateCache):
tuple[a: seq[Gwei], b: seq[Gwei]] =
# Return attestation reward/penalty deltas for each validator.
let
(source_rewards, source_penalties) = get_source_deltas(state, cache)
(target_rewards, target_penalties) = get_target_deltas(state, cache)
(head_rewards, head_penalties) = get_head_deltas(state, cache)
inclusion_delay_rewards = get_inclusion_delay_deltas(state, cache)
inactivity_penalties = get_inactivity_penalty_deltas(state, cache)
total_balance = get_total_active_balance(state, cache)
(source_rewards, source_penalties) =
get_source_deltas(state, total_balance, cache)
(target_rewards, target_penalties) =
get_target_deltas(state, total_balance, cache)
(head_rewards, head_penalties) =
get_head_deltas(state, total_balance, cache)
inclusion_delay_rewards =
get_inclusion_delay_deltas(state, total_balance, cache)
inactivity_penalties =
get_inactivity_penalty_deltas(state, total_balance, cache)
let rewards = mapIt(0 ..< len(state.validators),
source_rewards[it] + target_rewards[it] + head_rewards[it] +

View File

@ -136,6 +136,7 @@ template clearBit*(x: var BitList, idx: int) = clearBit(BitSeq(x), idx)
template overlaps*(a, b: BitList): bool = overlaps(BitSeq(a), BitSeq(b))
template combine*(a: var BitList, b: BitList) = combine(BitSeq(a), BitSeq(b))
template isSubsetOf*(a, b: BitList): bool = isSubsetOf(BitSeq(a), BitSeq(b))
template isZeros*(x: BitList): bool = isZeros(BitSeq(x))
template `$`*(a: BitList): string = $(BitSeq(a))
iterator items*(x: BitList): bool =

View File

@ -74,7 +74,7 @@ type
pending*: Table[uint64, SyncRequest[T]]
waiters: seq[SyncWaiter[T]]
syncUpdate*: SyncUpdateCallback[T]
getFirstSlotAFE*: GetSlotCallback
getFinalizedSlot*: GetSlotCallback
debtsQueue: HeapQueue[SyncRequest[T]]
debtsCount: uint64
readyQueue: HeapQueue[SyncResult[T]]
@ -91,7 +91,7 @@ type
toleranceValue: uint64
getLocalHeadSlot: GetSlotCallback
getLocalWallSlot: GetSlotCallback
getFirstSlotAFE: GetSlotCallback
getFinalizedSlot: GetSlotCallback
syncUpdate: SyncUpdateCallback[A]
chunkSize: uint64
queue: SyncQueue[A]
@ -208,7 +208,7 @@ proc isEmpty*[T](sr: SyncRequest[T]): bool {.inline.} =
proc init*[T](t1: typedesc[SyncQueue], t2: typedesc[T],
start, last: Slot, chunkSize: uint64,
updateCb: SyncUpdateCallback[T],
fsafeCb: GetSlotCallback,
getFinalizedSlotCb: GetSlotCallback,
queueSize: int = -1): SyncQueue[T] =
## Create new synchronization queue with parameters
##
@ -268,7 +268,7 @@ proc init*[T](t1: typedesc[SyncQueue], t2: typedesc[T],
chunkSize: chunkSize,
queueSize: queueSize,
syncUpdate: updateCb,
getFirstSlotAFE: fsafeCb,
getFinalizedSlot: getFinalizedSlotCb,
waiters: newSeq[SyncWaiter[T]](),
counter: 1'u64,
pending: initTable[uint64, SyncRequest[T]](),
@ -449,7 +449,7 @@ proc push*[T](sq: SyncQueue[T], sr: SyncRequest[T],
# of blocks with holes or `block_pool` is in incomplete state. We going
# to rewind to the first slot at latest finalized epoch.
let req = item.request
let finalizedSlot = sq.getFirstSlotAFE()
let finalizedSlot = sq.getFinalizedSlot()
if finalizedSlot < req.slot:
warn "Unexpected missing parent, rewind happens",
peer = req.item, rewind_to_slot = finalizedSlot,
@ -566,7 +566,7 @@ proc speed*(start, finish: SyncMoment): float {.inline.} =
proc newSyncManager*[A, B](pool: PeerPool[A, B],
getLocalHeadSlotCb: GetSlotCallback,
getLocalWallSlotCb: GetSlotCallback,
getFSAFECb: GetSlotCallback,
getFinalizedSlotCb: GetSlotCallback,
updateLocalBlocksCb: UpdateLocalBlocksCallback,
maxWorkers = 10,
maxStatusAge = uint64(SLOTS_PER_EPOCH * 4),
@ -586,8 +586,8 @@ proc newSyncManager*[A, B](pool: PeerPool[A, B],
peer.updateScore(PeerScoreGoodBlocks)
return res
let queue = SyncQueue.init(A, getLocalHeadSlotCb(), getLocalWallSlotCb(),
chunkSize, syncUpdate, getFSAFECb, 2)
let queue = SyncQueue.init(A, getFinalizedSlotCb(), getLocalWallSlotCb(),
chunkSize, syncUpdate, getFinalizedSlotCb, 2)
result = SyncManager[A, B](
pool: pool,
@ -596,7 +596,7 @@ proc newSyncManager*[A, B](pool: PeerPool[A, B],
getLocalHeadSlot: getLocalHeadSlotCb,
syncUpdate: syncUpdate,
getLocalWallSlot: getLocalWallSlotCb,
getFirstSlotAFE: getFSAFECb,
getFinalizedSlot: getFinalizedSlotCb,
maxHeadAge: maxHeadAge,
maxRecurringFailures: maxRecurringFailures,
sleepTime: sleepTime,
@ -875,9 +875,9 @@ proc sync*[A, B](man: SyncManager[A, B]) {.async.} =
debug "Synchronization lost, restoring",
wall_head_slot = wallSlot, local_head_slot = headSlot,
queue_last_slot = man.queue.lastSlot, topics = "syncman"
man.queue = SyncQueue.init(A, headSlot, wallSlot,
man.queue = SyncQueue.init(A, man.getFinalizedSlot(), wallSlot,
man.chunkSize, man.syncUpdate,
man.getFirstSlotAFE, 2)
man.getFinalizedSlot, 2)
debug "Synchronization loop starting new worker", peer = peer,
wall_head_slot = wallSlot, local_head_slot = headSlot,

View File

@ -7,10 +7,10 @@
import
# Standard library
tables, strutils, parseutils,
tables, strutils, parseutils, sequtils,
# Nimble packages
stew/[objects],
stew/[byteutils, objects],
chronos, metrics, json_rpc/[rpcserver, jsonmarshal],
chronicles,
@ -27,88 +27,283 @@ type
logScope: topics = "valapi"
proc toBlockSlot(blckRef: BlockRef): BlockSlot =
blckRef.atSlot(blckRef.slot)
proc parseRoot(str: string): Eth2Digest =
return Eth2Digest(data: hexToByteArray[32](str))
proc parsePubkey(str: string): ValidatorPubKey =
let pubkeyRes = fromHex(ValidatorPubKey, str)
if pubkeyRes.isErr:
raise newException(CatchableError, "Not a valid public key")
return pubkeyRes[]
proc doChecksAndGetCurrentHead(node: BeaconNode, slot: Slot): BlockRef =
result = node.blockPool.head.blck
if not node.isSynced(result):
raise newException(CatchableError, "Cannot fulfill request until ndoe is synced")
# TODO for now we limit the requests arbitrarily by up to 2 epochs into the future
if result.slot + uint64(2 * SLOTS_PER_EPOCH) < slot:
raise newException(CatchableError, "Requesting way ahead of the current head")
proc doChecksAndGetCurrentHead(node: BeaconNode, epoch: Epoch): BlockRef =
node.doChecksAndGetCurrentHead(epoch.compute_start_slot_at_epoch)
# TODO currently this function throws if the validator isn't found - is this OK?
proc getValidatorInfoFromValidatorId(
state: BeaconState,
current_epoch: Epoch,
validatorId: string,
status = ""):
Option[BeaconStatesValidatorsTuple] =
const allowedStatuses = ["", "pending", "pending_initialized", "pending_queued",
"active", "active_ongoing", "active_exiting", "active_slashed", "exited",
"exited_unslashed", "exited_slashed", "withdrawal", "withdrawal_possible",
"withdrawal_done"]
if status notin allowedStatuses:
raise newException(CatchableError, "Invalid status requested")
let validator = if validatorId.startsWith("0x"):
let pubkey = parsePubkey(validatorId)
let idx = state.validators.asSeq.findIt(it.pubKey == pubkey)
if idx == -1:
raise newException(CatchableError, "Could not find validator")
state.validators[idx]
else:
var valIdx: BiggestUInt
if parseBiggestUInt(validatorId, valIdx) != validatorId.len:
raise newException(CatchableError, "Not a valid index")
if state.validators.len >= valIdx.int:
raise newException(CatchableError, "Index out of bounds")
state.validators[valIdx]
# time to determine the status of the validator - the code mimics
# whatever is detailed here: https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ
let actual_status = if validator.activation_epoch > current_epoch:
# pending
if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH:
"pending_initialized"
else:
# validator.activation_eligibility_epoch < FAR_FUTURE_EPOCH:
"pending_queued"
elif validator.activation_epoch <= current_epoch and
current_epoch < validator.exit_epoch:
# active
if validator.exit_epoch == FAR_FUTURE_EPOCH:
"active_ongoing"
elif not validator.slashed:
# validator.exit_epoch < FAR_FUTURE_EPOCH
"active_exiting"
else:
# validator.exit_epoch < FAR_FUTURE_EPOCH and validator.slashed:
"active_slashed"
elif validator.exit_epoch <= current_epoch and
current_epoch < validator.withdrawable_epoch:
# exited
if not validator.slashed:
"exited_unslashed"
else:
# validator.slashed
"exited_slashed"
elif validator.withdrawable_epoch <= current_epoch:
# withdrawal
if validator.effective_balance != 0:
"withdrawal_possible"
else:
# validator.effective_balance == 0
"withdrawal_done"
else:
raise newException(CatchableError, "Invalid validator status")
# if the requested status doesn't match the actual status
if status != "" and status notin actual_status:
return none(BeaconStatesValidatorsTuple)
return some((validator: validator, status: actual_status,
balance: validator.effective_balance))
proc getBlockSlotFromString(node: BeaconNode, slot: string): BlockSlot =
var parsed: BiggestUInt
if parseBiggestUInt(slot, parsed) != slot.len:
raise newException(CatchableError, "Not a valid slot number")
let head = node.doChecksAndGetCurrentHead(parsed.Slot)
return head.atSlot(parsed.Slot)
proc getBlockDataFromBlockId(node: BeaconNode, blockId: string): BlockData =
result = case blockId:
of "head":
node.blockPool.get(node.blockPool.head.blck)
of "genesis":
node.blockPool.get(node.blockPool.tail)
of "finalized":
node.blockPool.get(node.blockPool.finalizedHead.blck)
else:
if blockId.startsWith("0x"):
let blckRoot = parseRoot(blockId)
let blockData = node.blockPool.get(blckRoot)
if blockData.isNone:
raise newException(CatchableError, "Block not found")
blockData.get()
else:
let blockSlot = node.getBlockSlotFromString(blockId)
if blockSlot.blck.isNil:
raise newException(CatchableError, "Block not found")
node.blockPool.get(blockSlot.blck)
proc stateIdToBlockSlot(node: BeaconNode, stateId: string): BlockSlot =
result = case stateId:
of "head":
node.blockPool.head.blck.toBlockSlot()
of "genesis":
node.blockPool.tail.toBlockSlot()
of "finalized":
node.blockPool.finalizedHead
of "justified":
node.blockPool.justifiedState.blck.toBlockSlot()
else:
if stateId.startsWith("0x"):
let blckRoot = parseRoot(stateId)
let blckRef = node.blockPool.getRef(blckRoot)
if blckRef.isNil:
raise newException(CatchableError, "Block not found")
blckRef.toBlockSlot()
else:
node.getBlockSlotFromString(stateId)
# TODO Probably the `beacon` ones should be defined elsewhere...?
proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
let GENESIS_FORK_VERSION = node.config.runtimePreset.GENESIS_FORK_VERSION
template withStateForSlot(stateId: string, body: untyped): untyped =
var res: BiggestInt
if parseBiggestInt(stateId, res) == stateId.len:
raise newException(CatchableError, "Not a valid slot number")
let head = node.updateHead()
let blockSlot = head.atSlot(res.Slot)
node.blockPool.withState(node.blockPool.tmpState, blockSlot):
template withStateForStateId(stateId: string, body: untyped): untyped =
node.blockPool.withState(node.blockPool.tmpState,
node.stateIdToBlockSlot(stateId)):
body
rpcServer.rpc("get_v1_beacon_genesis") do () -> BeaconGenesisTuple:
debug "get_v1_beacon_genesis"
return (genesis_time: node.blockPool.headState.data.data.genesis_time,
genesis_validators_root:
node.blockPool.headState.data.data.genesis_validators_root,
genesis_fork_version: GENESIS_FORK_VERSION)
rpcServer.rpc("get_v1_beacon_states_root") do (stateId: string) -> Eth2Digest:
debug "get_v1_beacon_states_root", stateId = stateId
# TODO do we need to call node.updateHead() before using headState?
result = case stateId:
of "head":
node.blockPool.headState.blck.root
of "genesis":
node.blockPool.headState.data.data.genesis_validators_root
of "finalized":
node.blockPool.headState.data.data.finalized_checkpoint.root
of "justified":
node.blockPool.headState.data.data.current_justified_checkpoint.root
else:
if stateId.startsWith("0x"):
# TODO not sure if `fromHex` is the right thing here...
# https://github.com/ethereum/eth2.0-APIs/issues/37#issuecomment-638566144
# we return whatever was passed to us (this is a nonsense request)
fromHex(Eth2Digest, stateId[2..<stateId.len]) # skip first 2 chars
else:
withStateForSlot(stateId):
hashedState.root
withStateForStateId(stateId):
return hashedState.root
rpcServer.rpc("get_v1_beacon_states_fork") do (stateId: string) -> Fork:
debug "get_v1_beacon_states_fork", stateId = stateId
result = case stateId:
of "head":
node.blockPool.headState.data.data.fork
of "genesis":
Fork(previous_version: GENESIS_FORK_VERSION,
current_version: GENESIS_FORK_VERSION,
epoch: GENESIS_EPOCH)
of "finalized":
node.blockPool.withState(node.blockPool.tmpState, node.blockPool.finalizedHead):
state.fork
of "justified":
node.blockPool.justifiedState.data.data.fork
else:
if stateId.startsWith("0x"):
# TODO not sure if `fromHex` is the right thing here...
# https://github.com/ethereum/eth2.0-APIs/issues/37#issuecomment-638566144
let blckRoot = fromHex(Eth2Digest, stateId[2..<stateId.len]) # skip first 2 chars
let blckRef = node.blockPool.getRef(blckRoot)
if blckRef.isNil:
raise newException(CatchableError, "Block not found")
let blckSlot = blckRef.atSlot(blckRef.slot)
node.blockPool.withState(node.blockPool.tmpState, blckSlot):
state.fork
withStateForStateId(stateId):
return state.fork
rpcServer.rpc("get_v1_beacon_states_finality_checkpoints") do (
stateId: string) -> BeaconStatesFinalityCheckpointsTuple:
withStateForStateId(stateId):
return (previous_justified: state.previous_justified_checkpoint,
current_justified: state.current_justified_checkpoint,
finalized: state.finalized_checkpoint)
rpcServer.rpc("get_v1_beacon_states_stateId_validators") do (
stateId: string, validatorIds: seq[string],
status: string) -> seq[BeaconStatesValidatorsTuple]:
let current_epoch = get_current_epoch(node.blockPool.headState.data.data)
withStateForStateId(stateId):
for validatorId in validatorIds:
let res = state.getValidatorInfoFromValidatorId(
current_epoch, validatorId, status)
if res.isSome():
result.add(res.get())
rpcServer.rpc("get_v1_beacon_states_stateId_validators_validatorId") do (
stateId: string, validatorId: string) -> BeaconStatesValidatorsTuple:
let current_epoch = get_current_epoch(node.blockPool.headState.data.data)
withStateForStateId(stateId):
let res = state.getValidatorInfoFromValidatorId(current_epoch, validatorId)
if res.isNone:
# TODO should we raise here? Maybe this is different from the array case...
raise newException(CatchableError, "Validator status differs")
return res.get()
rpcServer.rpc("get_v1_beacon_states_stateId_committees_epoch") do (
stateId: string, epoch: uint64, index: uint64, slot: uint64) ->
seq[BeaconStatesCommitteesTuple]:
withStateForStateId(stateId):
var cache = get_empty_per_epoch_cache() # TODO is this OK?
proc getCommittee(slot: Slot, index: CommitteeIndex): BeaconStatesCommitteesTuple =
let vals = get_beacon_committee(state, slot, index, cache).mapIt(it.uint64)
return (index: index.uint64, slot: slot.uint64, validators: vals)
proc forSlot(slot: Slot, res: var seq[BeaconStatesCommitteesTuple]) =
if index == 0: # TODO this means if the parameter is missing (its optional)
let committees_per_slot = get_committee_count_at_slot(state, slot)
for committee_index in 0'u64..<committees_per_slot:
res.add(getCommittee(slot, committee_index.CommitteeIndex))
else:
withStateForSlot(stateId):
state.fork
res.add(getCommittee(slot, index.CommitteeIndex))
if slot == 0: # TODO this means if the parameter is missing (its optional)
for i in 0 ..< SLOTS_PER_EPOCH:
forSlot((compute_start_slot_at_epoch(epoch.Epoch).int + i).Slot, result)
else:
forSlot(slot.Slot, result)
rpcServer.rpc("get_v1_beacon_headers") do (
slot: uint64, parent_root: Eth2Digest) -> seq[BeaconHeadersTuple]:
# @mratsim: I'm adding a toposorted iterator that returns all blocks from last finalization to all heads in the dual fork choice PR @viktor
# filterIt(dag.blocks.values(), it.blck.slot == slot_of_interest)
# maybe usesBlockPool.heads ??? or getBlockRange ???
# https://discordapp.com/channels/613988663034118151/614014714590134292/726095138484518912
discard # raise newException(CatchableError, "Not implemented") # cannot compile...
rpcServer.rpc("get_v1_beacon_headers_blockId") do (
blockId: string) -> tuple[canonical: bool, header: SignedBeaconBlockHeader]:
let bd = node.getBlockDataFromBlockId(blockId)
let tsbb = bd.data
result.header.signature.blob = tsbb.signature.data
result.header.message.slot = tsbb.message.slot
result.header.message.proposer_index = tsbb.message.proposer_index
result.header.message.parent_root = tsbb.message.parent_root
result.header.message.state_root = tsbb.message.state_root
result.header.message.body_root = tsbb.message.body.hash_tree_root()
result.canonical = bd.refs.isAncestorOf(node.blockPool.head.blck)
rpcServer.rpc("get_v1_beacon_blocks_blockId") do (
blockId: string) -> TrustedSignedBeaconBlock:
return node.getBlockDataFromBlockId(blockId).data
rpcServer.rpc("get_v1_beacon_blocks_blockId_root") do (
blockId: string) -> Eth2Digest:
return node.getBlockDataFromBlockId(blockId).data.message.state_root
rpcServer.rpc("get_v1_beacon_blocks_blockId_attestations") do (
blockId: string) -> seq[TrustedAttestation]:
return node.getBlockDataFromBlockId(blockId).data.message.body.attestations.asSeq
rpcServer.rpc("post_v1_beacon_pool_attestations") do (
attestation: Attestation) -> bool:
node.sendAttestation(attestation)
return true
rpcServer.rpc("get_v1_validator_blocks") do (
rpcServer.rpc("get_v1_config_fork_schedule") do (
) -> seq[tuple[epoch: uint64, version: Version]]:
discard # raise newException(CatchableError, "Not implemented") # cannot compile...
rpcServer.rpc("get_v1_debug_beacon_states_stateId") do (
stateId: string) -> BeaconState:
withStateForStateId(stateId):
return state
rpcServer.rpc("get_v1_validator_block") do (
slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig) -> BeaconBlock:
debug "get_v1_validator_blocks", slot = slot
let head = node.updateHead()
debug "get_v1_validator_block", slot = slot
let head = node.doChecksAndGetCurrentHead(slot)
let proposer = node.blockPool.getProposer(head, slot)
if proposer.isNone():
raise newException(CatchableError, "could not retrieve block for slot: " & $slot)
@ -120,18 +315,13 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
raise newException(CatchableError, "could not retrieve block for slot: " & $slot)
return res.message.get()
rpcServer.rpc("post_v1_beacon_blocks") do (body: SignedBeaconBlock) -> bool:
debug "post_v1_beacon_blocks",
rpcServer.rpc("post_v1_validator_block") do (body: SignedBeaconBlock) -> bool:
debug "post_v1_validator_block",
slot = body.message.slot,
prop_idx = body.message.proposer_index
let head = node.doChecksAndGetCurrentHead(body.message.slot)
let head = node.updateHead()
if head.slot >= body.message.slot:
warn "Skipping proposal, have newer head already",
headSlot = shortLog(head.slot),
headBlockRoot = shortLog(head.root),
slot = shortLog(body.message.slot),
cat = "fastforward"
raise newException(CatchableError,
"Proposal is for a past slot: " & $body.message.slot)
if head == await proposeSignedBlock(node, head, AttachedValidator(),
@ -139,26 +329,29 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
raise newException(CatchableError, "Could not propose block")
return true
rpcServer.rpc("get_v1_validator_attestation_data") do (
rpcServer.rpc("get_v1_validator_attestation") do (
slot: Slot, committee_index: CommitteeIndex) -> AttestationData:
let head = node.updateHead()
let attestationHead = head.atSlot(slot)
node.blockPool.withState(node.blockPool.tmpState, attestationHead):
debug "get_v1_validator_attestation", slot = slot
let head = node.doChecksAndGetCurrentHead(slot)
node.blockPool.withState(node.blockPool.tmpState, head.atSlot(slot)):
return makeAttestationData(state, slot, committee_index.uint64, blck.root)
rpcServer.rpc("get_v1_validator_aggregate_attestation") do (
rpcServer.rpc("get_v1_validator_aggregate_and_proof") do (
attestation_data: AttestationData)-> Attestation:
debug "get_v1_validator_aggregate_attestation"
debug "get_v1_validator_aggregate_and_proof"
raise newException(CatchableError, "Not implemented")
rpcServer.rpc("post_v1_validator_aggregate_and_proof") do (
payload: SignedAggregateAndProof) -> bool:
node.network.broadcast(node.topicAggregateAndProofs, payload)
return true
debug "post_v1_validator_aggregate_and_proof"
raise newException(CatchableError, "Not implemented")
rpcServer.rpc("post_v1_validator_duties_attester") do (
epoch: Epoch, public_keys: seq[ValidatorPubKey]) -> seq[AttesterDuties]:
debug "post_v1_validator_duties_attester", epoch = epoch
let head = node.updateHead()
let head = node.doChecksAndGetCurrentHead(epoch)
let attestationHead = head.atSlot(compute_start_slot_at_epoch(epoch))
node.blockPool.withState(node.blockPool.tmpState, attestationHead):
for pubkey in public_keys:
@ -176,7 +369,8 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
rpcServer.rpc("get_v1_validator_duties_proposer") do (
epoch: Epoch) -> seq[ValidatorPubkeySlotPair]:
debug "get_v1_validator_duties_proposer", epoch = epoch
let head = node.updateHead()
let head = node.doChecksAndGetCurrentHead(epoch)
for i in 0 ..< SLOTS_PER_EPOCH:
let currSlot = (compute_start_slot_at_epoch(epoch).int + i).Slot
let proposer = node.blockPool.getProposer(head, currSlot)

View File

@ -10,7 +10,7 @@ import
os, strutils, json, times,
# Nimble packages
stew/shims/[tables, macros],
stew/byteutils, stew/shims/[tables, macros],
chronos, confutils, metrics, json_rpc/[rpcclient, jsonmarshal],
chronicles,
blscurve, json_serialization/std/[options, sets, net],
@ -44,18 +44,6 @@ type
attestationsForEpoch: Table[Epoch, Table[Slot, seq[AttesterDuties]]]
beaconGenesis: BeaconGenesisTuple
proc connectToBN(vc: ValidatorClient) {.gcsafe, async.} =
while true:
try:
await vc.client.connect($vc.config.rpcAddress, Port(vc.config.rpcPort))
info "Connected to BN",
port = vc.config.rpcPort,
address = vc.config.rpcAddress
return
except CatchableError as err:
warn "Could not connect to the BN - retrying!", err = err.msg
await sleepAsync(chronos.seconds(1)) # 1 second before retrying
template attemptUntilSuccess(vc: ValidatorClient, body: untyped) =
while true:
try:
@ -63,9 +51,11 @@ template attemptUntilSuccess(vc: ValidatorClient, body: untyped) =
break
except CatchableError as err:
warn "Caught an unexpected error", err = err.msg
waitFor vc.connectToBN()
waitFor sleepAsync(chronos.seconds(1)) # 1 second before retrying
proc getValidatorDutiesForEpoch(vc: ValidatorClient, epoch: Epoch) {.gcsafe, async.} =
info "Getting validator duties for epoch", epoch = epoch
let proposals = await vc.client.get_v1_validator_duties_proposer(epoch)
# update the block proposal duties this VC should do during this epoch
vc.proposalsForCurrentEpoch.clear()
@ -79,29 +69,35 @@ proc getValidatorDutiesForEpoch(vc: ValidatorClient, epoch: Epoch) {.gcsafe, asy
validatorPubkeys.add key
proc getAttesterDutiesForEpoch(epoch: Epoch) {.gcsafe, async.} =
let attestations = await vc.client.post_v1_validator_duties_attester(
epoch, validatorPubkeys)
# make sure there's an entry
if not vc.attestationsForEpoch.contains epoch:
vc.attestationsForEpoch.add(epoch, Table[Slot, seq[AttesterDuties]]())
let attestations = await vc.client.post_v1_validator_duties_attester(
epoch, validatorPubkeys)
for a in attestations:
if vc.attestationsForEpoch[epoch].hasKeyOrPut(a.slot, @[a]):
vc.attestationsForEpoch[epoch][a.slot].add(a)
# clear both for the current epoch and the next because a change of
# fork could invalidate the attester duties even the current epoch
vc.attestationsForEpoch.clear()
await getAttesterDutiesForEpoch(epoch)
# obtain the attestation duties this VC should do during the next epoch
# TODO currently we aren't making use of this but perhaps we should
await getAttesterDutiesForEpoch(epoch + 1)
# also get the attestation duties for the current epoch if missing
if not vc.attestationsForEpoch.contains epoch:
await getAttesterDutiesForEpoch(epoch)
# cleanup old epoch attestation duties
vc.attestationsForEpoch.del(epoch - 1)
# TODO handle subscriptions to beacon committees for both the next epoch and
# for the current if missing (beacon_committee_subscriptions from the REST api)
# for now we will get the fork each time we update the validator duties for each epoch
# TODO should poll occasionally `/v1/config/fork_schedule`
vc.fork = await vc.client.get_v1_beacon_states_fork("head")
var numAttestationsForEpoch = 0
for _, dutiesForSlot in vc.attestationsForEpoch[epoch]:
numAttestationsForEpoch += dutiesForSlot.len
info "Got validator duties for epoch",
num_proposals = vc.proposalsForCurrentEpoch.len,
num_attestations = numAttestationsForEpoch
proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, async.} =
let
@ -135,18 +131,22 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
let public_key = vc.proposalsForCurrentEpoch[slot]
let validator = vc.attachedValidators.validators[public_key]
info "Proposing block", slot = slot, public_key = public_key
let randao_reveal = validator.genRandaoReveal(
vc.fork, vc.beaconGenesis.genesis_validators_root, slot)
var graffiti: Eth2Digest
graffiti.data[0..<5] = toBytes("quack")
var newBlock = SignedBeaconBlock(
message: await vc.client.get_v1_validator_blocks(slot, Eth2Digest(), randao_reveal)
message: await vc.client.get_v1_validator_block(slot, graffiti, randao_reveal)
)
let blockRoot = hash_tree_root(newBlock.message)
newBlock.signature = await validator.signBlockProposal(
vc.fork, vc.beaconGenesis.genesis_validators_root, slot, blockRoot)
discard await vc.client.post_v1_beacon_blocks(newBlock)
discard await vc.client.post_v1_validator_block(newBlock)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#attesting
# A validator should create and broadcast the attestation to the associated
@ -158,11 +158,13 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
seconds(int64(SECONDS_PER_SLOT)) div 3, slot, "Waiting to send attestations")
# check if we have validators which need to attest on this slot
if vc.attestationsForEpoch[epoch].contains slot:
if vc.attestationsForEpoch.contains(epoch) and
vc.attestationsForEpoch[epoch].contains slot:
for a in vc.attestationsForEpoch[epoch][slot]:
let validator = vc.attachedValidators.validators[a.public_key]
info "Attesting", slot = slot, public_key = a.public_key
let ad = await vc.client.get_v1_validator_attestation_data(slot, a.committee_index)
let validator = vc.attachedValidators.validators[a.public_key]
let ad = await vc.client.get_v1_validator_attestation(slot, a.committee_index)
# TODO I don't like these (u)int64-to-int conversions...
let attestation = await validator.produceAndSignAttestation(
@ -173,7 +175,6 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
except CatchableError as err:
warn "Caught an unexpected error", err = err.msg, slot = shortLog(slot)
await vc.connectToBN()
let
nextSlotStart = saturate(vc.beaconClock.fromNow(nextSlot))
@ -202,10 +203,6 @@ programMain:
setupMainProc(config.logLevel)
# TODO figure out how to re-enable this without the VCs continuing
# to run when `make eth2_network_simulation` is killed with CTRL+C
#ctrlCHandling: discard
case config.cmd
of VCNoCommand:
debug "Launching validator client",
@ -222,7 +219,10 @@ programMain:
for curr in vc.config.validatorKeys:
vc.attachedValidators.addLocalValidator(curr.toPubKey, curr)
waitFor vc.connectToBN()
waitFor vc.client.connect($vc.config.rpcAddress, Port(vc.config.rpcPort))
info "Connected to BN",
port = vc.config.rpcPort,
address = vc.config.rpcAddress
vc.attemptUntilSuccess:
# init the beacon clock

View File

@ -73,7 +73,7 @@ func getAttachedValidator*(node: BeaconNode,
let validatorKey = state.validators[idx].pubkey
node.attachedValidators.getValidator(validatorKey)
proc isSynced(node: BeaconNode, head: BlockRef): bool =
proc isSynced*(node: BeaconNode, head: BlockRef): bool =
## TODO This function is here as a placeholder for some better heurestics to
## determine if we're in sync and should be producing blocks and
## attestations. Generally, the problem is that slot time keeps advancing

1
docs/the_auditors_handbook/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
book

View File

@ -0,0 +1,6 @@
[book]
authors = ["Mamy André-Ratsimbazafy"]
language = "en"
multilingual = false
src = "src"
title = "The Auditors Handbook to Nimbus Beacon Chain"

View File

@ -0,0 +1,14 @@
# Introduction
The Nimbus Nim-Beacon-Chain (NBC) project is an implementation of the [Ethereum 2 Beacon Chain specification](https://github.com/ethereum/eth2.0-specs) in the [Nim programming language](https://nim-lang.org/).
The Auditors' Handbook aims to be provide a comprehensive introduction to:
- The Nim programming language, as used in the project.
- The NBC project.
- The dependencies of the project.
A particular focus will be given to features related to safety, correctness, error handling, testing, fuzzing, or inspecting Nim code.
One of the major highlights of Nim is that it compiles to C or C++ before compiling to native code. All techniques available to audit C code can be used to audit Nim.
The dependencies NBC rely on are detailed per audit phase in the [build system and dependencies](03.2_build_system_and_dependencies.md) section.

View File

@ -0,0 +1,53 @@
# Operators and bit manipulation
## Operators
A set of symbol and keywords can be used as infix operators
Nim supports operator overloading.
Those symbols are:
```
= + - * / < >
@ $ ~ & % |
! ? ^ . : \
```
The keyword operators are
```
and or not xor shl shr div mod in notin is isnot of as
```
In-particular:
- bitwise and, or, flip, xor are denoted `and`, `or`, `not`, `xor` instead of using a symbol
- shift left and right are `shl` and `shr`
- division and modulo are `div` and `mod`
### Implementation-defined behaviors
`mod` is defined as the mathematical remainder, like C. With signed integers `a mod b` has the same sign as `a`
`shr` of a signed integer will not preserve the sign bit.
`ashr` can be used for arithmetic right shift.
This distinction was introduced recently and may lead to subtle bugs,
also `ashr` relies on the C compiler actually lowering `>>` to SAR for signed integer.
This is specified for GCC and Clang ([https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation](https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation))
but implementation defined in general.
### Operator precedence
Operator precedence is specified described in the manual:
[https://nim-lang.org/docs/manual.html#syntax-precedence](https://nim-lang.org/docs/manual.html#syntax-precedence)
### Additional system operators
Nim system exports additional operators with a `%` like `+%` and `-%`.
Those cast the signed integer operand to unsigned and cast the result back to signed.
This is intended to use with the ByteAddress type for pointer arithmetic
## Bit manipulation
[https://github.com/status-im/nim-stew/blob/master/stew/bitops2.nim](https://github.com/status-im/nim-stew/blob/master/stew/bitops2.nim)

View File

@ -0,0 +1,3 @@
# Pointer manipulation
https://github.com/status-im/nim-stew/blob/master/stew/ptrops.nim

View File

@ -0,0 +1,3 @@
# Emitting raw C or Assembly code
TODO

View File

@ -0,0 +1,24 @@
# Closures and closures iterators
TODO
## At a low-level
Closures and closures iterators are implemented via
a pointer + an environment that stores the captured state necessary
to execute the function.
The Nim compiler has a limited form of borrow checking and prevents
capturing mutable variable or openarray (pointer+length pair).
It otherwise copies the capture variables in case of objects with value semantics
or increment the reference count in case of ref object.
The Chronos library needs to generate a closure iterator for all async proc instantiated
which may lead to long-lived ref objects and unreclaimable memory.
This may also extend to other resources like socket connections or file descriptors and
a significant effort is underway to track memory usage and detect such scenarios:
- [https://github.com/status-im/nim-libp2p/issues/145](https://github.com/status-im/nim-libp2p/issues/145)
- [https://github.com/status-im/nim-libp2p/issues/207](https://github.com/status-im/nim-libp2p/issues/207)

View File

@ -0,0 +1 @@
# Nim FAQ

View File

@ -0,0 +1,378 @@
# Nim Routines
Nim offers several kinds of "routines" that:
- do computation
- produce side-effect
- generate code
Those are:
- `proc` and `func`
- `method`
- `converter`
- `iterator`
- `template`
- `macro`
## `proc` and `func`
`proc` and `func` are the most basic routines.
At the moment, Nim requires forward declaration of proc and func.
Also it prevents circular dependencies, this means that a procedure is
always coming from one of the imports.
Additionally, all dependencies are submodules and a proc can be found by greping
`procname*`, the `*` being the export marker.
The only exception being the standard library. Procedures from the standard library are all listed in "[The Index](https://nim-lang.org/docs/theindex.html)".
### Function call syntax
Nim provides flexible call syntax, the following are possible:
```Nim
prof foo(a: int) =
discard
foo(a)
foo a
a.foo()
a.foo
```
Additionally this is also possible for strings:
```Nim
let a = fromHex"0x12345" # Without spaces
```
Nim doesn't enforce namespacing by default but is an option
```Nim
let a = byteutils.fromhex"0x12345"
```
### Parameters
Mutable parameters must be tagged with `var`
TODO
### Symbol resolution
If 2 procedures are visible in the same module (a module is a file) and
have the same name the compiler will infer which to call from the arguments
signatures. In case both are applicable, the compiler will throw an "ambiguous call" compile-time error.
Note that a procedure specialized to a concrete type has priority over a generic procedure, for example a procedure for int64 over a procedure for all number types.
### `func` and side effect analysis
In Nim a proc is considered to have side-effect if it accesses a global variable.
Mutating a declared function parameter is not considered a side-effect as there is no access to a global variable.
Printing to the standard output or reading the standard input is considered a sideeffect.
`func` are syntactic sugar for `proc` without sideeffects. In particular this means that `func` behaviors are fully determined by their input parameters.
In the codebase, logging at the `trace` level are not considered a sideeffect.
Additionally some logging statements and metrics statement may be in an explicit `{.noSideEffect.}:` code-block.
### Returning values
There are 3 syntaxes to return a value from a procedure:
1. The return statement
2. The implicit `result` variable
3. The "last statement as expression"
```Nim
proc add1(x: int): int =
return x + 1
proc add2(x: int): int =
result = x + 2
proc add3(x: int): int =
x + 3
```
The main differences are:
1. `return` allows early returns, in particular from a loop.
2. `result` offers Return Value Optimization and Copy Elision
which is particularly valuable for array types.
3. Requires the last statement to be a valid expression.
This is particularly interesting for conditional return values
as forgetting to set the value in a branch will be a compile-time error,
for example:
```Nim
proc select(ctl: bool, a, b: int): int =
if ctl:
echo "heavy processing"
a
else:
echo "heavy processing"
b
```
Omitting `a` or `b` will be a compiletime error, unlike
```Nim
proc select(ctl: bool, a, b: int): int =
if ctl:
echo "heavy processing"
return a
else:
echo "heavy processing"
# Forgot to return b
```
```Nim
proc select(ctl: bool, a, b: int): int =
if ctl:
echo "heavy processing"
result = a
else:
echo "heavy processing"
# Forgot to result = b
```
Due to the differences we prefer using the "last statement as expression" unless
- copying the type is expensive (SHA256 hash for example)
- or we need early returns
#### Ignoring return values
Unlike C, return values MUST be used or explicitly discarded.
#### Mutable return values
TODO
### At a low-level
#### Argument passing
Nim passes arguments by value if they take less than 3*sizeof(pointer) (i.e. 24 bytes on 64-bit OS) and passes them by pointer with the C backend or reference with the C++ backend if they are bigger.
Mutable arguments are always passed by pointer.
This behavior can be changed on a type-by-type bases by tagging them {.bycopy.} or {.byref.}. This is only used for interfacing with non-Nim code.
#### Stacktraces
With `--stacktrace:on`, Nim create a stackframe on `proc` entry and destroys it on exit. This is used for reporting stacktraces.
NBC is always compiled with `--stacktraces:on`
NBC uses [libbacktrace](https://github.com/ianlancetaylor/libbacktrace) to have less costly stacktraces.
#### Name in the C code or Assembly
`proc` and `func` are materialized in the produced C code with name-mangling appended at the end. For the purpose of building Nim libraries, the name can be controlled by:
- `{.exportc.}` so that the generated C name is the same as Nim
- `{.exportc: "specific_name".} to generate a specific name
## `method`
`method`s are used for dynamic dispatch when an object has an inherited subtype only known at runtime.
`method` are dispatched using a dispatch tree in the C code instead of a VTable.
There might be some cases where `method` were used not for their intended purpose
## `converter`
Converters are procedures that are implicitly called on a value to change its type.
For example with a fictional option type that automatically extracts the boxed type.
```Nim
type Option[T] = object
case hasValue: bool
of true:
value: T
else:
discard
converter get[T](x: Option[T]): T =
x.value
let x = Option[int](hasValue: true, value: 1)
let y = Option[int](hasValue: true, value: 2)
let z = x + y
```
Even though the `+` operator is not defined for `Option[int]` it is defined for `int` and Nim implicitly calls the converter.
`converter` are seldom used in the codebase as we prefer explicit over implicit.
Note that in case an operation is defined on both the convertible and the converted type, the operation without conversion should be preferred however [the compiler might throw an ambiguous call instead](https://github.com/nim-lang/Nim/issues/7416).
## Iterators
Iterators are construct that transforms a for loop.
For example to iterate on a custom array collection
```Nim
const MaxSize = 7
type SmallVec[T] = object
buffer*: array[MaxSize, T]
len*: int
iterator items*[T](a: SmallVec[T]): T =
for i in 0 ..< a.len:
yield a.data[i]
```
Now iterating becomes
```Nim
for value in a.items():
echo a
```
A singly-linked list forward iterator could be implemented as
```Nim
iterator items[T](head: ref T): ref T =
## Singly-linked list iterator
assert: not head.isNil
var cur = head
while true:
let next = cur.next
yield cur
cur = next
if cur.isNil:
break
```
a doubly-linked list backward iterator as
```Nim
iterator backward[T](tail: ptr T): ptr T =
var cur = tail
while not cur.isNil:
let prev = cur.prev
yield cur
cur = prev
```
an iterator to unpack individual bits from a byte as:
```Nim
iterator unpack(scalarByte: byte): bool =
yield bool((scalarByte and 0b10000000) shr 7)
yield bool((scalarByte and 0b01000000) shr 6)
yield bool((scalarByte and 0b00100000) shr 5)
yield bool((scalarByte and 0b00010000) shr 4)
yield bool((scalarByte and 0b00001000) shr 3)
yield bool((scalarByte and 0b00000100) shr 2)
yield bool((scalarByte and 0b00000010) shr 1)
yield bool( scalarByte and 0b00000001)
```
In all cases, the syntax to iterate on the collection remains:
```Nim
for value in a.items():
echo a
for value in b.backward():
echo b
for bit in s.unpack():
echo s
```
The echo is inlined at "yield".
Iterators are not present in the produced C code, they are always inlined at the callsite.
Iterators are prone to code bloat, for example
```Nim
iterator iterate[T](s: seq[T], backward: bool): T =
if backward:
for i in s.len-1 .. 0:
yield s[i]
else:
for i in 0 ..< s.len:
yield s[i]
for value in s.iterate(backward = false):
## Long-series of operations
echo value
```
The long series of operation will be duplicated.
### `items` and `pairs`
The `items` and `pairs` iterator are special cased and implictly call
if there is respectively one and two iteration variables hence:
```Nim
for x in collection:
echo x
```
will automatically call the `items` proc defined for the collection (or error)
```Nim
for x, y in collection:
echo x
echo y
```
will automatically call the `pairs` proc defined for the collection (or error)
### `fields` and `fieldPairs`
`fields` and `fieldsPairs` are iterator-like magic, that allow "iterating" on an object field. Note that those are unrolled at compile-time.
## Closures and closure iterators
Will be covered in a dedicated section.
They are the backbone of Chronos, our async/await framework and also
have a major potential for memory leaks.
## `template`
`template`s in Nim allows raw code substitution.
`templates` are hygienic and typechecked unlike the C preprocessor.
Also they create their own scope unless tagged with the `{.dirty.}` pragma.
A major issue with templates is that as they "copy-paste" code, it is very easy to misuse them and do a computation twice.
For instance
```Nim
proc foo(): int =
echo "launch missile"
return 1
template doSomething(a: int) =
process(a)
log(a)
```
This would be transformed to:
```Nim
process(foo())
log(foo())
```
and triggers the "launch missile" side-effect twice.
Another issue with templates is that they may not generate stacktraces properly as
they are not materialized in the C code.
### Symbol visibility and {.inject.}
TODO
## `macro`
TODO
## The `do` notation
TODO

View File

@ -0,0 +1,3 @@
# Builtin types
TODO

View File

@ -0,0 +1,23 @@
# Casting and low-level memory representation
## Conversions
Casting to or from a signed integer will lead to a range check
## Casting integers
Unsigned integer casts behave like C.
Upcasting will lead to zero extension
Downcasting will lead to truncation
Signed integer ranges will not be checked with a cast.
## Casting to/from other types
Casting to or from any other types will be done through:
- union casts by default
- or C conversion if the type is a pointer.
In practice this means that the bit-pattern will be reinterpreted as the new type, similar to C++ reinterpret cast.

View File

@ -0,0 +1,33 @@
# Nim memory management
Nim memory management is on a per-type basis.
Plain objects and char and numerical types are allocated on the stack.
Sequences and strings are allocated on the heap but have value semantics.
They are copied on assignment
Ref types are allocated on the heap and have reference semantics, i.e. an unique instance
can be held by multiple variables and only when all those variables go out-of-scope is
the ref type discarded.
By default Nim uses a deferred reference counting GC. Additionally, if the type can lead
to cycles, Nim will add "mark-and-sweep" passes to collect them.
## Destructors
TODO
## Nim allocator
Nim GCs are backed by a TLSF allocator which allows Nim to provide soft real-time guarantees if needed.
## Analyzing memory leaks
Nim can be compiled with `-d:useMalloc` to bypass the TLSF allocator and directly use malloc/free
## References
- [https://nim-lang.org/docs/gc.html](https://nim-lang.org/docs/gc.html)
- [http://www.gii.upv.es/tlsf/](http://www.gii.upv.es/tlsf/)

View File

@ -0,0 +1,30 @@
# Generics and Static types
Nim types can be parametrized by types (generics) or compile-time values (static)
For example
```Nim
type
MySeq[T] = object
len, reserved: int
data: ptr UncheckedArray[T]
```
The generics can be restricted
```Nim
type
MySeq[T: int32 or int64] = object
len, reserved: int
data: ptr UncheckedArray[T]
```
With static types
```Nim
type
SmallSeq[MaxLen: static int, T] = object
len: int
data: array[MaxLen, T]
```

View File

@ -0,0 +1,21 @@
# Arrays, openarrays, varargs
## Arrays
TODO
## Openarrays
Openarray are a parameter-only type that represent a (pointer, length) pair.
In other languages they are also known as slices, ranges, views, spans.
_The name openarray is inherited from Pascal, Oberon and Modula 2_
Arrays and sequences are implictily converted to openarray.
The compiler has a limited form of escape analysis to prevent capturing openarrays in closures
or returning them.
## UncheckedArrays
TODO

View File

@ -0,0 +1,2 @@
# Runtime types, variants, object-oriented programming
TODO

View File

@ -0,0 +1,2 @@
# Compile-time evaluation
TODO

View File

@ -0,0 +1,109 @@
# Nim data types
## Builtin types
### Numerical types
For integers, Nim offers `uint8`, `uint16`, `uint32` and `uint64` types as well as `int8`, `int16`, `int32`, `int64` types.
Operations on signed integers are overflowed and underflowed checked unless the flag `--overflowChecks:off` is used.
Nim also offers `int` and `uint` types, which have the same size as the platform word size, so 4 bytes when compiled for 32-bit OS
or 8 bytes for a 64-bit OS.
Conversion between `int` and `int32` or `int64` must be explicit except for string literals.
Integer literals default to `int`.
`float32` and `float64` maps to C `float` and `double`. `float` is an alias to `float64` whether on a 32-bit or 64-bit platform
#### Binary blobs
Nim has a specific `byte` type which behaves like uint8.
It is the preferred type to represent binary blobs, i.e. we use `seq[byte]` over `string`, `seq[char]` or `seq[uint8]` for binary data.
#### Range types
Nim allows defining ranges of valid value which will be runtime checked everytime the value changes for example
Nim defines by default `type Natural = range[0 .. high(int)]`. If the value of a Natural becomes less than 0
an RangeError exception will be thrown.
This is valuable to catch / prevent underflows.
## Sequences
Sequences are heap-allocated containers similar to C++ vectors.
They have value-semantics and are copied on assignments.
Sequences have a data pointer, reserved memory and current length
## Strings
Sequences are heap-allocated containers.
They have value-semantics and are copied on assignments.
Strings have a data pointer, reserved memory and current length.
The data is terminated by `\0`.
Nim strings automatically decays without copy to C strings in FFI calls
The representation is the same as a `seq[byte]` or `seq[char]` except for the terminating nul-byte,
including within the GC tracking data structures
Consequently, strings can be cast to `seq[byte]` but inversely casting `seq[byte]` to string
will lead to non nul-terminated string in C FFI and buffer overflows.
## Record / structs
Nim has 3 kinds of record types
- Value
```Nim
type Foo = object
field0: int
field1: float
```
- Reference
```Nim
type Foo = ref object
field0: int
field1: float
```
- Pointer
```Nim
type Foo = ptr object
field0: int
field1: float
```
### Value
A value object is allocated on the stack (unless if nested in heap-allocated types).
The equality check is structural.
Copy is done by copyMem (C memcpy)
Object variants do not have an equality operator set by default
### Reference
A reference object is allocated on the heap and managed by the GC.
They are not thread-safe.
The equality check is by reference
Copy is done by copying the reference and increment the reference count
### Pointer
A pointer object is manually managed, it can be on the heap (malloc) or on the stack (alloca)

View File

@ -0,0 +1,101 @@
# Correctness, distinct, mutability, effects, exceptions
The Nim compiler provides several constraints that can be used to enforce
proper usage of variables, types and error handling at compile-time.
One was already mentioned in previous paragraphs:
- Side-Effect analysis via using `func` or `{.noSideEffect.}` (in the routines chapter)
_Note that range types currently work at runtime only_
## Side-Effect
As mentioned in the routines chapter, using a `func` or a proc tagged with `{.noSideEffect.}` will prevent reading or writing to global variables (i.e. variables that are neither parameters or locals).
Note that side-effect analysis cannot analyse raw emitted C code
Additionally, allocating a sequence or a string, even if they technically
access a global memory allocator, is not considered a side-effect.
The compiler will ignore statements in a `{.noSIdeEffect.}` block for the purpose of side-effect analysis. At the moment this is only used for trace and some debug logs, as writing to stdout/stderr is considered writing to a global variables and so a side-effect.
## `not nil`
The compiler exposes a "not nil" annotation for reference and pointer types.
This enforces that parameters are proven always initialized in tagged:
- procedures
- types
This is not used in the codebase as a more powerful prover is required for our application.
Currently, the compiler warns when it cannot prove that a result reference is not nil.
## `distinct` types
A distinct type is a type that has the same representation as a base type at a low-level but cannot be used in its stead.
```Nim
type Miles = distinct float32
type Meters = distinct float32
```
Procedures accepting:
- `float32` will not accept `Miles` or `Meters`
- `Miles` will not accept `float32` or `Meters`
- `Meters` will not accept `float32` or `Miles`
`distinct` type can reuse the base type procedures and fields via the borrow annotation as described in the manual ([https://nim-lang.org/docs/manual.html#types-distinct-type](https://nim-lang.org/docs/manual.html#types-distinct-type))
## Enforcing exception handling
The codebase uses a mix of Result and Exceptions for error handling
with option types and bool in some cases.
As an effort to sanitize error handling and ensure that all exception paths are handled, we use the effect tracking system the following way:
```Nim
proc foo() {.raises: [].} =
discard
```
The procedure above will refuse to compile if its body can throw an unhandled exception.
```Nim
proc foo() {.raises: [ValueError].} =
discard
```
The procedure above will refuse to compile if its body can throw an exception besides a ValueError.
In particular Nim distinguishes between Defects, which are non-recoverable, and Exceptions, which we should recover from.
For our purposes, we allow all procedures to throw a Defect (for example an assertion broken), this is done by adding `{.push raises:[Defect]}` at the start of a file
```Nim
{.push raises:[Defect]}
proc foo1() =
discard
proc foo2() =
discard
{.pop.}
```
## Mutability
Only variables declared with `var` and `var` parameters are mutable.
Note that mutability analysis is not deep for reference types or pointer types.
You can always mutate through a pointer.
## Future
Significant improvements are in-progress planned to improve Nim safety:
- `lent` return values and owned reference, a limited form of borrow checker: [https://nim-lang.org/docs/destructors.html#lent-type](https://nim-lang.org/docs/destructors.html#lent-type)
- Z3 theorem prover for bound checks and nil checks at compile-time: [https://nim-lang.org/docs/drnim.html](https://nim-lang.org/docs/drnim.html)
- write-tracking to prevent deep immutability: [https://github.com/nim-lang/RFCs/issues/234](https://github.com/nim-lang/RFCs/issues/234) ([https://nim-lang.org/araq/writetracking.html](https://nim-lang.org/araq/writetracking.html))
however it is too early to use them in a production codebase.

View File

@ -0,0 +1,67 @@
# Debugging Nim
Reference article: [https://nim-lang.org/blog/2017/10/02/documenting-profiling-and-debugging-nim-code.html](https://nim-lang.org/blog/2017/10/02/documenting-profiling-and-debugging-nim-code.html)
## GDB / LLDB
Nim can be instrumented with sourcemaps:
- by passing `--debugger:native` to the compiler, so that the stacktraces in gdb shows the Nim source code
- by passing `--passC:"-g"` to the compiler, so that the stacktraces in gdb shows the C source code
## Sanitizers & Valgrind
LLVM and GCC sanitizers can be used with
```Nim
nim c --cc:clang -r -d:release --debugger:native \
--passC:"-fsanitize=address" --passL:"-fsanitize=address" \
--outdir:build target_application.nim
```
Note on deactivating Nim memory allocator:
As mentioned in the memory management section, Nim has
- a garbage collector, by default deferred reference counting + cycle detection via mark-and-sweep if the types can have cycles (and is not tagged {.acyclic.})
- an allocator based on TLSF
Instead of Nim custom allocators, the sys malloc/free can be used by passing
`-d:useMalloc` in the command-line
_Some GC/versions might not properly accept the flag, this is a Nim bug and we can patch upstream and our own fork in that case_
## Fuzzers
TODO
### Internal fuzzing
We are currently adding fuzzing to our repositories via libFuzzer and AFL.
- Fuzzing abstraction: [https://github.com/status-im/nim-testutils](https://github.com/status-im/nim-testutils)
- Network lib fuzzing: [https://github.com/status-im/nim-eth/tree/4f533eb5/tests/fuzzing](https://github.com/status-im/nim-eth/tree/4f533eb5/tests/fuzzing)
- Eth2 core/Validator core fuzzing: [https://github.com/status-im/nim-blscurve/tree/271a5738/tests/fuzzing](https://github.com/status-im/nim-blscurve/tree/271a5738/tests/fuzzing)
### External "Consensus" fuzzing
Sigma Prima is fuzzing all Ethereum 2 clients on the spec (Eth2 core/Validator core)
We provide them with a simple C API
- [https://github.com/status-im/nim-beacon-chain/blob/4a2e1806/nfuzz/libnfuzz.h](https://github.com/status-im/nim-beacon-chain/blob/4a2e1806/nfuzz/libnfuzz.h)
That we implement:
- [https://github.com/status-im/nim-beacon-chain/blob/4a2e1806/nfuzz/libnfuzz.nim](https://github.com/status-im/nim-beacon-chain/blob/4a2e1806/nfuzz/libnfuzz.nim)
And compile the nim code as a shared or static library.
On usage, the only extra limitation compared to a C library is the need to call `NimMain()` before calling any Nim function to initialize the Nim runtime.
## Inspecting the generated intermediate code and objects
The intermediate C code and object files are stored in
- `$HOME/.cache/nim/compiled_target_d` or `$HOME/.cache/nim/compiled_target_r` on UNIX (d for debug, r for release)
- `$HOME/nimcache/compiled_target_d` or `$HOME/.cache/nim/compiled_target_r` on Windows
The cache directory can be set with the `--nimcache=<targetdir>` compiler flag.
Repositories vendoring `nimbus-build-system` do not use the default setting
but generate directly in the (gitignored) `nimcache` folder of the repo.

View File

@ -0,0 +1,48 @@
# Foreign language interop
## Wrapping C
### Using shared library
Example: secp256k1
[https://github.com/status-im/nim-secp256k1/blob/master/secp256k1_abi.nim](https://github.com/status-im/nim-secp256k1/blob/master/secp256k1_abi.nim)
### Compiling directly the C files
Example: Apache Milagro Crypto
[https://github.com/status-im/nim-blscurve/blob/master/blscurve/milagro.nim](https://github.com/status-im/nim-blscurve/blob/master/blscurve/milagro.nim)
## Wrapping C++
Beyond the syntax for wrapping C, Nim offers a flexible syntax for wrapping C++,
for example for vectors:
```Nim
type
CppVector* {.importcpp"std::vector", header: "<vector>", byref.} [T] = object
proc newCppVector*[T](): CppVector[T] {.importcpp: "std::vector<'*0>()", header: "<vector>", constructor.}
proc newCppVector*[T](size: int): CppVector[T] {.importcpp: "std::vector<'*0>(#)", header: "<vector>", constructor.}
proc len*(v: CppVector): int {.importcpp: "#.size()", header: "<vector>".}
proc add*[T](v: var CppVector[T], elem: T){.importcpp: "#.push_back(#)", header: "<vector>".}
proc `[]`*[T](v: CppVector[T], idx: int): T{.importcpp: "#[#]", header: "<vector>".}
proc `[]`*[T](v: var CppVector[T], idx: int): var T{.importcpp: "#[#]", header: "<vector>".}
proc `[]=`*[T](v: var CppVector[T], idx: int, value: T) {.importcpp: "#[#]=#", header: "<vector>".}
```
Example: ttmath
[https://github.com/status-im/nim-ttmath/blob/master/src/ttmath.nim](https://github.com/status-im/nim-ttmath/blob/master/src/ttmath.nim)
## Exporting
See "Fuzzing" chapter for exporting a C API for [fuzzing](02.4_debugging_Nim_sanitizers_fuzzers.md#Fuzzers)
## References
- Nim manual:
- [https://nim-lang.org/docs/manual.html#foreign-function-interface-importc-pragma](https://nim-lang.org/docs/manual.html#foreign-function-interface-importc-pragma)
- [https://nim-lang.org/docs/manual.html#implementation-specific-pragmas-importcpp-pragma](https://nim-lang.org/docs/manual.html#implementation-specific-pragmas-importcpp-pragma)
- Nim backend integration: [https://nim-lang.org/docs/backends.html](https://nim-lang.org/docs/backends.html)

View File

@ -0,0 +1,12 @@
# Nim threat model
Nim and its standard library are not the focus of the audits.
In particular the codebase intentionally limits reliance on the standard library
so that it is not tied to Nim release schedule, instead of the standard library
we use `stew` most of the time: [https://github.com/status-im/nim-stew](https://github.com/status-im/nim-stew).
Nim standard library is implemented here:
- [https://github.com/nim-lang/Nim/tree/v1.2.4/lib](https://github.com/nim-lang/Nim/tree/v1.2.4/lib)
We target Nim v1.2.2

View File

@ -0,0 +1 @@
# Nim standard library use in Nimbus

View File

@ -0,0 +1,90 @@
# The Nim Programming Language
The Nim programming language is a compiled language, with strong static typing.
The rest of the Handbook will assume that Nim-by-example was read.
Nim compilation process is in 2 phases, first lowering the Nim code to C, C++ or Javascript. Then for machine code, rely on the C/C++ compiler to produce the final code.
Nim can target any combination of C compiler, host OS and hardware architecture as long as the C compiler supports it.
## Installing
Nim can be installed via:
- A Linux distribution package manager or Homebrew on MacOS
- Instructions at [https://nim-lang.org/install.html](https://nim-lang.org/install.html)
- Docker: [https://hub.docker.com/r/nimlang/nim/](https://hub.docker.com/r/nimlang/nim/)
- Choosenim [https://github.com/dom96/choosenim](https://github.com/dom96/choosenim)
Nim Vagrant [https://github.com/status-im/nim-vagrant](https://github.com/status-im/nim-vagrant) is unmaintained but might
help setting up your own virtualized environment.
We target Nim 1.2.2 and should be compatible with the latest stable, Nim 1.2.4
## Casings
Nim has unusual partial case insensitivity for identifiers. The rationales being:
- Preventing bugs when using `SDL_QUIT` instead of `SDL_Quit`.
- Having consistent casing in a codebase even when relying on external dependencies with different casing.
The convention used in Nim-Beacon-Chain is:
- `snake_case` for fields and procedures names from the Ethereum spec
- `MACRO_CASE` for Ethereum spec constants
- `PascalCase` for all types (Ethereum or additional)
- `camelCase` for our own additional code
- `PascalCase` for our additional constants
In summary, we respect the Ethereum spec for Ethereum specified identifiers
and use Nim NEP-1 for the rest.
## Checking the C code
By default the intermediate C code produced by the Nim compiler is available at
- `$HOME/.nim/compiled_project_d` on UNIX systems
- `$HOME/nimcache/compiled_project_d` on Windows
The suffix `_d` indicates a debug build, the suffix `_d` indicates a release build
## Compiler options
At the time of writing, NBC targets Nim v1.2.2 compiler.
The build system is at [https://github.com/status-im/nimbus-build-system](https://github.com/status-im/nimbus-build-system)
No patching is done at the moment on the Nim compiler, we use vanilla v1.2.2 upstream.
Nim compiler offers debug, release with `-d:release` and danger with `-d:danger` flag.
The debug and `-d:release` build differ by, the verbosity of stacktraces and passing `-O3` or equivalent to the C compiler.
Runtime checks (overflow, array bounds checks, nil checks, ...) are still included in `-d:release` build. We also choose to have verbose stacktraces in NBC.
A danger build optimizes away all runtime checks and debugging help like stackframes. This might have a significant impact on performance
as it may enable optimizations that were not possible like optimizing tail calls. This is not used in NBC.
## References
- Nim by example:
- [https://nim-by-example.github.io/getting_started/](https://nim-by-example.github.io/getting_started/)
- The Nim Manual, is a specification of how Nim should behave\
[https://nim-lang.org/docs/manual.html](https://nim-lang.org/docs/manual.html)
- Nim tutorials
- [https://nim-lang.org/docs/tut1.html](https://nim-lang.org/docs/tut1.html)
- [https://nim-lang.org/docs/tut2.html](https://nim-lang.org/docs/tut2.html)
- [https://nim-lang.org/docs/tut3.html](https://nim-lang.org/docs/tut3.html)
- Nim for
- the C programmer: [https://github.com/nim-lang/Nim/wiki/Nim-for-C-programmers](https://github.com/nim-lang/Nim/wiki/Nim-for-C-programmers)
- Python programmer: [https://github.com/nim-lang/Nim/wiki/Nim-for-Python-Programmers](https://github.com/nim-lang/Nim/wiki/Nim-for-Python-Programmers)
Further resources are collected at:
- [https://nim-lang.org/learn.html](https://nim-lang.org/learn.html)
### Compiler configuration
- Compiler User Guide: [https://nim-lang.org/docs/nimc.html](https://nim-lang.org/docs/nimc.html)
### Style Guide
- [https://nim-lang.org/docs/nep1.html](https://nim-lang.org/docs/nep1.html)

View File

@ -0,0 +1,99 @@
# Nimbus build system & dependencies
## Build system
### NBC repo
`nim-beacon-chain` uses a build system based on makefiles.
Usage is described in the README.
In particular a `env.sh` scripts is provided that setups the environment variable
expected.
### Libraries
Library repositories uses the official package manager called `nimble`
for building and testing.
In particular, we use by convention the following command to run the test suite via nimble task system:
```Nim
nimble test
```
The details are implemented in `<package name>.nimble` at the root of the repository.
The continuous integration setup for Travis, Azure Pipelines and/or Appveyor
are also setting up a Nim environment from scratch.
## Package dependencies
### NBC repo
For the `nim-beacon-chain` application, all dependencies are vendored
in the `vendor` folder.
With regards to the audit scope we have the following dependencies:
#### All
- Async
- asynctools
- nim-chronos
- Logging
- jswebsockets
- karax
- nim-chronicles
- nim-chronicles-tail (devtool, out-of-audit-scope)
- nim-libbacktrace
- websocket.nim
- nim-protobuf-serialization
- nim-rocksdb
- CLI interface
- nim-confutils
- nim-prompt
- nim-unicodedb
- Metrics
- nim-metrics (the server endpoint is out-of-audit-scope)
- Helpers
- nim-stew
- nim-testutils (fuzzing)
- nimYAML (should be test only)
#### Network core
- Cryptography
- nim-bearssl
- nim-secp256k1
- nimcrypto
- Networking & Discovery
- nim-eth
- nim-libp2p
- nim-nat-traversal
- Serialization
- nim-faststreams
- nim-serialization
- nim-snappy
- BigInt
- nim-stint (Note: one of the main use is 256-bit bloomfilter, a dedicated library should be used instead)
#### ETH2 Specification core
- Cryptography
- nim-blscurve
- Database
- nim-sqlite3-abi
- Eth1 monitoring
- nim-web3
- Build system
- nimbus-build-system
#### Validator core
- Cryptography
- nim-blscurve
- RPC
- news
- nim-http-utils
- nim-json-rpc
- nim-json-serialization

View File

@ -0,0 +1,42 @@
# NBC Threat model
NBC primarily targets resource restricted devices like Raspberry Pi or smartphones to desktop computers.
We focus on:
- remote attacks
This includes but not limited to:
- denial-of-services
- retrieving a secret key
- via software defect or misuse of libraries (cryptography, networking)
- or poor key management practices or UI or documentation confusing the user
- collusion of peers (eclipse, surround voting, ...)
- loss of funds
In particular, we do not defend against a malicious cloud provider which would offer
a “Rent-a-Raspberry-Pi” service for validators and would subsequently engage in attacks against
those validators, the implest being, taking the validator offline when it's their duty time.
Sensitive data:
1. Signing key: each validator has a secret signing key used to sign attestation and blocks with 32 ETH at stake (about 7500 USD at the current rate)
Each beacon node instance can have dozens to hundreds of validators attached.
Leaking the signing key would allow an attacker to double-vote with the key, leading to slashing and ultimately ejecting the validator
once its stake reaches below 16 ETH.
The signing key must be present in memory as a validator may have signing duties every 6 seconds
2. Withdrawal key: a withdrawal key allows withdrawing the stake.
A withdrawal key is unnecessary during day-to-day operation and can be stored in cold storage, possibly hardware wallet
3. IP address: if a validator IP address leaks, they might be subject to target DoS attacks taking their node offline and making them miss their duties.
In a regular setting, the network will have "relayer"/"listener" nodes and validator nodes that shouldn't be distinguishable.
4. Random number generation:
- Strong recommendations to generate the withdrawal key offline.
- The RNG is used in particular to select which peer to connect to from a pool of candidates.
## Cryptographic libraries
A review in in progress to select BLS signature backend.
See [https://notes.status.im/nim-bls-curve-backends#Threat-model](https://notes.status.im/nim-bls-curve-backends#Threat-model)
## Resources
Surround vote detection (not in audit scope): [https://github.com/protolambda/eth2-surround](https://github.com/protolambda/eth2-surround)

View File

@ -0,0 +1,63 @@
# Nimbus Beacon Chain
[https://github.com/status-im/nim-beacon-chain](https://github.com/status-im/nim-beacon-chain)
Nimbus Beacon Chain (NBC) is an implementation of an Ethereum 2 client.
## Audit scope
### Network Core (leveraging the libp2p framework)
| Sub-topic |
| -------------------------------------- |
| Discovery Protocol (discv5) |
| Publish/Subscribe protocol |
| Eth2 Request/Response protocol |
| SSZ - (De)serialization & tree hashing |
| Wire encryption |
### ETH2 Specification core
| Sub-topic |
| ------------------------------------- |
| State transition logic |
| Signature verification |
| Epoch finalisation and justification |
| Reward processing |
| Eth1 data processing |
| Fork choice logic |
| Block processing and production |
| Attestation processing and production |
| Block synchronization |
| Peer pool management |
### Validator core and user experience
| Sub-topic |
| --------------------------------- |
| Block/attestation signing |
| Slash-prevention mechanisms |
| RPC API |
| Accounts management & key storage |
| Command Line Interface (CLI) |
## High-level view of the stack
[https://miro.com/app/board/o9J_kvfytDI=/](https://miro.com/app/board/o9J_kvfytDI=/)
## Diagram
TODO
## Specifications
We target v0.12.1 phase0 of [https://github.com/ethereum/eth2.0-specs](https://github.com/ethereum/eth2.0-specs)
- [https://github.com/ethereum/eth2.0-specs/tree/v0.12.1/specs/phase0](https://github.com/ethereum/eth2.0-specs/tree/v0.12.1/specs/phase0)
The p2p-interface specs in particular describe the subset of libp2p spec that
are used to implement Ethereum 2
## Resources
Ethereum 2.0 Ask Me Anything:
- [https://docs.ethhub.io/other/ethereum-2.0-ama/](https://docs.ethhub.io/other/ethereum-2.0-ama/)

View File

@ -0,0 +1,2 @@
# Serialization

View File

@ -0,0 +1 @@
# Async/Await with the Chronos library

View File

@ -0,0 +1 @@
# Cryptography

View File

@ -0,0 +1 @@
# Ethereum Networking

View File

@ -0,0 +1,36 @@
# Summary
- [Introduction](01_introduction.md)
- [The Nim Programming Language](02_the_Nim_programming_language.md)
- [Nim routines, procedures, functions, templates, macros](02.1_nim_routines_proc_func_templates_macros.md)
- [Operators, bit manipulation](02.1.1_operators_bit_manipulation.md)
- [Closure iterators](02.1.4_closure_iterators.md)
- [Datatypes: value, ref, ptr](02.2_stack_ref_ptr_types.md)
- [Casting and low-level memory representation](02.2.2_casting_and_low_level_memory_representation.md)
- [Memory Management and Garbage Collection](02.2.3_memory_management_gc.md)
- [Generic types & static types](02.2.4_generics_types_static_types.md)
- [Arrays, openarrays, strings, C-strings](02.2.5_arrays_openarrays_strings_cstring.md)
- [Correctness: distinct, mutability, effects, exceptions](02.3_correctness_distinct_mutability_effects_exceptions.md)
- [Debugging Nim, sanitizers, fuzzers](02.4_debugging_Nim_sanitizers_fuzzers.md)
- [Foreign lang interop: C and C++](02.5_foreign_lang_to_from_interop.md)
- [Nim threat model](02.8_Nim_threat_model.md)
- [Nim FAQ](02.10_Nim_FAQ.md)
- [Nimbus NBC - The Nim-Beacon-Chain](03_nbc_nimbus_beacon_chain.md)
- [Build system and dependencies](03.2_build_system_and_dependencies.md)
- [Threat model](03.5_the_threat_model.md)
- [Serialization](04_serialization.md)
- [Async/Await with Chronos](05_async_with_chronos.md)
- [Cryptography](06_cryptography_and_rng.md)
- [Ethereum Networking](07_nim-eth.md)
<!-- Not fleshed out, out of line because mdbook bug -->
<!-- - [Pointer manipulation](02.1.2_pointer_manipulation.md) -->
<!-- - [Emitting raw C or Assembly code](02.1.3_emitting_raw_C_assembly_code.md) -->
<!-- - [Builtin types](02.2.1_builtin_types.md) -->
<!-- - [Runtime types: Variants & Object-Oriented Programming](02.2.6_runtime_types_variants_oop.md) -->
<!-- - [Compile-time Evaluation](02.2.7_compiletime_evaluation.md) -->
<!-- - [Nim standard library use in Nimbus](02.9_Nim_stdlib_use_in_nimbus.md) -->

View File

@ -1,5 +1,5 @@
[book]
authors = ["Lee Ting Ting"]
[book]
authors = ["Lee Ting Ting", "Jacek Sieka"]
language = "en"
multilingual = false
src = "src"

View File

@ -2,8 +2,8 @@
- [Introduction](./intro.md)
- [Installation](./install.md)
- [Become a Validator](./validator.md)
- [Command-line Options](./cli.md)
- [Running the beacon node](./beacon_node.md)
- [Becoming a Validator](./validator.md)
- [API](./api.md)
- [Advanced Usage for Developers](./advanced.md)
- [FAQs](./faq.md)

View File

@ -19,6 +19,8 @@ Before you can access the API, make sure it's enabled using the RPC flag (`beaco
--rpc-address Listening address of the RPC server.
```
One difference is that currently endpoints that correspond to specific ones from the [spec](https://ethereum.github.io/eth2.0-APIs/) are named weirdly - for example an endpoint such as [`getGenesis`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getGenesis) is currently named `get_v1_beacon_genesis` which would map 1:1 to the actual REST path in the future - verbose but unambiguous.
## Beacon Node API
### getBeaconHead
@ -53,10 +55,104 @@ curl -d '{"jsonrpc":"2.0","id":"id","method":"getChainHead","params":[] }' -H 'C
curl -d '{"jsonrpc":"2.0","id":"id","method":"getNetworkEnr","params":[] }' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_genesis`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getGenesis)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_genesis","params":[],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_states_root`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateRoot)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_states_root","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_states_fork`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateFork)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_states_fork","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_states_finality_checkpoints`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateFinalityCheckpoints)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_states_finality_checkpoints","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_states_stateId_validators`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateValidators)
### [`get_v1_beacon_states_stateId_validators_validatorId`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateValidator)
### [`get_v1_beacon_states_stateId_committees_epoch`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getEpochCommittees)
### [`get_v1_beacon_headers`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeaders)
### [`get_v1_beacon_headers_blockId`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeader)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_headers_blockId","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_blocks_blockId`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlock)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_blocks_blockId","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_blocks_blockId_root`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockRoot)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_blocks_blockId_root","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_blocks_blockId_attestations`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockAttestations)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_blocks_blockId_attestations","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`post_v1_beacon_pool_attestations`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolAttestations)
## Valdiator API
### [`get_v1_validator_block`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/produceBlock)
### [`post_v1_validator_block`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/publishBlock)
### [`get_v1_validator_attestation`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/produceAttestation)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_validator_attestation_data","params":[0,3],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_validator_aggregate_and_proof`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/getAggregatedAttestation)
### [`post_v1_validator_aggregate_and_proof`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/publishAggregateAndProof)
### [`post_v1_validator_duties_attester`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/getAttesterDuties)
```
curl -d '{"jsonrpc":"2.0","method":"post_v1_validator_duties_attester","params":[1,["a7a0502eae26043d1ac39a39457a6cdf68fae2055d89c7dc59092c25911e4ee55c4e7a31ade61c39480110a393be28e8","a1826dd94cd96c48a81102d316a2af4960d19ca0b574ae5695f2d39a88685a43997cef9a5c26ad911847674d20c46b75"]],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_validator_duties_proposer`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/getProposerDuties)
```
curl -d '{"jsonrpc":"2.0","id":"id","method":"get_v1_validator_duties_proposer","params":[1] }' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
## Config
### [`get_v1_config_fork_schedule`](https://ethereum.github.io/eth2.0-APIs/#/Config/getForkSchedule)
## Administrative / Debug API
### `get_v1_debug_beacon_states_stateId` - returns an entire `BeaconState` object for the specified `stateId`
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_debug_beacon_states_stateId","params":["head"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### getNodeVersion
Show version of the software

View File

@ -1,16 +1,69 @@
# Command-line Options
# The beacon node
You can run your customized beacon node using the `beacon_node` executable. The available options are shown below - you can also run `beacon_node --help` for a reminder.
The beacon node application connects to an Ethereum 2 network, manages the block chain and provides API's to interact with the beacon chain.
You can run the beacon node without being a validator - doing so will allow you to sync the network and access its latest state.
## Prerequisites
Specifying a genesis file is mandatory to run this executable. You can either get it from the official eth2 repository [here](https://github.com/eth2-clients/eth2-testnets/blob/master/shared/witti/genesis.ssz) or generate your own like [this](https://github.com/status-im/nim-beacon-chain/blob/db92c2f2549a339be60896c3907cefdb394b5e11/scripts/launch_local_testnet.sh#L154) when starting a local testnet. You can also specify the path of your genesis file like [this](https://github.com/status-im/nim-beacon-chain/blob/db92c2f2549a339be60896c3907cefdb394b5e11/scripts/launch_local_testnet.sh#L229).
Before compiling and running the application, make sure you've completed the [installation guidelines](./install.md) for the prerequisites.
For example, download a genesis file and then run the following command to start the node:
## Running the node
<img src="./img/beacon_node_example.PNG" alt="" style="margin: 0 40 0 40"/>
When running the beacon node, you connect to a specific ethereum 2 network - this may be a private network or a public testnet like [altona](https://github.com/goerli/altona/tree/master/altona).
## Usage
When running the node for the first time, you need to specify network parameters, boot nodes and genesis information. This information can typically be found in the [eth2 testnets](https://github.com/eth2-clients/eth2-testnets) repository. This information is automatically downloaded when using the simplified startup.
Once the beacon node is running, it will first connect to the boot nodes in the network, look for more peers and start syncing the chain. Once sync is complete, it will keep following the head of the chain and can be interacted with through the [API](./api.md).
Before running the beacon node, it is important that computer has the correct time set - preferably from a trusted time source (this can be an NTP server you trust, GPS time or any other precise source of time).
To start syncing the `altona` network, run the build process. When asked for a key, just press enter.
```
$ make altona
# Build output...
Please enter your Goerli Eth1 private key in hex form (e.g. 0x1a2...f3c) in order to become a validator (you'll need access to 32 GoETH).
Hit Enter to skip this.
>
```
Hit enter above - you will be prompted again if you want to become a validator. Beacon node will launch and start syncing.
```
INF 2020-07-03 15:28:15+02:00 Starting beacon node topics="beacnde" tid=176865 file=beacon_node.nim:866 SECONDS_PER_SLOT=12 SLOTS_PER_EPOCH=32 SPEC_VERSION=0.12.1 cat=init dataDir=/home/arnetheduck/status/nim-beacon-chain/build/data/shared_altona_0 finalizedRoot=72e7b21c finalizedSlot=20064 headRoot=f92bf720 headSlot=20142 nim="Nim Compiler Version 1.2.2 [Linux: amd64] (be34b5ab)" pcs=start_beacon_node timeSinceFinalization=-108322 version="0.5.0 (c64737e)"
peers: 7 finalized: 3a806c9f:634 head: b364f8e9:636:29 time: 909:7 (29095) ETH: 0.0
```
### Status bar
The status bar shows important health information about your node:
* peers - The number of peers you're connected to
* finalized - The block root and epoch of the latest finalized checkpoint - when the network is healthy, this value will stay at 2-3 epochs from the wall clock
* head - The block root and time of the head block - as blocks are produced and processed, this will be updated to the latest head block as chosen by the consensus algorithm.
* time - The current wall time according to your computer - when the node is synced, the head block will closely follow this time.
* ETH: the total ETH validators attached to the node have accumulated. When there are no validators attached, this number will be 0.
Time is shown as `epoch:subslot`, starting from the block chain genesis time - one epoch is typically 32 slots but this may vary between networks.
The status bar content may be updated using command line flags.
### Metrics
Nimbus includes metrics support using the Prometheus format. To enable it, you need to enable insecure feature when compiling the application. The http server that exports Prometheus metrics should not be exposed to external parties.
```
# Compile with insecure features enabled
make NIMFLAGS="-d:insecure" altona
```
## Command line options
```
$ ./beacon_node --help
@ -141,3 +194,7 @@ beacon_node_shared_altona_0 wallets list
Lists details about all wallets.
```
## Next steps
Once you're synced, you can move on to become a [validator](./validator.md).

View File

@ -1,16 +1,16 @@
# Frequently Asked Questions
## 1. What is Beacon Chain?
## What is Beacon Chain?
A complete introduction about the beacon chain can be found in the [Ethereum 2.0 blog series](https://our.status.im/two-point-oh-the-beacon-chain/).
In short, the beacon chain is a **new type of blockchain** to help the Ethereum blockchain to smoothly transfer its consensus algorithm from PoW (Proof of Work) to PoS (Proof of Stake), aka Ethereum 2.0.
## 2. Differences Between Beacon Chain and Ethereum 1.0
## Differences Between Beacon Chain and Ethereum 1.0
In traditional PoW, those that propose new blocks are called **_miners_**, whereas in PoS, they are called **_validators_**. In essence, _miners_ rely on actual hardware (such as some specifically manufactured mining machines), while _validators_ rely on just software and a good network connection.
## 3. What it is Like to Be a Validator?
## What it is Like to Be a Validator?
It is obvious that you must have enough computing power or dedicated hardware in order to be a miner, but how about being a validator? Here is a brief overview:
@ -19,7 +19,7 @@ It is obvious that you must have enough computing power or dedicated hardware in
3. Run the beacon node and wait for the network to sync before your validator is activated.
4. That's all! Remember to stay connected to the network, or you may lose some of your deposit, as punishment, depending on how long you're offline. :P
## 4. What is Nimbus?
## What is Nimbus?
In a sentence, Nimbus is an Ethereum 1.0 & 2.0 Client for Resource-Restricted Devices.

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -2,6 +2,18 @@
Beacon chain can run on Linux, macOS, Windows, and Android. At the moment, Nimbus has to be built from source.
## Time
The beacon chain relies on your computer having the correct time set, down to at most 0.5 seconds.
It is recommended that you run a high quality time service on your computer such as:
* GPS
* NTS (network time security, IETF draft)
* Roughtime (google)
As a minimum, you should run an NTP client on the server - NTP may easily be disrupted however.
## External Dependencies
- Developer tools (C compiler, Make, Bash, Git)
@ -29,7 +41,7 @@ yourAURmanager -S base-devel pcre-static
Assuming you use [Homebrew](https://brew.sh/) to manage packages
```sh
brew install pcre
brew install pcre cmake
```
### Windows
@ -51,4 +63,4 @@ apt install build-essential git libpcre3-dev
## Next steps
Once you've installed build tools, you're ready to move on to launching the beacon node and becoming a [validator](./validator.md)
Once you've installed the prerequisites, you're ready to move on to launching the [beacon node](./beacon_node.md).

View File

@ -13,8 +13,8 @@ In this book, we will cover:
1. [What is beacon chain](./faq.md#1-what-is-beacon-chain) and [what is Nimbus](./faq.md#4-what-is-nimbus) to equip you with some basic knowledge.
2. [Installation steps](./install.md) outline the prerequisites to get started.
3. How to [become a validator](./validator.md) in Ethereum 2.0 as a user, for example on the Altona testnet.
4. [CLI](./cli.md) for running your customized nimbus beacon node.
3. [Running the beacon node](./beacon_node.md) describes how to run the beacon node software to sync the beacon chain.
4. How to [become a validator](./validator.md) in Ethereum 2.0 as a user, for example on the Altona testnet.
5. [API](./api.md) for monitoring your node through `http`.
6. [Advanced usage](./advanced.md) for developers.
7. Common [questions and answers](./faq.md) to satisfy your curiosity.
@ -22,6 +22,11 @@ In this book, we will cover:
Feel free to give us feedback on how to improve as well as contribute to our book on github. :)
## Get in touch
Need help with anything? Join us using [Status](https://join.status.im/nimbus-general) or [Discord](https://discord.gg/9dWwPnG)!
## Disclaimer
This documentation is assuming Nimbus is in its ideal state. The project is still under active development. Please submit Github issues if encountered any problem.

View File

@ -1,8 +1,8 @@
# Become a Validator
To become a validator, you need to install the beacon chain software, acquire 32 ETH, set up your validator account and register with the deposit contract on Ethereum.
To become a validator, you need to install the beacon chain software, acquire 32 ETH, set up your validator account, and register with the deposit contract on ethereum.
There is currently no Eth2 mainnet - all networks are testnets.
There is currently no eth2 mainnet - all networks are testnets.
## Recommended Testnets
@ -15,13 +15,13 @@ Though Nimbus can connect to any of the testnets published in the [eth2-clients/
### Initial setup
Before we start, we have to obtain 32 ETH on the Goerli testnet. Then, we can deposit 32 Ethers to the registration smart contract to become a validator.
Before we start, we have to obtain 32 ETH on the Goerli testnet (later on, we'll need to send this ETH to the deposit contract in order to become a validator).
1. Open your [MetaMask](https://metamask.io/) wallet, switch to the `Goerli Test Network` option from the top right corner.
2. Copy your account address by clicking on one of your accounts.
3. Post your account address on a social media platform (Twitter or Facebook). Copy the url to the post.
4. Paste your post url on the [Goerli faucet](https://faucet.goerli.mudit.blog/) and select `Give me Ether > 37.5 Ethers` from the top right cornor of the page.
5. Wait for a few seconds and return to your MetaMask wallet to check if you have successfully received.
4. Paste your post url on the [Goerli faucet](https://faucet.goerli.mudit.blog/) and select `Give me Ether > 37.5 Ethers` from the top right corner of the page.
5. Wait for a few seconds and return to your MetaMask wallet to check if you have successfully received the ETH.
6. Once the [prerequisites](./install.md) are installed, you can connect to the altona testnet with the following commands: <br>
- **_Remember to replace `make` with `mingw32-make` if using Windows._**
@ -36,27 +36,63 @@ make altona # This will build Nimbus and all other dependencies
# and connect you to altona
```
_Once Nimbus has finished building, you will be prompted to enter the private key of the account you want to deposit the 32 ETH from._
<img src="./img/connect_testnet.PNG" alt="" style="margin: 0 40 0 40"/>
7. You will be prompted to enter your private key of the account you want to deposit the 32 Ether from. Find your private key from MetaMask as below:
7. Copy your private key from MetaMask (click on the three dots in the top right, followed by **Account Details** and **Export Private Key**).
<img src="./img/export_pkey.PNG" alt="" width="200" style="margin: 0 40 0 40"/>
<img src="./img/metamask_export_private_1.png" alt="" width="200" style="margin: 0 40 0 40"/>
<img src="./img/metamask_export_private_2.png" alt="" width="200" style="padding-left: 60px; padding-right: 60px; margin: 0 40 0 40"/>
<img src="./img/metamask_export_private_3.png" alt="" width="200" style="margin: 0 40 0 40"/>
8. Paste your private key into the console.
<img src="./img/enter_private_key.PNG" alt="" style="margin: 0 40 0 40"/>
8. Wait for a few seconds until you see that your deposit has been sent:
<img src="./img/deposit_sent.PNG" alt="" style="margin: 0 40 0 40"/>
9. Press enter and wait for a few seconds until you see *Deposit sent*.
9. The beacon chain client will start syncing the network while your deposit is being processed. As soon as the deposit has been added, the client will start performing validation duties.
```
INF 2020-07-05 12:58:25+02:00 Generating deposits ... validatorsDir=/Users/sssaintleger/nim-beacon-chain/build/data/shared_altona_0/validators ...
INF 2020-07-05 12:58:25+02:00 Sending deposits ... DepositContract=0x16e82D77882A663454Ef92806b7DeCa1D394810f ...
INF 2020-07-05 12:58:26+02:00 Deposit sent ... status=0x5455b1faf773a535668bdd4ade6b03f6cfd52f88414a5ad74bbdfdfd89f28b86
<img src="./img/success.PNG" alt="" style="margin: 0 40 0 40"/>
Deposit sent, wait for confirmation then press enter to continue
```
You can also get a brief estimate of the time remaining until your network gets synced by comparing the output `epoch` value and the one in the blockchain explorer (the [altona explorer](https://altona.beaconcha.in) for example).
10. Check the status of your deposit by copying and pasting the transaction hash into [https://goerli.etherscan.io/](https://goerli.etherscan.io/) (where the transacton hash is the text displayed after `status=` in your console). For example, in the output displayed after step 9, we have `status=0x5455...` so the transaction hash is `0x5455...`. If you see a green *Success* box (see the image below) you can go ahead and press enter to continue.
<img src="./img/deposit-transaction-details.png" alt="" style="margin: 0 40 0 40"/>
> Note: it should take approximately 8 hours for your deposit to be processed by the beacon chain. To keep track of the status of your validator you should go to [https://altona.beaconcha.in/validators/eth1deposits/[Validator PubKey]](https://altona.beaconcha.in/validators/eth1deposits), replacing [Validator PubKey] with your actual Validator PubKey -- you can find this in the etherscan transaction details (for example, in the image above the Validator PubKey is `0x95aa...`).
And voila! That's all there is to it :)
The beacon chain client will start syncing the network while your deposit is being processed. As soon as it has synced, and your validator has been confirmed as active, the client will start performing validation duties.
P.S. at the bottom of the console, you should see something like:
```
peers: 18 > finalized: 710631d5:283 > head: 98204d1:285:10 > time: 1405:2 (44982)
ETH: 0
```
You can get a brief estimate of the time remaining until you've synced to the network by comparing `head` with `time` (`time` gives the current epoch, whereas `head` tells you how many epochs you've synced so far). From the above output, we see that the number of epochs synced so far is `285`, and the current epoch is `1405`.
### Upgrading
When restarting the beacon node, the software will resume from where it left off, using your previous deposits.
When you restart the beacon node, the software will resume from where it left off, using your previous deposits.
```
cd nim-beacon-chain
@ -67,7 +103,16 @@ make altona # Restart using same keys as last run
## Key management
Keys are stored in the `build/data/testnet_name/` folder, under `secrets` and `validators` - make sure to keep these folders backed up.
Keys are stored in the `build/data/[testnet_name]/` folder, under `secrets` and `validators` - make sure you keep these folders backed up.
The `secrets` folder contains the common secret that gives you access to all your validator keys.
The `validators` folder contains your keystores. Keystores are used by validators as a method for exchanging keys.
For more on keys in eth2, see [here](https://blog.ethereum.org/2020/05/21/keys/).
## Metrics
@ -77,18 +122,18 @@ Metrics are not included in the binary by default - to enable them, use the foll
make NIMFLAGS="-d:insecure" altona
```
You can now browse the metrics using a browser and connecting to:
You can then browse the metrics by connecting to:
http://localhost:8008/metrics
Make sure to protect this port as the http server used is not considered secure and should not be used by untrusted peers.
Make sure this port is protected as the http server used is not considered secure (it should not be used by untrusted peers).
## Troubleshooting
1. The directory that stores the blockchain data of the testnet is `build/data/shared_altona_0` (replace `altona` with other testnet names). Delete this folder if you want to start over. For example, you can start over with a fresh storage if you entered a wrong private key.
1. The directory that stores the blockchain data of the testnet is `build/data/shared_altona_0` (if you're connecting to another testnet, replace `altona` with that testnet's name). Delete this folder if you want to start over (for example, if you entered a wrong private key).
2. Currently, you have to switch to the devel branch in order to run the validator node successfully.
2. Currently, you have to switch to the `devel` branch in order to run the validator node successfully.
3. Everytime you want to update your node to the latest version, run `git pull`, `make update`, and then `make altona`.
4. If `make update` has been running for too long, you can use `make update V=1` or `make update V=2` for verbose output.
4. If `make update` causes the console to hang for too long, try running `make update V=1` or `make update V=2` instead (these will print a more verbose output to the console which may make it easier to diagnose the problem).

View File

@ -204,8 +204,9 @@ template genProcessEpochScenario(name, transitionFn: untyped, needCache: static
proc `name`*(dir, preState: string) =
processEpochScenarioImpl(dir, preState, transitionFn, needCache)
proc process_deposit(state: var BeaconState; deposit: Deposit;
flags: UpdateFlags = {}): bool =
proc process_deposit(state: var BeaconState;
deposit: Deposit;
flags: UpdateFlags = {}): Result[void, cstring] =
process_deposit(defaultRuntimePreset, state, deposit, flags)
template processBlockScenarioImpl(
@ -232,13 +233,13 @@ template processBlockScenarioImpl(
var consObj = parseSSZ(consObjPath, ConsensusObjectRefType)
when needFlags and needCache:
let success = transitionFn(state.data, consObj[], flags, cache)
let success = transitionFn(state.data, consObj[], flags, cache).isOk
elif needFlags:
let success = transitionFn(state.data, consObj[], flags)
let success = transitionFn(state.data, consObj[], flags).isOk
elif needCache:
let success = transitionFn(state, consObj[], flags, cache)
let success = transitionFn(state, consObj[], flags, cache).isOk
else:
let success = transitionFn(state, consObj[])
let success = transitionFn(state, consObj[]).isOk
echo astToStr(transitionFn) & " status: ", if success: "SUCCESS ✓" else: "FAILURE ⚠️"

View File

@ -97,12 +97,12 @@ template decodeAndProcess(typ, process: untyped): bool =
proc nfuzz_attestation(input: openArray[byte], xoutput: ptr byte,
xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError, Defect].} =
decodeAndProcess(AttestationInput):
process_attestation(data.state, data.attestation, flags, cache)
process_attestation(data.state, data.attestation, flags, cache).isOk
proc nfuzz_attester_slashing(input: openArray[byte], xoutput: ptr byte,
xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError, Defect].} =
decodeAndProcess(AttesterSlashingInput):
process_attester_slashing(data.state, data.attesterSlashing, flags, cache)
process_attester_slashing(data.state, data.attesterSlashing, flags, cache).isOk
proc nfuzz_block(input: openArray[byte], xoutput: ptr byte,
xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError, Defect].} =
@ -123,22 +123,22 @@ proc nfuzz_block(input: openArray[byte], xoutput: ptr byte,
proc nfuzz_block_header(input: openArray[byte], xoutput: ptr byte,
xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError, Defect].} =
decodeAndProcess(BlockHeaderInput):
process_block_header(data.state, data.beaconBlock.message, flags, cache)
process_block_header(data.state, data.beaconBlock.message, flags, cache).isOk
proc nfuzz_deposit(input: openArray[byte], xoutput: ptr byte,
xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError, Defect].} =
decodeAndProcess(DepositInput):
process_deposit(defaultRuntimePreset, data.state, data.deposit, flags)
process_deposit(defaultRuntimePreset, data.state, data.deposit, flags).isOk
proc nfuzz_proposer_slashing(input: openArray[byte], xoutput: ptr byte,
xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError, Defect].} =
decodeAndProcess(ProposerSlashingInput):
process_proposer_slashing(data.state, data.proposerSlashing, flags, cache)
process_proposer_slashing(data.state, data.proposerSlashing, flags, cache).isOk
proc nfuzz_voluntary_exit(input: openArray[byte], xoutput: ptr byte,
xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError, Defect].} =
decodeAndProcess(VoluntaryExitInput):
process_voluntary_exit(data.state, data.exit, flags)
process_voluntary_exit(data.state, data.exit, flags).isOk
# Note: Could also accept raw input pointer and access list_size + seed here.
# However, list_size needs to be known also outside this proc to allocate xoutput.

View File

@ -40,10 +40,11 @@ proc updateTestnetsRepo(allTestnetsDir, buildDir: string) =
proc makePrometheusConfig(nodeID, baseMetricsPort: int, dataDir: string) =
# macOS may not have gnu-getopts installed and in the PATH
execIgnoringExitCode &"""./scripts/make_prometheus_config.sh --nodes """ & $(1 + nodeID) & &""" --base-metrics-port {baseMetricsPort} --config-file "{dataDir}/prometheus.yml""""
execIgnoringExitCode &"""./scripts/make_prometheus_config.sh --nodes """ & $(1 + nodeID) & &""" --base-metrics-port {baseMetricsPort} --config-file "{dataDir}/prometheus.yml" """
proc buildNode(nimFlags, preset, beaconNodeBinary: string) =
exec &"""nim c {nimFlags} -d:"const_preset={preset}" -o:"{beaconNodeBinary}" beacon_chain/beacon_node.nim"""
proc buildBinary(nimFlags, preset, binary, nimFile: string) =
if binary != "":
exec &"""nim c {nimFlags} -d:"const_preset={preset}" -o:"{binary}" beacon_chain/{nimFile}"""
proc becomeValidator(validatorsDir, beaconNodeBinary, secretsDir, depositContractOpt, privateGoerliKey: string,
becomeValidatorOnly: bool) =
@ -75,8 +76,8 @@ proc becomeValidator(validatorsDir, beaconNodeBinary, secretsDir, depositContrac
echo "\nDeposit sent, wait for confirmation then press enter to continue"
discard readLineFromStdin()
proc runNode(dataDir, beaconNodeBinary, bootstrapFileOpt, depositContractOpt,
genesisFileOpt, natConfig, metricsAddress, rpcAddress: string,
proc runNode(dataDir, beaconNodeBinary, validatorClientBinary, bootstrapFileOpt,
depositContractOpt, genesisFileOpt, extraBeaconNodeOptions: string,
basePort, nodeID, baseMetricsPort, baseRpcPort: int,
printCmdOnly: bool) =
let logLevel = getEnv("LOG_LEVEL")
@ -99,23 +100,36 @@ proc runNode(dataDir, beaconNodeBinary, bootstrapFileOpt, depositContractOpt,
echo &"cd {dataDir}; exec {cmd}"
else:
cd dataDir
mkDir dataDir & "/empty_dummy_folder"
# if launching a VC as well - send the BN looking nowhere for validators/secrets
# TODO use `start` (or something else) on windows (instead of `&`) for the 2 processes
let vcCommand = if validatorClientBinary == "": "" else:
&"""
--secrets-dir={dataDir}/empty_dummy_folder
--validators-dir={dataDir}/empty_dummy_folder
& {validatorClientBinary}
--rpc-port={baseRpcPort + nodeID}
--data-dir="{dataDir}"
{logLevelOpt}
"""
cmd = replace(&"""{beaconNodeBinary}
--data-dir="{dataDir}"
--dump
--web3-url={web3Url}
--nat={natConfig}
--tcp-port=""" & $(basePort + nodeID) & &"""
--udp-port=""" & $(basePort + nodeID) & &"""
--tcp-port={basePort + nodeID}
--udp-port={basePort + nodeID}
--metrics
--metrics-address={metricsAddress}
--metrics-port=""" & $(baseMetricsPort + nodeID) & &"""
--metrics-port={baseMetricsPort + nodeID}
--rpc
--rpc-address={rpcAddress}
--rpc-port=""" & $(baseRpcPort + nodeID) & &"""
--rpc-port={baseRpcPort + nodeID}
{bootstrapFileOpt}
{logLevelOpt}
{depositContractOpt}
{genesisFileOpt} """, "\n", " ")
{genesisFileOpt}
{extraBeaconNodeOptions}
{vcCommand} """, "\n", " ")
execIgnoringExitCode cmd
cli do (skipGoerliKey {.
@ -138,24 +152,14 @@ cli do (skipGoerliKey {.
basePort {.
desc: "Base TCP/UDP port (nodeID will be added to it)" .} = 9000.int,
metricsAddress {.
desc: "Listening address of the metrics server"
name: "metrics-address" .} = "127.0.0.1",
baseMetricsPort {.
desc: "Base metrics port (nodeID will be added to it)" .} = 8008.int,
rpcAddress {.
desc: "Listening address of the RPC server"
name: "rpc-address" .} = "127.0.0.1",
baseRpcPort {.
desc: "Base rpc port (nodeID will be added to it)" .} = 9190.int,
natConfig {.
desc: "Specify method to use for determining public address. " &
"Must be one of: any, none, upnp, pmp, extip:<IP>",
name: "nat" .} = "any",
extraBeaconNodeOptions {.
desc: "Extra options to pass to our beacon_node instance" .} = "",
writeLogFile {.
desc: "Write a log file in dataDir" .} = true,
@ -169,6 +173,9 @@ cli do (skipGoerliKey {.
runOnly {.
desc: "Just run it." .} = false,
separateVC {.
desc: "use a separate validator client process." .} = false,
printCmdOnly {.
desc: "Just print the commands (suitable for passing to 'eval'; might replace current shell)." .} = false,
@ -232,6 +239,11 @@ cli do (skipGoerliKey {.
doAssert specVersion in ["v0.11.3", "v0.12.1"]
if defined(windows) and separateVC:
# TODO use `start` (or something else) on windows (instead of `&`) for the 2 processes
echo "Cannot use a separate validator client process on Windows! (remove --separateVC)"
quit(1)
let
dataDirName = testnetName.replace("/", "_")
.replace("(", "_")
@ -240,12 +252,17 @@ cli do (skipGoerliKey {.
validatorsDir = dataDir / "validators"
secretsDir = dataDir / "secrets"
beaconNodeBinary = buildDir / "beacon_node_" & dataDirName
# using a separate VC is disabled on windows until we find a substitute for `&`
validatorClientBinary = if separateVC: buildDir / "validator_client_" & dataDirName else: ""
var
nimFlags = &"-d:chronicles_log_level=TRACE " & getEnv("NIM_PARAMS")
nimFlagsBN = &"-d:chronicles_log_level=TRACE " & getEnv("NIM_PARAMS")
nimFlagsVC = nimFlagsBN
if writeLogFile:
# write the logs to a file
nimFlags.add """ -d:"chronicles_sinks=textlines,json[file(nbc""" & staticExec("date +\"%Y%m%d%H%M%S\"") & """.log)]" """
let logFileNimParams = """ -d:"chronicles_sinks=textlines,json[file(placeholder_""" & staticExec("date +\"%Y%m%d%H%M%S\"") & """.log)]" """
nimFlagsBN.add logFileNimParams.replace("placeholder_", "nbc_bn_")
nimFlagsVC.add logFileNimParams.replace("placeholder_", "nbc_vc_")
let depositContractFile = testnetDir / depositContractFileName
if system.fileExists(depositContractFile):
@ -260,12 +277,16 @@ cli do (skipGoerliKey {.
if doBuild:
makePrometheusConfig(nodeID, baseMetricsPort, dataDir)
buildNode(nimFlags, preset, beaconNodeBinary)
buildBinary(nimFlagsBN, preset, beaconNodeBinary, "beacon_node.nim")
buildBinary(nimFlagsVC, preset, validatorClientBinary, "validator_client.nim")
if doBecomeValidator and depositContractOpt.len > 0 and not system.dirExists(validatorsDir):
becomeValidator(validatorsDir, beaconNodeBinary, secretsDir, depositContractOpt, privateGoerliKey, becomeValidatorOnly)
echo &"extraBeaconNodeOptions = '{extraBeaconNodeOptions}'"
if doRun:
runNode(dataDir, beaconNodeBinary, bootstrapFileOpt, depositContractOpt,
genesisFileOpt, natConfig, metricsAddress, rpcAddress, basePort,
nodeID, baseMetricsPort, baseRpcPort, printCmdOnly)
runNode(dataDir, beaconNodeBinary, validatorClientBinary, bootstrapFileOpt,
depositContractOpt, genesisFileOpt, extraBeaconNodeOptions,
basePort, nodeID, baseMetricsPort, baseRpcPort,
printCmdOnly)

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2018-2020 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -7,8 +7,4 @@
func round_multiple_down*(x: uint64, n: uint64): uint64 {.inline.} =
## Round the input to the previous multiple of "n"
result = x - x mod n
func round_multiple_up*(x: uint64, n: uint64): uint64 {.inline.} =
## Round the input to the next multiple of "n"
result = ((x + n - 1) div n) * n
x - x mod n

View File

@ -112,7 +112,7 @@ proc mockAttestation*(
flags: UpdateFlags = {}): Attestation {.inline.}=
mockAttestationImpl(state, slot, flags)
proc fillAggregateAttestation*(state: BeaconState, attestation: var Attestation) =
func fillAggregateAttestation*(state: BeaconState, attestation: var Attestation) =
var cache = get_empty_per_epoch_cache()
let beacon_committee = get_beacon_committee(
state,

View File

@ -20,11 +20,11 @@ proc initGenesisState*(num_validators: uint64, genesis_time: uint64 = 0): Hashed
let deposits = mockGenesisBalancedDeposits(
validatorCount = num_validators,
amountInEth = 32, # We create canonical validators with 32 Eth
flags = {skipBlsValidation}
flags = {}
)
initialize_hashed_beacon_state_from_eth1(
defaultRuntimePreset, eth1BlockHash, 0, deposits, {skipBlsValidation})
defaultRuntimePreset, eth1BlockHash, 0, deposits, {})
when isMainModule:
# Smoke test

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2018-2020 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -9,20 +9,38 @@
# ---------------------------------------------------------------
import
# Specs
bearssl, eth/keys,
blscurve/bls_signature_scheme,
../../beacon_chain/spec/[datatypes, crypto, presets]
proc newKeyPair(rng: var BrHmacDrbgContext): BlsResult[tuple[pub: ValidatorPubKey, priv: ValidatorPrivKey]] =
## Generates a new public-private keypair
## This requires entropy on the system
# The input-keying-material requires 32 bytes at least for security
# The generation is deterministic and the input-keying-material
# must be protected against side-channel attacks
var ikm: array[32, byte]
brHmacDrbgGenerate(rng, ikm)
var
sk: SecretKey
pk: bls_signature_scheme.PublicKey
if keyGen(ikm, pk, sk):
ok((ValidatorPubKey(kind: Real, blsValue: pk), ValidatorPrivKey(sk)))
else:
err "bls: cannot generate keypair"
# this is being indexed inside "mock_deposits.nim" by a value up to `validatorCount`
# which is `num_validators` which is `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT`
proc genMockPrivKeys(privkeys: var openarray[ValidatorPrivKey]) =
let rng = newRng()
for i in 0 ..< privkeys.len:
let pair = newKeyPair()[]
let pair = newKeyPair(rng[])[]
privkeys[i] = pair.priv
proc genMockPubKeys(
pubkeys: var openarray[ValidatorPubKey],
privkeys: openarray[ValidatorPrivKey]
) =
func genMockPubKeys(pubkeys: var openarray[ValidatorPubKey],
privkeys: openarray[ValidatorPrivKey]) =
for i in 0 ..< privkeys.len:
pubkeys[i] = toPubKey(privkeys[i])

View File

@ -10,6 +10,8 @@
import
# Standard library
os, unittest,
# Utilities
stew/results,
# Beacon chain internals
../../beacon_chain/spec/[datatypes, beaconstate, validator],
../../beacon_chain/ssz,
@ -44,12 +46,12 @@ proc runTest(identifier: string) =
if existsFile(testDir/"post.ssz"):
let postState = newClone(parseTest(testDir/"post.ssz", SSZ, BeaconState))
let done = process_attestation(preState[], attestation, {}, cache)
let done = process_attestation(preState[], attestation, {}, cache).isOk
doAssert done, "Valid attestation not processed"
check: preState[].hash_tree_root() == postState[].hash_tree_root()
reportDiff(preState, postState)
else:
let done = process_attestation(preState[], attestation, {}, cache)
let done = process_attestation(preState[], attestation, {}, cache).isOk
doAssert done == false, "We didn't expect this invalid attestation to be processed."
`testImpl _ operations_attestations _ identifier`()

View File

@ -10,6 +10,8 @@
import
# Standard library
os, unittest,
# Utilities
stew/results,
# Beacon chain internals
../../beacon_chain/spec/[datatypes, state_transition_block, validator],
../../beacon_chain/ssz,
@ -45,13 +47,13 @@ proc runTest(identifier: string) =
if existsFile(testDir/"post.ssz"):
let postState = newClone(parseTest(testDir/"post.ssz", SSZ, BeaconState))
let done = process_attester_slashing(preState[], attesterSlashing,
{}, cache)
{}, cache).isOk
doAssert done, "Valid attestater slashing not processed"
check: preState[].hash_tree_root() == postState[].hash_tree_root()
reportDiff(preState, postState)
else:
let done = process_attester_slashing(preState[], attesterSlashing,
{}, cache)
{}, cache).isOk
doAssert done == false, "We didn't expect this invalid attester slashing to be processed."
`testImpl _ operations_attester_slashing _ identifier`()

View File

@ -10,6 +10,8 @@
import
# Standard library
os, unittest,
# Utilities
stew/results,
# Beacon chain internals
../../beacon_chain/spec/[datatypes, state_transition_block, validator],
../../beacon_chain/ssz,
@ -44,12 +46,12 @@ proc runTest(identifier: string) =
if existsFile(testDir/"post.ssz"):
let postState = newClone(parseTest(testDir/"post.ssz", SSZ, BeaconState))
let done = process_block_header(preState[], blck, {}, cache)
let done = process_block_header(preState[], blck, {}, cache).isOk
doAssert done, "Valid block header not processed"
check: preState[].hash_tree_root() == postState[].hash_tree_root()
reportDiff(preState, postState)
else:
let done = process_block_header(preState[], blck, {}, cache)
let done = process_block_header(preState[], blck, {}, cache).isOk
doAssert done == false, "We didn't expect this invalid block header to be processed."
`testImpl _ blockheader _ identifier`()

View File

@ -10,6 +10,8 @@
import
# Standard library
os, unittest,
# Utilities
stew/results,
# Beacon chain internals
../../beacon_chain/spec/[datatypes, beaconstate, presets],
../../beacon_chain/[ssz, extras],
@ -48,7 +50,7 @@ proc runTest(identifier: string) =
discard process_deposit(defaultRuntimePreset, preState[], deposit, flags)
reportDiff(preState, postState)
else:
check not process_deposit(defaultRuntimePreset, preState[], deposit, flags)
check process_deposit(defaultRuntimePreset, preState[], deposit, flags).isErr
`testImpl _ operations_deposits _ identifier`()

View File

@ -10,6 +10,8 @@
import
# Standard library
os, unittest,
# Utilities
stew/results,
# Beacon chain internals
../../beacon_chain/spec/[datatypes, state_transition_block, validator],
../../beacon_chain/ssz,
@ -44,12 +46,12 @@ proc runTest(identifier: string) =
if existsFile(testDir/"post.ssz"):
let postState = newClone(parseTest(testDir/"post.ssz", SSZ, BeaconState))
let done = process_proposer_slashing(preState[], proposerSlashing, {}, cache)
let done = process_proposer_slashing(preState[], proposerSlashing, {}, cache).isOk
doAssert done, "Valid proposer slashing not processed"
check: preState[].hash_tree_root() == postState[].hash_tree_root()
reportDiff(preState, postState)
else:
let done = process_proposer_slashing(preState[], proposerSlashing, {}, cache)
let done = process_proposer_slashing(preState[], proposerSlashing, {}, cache).isOk
doAssert done == false, "We didn't expect this invalid proposer slashing to be processed."
`testImpl_proposer_slashing _ identifier`()

View File

@ -10,6 +10,8 @@
import
# Standard library
os, unittest,
# Utilities
stew/results,
# Beacon chain internals
../../beacon_chain/spec/[datatypes, state_transition_block],
../../beacon_chain/ssz,
@ -42,12 +44,12 @@ proc runTest(identifier: string) =
if existsFile(testDir/"post.ssz"):
let postState = newClone(parseTest(testDir/"post.ssz", SSZ, BeaconState))
let done = process_voluntary_exit(preState[], voluntaryExit, {})
let done = process_voluntary_exit(preState[], voluntaryExit, {}).isOk
doAssert done, "Valid voluntary exit not processed"
check: preState[].hash_tree_root() == postState[].hash_tree_root()
reportDiff(preState, postState)
else:
let done = process_voluntary_exit(preState[], voluntaryExit, {})
let done = process_voluntary_exit(preState[], voluntaryExit, {}).isOk
doAssert done == false, "We didn't expect this invalid voluntary exit to be processed."
`testImpl _ voluntary_exit _ identifier`()

View File

@ -14,6 +14,7 @@
import
# Standard library
unittest,
stew/results,
# Specs
../../beacon_chain/spec/[beaconstate, datatypes, helpers, validator],
# Mock helpers
@ -50,7 +51,7 @@ suiteReport "[Unit - Spec - Block processing] Attestations " & preset():
var cache = get_empty_per_epoch_cache()
check process_attestation(
state.data, attestation, flags = {}, cache
)
).isOk
# Check that the attestation was processed
if attestation.data.target.epoch == get_current_epoch(state.data):

View File

@ -30,7 +30,6 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
doAssert genesisState.data.validators.len == int NumValidators
template valid_deposit(deposit_amount: uint64, name: string): untyped =
# TODO: BLS signature
timedTest "Deposit " & name & " MAX_EFFECTIVE_BALANCE balance (" &
$(MAX_EFFECTIVE_BALANCE div 10'u64^9) & " ETH)":
var state = assignClone(genesisState[])
@ -42,7 +41,7 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
state.data,
uint64 validator_index,
deposit_amount,
flags = {skipBlsValidation}
flags = {}
)
# Params for sanity checks
@ -55,7 +54,7 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
# State transition
# ----------------------------------------
check: process_deposit(defaultRuntimePreset(), state.data, deposit, {skipBlsValidation})
check: process_deposit(defaultRuntimePreset(), state.data, deposit, {}).isOk
# Check invariants
# ----------------------------------------
@ -84,7 +83,7 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
state.data,
uint64 validator_index,
deposit_amount,
flags = {skipBlsValidation}
flags = {}
)
# Params for sanity checks
@ -97,7 +96,7 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
# State transition
# ----------------------------------------
check: process_deposit(defaultRuntimePreset(), state.data, deposit, {skipBlsValidation})
check: process_deposit(defaultRuntimePreset(), state.data, deposit, {}).isOk
# Check invariants
# ----------------------------------------
@ -111,8 +110,39 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
EFFECTIVE_BALANCE_INCREMENT
)
template invalid_signature(deposit_amount: uint64, name: string): untyped =
timedTest "Invalid deposit " & name & " MAX_EFFECTIVE_BALANCE balance (" &
$(MAX_EFFECTIVE_BALANCE div 10'u64^9) & " ETH)":
var state = assignClone(genesisState[])
# Test configuration
# ----------------------------------------
let validator_index = state.data.validators.len
var deposit = mockUpdateStateForNewDeposit(
state.data,
uint64 validator_index,
deposit_amount,
flags = {skipBlsValidation}
)
# Params for sanity checks
# ----------------------------------------
let pre_val_count = state.data.validators.len
# State transition
# ----------------------------------------
check:
process_deposit(defaultRuntimePreset(), state.data, deposit, {}).isOk
# Check invariants
# ----------------------------------------
check:
state.data.validators.len == pre_val_count
state.data.balances.len == pre_val_count
invalid_signature(MAX_EFFECTIVE_BALANCE, "at")
# TODO, tests with:
# - invalid BLS signature
# - invalid withdrawal credential
# - invalid deposit root
# - invalid merkle proof

View File

@ -9,7 +9,7 @@
import
unittest, ./testutil, json,
stew/byteutils, blscurve,
stew/byteutils, blscurve, eth/keys,
../beacon_chain/spec/[crypto, keystore]
from strutils import replace
@ -86,6 +86,9 @@ const
salt = hexToSeqByte("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
iv = hexToSeqByte("264daa3f303d7259501c93d997d84fe6")
let
rng = newRng()
suiteReport "Keystore":
setup:
let secret = ValidatorPrivKey.fromRaw(secretBytes).get
@ -97,7 +100,7 @@ suiteReport "Keystore":
check secret == decrypt.get()
timedTest "Pbkdf2 encryption":
let encrypt = encryptKeystore(KdfPbkdf2, secret,
let encrypt = encryptKeystore(KdfPbkdf2, rng[], secret,
KeyStorePass password,
salt=salt, iv=iv,
path = validateKeyPath "m/12381/60/0/0")
@ -111,10 +114,10 @@ suiteReport "Keystore":
timedTest "Pbkdf2 errors":
expect Defect:
echo encryptKeystore(KdfPbkdf2, secret, salt = [byte 1]).string
echo encryptKeystore(KdfPbkdf2, rng[], secret, salt = [byte 1]).string
expect Defect:
echo encryptKeystore(KdfPbkdf2, secret, iv = [byte 1]).string
echo encryptKeystore(KdfPbkdf2, rng[], secret, iv = [byte 1]).string
check decryptKeystore(KeyStoreContent pbkdf2Vector,
KeyStorePass "wrong pass").isErr

View File

@ -80,7 +80,7 @@ suiteReport "PeerPool testing suite":
doAssert(fut1.finished == false)
doAssert(fut2.finished == false)
peer0.close()
await sleepAsync(100.milliseconds)
await sleepAsync(10.milliseconds)
doAssert(fut1.finished == false)
doAssert(fut2.finished == true and fut2.failed == false)
result = true
@ -102,11 +102,11 @@ suiteReport "PeerPool testing suite":
doAssert(fut2.finished == false)
doAssert(fut3.finished == false)
peer0.close()
await sleepAsync(100.milliseconds)
await sleepAsync(10.milliseconds)
doAssert(fut2.finished == true and fut2.failed == false)
doAssert(fut3.finished == false)
peer1.close()
await sleepAsync(100.milliseconds)
await sleepAsync(10.milliseconds)
doAssert(fut3.finished == true and fut3.failed == false)
result = true
@ -128,18 +128,61 @@ suiteReport "PeerPool testing suite":
doAssert(fut2.finished == true and fut2.failed == false)
doAssert(fut3.finished == false)
peer0.close()
await sleepAsync(100.milliseconds)
await sleepAsync(10.milliseconds)
doAssert(fut1.finished == true and fut1.failed == false)
doAssert(fut3.finished == false)
peer2.close()
await sleepAsync(100.milliseconds)
await sleepAsync(10.milliseconds)
doAssert(fut3.finished == true and fut3.failed == false)
result = true
proc testAddPeer4(): Future[bool] {.async.} =
var pool = newPeerPool[PeerTest, PeerTestID](maxPeers = 3)
var peer0 = PeerTest.init("idInc0")
var peer1 = PeerTest.init("idInc1")
var peer2 = PeerTest.init("idOut0")
var peer3 = PeerTest.init("idOut1")
var peer4 = PeerTest.init("idOut2")
var peer5 = PeerTest.init("idInc2")
var fut0 = pool.addIncomingPeer(peer0)
var fut1 = pool.addIncomingPeer(peer1)
var fut2 = pool.addOutgoingPeer(peer2)
var fut3 = pool.addOutgoingPeer(peer3)
var fut4 = pool.addOutgoingPeer(peer4)
var fut5 = pool.addIncomingPeer(peer5)
doAssert(fut0.finished == true and fut0.failed == false)
doAssert(fut1.finished == true and fut1.failed == false)
doAssert(fut2.finished == true and fut2.failed == false)
doAssert(fut3.finished == false)
doAssert(fut4.finished == false)
doAssert(fut5.finished == false)
await sleepAsync(10.milliseconds)
doAssert(fut3.finished == false)
doAssert(fut4.finished == false)
doAssert(fut5.finished == false)
peer0.close()
await sleepAsync(10.milliseconds)
doAssert(fut3.finished == true and fut3.failed == false)
doAssert(fut4.finished == false)
doAssert(fut5.finished == false)
peer1.close()
await sleepAsync(10.milliseconds)
doAssert(fut4.finished == true and fut4.failed == false)
doAssert(fut5.finished == false)
peer2.close()
await sleepAsync(10.milliseconds)
doAssert(fut5.finished == true and fut5.failed == false)
result = true
check:
waitFor(testAddPeer1()) == true
waitFor(testAddPeer2()) == true
waitFor(testAddPeer3()) == true
waitFor(testAddPeer4()) == true
timedTest "Acquire from empty pool":
var pool0 = newPeerPool[PeerTest, PeerTestID]()
@ -399,7 +442,7 @@ suiteReport "PeerPool testing suite":
proc testConsumer() {.async.} =
var p = await pool.acquire()
await sleepAsync(100.milliseconds)
await sleepAsync(10.milliseconds)
pool.release(p)
proc testClose(): Future[bool] {.async.} =

View File

@ -9,7 +9,7 @@
import
unittest, options, json_serialization,
nimcrypto, serialization/testing/generic_suite,
serialization/testing/generic_suite,
./testutil,
../beacon_chain/spec/[datatypes, digest],
../beacon_chain/ssz, ../beacon_chain/ssz/[navigator, dynamic_navigator]

2
vendor/nim-bearssl vendored

@ -1 +1 @@
Subproject commit 68c6d27304245c948526487b37e10951acf7dbc8
Subproject commit 33b2303fc3b64359970b77bb09274c3e012ff37f

2
vendor/nim-eth vendored

@ -1 +1 @@
Subproject commit bfda38ba82d73ed3f7bf41a41a51d69ba1c2cbe5
Subproject commit 484fbcab1b25072b4c45f496a88d361fc9479be4

2
vendor/nim-libp2p vendored

@ -1 +1 @@
Subproject commit b49c619ca851ec6e832243b633efee4c57af5e40
Subproject commit d522537b19a532bc4af94fcd146f779c1f23bad0

@ -1 +1 @@
Subproject commit be989635994377e0e421e4a039230098ba5ccd28
Subproject commit bf6cc94a3cbab16cf7ffadad11b50c52f161d0a8

2
vendor/nim-web3 vendored

@ -1 +1 @@
Subproject commit 227927ddc80e7d6bd432f70fe8c803286b46e770
Subproject commit dd132ba024fd8784aab7b5c306c4ec61c86e8613