# Nimbus # Copyright (c) 2018 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or # http://opensource.org/licenses/MIT) # at your option. This file may not be copied, modified, or distributed except # according to those terms. ## ## Snapshot Structure for Clique PoA Consensus Protocol ## ==================================================== ## ## For details see ## `EIP-225 `_ ## and ## `go-ethereum `_ ## import std/[algorithm, sequtils, strformat, strutils, tables], ../../../db/storage_types, ../clique_cfg, ../clique_defs, ../clique_helpers, ./ballot, chronicles, eth/[common, rlp, trie/db], stew/results type SnapshotResult* = ##\ ## Snapshot/error result type Result[Snapshot,CliqueError] AddressHistory* = Table[BlockNumber,EthAddress] SnapshotData* = object blockNumber: BlockNumber ## block number where snapshot was created on blockHash: Hash256 ## block hash where snapshot was created on recents: AddressHistory ## recent signers for spam protections # clique/snapshot.go(58): Recents map[uint64]common.Address [..] ballot: Ballot ## Votes => authorised signers # clique/snapshot.go(50): type Snapshot struct [..] Snapshot* = ref object ## Snapshot is the state of the authorization ## voting at a given point in time. cfg: CliqueCfg ## parameters to fine tune behavior data*: SnapshotData ## real snapshot {.push raises: [Defect].} logScope: topics = "clique PoA snapshot" # ------------------------------------------------------------------------------ # Pretty printers for debugging # ------------------------------------------------------------------------------ proc getPrettyPrinters*(s: Snapshot): var PrettyPrinters {.gcsafe.} proc pp*(s: Snapshot; v: Vote): string {.gcsafe.} proc votesList(s: Snapshot; sep: string): string = proc s3Cmp(a, b: (string,string,Vote)): int = result = cmp(a[0], b[0]) if result == 0: result = cmp(a[1], b[1]) s.data.ballot.votesInternal .mapIt((s.pp(it[0]),s.pp(it[1]),it[2])) .sorted(cmp = s3Cmp) .mapIt(s.pp(it[2])) .join(sep) proc signersList(s: Snapshot): string = s.pp(s.data.ballot.authSigners).sorted.join(",") # ------------------------------------------------------------------------------ # Private functions needed to support RLP conversion # ------------------------------------------------------------------------------ proc append[K,V](rw: var RlpWriter; tab: Table[K,V]) = rw.startList(tab.len) for key,value in tab.pairs: rw.append((key,value)) proc read[K,V](rlp: var Rlp; Q: type Table[K,V]): Q {.raises: [Defect,CatchableError].} = for w in rlp.items: let (key,value) = w.read((K,V)) result[key] = value # ------------------------------------------------------------------------------ # Private constructor helper # ------------------------------------------------------------------------------ # clique/snapshot.go(72): func newSnapshot(config [..] proc initSnapshot(s: Snapshot; cfg: CliqueCfg; number: BlockNumber; hash: Hash256; signers: openArray[EthAddress]) = ## Initalise a new snapshot. s.cfg = cfg s.data.blockNumber = number s.data.blockHash = hash s.data.recents = initTable[BlockNumber,EthAddress]() s.data.ballot.initBallot(signers) s.data.ballot.debug = s.cfg.debug # ------------------------------------------------------------------------------ # Public pretty printers # ------------------------------------------------------------------------------ proc getPrettyPrinters*(s: Snapshot): var PrettyPrinters = ## Mixin for pretty printers s.cfg.prettyPrint proc pp*(s: Snapshot; h: var AddressHistory): string {.gcsafe.} = ppExceptionWrap: toSeq(h.keys) .sorted .mapIt("#" & $it & ":" & s.pp(h[it.u256])) .join(",") proc pp*(s: Snapshot; v: Vote): string = proc authorized(b: bool): string = if b: "authorise" else: "de-authorise" ppExceptionWrap: "(" & &"address={s.pp(v.address)}" & &",signer={s.pp(v.signer)}" & &",blockNumber={v.blockNumber}" & &",{authorized(v.authorize)}" & ")" proc pp*(s: Snapshot; delim: string): string {.gcsafe.} = ## Pretty print descriptor let sep1 = if 0 < delim.len: delim else: ";" sep2 = if 0 < delim.len and delim[0] == '\n': delim & ' '.repeat(7) else: ";" ppExceptionWrap: &"(blockNumber=#{s.data.blockNumber}" & &"{sep1}recents=" & "{" & s.pp(s.data.recents) & "}" & &"{sep1}signers=" & "{" & s.signersList & "}" & &"{sep1}votes=[" & s.votesList(sep2) & "])" proc pp*(s: Snapshot; indent = 0): string {.gcsafe.} = ## Pretty print descriptor let delim = if 0 < indent: "\n" & ' '.repeat(indent) else: " " s.pp(delim) # ------------------------------------------------------------------------------ # Public Constructor # ------------------------------------------------------------------------------ proc newSnapshot*(cfg: CliqueCfg; header: BlockHeader): Snapshot = ## Create a new snapshot for the given header. The header need not be on the ## block chain, yet. The trusted signer list is derived from the ## `extra data` field of the header. new result let signers = header.extraData.extraDataAddresses result.initSnapshot(cfg, header.blockNumber, header.blockHash, signers) # ------------------------------------------------------------------------------ # Public getters # ------------------------------------------------------------------------------ proc cfg*(s: Snapshot): CliqueCfg = ## Getter s.cfg proc blockNumber*(s: Snapshot): BlockNumber = ## Getter s.data.blockNumber proc blockHash*(s: Snapshot): Hash256 = ## Getter s.data.blockHash proc recents*(s: Snapshot): var AddressHistory = ## Retrieves the list of recently added addresses s.data.recents proc ballot*(s: Snapshot): var Ballot = ## Retrieves the ballot box descriptor with the votes s.data.ballot # ------------------------------------------------------------------------------ # Public setters # ------------------------------------------------------------------------------ proc `blockNumber=`*(s: Snapshot; number: BlockNumber) = ## Getter s.data.blockNumber = number proc `blockHash=`*(s: Snapshot; hash: Hash256) = ## Getter s.data.blockHash = hash # ------------------------------------------------------------------------------ # Public load/store support # ------------------------------------------------------------------------------ # clique/snapshot.go(88): func loadSnapshot(config [..] proc loadSnapshot*(cfg: CliqueCfg; hash: Hash256): Result[Snapshot,CliqueError] {.gcsafe, raises: [Defect].} = ## Load an existing snapshot from the database. var s = Snapshot(cfg: cfg) try: let rlpData = s.cfg.db.db.get(hash.cliqueSnapshotKey.toOpenArray) # The following check is only needed for Github/CI for 64bit Windows (not # reproducible on my local Win7 -- jordan). What normally happens when # `rlpData == @[]` holds is that the library function `eth.readImpl()` # throws an `RlpTypeMismatch` error as the inner function `isList()` fails. # On Github CI for 64bit Windows, the unit test crashes with a segmentation # fault. if rlpData.len == 0: return err((errSnapshotLoad,"")) s.data = rlpData.decode(SnapshotData) s.data.ballot.debug = s.cfg.debug except CatchableError as e: return err((errSnapshotLoad, $e.name & ": " & e.msg)) ok(s) # clique/snapshot.go(104): func (s *Snapshot) store(db [..] proc storeSnapshot*(s: Snapshot): CliqueOkResult {.gcsafe,raises: [Defect].} = ## Insert the snapshot into the database. try: s.cfg.db.db .put(s.data.blockHash.cliqueSnapshotKey.toOpenArray, rlp.encode(s.data)) except CatchableError as e: return err((errSnapshotStore, $e.name & ": " & e.msg)) ok() # ------------------------------------------------------------------------------ # Public deep copy # ------------------------------------------------------------------------------ proc cloneSnapshot*(s: Snapshot): Snapshot = ## Clone the snapshot Snapshot( cfg: s.cfg, # copy ref data: s.data) # copy data # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------