Better error-handling for the slashingdb import/export feature

* Error when specifying an invalid --data-dir (or --validator-dir)
* Error when entering an invalid validator public key (e.g. invalid hex value)
* Warning when attempting to export a validator not present in the local database

Some unnecessary remains of the v1 mode has been removed as well
This commit is contained in:
Zahary Karadjov 2021-05-17 19:55:46 +03:00
parent 5fadaa247c
commit b9924214ab
No known key found for this signature in database
GPG Key ID: C8936F8A3073D609
7 changed files with 50 additions and 110 deletions

View File

@ -9,15 +9,18 @@
import
strutils, os, options, unicode, uri,
chronicles, chronicles/options as chroniclesOptions,
confutils, confutils/defs, confutils/std/net, stew/shims/net as stewNet,
stew/io2, unicodedb/properties, normalize,
stew/[io2, byteutils], unicodedb/properties, normalize,
eth/common/eth_types as commonEthTypes, eth/net/nat,
eth/p2p/discoveryv5/enr,
json_serialization, web3/[ethtypes, confutils_defs],
spec/[crypto, keystore, digest, datatypes, network],
./spec/[crypto, keystore, digest, datatypes, network],
./networking/network_metadata,
filepath
./validators/slashing_protection_common,
./filepath
export
uri,
@ -505,7 +508,7 @@ type
desc: "Limit the export to specific validators " &
"(specified as numeric indices or public keys)"
abbr: "v"
name: "validator" }: seq[string]
name: "validator" }: seq[PubKey0x]
exportedInterchangeFile* {.
desc: "EIP-3076 slashing protection interchange file to export"
argument }: OutFile
@ -559,8 +562,9 @@ type
name: "rpc-port" }: Port
rpcAddress* {.
defaultValue: defaultAdminListenAddress
desc: "Address of the server to connect to for RPC [=127.0.0.1]"
desc: "Address of the server to connect to for RPC"
defaultValue: init(ValidIpAddress, "127.0.0.1")
defaultValueDesc: "127.0.0.1"
name: "rpc-address" }: ValidIpAddress
retryDelay* {.
@ -623,6 +627,13 @@ func parseCmdArg*(T: type Uri, input: TaintedString): T
func completeCmdArg*(T: type Uri, input: TaintedString): seq[string] =
return @[]
func parseCmdArg*(T: type PubKey0x, input: TaintedString): T
{.raises: [ValueError, Defect].} =
PubKey0x(hexToPaddedByteArray[RawPubKeySize](input.string))
func completeCmdArg*(T: type PubKey0x, input: TaintedString): seq[string] =
return @[]
func parseCmdArg*(T: type Checkpoint, input: TaintedString): T
{.raises: [ValueError, Defect].} =
let sepIdx = find(input.string, ':')

View File

@ -1962,8 +1962,7 @@ proc doSlashingImport(conf: BeaconNodeConf) {.raises: [SerializationError, IOErr
genesis_validators_root = Eth2Digest spdir.metadata.genesis_validators_root,
basePath = dir,
dbname = filetrunc,
modes = {kCompleteArchiveV2},
disagreementBehavior = kChooseV2
modes = {kCompleteArchive}
)
# Now import the slashing interchange file

View File

@ -45,9 +45,8 @@ export chronicles
type
SlashProtDBMode* = enum
kCompleteArchiveV1 # Complete Format V1 backend (saves all attestations)
kCompleteArchiveV2 # Complete Format V2 backend (saves all attestations)
kLowWatermarkV2 # Low-Watermark Format V2 backend (prunes attestations)
kCompleteArchive # Complete Format V2 backend (saves all attestations)
kLowWatermark # Low-Watermark Format V2 backend (prunes attestations)
SlashingProtectionDB* = ref object
## Database storing the blocks attested
@ -55,13 +54,6 @@ type
## or validator client.
db_v2*: SlashingProtectionDB_v2
modes: set[SlashProtDBMode]
disagreementBehavior: DisagreementBehavior
DisagreementBehavior* = enum
## How to handle disagreement between DB versions
kCrash
kChooseV1
kChooseV2
# DB Multiversioning
# -------------------------------------------------------------
@ -78,7 +70,6 @@ proc init*(
genesis_validators_root: Eth2Digest,
basePath, dbname: string,
modes: set[SlashProtDBMode],
disagreementBehavior: DisagreementBehavior
): T =
## Initialize or load a slashing protection DB
## This is for Beacon Node usage
@ -86,12 +77,11 @@ proc init*(
doAssert modes.card >= 1, "No slashing protection mode chosen. Choose a v1, a v2 or v1 and v2 slashing DB mode."
doAssert not(
kCompleteArchiveV2 in modes and
kLowWatermarkV2 in modes), "Mode(s): " & $modes & ". Choose only one of V2 DB modes."
kCompleteArchive in modes and
kLowWatermark in modes), "Mode(s): " & $modes & ". Choose only one of V2 DB modes."
new result
result.modes = modes
result.disagreementBehavior = disagreementBehavior
let (db, requiresMigration) = SlashingProtectionDB_v2.initCompatV1(
genesis_validators_root,
@ -142,8 +132,7 @@ proc init*(
## Does not handle migration
init(
T, genesis_validators_root, basePath, dbname,
modes = {kLowWatermarkV2},
disagreementBehavior = kChooseV2
modes = {kLowWatermark}
)
proc loadUnchecked*(
@ -158,15 +147,15 @@ proc loadUnchecked*(
new result
result.modes = {}
result.disagreementBehavior = kCrash
try:
result.db_v2 = SlashingProtectionDB_v2.loadUnchecked(
basePath, dbname, readOnly
)
result.modes.incl(kCompleteArchiveV2)
except:
result.disagreementBehavior = kChooseV1
result.modes.incl(kCompleteArchive)
except CatchableError as err:
error "Failed to load the Slashing protection database", err = err.msg
quit 1
proc close*(db: SlashingProtectionDB) =
## Close a slashing protection database
@ -280,10 +269,10 @@ proc pruneAfterFinalization*(
## This ensures that even if pruning is called with an incorrect epoch
## slashing protection can fallback to the minimal / high-watermark protection mode.
##
## Pruning is only done if pruning is enabled (DB in kLowWatermarkV2 mode)
## Pruning is only done if pruning is enabled (DB in kLowWatermark mode)
## Pruning is only triggered on v2 database.
if kLowWatermarkV2 in db.modes:
if kLowWatermark in db.modes:
db.db_v2.pruneAfterFinalization(finalizedEpoch)
# The high-level import/export functions are
@ -305,7 +294,7 @@ proc toSPDIR*(db: SlashingProtectionDB): SPDIR
proc exportSlashingInterchange*(
db: SlashingProtectionDB,
path: string,
validatorsWhiteList: seq[string] = @[],
validatorsWhiteList: seq[PubKey0x] = @[],
prettify = true) {.raises: [Defect, IOError].} =
## Export a database to the Slashing Protection Database Interchange Format
# We could modify toSPDIR to do the filtering directly
@ -317,7 +306,14 @@ proc exportSlashingInterchange*(
# O(a log b) with b the number of validators to keep
# and a the total number of validators in DB
let validators = validatorsWhiteList.sorted()
spdir.data.keepItIf(validators.binarySearch("0x" & it.pubkey.PubKeyBytes.toHex()) != -1)
spdir.data.keepItIf(validators.binarySearch(it.pubkey) != -1)
if spdir.data.len != validatorsWhiteList.len:
let exportedKeys = spdir.data.mapIt(it.pubkey).sorted()
for v in validators:
if exportedKeys.binarySearch(v) == -1:
warn "Specified validator key not found in the slashing database",
key = v.PubKeyBytes.toHex
Json.saveFile(path, spdir, prettify)
echo "Exported slashing protection DB to '", path, "'"

View File

@ -9,7 +9,7 @@
import
# Stdlib
std/[typetraits, strutils, algorithm, sequtils],
std/[typetraits, strutils, algorithm],
# Status
eth/db/[kvstore, kvstore_sqlite3],
stew/results,
@ -170,6 +170,15 @@ func `==`*(a, b: BadVote): bool =
of BadVoteKind.DatabaseError:
true
template `==`*(a, b: PubKey0x): bool =
PubKeyBytes(a) == PubKeyBytes(b)
template `<`*(a, b: PubKey0x): bool =
PubKeyBytes(a) < PubKeyBytes(b)
template cmp*(a, b: PubKey0x): bool =
cmp(PubKeyBytes(a), PubKeyBytes(b))
func `==`*(a, b: BadProposal): bool =
## Comparison operator.
## Used implictily by Result when comparing the

View File

@ -398,7 +398,7 @@ proc loadUnchecked*(
subkey(kGenesisValidatorsRoot)
).get(), "The Slashing DB is missing genesis information"
result = T(backend: backend)
T(backend: backend)
proc close*(db: SlashingProtectionDB_v1) =
discard db.backend.close()

View File

@ -1,75 +0,0 @@
# Slashing interchange
Importing and exporting validators is available via the following commands:
- `path/to/nimbus_beacon_node slashingdb import infile.json`
- `path/to/nimbus_beacon_node slashingdb export outfile.json`
- `path/to/nimbus_beacon_node slashingdb export outfile.json --validator=0xAAAA...AAA --validator=0xBBBB...BBBB --validator=0xCCCC...CCCC`
## Importing new validators
## Importing validators
The default command for import into the database is:
```
build/nimbus_beacon_node slashingdb import interchange.json
```
### With specified validators folder
The validators folder contains the valdiators setup.
By default it is `path/to/datadir/validators`
```
build/nimbus_beacon_node slashingdb export interchange.json --validators-dir=path/to/validatorsdir/
```
### With the data-dir folder
The data-dir contains the beacon node setup.
```
build/nimbus_beacon_node slashingdb export interchange.json --data-dir=path/to/datadir/
```
## Exporting all validators
The default command for exporting the database is:
```
build/nimbus_beacon_node slashingdb export interchange.json
```
On success you will have a message similar to:
```
Exported slashing protection DB to 'interchange.json'
Export finished: '$HOME/.cache/nimbus/BeaconNode/validators/slashing_protection.sqlite3' into 'interchange.json'
```
### With specified validators folder
The validators folder contains the valdiators setup.
By default it is `path/to/datadir/validators`
```
build/nimbus_beacon_node slashingdb export interchange.json --validators-dir=path/to/validatorsdir/
```
### With the data-dir folder
The data-dir contains the beacon node setup.
```
build/nimbus_beacon_node slashingdb export interchange.json --data-dir=path/to/datadir/
```
## Partial exports
Partial export can be done by specifying the public keys of the relevant validators.
The `--validator` command can be specified multiple time, once per validator.
```
build/nimbus_beacon_node slashingdb export interchange.json --validator=0xb5da853a51d935da6f3bd46934c719fcca1bbf0b493264d3d9e7c35a1023b73c703b56d598edf0239663820af36ec615
```

@ -1 +1 @@
Subproject commit d1a45cfa9aa222cceb9b8365f8aeedeb4d51df36
Subproject commit 5f2f882151fee87471514bb74c1311856c636594