From f7e876471e9896dd8cb3e75f6f50985927f0741b Mon Sep 17 00:00:00 2001 From: mratsim Date: Mon, 3 Sep 2018 18:42:22 +0200 Subject: [PATCH] initial impl of fork choice rules. Main types sorted out --- beacon_chain/datatypes.nim | 20 +-- .../fork_choice_rule/fork_choice_rule.nim | 122 ++++++++++++++++++ beacon_chain/fork_choice_rule/networksim.nim | 21 +++ nim.cfg | 5 + 4 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 beacon_chain/fork_choice_rule/fork_choice_rule.nim create mode 100644 beacon_chain/fork_choice_rule/networksim.nim create mode 100644 nim.cfg diff --git a/beacon_chain/datatypes.nim b/beacon_chain/datatypes.nim index d219c83f8..5e88274ea 100644 --- a/beacon_chain/datatypes.nim +++ b/beacon_chain/datatypes.nim @@ -15,17 +15,17 @@ type Uint24* = range[0'u32 .. 0xFFFFFF'u32] # TODO: wrap-around BeaconBlock* = object - parent_hash*: Blake2_256_Digest # Hash of the parent block + parent_hash*: Blake2_256_Digest # Hash of the parent block slot_number*: int64 # Slot number (for the PoS mechanism) - randao_reveal*: Blake2_256_Digest # Randao commitment reveal + randao_reveal*: Blake2_256_Digest # Randao commitment reveal attestations*: seq[AttestationRecord] # Attestation votes - pow_chain_ref*: Blake2_256_Digest # Reference to main chain block + pow_chain_ref*: Blake2_256_Digest # Reference to main chain block active_state_root*: Blake2_256_Digest # Hash of the active state crystallized_state_root*: Blake2_256_Digest # Hash of the crystallized state ActiveState* = object pending_attestations*: seq[AttestationRecord] # Attestations that have not yet been processed - recent_block_hashes*: seq[Blake2_256_Digest] # Most recent 2 * CYCLE_LENGTH block hashes, older to newer + recent_block_hashes*: seq[Blake2_256_Digest] # Most recent 2 * CYCLE_LENGTH block hashes, older to newer CrystallizedState* = object validators*: seq[ValidatorRecord] # List of active validators @@ -41,7 +41,7 @@ type crosslinking_start_shard*: int16 # The next shard that cross-linking assignment will start from crosslink_records*: seq[CrosslinkRecord] # Records about the most recent crosslink for each shard total_deposits*: Int256 # Total balance of deposits - dynasty_seed*: Blake2_256_Digest # Used to select the committees for each shard + dynasty_seed*: Blake2_256_Digest # Used to select the committees for each shard dynasty_seed_last_reset*: int64 # Last epoch the crosslink seed was reset ShardAndCommittee* = object @@ -52,16 +52,16 @@ type pubkey*: BLSPublicKey # The validator's public key withdrawal_shard*: int16 # What shard the validator's balance will be sent to after withdrawal withdrawal_address*: EthAddress # And what address - randao_commitment*: Blake2_256_Digest # The validator's current RANDAO beacon commitment + randao_commitment*: Blake2_256_Digest # The validator's current RANDAO beacon commitment balance*: int64 # Current balance start_dynasty*: int64 # Dynasty where the validator is inducted end_dynasty*: int64 # Dynasty where the validator leaves CrosslinkRecord* = object dynasty: int64 # What dynasty the crosslink was submitted in - hash: Blake2_256_Digest # The block hash + hash: Blake2_256_Digest # The block hash - BLSPublicKey = object + BLSPublicKey* = object # Stub for BLS signature data: array[32, byte] @@ -71,7 +71,7 @@ type oblique_parent_hashes*: seq[Blake2_256_Digest] # List of block hashes that this signature is signing over that # are NOT part of the current chain, in order of oldest to newest - shard_block_hash*: Blake2_256_Digest # Block hash in the shard that we are attesting to + shard_block_hash*: Blake2_256_Digest # Block hash in the shard that we are attesting to attester_bitfield*: IntSet # Who is participating aggregateSig*: seq[BLSPublicKey] # The actual signature # Note: @@ -91,7 +91,7 @@ type const SHARD_COUNT* = 1024 # a constant referring to the number of shards - DEPOSITE_SIZE* = 32 # You need to deposit 32 ETH to be a validator in Casper + DEPOSIT_SIZE* = 32 # You need to deposit 32 ETH to be a validator in Casper MAX_VALIDATOR_COUNT* = 2^22 # 4_194_304, this means that ~132M ETH can stake at the same time (= MaxValidator Count * DepositSize) SLOT_DURATION* = 8 # seconds CYCLE_LENGTH* = 64 # slots diff --git a/beacon_chain/fork_choice_rule/fork_choice_rule.nim b/beacon_chain/fork_choice_rule/fork_choice_rule.nim new file mode 100644 index 000000000..53a6ba72f --- /dev/null +++ b/beacon_chain/fork_choice_rule/fork_choice_rule.nim @@ -0,0 +1,122 @@ +# beacon_chain +# Copyright (c) 2018 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +# A port of https://github.com/ethereum/research/blob/master/clock_disparity/ghost_node.py +# Specs: https://ethresear.ch/t/beacon-chain-casper-ffg-rpj-mini-spec/2760 +# Part of Casper+Sharding chain v2.1: https://notes.ethereum.org/SCIg8AH5SA-O4C1G1LYZHQ# + +import + tables, deques, # Stdlib + nimcrypto, # Nimble packages + # ../datatypes, # BeaconBlock is still different from the simulation blocks + ./networksim # From repo + +import hashes +func hash(x: MDigest): Hash = + # Allow usage of MDigest in hashtables + const bytes = x.type.bits div 8 + result = x.unsafeAddr.hashData(bytes) + +const + NOTARIES = 100 # Committee size in Casper v2.1 + SLOT_SIZE = 6 # Slot duration in Casper v2.1 + EPOCH_LENGTH = 25 # Cycle length inCasper v2. + + # TODO, clear up if reference semantics are needed + # for the tables, Block and Sig + +type + Block = ref object + contents: array[32, byte] + parent_hash: MDigest[256] + hash: MDigest[256] + height: int # slot in Casper v2.1 spec + proposer: int64 + slot: int64 + +func min_timestamp(self: Block): int64 = + SLOT_SIZE * self.slot + +let Genesis = Block() + +type + Sig = object + # TODO: unsure if this is still relevant in Casper v2.1 + proposer: int64 # the validator that creates a block + targets: seq[MDigest[256]] # the hash of blocks proposed + slot: int64 # slot number + timestamp: int64 # ts in the ref implementation + hash: MDigest[384] # The signature (BLS12-384) + +type + Node = ref object + + blocks: TableRef[MDigest[256], Block] + sigs: TableRef[MDigest[384], Sig] + main_chain: seq[MDigest[256]] + timequeue: seq[Block] + parentqueue: TableRef[MDigest[256], Node] + children: TableRef[MDigest[256], seq[MDigest[256]]] + scores: TableRef[MDigest[256], int] + scores_at_height: TableRef[MDigest[256], int] # Should be slot not height in v2.1 + justified: TableRef[MDigest[256], bool] + finalized: TableRef[MDigest[256], bool] + timestamp: int64 + id: int64 + network: NetworkSimulator + used_parents: TableRef[MDigest[256], Node] + processed: TableRef[MDigest[256], Block] + sleepy: bool + careless: bool + first_round: bool + last_made_block: int64 + last_made_sig: int64 + +proc log(self: Node, words: string, lvl = 3, all = false) = + if (self.id == 0 or all) and lvl >= 2: + echo self.id, words + +func add_to_timequeue(self: Node, obj: Block) = + var i = 0 + while i < self.timequeue.len and self.timequeue[i].min_timestamp < obj.min_timestamp: + inc i + self.timequeue.insert(obj, i) + +func add_to_multiset[K, V](self: Node, multiset: var TableRef[K, V], k: K, v: V) = + if k notin multiset: + multiset[k] = @[] + multiset[k].add v + +func change_head(self: Node, chain: var seq[MDigest[256]], new_head: Block) = + chain.add newSeq[MDigest[256]](new_head.height + 1 - chain.len) + var (i, c) = (new_head.height, new_head.hash) + while c != chain[i]: + chain[i] = c + c = self.blocks[c].parent_hash + dec i + for idx, val in chain: + doAssert self.blocks[val].height == idx + +func recalculate_head(self: Node) = + while true: + var + descendant_queue = initDeque[MDigest[256]]() + new_head: MDigest[256] + max_count = 0 + descendant_queue.addFirst self.main_chain[^1] + while descendant_queue.len != 0: + let first = descendant_queue.popFirst() + if first in self.children: + for c in self.children[first]: + descendant_queue.addLast c + if self.scores.getOrDefault(first, 0) > max_count and first != self.main_chain[^1]: + new_head = first + max_count = self.scores.getOrDefault(first, 0) + if new_head != MDigest[256](): # != default init, a 32-byte array of 0 + self.change_head(self.main_chain, self.blocks[new_head]) + else: + return diff --git a/beacon_chain/fork_choice_rule/networksim.nim b/beacon_chain/fork_choice_rule/networksim.nim new file mode 100644 index 000000000..98d7b4975 --- /dev/null +++ b/beacon_chain/fork_choice_rule/networksim.nim @@ -0,0 +1,21 @@ +# beacon_chain +# Copyright (c) 2018 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +# A port of https://github.com/ethereum/research/blob/master/clock_disparity/ghost_node.py +# Specs: https://ethresear.ch/t/beacon-chain-casper-ffg-rpj-mini-spec/2760 +# Part of Casper+Sharding chain v2.1: https://notes.ethereum.org/SCIg8AH5SA-O4C1G1LYZHQ# + +import tables + +type + NetworkSimulator* = ref object + agents*: seq[int] + latency_distribution_sample*: seq[int] + time*: int64 + objqueue*: Table[int, int] + peers*: Table[int, int] + reliability*: float diff --git a/nim.cfg b/nim.cfg new file mode 100644 index 000000000..de3bff039 --- /dev/null +++ b/nim.cfg @@ -0,0 +1,5 @@ +# https://github.com/nim-lang/Nim/issues/8691#issuecomment-414662209 +# Doesn't work unfortunately +@if nimHasForLoopMacros: +--experimental:forLoopMacros +@end