mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-10 13:36:40 +00:00
avoid some slashing protection queries (#2528)
This PR reduces the number of database queries for slashing protection from 5 reads and 1 write to 2 reads and 1 write in the optimistic case. In the process, it removes user-level support for writing the database in the version 1 format in order to simplify the code flow, and prevent code rot. In particular, the v1 format was not covered by any unit tests and has no advantages over v2. The concrete code to read and write it remains for now, in particular to support upgrades from v1 to v2. The branch also removes the use of concepts which doesn't work with checked exceptions - in particular, this highlights code that both raises exceptions and returns error codes, which could be cleaned up in the future. * Cache internal validator ID * Rely on unique index to check for trivial duplicate votes * Combine two surround vote queries into one * Combine API for checking and registering slashing into single function The slashing DB is normally not a bottleneck, but may become one with high attached validator counts.
This commit is contained in:
parent
290b889ce6
commit
efdf759cc0
@ -145,7 +145,7 @@ type
|
||||
slashingDbKind* {.
|
||||
hidden
|
||||
defaultValue: SlashingDbKind.v2
|
||||
desc: "The slashing DB flavour to use (v1, v2 or both) [=both]"
|
||||
desc: "The slashing DB flavour to use (v2) [=v2]"
|
||||
name: "slashing-db-kind" }: SlashingDbKind
|
||||
|
||||
stateDbKind* {.
|
||||
|
@ -322,33 +322,24 @@ proc init*(T: type BeaconNode,
|
||||
attestationPool = newClone(AttestationPool.init(chainDag, quarantine))
|
||||
exitPool = newClone(ExitPool.init(chainDag, quarantine))
|
||||
|
||||
case config.slashingDbKind
|
||||
of SlashingDbKind.v2:
|
||||
discard
|
||||
of SlashingDbKind.v1:
|
||||
error "Slashing DB v1 is no longer supported for writing"
|
||||
quit 1
|
||||
of SlashingDbKind.both:
|
||||
warn "Slashing DB v1 deprecated, writing only v2"
|
||||
|
||||
info "Loading slashing protection database (v2)",
|
||||
path = config.validatorsDir()
|
||||
|
||||
let
|
||||
slashingProtectionDB =
|
||||
case config.slashingDbKind
|
||||
of SlashingDbKind.v1:
|
||||
info "Loading slashing protection database",
|
||||
path = config.validatorsDir()
|
||||
SlashingProtectionDB.init(
|
||||
getStateField(chainDag.headState, genesis_validators_root),
|
||||
config.validatorsDir(), "slashing_protection",
|
||||
modes = {kCompleteArchiveV1},
|
||||
disagreementBehavior = kChooseV1
|
||||
)
|
||||
of SlashingDbKind.v2:
|
||||
info "Loading slashing protection database (v2)",
|
||||
path = config.validatorsDir()
|
||||
SlashingProtectionDB.init(
|
||||
SlashingProtectionDB.init(
|
||||
getStateField(chainDag.headState, genesis_validators_root),
|
||||
config.validatorsDir(), "slashing_protection"
|
||||
)
|
||||
of SlashingDbKind.both:
|
||||
info "Loading slashing protection database (dual DB mode)",
|
||||
path = config.validatorsDir()
|
||||
SlashingProtectionDB.init(
|
||||
getStateField(chainDag.headState, genesis_validators_root),
|
||||
config.validatorsDir(), "slashing_protection",
|
||||
modes = {kCompleteArchiveV1, kCompleteArchiveV2},
|
||||
disagreementBehavior = kChooseV2
|
||||
)
|
||||
validatorPool = newClone(ValidatorPool.init(slashingProtectionDB))
|
||||
|
||||
consensusManager = ConsensusManager.new(
|
||||
|
@ -135,25 +135,25 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
|
||||
if vc.proposalsForCurrentEpoch.contains slot:
|
||||
let public_key = vc.proposalsForCurrentEpoch[slot]
|
||||
|
||||
notice "Proposing block", slot = slot, public_key = public_key
|
||||
|
||||
let validator = vc.attachedValidators.validators[public_key]
|
||||
let randao_reveal = await validator.genRandaoReveal(
|
||||
vc.fork, vc.beaconGenesis.genesis_validators_root, slot)
|
||||
var newBlock = SignedBeaconBlock(
|
||||
message: await vc.client.get_v1_validator_block(slot, vc.graffitiBytes, randao_reveal)
|
||||
)
|
||||
newBlock.root = hash_tree_root(newBlock.message)
|
||||
|
||||
# TODO: signing_root is recomputed in signBlockProposal just after
|
||||
let signing_root = compute_block_root(vc.fork, vc.beaconGenesis.genesis_validators_root, slot, newBlock.root)
|
||||
let notSlashable = vc.attachedValidators
|
||||
.slashingProtection
|
||||
.checkSlashableBlockProposal(public_key, slot)
|
||||
.slashingProtection
|
||||
.registerBlock(
|
||||
newBlock.message.proposer_index.ValidatorIndex, public_key, slot,
|
||||
signing_root)
|
||||
|
||||
if notSlashable.isOk:
|
||||
let validator = vc.attachedValidators.validators[public_key]
|
||||
notice "Proposing block", slot = slot, public_key = public_key
|
||||
let randao_reveal = await validator.genRandaoReveal(
|
||||
vc.fork, vc.beaconGenesis.genesis_validators_root, slot)
|
||||
var newBlock = SignedBeaconBlock(
|
||||
message: await vc.client.get_v1_validator_block(slot, vc.graffitiBytes, randao_reveal)
|
||||
)
|
||||
newBlock.root = hash_tree_root(newBlock.message)
|
||||
|
||||
# TODO: signing_root is recomputed in signBlockProposal just after
|
||||
let signing_root = compute_block_root(vc.fork, vc.beaconGenesis.genesis_validators_root, slot, newBlock.root)
|
||||
vc.attachedValidators
|
||||
.slashingProtection
|
||||
.registerBlock(public_key, slot, signing_root)
|
||||
|
||||
newBlock.signature = await validator.signBlockProposal(
|
||||
vc.fork, vc.beaconGenesis.genesis_validators_root, slot, newBlock.root)
|
||||
|
||||
@ -181,21 +181,14 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
|
||||
let validator = vc.attachedValidators.validators[a.public_key]
|
||||
let ad = await vc.client.get_v1_validator_attestation_data(slot, a.committee_index)
|
||||
|
||||
# TODO signing_root is recomputed in produceAndSignAttestation/signAttestation just after
|
||||
let signing_root = compute_attestation_root(
|
||||
vc.fork, vc.beaconGenesis.genesis_validators_root, ad)
|
||||
let notSlashable = vc.attachedValidators
|
||||
.slashingProtection
|
||||
.checkSlashableAttestation(
|
||||
a.public_key,
|
||||
ad.source.epoch,
|
||||
ad.target.epoch)
|
||||
.slashingProtection
|
||||
.registerAttestation(
|
||||
a.validator_index, a.public_key, ad.source.epoch, ad.target.epoch, signing_root)
|
||||
if notSlashable.isOk():
|
||||
# TODO signing_root is recomputed in produceAndSignAttestation/signAttestation just after
|
||||
let signing_root = compute_attestation_root(
|
||||
vc.fork, vc.beaconGenesis.genesis_validators_root, ad)
|
||||
vc.attachedValidators
|
||||
.slashingProtection
|
||||
.registerAttestation(
|
||||
a.public_key, ad.source.epoch, ad.target.epoch, signing_root)
|
||||
|
||||
# TODO I don't like these (u)int64-to-int conversions...
|
||||
let attestation = await validator.produceAndSignAttestation(
|
||||
ad, a.committee_length.int, a.validator_committee_index,
|
||||
|
@ -5,8 +5,7 @@
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
# TODO doesn't work with concepts (sigh)
|
||||
# {.push raises: [Defect].}
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
# stdlib
|
||||
@ -21,6 +20,7 @@ import
|
||||
./slashing_protection_v2
|
||||
|
||||
export slashing_protection_common
|
||||
|
||||
# Generic sandwich
|
||||
export chronicles
|
||||
|
||||
@ -52,8 +52,7 @@ type
|
||||
## Database storing the blocks attested
|
||||
## by validators attached to a beacon node
|
||||
## or validator client.
|
||||
db_v1: SlashingProtectionDB_v1
|
||||
db_v2: SlashingProtectionDB_v2
|
||||
db_v2*: SlashingProtectionDB_v2
|
||||
modes: set[SlashProtDBMode]
|
||||
disagreementBehavior: DisagreementBehavior
|
||||
|
||||
@ -99,17 +98,27 @@ proc init*(
|
||||
)
|
||||
result.db_v2 = db
|
||||
|
||||
var db_v1: SlashingProtectionDB_v1
|
||||
|
||||
let rawdb = kvstore result.db_v2.getRawDBHandle()
|
||||
if not rawdb.checkOrPutGenesis_DbV1(genesis_validators_root):
|
||||
fatal "The slashing database refers to another chain/mainnet/testnet",
|
||||
path = basePath/dbname,
|
||||
genesis_validators_root = genesis_validators_root
|
||||
result.db_v1.fromRawDB(rawdb)
|
||||
db_v1.fromRawDB(rawdb)
|
||||
|
||||
if requiresMigration:
|
||||
info "Migrating local validators slashing DB from v1 to v2"
|
||||
let spdir = result.db_v1.toSPDIR_lowWatermark()
|
||||
let status = result.db_v2.inclSPDIR(spdir)
|
||||
let spdir = try: db_v1.toSPDIR_lowWatermark()
|
||||
except IOError as exc:
|
||||
fatal "Cannot migrate v1 database", err = exc.msg
|
||||
quit 1
|
||||
|
||||
let status = try: result.db_v2.inclSPDIR(spdir)
|
||||
except CatchableError as exc:
|
||||
fatal "Writing DB v2 failed", err = exc.msg
|
||||
quit 1
|
||||
|
||||
case status
|
||||
of siSuccess:
|
||||
info "Slashing DB migration successful."
|
||||
@ -163,77 +172,9 @@ proc close*(db: SlashingProtectionDB) =
|
||||
# DB Queries
|
||||
# --------------------------------------------
|
||||
|
||||
proc useV1(db: SlashingProtectionDB): bool =
|
||||
kCompleteArchiveV1 in db.modes
|
||||
|
||||
proc useV2(db: SlashingProtectionDB): bool =
|
||||
kCompleteArchiveV2 in db.modes or
|
||||
kLowWatermarkV2 in db.modes
|
||||
|
||||
template queryVersions(
|
||||
db: SlashingProtectionDB,
|
||||
queryExpr: untyped
|
||||
): auto =
|
||||
## Query multiple DB versions
|
||||
## Query should be in the form
|
||||
## myQuery(db_version, args...)
|
||||
##
|
||||
## Resolve conflicts according to
|
||||
## `db.disagreementBehavior`
|
||||
##
|
||||
## For example
|
||||
## checkSlashableBlockProposal(db_version, validator, slot)
|
||||
##
|
||||
## db_version will be replaced by db_v1 and db_v2 accordingly
|
||||
type T = typeof(block:
|
||||
template db_version: untyped = db.db_v1
|
||||
queryExpr
|
||||
)
|
||||
|
||||
var res1, res2: T
|
||||
let useV1 = db.useV1()
|
||||
let useV2 = db.useV2()
|
||||
|
||||
if useV1:
|
||||
template db_version: untyped = db.db_v1
|
||||
res1 = queryExpr
|
||||
if useV2:
|
||||
template db_version: untyped = db.db_v2
|
||||
res2 = queryExpr
|
||||
|
||||
if useV1 and useV2:
|
||||
if res1 == res2:
|
||||
res1
|
||||
else:
|
||||
# TODO: Chronicles doesn't work with astToStr.
|
||||
const queryStr = astToStr(queryExpr)
|
||||
case db.disagreementBehavior
|
||||
of kCrash:
|
||||
fatal "Slashing protection DB has an internal error",
|
||||
query = queryStr,
|
||||
dbV1_result = res1,
|
||||
dbV2_result = res2
|
||||
doAssert false, "Slashing DB internal error"
|
||||
res1 # For proper type deduction
|
||||
of kChooseV1:
|
||||
error "Slashing protection DB has an internal error, using v1 result",
|
||||
query = queryStr,
|
||||
dbV1_result = res1,
|
||||
dbV2_result = res2
|
||||
res1
|
||||
of kChooseV2:
|
||||
error "Slashing protection DB has an internal error, using v2 result",
|
||||
query = queryStr,
|
||||
dbV1_result = res1,
|
||||
dbV2_result = res2
|
||||
res2
|
||||
elif useV1:
|
||||
res1
|
||||
else:
|
||||
res2
|
||||
|
||||
proc checkSlashableBlockProposal*(
|
||||
db: SlashingProtectionDB,
|
||||
index: ValidatorIndex,
|
||||
validator: ValidatorPubKey,
|
||||
slot: Slot
|
||||
): Result[void, BadProposal] =
|
||||
@ -243,12 +184,11 @@ proc checkSlashableBlockProposal*(
|
||||
## The error contains the blockroot that was already proposed
|
||||
##
|
||||
## Returns success otherwise
|
||||
db.queryVersions(
|
||||
checkSlashableBlockProposal(db_version, validator, slot)
|
||||
)
|
||||
checkSlashableBlockProposal(db.db_v2, some(index), validator, slot)
|
||||
|
||||
proc checkSlashableAttestation*(
|
||||
db: SlashingProtectionDB,
|
||||
index: ValidatorIndex,
|
||||
validator: ValidatorPubKey,
|
||||
source: Epoch,
|
||||
target: Epoch
|
||||
@ -259,65 +199,36 @@ proc checkSlashableAttestation*(
|
||||
## (surrounding vote or surrounded vote).
|
||||
##
|
||||
## Returns success otherwise
|
||||
db.queryVersions(
|
||||
checkSlashableAttestation(db_version, validator, source, target)
|
||||
)
|
||||
checkSlashableAttestation(db.db_v2, some(index), validator, source, target)
|
||||
|
||||
# DB Updates
|
||||
# DB Updates - only v2 supported here
|
||||
# --------------------------------------------
|
||||
|
||||
template updateVersions(
|
||||
db: SlashingProtectionDB,
|
||||
query: untyped
|
||||
) {.dirty.} =
|
||||
## Update multiple DB versions
|
||||
## Query should be in the form
|
||||
## myQuery(db_version, args...)
|
||||
##
|
||||
## Resolve conflicts according to
|
||||
## `db.disagreementBehavior`
|
||||
##
|
||||
## For example
|
||||
## registerBlock(db_version, validator, slot, block_root)
|
||||
##
|
||||
## db_version will be replaced by db_v1 and db_v2 accordingly
|
||||
|
||||
if db.useV1():
|
||||
template db_version: untyped = db.db_v1
|
||||
query
|
||||
if db.useV2():
|
||||
template db_version: untyped = db.db_v2
|
||||
query
|
||||
|
||||
proc registerBlock*(
|
||||
db: SlashingProtectionDB,
|
||||
index: ValidatorIndex,
|
||||
validator: ValidatorPubKey,
|
||||
slot: Slot, block_signing_root: Eth2Digest) =
|
||||
## Add a block to the slashing protection DB
|
||||
## `checkSlashableBlockProposal` MUST be run
|
||||
## before to ensure no overwrite.
|
||||
slot: Slot, block_signing_root: Eth2Digest): Result[void, BadProposal] =
|
||||
## Add a block to the slashing protection DB - the registration will
|
||||
## fail if it would violate a slashing protection rule.
|
||||
##
|
||||
## block_signing_root is the output of
|
||||
## compute_signing_root(block, domain)
|
||||
db.updateVersions(
|
||||
registerBlock(db_version, validator, slot, block_signing_root)
|
||||
)
|
||||
registerBlock(db.db_v2, some(index), validator, slot, block_signing_root)
|
||||
|
||||
proc registerAttestation*(
|
||||
db: SlashingProtectionDB,
|
||||
index: ValidatorIndex,
|
||||
validator: ValidatorPubKey,
|
||||
source, target: Epoch,
|
||||
attestation_signing_root: Eth2Digest) =
|
||||
## Add an attestation to the slashing protection DB
|
||||
## `checkSlashableAttestation` MUST be run
|
||||
## before to ensure no overwrite.
|
||||
attestation_signing_root: Eth2Digest): Result[void, BadVote] =
|
||||
## Add an attestation to the slashing protection DB - the registration will
|
||||
## fail if it would violate a slashing protection rule.
|
||||
##
|
||||
## attestation_signing_root is the output of
|
||||
## compute_signing_root(attestation, domain)
|
||||
db.updateVersions(
|
||||
registerAttestation(db_version, validator,
|
||||
source, target, attestation_signing_root)
|
||||
)
|
||||
registerAttestation(db.db_v2, some(index), validator,
|
||||
source, target, attestation_signing_root)
|
||||
|
||||
# DB maintenance
|
||||
# --------------------------------------------
|
||||
@ -366,41 +277,6 @@ proc pruneAfterFinalization*(
|
||||
fatal "Pruning is not implemented"
|
||||
quit 1
|
||||
|
||||
# Interchange
|
||||
# --------------------------------------------
|
||||
|
||||
proc toSPDIR*(db: SlashingProtectionDB): SPDIR
|
||||
{.raises: [IOError, Defect].} =
|
||||
## Assumes that if the db uses both v1 and v2
|
||||
## the v2 has the latest information and includes the v1 DB
|
||||
if db.useV2():
|
||||
return db.db_v2.toSPDIR()
|
||||
else:
|
||||
doAssert db.useV1()
|
||||
return db.db_v1.toSPDIR()
|
||||
|
||||
proc inclSPDIR*(db: SlashingProtectionDB, spdir: SPDIR): SlashingImportStatus
|
||||
{.raises: [SerializationError, IOError, Defect].} =
|
||||
let useV1 = db.useV1()
|
||||
let useV2 = db.useV2()
|
||||
|
||||
if useV2 and useV1:
|
||||
let resultV2 = db.db_v2.inclSPDIR(spdir)
|
||||
let resultV1 = db.db_v1.inclSPDIR(spdir)
|
||||
if resultV1 == resultV2:
|
||||
return resultV2
|
||||
else:
|
||||
error "The legacy and new slashing protection DB have imported the file with different level of success",
|
||||
resultV1 = resultV1,
|
||||
resultV2 = resultV2
|
||||
return resultV2
|
||||
|
||||
if useV2 and not useV1:
|
||||
return db.db_v2.inclSPDIR(spdir)
|
||||
else:
|
||||
doAssert useV1
|
||||
return db.db_v1.inclSPDIR(spdir)
|
||||
|
||||
# The high-level import/export functions are
|
||||
# - importSlashingInterchange
|
||||
# - exportSlashingInterchange
|
||||
@ -409,7 +285,10 @@ proc inclSPDIR*(db: SlashingProtectionDB, spdir: SPDIR): SlashingImportStatus
|
||||
# That builds on a DB backend inclSPDIR and toSPDIR
|
||||
# SPDIR being a common Intermediate Representation
|
||||
|
||||
# Sanity check
|
||||
# --------------------------------------------------------------
|
||||
proc inclSPDIR*(db: SlashingProtectionDB, spdir: SPDIR): SlashingImportStatus
|
||||
{.raises: [SerializationError, IOError, Defect].} =
|
||||
db.db_v2.inclSPDIR(spdir)
|
||||
|
||||
static: doAssert SlashingProtectionDB is SlashingProtectionDB_Concept
|
||||
proc toSPDIR*(db: SlashingProtectionDB): SPDIR
|
||||
{.raises: [IOError, Defect].} =
|
||||
db.db_v2.toSPDIR()
|
||||
|
@ -82,56 +82,6 @@ type
|
||||
# Slashing Protection types
|
||||
# --------------------------------------------
|
||||
|
||||
type
|
||||
SlashingProtectionDB_Concept* = concept db, type DB
|
||||
## Database storing the blocks attested
|
||||
## by validators attached to a beacon node
|
||||
## or validator client.
|
||||
|
||||
# Metadata
|
||||
# --------------------------------------------
|
||||
DB.version is int
|
||||
|
||||
# Resource Management
|
||||
# --------------------------------------------
|
||||
DB is ref
|
||||
|
||||
DB.init(Eth2Digest, string, string) is DB
|
||||
# DB.init(genesis_root, dir, filename)
|
||||
DB.loadUnchecked(string, string, bool) is DB
|
||||
# DB.load(dir, filename, readOnly)
|
||||
db.close()
|
||||
|
||||
# Queries
|
||||
# --------------------------------------------
|
||||
db.checkSlashableBlockProposal(ValidatorPubKey, Slot) is Result[void, BadProposal]
|
||||
# db.checkSlashableBlockProposal(validator, slot)
|
||||
db.checkSlashableAttestation(ValidatorPubKey, Epoch, Epoch) is Result[void, BadVote]
|
||||
# db.checkSlashableAttestation(validator, source, target)
|
||||
|
||||
# Updates
|
||||
# --------------------------------------------
|
||||
db.registerBlock(ValidatorPubKey, Slot, Eth2Digest)
|
||||
# db.checkSlashableAttestation(validator, slot, block_root)
|
||||
db.registerAttestation(ValidatorPubKey, Epoch, Epoch, Eth2Digest)
|
||||
# db.checkSlashableAttestation(validator, source, target, block_root)
|
||||
|
||||
# Pruning
|
||||
# --------------------------------------------
|
||||
db.pruneBlocks(ValidatorPubKey, Slot)
|
||||
db.pruneAttestations(ValidatorPubKey, int64, int64)
|
||||
db.pruneAfterFinalization(Epoch)
|
||||
|
||||
# Interchange
|
||||
# --------------------------------------------
|
||||
db.toSPDIR() is SPDIR
|
||||
# to Slashing Protection Data Intermediate Representation
|
||||
# db.toSPDIR()
|
||||
db.inclSPDIR(SPDIR) is SlashingImportStatus
|
||||
# include the content of Slashing Protection Data Intermediate Representation
|
||||
# in the database
|
||||
# db.inclSPDIR(path)
|
||||
|
||||
SlashingImportStatus* = enum
|
||||
siSuccess
|
||||
siFailure
|
||||
@ -146,9 +96,8 @@ type
|
||||
# 2: candidate attestation
|
||||
|
||||
# Spec slashing condition
|
||||
DoubleVote # h(t1) == h(t2)
|
||||
SurroundedVote # h(s1) < h(s2) < h(t2) < h(t1)
|
||||
SurroundingVote # h(s2) < h(s1) < h(t1) < h(t2)
|
||||
DoubleVote # h(t1) == h(t2)
|
||||
SurroundVote # h(s1) < h(s2) < h(t2) < h(t1) or h(s2) < h(s1) < h(t1) < h(t2)
|
||||
|
||||
# Non-spec, should never happen in a well functioning client
|
||||
TargetPrecedesSource # h(t1) < h(s1) - current epoch precedes last justified epoch
|
||||
@ -156,12 +105,13 @@ type
|
||||
# EIP-3067 (https://eips.ethereum.org/EIPS/eip-3076)
|
||||
MinSourceViolation # h(s2) < h(s1) - EIP3067 condition 4 (strict inequality)
|
||||
MinTargetViolation # h(t2) <= h(t1) - EIP3067 condition 5
|
||||
DatabaseError # Cannot read/write the slashing protection db
|
||||
|
||||
BadVote* = object
|
||||
BadVote* {.pure.} = object
|
||||
case kind*: BadVoteKind
|
||||
of DoubleVote:
|
||||
existingAttestation*: Eth2Digest
|
||||
of SurroundedVote, SurroundingVote:
|
||||
of SurroundVote:
|
||||
existingAttestationRoot*: Eth2Digest # Many roots might be in conflict
|
||||
sourceExisting*, targetExisting*: Epoch
|
||||
sourceSlashable*, targetSlashable*: Epoch
|
||||
@ -173,12 +123,15 @@ type
|
||||
of MinTargetViolation:
|
||||
minTarget*: Epoch
|
||||
candidateTarget*: Epoch
|
||||
of BadVoteKind.DatabaseError:
|
||||
message*: string
|
||||
|
||||
BadProposalKind* = enum
|
||||
BadProposalKind* {.pure.} = enum
|
||||
# Spec slashing condition
|
||||
DoubleProposal # h(t1) == h(t2)
|
||||
# EIP-3067 (https://eips.ethereum.org/EIPS/eip-3076)
|
||||
MinSlotViolation # h(t2) <= h(t1)
|
||||
DatabaseError # Cannot read/write the slashing protection db
|
||||
|
||||
BadProposal* = object
|
||||
case kind*: BadProposalKind
|
||||
@ -187,6 +140,8 @@ type
|
||||
of MinSlotViolation:
|
||||
minSlot*: Slot
|
||||
candidateSlot*: Slot
|
||||
of BadProposalKind.DatabaseError:
|
||||
message*: string
|
||||
|
||||
func `==`*(a, b: BadVote): bool =
|
||||
## Comparison operator.
|
||||
@ -194,24 +149,26 @@ func `==`*(a, b: BadVote): bool =
|
||||
## result of multiple DB versions
|
||||
if a.kind != b.kind:
|
||||
false
|
||||
elif a.kind == DoubleVote:
|
||||
a.existingAttestation == b.existingAttestation
|
||||
elif a.kind in {SurroundedVote, SurroundingVote}:
|
||||
(a.existingAttestationRoot == b.existingAttestationRoot) and
|
||||
(a.sourceExisting == b.sourceExisting) and
|
||||
(a.targetExisting == b.targetExisting) and
|
||||
(a.sourceSlashable == b.sourceSlashable) and
|
||||
(a.targetSlashable == b.targetSlashable)
|
||||
elif a.kind == TargetPrecedesSource:
|
||||
true
|
||||
elif a.kind == MinSourceViolation:
|
||||
(a.minSource == b.minSource) and
|
||||
(a.candidateSource == b.candidateSource)
|
||||
elif a.kind == MinTargetViolation:
|
||||
(a.minTarget == b.minTarget) and
|
||||
(a.candidateTarget == b.candidateTarget)
|
||||
else: # Unreachable
|
||||
false
|
||||
else:
|
||||
case a.kind
|
||||
of DoubleVote:
|
||||
a.existingAttestation == b.existingAttestation
|
||||
of SurroundVote:
|
||||
(a.existingAttestationRoot == b.existingAttestationRoot) and
|
||||
(a.sourceExisting == b.sourceExisting) and
|
||||
(a.targetExisting == b.targetExisting) and
|
||||
(a.sourceSlashable == b.sourceSlashable) and
|
||||
(a.targetSlashable == b.targetSlashable)
|
||||
of TargetPrecedesSource:
|
||||
true
|
||||
of MinSourceViolation:
|
||||
(a.minSource == b.minSource) and
|
||||
(a.candidateSource == b.candidateSource)
|
||||
of MinTargetViolation:
|
||||
(a.minTarget == b.minTarget) and
|
||||
(a.candidateTarget == b.candidateTarget)
|
||||
of BadVoteKind.DatabaseError:
|
||||
true
|
||||
|
||||
func `==`*(a, b: BadProposal): bool =
|
||||
## Comparison operator.
|
||||
@ -266,7 +223,7 @@ proc readValue*(r: var JsonReader, a: var (SlotString or EpochString))
|
||||
raiseUnexpectedValue(r, "Integer in a string expected")
|
||||
|
||||
proc exportSlashingInterchange*(
|
||||
db: SlashingProtectionDB_Concept,
|
||||
db: auto,
|
||||
path: string, prettify = true) {.raises: [Defect, IOError].} =
|
||||
## Export a database to the Slashing Protection Database Interchange Format
|
||||
let spdir = db.toSPDIR()
|
||||
@ -274,7 +231,7 @@ proc exportSlashingInterchange*(
|
||||
echo "Exported slashing protection DB to '", path, "'"
|
||||
|
||||
proc importSlashingInterchange*(
|
||||
db: SlashingProtectionDB_Concept,
|
||||
db: auto,
|
||||
path: string): SlashingImportStatus {.raises: [Defect, IOError, SerializationError].} =
|
||||
## Import a Slashing Protection Database Interchange Format
|
||||
## into a Nimbus DB.
|
||||
@ -307,7 +264,7 @@ chronicles.formatIt SPDIR_SignedAttestation: it.shortLog
|
||||
# --------------------------------------------
|
||||
|
||||
proc importInterchangeV5Impl*(
|
||||
db: SlashingProtectionDB_Concept,
|
||||
db: auto,
|
||||
spdir: var SPDIR
|
||||
): SlashingImportStatus
|
||||
{.raises: [SerializationError, IOError, Defect].} =
|
||||
@ -359,8 +316,8 @@ proc importInterchangeV5Impl*(
|
||||
|
||||
for b in 0 ..< spdir.data[v].signed_blocks.len:
|
||||
template B: untyped = spdir.data[v].signed_blocks[b]
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
parsedKey, B.slot.Slot
|
||||
let status = db.registerBlock(
|
||||
parsedKey, B.slot.Slot, B.signing_root.Eth2Digest
|
||||
)
|
||||
if status.isErr():
|
||||
# We might be importing a duplicate which EIP-3076 allows
|
||||
@ -388,12 +345,6 @@ proc importInterchangeV5Impl*(
|
||||
if B.slot.int > maxValidSlotSeen:
|
||||
maxValidSlotSeen = B.slot.int
|
||||
|
||||
db.registerBlock(
|
||||
parsedKey,
|
||||
B.slot.Slot,
|
||||
B.signing_root.Eth2Digest
|
||||
)
|
||||
|
||||
# Now prune everything that predates
|
||||
# this interchange file max slot
|
||||
db.pruneBlocks(parsedKey, Slot maxValidSlotSeen)
|
||||
@ -409,10 +360,11 @@ proc importInterchangeV5Impl*(
|
||||
|
||||
for a in 0 ..< spdir.data[v].signed_attestations.len:
|
||||
template A: untyped = spdir.data[v].signed_attestations[a]
|
||||
let status = db.checkSlashableAttestation(
|
||||
let status = db.registerAttestation(
|
||||
parsedKey,
|
||||
A.source_epoch.Epoch,
|
||||
A.target_epoch.Epoch
|
||||
A.target_epoch.Epoch,
|
||||
A.signing_root.Eth2Digest
|
||||
)
|
||||
if status.isErr():
|
||||
# We might be importing a duplicate which EIP-3076 allows
|
||||
@ -439,13 +391,6 @@ proc importInterchangeV5Impl*(
|
||||
if A.target_epoch.int > maxValidTargetEpochSeen:
|
||||
maxValidTargetEpochSeen = A.target_epoch.int
|
||||
|
||||
db.registerAttestation(
|
||||
parsedKey,
|
||||
A.source_epoch.Epoch,
|
||||
A.target_epoch.Epoch,
|
||||
A.signing_root.Eth2Digest
|
||||
)
|
||||
|
||||
# Now prune everything that predates
|
||||
# this interchange file max slot
|
||||
if maxValidSourceEpochSeen < 0 or maxValidTargetEpochSeen < 0:
|
||||
|
@ -531,7 +531,7 @@ proc checkSlashableAttestation*(
|
||||
# s2 < s1 < t1 < t2
|
||||
# Logged by caller
|
||||
return err(BadVote(
|
||||
kind: SurroundingVote,
|
||||
kind: SurroundVote,
|
||||
existingAttestationRoot: ar1,
|
||||
sourceExisting: s1,
|
||||
targetExisting: t1,
|
||||
@ -542,7 +542,7 @@ proc checkSlashableAttestation*(
|
||||
# s1 < s2 < t2 < t1
|
||||
# Logged by caller
|
||||
return err(BadVote(
|
||||
kind: SurroundedVote,
|
||||
kind: SurroundVote,
|
||||
existingAttestationRoot: ar1,
|
||||
sourceExisting: s1,
|
||||
targetExisting: t1,
|
||||
@ -587,10 +587,10 @@ proc registerValidator(db: SlashingProtectionDB_v1, validator: ValidatorPubKey)
|
||||
proc registerBlock*(
|
||||
db: SlashingProtectionDB_v1,
|
||||
validator: ValidatorPubKey,
|
||||
slot: Slot, block_root: Eth2Digest) =
|
||||
slot: Slot, block_root: Eth2Digest): Result[void, BadProposal] =
|
||||
## Add a block to the slashing protection DB
|
||||
## `checkSlashableBlockProposal` MUST be run
|
||||
## before to ensure no overwrite.
|
||||
|
||||
? checkSlashableBlockProposal(db, validator, slot)
|
||||
|
||||
let valID = validator.toRaw()
|
||||
|
||||
@ -620,7 +620,7 @@ proc registerBlock*(
|
||||
# targetEpochs.isInit will be false
|
||||
)
|
||||
)
|
||||
return
|
||||
return ok()
|
||||
|
||||
var ll = maybeLL.unsafeGet()
|
||||
var cur = ll.blockSlots.stop
|
||||
@ -632,7 +632,7 @@ proc registerBlock*(
|
||||
db.put(subkey(kBlock, valID, slot), node)
|
||||
# TODO: what if crash here?
|
||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||
return
|
||||
return ok()
|
||||
|
||||
if cur < slot:
|
||||
# Adding a block later than all known blocks
|
||||
@ -652,7 +652,7 @@ proc registerBlock*(
|
||||
db.put(subkey(kBlock, valID, cur), prevNode)
|
||||
# TODO: what if crash here?
|
||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||
return
|
||||
return ok()
|
||||
|
||||
# TODO: we likely want a proper DB or better KV-store high-level API
|
||||
# in the future.
|
||||
@ -687,7 +687,7 @@ proc registerBlock*(
|
||||
# TODO: what if crash here?
|
||||
db.put(subkey(kBlock, valID, cur), curNode)
|
||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||
return
|
||||
return ok()
|
||||
elif slot > curNode.prev:
|
||||
# Reached: prev < slot < cur
|
||||
# Change: prev <-> cur
|
||||
@ -709,7 +709,7 @@ proc registerBlock*(
|
||||
# TODO: what if crash here?
|
||||
db.put(subkey(kBlock, valID, cur), curNode)
|
||||
db.put(subkey(kBlock, valID, prev), prevNode)
|
||||
return
|
||||
return ok()
|
||||
|
||||
# Previous
|
||||
cur = curNode.prev
|
||||
@ -720,15 +720,19 @@ proc registerBlock*(
|
||||
# ).expect("Consistent linked-list in DB")
|
||||
).unsafeGet()
|
||||
|
||||
ok()
|
||||
|
||||
proc registerAttestation*(
|
||||
db: SlashingProtectionDB_v1,
|
||||
validator: ValidatorPubKey,
|
||||
source, target: Epoch,
|
||||
attestation_root: Eth2Digest) =
|
||||
attestation_root: Eth2Digest): Result[void, BadVote] =
|
||||
## Add an attestation to the slashing protection DB
|
||||
## `checkSlashableAttestation` MUST be run
|
||||
## before to ensure no overwrite.
|
||||
|
||||
? checkSlashableAttestation(db, validator, source, target)
|
||||
|
||||
let valID = validator.toRaw()
|
||||
|
||||
# We want to keep the linked-list ordered
|
||||
@ -759,7 +763,7 @@ proc registerAttestation*(
|
||||
targetEpochs: EpochDesc(start: target, stop: target, isInit: true)
|
||||
)
|
||||
)
|
||||
return
|
||||
return ok()
|
||||
|
||||
var ll = maybeLL.unsafeGet()
|
||||
var cur = ll.targetEpochs.stop
|
||||
@ -773,7 +777,7 @@ proc registerAttestation*(
|
||||
db.put(subkey(kTargetEpoch, valID, target), node)
|
||||
# TODO: what if crash here?
|
||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||
return
|
||||
return ok()
|
||||
|
||||
block: # Update source epoch
|
||||
if ll.sourceEpochs.stop < source:
|
||||
@ -800,7 +804,7 @@ proc registerAttestation*(
|
||||
db.put(subkey(kTargetEpoch, valID, cur), prevNode)
|
||||
# TODO: what if crash here?
|
||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||
return
|
||||
return ok()
|
||||
|
||||
# TODO: we likely want a proper DB or better KV-store high-level API
|
||||
# in the future.
|
||||
@ -835,7 +839,7 @@ proc registerAttestation*(
|
||||
# TODO: what if crash here?
|
||||
db.put(subkey(kTargetEpoch, valID, cur), curNode)
|
||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||
return
|
||||
return ok()
|
||||
elif target > curNode.prev:
|
||||
# Reached: prev < target < cur
|
||||
# Change: prev <-> cur
|
||||
@ -858,7 +862,7 @@ proc registerAttestation*(
|
||||
# TODO: what if crash here?
|
||||
db.put(subkey(kTargetEpoch, valID, cur), curNode)
|
||||
db.put(subkey(kTargetEpoch, valID, prev), prevNode)
|
||||
return
|
||||
return ok()
|
||||
|
||||
# Previous
|
||||
cur = curNode.prev
|
||||
@ -869,6 +873,8 @@ proc registerAttestation*(
|
||||
# ).expect("Consistent linked-list in DB")
|
||||
).unsafeGet()
|
||||
|
||||
ok()
|
||||
|
||||
# Debug tools
|
||||
# --------------------------------------------
|
||||
|
||||
@ -1141,8 +1147,3 @@ proc inclSPDIR*(db: SlashingProtectionDB_v1, spdir: SPDIR): SlashingImportStatus
|
||||
# Create a mutable copy for sorting
|
||||
var spdir = spdir
|
||||
return db.importInterchangeV5Impl(spdir)
|
||||
|
||||
# Sanity check
|
||||
# --------------------------------------------------------------
|
||||
|
||||
static: doAssert SlashingProtectionDB_v1 is SlashingProtectionDB_Concept
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
import
|
||||
# Standard library
|
||||
std/[os, options, typetraits, decls],
|
||||
std/[os, options, typetraits, decls, tables],
|
||||
# Status
|
||||
stew/byteutils,
|
||||
eth/db/[kvstore, kvstore_sqlite3],
|
||||
@ -211,12 +211,13 @@ type
|
||||
# Cached queries - read
|
||||
sqlGetValidatorInternalID: SqliteStmt[PubKeyBytes, ValidatorInternalID]
|
||||
sqlAttForSameTargetEpoch: SqliteStmt[(ValidatorInternalID, int64), Hash32]
|
||||
sqlAttSurrounded: SqliteStmt[(ValidatorInternalID, int64, int64), (int64, int64, Hash32)]
|
||||
sqlAttSurrounding: SqliteStmt[(ValidatorInternalID, int64, int64), (int64, int64, Hash32)]
|
||||
sqlAttSurrounds: SqliteStmt[(ValidatorInternalID, int64, int64, int64, int64), (int64, int64, Hash32)]
|
||||
sqlAttMinSourceTargetEpochs: SqliteStmt[ValidatorInternalID, (int64, int64)]
|
||||
sqlBlockForSameSlot: SqliteStmt[(ValidatorInternalID, int64), Hash32]
|
||||
sqlBlockMinSlot: SqliteStmt[ValidatorInternalID, int64]
|
||||
|
||||
internalIds: Table[ValidatorIndex, ValidatorInternalID]
|
||||
|
||||
ValidatorInternalID = int32
|
||||
## Validator internal ID in the DB
|
||||
## This is cached to cost querying cost
|
||||
@ -382,30 +383,17 @@ proc setupCachedQueries(db: SlashingProtectionDB_v2) =
|
||||
""", (ValidatorInternalID, int64), Hash32
|
||||
).get()
|
||||
|
||||
db.sqlAttSurrounded = db.backend.prepareStmt("""
|
||||
db.sqlAttSurrounds = db.backend.prepareStmt("""
|
||||
SELECT
|
||||
source_epoch, target_epoch, signing_root
|
||||
FROM
|
||||
signed_attestations
|
||||
WHERE 1=1
|
||||
and validator_id = ?
|
||||
and source_epoch < ?
|
||||
and ? < target_epoch
|
||||
and ((source_epoch < ? and ? < target_epoch) OR
|
||||
(? < source_epoch and target_epoch < ?))
|
||||
LIMIT 1
|
||||
""", (ValidatorInternalID, int64, int64), (int64, int64, Hash32)
|
||||
).get()
|
||||
|
||||
db.sqlAttSurrounding = db.backend.prepareStmt("""
|
||||
SELECT
|
||||
source_epoch, target_epoch, signing_root
|
||||
FROM
|
||||
signed_attestations
|
||||
WHERE 1=1
|
||||
and validator_id = ?
|
||||
and ? < source_epoch
|
||||
and target_epoch < ?
|
||||
LIMIT 1
|
||||
""", (ValidatorInternalID, int64, int64), (int64, int64, Hash32)
|
||||
""", (ValidatorInternalID, int64, int64, int64, int64), (int64, int64, Hash32)
|
||||
).get()
|
||||
|
||||
# By default an aggregate always return a value
|
||||
@ -680,8 +668,20 @@ proc foundAnyResult(status: KVResult[bool]): bool {.inline.}=
|
||||
|
||||
proc getValidatorInternalID(
|
||||
db: SlashingProtectionDB_v2,
|
||||
index: Option[ValidatorIndex],
|
||||
validator: ValidatorPubKey): Option[ValidatorInternalID] =
|
||||
## Retrieve a validator internal ID
|
||||
if index.isSome():
|
||||
# Validator keys are mapped to internal id:s instead of using the
|
||||
# validator index - this allows importing keys without knowing the
|
||||
# state but has the unfortunate consequence of introducing an indirection
|
||||
# that must be kept updated at some cost. In a future version of the
|
||||
# database, one could consider a simplified design that directly uses the
|
||||
# validator index. In the meantime, this cache avoids some of the
|
||||
# unnecessary read traffic when checking and registering entries.
|
||||
db.internalIds.withValue(index.get(), internal) do:
|
||||
return some(internal[])
|
||||
|
||||
let serializedPubkey = validator.toRaw() # Miracl/BLST to bytes
|
||||
var valID: ValidatorInternalID
|
||||
let status = db.sqlGetValidatorInternalID.exec(serializedPubkey) do (res: ValidatorInternalID):
|
||||
@ -689,13 +689,15 @@ proc getValidatorInternalID(
|
||||
|
||||
# Note: we enforce at the DB level that if the pubkey exists it is unique.
|
||||
if status.foundAnyResult():
|
||||
if index.isSome():
|
||||
db.internalIds[index.get()] = valID
|
||||
some(valID)
|
||||
else:
|
||||
none(ValidatorInternalID)
|
||||
|
||||
proc checkSlashableBlockProposal*(
|
||||
proc checkSlashableBlockProposalOther(
|
||||
db: SlashingProtectionDB_v2,
|
||||
validator: ValidatorPubKey,
|
||||
valID: ValidatorInternalID,
|
||||
slot: Slot
|
||||
): Result[void, BadProposal] =
|
||||
## Returns an error if the specified validator
|
||||
@ -706,49 +708,6 @@ proc checkSlashableBlockProposal*(
|
||||
## Returns success otherwise
|
||||
# TODO distinct type for the result block root
|
||||
|
||||
let valID = block:
|
||||
let id = db.getValidatorInternalID(validator)
|
||||
if id.isNone():
|
||||
notice "No slashing protection data - first block proposal?",
|
||||
validator = validator,
|
||||
slot = slot
|
||||
return ok()
|
||||
else:
|
||||
id.unsafeGet()
|
||||
|
||||
# Casper FFG 1st slashing condition
|
||||
# Detect h(t1) = h(t2)
|
||||
# ---------------------------------
|
||||
block:
|
||||
# Condition 1 at https://eips.ethereum.org/EIPS/eip-3076
|
||||
var root: ETH2Digest
|
||||
|
||||
# 6 second (minimal preset) slots => overflow at ~1.75 trillion years under
|
||||
# minimal preset, and twice that with mainnet preset
|
||||
doAssert slot <= high(int64).uint64
|
||||
|
||||
let status = db.sqlBlockForSameSlot.exec(
|
||||
(valID, int64 slot)
|
||||
) do (res: Hash32):
|
||||
root.data = res
|
||||
|
||||
# Note: we enforce at the DB level that if (pubkey, slot) exists it maps to a unique block root.
|
||||
#
|
||||
# It's possible to allow republishing an already signed block here (Lighthouse does it)
|
||||
# AFAIK repeat signing only happens if the node crashes after saving to the DB and
|
||||
# there is still time to redo the validator work but:
|
||||
# - will the validator have reconstructed the same state in memory?
|
||||
# for example if the validator has different attestations
|
||||
# it can't reconstruct the previous signed block anyway.
|
||||
# - it is useful if the validator couldn't gossip.
|
||||
# Rather than adding Result "Ok" and Result "OkRepeatSigning"
|
||||
# and an extra Eth2Digest comparison for that case, we just refuse repeat signing.
|
||||
if status.foundAnyResult():
|
||||
# Conflicting block exist
|
||||
return err(BadProposal(
|
||||
kind: DoubleProposal,
|
||||
existing_block: root))
|
||||
|
||||
# EIP-3067 - Low-watermark
|
||||
# Detect h(t1) <= h(t2)
|
||||
# ---------------------------------
|
||||
@ -778,38 +737,88 @@ proc checkSlashableBlockProposal*(
|
||||
|
||||
ok()
|
||||
|
||||
proc checkSlashableAttestation*(
|
||||
proc checkSlashableBlockProposalDoubleProposal(
|
||||
db: SlashingProtectionDB_v2,
|
||||
validator: ValidatorPubKey,
|
||||
source: Epoch,
|
||||
target: Epoch
|
||||
): Result[void, BadVote] =
|
||||
valID: ValidatorInternalID,
|
||||
slot: Slot
|
||||
): Result[void, BadProposal] =
|
||||
## Returns an error if the specified validator
|
||||
## already voted for the specified slot
|
||||
## or would vote in a contradiction to previous votes
|
||||
## (surrounding vote or surrounded vote).
|
||||
## already proposed a block for the specified slot.
|
||||
## This would lead to slashing.
|
||||
## The error contains the blockroot that was already proposed
|
||||
##
|
||||
## Returns success otherwise
|
||||
# TODO distinct type for the result attestation root
|
||||
# TODO distinct type for the result block root
|
||||
|
||||
# Casper FFG 1st slashing condition
|
||||
# Detect h(t1) = h(t2)
|
||||
# ---------------------------------
|
||||
block:
|
||||
# Condition 1 at https://eips.ethereum.org/EIPS/eip-3076
|
||||
var root: ETH2Digest
|
||||
let status = db.sqlBlockForSameSlot.exec(
|
||||
(valID, int64 slot)
|
||||
) do (res: Hash32):
|
||||
root.data = res
|
||||
|
||||
# Note: we enforce at the DB level that if (pubkey, slot) exists it maps to a unique block root.
|
||||
#
|
||||
# It's possible to allow republishing an already signed block here (Lighthouse does it)
|
||||
# AFAIK repeat signing only happens if the node crashes after saving to the DB and
|
||||
# there is still time to redo the validator work but:
|
||||
# - will the validator have reconstructed the same state in memory?
|
||||
# for example if the validator has different attestations
|
||||
# it can't reconstruct the previous signed block anyway.
|
||||
# - it is useful if the validator couldn't gossip.
|
||||
# Rather than adding Result "Ok" and Result "OkRepeatSigning"
|
||||
# and an extra Eth2Digest comparison for that case, we just refuse repeat signing.
|
||||
if status.foundAnyResult():
|
||||
# Conflicting block exist
|
||||
return err(BadProposal(
|
||||
kind: DoubleProposal,
|
||||
existing_block: root))
|
||||
|
||||
ok()
|
||||
|
||||
proc checkSlashableBlockProposal*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
index: Option[ValidatorIndex],
|
||||
validator: ValidatorPubKey,
|
||||
slot: Slot
|
||||
): Result[void, BadProposal] =
|
||||
## Returns an error if the specified validator
|
||||
## already proposed a block for the specified slot.
|
||||
## This would lead to slashing.
|
||||
## The error contains the blockroot that was already proposed
|
||||
##
|
||||
## Returns success otherwise
|
||||
# TODO distinct type for the result block root
|
||||
|
||||
let valID = block:
|
||||
let id = db.getValidatorInternalID(index, validator)
|
||||
if id.isNone():
|
||||
notice "No slashing protection data - first block proposal?",
|
||||
validator = validator,
|
||||
slot = slot
|
||||
return ok()
|
||||
else:
|
||||
id.unsafeGet()
|
||||
|
||||
? checkSlashableBlockProposalDoubleProposal(db, valID, slot)
|
||||
? checkSlashableBlockProposalOther(db, valID, slot)
|
||||
|
||||
ok()
|
||||
|
||||
proc checkSlashableAttestationDoubleVote(
|
||||
db: SlashingProtectionDB_v2,
|
||||
valID: ValidatorInternalID,
|
||||
source: Epoch,
|
||||
target: Epoch): Result[void, BadVote] =
|
||||
# Sanity
|
||||
# ---------------------------------
|
||||
if source > target:
|
||||
return err(BadVote(kind: TargetPrecedesSource))
|
||||
|
||||
# Internal metadata
|
||||
# ---------------------------------
|
||||
let valID = block:
|
||||
let id = db.getValidatorInternalID(validator)
|
||||
if id.isNone():
|
||||
notice "No slashing protection data - first attestation?",
|
||||
validator = validator,
|
||||
attSource = source,
|
||||
attTarget = target
|
||||
return ok()
|
||||
else:
|
||||
id.unsafeGet()
|
||||
|
||||
# Casper FFG 1st slashing condition
|
||||
# Detect h(t1) = h(t2)
|
||||
# ---------------------------------
|
||||
@ -833,12 +842,38 @@ proc checkSlashableAttestation*(
|
||||
existingAttestation: root
|
||||
))
|
||||
|
||||
ok()
|
||||
|
||||
proc checkSlashableAttestationOther(
|
||||
db: SlashingProtectionDB_v2,
|
||||
valID: ValidatorInternalID,
|
||||
source: Epoch,
|
||||
target: Epoch): Result[void, BadVote] =
|
||||
# Simple double votes are protected by the unique index on the database table
|
||||
# - this function checks everything else!
|
||||
|
||||
## Returns an error if the specified validator
|
||||
## already voted for the specified slot
|
||||
## or would vote in a contradiction to previous votes
|
||||
## (surrounding vote or surrounded vote).
|
||||
##
|
||||
## Returns success otherwise
|
||||
# TODO distinct type for the result attestation root
|
||||
|
||||
# Sanity
|
||||
# ---------------------------------
|
||||
if source > target:
|
||||
return err(BadVote(kind: TargetPrecedesSource))
|
||||
|
||||
# Casper FFG 2nd slashing condition
|
||||
# -> Surrounded vote
|
||||
# Detect h(s1) < h(s2) < h(t2) < h(t1)
|
||||
# -> Surrounding vote
|
||||
# Detect h(s2) < h(s1) < h(t1) < h(t2)
|
||||
# ---------------------------------
|
||||
block:
|
||||
# Condition 3 part 2/3 at https://eips.ethereum.org/EIPS/eip-3076
|
||||
# Condition 3 part 3/3 at https://eips.ethereum.org/EIPS/eip-3076
|
||||
var root: ETH2Digest
|
||||
var db_source, db_target: Epoch
|
||||
|
||||
@ -846,8 +881,8 @@ proc checkSlashableAttestation*(
|
||||
doAssert source <= high(int64).uint64
|
||||
doAssert target <= high(int64).uint64
|
||||
|
||||
let status = db.sqlAttSurrounded.exec(
|
||||
(valID, int64 source, int64 target)
|
||||
let status = db.sqlAttSurrounds.exec(
|
||||
(valID, int64 source, int64 target, int64 source, int64 target)
|
||||
) do (res: tuple[source, target: int64, root: Hash32]):
|
||||
db_source = Epoch res.source
|
||||
db_target = Epoch res.target
|
||||
@ -858,35 +893,7 @@ proc checkSlashableAttestation*(
|
||||
# Conflicting attestation exist, log by caller
|
||||
# s1 < s2 < t2 < t1
|
||||
return err(BadVote(
|
||||
kind: SurroundedVote,
|
||||
existingAttestationRoot: root,
|
||||
sourceExisting: db_source,
|
||||
targetExisting: db_target,
|
||||
sourceSlashable: source,
|
||||
targetSlashable: target
|
||||
))
|
||||
|
||||
# Casper FFG 2nd slashing condition
|
||||
# -> Surrounding vote
|
||||
# Detect h(s2) < h(s1) < h(t1) < h(t2)
|
||||
# ---------------------------------
|
||||
block:
|
||||
# Condition 3 part 3/3 at https://eips.ethereum.org/EIPS/eip-3076
|
||||
var root: ETH2Digest
|
||||
var db_source, db_target: Epoch
|
||||
let status = db.sqlAttSurrounding.exec(
|
||||
(valID, int64 source, int64 target)
|
||||
) do (res: tuple[source, target: int64, root: Hash32]):
|
||||
db_source = Epoch res.source
|
||||
db_target = Epoch res.target
|
||||
root.data = res.root
|
||||
|
||||
# Note: we enforce at the DB level that if (pubkey, target) exists it maps to a unique block root.
|
||||
if status.foundAnyResult():
|
||||
# Conflicting attestation exist, log by caller
|
||||
# s1 < s2 < t2 < t1
|
||||
return err(BadVote(
|
||||
kind: SurroundingVote,
|
||||
kind: SurroundVote,
|
||||
existingAttestationRoot: root,
|
||||
sourceExisting: db_source,
|
||||
targetExisting: db_target,
|
||||
@ -933,7 +940,31 @@ proc checkSlashableAttestation*(
|
||||
candidateTarget: target
|
||||
))
|
||||
|
||||
return ok()
|
||||
ok()
|
||||
|
||||
proc checkSlashableAttestation*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
index: Option[ValidatorIndex],
|
||||
validator: ValidatorPubKey,
|
||||
source: Epoch,
|
||||
target: Epoch
|
||||
): Result[void, BadVote] =
|
||||
if source > target:
|
||||
return err(BadVote(kind: TargetPrecedesSource))
|
||||
|
||||
let valID = block:
|
||||
let id = db.getValidatorInternalID(index, validator)
|
||||
if id.isNone():
|
||||
notice "No slashing protection data - first attestation?",
|
||||
validator, source, target
|
||||
return ok()
|
||||
else:
|
||||
id.unsafeGet()
|
||||
|
||||
? checkSlashableAttestationDoubleVote(db, valID, source, target)
|
||||
? checkSlashableAttestationOther(db, valID, source, target)
|
||||
|
||||
ok()
|
||||
|
||||
# DB update
|
||||
# --------------------------------------------
|
||||
@ -948,16 +979,17 @@ proc registerValidator(db: SlashingProtectionDB_v2, validator: ValidatorPubKey)
|
||||
|
||||
proc getOrRegisterValidator(
|
||||
db: SlashingProtectionDB_v2,
|
||||
index: Option[ValidatorIndex],
|
||||
validator: ValidatorPubKey): ValidatorInternalID =
|
||||
## Get validator from the database
|
||||
## or register it and then return it
|
||||
let id = db.getValidatorInternalID(validator)
|
||||
let id = db.getValidatorInternalID(index, validator)
|
||||
if id.isNone():
|
||||
info "No slashing protection data for validator - initiating",
|
||||
validator = validator
|
||||
|
||||
db.registerValidator(validator)
|
||||
let id = db.getValidatorInternalID(validator)
|
||||
let id = db.getValidatorInternalID(index, validator)
|
||||
doAssert id.isSome()
|
||||
id.unsafeGet()
|
||||
else:
|
||||
@ -965,33 +997,65 @@ proc getOrRegisterValidator(
|
||||
|
||||
proc registerBlock*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
index: Option[ValidatorIndex],
|
||||
validator: ValidatorPubKey,
|
||||
slot: Slot, block_root: Eth2Digest) =
|
||||
slot: Slot, block_root: Eth2Digest): Result[void, BadProposal] =
|
||||
## Add a block to the slashing protection DB
|
||||
## `checkSlashableBlockProposal` MUST be run
|
||||
## before to ensure no overwrite.
|
||||
let valID = db.getOrRegisterValidator(validator)
|
||||
let valID = db.getOrRegisterValidator(index, validator)
|
||||
|
||||
# 6 second (minimal preset) slots => overflow at ~1.75 trillion years under
|
||||
# minimal preset, and twice that with mainnet preset
|
||||
doAssert slot <= high(int64).uint64
|
||||
|
||||
let check = checkSlashableBlockProposalOther(db, valID, slot)
|
||||
if check.isErr():
|
||||
# Check for double vote to get more accurate error information
|
||||
? checkSlashableBlockProposalDoubleProposal(db, valID, slot)
|
||||
return check
|
||||
|
||||
let status = db.sqlInsertBlock.exec(
|
||||
(valID, int64 slot,
|
||||
block_root.data))
|
||||
doAssert status.isOk(),
|
||||
"SQLite error when registering block: " & $status.error & "\n" &
|
||||
"for validator: 0x" & validator.toHex() & ", slot: " & $slot
|
||||
(valID, int64 slot, block_root.data))
|
||||
if status.isErr():
|
||||
# Inserting primarily fails when the constraint for double proposals is
|
||||
# violated but may also happen due to disk full and other storage issues -
|
||||
# in any case, we'll return an error so that production is halted
|
||||
? checkSlashableBlockProposalDoubleProposal(db, valID, slot)
|
||||
# If this was not a slashing error, it must have been a database error
|
||||
return err(BadProposal(
|
||||
kind: BadProposalKind.DatabaseError,
|
||||
message: status.error))
|
||||
|
||||
ok()
|
||||
|
||||
proc registerBlock*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
validator: ValidatorPubKey,
|
||||
slot: Slot, block_root: Eth2Digest): Result[void, BadProposal] =
|
||||
registerBlock(db, none(ValidatorIndex), validator, slot, block_root)
|
||||
|
||||
proc registerAttestation*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
index: Option[ValidatorIndex],
|
||||
validator: ValidatorPubKey,
|
||||
source, target: Epoch,
|
||||
attestation_root: Eth2Digest) =
|
||||
attestation_root: Eth2Digest): Result[void, BadVote] =
|
||||
## Add an attestation to the slashing protection DB
|
||||
## `checkSlashableAttestation` MUST be run
|
||||
## before to ensure no overwrite.
|
||||
let valID = db.getOrRegisterValidator(validator)
|
||||
if source > target:
|
||||
return err(BadVote(kind: TargetPrecedesSource))
|
||||
|
||||
let valID = db.getOrRegisterValidator(index, validator)
|
||||
|
||||
# Double votes caught by database index!
|
||||
let check = checkSlashableAttestationOther(db, valID, source, target)
|
||||
|
||||
if check.isErr():
|
||||
# Check for double vote to get more accurate error information
|
||||
? checkSlashableAttestationDoubleVote(db, valID, source, target)
|
||||
return check
|
||||
|
||||
# Overflows in 14 trillion years (minimal) or 112 trillion years (mainnet)
|
||||
doAssert source <= high(int64).uint64
|
||||
@ -1000,27 +1064,49 @@ proc registerAttestation*(
|
||||
let status = db.sqlInsertAtt.exec(
|
||||
(valID, int64 source, int64 target,
|
||||
attestation_root.data))
|
||||
doAssert status.isOk(),
|
||||
"SQLite error when registering attestation: " & $status.error & "\n" &
|
||||
"for validator: 0x" & validator.toHex() &
|
||||
", sourceEpoch: " & $source &
|
||||
", targetEpoch: " & $target
|
||||
if status.isErr():
|
||||
# Inserting primarily fails when the constraint for double votes is
|
||||
# violated but may also happen due to disk full and other storage issues -
|
||||
# in any case, we'll return an error so that production is halted
|
||||
? checkSlashableAttestationDoubleVote(db, valID, source, target)
|
||||
# If this was not a slashing error, it must have been a database error
|
||||
return err(BadVote(
|
||||
kind: BadVoteKind.DatabaseError,
|
||||
message: status.error))
|
||||
|
||||
ok()
|
||||
|
||||
proc registerAttestation*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
validator: ValidatorPubKey,
|
||||
source, target: Epoch,
|
||||
attestation_root: Eth2Digest): Result[void, BadVote] =
|
||||
registerAttestation(
|
||||
db, none(ValidatorIndex), validator, source, target, attestation_root)
|
||||
# DB maintenance
|
||||
# --------------------------------------------
|
||||
proc pruneBlocks*(db: SlashingProtectionDB_v2, validator: ValidatorPubkey, newMinSlot: Slot) =
|
||||
proc pruneBlocks*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
index: Option[ValidatorIndex],
|
||||
validator: ValidatorPubkey, newMinSlot: Slot) =
|
||||
## Prune all blocks from a validator before the specified newMinSlot
|
||||
## This is intended for interchange import to ensure
|
||||
## that in case of a gap, we don't allow signing in that gap.
|
||||
let valID = db.getOrRegisterValidator(validator)
|
||||
let valID = db.getOrRegisterValidator(index, validator)
|
||||
let status = db.sqlPruneValidatorBlocks.exec(
|
||||
(valID, int64 newMinSlot))
|
||||
doAssert status.isOk(),
|
||||
"SQLite error when pruning validator blocks: " & $status.error & "\n" &
|
||||
"for validator: 0x" & validator.toHex() & ", newMinSlot: " & $newMinSlot
|
||||
|
||||
proc pruneBlocks*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
validator: ValidatorPubkey, newMinSlot: Slot) =
|
||||
pruneBlocks(db, none(ValidatorIndex), validator, newMinSlot)
|
||||
|
||||
proc pruneAttestations*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
index: Option[ValidatorIndex],
|
||||
validator: ValidatorPubkey,
|
||||
newMinSourceEpoch: int64,
|
||||
newMinTargetEpoch: int64) =
|
||||
@ -1028,7 +1114,7 @@ proc pruneAttestations*(
|
||||
## This is intended for interchange import.
|
||||
## Negative source/target epoch of -1 can be received if no attestation was imported
|
||||
## In that case nothing is done (since we used signed int in SQLite)
|
||||
let valID = db.getOrRegisterValidator(validator)
|
||||
let valID = db.getOrRegisterValidator(index, validator)
|
||||
|
||||
let status = db.sqlPruneValidatorAttestations.exec(
|
||||
(valID, newMinSourceEpoch, newMinTargetEpoch))
|
||||
@ -1038,6 +1124,14 @@ proc pruneAttestations*(
|
||||
", newSourceEpoch: " & $newMinSourceEpoch &
|
||||
", newTargetEpoch: " & $newMinTargetEpoch
|
||||
|
||||
proc pruneAttestations*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
validator: ValidatorPubkey,
|
||||
newMinSourceEpoch: int64,
|
||||
newMinTargetEpoch: int64) =
|
||||
pruneAttestations(
|
||||
db, none(ValidatorIndex), validator, newMinSourceEpoch, newMinTargetEpoch)
|
||||
|
||||
proc pruneAfterFinalization*(
|
||||
db: SlashingProtectionDB_v2,
|
||||
finalizedEpoch: Epoch
|
||||
@ -1205,8 +1299,3 @@ proc inclSPDIR*(db: SlashingProtectionDB_v2, spdir: SPDIR): SlashingImportStatus
|
||||
# Create a mutable copy for sorting
|
||||
var spdir = spdir
|
||||
return db.importInterchangeV5Impl(spdir)
|
||||
|
||||
# Sanity check
|
||||
# --------------------------------------------------------------
|
||||
|
||||
static: doAssert SlashingProtectionDB_v2 is SlashingProtectionDB_Concept
|
||||
|
@ -338,27 +338,18 @@ proc proposeBlock(node: BeaconNode,
|
||||
slot = shortLog(slot)
|
||||
return head
|
||||
|
||||
let notSlashable = node.attachedValidators
|
||||
.slashingProtection
|
||||
.checkSlashableBlockProposal(validator.pubkey, slot)
|
||||
if notSlashable.isErr:
|
||||
warn "Slashing protection activated",
|
||||
validator = validator.pubkey,
|
||||
slot = slot,
|
||||
existingProposal = notSlashable.error
|
||||
return head
|
||||
|
||||
let
|
||||
fork = getStateField(node.chainDag.headState, fork)
|
||||
genesis_validators_root =
|
||||
getStateField(node.chainDag.headState, genesis_validators_root)
|
||||
let
|
||||
randao = await validator.genRandaoReveal(
|
||||
fork, genesis_validators_root, slot)
|
||||
message = makeBeaconBlockForHeadAndSlot(
|
||||
node, randao, validator_index, node.graffitiBytes, head, slot)
|
||||
|
||||
if not message.isSome():
|
||||
return head # already logged elsewhere!
|
||||
|
||||
var
|
||||
newBlock = SignedBeaconBlock(
|
||||
message: message.get()
|
||||
@ -369,9 +360,16 @@ proc proposeBlock(node: BeaconNode,
|
||||
# TODO: recomputed in block proposal
|
||||
let signing_root = compute_block_root(
|
||||
fork, genesis_validators_root, slot, newBlock.root)
|
||||
node.attachedValidators
|
||||
let notSlashable = node.attachedValidators
|
||||
.slashingProtection
|
||||
.registerBlock(validator.pubkey, slot, signing_root)
|
||||
.registerBlock(validator_index, validator.pubkey, slot, signing_root)
|
||||
|
||||
if notSlashable.isErr:
|
||||
warn "Slashing protection activated",
|
||||
validator = validator.pubkey,
|
||||
slot = slot,
|
||||
existingProposal = notSlashable.error
|
||||
return head
|
||||
|
||||
newBlock.signature = await validator.signBlockProposal(
|
||||
fork, genesis_validators_root, slot, newBlock.root)
|
||||
@ -409,7 +407,7 @@ proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
|
||||
|
||||
var attestations: seq[tuple[
|
||||
data: AttestationData, committeeLen, indexInCommittee: int,
|
||||
validator: AttachedValidator]]
|
||||
validator: AttachedValidator, validator_index: ValidatorIndex]]
|
||||
|
||||
# We need to run attestations exactly for the slot that we're attesting to.
|
||||
# In case blocks went missing, this means advancing past the latest block
|
||||
@ -429,34 +427,28 @@ proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
|
||||
let committee = get_beacon_committee(
|
||||
epochRef, slot, committee_index.CommitteeIndex)
|
||||
|
||||
for index_in_committee, validatorIdx in committee:
|
||||
let validator = node.getAttachedValidator(epochRef, validatorIdx)
|
||||
for index_in_committee, validator_index in committee:
|
||||
let validator = node.getAttachedValidator(epochRef, validator_index)
|
||||
if validator != nil:
|
||||
let ad = makeAttestationData(
|
||||
epochRef, attestationHead, committee_index.CommitteeIndex)
|
||||
attestations.add((ad, committee.len, index_in_committee, validator))
|
||||
attestations.add(
|
||||
(ad, committee.len, index_in_committee, validator, validator_index))
|
||||
|
||||
for a in attestations:
|
||||
# TODO signing_root is recomputed in produceAndSignAttestation/signAttestation just after
|
||||
let signing_root = compute_attestation_root(
|
||||
fork, genesis_validators_root, a.data)
|
||||
let notSlashable = node.attachedValidators
|
||||
.slashingProtection
|
||||
.checkSlashableAttestation(
|
||||
a.validator.pubkey,
|
||||
a.data.source.epoch,
|
||||
a.data.target.epoch)
|
||||
|
||||
.slashingProtection
|
||||
.registerAttestation(
|
||||
a.validator_index,
|
||||
a.validator.pubkey,
|
||||
a.data.source.epoch,
|
||||
a.data.target.epoch,
|
||||
signing_root
|
||||
)
|
||||
if notSlashable.isOk():
|
||||
# TODO signing_root is recomputed in produceAndSignAttestation/signAttestation just after
|
||||
let signing_root = compute_attestation_root(
|
||||
fork, genesis_validators_root, a.data)
|
||||
node.attachedValidators
|
||||
.slashingProtection
|
||||
.registerAttestation(
|
||||
a.validator.pubkey,
|
||||
a.data.source.epoch,
|
||||
a.data.target.epoch,
|
||||
signing_root
|
||||
)
|
||||
|
||||
traceAsyncErrors createAndSendAttestation(
|
||||
node, fork, genesis_validators_root, a.validator, a.data,
|
||||
a.committeeLen, a.indexInCommittee, num_active_validators)
|
||||
|
@ -11,7 +11,7 @@ import
|
||||
# Standard library
|
||||
std/[os],
|
||||
# Status lib
|
||||
eth/db/kvstore,
|
||||
eth/db/[kvstore, kvstore_sqlite3],
|
||||
stew/results,
|
||||
nimcrypto/utils,
|
||||
serialization,
|
||||
@ -25,25 +25,6 @@ import
|
||||
# Test utilies
|
||||
../testutil
|
||||
|
||||
template wrappedTimedTest(name: string, body: untyped) =
|
||||
# `check` macro takes a copy of whatever it's checking, on the stack!
|
||||
block: # Symbol namespacing
|
||||
proc wrappedTest() =
|
||||
test name:
|
||||
body
|
||||
wrappedTest()
|
||||
|
||||
func fakeRoot(index: SomeInteger): Eth2Digest =
|
||||
## Create fake roots
|
||||
## Those are just the value serialized in big-endian
|
||||
## We prevent zero hash special case via a power of 2 prefix
|
||||
result.data[0 ..< 8] = (1'u64 shl 32 + index.uint64).toBytesBE()
|
||||
|
||||
func fakeValidator(index: SomeInteger): ValidatorPubKey =
|
||||
## Create fake validator public key
|
||||
result = ValidatorPubKey()
|
||||
result.blob[0 ..< 8] = (1'u64 shl 48 + index.uint64).toBytesBE()
|
||||
|
||||
func hexToDigest(hex: string): Eth2Digest =
|
||||
result = Eth2Digest.fromHex(hex)
|
||||
|
||||
@ -59,7 +40,7 @@ suite "Slashing Protection DB - v1 and v2 migration" & preset():
|
||||
# https://eips.ethereum.org/EIPS/eip-3076
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
wrappedTimedTest "Minimal format migration" & preset():
|
||||
test "Minimal format migration" & preset():
|
||||
let genesis_validators_root = hexToDigest"0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673"
|
||||
block: # export from a v1 DB
|
||||
let db = SlashingProtectionDB_v1.init(
|
||||
@ -71,18 +52,19 @@ suite "Slashing Protection DB - v1 and v2 migration" & preset():
|
||||
let pubkey = ValidatorPubKey
|
||||
.fromHex"0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed"
|
||||
.get()
|
||||
db.registerBlock(
|
||||
pubkey,
|
||||
Slot 81952,
|
||||
Eth2Digest()
|
||||
)
|
||||
check:
|
||||
db.registerBlock(
|
||||
pubkey,
|
||||
Slot 81952,
|
||||
Eth2Digest()
|
||||
).isOk()
|
||||
|
||||
db.registerAttestation(
|
||||
pubkey,
|
||||
source = Epoch 2290,
|
||||
target = Epoch 3007,
|
||||
Eth2Digest()
|
||||
)
|
||||
db.registerAttestation(
|
||||
pubkey,
|
||||
source = Epoch 2290,
|
||||
target = Epoch 3007,
|
||||
Eth2Digest()
|
||||
).isOk()
|
||||
|
||||
let spdir = db.toSPDIR_lowWatermark()
|
||||
Json.saveFile(
|
||||
|
@ -13,7 +13,7 @@ import
|
||||
nimcrypto/utils,
|
||||
chronicles,
|
||||
# Internal
|
||||
../../beacon_chain/validators/slashing_protection,
|
||||
../../beacon_chain/validators/[slashing_protection, slashing_protection_v2],
|
||||
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
|
||||
# Test utilies
|
||||
../testutil, ../testdbutil,
|
||||
@ -174,7 +174,7 @@ proc runTest(identifier: string) =
|
||||
" " & $status & "\n"
|
||||
|
||||
for blck in step.blocks:
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
let status = db.db_v2.checkSlashableBlockProposal(none(ValidatorIndex),
|
||||
ValidatorPubKey.fromRaw(blck.pubkey.PubKeyBytes).get(),
|
||||
Slot blck.slot
|
||||
)
|
||||
@ -190,7 +190,7 @@ proc runTest(identifier: string) =
|
||||
" for " & $toHexLogs(blck)
|
||||
|
||||
for att in step.attestations:
|
||||
let status = db.checkSlashableAttestation(
|
||||
let status = db.db_v2.checkSlashableAttestation(none(ValidatorIndex),
|
||||
ValidatorPubKey.fromRaw(att.pubkey.PubKeyBytes).get(),
|
||||
Epoch att.source_epoch,
|
||||
Epoch att.target_epoch
|
||||
|
@ -11,11 +11,11 @@ import
|
||||
# Standard library
|
||||
std/[os],
|
||||
# Status lib
|
||||
eth/db/kvstore,
|
||||
eth/db/[kvstore, kvstore_sqlite3],
|
||||
stew/results,
|
||||
nimcrypto/utils,
|
||||
# Internal
|
||||
../../beacon_chain/validators/slashing_protection,
|
||||
../../beacon_chain/validators/[slashing_protection, slashing_protection_v2],
|
||||
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
|
||||
# Test utilies
|
||||
../testutil
|
||||
@ -62,29 +62,30 @@ suite "Slashing Protection DB - Interchange" & preset():
|
||||
let pubkey = ValidatorPubKey
|
||||
.fromHex"0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed"
|
||||
.get()
|
||||
db.registerBlock(
|
||||
pubkey,
|
||||
Slot 81952,
|
||||
hexToDigest"0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b"
|
||||
)
|
||||
# db.registerBlock(
|
||||
# pubkey,
|
||||
# Slot 81951,
|
||||
# fakeRoot(65535)
|
||||
# )
|
||||
check:
|
||||
db.db_v2.registerBlock(
|
||||
pubkey,
|
||||
Slot 81952,
|
||||
hexToDigest"0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b"
|
||||
).isOk()
|
||||
# db.registerBlock(
|
||||
# pubkey,
|
||||
# Slot 81951,
|
||||
# fakeRoot(65535)
|
||||
# )
|
||||
|
||||
db.registerAttestation(
|
||||
pubkey,
|
||||
source = Epoch 2290,
|
||||
target = Epoch 3007,
|
||||
hexToDigest"0x587d6a4f59a58fe24f406e0502413e77fe1babddee641fda30034ed37ecc884d"
|
||||
)
|
||||
db.registerAttestation(
|
||||
pubkey,
|
||||
source = Epoch 2290,
|
||||
target = Epoch 3008,
|
||||
fakeRoot(65535)
|
||||
)
|
||||
db.db_v2.registerAttestation(
|
||||
pubkey,
|
||||
source = Epoch 2290,
|
||||
target = Epoch 3007,
|
||||
hexToDigest"0x587d6a4f59a58fe24f406e0502413e77fe1babddee641fda30034ed37ecc884d"
|
||||
).isOk()
|
||||
db.db_v2.registerAttestation(
|
||||
pubkey,
|
||||
source = Epoch 2290,
|
||||
target = Epoch 3008,
|
||||
fakeRoot(65535)
|
||||
).isOk()
|
||||
|
||||
db.exportSlashingInterchange(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")
|
||||
|
||||
|
@ -11,7 +11,7 @@ import
|
||||
# Standard library
|
||||
std/[os],
|
||||
# Status lib
|
||||
eth/db/kvstore,
|
||||
eth/db/[kvstore, kvstore_sqlite3],
|
||||
stew/results,
|
||||
# Internal
|
||||
../../beacon_chain/validators/slashing_protection,
|
||||
@ -19,14 +19,6 @@ import
|
||||
# Test utilies
|
||||
../testutil
|
||||
|
||||
template wrappedTimedTest(name: string, body: untyped) =
|
||||
# `check` macro takes a copy of whatever it's checking, on the stack!
|
||||
block: # Symbol namespacing
|
||||
proc wrappedTest() =
|
||||
test name:
|
||||
body
|
||||
wrappedTest()
|
||||
|
||||
func fakeRoot(index: SomeInteger): Eth2Digest =
|
||||
## Create fake roots
|
||||
## Those are just the value serialized in big-endian
|
||||
@ -57,7 +49,7 @@ const TestDbName = "test_slashprot"
|
||||
# - (validator_id, slot)
|
||||
|
||||
suite "Slashing Protection DB" & preset():
|
||||
wrappedTimedTest "Empty database" & preset():
|
||||
test "Empty database" & preset():
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
let db = SlashingProtectionDB.init(
|
||||
default(Eth2Digest),
|
||||
@ -70,21 +62,24 @@ suite "Slashing Protection DB" & preset():
|
||||
|
||||
check:
|
||||
db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(1234),
|
||||
fakeValidator(1234),
|
||||
slot = Slot 1
|
||||
).isOk()
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(1234),
|
||||
fakeValidator(1234),
|
||||
source = Epoch 1,
|
||||
target = Epoch 2
|
||||
).isOk()
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(1234),
|
||||
fakeValidator(1234),
|
||||
source = Epoch 2,
|
||||
target = Epoch 1
|
||||
).error.kind == TargetPrecedesSource
|
||||
|
||||
wrappedTimedTest "SP for block proposal - linear append":
|
||||
test "SP for block proposal - linear append":
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
let db = SlashingProtectionDB.init(
|
||||
default(Eth2Digest),
|
||||
@ -95,53 +90,86 @@ suite "Slashing Protection DB" & preset():
|
||||
db.close()
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
db.registerBlock(
|
||||
fakeValidator(100),
|
||||
Slot 10,
|
||||
fakeRoot(100)
|
||||
)
|
||||
db.registerBlock(
|
||||
fakeValidator(111),
|
||||
Slot 15,
|
||||
fakeRoot(111)
|
||||
)
|
||||
check:
|
||||
db.registerBlock(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot 10,
|
||||
fakeRoot(100)
|
||||
).isOk()
|
||||
db.registerBlock(
|
||||
ValidatorIndex(111),
|
||||
fakeValidator(111),
|
||||
Slot 15,
|
||||
fakeRoot(111)
|
||||
).isOk()
|
||||
|
||||
# Slot occupied by same validator
|
||||
db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
slot = Slot 10
|
||||
).isErr()
|
||||
db.registerBlock(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
slot = Slot 10,
|
||||
fakeRoot(101)
|
||||
).isErr()
|
||||
# Slot occupied by another validator
|
||||
db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
slot = Slot 15
|
||||
).isOk()
|
||||
db.registerBlock(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
slot = Slot 15,
|
||||
fakeRoot(150)
|
||||
).isOk()
|
||||
# Slot occupied by same validator
|
||||
db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(111),
|
||||
fakeValidator(111),
|
||||
slot = Slot 15
|
||||
).isErr()
|
||||
db.registerBlock(
|
||||
ValidatorIndex(111),
|
||||
fakeValidator(111),
|
||||
slot = Slot 15,
|
||||
fakeRoot(151)
|
||||
).isErr()
|
||||
|
||||
# Slot inoccupied
|
||||
db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(255),
|
||||
fakeValidator(255),
|
||||
slot = Slot 20
|
||||
).isOk()
|
||||
|
||||
db.registerBlock(
|
||||
fakeValidator(255),
|
||||
slot = Slot 20,
|
||||
fakeRoot(4321)
|
||||
)
|
||||
db.registerBlock(
|
||||
ValidatorIndex(255),
|
||||
fakeValidator(255),
|
||||
slot = Slot 20,
|
||||
fakeRoot(4321)
|
||||
).isOk()
|
||||
|
||||
check:
|
||||
# Slot now occupied
|
||||
db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(255),
|
||||
fakeValidator(255),
|
||||
slot = Slot 20
|
||||
).isErr()
|
||||
db.registerBlock(
|
||||
ValidatorIndex(255),
|
||||
fakeValidator(255),
|
||||
slot = Slot 20,
|
||||
fakeRoot(4322)
|
||||
).isErr()
|
||||
|
||||
wrappedTimedTest "SP for block proposal - backtracking append":
|
||||
test "SP for block proposal - backtracking append":
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
let db = SlashingProtectionDB.init(
|
||||
default(Eth2Digest),
|
||||
@ -153,111 +181,129 @@ suite "Slashing Protection DB" & preset():
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
# last finalized block
|
||||
db.registerBlock(
|
||||
fakeValidator(0),
|
||||
Slot 0,
|
||||
fakeRoot(0)
|
||||
)
|
||||
check:
|
||||
db.registerBlock(
|
||||
ValidatorIndex(0),
|
||||
fakeValidator(0),
|
||||
Slot 0,
|
||||
fakeRoot(0)
|
||||
).isOk()
|
||||
|
||||
db.registerBlock(
|
||||
fakeValidator(100),
|
||||
Slot 10,
|
||||
fakeRoot(10)
|
||||
)
|
||||
db.registerBlock(
|
||||
fakeValidator(100),
|
||||
Slot 20,
|
||||
fakeRoot(20)
|
||||
)
|
||||
db.registerBlock(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot 10,
|
||||
fakeRoot(10)
|
||||
).isOk()
|
||||
db.registerBlock(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot 20,
|
||||
fakeRoot(20)
|
||||
).isOk()
|
||||
for i in 0 ..< 30:
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot i
|
||||
)
|
||||
if i > 10 and i != 20: # MinSlotViolation and DupSlot
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
fakeValidator(100),
|
||||
Slot i
|
||||
)
|
||||
doAssert status.isOk, "error: " & $status
|
||||
else:
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
fakeValidator(100),
|
||||
Slot i
|
||||
)
|
||||
doAssert status.isErr, "error: " & $status
|
||||
db.registerBlock(
|
||||
fakeValidator(100),
|
||||
Slot 15,
|
||||
fakeRoot(15)
|
||||
)
|
||||
check:
|
||||
db.registerBlock(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot 15,
|
||||
fakeRoot(15)
|
||||
).isOk()
|
||||
for i in 0 ..< 30:
|
||||
if i > 10 and i notin {15, 20}: # MinSlotViolation and DupSlot
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot i
|
||||
)
|
||||
doAssert status.isOk, "error: " & $status
|
||||
else:
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot i
|
||||
)
|
||||
doAssert status.isErr, "error: " & $status
|
||||
check:
|
||||
db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(0xDEADBEEF),
|
||||
fakeValidator(0xDEADBEEF),
|
||||
Slot i
|
||||
).isOk()
|
||||
db.registerBlock(
|
||||
fakeValidator(100),
|
||||
Slot 12,
|
||||
fakeRoot(12)
|
||||
)
|
||||
db.registerBlock(
|
||||
fakeValidator(100),
|
||||
Slot 17,
|
||||
fakeRoot(17)
|
||||
)
|
||||
check:
|
||||
db.registerBlock(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot 12,
|
||||
fakeRoot(12)
|
||||
).isOk()
|
||||
db.registerBlock(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot 17,
|
||||
fakeRoot(17)
|
||||
).isOk()
|
||||
for i in 0 ..< 30:
|
||||
if i > 10 and i notin {12, 15, 17, 20}:
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot i
|
||||
)
|
||||
doAssert status.isOk, "error: " & $status
|
||||
else:
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot i
|
||||
)
|
||||
doAssert status.isErr, "error: " & $status
|
||||
check:
|
||||
db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(0xDEADBEEF),
|
||||
fakeValidator(0xDEADBEEF),
|
||||
Slot i
|
||||
).isOk()
|
||||
db.registerBlock(
|
||||
fakeValidator(100),
|
||||
Slot 29,
|
||||
fakeRoot(29)
|
||||
)
|
||||
check:
|
||||
db.registerBlock(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot 29,
|
||||
fakeRoot(29)
|
||||
).isOk()
|
||||
|
||||
for i in 0 ..< 30:
|
||||
if i > 10 and i notin {12, 15, 17, 20, 29}:
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot i
|
||||
)
|
||||
doAssert status.isOk, "error: " & $status
|
||||
else:
|
||||
let status = db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Slot i
|
||||
)
|
||||
doAssert status.isErr, "error: " & $status
|
||||
check:
|
||||
db.checkSlashableBlockProposal(
|
||||
ValidatorIndex(0xDEADBEEF),
|
||||
fakeValidator(0xDEADBEEF),
|
||||
Slot i
|
||||
).isOk()
|
||||
|
||||
wrappedTimedTest "SP for same epoch attestation target - linear append":
|
||||
test "SP for same epoch attestation target - linear append":
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
let db = SlashingProtectionDB.init(
|
||||
default(Eth2Digest),
|
||||
@ -268,53 +314,81 @@ suite "Slashing Protection DB" & preset():
|
||||
db.close()
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 10,
|
||||
fakeRoot(100)
|
||||
)
|
||||
db.registerAttestation(
|
||||
fakeValidator(111),
|
||||
Epoch 0, Epoch 15,
|
||||
fakeRoot(111)
|
||||
)
|
||||
check:
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 10,
|
||||
fakeRoot(100)
|
||||
).isOk()
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(111),
|
||||
fakeValidator(111),
|
||||
Epoch 0, Epoch 15,
|
||||
fakeRoot(111)
|
||||
).isOk()
|
||||
|
||||
# Epoch occupied by same validator
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 10,
|
||||
).error.kind == DoubleVote
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 10, fakeRoot(101)
|
||||
).error.kind == DoubleVote
|
||||
|
||||
# Epoch occupied by another validator
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 15
|
||||
).isOk()
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 15, fakeRoot(151)
|
||||
).isOk()
|
||||
|
||||
# Epoch occupied by same validator
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(111),
|
||||
fakeValidator(111),
|
||||
Epoch 0, Epoch 15
|
||||
).error.kind == DoubleVote
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(111),
|
||||
fakeValidator(111),
|
||||
Epoch 0, Epoch 15, fakeRoot(161)
|
||||
).error.kind == DoubleVote
|
||||
|
||||
# Epoch inoccupied
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(255),
|
||||
fakeValidator(255),
|
||||
Epoch 0, Epoch 20
|
||||
).isOk()
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(255),
|
||||
fakeValidator(255),
|
||||
Epoch 0, Epoch 20, fakeRoot(4321)
|
||||
).isOk()
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(255),
|
||||
Epoch 0, Epoch 20,
|
||||
fakeRoot(4321)
|
||||
)
|
||||
|
||||
check:
|
||||
# Epoch now occupied
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(255),
|
||||
fakeValidator(255),
|
||||
Epoch 0, Epoch 20
|
||||
).error.kind == DoubleVote
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(255),
|
||||
fakeValidator(255),
|
||||
Epoch 0, Epoch 20, fakeRoot(4322)
|
||||
).error.kind == DoubleVote
|
||||
|
||||
wrappedTimedTest "SP for surrounded attestations":
|
||||
test "SP for surrounded attestations":
|
||||
block:
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
let db = SlashingProtectionDB.init(
|
||||
@ -326,22 +400,26 @@ suite "Slashing Protection DB" & preset():
|
||||
db.close()
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
)
|
||||
|
||||
check:
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
).isOk()
|
||||
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 11, Epoch 19
|
||||
).error.kind == SurroundedVote
|
||||
).error.kind == SurroundVote
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(200),
|
||||
fakeValidator(200),
|
||||
Epoch 11, Epoch 19
|
||||
).isOk
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 11, Epoch 21
|
||||
).isOk
|
||||
@ -357,39 +435,44 @@ suite "Slashing Protection DB" & preset():
|
||||
db.close()
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 1,
|
||||
fakeRoot(1)
|
||||
)
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
)
|
||||
|
||||
check:
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 1,
|
||||
fakeRoot(1)
|
||||
).isOk()
|
||||
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
).isOk()
|
||||
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 11, Epoch 19
|
||||
).error.kind == SurroundedVote
|
||||
).error.kind == SurroundVote
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(200),
|
||||
fakeValidator(200),
|
||||
Epoch 11, Epoch 19
|
||||
).isOk
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 11, Epoch 21
|
||||
).isOk
|
||||
# TODO: is that possible?
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 9, Epoch 19
|
||||
).isOk
|
||||
|
||||
|
||||
wrappedTimedTest "SP for surrounding attestations":
|
||||
test "SP for surrounding attestations":
|
||||
block:
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
let db = SlashingProtectionDB.init(
|
||||
@ -400,22 +483,24 @@ suite "Slashing Protection DB" & preset():
|
||||
defer:
|
||||
db.close()
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
)
|
||||
|
||||
check:
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
).isOk()
|
||||
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 9, Epoch 21
|
||||
).error.kind == SurroundingVote
|
||||
).error.kind == SurroundVote
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 21
|
||||
).error.kind == SurroundingVote
|
||||
).error.kind == SurroundVote
|
||||
|
||||
block:
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
@ -428,29 +513,34 @@ suite "Slashing Protection DB" & preset():
|
||||
db.close()
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 1,
|
||||
fakeRoot(1)
|
||||
)
|
||||
check:
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 1,
|
||||
fakeRoot(1)
|
||||
).isOk()
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
)
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
).isOk()
|
||||
|
||||
check:
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 9, Epoch 21
|
||||
).error.kind == SurroundingVote
|
||||
).error.kind == SurroundVote
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 0, Epoch 21
|
||||
).error.kind == SurroundingVote
|
||||
).error.kind == SurroundVote
|
||||
|
||||
wrappedTimedTest "Attestation ordering #1698":
|
||||
test "Attestation ordering #1698":
|
||||
block:
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
let db = SlashingProtectionDB.init(
|
||||
@ -462,41 +552,47 @@ suite "Slashing Protection DB" & preset():
|
||||
db.close()
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 1, Epoch 2,
|
||||
fakeRoot(2)
|
||||
)
|
||||
check:
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 1, Epoch 2,
|
||||
fakeRoot(2)
|
||||
).isOk()
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 8, Epoch 10,
|
||||
fakeRoot(10)
|
||||
)
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 8, Epoch 10,
|
||||
fakeRoot(10)
|
||||
).isOk()
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 14, Epoch 15,
|
||||
fakeRoot(15)
|
||||
)
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 14, Epoch 15,
|
||||
fakeRoot(15)
|
||||
).isOk()
|
||||
|
||||
# The current list is, 2 -> 10 -> 15
|
||||
# The current list is, 2 -> 10 -> 15
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 3, Epoch 6,
|
||||
fakeRoot(6)
|
||||
)
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 3, Epoch 6,
|
||||
fakeRoot(6)
|
||||
).isOk()
|
||||
|
||||
# The current list is 2 -> 6 -> 10 -> 15
|
||||
# The current list is 2 -> 6 -> 10 -> 15
|
||||
|
||||
check:
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 7, Epoch 11
|
||||
).error.kind == SurroundingVote
|
||||
).error.kind == SurroundVote
|
||||
|
||||
wrappedTimedTest "Test valid attestation #1699":
|
||||
test "Test valid attestation #1699":
|
||||
block:
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
let db = SlashingProtectionDB.init(
|
||||
@ -508,20 +604,24 @@ suite "Slashing Protection DB" & preset():
|
||||
db.close()
|
||||
sqlite3db_delete(TestDir, TestDbName)
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
)
|
||||
check:
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 10, Epoch 20,
|
||||
fakeRoot(20)
|
||||
).isOk()
|
||||
|
||||
db.registerAttestation(
|
||||
fakeValidator(100),
|
||||
Epoch 40, Epoch 50,
|
||||
fakeRoot(50)
|
||||
)
|
||||
db.registerAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 40, Epoch 50,
|
||||
fakeRoot(50)
|
||||
).isOk()
|
||||
|
||||
check:
|
||||
db.checkSlashableAttestation(
|
||||
ValidatorIndex(100),
|
||||
fakeValidator(100),
|
||||
Epoch 20, Epoch 30
|
||||
).isOk
|
||||
|
Loading…
x
Reference in New Issue
Block a user