diff --git a/Makefile b/Makefile index 408e4c44e..6fcde205f 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ TOOLS := \ nbench_spec_scenarios \ ncli \ ncli_db \ + ncli_slashing \ process_dashboard \ stack_sizes \ nimbus_validator_client \ diff --git a/beacon_chain/validators/slashing_protection.nim b/beacon_chain/validators/slashing_protection.nim index 9b6b9ad88..208f126c6 100644 --- a/beacon_chain/validators/slashing_protection.nim +++ b/beacon_chain/validators/slashing_protection.nim @@ -105,6 +105,7 @@ proc init*( fatal "The slashing database refers to another chain/mainnet/testnet", path = basePath/dbname, genesis_validators_root = genesis_validators_root + quit 1 db_v1.fromRawDB(rawdb) if requiresMigration: @@ -153,15 +154,25 @@ proc loadUnchecked*( ## this doesn't check the genesis validator root ## ## Does not handle migration + new result - result.modes = {kCompleteArchiveV1, kCompleteArchiveV2} + result.modes = {} result.disagreementBehavior = kCrash - result.db_v2 = SlashingProtectionDB_v2.loadUnchecked( - basePath, dbname, readOnly - ) + try: + result.db_v2 = SlashingProtectionDB_v2.loadUnchecked( + basePath, dbname, readOnly + ) + result.modes.incl(kCompleteArchiveV2) + except: + result.disagreementBehavior = kChooseV1 - result.db_v1.fromRawDB(kvstore result.db_v2.getRawDBHandle()) + try: + result.db_v1.fromRawDB(kvstore result.db_v2.getRawDBHandle()) + result.modes.incl(kCompleteArchiveV1) + except: + doAssert result.modes.card > 0, "Couldn't open the DB." + result.disagreementBehavior = kChooseV2 proc close*(db: SlashingProtectionDB) = ## Close a slashing protection database diff --git a/beacon_chain/validators/slashing_protection_common.nim b/beacon_chain/validators/slashing_protection_common.nim index 3fdab4d07..dda1391df 100644 --- a/beacon_chain/validators/slashing_protection_common.nim +++ b/beacon_chain/validators/slashing_protection_common.nim @@ -196,7 +196,7 @@ proc writeValue*(writer: var JsonWriter, value: PubKey0x) proc readValue*(reader: var JsonReader, value: var PubKey0x) {.raises: [SerializationError, IOError, Defect].} = try: - value = PubKey0x reader.readValue(string).hexToByteArray(RawPubKeySize) + value = PubKey0x hexToByteArray(reader.readValue(string), RawPubKeySize) except ValueError: raiseUnexpectedValue(reader, "Hex string expected") diff --git a/ncli/ncli_slashing.nim b/ncli/ncli_slashing.nim index 8bf02b7cc..e824cce73 100644 --- a/ncli/ncli_slashing.nim +++ b/ncli/ncli_slashing.nim @@ -10,6 +10,7 @@ import std/[os, strutils], confutils, + serialization, json_serialization, eth/db/[kvstore, kvstore_sqlite3], ../beacon_chain/validators/slashing_protection, ../beacon_chain/spec/digest @@ -21,24 +22,63 @@ type SlashProtConf = object + db{.desc: "The path to the database .sqlite3 file" .}: string + case cmd {. command, - desc: "Dump database or restore" .}: SlashProtCmd - of dump, restore: - infile {.argument.}: string + desc: "Dump database to or restore from a slashing interchange file" .}: SlashProtCmd + of dump: outfile {.argument.}: string + of restore: + infile {.argument.}: string proc doDump(conf: SlashProtConf) = - let (dir, file) = splitPath(conf.infile) + let (dir, file) = splitPath(conf.db) # TODO: Make it read-only https://github.com/status-im/nim-eth/issues/312 # TODO: why is sqlite3 always appending .sqlite3 ? let filetrunc = file.changeFileExt("") let db = SlashingProtectionDB.loadUnchecked(dir, filetrunc, readOnly = false) db.exportSlashingInterchange(conf.outfile) + echo "Export finished: '", conf.db, "' into '", conf.outfile, "'" + +proc doRestore(conf: SlashProtConf) = + let (dir, file) = splitPath(conf.db) + # TODO: Make it read-only https://github.com/status-im/nim-eth/issues/312 + # TODO: why is sqlite3 always appending .sqlite3 ? + let filetrunc = file.changeFileExt("") + + var spdir: SPDIR + try: + spdir = JSON.loadFile(conf.infile, SPDIR) + except SerializationError as err: + writeStackTrace() + stderr.write $JSON & " load issue for file \"", conf.infile, "\"\n" + stderr.write err.formatMsg(conf.infile), "\n" + quit 1 + + # Open DB and handle migration from v1 to v2 if needed + let db = SlashingProtectionDB.init( + genesis_validators_root = Eth2Digest spdir.metadata.genesis_validators_root, + basePath = dir, + dbname = filetrunc, + modes = {kCompleteArchiveV2}, + disagreementBehavior = kChooseV2 + ) + + # Now import the slashing interchange file + # Failures mode: + # - siError can only happen with invalid genesis_validators_root which would be caught above + # - siPartial can happen for invalid public keys, slashable blocks, slashable votes + let status = db.inclSPDIR(spdir) + doAssert status in {siSuccess, siPartial} + + echo "Import finished: '", conf.infile, "' into '", conf.db, "'" + # TODO: do we mention that v2 MUST be used? + # normally this has always been a hidden option. when isMainModule: let conf = SlashProtConf.load() case conf.cmd: of dump: conf.doDump() - of restore: doAssert false, "unimplemented" + of restore: conf.doRestore()