528 lines
13 KiB
Nim
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
|