nimbus-eth2/tests/slashing_protection/test_slashing_protection_db...

528 lines
13 KiB
Nim

# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.used.}
import
# Standard library
std/[unittest, os],
# Status lib
eth/db/kvstore,
stew/results,
# Internal
../../beacon_chain/validators/slashing_protection,
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
# Test utilies
../testutil
template wrappedTimedTest(name: string, body: untyped) =
# `check` macro takes a copy of whatever it's checking, on the stack!
block: # Symbol namespacing
proc wrappedTest() =
timedTest 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()
proc sqlite3db_delete(basepath, dbname: string) =
removeFile(basepath / dbname&".sqlite3-shm")
removeFile(basepath / dbname&".sqlite3-wal")
removeFile(basepath / dbname&".sqlite3")
const TestDir = ""
const TestDbName = "test_slashprot"
# Reminder of SQLite constraints for fake data:
# attestations:
# - all fields are NOT NULL
# - attestation_root is unique
# - (validator_id, target_epoch)
# blocks:
# - all fields are NOT NULL
# - block_root is unique
# - (validator_id, slot)
suiteReport "Slashing Protection DB" & preset():
wrappedTimedTest "Empty database" & preset():
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
check:
db.checkSlashableBlockProposal(
fakeValidator(1234),
slot = Slot 1
).isOk()
db.checkSlashableAttestation(
fakeValidator(1234),
source = Epoch 1,
target = Epoch 2
).isOk()
db.checkSlashableAttestation(
fakeValidator(1234),
source = Epoch 2,
target = Epoch 1
).error.kind == TargetPrecedesSource
wrappedTimedTest "SP for block proposal - linear append":
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
db.registerBlock(
fakeValidator(100),
Slot 10,
fakeRoot(100)
)
db.registerBlock(
fakeValidator(111),
Slot 15,
fakeRoot(111)
)
check:
# Slot occupied by same validator
db.checkSlashableBlockProposal(
fakeValidator(100),
slot = Slot 10
).isErr()
# Slot occupied by another validator
db.checkSlashableBlockProposal(
fakeValidator(100),
slot = Slot 15
).isOk()
# Slot occupied by same validator
db.checkSlashableBlockProposal(
fakeValidator(111),
slot = Slot 15
).isErr()
# Slot inoccupied
db.checkSlashableBlockProposal(
fakeValidator(255),
slot = Slot 20
).isOk()
db.registerBlock(
fakeValidator(255),
slot = Slot 20,
fakeRoot(4321)
)
check:
# Slot now occupied
db.checkSlashableBlockProposal(
fakeValidator(255),
slot = Slot 20
).isErr()
wrappedTimedTest "SP for block proposal - backtracking append":
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
# last finalized block
db.registerBlock(
fakeValidator(0),
Slot 0,
fakeRoot(0)
)
db.registerBlock(
fakeValidator(100),
Slot 10,
fakeRoot(10)
)
db.registerBlock(
fakeValidator(100),
Slot 20,
fakeRoot(20)
)
for i in 0 ..< 30:
if i > 10 and i != 20: # MinSlotViolation and DupSlot
let status = db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
)
doAssert status.isOk, "error: " & $status
else:
let status = db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
)
doAssert status.isErr, "error: " & $status
db.registerBlock(
fakeValidator(100),
Slot 15,
fakeRoot(15)
)
for i in 0 ..< 30:
if i > 10 and i notin {15, 20}: # MinSlotViolation and DupSlot
let status = db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
)
doAssert status.isOk, "error: " & $status
else:
let status = db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
)
doAssert status.isErr, "error: " & $status
check:
db.checkSlashableBlockProposal(
fakeValidator(0xDEADBEEF),
Slot i
).isOk()
db.registerBlock(
fakeValidator(100),
Slot 12,
fakeRoot(12)
)
db.registerBlock(
fakeValidator(100),
Slot 17,
fakeRoot(17)
)
for i in 0 ..< 30:
if i > 10 and i notin {12, 15, 17, 20}:
let status = db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
)
doAssert status.isOk, "error: " & $status
else:
let status = db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
)
doAssert status.isErr, "error: " & $status
check:
db.checkSlashableBlockProposal(
fakeValidator(0xDEADBEEF),
Slot i
).isOk()
db.registerBlock(
fakeValidator(100),
Slot 29,
fakeRoot(29)
)
for i in 0 ..< 30:
if i > 10 and i notin {12, 15, 17, 20, 29}:
let status = db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
)
doAssert status.isOk, "error: " & $status
else:
let status = db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
)
doAssert status.isErr, "error: " & $status
check:
db.checkSlashableBlockProposal(
fakeValidator(0xDEADBEEF),
Slot i
).isOk()
wrappedTimedTest "SP for same epoch attestation target - linear append":
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 10,
fakeRoot(100)
)
db.registerAttestation(
fakeValidator(111),
Epoch 0, Epoch 15,
fakeRoot(111)
)
check:
# Epoch occupied by same validator
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch 10,
).error.kind == DoubleVote
# Epoch occupied by another validator
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch 15
).isOk()
# Epoch occupied by same validator
db.checkSlashableAttestation(
fakeValidator(111),
Epoch 0, Epoch 15
).error.kind == DoubleVote
# Epoch inoccupied
db.checkSlashableAttestation(
fakeValidator(255),
Epoch 0, Epoch 20
).isOk()
db.registerAttestation(
fakeValidator(255),
Epoch 0, Epoch 20,
fakeRoot(4321)
)
check:
# Epoch now occupied
db.checkSlashableAttestation(
fakeValidator(255),
Epoch 0, Epoch 20
).error.kind == DoubleVote
wrappedTimedTest "SP for surrounded attestations":
block:
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
db.registerAttestation(
fakeValidator(100),
Epoch 10, Epoch 20,
fakeRoot(20)
)
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 11, Epoch 19
).error.kind == SurroundedVote
db.checkSlashableAttestation(
fakeValidator(200),
Epoch 11, Epoch 19
).isOk
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 11, Epoch 21
).isOk
block:
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 1,
fakeRoot(1)
)
db.registerAttestation(
fakeValidator(100),
Epoch 10, Epoch 20,
fakeRoot(20)
)
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 11, Epoch 19
).error.kind == SurroundedVote
db.checkSlashableAttestation(
fakeValidator(200),
Epoch 11, Epoch 19
).isOk
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 11, Epoch 21
).isOk
# TODO: is that possible?
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 9, Epoch 19
).isOk
wrappedTimedTest "SP for surrounding attestations":
block:
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
db.registerAttestation(
fakeValidator(100),
Epoch 10, Epoch 20,
fakeRoot(20)
)
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 9, Epoch 21
).error.kind == SurroundingVote
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch 21
).error.kind == SurroundingVote
block:
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 1,
fakeRoot(1)
)
db.registerAttestation(
fakeValidator(100),
Epoch 10, Epoch 20,
fakeRoot(20)
)
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 9, Epoch 21
).error.kind == SurroundingVote
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch 21
).error.kind == SurroundingVote
wrappedTimedTest "Attestation ordering #1698":
block:
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
db.registerAttestation(
fakeValidator(100),
Epoch 1, Epoch 2,
fakeRoot(2)
)
db.registerAttestation(
fakeValidator(100),
Epoch 8, Epoch 10,
fakeRoot(10)
)
db.registerAttestation(
fakeValidator(100),
Epoch 14, Epoch 15,
fakeRoot(15)
)
# The current list is, 2 -> 10 -> 15
db.registerAttestation(
fakeValidator(100),
Epoch 3, Epoch 6,
fakeRoot(6)
)
# The current list is 2 -> 6 -> 10 -> 15
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 7, Epoch 11
).error.kind == SurroundingVote
wrappedTimedTest "Test valid attestation #1699":
block:
sqlite3db_delete(TestDir, TestDbName)
let db = SlashingProtectionDB.init(
default(Eth2Digest),
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
db.registerAttestation(
fakeValidator(100),
Epoch 10, Epoch 20,
fakeRoot(20)
)
db.registerAttestation(
fakeValidator(100),
Epoch 40, Epoch 50,
fakeRoot(50)
)
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 20, Epoch 30
).isOk