Merge devel and resolve the conflicts
This commit is contained in:
commit
318b225ccd
|
@ -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
|
||||
|
|
32
.travis.yml
32
.travis.yml
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
}
|
||||
},
|
||||
|
|
17
Makefile
17
Makefile
|
@ -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
|
||||
|
|
|
@ -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)).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 -
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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, @[])
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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" &
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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] +
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
book
|
|
@ -0,0 +1,6 @@
|
|||
[book]
|
||||
authors = ["Mamy André-Ratsimbazafy"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "The Auditors Handbook to Nimbus Beacon Chain"
|
|
@ -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.
|
|
@ -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)
|
|
@ -0,0 +1,3 @@
|
|||
# Pointer manipulation
|
||||
|
||||
https://github.com/status-im/nim-stew/blob/master/stew/ptrops.nim
|
|
@ -0,0 +1,3 @@
|
|||
# Emitting raw C or Assembly code
|
||||
|
||||
TODO
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
|||
# Nim FAQ
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
# Builtin types
|
||||
|
||||
TODO
|
|
@ -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.
|
|
@ -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/)
|
|
@ -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]
|
||||
```
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
# Runtime types, variants, object-oriented programming
|
||||
TODO
|
|
@ -0,0 +1,2 @@
|
|||
# Compile-time evaluation
|
||||
TODO
|
|
@ -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)
|
|
@ -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.
|
|
@ -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.
|
|
@ -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)
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
# Nim standard library use in Nimbus
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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/)
|
|
@ -0,0 +1,2 @@
|
|||
# Serialization
|
||||
|
|
@ -0,0 +1 @@
|
|||
# Async/Await with the Chronos library
|
|
@ -0,0 +1 @@
|
|||
# Cryptography
|
|
@ -0,0 +1 @@
|
|||
# Ethereum Networking
|
|
@ -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) -->
|
|
@ -1,5 +1,5 @@
|
|||
[book]
|
||||
authors = ["Lee Ting Ting"]
|
||||
[book]
|
||||
authors = ["Lee Ting Ting", "Jacek Sieka"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
|
@ -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 |
|
@ -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).
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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 ⚠️"
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
|
@ -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`()
|
||||
|
|
|
@ -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`()
|
||||
|
|
|
@ -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`()
|
||||
|
|
|
@ -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`()
|
||||
|
||||
|
|
|
@ -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`()
|
||||
|
|
|
@ -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`()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.} =
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 68c6d27304245c948526487b37e10951acf7dbc8
|
||||
Subproject commit 33b2303fc3b64359970b77bb09274c3e012ff37f
|
|
@ -1 +1 @@
|
|||
Subproject commit bfda38ba82d73ed3f7bf41a41a51d69ba1c2cbe5
|
||||
Subproject commit 484fbcab1b25072b4c45f496a88d361fc9479be4
|
|
@ -1 +1 @@
|
|||
Subproject commit b49c619ca851ec6e832243b633efee4c57af5e40
|
||||
Subproject commit d522537b19a532bc4af94fcd146f779c1f23bad0
|
|
@ -1 +1 @@
|
|||
Subproject commit be989635994377e0e421e4a039230098ba5ccd28
|
||||
Subproject commit bf6cc94a3cbab16cf7ffadad11b50c52f161d0a8
|
|
@ -1 +1 @@
|
|||
Subproject commit 227927ddc80e7d6bd432f70fe8c803286b46e770
|
||||
Subproject commit dd132ba024fd8784aab7b5c306c4ec61c86e8613
|
Loading…
Reference in New Issue