mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-10 21:46:32 +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* {.
|
slashingDbKind* {.
|
||||||
hidden
|
hidden
|
||||||
defaultValue: SlashingDbKind.v2
|
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
|
name: "slashing-db-kind" }: SlashingDbKind
|
||||||
|
|
||||||
stateDbKind* {.
|
stateDbKind* {.
|
||||||
|
@ -322,33 +322,24 @@ proc init*(T: type BeaconNode,
|
|||||||
attestationPool = newClone(AttestationPool.init(chainDag, quarantine))
|
attestationPool = newClone(AttestationPool.init(chainDag, quarantine))
|
||||||
exitPool = newClone(ExitPool.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 =
|
slashingProtectionDB =
|
||||||
case config.slashingDbKind
|
SlashingProtectionDB.init(
|
||||||
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(
|
|
||||||
getStateField(chainDag.headState, genesis_validators_root),
|
getStateField(chainDag.headState, genesis_validators_root),
|
||||||
config.validatorsDir(), "slashing_protection"
|
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))
|
validatorPool = newClone(ValidatorPool.init(slashingProtectionDB))
|
||||||
|
|
||||||
consensusManager = ConsensusManager.new(
|
consensusManager = ConsensusManager.new(
|
||||||
|
@ -135,25 +135,25 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
|
|||||||
if vc.proposalsForCurrentEpoch.contains slot:
|
if vc.proposalsForCurrentEpoch.contains slot:
|
||||||
let public_key = vc.proposalsForCurrentEpoch[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
|
let notSlashable = vc.attachedValidators
|
||||||
.slashingProtection
|
.slashingProtection
|
||||||
.checkSlashableBlockProposal(public_key, slot)
|
.registerBlock(
|
||||||
|
newBlock.message.proposer_index.ValidatorIndex, public_key, slot,
|
||||||
|
signing_root)
|
||||||
|
|
||||||
if notSlashable.isOk:
|
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(
|
newBlock.signature = await validator.signBlockProposal(
|
||||||
vc.fork, vc.beaconGenesis.genesis_validators_root, slot, newBlock.root)
|
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 validator = vc.attachedValidators.validators[a.public_key]
|
||||||
let ad = await vc.client.get_v1_validator_attestation_data(slot, a.committee_index)
|
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
|
let notSlashable = vc.attachedValidators
|
||||||
.slashingProtection
|
.slashingProtection
|
||||||
.checkSlashableAttestation(
|
.registerAttestation(
|
||||||
a.public_key,
|
a.validator_index, a.public_key, ad.source.epoch, ad.target.epoch, signing_root)
|
||||||
ad.source.epoch,
|
|
||||||
ad.target.epoch)
|
|
||||||
if notSlashable.isOk():
|
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...
|
# TODO I don't like these (u)int64-to-int conversions...
|
||||||
let attestation = await validator.produceAndSignAttestation(
|
let attestation = await validator.produceAndSignAttestation(
|
||||||
ad, a.committee_length.int, a.validator_committee_index,
|
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).
|
# * 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.
|
# 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
|
import
|
||||||
# stdlib
|
# stdlib
|
||||||
@ -21,6 +20,7 @@ import
|
|||||||
./slashing_protection_v2
|
./slashing_protection_v2
|
||||||
|
|
||||||
export slashing_protection_common
|
export slashing_protection_common
|
||||||
|
|
||||||
# Generic sandwich
|
# Generic sandwich
|
||||||
export chronicles
|
export chronicles
|
||||||
|
|
||||||
@ -52,8 +52,7 @@ type
|
|||||||
## Database storing the blocks attested
|
## Database storing the blocks attested
|
||||||
## by validators attached to a beacon node
|
## by validators attached to a beacon node
|
||||||
## or validator client.
|
## or validator client.
|
||||||
db_v1: SlashingProtectionDB_v1
|
db_v2*: SlashingProtectionDB_v2
|
||||||
db_v2: SlashingProtectionDB_v2
|
|
||||||
modes: set[SlashProtDBMode]
|
modes: set[SlashProtDBMode]
|
||||||
disagreementBehavior: DisagreementBehavior
|
disagreementBehavior: DisagreementBehavior
|
||||||
|
|
||||||
@ -99,17 +98,27 @@ proc init*(
|
|||||||
)
|
)
|
||||||
result.db_v2 = db
|
result.db_v2 = db
|
||||||
|
|
||||||
|
var db_v1: SlashingProtectionDB_v1
|
||||||
|
|
||||||
let rawdb = kvstore result.db_v2.getRawDBHandle()
|
let rawdb = kvstore result.db_v2.getRawDBHandle()
|
||||||
if not rawdb.checkOrPutGenesis_DbV1(genesis_validators_root):
|
if not rawdb.checkOrPutGenesis_DbV1(genesis_validators_root):
|
||||||
fatal "The slashing database refers to another chain/mainnet/testnet",
|
fatal "The slashing database refers to another chain/mainnet/testnet",
|
||||||
path = basePath/dbname,
|
path = basePath/dbname,
|
||||||
genesis_validators_root = genesis_validators_root
|
genesis_validators_root = genesis_validators_root
|
||||||
result.db_v1.fromRawDB(rawdb)
|
db_v1.fromRawDB(rawdb)
|
||||||
|
|
||||||
if requiresMigration:
|
if requiresMigration:
|
||||||
info "Migrating local validators slashing DB from v1 to v2"
|
info "Migrating local validators slashing DB from v1 to v2"
|
||||||
let spdir = result.db_v1.toSPDIR_lowWatermark()
|
let spdir = try: db_v1.toSPDIR_lowWatermark()
|
||||||
let status = result.db_v2.inclSPDIR(spdir)
|
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
|
case status
|
||||||
of siSuccess:
|
of siSuccess:
|
||||||
info "Slashing DB migration successful."
|
info "Slashing DB migration successful."
|
||||||
@ -163,77 +172,9 @@ proc close*(db: SlashingProtectionDB) =
|
|||||||
# DB Queries
|
# 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*(
|
proc checkSlashableBlockProposal*(
|
||||||
db: SlashingProtectionDB,
|
db: SlashingProtectionDB,
|
||||||
|
index: ValidatorIndex,
|
||||||
validator: ValidatorPubKey,
|
validator: ValidatorPubKey,
|
||||||
slot: Slot
|
slot: Slot
|
||||||
): Result[void, BadProposal] =
|
): Result[void, BadProposal] =
|
||||||
@ -243,12 +184,11 @@ proc checkSlashableBlockProposal*(
|
|||||||
## The error contains the blockroot that was already proposed
|
## The error contains the blockroot that was already proposed
|
||||||
##
|
##
|
||||||
## Returns success otherwise
|
## Returns success otherwise
|
||||||
db.queryVersions(
|
checkSlashableBlockProposal(db.db_v2, some(index), validator, slot)
|
||||||
checkSlashableBlockProposal(db_version, validator, slot)
|
|
||||||
)
|
|
||||||
|
|
||||||
proc checkSlashableAttestation*(
|
proc checkSlashableAttestation*(
|
||||||
db: SlashingProtectionDB,
|
db: SlashingProtectionDB,
|
||||||
|
index: ValidatorIndex,
|
||||||
validator: ValidatorPubKey,
|
validator: ValidatorPubKey,
|
||||||
source: Epoch,
|
source: Epoch,
|
||||||
target: Epoch
|
target: Epoch
|
||||||
@ -259,65 +199,36 @@ proc checkSlashableAttestation*(
|
|||||||
## (surrounding vote or surrounded vote).
|
## (surrounding vote or surrounded vote).
|
||||||
##
|
##
|
||||||
## Returns success otherwise
|
## Returns success otherwise
|
||||||
db.queryVersions(
|
checkSlashableAttestation(db.db_v2, some(index), validator, source, target)
|
||||||
checkSlashableAttestation(db_version, 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*(
|
proc registerBlock*(
|
||||||
db: SlashingProtectionDB,
|
db: SlashingProtectionDB,
|
||||||
|
index: ValidatorIndex,
|
||||||
validator: ValidatorPubKey,
|
validator: ValidatorPubKey,
|
||||||
slot: Slot, block_signing_root: Eth2Digest) =
|
slot: Slot, block_signing_root: Eth2Digest): Result[void, BadProposal] =
|
||||||
## Add a block to the slashing protection DB
|
## Add a block to the slashing protection DB - the registration will
|
||||||
## `checkSlashableBlockProposal` MUST be run
|
## fail if it would violate a slashing protection rule.
|
||||||
## before to ensure no overwrite.
|
|
||||||
##
|
##
|
||||||
## block_signing_root is the output of
|
## block_signing_root is the output of
|
||||||
## compute_signing_root(block, domain)
|
## compute_signing_root(block, domain)
|
||||||
db.updateVersions(
|
registerBlock(db.db_v2, some(index), validator, slot, block_signing_root)
|
||||||
registerBlock(db_version, validator, slot, block_signing_root)
|
|
||||||
)
|
|
||||||
|
|
||||||
proc registerAttestation*(
|
proc registerAttestation*(
|
||||||
db: SlashingProtectionDB,
|
db: SlashingProtectionDB,
|
||||||
|
index: ValidatorIndex,
|
||||||
validator: ValidatorPubKey,
|
validator: ValidatorPubKey,
|
||||||
source, target: Epoch,
|
source, target: Epoch,
|
||||||
attestation_signing_root: Eth2Digest) =
|
attestation_signing_root: Eth2Digest): Result[void, BadVote] =
|
||||||
## Add an attestation to the slashing protection DB
|
## Add an attestation to the slashing protection DB - the registration will
|
||||||
## `checkSlashableAttestation` MUST be run
|
## fail if it would violate a slashing protection rule.
|
||||||
## before to ensure no overwrite.
|
|
||||||
##
|
##
|
||||||
## attestation_signing_root is the output of
|
## attestation_signing_root is the output of
|
||||||
## compute_signing_root(attestation, domain)
|
## compute_signing_root(attestation, domain)
|
||||||
db.updateVersions(
|
registerAttestation(db.db_v2, some(index), validator,
|
||||||
registerAttestation(db_version, validator,
|
source, target, attestation_signing_root)
|
||||||
source, target, attestation_signing_root)
|
|
||||||
)
|
|
||||||
|
|
||||||
# DB maintenance
|
# DB maintenance
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
@ -366,41 +277,6 @@ proc pruneAfterFinalization*(
|
|||||||
fatal "Pruning is not implemented"
|
fatal "Pruning is not implemented"
|
||||||
quit 1
|
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
|
# The high-level import/export functions are
|
||||||
# - importSlashingInterchange
|
# - importSlashingInterchange
|
||||||
# - exportSlashingInterchange
|
# - exportSlashingInterchange
|
||||||
@ -409,7 +285,10 @@ proc inclSPDIR*(db: SlashingProtectionDB, spdir: SPDIR): SlashingImportStatus
|
|||||||
# That builds on a DB backend inclSPDIR and toSPDIR
|
# That builds on a DB backend inclSPDIR and toSPDIR
|
||||||
# SPDIR being a common Intermediate Representation
|
# 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
|
# 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
|
SlashingImportStatus* = enum
|
||||||
siSuccess
|
siSuccess
|
||||||
siFailure
|
siFailure
|
||||||
@ -146,9 +96,8 @@ type
|
|||||||
# 2: candidate attestation
|
# 2: candidate attestation
|
||||||
|
|
||||||
# Spec slashing condition
|
# Spec slashing condition
|
||||||
DoubleVote # h(t1) == h(t2)
|
DoubleVote # h(t1) == h(t2)
|
||||||
SurroundedVote # h(s1) < h(s2) < h(t2) < h(t1)
|
SurroundVote # h(s1) < h(s2) < h(t2) < h(t1) or h(s2) < h(s1) < h(t1) < h(t2)
|
||||||
SurroundingVote # h(s2) < h(s1) < h(t1) < h(t2)
|
|
||||||
|
|
||||||
# Non-spec, should never happen in a well functioning client
|
# Non-spec, should never happen in a well functioning client
|
||||||
TargetPrecedesSource # h(t1) < h(s1) - current epoch precedes last justified epoch
|
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)
|
# EIP-3067 (https://eips.ethereum.org/EIPS/eip-3076)
|
||||||
MinSourceViolation # h(s2) < h(s1) - EIP3067 condition 4 (strict inequality)
|
MinSourceViolation # h(s2) < h(s1) - EIP3067 condition 4 (strict inequality)
|
||||||
MinTargetViolation # h(t2) <= h(t1) - EIP3067 condition 5
|
MinTargetViolation # h(t2) <= h(t1) - EIP3067 condition 5
|
||||||
|
DatabaseError # Cannot read/write the slashing protection db
|
||||||
|
|
||||||
BadVote* = object
|
BadVote* {.pure.} = object
|
||||||
case kind*: BadVoteKind
|
case kind*: BadVoteKind
|
||||||
of DoubleVote:
|
of DoubleVote:
|
||||||
existingAttestation*: Eth2Digest
|
existingAttestation*: Eth2Digest
|
||||||
of SurroundedVote, SurroundingVote:
|
of SurroundVote:
|
||||||
existingAttestationRoot*: Eth2Digest # Many roots might be in conflict
|
existingAttestationRoot*: Eth2Digest # Many roots might be in conflict
|
||||||
sourceExisting*, targetExisting*: Epoch
|
sourceExisting*, targetExisting*: Epoch
|
||||||
sourceSlashable*, targetSlashable*: Epoch
|
sourceSlashable*, targetSlashable*: Epoch
|
||||||
@ -173,12 +123,15 @@ type
|
|||||||
of MinTargetViolation:
|
of MinTargetViolation:
|
||||||
minTarget*: Epoch
|
minTarget*: Epoch
|
||||||
candidateTarget*: Epoch
|
candidateTarget*: Epoch
|
||||||
|
of BadVoteKind.DatabaseError:
|
||||||
|
message*: string
|
||||||
|
|
||||||
BadProposalKind* = enum
|
BadProposalKind* {.pure.} = enum
|
||||||
# Spec slashing condition
|
# Spec slashing condition
|
||||||
DoubleProposal # h(t1) == h(t2)
|
DoubleProposal # h(t1) == h(t2)
|
||||||
# EIP-3067 (https://eips.ethereum.org/EIPS/eip-3076)
|
# EIP-3067 (https://eips.ethereum.org/EIPS/eip-3076)
|
||||||
MinSlotViolation # h(t2) <= h(t1)
|
MinSlotViolation # h(t2) <= h(t1)
|
||||||
|
DatabaseError # Cannot read/write the slashing protection db
|
||||||
|
|
||||||
BadProposal* = object
|
BadProposal* = object
|
||||||
case kind*: BadProposalKind
|
case kind*: BadProposalKind
|
||||||
@ -187,6 +140,8 @@ type
|
|||||||
of MinSlotViolation:
|
of MinSlotViolation:
|
||||||
minSlot*: Slot
|
minSlot*: Slot
|
||||||
candidateSlot*: Slot
|
candidateSlot*: Slot
|
||||||
|
of BadProposalKind.DatabaseError:
|
||||||
|
message*: string
|
||||||
|
|
||||||
func `==`*(a, b: BadVote): bool =
|
func `==`*(a, b: BadVote): bool =
|
||||||
## Comparison operator.
|
## Comparison operator.
|
||||||
@ -194,24 +149,26 @@ func `==`*(a, b: BadVote): bool =
|
|||||||
## result of multiple DB versions
|
## result of multiple DB versions
|
||||||
if a.kind != b.kind:
|
if a.kind != b.kind:
|
||||||
false
|
false
|
||||||
elif a.kind == DoubleVote:
|
else:
|
||||||
a.existingAttestation == b.existingAttestation
|
case a.kind
|
||||||
elif a.kind in {SurroundedVote, SurroundingVote}:
|
of DoubleVote:
|
||||||
(a.existingAttestationRoot == b.existingAttestationRoot) and
|
a.existingAttestation == b.existingAttestation
|
||||||
(a.sourceExisting == b.sourceExisting) and
|
of SurroundVote:
|
||||||
(a.targetExisting == b.targetExisting) and
|
(a.existingAttestationRoot == b.existingAttestationRoot) and
|
||||||
(a.sourceSlashable == b.sourceSlashable) and
|
(a.sourceExisting == b.sourceExisting) and
|
||||||
(a.targetSlashable == b.targetSlashable)
|
(a.targetExisting == b.targetExisting) and
|
||||||
elif a.kind == TargetPrecedesSource:
|
(a.sourceSlashable == b.sourceSlashable) and
|
||||||
true
|
(a.targetSlashable == b.targetSlashable)
|
||||||
elif a.kind == MinSourceViolation:
|
of TargetPrecedesSource:
|
||||||
(a.minSource == b.minSource) and
|
true
|
||||||
(a.candidateSource == b.candidateSource)
|
of MinSourceViolation:
|
||||||
elif a.kind == MinTargetViolation:
|
(a.minSource == b.minSource) and
|
||||||
(a.minTarget == b.minTarget) and
|
(a.candidateSource == b.candidateSource)
|
||||||
(a.candidateTarget == b.candidateTarget)
|
of MinTargetViolation:
|
||||||
else: # Unreachable
|
(a.minTarget == b.minTarget) and
|
||||||
false
|
(a.candidateTarget == b.candidateTarget)
|
||||||
|
of BadVoteKind.DatabaseError:
|
||||||
|
true
|
||||||
|
|
||||||
func `==`*(a, b: BadProposal): bool =
|
func `==`*(a, b: BadProposal): bool =
|
||||||
## Comparison operator.
|
## Comparison operator.
|
||||||
@ -266,7 +223,7 @@ proc readValue*(r: var JsonReader, a: var (SlotString or EpochString))
|
|||||||
raiseUnexpectedValue(r, "Integer in a string expected")
|
raiseUnexpectedValue(r, "Integer in a string expected")
|
||||||
|
|
||||||
proc exportSlashingInterchange*(
|
proc exportSlashingInterchange*(
|
||||||
db: SlashingProtectionDB_Concept,
|
db: auto,
|
||||||
path: string, prettify = true) {.raises: [Defect, IOError].} =
|
path: string, prettify = true) {.raises: [Defect, IOError].} =
|
||||||
## Export a database to the Slashing Protection Database Interchange Format
|
## Export a database to the Slashing Protection Database Interchange Format
|
||||||
let spdir = db.toSPDIR()
|
let spdir = db.toSPDIR()
|
||||||
@ -274,7 +231,7 @@ proc exportSlashingInterchange*(
|
|||||||
echo "Exported slashing protection DB to '", path, "'"
|
echo "Exported slashing protection DB to '", path, "'"
|
||||||
|
|
||||||
proc importSlashingInterchange*(
|
proc importSlashingInterchange*(
|
||||||
db: SlashingProtectionDB_Concept,
|
db: auto,
|
||||||
path: string): SlashingImportStatus {.raises: [Defect, IOError, SerializationError].} =
|
path: string): SlashingImportStatus {.raises: [Defect, IOError, SerializationError].} =
|
||||||
## Import a Slashing Protection Database Interchange Format
|
## Import a Slashing Protection Database Interchange Format
|
||||||
## into a Nimbus DB.
|
## into a Nimbus DB.
|
||||||
@ -307,7 +264,7 @@ chronicles.formatIt SPDIR_SignedAttestation: it.shortLog
|
|||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
||||||
proc importInterchangeV5Impl*(
|
proc importInterchangeV5Impl*(
|
||||||
db: SlashingProtectionDB_Concept,
|
db: auto,
|
||||||
spdir: var SPDIR
|
spdir: var SPDIR
|
||||||
): SlashingImportStatus
|
): SlashingImportStatus
|
||||||
{.raises: [SerializationError, IOError, Defect].} =
|
{.raises: [SerializationError, IOError, Defect].} =
|
||||||
@ -359,8 +316,8 @@ proc importInterchangeV5Impl*(
|
|||||||
|
|
||||||
for b in 0 ..< spdir.data[v].signed_blocks.len:
|
for b in 0 ..< spdir.data[v].signed_blocks.len:
|
||||||
template B: untyped = spdir.data[v].signed_blocks[b]
|
template B: untyped = spdir.data[v].signed_blocks[b]
|
||||||
let status = db.checkSlashableBlockProposal(
|
let status = db.registerBlock(
|
||||||
parsedKey, B.slot.Slot
|
parsedKey, B.slot.Slot, B.signing_root.Eth2Digest
|
||||||
)
|
)
|
||||||
if status.isErr():
|
if status.isErr():
|
||||||
# We might be importing a duplicate which EIP-3076 allows
|
# We might be importing a duplicate which EIP-3076 allows
|
||||||
@ -388,12 +345,6 @@ proc importInterchangeV5Impl*(
|
|||||||
if B.slot.int > maxValidSlotSeen:
|
if B.slot.int > maxValidSlotSeen:
|
||||||
maxValidSlotSeen = B.slot.int
|
maxValidSlotSeen = B.slot.int
|
||||||
|
|
||||||
db.registerBlock(
|
|
||||||
parsedKey,
|
|
||||||
B.slot.Slot,
|
|
||||||
B.signing_root.Eth2Digest
|
|
||||||
)
|
|
||||||
|
|
||||||
# Now prune everything that predates
|
# Now prune everything that predates
|
||||||
# this interchange file max slot
|
# this interchange file max slot
|
||||||
db.pruneBlocks(parsedKey, Slot maxValidSlotSeen)
|
db.pruneBlocks(parsedKey, Slot maxValidSlotSeen)
|
||||||
@ -409,10 +360,11 @@ proc importInterchangeV5Impl*(
|
|||||||
|
|
||||||
for a in 0 ..< spdir.data[v].signed_attestations.len:
|
for a in 0 ..< spdir.data[v].signed_attestations.len:
|
||||||
template A: untyped = spdir.data[v].signed_attestations[a]
|
template A: untyped = spdir.data[v].signed_attestations[a]
|
||||||
let status = db.checkSlashableAttestation(
|
let status = db.registerAttestation(
|
||||||
parsedKey,
|
parsedKey,
|
||||||
A.source_epoch.Epoch,
|
A.source_epoch.Epoch,
|
||||||
A.target_epoch.Epoch
|
A.target_epoch.Epoch,
|
||||||
|
A.signing_root.Eth2Digest
|
||||||
)
|
)
|
||||||
if status.isErr():
|
if status.isErr():
|
||||||
# We might be importing a duplicate which EIP-3076 allows
|
# We might be importing a duplicate which EIP-3076 allows
|
||||||
@ -439,13 +391,6 @@ proc importInterchangeV5Impl*(
|
|||||||
if A.target_epoch.int > maxValidTargetEpochSeen:
|
if A.target_epoch.int > maxValidTargetEpochSeen:
|
||||||
maxValidTargetEpochSeen = A.target_epoch.int
|
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
|
# Now prune everything that predates
|
||||||
# this interchange file max slot
|
# this interchange file max slot
|
||||||
if maxValidSourceEpochSeen < 0 or maxValidTargetEpochSeen < 0:
|
if maxValidSourceEpochSeen < 0 or maxValidTargetEpochSeen < 0:
|
||||||
|
@ -531,7 +531,7 @@ proc checkSlashableAttestation*(
|
|||||||
# s2 < s1 < t1 < t2
|
# s2 < s1 < t1 < t2
|
||||||
# Logged by caller
|
# Logged by caller
|
||||||
return err(BadVote(
|
return err(BadVote(
|
||||||
kind: SurroundingVote,
|
kind: SurroundVote,
|
||||||
existingAttestationRoot: ar1,
|
existingAttestationRoot: ar1,
|
||||||
sourceExisting: s1,
|
sourceExisting: s1,
|
||||||
targetExisting: t1,
|
targetExisting: t1,
|
||||||
@ -542,7 +542,7 @@ proc checkSlashableAttestation*(
|
|||||||
# s1 < s2 < t2 < t1
|
# s1 < s2 < t2 < t1
|
||||||
# Logged by caller
|
# Logged by caller
|
||||||
return err(BadVote(
|
return err(BadVote(
|
||||||
kind: SurroundedVote,
|
kind: SurroundVote,
|
||||||
existingAttestationRoot: ar1,
|
existingAttestationRoot: ar1,
|
||||||
sourceExisting: s1,
|
sourceExisting: s1,
|
||||||
targetExisting: t1,
|
targetExisting: t1,
|
||||||
@ -587,10 +587,10 @@ proc registerValidator(db: SlashingProtectionDB_v1, validator: ValidatorPubKey)
|
|||||||
proc registerBlock*(
|
proc registerBlock*(
|
||||||
db: SlashingProtectionDB_v1,
|
db: SlashingProtectionDB_v1,
|
||||||
validator: ValidatorPubKey,
|
validator: ValidatorPubKey,
|
||||||
slot: Slot, block_root: Eth2Digest) =
|
slot: Slot, block_root: Eth2Digest): Result[void, BadProposal] =
|
||||||
## Add a block to the slashing protection DB
|
## 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()
|
let valID = validator.toRaw()
|
||||||
|
|
||||||
@ -620,7 +620,7 @@ proc registerBlock*(
|
|||||||
# targetEpochs.isInit will be false
|
# targetEpochs.isInit will be false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return ok()
|
||||||
|
|
||||||
var ll = maybeLL.unsafeGet()
|
var ll = maybeLL.unsafeGet()
|
||||||
var cur = ll.blockSlots.stop
|
var cur = ll.blockSlots.stop
|
||||||
@ -632,7 +632,7 @@ proc registerBlock*(
|
|||||||
db.put(subkey(kBlock, valID, slot), node)
|
db.put(subkey(kBlock, valID, slot), node)
|
||||||
# TODO: what if crash here?
|
# TODO: what if crash here?
|
||||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||||
return
|
return ok()
|
||||||
|
|
||||||
if cur < slot:
|
if cur < slot:
|
||||||
# Adding a block later than all known blocks
|
# Adding a block later than all known blocks
|
||||||
@ -652,7 +652,7 @@ proc registerBlock*(
|
|||||||
db.put(subkey(kBlock, valID, cur), prevNode)
|
db.put(subkey(kBlock, valID, cur), prevNode)
|
||||||
# TODO: what if crash here?
|
# TODO: what if crash here?
|
||||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||||
return
|
return ok()
|
||||||
|
|
||||||
# TODO: we likely want a proper DB or better KV-store high-level API
|
# TODO: we likely want a proper DB or better KV-store high-level API
|
||||||
# in the future.
|
# in the future.
|
||||||
@ -687,7 +687,7 @@ proc registerBlock*(
|
|||||||
# TODO: what if crash here?
|
# TODO: what if crash here?
|
||||||
db.put(subkey(kBlock, valID, cur), curNode)
|
db.put(subkey(kBlock, valID, cur), curNode)
|
||||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||||
return
|
return ok()
|
||||||
elif slot > curNode.prev:
|
elif slot > curNode.prev:
|
||||||
# Reached: prev < slot < cur
|
# Reached: prev < slot < cur
|
||||||
# Change: prev <-> cur
|
# Change: prev <-> cur
|
||||||
@ -709,7 +709,7 @@ proc registerBlock*(
|
|||||||
# TODO: what if crash here?
|
# TODO: what if crash here?
|
||||||
db.put(subkey(kBlock, valID, cur), curNode)
|
db.put(subkey(kBlock, valID, cur), curNode)
|
||||||
db.put(subkey(kBlock, valID, prev), prevNode)
|
db.put(subkey(kBlock, valID, prev), prevNode)
|
||||||
return
|
return ok()
|
||||||
|
|
||||||
# Previous
|
# Previous
|
||||||
cur = curNode.prev
|
cur = curNode.prev
|
||||||
@ -720,15 +720,19 @@ proc registerBlock*(
|
|||||||
# ).expect("Consistent linked-list in DB")
|
# ).expect("Consistent linked-list in DB")
|
||||||
).unsafeGet()
|
).unsafeGet()
|
||||||
|
|
||||||
|
ok()
|
||||||
|
|
||||||
proc registerAttestation*(
|
proc registerAttestation*(
|
||||||
db: SlashingProtectionDB_v1,
|
db: SlashingProtectionDB_v1,
|
||||||
validator: ValidatorPubKey,
|
validator: ValidatorPubKey,
|
||||||
source, target: Epoch,
|
source, target: Epoch,
|
||||||
attestation_root: Eth2Digest) =
|
attestation_root: Eth2Digest): Result[void, BadVote] =
|
||||||
## Add an attestation to the slashing protection DB
|
## Add an attestation to the slashing protection DB
|
||||||
## `checkSlashableAttestation` MUST be run
|
## `checkSlashableAttestation` MUST be run
|
||||||
## before to ensure no overwrite.
|
## before to ensure no overwrite.
|
||||||
|
|
||||||
|
? checkSlashableAttestation(db, validator, source, target)
|
||||||
|
|
||||||
let valID = validator.toRaw()
|
let valID = validator.toRaw()
|
||||||
|
|
||||||
# We want to keep the linked-list ordered
|
# We want to keep the linked-list ordered
|
||||||
@ -759,7 +763,7 @@ proc registerAttestation*(
|
|||||||
targetEpochs: EpochDesc(start: target, stop: target, isInit: true)
|
targetEpochs: EpochDesc(start: target, stop: target, isInit: true)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return ok()
|
||||||
|
|
||||||
var ll = maybeLL.unsafeGet()
|
var ll = maybeLL.unsafeGet()
|
||||||
var cur = ll.targetEpochs.stop
|
var cur = ll.targetEpochs.stop
|
||||||
@ -773,7 +777,7 @@ proc registerAttestation*(
|
|||||||
db.put(subkey(kTargetEpoch, valID, target), node)
|
db.put(subkey(kTargetEpoch, valID, target), node)
|
||||||
# TODO: what if crash here?
|
# TODO: what if crash here?
|
||||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||||
return
|
return ok()
|
||||||
|
|
||||||
block: # Update source epoch
|
block: # Update source epoch
|
||||||
if ll.sourceEpochs.stop < source:
|
if ll.sourceEpochs.stop < source:
|
||||||
@ -800,7 +804,7 @@ proc registerAttestation*(
|
|||||||
db.put(subkey(kTargetEpoch, valID, cur), prevNode)
|
db.put(subkey(kTargetEpoch, valID, cur), prevNode)
|
||||||
# TODO: what if crash here?
|
# TODO: what if crash here?
|
||||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||||
return
|
return ok()
|
||||||
|
|
||||||
# TODO: we likely want a proper DB or better KV-store high-level API
|
# TODO: we likely want a proper DB or better KV-store high-level API
|
||||||
# in the future.
|
# in the future.
|
||||||
@ -835,7 +839,7 @@ proc registerAttestation*(
|
|||||||
# TODO: what if crash here?
|
# TODO: what if crash here?
|
||||||
db.put(subkey(kTargetEpoch, valID, cur), curNode)
|
db.put(subkey(kTargetEpoch, valID, cur), curNode)
|
||||||
db.put(subkey(kLinkedListMeta, valID), ll)
|
db.put(subkey(kLinkedListMeta, valID), ll)
|
||||||
return
|
return ok()
|
||||||
elif target > curNode.prev:
|
elif target > curNode.prev:
|
||||||
# Reached: prev < target < cur
|
# Reached: prev < target < cur
|
||||||
# Change: prev <-> cur
|
# Change: prev <-> cur
|
||||||
@ -858,7 +862,7 @@ proc registerAttestation*(
|
|||||||
# TODO: what if crash here?
|
# TODO: what if crash here?
|
||||||
db.put(subkey(kTargetEpoch, valID, cur), curNode)
|
db.put(subkey(kTargetEpoch, valID, cur), curNode)
|
||||||
db.put(subkey(kTargetEpoch, valID, prev), prevNode)
|
db.put(subkey(kTargetEpoch, valID, prev), prevNode)
|
||||||
return
|
return ok()
|
||||||
|
|
||||||
# Previous
|
# Previous
|
||||||
cur = curNode.prev
|
cur = curNode.prev
|
||||||
@ -869,6 +873,8 @@ proc registerAttestation*(
|
|||||||
# ).expect("Consistent linked-list in DB")
|
# ).expect("Consistent linked-list in DB")
|
||||||
).unsafeGet()
|
).unsafeGet()
|
||||||
|
|
||||||
|
ok()
|
||||||
|
|
||||||
# Debug tools
|
# Debug tools
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
||||||
@ -1141,8 +1147,3 @@ proc inclSPDIR*(db: SlashingProtectionDB_v1, spdir: SPDIR): SlashingImportStatus
|
|||||||
# Create a mutable copy for sorting
|
# Create a mutable copy for sorting
|
||||||
var spdir = spdir
|
var spdir = spdir
|
||||||
return db.importInterchangeV5Impl(spdir)
|
return db.importInterchangeV5Impl(spdir)
|
||||||
|
|
||||||
# Sanity check
|
|
||||||
# --------------------------------------------------------------
|
|
||||||
|
|
||||||
static: doAssert SlashingProtectionDB_v1 is SlashingProtectionDB_Concept
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import
|
import
|
||||||
# Standard library
|
# Standard library
|
||||||
std/[os, options, typetraits, decls],
|
std/[os, options, typetraits, decls, tables],
|
||||||
# Status
|
# Status
|
||||||
stew/byteutils,
|
stew/byteutils,
|
||||||
eth/db/[kvstore, kvstore_sqlite3],
|
eth/db/[kvstore, kvstore_sqlite3],
|
||||||
@ -211,12 +211,13 @@ type
|
|||||||
# Cached queries - read
|
# Cached queries - read
|
||||||
sqlGetValidatorInternalID: SqliteStmt[PubKeyBytes, ValidatorInternalID]
|
sqlGetValidatorInternalID: SqliteStmt[PubKeyBytes, ValidatorInternalID]
|
||||||
sqlAttForSameTargetEpoch: SqliteStmt[(ValidatorInternalID, int64), Hash32]
|
sqlAttForSameTargetEpoch: SqliteStmt[(ValidatorInternalID, int64), Hash32]
|
||||||
sqlAttSurrounded: SqliteStmt[(ValidatorInternalID, int64, int64), (int64, int64, Hash32)]
|
sqlAttSurrounds: SqliteStmt[(ValidatorInternalID, int64, int64, int64, int64), (int64, int64, Hash32)]
|
||||||
sqlAttSurrounding: SqliteStmt[(ValidatorInternalID, int64, int64), (int64, int64, Hash32)]
|
|
||||||
sqlAttMinSourceTargetEpochs: SqliteStmt[ValidatorInternalID, (int64, int64)]
|
sqlAttMinSourceTargetEpochs: SqliteStmt[ValidatorInternalID, (int64, int64)]
|
||||||
sqlBlockForSameSlot: SqliteStmt[(ValidatorInternalID, int64), Hash32]
|
sqlBlockForSameSlot: SqliteStmt[(ValidatorInternalID, int64), Hash32]
|
||||||
sqlBlockMinSlot: SqliteStmt[ValidatorInternalID, int64]
|
sqlBlockMinSlot: SqliteStmt[ValidatorInternalID, int64]
|
||||||
|
|
||||||
|
internalIds: Table[ValidatorIndex, ValidatorInternalID]
|
||||||
|
|
||||||
ValidatorInternalID = int32
|
ValidatorInternalID = int32
|
||||||
## Validator internal ID in the DB
|
## Validator internal ID in the DB
|
||||||
## This is cached to cost querying cost
|
## This is cached to cost querying cost
|
||||||
@ -382,30 +383,17 @@ proc setupCachedQueries(db: SlashingProtectionDB_v2) =
|
|||||||
""", (ValidatorInternalID, int64), Hash32
|
""", (ValidatorInternalID, int64), Hash32
|
||||||
).get()
|
).get()
|
||||||
|
|
||||||
db.sqlAttSurrounded = db.backend.prepareStmt("""
|
db.sqlAttSurrounds = db.backend.prepareStmt("""
|
||||||
SELECT
|
SELECT
|
||||||
source_epoch, target_epoch, signing_root
|
source_epoch, target_epoch, signing_root
|
||||||
FROM
|
FROM
|
||||||
signed_attestations
|
signed_attestations
|
||||||
WHERE 1=1
|
WHERE 1=1
|
||||||
and validator_id = ?
|
and validator_id = ?
|
||||||
and source_epoch < ?
|
and ((source_epoch < ? and ? < target_epoch) OR
|
||||||
and ? < target_epoch
|
(? < source_epoch and target_epoch < ?))
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""", (ValidatorInternalID, int64, int64), (int64, int64, Hash32)
|
""", (ValidatorInternalID, int64, int64, 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)
|
|
||||||
).get()
|
).get()
|
||||||
|
|
||||||
# By default an aggregate always return a value
|
# By default an aggregate always return a value
|
||||||
@ -680,8 +668,20 @@ proc foundAnyResult(status: KVResult[bool]): bool {.inline.}=
|
|||||||
|
|
||||||
proc getValidatorInternalID(
|
proc getValidatorInternalID(
|
||||||
db: SlashingProtectionDB_v2,
|
db: SlashingProtectionDB_v2,
|
||||||
|
index: Option[ValidatorIndex],
|
||||||
validator: ValidatorPubKey): Option[ValidatorInternalID] =
|
validator: ValidatorPubKey): Option[ValidatorInternalID] =
|
||||||
## Retrieve a validator internal ID
|
## 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
|
let serializedPubkey = validator.toRaw() # Miracl/BLST to bytes
|
||||||
var valID: ValidatorInternalID
|
var valID: ValidatorInternalID
|
||||||
let status = db.sqlGetValidatorInternalID.exec(serializedPubkey) do (res: 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.
|
# Note: we enforce at the DB level that if the pubkey exists it is unique.
|
||||||
if status.foundAnyResult():
|
if status.foundAnyResult():
|
||||||
|
if index.isSome():
|
||||||
|
db.internalIds[index.get()] = valID
|
||||||
some(valID)
|
some(valID)
|
||||||
else:
|
else:
|
||||||
none(ValidatorInternalID)
|
none(ValidatorInternalID)
|
||||||
|
|
||||||
proc checkSlashableBlockProposal*(
|
proc checkSlashableBlockProposalOther(
|
||||||
db: SlashingProtectionDB_v2,
|
db: SlashingProtectionDB_v2,
|
||||||
validator: ValidatorPubKey,
|
valID: ValidatorInternalID,
|
||||||
slot: Slot
|
slot: Slot
|
||||||
): Result[void, BadProposal] =
|
): Result[void, BadProposal] =
|
||||||
## Returns an error if the specified validator
|
## Returns an error if the specified validator
|
||||||
@ -706,49 +708,6 @@ proc checkSlashableBlockProposal*(
|
|||||||
## Returns success otherwise
|
## Returns success otherwise
|
||||||
# TODO distinct type for the result block root
|
# 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
|
# EIP-3067 - Low-watermark
|
||||||
# Detect h(t1) <= h(t2)
|
# Detect h(t1) <= h(t2)
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
@ -778,38 +737,88 @@ proc checkSlashableBlockProposal*(
|
|||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
proc checkSlashableAttestation*(
|
proc checkSlashableBlockProposalDoubleProposal(
|
||||||
db: SlashingProtectionDB_v2,
|
db: SlashingProtectionDB_v2,
|
||||||
validator: ValidatorPubKey,
|
valID: ValidatorInternalID,
|
||||||
source: Epoch,
|
slot: Slot
|
||||||
target: Epoch
|
): Result[void, BadProposal] =
|
||||||
): Result[void, BadVote] =
|
|
||||||
## Returns an error if the specified validator
|
## Returns an error if the specified validator
|
||||||
## already voted for the specified slot
|
## already proposed a block for the specified slot.
|
||||||
## or would vote in a contradiction to previous votes
|
## This would lead to slashing.
|
||||||
## (surrounding vote or surrounded vote).
|
## The error contains the blockroot that was already proposed
|
||||||
##
|
##
|
||||||
## Returns success otherwise
|
## 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
|
# Sanity
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
if source > target:
|
if source > target:
|
||||||
return err(BadVote(kind: TargetPrecedesSource))
|
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
|
# Casper FFG 1st slashing condition
|
||||||
# Detect h(t1) = h(t2)
|
# Detect h(t1) = h(t2)
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
@ -833,12 +842,38 @@ proc checkSlashableAttestation*(
|
|||||||
existingAttestation: root
|
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
|
# Casper FFG 2nd slashing condition
|
||||||
# -> Surrounded vote
|
# -> Surrounded vote
|
||||||
# Detect h(s1) < h(s2) < h(t2) < h(t1)
|
# Detect h(s1) < h(s2) < h(t2) < h(t1)
|
||||||
|
# -> Surrounding vote
|
||||||
|
# Detect h(s2) < h(s1) < h(t1) < h(t2)
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
block:
|
block:
|
||||||
# Condition 3 part 2/3 at https://eips.ethereum.org/EIPS/eip-3076
|
# 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 root: ETH2Digest
|
||||||
var db_source, db_target: Epoch
|
var db_source, db_target: Epoch
|
||||||
|
|
||||||
@ -846,8 +881,8 @@ proc checkSlashableAttestation*(
|
|||||||
doAssert source <= high(int64).uint64
|
doAssert source <= high(int64).uint64
|
||||||
doAssert target <= high(int64).uint64
|
doAssert target <= high(int64).uint64
|
||||||
|
|
||||||
let status = db.sqlAttSurrounded.exec(
|
let status = db.sqlAttSurrounds.exec(
|
||||||
(valID, int64 source, int64 target)
|
(valID, int64 source, int64 target, int64 source, int64 target)
|
||||||
) do (res: tuple[source, target: int64, root: Hash32]):
|
) do (res: tuple[source, target: int64, root: Hash32]):
|
||||||
db_source = Epoch res.source
|
db_source = Epoch res.source
|
||||||
db_target = Epoch res.target
|
db_target = Epoch res.target
|
||||||
@ -858,35 +893,7 @@ proc checkSlashableAttestation*(
|
|||||||
# Conflicting attestation exist, log by caller
|
# Conflicting attestation exist, log by caller
|
||||||
# s1 < s2 < t2 < t1
|
# s1 < s2 < t2 < t1
|
||||||
return err(BadVote(
|
return err(BadVote(
|
||||||
kind: SurroundedVote,
|
kind: SurroundVote,
|
||||||
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,
|
|
||||||
existingAttestationRoot: root,
|
existingAttestationRoot: root,
|
||||||
sourceExisting: db_source,
|
sourceExisting: db_source,
|
||||||
targetExisting: db_target,
|
targetExisting: db_target,
|
||||||
@ -933,7 +940,31 @@ proc checkSlashableAttestation*(
|
|||||||
candidateTarget: target
|
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
|
# DB update
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
@ -948,16 +979,17 @@ proc registerValidator(db: SlashingProtectionDB_v2, validator: ValidatorPubKey)
|
|||||||
|
|
||||||
proc getOrRegisterValidator(
|
proc getOrRegisterValidator(
|
||||||
db: SlashingProtectionDB_v2,
|
db: SlashingProtectionDB_v2,
|
||||||
|
index: Option[ValidatorIndex],
|
||||||
validator: ValidatorPubKey): ValidatorInternalID =
|
validator: ValidatorPubKey): ValidatorInternalID =
|
||||||
## Get validator from the database
|
## Get validator from the database
|
||||||
## or register it and then return it
|
## or register it and then return it
|
||||||
let id = db.getValidatorInternalID(validator)
|
let id = db.getValidatorInternalID(index, validator)
|
||||||
if id.isNone():
|
if id.isNone():
|
||||||
info "No slashing protection data for validator - initiating",
|
info "No slashing protection data for validator - initiating",
|
||||||
validator = validator
|
validator = validator
|
||||||
|
|
||||||
db.registerValidator(validator)
|
db.registerValidator(validator)
|
||||||
let id = db.getValidatorInternalID(validator)
|
let id = db.getValidatorInternalID(index, validator)
|
||||||
doAssert id.isSome()
|
doAssert id.isSome()
|
||||||
id.unsafeGet()
|
id.unsafeGet()
|
||||||
else:
|
else:
|
||||||
@ -965,33 +997,65 @@ proc getOrRegisterValidator(
|
|||||||
|
|
||||||
proc registerBlock*(
|
proc registerBlock*(
|
||||||
db: SlashingProtectionDB_v2,
|
db: SlashingProtectionDB_v2,
|
||||||
|
index: Option[ValidatorIndex],
|
||||||
validator: ValidatorPubKey,
|
validator: ValidatorPubKey,
|
||||||
slot: Slot, block_root: Eth2Digest) =
|
slot: Slot, block_root: Eth2Digest): Result[void, BadProposal] =
|
||||||
## Add a block to the slashing protection DB
|
## Add a block to the slashing protection DB
|
||||||
## `checkSlashableBlockProposal` MUST be run
|
## `checkSlashableBlockProposal` MUST be run
|
||||||
## before to ensure no overwrite.
|
## 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
|
# 6 second (minimal preset) slots => overflow at ~1.75 trillion years under
|
||||||
# minimal preset, and twice that with mainnet preset
|
# minimal preset, and twice that with mainnet preset
|
||||||
doAssert slot <= high(int64).uint64
|
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(
|
let status = db.sqlInsertBlock.exec(
|
||||||
(valID, int64 slot,
|
(valID, int64 slot, block_root.data))
|
||||||
block_root.data))
|
if status.isErr():
|
||||||
doAssert status.isOk(),
|
# Inserting primarily fails when the constraint for double proposals is
|
||||||
"SQLite error when registering block: " & $status.error & "\n" &
|
# violated but may also happen due to disk full and other storage issues -
|
||||||
"for validator: 0x" & validator.toHex() & ", slot: " & $slot
|
# 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*(
|
proc registerAttestation*(
|
||||||
db: SlashingProtectionDB_v2,
|
db: SlashingProtectionDB_v2,
|
||||||
|
index: Option[ValidatorIndex],
|
||||||
validator: ValidatorPubKey,
|
validator: ValidatorPubKey,
|
||||||
source, target: Epoch,
|
source, target: Epoch,
|
||||||
attestation_root: Eth2Digest) =
|
attestation_root: Eth2Digest): Result[void, BadVote] =
|
||||||
## Add an attestation to the slashing protection DB
|
## Add an attestation to the slashing protection DB
|
||||||
## `checkSlashableAttestation` MUST be run
|
## `checkSlashableAttestation` MUST be run
|
||||||
## before to ensure no overwrite.
|
## 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)
|
# Overflows in 14 trillion years (minimal) or 112 trillion years (mainnet)
|
||||||
doAssert source <= high(int64).uint64
|
doAssert source <= high(int64).uint64
|
||||||
@ -1000,27 +1064,49 @@ proc registerAttestation*(
|
|||||||
let status = db.sqlInsertAtt.exec(
|
let status = db.sqlInsertAtt.exec(
|
||||||
(valID, int64 source, int64 target,
|
(valID, int64 source, int64 target,
|
||||||
attestation_root.data))
|
attestation_root.data))
|
||||||
doAssert status.isOk(),
|
if status.isErr():
|
||||||
"SQLite error when registering attestation: " & $status.error & "\n" &
|
# Inserting primarily fails when the constraint for double votes is
|
||||||
"for validator: 0x" & validator.toHex() &
|
# violated but may also happen due to disk full and other storage issues -
|
||||||
", sourceEpoch: " & $source &
|
# in any case, we'll return an error so that production is halted
|
||||||
", targetEpoch: " & $target
|
? 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
|
# 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
|
## Prune all blocks from a validator before the specified newMinSlot
|
||||||
## This is intended for interchange import to ensure
|
## This is intended for interchange import to ensure
|
||||||
## that in case of a gap, we don't allow signing in that gap.
|
## 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(
|
let status = db.sqlPruneValidatorBlocks.exec(
|
||||||
(valID, int64 newMinSlot))
|
(valID, int64 newMinSlot))
|
||||||
doAssert status.isOk(),
|
doAssert status.isOk(),
|
||||||
"SQLite error when pruning validator blocks: " & $status.error & "\n" &
|
"SQLite error when pruning validator blocks: " & $status.error & "\n" &
|
||||||
"for validator: 0x" & validator.toHex() & ", newMinSlot: " & $newMinSlot
|
"for validator: 0x" & validator.toHex() & ", newMinSlot: " & $newMinSlot
|
||||||
|
|
||||||
|
proc pruneBlocks*(
|
||||||
|
db: SlashingProtectionDB_v2,
|
||||||
|
validator: ValidatorPubkey, newMinSlot: Slot) =
|
||||||
|
pruneBlocks(db, none(ValidatorIndex), validator, newMinSlot)
|
||||||
|
|
||||||
proc pruneAttestations*(
|
proc pruneAttestations*(
|
||||||
db: SlashingProtectionDB_v2,
|
db: SlashingProtectionDB_v2,
|
||||||
|
index: Option[ValidatorIndex],
|
||||||
validator: ValidatorPubkey,
|
validator: ValidatorPubkey,
|
||||||
newMinSourceEpoch: int64,
|
newMinSourceEpoch: int64,
|
||||||
newMinTargetEpoch: int64) =
|
newMinTargetEpoch: int64) =
|
||||||
@ -1028,7 +1114,7 @@ proc pruneAttestations*(
|
|||||||
## This is intended for interchange import.
|
## This is intended for interchange import.
|
||||||
## Negative source/target epoch of -1 can be received if no attestation was imported
|
## 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)
|
## 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(
|
let status = db.sqlPruneValidatorAttestations.exec(
|
||||||
(valID, newMinSourceEpoch, newMinTargetEpoch))
|
(valID, newMinSourceEpoch, newMinTargetEpoch))
|
||||||
@ -1038,6 +1124,14 @@ proc pruneAttestations*(
|
|||||||
", newSourceEpoch: " & $newMinSourceEpoch &
|
", newSourceEpoch: " & $newMinSourceEpoch &
|
||||||
", newTargetEpoch: " & $newMinTargetEpoch
|
", newTargetEpoch: " & $newMinTargetEpoch
|
||||||
|
|
||||||
|
proc pruneAttestations*(
|
||||||
|
db: SlashingProtectionDB_v2,
|
||||||
|
validator: ValidatorPubkey,
|
||||||
|
newMinSourceEpoch: int64,
|
||||||
|
newMinTargetEpoch: int64) =
|
||||||
|
pruneAttestations(
|
||||||
|
db, none(ValidatorIndex), validator, newMinSourceEpoch, newMinTargetEpoch)
|
||||||
|
|
||||||
proc pruneAfterFinalization*(
|
proc pruneAfterFinalization*(
|
||||||
db: SlashingProtectionDB_v2,
|
db: SlashingProtectionDB_v2,
|
||||||
finalizedEpoch: Epoch
|
finalizedEpoch: Epoch
|
||||||
@ -1205,8 +1299,3 @@ proc inclSPDIR*(db: SlashingProtectionDB_v2, spdir: SPDIR): SlashingImportStatus
|
|||||||
# Create a mutable copy for sorting
|
# Create a mutable copy for sorting
|
||||||
var spdir = spdir
|
var spdir = spdir
|
||||||
return db.importInterchangeV5Impl(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)
|
slot = shortLog(slot)
|
||||||
return head
|
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
|
let
|
||||||
fork = getStateField(node.chainDag.headState, fork)
|
fork = getStateField(node.chainDag.headState, fork)
|
||||||
genesis_validators_root =
|
genesis_validators_root =
|
||||||
getStateField(node.chainDag.headState, genesis_validators_root)
|
getStateField(node.chainDag.headState, genesis_validators_root)
|
||||||
let
|
|
||||||
randao = await validator.genRandaoReveal(
|
randao = await validator.genRandaoReveal(
|
||||||
fork, genesis_validators_root, slot)
|
fork, genesis_validators_root, slot)
|
||||||
message = makeBeaconBlockForHeadAndSlot(
|
message = makeBeaconBlockForHeadAndSlot(
|
||||||
node, randao, validator_index, node.graffitiBytes, head, slot)
|
node, randao, validator_index, node.graffitiBytes, head, slot)
|
||||||
|
|
||||||
if not message.isSome():
|
if not message.isSome():
|
||||||
return head # already logged elsewhere!
|
return head # already logged elsewhere!
|
||||||
|
|
||||||
var
|
var
|
||||||
newBlock = SignedBeaconBlock(
|
newBlock = SignedBeaconBlock(
|
||||||
message: message.get()
|
message: message.get()
|
||||||
@ -369,9 +360,16 @@ proc proposeBlock(node: BeaconNode,
|
|||||||
# TODO: recomputed in block proposal
|
# TODO: recomputed in block proposal
|
||||||
let signing_root = compute_block_root(
|
let signing_root = compute_block_root(
|
||||||
fork, genesis_validators_root, slot, newBlock.root)
|
fork, genesis_validators_root, slot, newBlock.root)
|
||||||
node.attachedValidators
|
let notSlashable = node.attachedValidators
|
||||||
.slashingProtection
|
.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(
|
newBlock.signature = await validator.signBlockProposal(
|
||||||
fork, genesis_validators_root, slot, newBlock.root)
|
fork, genesis_validators_root, slot, newBlock.root)
|
||||||
@ -409,7 +407,7 @@ proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
|
|||||||
|
|
||||||
var attestations: seq[tuple[
|
var attestations: seq[tuple[
|
||||||
data: AttestationData, committeeLen, indexInCommittee: int,
|
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.
|
# 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
|
# 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(
|
let committee = get_beacon_committee(
|
||||||
epochRef, slot, committee_index.CommitteeIndex)
|
epochRef, slot, committee_index.CommitteeIndex)
|
||||||
|
|
||||||
for index_in_committee, validatorIdx in committee:
|
for index_in_committee, validator_index in committee:
|
||||||
let validator = node.getAttachedValidator(epochRef, validatorIdx)
|
let validator = node.getAttachedValidator(epochRef, validator_index)
|
||||||
if validator != nil:
|
if validator != nil:
|
||||||
let ad = makeAttestationData(
|
let ad = makeAttestationData(
|
||||||
epochRef, attestationHead, committee_index.CommitteeIndex)
|
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:
|
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
|
let notSlashable = node.attachedValidators
|
||||||
.slashingProtection
|
.slashingProtection
|
||||||
.checkSlashableAttestation(
|
.registerAttestation(
|
||||||
a.validator.pubkey,
|
a.validator_index,
|
||||||
a.data.source.epoch,
|
a.validator.pubkey,
|
||||||
a.data.target.epoch)
|
a.data.source.epoch,
|
||||||
|
a.data.target.epoch,
|
||||||
|
signing_root
|
||||||
|
)
|
||||||
if notSlashable.isOk():
|
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(
|
traceAsyncErrors createAndSendAttestation(
|
||||||
node, fork, genesis_validators_root, a.validator, a.data,
|
node, fork, genesis_validators_root, a.validator, a.data,
|
||||||
a.committeeLen, a.indexInCommittee, num_active_validators)
|
a.committeeLen, a.indexInCommittee, num_active_validators)
|
||||||
|
@ -11,7 +11,7 @@ import
|
|||||||
# Standard library
|
# Standard library
|
||||||
std/[os],
|
std/[os],
|
||||||
# Status lib
|
# Status lib
|
||||||
eth/db/kvstore,
|
eth/db/[kvstore, kvstore_sqlite3],
|
||||||
stew/results,
|
stew/results,
|
||||||
nimcrypto/utils,
|
nimcrypto/utils,
|
||||||
serialization,
|
serialization,
|
||||||
@ -25,25 +25,6 @@ import
|
|||||||
# Test utilies
|
# Test utilies
|
||||||
../testutil
|
../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 =
|
func hexToDigest(hex: string): Eth2Digest =
|
||||||
result = Eth2Digest.fromHex(hex)
|
result = Eth2Digest.fromHex(hex)
|
||||||
|
|
||||||
@ -59,7 +40,7 @@ suite "Slashing Protection DB - v1 and v2 migration" & preset():
|
|||||||
# https://eips.ethereum.org/EIPS/eip-3076
|
# https://eips.ethereum.org/EIPS/eip-3076
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
|
|
||||||
wrappedTimedTest "Minimal format migration" & preset():
|
test "Minimal format migration" & preset():
|
||||||
let genesis_validators_root = hexToDigest"0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673"
|
let genesis_validators_root = hexToDigest"0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673"
|
||||||
block: # export from a v1 DB
|
block: # export from a v1 DB
|
||||||
let db = SlashingProtectionDB_v1.init(
|
let db = SlashingProtectionDB_v1.init(
|
||||||
@ -71,18 +52,19 @@ suite "Slashing Protection DB - v1 and v2 migration" & preset():
|
|||||||
let pubkey = ValidatorPubKey
|
let pubkey = ValidatorPubKey
|
||||||
.fromHex"0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed"
|
.fromHex"0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed"
|
||||||
.get()
|
.get()
|
||||||
db.registerBlock(
|
check:
|
||||||
pubkey,
|
db.registerBlock(
|
||||||
Slot 81952,
|
pubkey,
|
||||||
Eth2Digest()
|
Slot 81952,
|
||||||
)
|
Eth2Digest()
|
||||||
|
).isOk()
|
||||||
|
|
||||||
db.registerAttestation(
|
db.registerAttestation(
|
||||||
pubkey,
|
pubkey,
|
||||||
source = Epoch 2290,
|
source = Epoch 2290,
|
||||||
target = Epoch 3007,
|
target = Epoch 3007,
|
||||||
Eth2Digest()
|
Eth2Digest()
|
||||||
)
|
).isOk()
|
||||||
|
|
||||||
let spdir = db.toSPDIR_lowWatermark()
|
let spdir = db.toSPDIR_lowWatermark()
|
||||||
Json.saveFile(
|
Json.saveFile(
|
||||||
|
@ -13,7 +13,7 @@ import
|
|||||||
nimcrypto/utils,
|
nimcrypto/utils,
|
||||||
chronicles,
|
chronicles,
|
||||||
# Internal
|
# Internal
|
||||||
../../beacon_chain/validators/slashing_protection,
|
../../beacon_chain/validators/[slashing_protection, slashing_protection_v2],
|
||||||
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
|
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
|
||||||
# Test utilies
|
# Test utilies
|
||||||
../testutil, ../testdbutil,
|
../testutil, ../testdbutil,
|
||||||
@ -174,7 +174,7 @@ proc runTest(identifier: string) =
|
|||||||
" " & $status & "\n"
|
" " & $status & "\n"
|
||||||
|
|
||||||
for blck in step.blocks:
|
for blck in step.blocks:
|
||||||
let status = db.checkSlashableBlockProposal(
|
let status = db.db_v2.checkSlashableBlockProposal(none(ValidatorIndex),
|
||||||
ValidatorPubKey.fromRaw(blck.pubkey.PubKeyBytes).get(),
|
ValidatorPubKey.fromRaw(blck.pubkey.PubKeyBytes).get(),
|
||||||
Slot blck.slot
|
Slot blck.slot
|
||||||
)
|
)
|
||||||
@ -190,7 +190,7 @@ proc runTest(identifier: string) =
|
|||||||
" for " & $toHexLogs(blck)
|
" for " & $toHexLogs(blck)
|
||||||
|
|
||||||
for att in step.attestations:
|
for att in step.attestations:
|
||||||
let status = db.checkSlashableAttestation(
|
let status = db.db_v2.checkSlashableAttestation(none(ValidatorIndex),
|
||||||
ValidatorPubKey.fromRaw(att.pubkey.PubKeyBytes).get(),
|
ValidatorPubKey.fromRaw(att.pubkey.PubKeyBytes).get(),
|
||||||
Epoch att.source_epoch,
|
Epoch att.source_epoch,
|
||||||
Epoch att.target_epoch
|
Epoch att.target_epoch
|
||||||
|
@ -11,11 +11,11 @@ import
|
|||||||
# Standard library
|
# Standard library
|
||||||
std/[os],
|
std/[os],
|
||||||
# Status lib
|
# Status lib
|
||||||
eth/db/kvstore,
|
eth/db/[kvstore, kvstore_sqlite3],
|
||||||
stew/results,
|
stew/results,
|
||||||
nimcrypto/utils,
|
nimcrypto/utils,
|
||||||
# Internal
|
# Internal
|
||||||
../../beacon_chain/validators/slashing_protection,
|
../../beacon_chain/validators/[slashing_protection, slashing_protection_v2],
|
||||||
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
|
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
|
||||||
# Test utilies
|
# Test utilies
|
||||||
../testutil
|
../testutil
|
||||||
@ -62,29 +62,30 @@ suite "Slashing Protection DB - Interchange" & preset():
|
|||||||
let pubkey = ValidatorPubKey
|
let pubkey = ValidatorPubKey
|
||||||
.fromHex"0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed"
|
.fromHex"0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed"
|
||||||
.get()
|
.get()
|
||||||
db.registerBlock(
|
check:
|
||||||
pubkey,
|
db.db_v2.registerBlock(
|
||||||
Slot 81952,
|
pubkey,
|
||||||
hexToDigest"0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b"
|
Slot 81952,
|
||||||
)
|
hexToDigest"0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b"
|
||||||
# db.registerBlock(
|
).isOk()
|
||||||
# pubkey,
|
# db.registerBlock(
|
||||||
# Slot 81951,
|
# pubkey,
|
||||||
# fakeRoot(65535)
|
# Slot 81951,
|
||||||
# )
|
# fakeRoot(65535)
|
||||||
|
# )
|
||||||
|
|
||||||
db.registerAttestation(
|
db.db_v2.registerAttestation(
|
||||||
pubkey,
|
pubkey,
|
||||||
source = Epoch 2290,
|
source = Epoch 2290,
|
||||||
target = Epoch 3007,
|
target = Epoch 3007,
|
||||||
hexToDigest"0x587d6a4f59a58fe24f406e0502413e77fe1babddee641fda30034ed37ecc884d"
|
hexToDigest"0x587d6a4f59a58fe24f406e0502413e77fe1babddee641fda30034ed37ecc884d"
|
||||||
)
|
).isOk()
|
||||||
db.registerAttestation(
|
db.db_v2.registerAttestation(
|
||||||
pubkey,
|
pubkey,
|
||||||
source = Epoch 2290,
|
source = Epoch 2290,
|
||||||
target = Epoch 3008,
|
target = Epoch 3008,
|
||||||
fakeRoot(65535)
|
fakeRoot(65535)
|
||||||
)
|
).isOk()
|
||||||
|
|
||||||
db.exportSlashingInterchange(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")
|
db.exportSlashingInterchange(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import
|
|||||||
# Standard library
|
# Standard library
|
||||||
std/[os],
|
std/[os],
|
||||||
# Status lib
|
# Status lib
|
||||||
eth/db/kvstore,
|
eth/db/[kvstore, kvstore_sqlite3],
|
||||||
stew/results,
|
stew/results,
|
||||||
# Internal
|
# Internal
|
||||||
../../beacon_chain/validators/slashing_protection,
|
../../beacon_chain/validators/slashing_protection,
|
||||||
@ -19,14 +19,6 @@ import
|
|||||||
# Test utilies
|
# Test utilies
|
||||||
../testutil
|
../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 =
|
func fakeRoot(index: SomeInteger): Eth2Digest =
|
||||||
## Create fake roots
|
## Create fake roots
|
||||||
## Those are just the value serialized in big-endian
|
## Those are just the value serialized in big-endian
|
||||||
@ -57,7 +49,7 @@ const TestDbName = "test_slashprot"
|
|||||||
# - (validator_id, slot)
|
# - (validator_id, slot)
|
||||||
|
|
||||||
suite "Slashing Protection DB" & preset():
|
suite "Slashing Protection DB" & preset():
|
||||||
wrappedTimedTest "Empty database" & preset():
|
test "Empty database" & preset():
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
let db = SlashingProtectionDB.init(
|
let db = SlashingProtectionDB.init(
|
||||||
default(Eth2Digest),
|
default(Eth2Digest),
|
||||||
@ -70,21 +62,24 @@ suite "Slashing Protection DB" & preset():
|
|||||||
|
|
||||||
check:
|
check:
|
||||||
db.checkSlashableBlockProposal(
|
db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(1234),
|
||||||
fakeValidator(1234),
|
fakeValidator(1234),
|
||||||
slot = Slot 1
|
slot = Slot 1
|
||||||
).isOk()
|
).isOk()
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(1234),
|
||||||
fakeValidator(1234),
|
fakeValidator(1234),
|
||||||
source = Epoch 1,
|
source = Epoch 1,
|
||||||
target = Epoch 2
|
target = Epoch 2
|
||||||
).isOk()
|
).isOk()
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(1234),
|
||||||
fakeValidator(1234),
|
fakeValidator(1234),
|
||||||
source = Epoch 2,
|
source = Epoch 2,
|
||||||
target = Epoch 1
|
target = Epoch 1
|
||||||
).error.kind == TargetPrecedesSource
|
).error.kind == TargetPrecedesSource
|
||||||
|
|
||||||
wrappedTimedTest "SP for block proposal - linear append":
|
test "SP for block proposal - linear append":
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
let db = SlashingProtectionDB.init(
|
let db = SlashingProtectionDB.init(
|
||||||
default(Eth2Digest),
|
default(Eth2Digest),
|
||||||
@ -95,53 +90,86 @@ suite "Slashing Protection DB" & preset():
|
|||||||
db.close()
|
db.close()
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
|
|
||||||
db.registerBlock(
|
|
||||||
fakeValidator(100),
|
|
||||||
Slot 10,
|
|
||||||
fakeRoot(100)
|
|
||||||
)
|
|
||||||
db.registerBlock(
|
|
||||||
fakeValidator(111),
|
|
||||||
Slot 15,
|
|
||||||
fakeRoot(111)
|
|
||||||
)
|
|
||||||
check:
|
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
|
# Slot occupied by same validator
|
||||||
db.checkSlashableBlockProposal(
|
db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
slot = Slot 10
|
slot = Slot 10
|
||||||
).isErr()
|
).isErr()
|
||||||
|
db.registerBlock(
|
||||||
|
ValidatorIndex(100),
|
||||||
|
fakeValidator(100),
|
||||||
|
slot = Slot 10,
|
||||||
|
fakeRoot(101)
|
||||||
|
).isErr()
|
||||||
# Slot occupied by another validator
|
# Slot occupied by another validator
|
||||||
db.checkSlashableBlockProposal(
|
db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
slot = Slot 15
|
slot = Slot 15
|
||||||
).isOk()
|
).isOk()
|
||||||
|
db.registerBlock(
|
||||||
|
ValidatorIndex(100),
|
||||||
|
fakeValidator(100),
|
||||||
|
slot = Slot 15,
|
||||||
|
fakeRoot(150)
|
||||||
|
).isOk()
|
||||||
# Slot occupied by same validator
|
# Slot occupied by same validator
|
||||||
db.checkSlashableBlockProposal(
|
db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(111),
|
||||||
fakeValidator(111),
|
fakeValidator(111),
|
||||||
slot = Slot 15
|
slot = Slot 15
|
||||||
).isErr()
|
).isErr()
|
||||||
|
db.registerBlock(
|
||||||
|
ValidatorIndex(111),
|
||||||
|
fakeValidator(111),
|
||||||
|
slot = Slot 15,
|
||||||
|
fakeRoot(151)
|
||||||
|
).isErr()
|
||||||
|
|
||||||
# Slot inoccupied
|
# Slot inoccupied
|
||||||
db.checkSlashableBlockProposal(
|
db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(255),
|
||||||
fakeValidator(255),
|
fakeValidator(255),
|
||||||
slot = Slot 20
|
slot = Slot 20
|
||||||
).isOk()
|
).isOk()
|
||||||
|
|
||||||
db.registerBlock(
|
db.registerBlock(
|
||||||
fakeValidator(255),
|
ValidatorIndex(255),
|
||||||
slot = Slot 20,
|
fakeValidator(255),
|
||||||
fakeRoot(4321)
|
slot = Slot 20,
|
||||||
)
|
fakeRoot(4321)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
check:
|
check:
|
||||||
# Slot now occupied
|
# Slot now occupied
|
||||||
db.checkSlashableBlockProposal(
|
db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(255),
|
||||||
fakeValidator(255),
|
fakeValidator(255),
|
||||||
slot = Slot 20
|
slot = Slot 20
|
||||||
).isErr()
|
).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)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
let db = SlashingProtectionDB.init(
|
let db = SlashingProtectionDB.init(
|
||||||
default(Eth2Digest),
|
default(Eth2Digest),
|
||||||
@ -153,111 +181,129 @@ suite "Slashing Protection DB" & preset():
|
|||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
|
|
||||||
# last finalized block
|
# last finalized block
|
||||||
db.registerBlock(
|
check:
|
||||||
fakeValidator(0),
|
db.registerBlock(
|
||||||
Slot 0,
|
ValidatorIndex(0),
|
||||||
fakeRoot(0)
|
fakeValidator(0),
|
||||||
)
|
Slot 0,
|
||||||
|
fakeRoot(0)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
db.registerBlock(
|
db.registerBlock(
|
||||||
fakeValidator(100),
|
ValidatorIndex(100),
|
||||||
Slot 10,
|
fakeValidator(100),
|
||||||
fakeRoot(10)
|
Slot 10,
|
||||||
)
|
fakeRoot(10)
|
||||||
db.registerBlock(
|
).isOk()
|
||||||
fakeValidator(100),
|
db.registerBlock(
|
||||||
Slot 20,
|
ValidatorIndex(100),
|
||||||
fakeRoot(20)
|
fakeValidator(100),
|
||||||
)
|
Slot 20,
|
||||||
|
fakeRoot(20)
|
||||||
|
).isOk()
|
||||||
for i in 0 ..< 30:
|
for i in 0 ..< 30:
|
||||||
|
let status = db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(100),
|
||||||
|
fakeValidator(100),
|
||||||
|
Slot i
|
||||||
|
)
|
||||||
if i > 10 and i != 20: # MinSlotViolation and DupSlot
|
if i > 10 and i != 20: # MinSlotViolation and DupSlot
|
||||||
let status = db.checkSlashableBlockProposal(
|
|
||||||
fakeValidator(100),
|
|
||||||
Slot i
|
|
||||||
)
|
|
||||||
doAssert status.isOk, "error: " & $status
|
doAssert status.isOk, "error: " & $status
|
||||||
else:
|
else:
|
||||||
let status = db.checkSlashableBlockProposal(
|
|
||||||
fakeValidator(100),
|
|
||||||
Slot i
|
|
||||||
)
|
|
||||||
doAssert status.isErr, "error: " & $status
|
doAssert status.isErr, "error: " & $status
|
||||||
db.registerBlock(
|
check:
|
||||||
fakeValidator(100),
|
db.registerBlock(
|
||||||
Slot 15,
|
ValidatorIndex(100),
|
||||||
fakeRoot(15)
|
fakeValidator(100),
|
||||||
)
|
Slot 15,
|
||||||
|
fakeRoot(15)
|
||||||
|
).isOk()
|
||||||
for i in 0 ..< 30:
|
for i in 0 ..< 30:
|
||||||
if i > 10 and i notin {15, 20}: # MinSlotViolation and DupSlot
|
if i > 10 and i notin {15, 20}: # MinSlotViolation and DupSlot
|
||||||
let status = db.checkSlashableBlockProposal(
|
let status = db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Slot i
|
Slot i
|
||||||
)
|
)
|
||||||
doAssert status.isOk, "error: " & $status
|
doAssert status.isOk, "error: " & $status
|
||||||
else:
|
else:
|
||||||
let status = db.checkSlashableBlockProposal(
|
let status = db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Slot i
|
Slot i
|
||||||
)
|
)
|
||||||
doAssert status.isErr, "error: " & $status
|
doAssert status.isErr, "error: " & $status
|
||||||
check:
|
check:
|
||||||
db.checkSlashableBlockProposal(
|
db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(0xDEADBEEF),
|
||||||
fakeValidator(0xDEADBEEF),
|
fakeValidator(0xDEADBEEF),
|
||||||
Slot i
|
Slot i
|
||||||
).isOk()
|
).isOk()
|
||||||
db.registerBlock(
|
check:
|
||||||
fakeValidator(100),
|
db.registerBlock(
|
||||||
Slot 12,
|
ValidatorIndex(100),
|
||||||
fakeRoot(12)
|
fakeValidator(100),
|
||||||
)
|
Slot 12,
|
||||||
db.registerBlock(
|
fakeRoot(12)
|
||||||
fakeValidator(100),
|
).isOk()
|
||||||
Slot 17,
|
db.registerBlock(
|
||||||
fakeRoot(17)
|
ValidatorIndex(100),
|
||||||
)
|
fakeValidator(100),
|
||||||
|
Slot 17,
|
||||||
|
fakeRoot(17)
|
||||||
|
).isOk()
|
||||||
for i in 0 ..< 30:
|
for i in 0 ..< 30:
|
||||||
if i > 10 and i notin {12, 15, 17, 20}:
|
if i > 10 and i notin {12, 15, 17, 20}:
|
||||||
let status = db.checkSlashableBlockProposal(
|
let status = db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Slot i
|
Slot i
|
||||||
)
|
)
|
||||||
doAssert status.isOk, "error: " & $status
|
doAssert status.isOk, "error: " & $status
|
||||||
else:
|
else:
|
||||||
let status = db.checkSlashableBlockProposal(
|
let status = db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Slot i
|
Slot i
|
||||||
)
|
)
|
||||||
doAssert status.isErr, "error: " & $status
|
doAssert status.isErr, "error: " & $status
|
||||||
check:
|
check:
|
||||||
db.checkSlashableBlockProposal(
|
db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(0xDEADBEEF),
|
||||||
fakeValidator(0xDEADBEEF),
|
fakeValidator(0xDEADBEEF),
|
||||||
Slot i
|
Slot i
|
||||||
).isOk()
|
).isOk()
|
||||||
db.registerBlock(
|
check:
|
||||||
fakeValidator(100),
|
db.registerBlock(
|
||||||
Slot 29,
|
ValidatorIndex(100),
|
||||||
fakeRoot(29)
|
fakeValidator(100),
|
||||||
)
|
Slot 29,
|
||||||
|
fakeRoot(29)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
for i in 0 ..< 30:
|
for i in 0 ..< 30:
|
||||||
if i > 10 and i notin {12, 15, 17, 20, 29}:
|
if i > 10 and i notin {12, 15, 17, 20, 29}:
|
||||||
let status = db.checkSlashableBlockProposal(
|
let status = db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Slot i
|
Slot i
|
||||||
)
|
)
|
||||||
doAssert status.isOk, "error: " & $status
|
doAssert status.isOk, "error: " & $status
|
||||||
else:
|
else:
|
||||||
let status = db.checkSlashableBlockProposal(
|
let status = db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Slot i
|
Slot i
|
||||||
)
|
)
|
||||||
doAssert status.isErr, "error: " & $status
|
doAssert status.isErr, "error: " & $status
|
||||||
check:
|
check:
|
||||||
db.checkSlashableBlockProposal(
|
db.checkSlashableBlockProposal(
|
||||||
|
ValidatorIndex(0xDEADBEEF),
|
||||||
fakeValidator(0xDEADBEEF),
|
fakeValidator(0xDEADBEEF),
|
||||||
Slot i
|
Slot i
|
||||||
).isOk()
|
).isOk()
|
||||||
|
|
||||||
wrappedTimedTest "SP for same epoch attestation target - linear append":
|
test "SP for same epoch attestation target - linear append":
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
let db = SlashingProtectionDB.init(
|
let db = SlashingProtectionDB.init(
|
||||||
default(Eth2Digest),
|
default(Eth2Digest),
|
||||||
@ -268,53 +314,81 @@ suite "Slashing Protection DB" & preset():
|
|||||||
db.close()
|
db.close()
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
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:
|
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
|
# Epoch occupied by same validator
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 0, Epoch 10,
|
Epoch 0, Epoch 10,
|
||||||
).error.kind == DoubleVote
|
).error.kind == DoubleVote
|
||||||
|
db.registerAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
|
fakeValidator(100),
|
||||||
|
Epoch 0, Epoch 10, fakeRoot(101)
|
||||||
|
).error.kind == DoubleVote
|
||||||
|
|
||||||
# Epoch occupied by another validator
|
# Epoch occupied by another validator
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 0, Epoch 15
|
Epoch 0, Epoch 15
|
||||||
).isOk()
|
).isOk()
|
||||||
|
db.registerAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
|
fakeValidator(100),
|
||||||
|
Epoch 0, Epoch 15, fakeRoot(151)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
# Epoch occupied by same validator
|
# Epoch occupied by same validator
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(111),
|
||||||
fakeValidator(111),
|
fakeValidator(111),
|
||||||
Epoch 0, Epoch 15
|
Epoch 0, Epoch 15
|
||||||
).error.kind == DoubleVote
|
).error.kind == DoubleVote
|
||||||
|
db.registerAttestation(
|
||||||
|
ValidatorIndex(111),
|
||||||
|
fakeValidator(111),
|
||||||
|
Epoch 0, Epoch 15, fakeRoot(161)
|
||||||
|
).error.kind == DoubleVote
|
||||||
|
|
||||||
# Epoch inoccupied
|
# Epoch inoccupied
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(255),
|
||||||
fakeValidator(255),
|
fakeValidator(255),
|
||||||
Epoch 0, Epoch 20
|
Epoch 0, Epoch 20
|
||||||
).isOk()
|
).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
|
# Epoch now occupied
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(255),
|
||||||
fakeValidator(255),
|
fakeValidator(255),
|
||||||
Epoch 0, Epoch 20
|
Epoch 0, Epoch 20
|
||||||
).error.kind == DoubleVote
|
).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:
|
block:
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
let db = SlashingProtectionDB.init(
|
let db = SlashingProtectionDB.init(
|
||||||
@ -326,22 +400,26 @@ suite "Slashing Protection DB" & preset():
|
|||||||
db.close()
|
db.close()
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
|
|
||||||
db.registerAttestation(
|
|
||||||
fakeValidator(100),
|
|
||||||
Epoch 10, Epoch 20,
|
|
||||||
fakeRoot(20)
|
|
||||||
)
|
|
||||||
|
|
||||||
check:
|
check:
|
||||||
|
db.registerAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
|
fakeValidator(100),
|
||||||
|
Epoch 10, Epoch 20,
|
||||||
|
fakeRoot(20)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 11, Epoch 19
|
Epoch 11, Epoch 19
|
||||||
).error.kind == SurroundedVote
|
).error.kind == SurroundVote
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(200),
|
||||||
fakeValidator(200),
|
fakeValidator(200),
|
||||||
Epoch 11, Epoch 19
|
Epoch 11, Epoch 19
|
||||||
).isOk
|
).isOk
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 11, Epoch 21
|
Epoch 11, Epoch 21
|
||||||
).isOk
|
).isOk
|
||||||
@ -357,39 +435,44 @@ suite "Slashing Protection DB" & preset():
|
|||||||
db.close()
|
db.close()
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
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:
|
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(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 11, Epoch 19
|
Epoch 11, Epoch 19
|
||||||
).error.kind == SurroundedVote
|
).error.kind == SurroundVote
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(200),
|
||||||
fakeValidator(200),
|
fakeValidator(200),
|
||||||
Epoch 11, Epoch 19
|
Epoch 11, Epoch 19
|
||||||
).isOk
|
).isOk
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 11, Epoch 21
|
Epoch 11, Epoch 21
|
||||||
).isOk
|
).isOk
|
||||||
# TODO: is that possible?
|
# TODO: is that possible?
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 9, Epoch 19
|
Epoch 9, Epoch 19
|
||||||
).isOk
|
).isOk
|
||||||
|
|
||||||
|
test "SP for surrounding attestations":
|
||||||
wrappedTimedTest "SP for surrounding attestations":
|
|
||||||
block:
|
block:
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
let db = SlashingProtectionDB.init(
|
let db = SlashingProtectionDB.init(
|
||||||
@ -400,22 +483,24 @@ suite "Slashing Protection DB" & preset():
|
|||||||
defer:
|
defer:
|
||||||
db.close()
|
db.close()
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
|
|
||||||
db.registerAttestation(
|
|
||||||
fakeValidator(100),
|
|
||||||
Epoch 10, Epoch 20,
|
|
||||||
fakeRoot(20)
|
|
||||||
)
|
|
||||||
|
|
||||||
check:
|
check:
|
||||||
|
db.registerAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
|
fakeValidator(100),
|
||||||
|
Epoch 10, Epoch 20,
|
||||||
|
fakeRoot(20)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 9, Epoch 21
|
Epoch 9, Epoch 21
|
||||||
).error.kind == SurroundingVote
|
).error.kind == SurroundVote
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 0, Epoch 21
|
Epoch 0, Epoch 21
|
||||||
).error.kind == SurroundingVote
|
).error.kind == SurroundVote
|
||||||
|
|
||||||
block:
|
block:
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
@ -428,29 +513,34 @@ suite "Slashing Protection DB" & preset():
|
|||||||
db.close()
|
db.close()
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
|
|
||||||
db.registerAttestation(
|
check:
|
||||||
fakeValidator(100),
|
db.registerAttestation(
|
||||||
Epoch 0, Epoch 1,
|
ValidatorIndex(100),
|
||||||
fakeRoot(1)
|
fakeValidator(100),
|
||||||
)
|
Epoch 0, Epoch 1,
|
||||||
|
fakeRoot(1)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
db.registerAttestation(
|
db.registerAttestation(
|
||||||
fakeValidator(100),
|
ValidatorIndex(100),
|
||||||
Epoch 10, Epoch 20,
|
fakeValidator(100),
|
||||||
fakeRoot(20)
|
Epoch 10, Epoch 20,
|
||||||
)
|
fakeRoot(20)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
check:
|
check:
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 9, Epoch 21
|
Epoch 9, Epoch 21
|
||||||
).error.kind == SurroundingVote
|
).error.kind == SurroundVote
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 0, Epoch 21
|
Epoch 0, Epoch 21
|
||||||
).error.kind == SurroundingVote
|
).error.kind == SurroundVote
|
||||||
|
|
||||||
wrappedTimedTest "Attestation ordering #1698":
|
test "Attestation ordering #1698":
|
||||||
block:
|
block:
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
let db = SlashingProtectionDB.init(
|
let db = SlashingProtectionDB.init(
|
||||||
@ -462,41 +552,47 @@ suite "Slashing Protection DB" & preset():
|
|||||||
db.close()
|
db.close()
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
|
|
||||||
db.registerAttestation(
|
check:
|
||||||
fakeValidator(100),
|
db.registerAttestation(
|
||||||
Epoch 1, Epoch 2,
|
ValidatorIndex(100),
|
||||||
fakeRoot(2)
|
fakeValidator(100),
|
||||||
)
|
Epoch 1, Epoch 2,
|
||||||
|
fakeRoot(2)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
db.registerAttestation(
|
db.registerAttestation(
|
||||||
fakeValidator(100),
|
ValidatorIndex(100),
|
||||||
Epoch 8, Epoch 10,
|
fakeValidator(100),
|
||||||
fakeRoot(10)
|
Epoch 8, Epoch 10,
|
||||||
)
|
fakeRoot(10)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
db.registerAttestation(
|
db.registerAttestation(
|
||||||
fakeValidator(100),
|
ValidatorIndex(100),
|
||||||
Epoch 14, Epoch 15,
|
fakeValidator(100),
|
||||||
fakeRoot(15)
|
Epoch 14, Epoch 15,
|
||||||
)
|
fakeRoot(15)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
# The current list is, 2 -> 10 -> 15
|
# The current list is, 2 -> 10 -> 15
|
||||||
|
|
||||||
db.registerAttestation(
|
db.registerAttestation(
|
||||||
fakeValidator(100),
|
ValidatorIndex(100),
|
||||||
Epoch 3, Epoch 6,
|
fakeValidator(100),
|
||||||
fakeRoot(6)
|
Epoch 3, Epoch 6,
|
||||||
)
|
fakeRoot(6)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
# The current list is 2 -> 6 -> 10 -> 15
|
# The current list is 2 -> 6 -> 10 -> 15
|
||||||
|
|
||||||
check:
|
check:
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 7, Epoch 11
|
Epoch 7, Epoch 11
|
||||||
).error.kind == SurroundingVote
|
).error.kind == SurroundVote
|
||||||
|
|
||||||
wrappedTimedTest "Test valid attestation #1699":
|
test "Test valid attestation #1699":
|
||||||
block:
|
block:
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
let db = SlashingProtectionDB.init(
|
let db = SlashingProtectionDB.init(
|
||||||
@ -508,20 +604,24 @@ suite "Slashing Protection DB" & preset():
|
|||||||
db.close()
|
db.close()
|
||||||
sqlite3db_delete(TestDir, TestDbName)
|
sqlite3db_delete(TestDir, TestDbName)
|
||||||
|
|
||||||
db.registerAttestation(
|
check:
|
||||||
fakeValidator(100),
|
db.registerAttestation(
|
||||||
Epoch 10, Epoch 20,
|
ValidatorIndex(100),
|
||||||
fakeRoot(20)
|
fakeValidator(100),
|
||||||
)
|
Epoch 10, Epoch 20,
|
||||||
|
fakeRoot(20)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
db.registerAttestation(
|
db.registerAttestation(
|
||||||
fakeValidator(100),
|
ValidatorIndex(100),
|
||||||
Epoch 40, Epoch 50,
|
fakeValidator(100),
|
||||||
fakeRoot(50)
|
Epoch 40, Epoch 50,
|
||||||
)
|
fakeRoot(50)
|
||||||
|
).isOk()
|
||||||
|
|
||||||
check:
|
check:
|
||||||
db.checkSlashableAttestation(
|
db.checkSlashableAttestation(
|
||||||
|
ValidatorIndex(100),
|
||||||
fakeValidator(100),
|
fakeValidator(100),
|
||||||
Epoch 20, Epoch 30
|
Epoch 20, Epoch 30
|
||||||
).isOk
|
).isOk
|
||||||
|
Loading…
x
Reference in New Issue
Block a user