nimbus-eth2/tests/slashing_protection/test_slashing_interchange.nim
Jacek Sieka efdf759cc0
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.
2021-05-04 15:17:28 +02:00

131 lines
4.6 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/[os],
# Status lib
eth/db/[kvstore, kvstore_sqlite3],
stew/results,
nimcrypto/utils,
# Internal
../../beacon_chain/validators/[slashing_protection, slashing_protection_v2],
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
# Test utilies
../testutil
func fakeRoot(index: SomeInteger): Eth2Digest =
## Create fake roots
## Those are just the value serialized in big-endian
## We prevent zero hash special case via a power of 2 prefix
result.data[0 ..< 8] = (1'u64 shl 32 + index.uint64).toBytesBE()
func fakeValidator(index: SomeInteger): ValidatorPubKey =
## Create fake validator public key
result = ValidatorPubKey()
result.blob[0 ..< 8] = (1'u64 shl 48 + index.uint64).toBytesBE()
func hexToDigest(hex: string): Eth2Digest =
result = Eth2Digest.fromHex(hex)
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"
suite "Slashing Protection DB - Interchange" & preset():
# https://hackmd.io/@sproul/Bk0Y0qdGD#Format-1-Complete
# https://eips.ethereum.org/EIPS/eip-3076
sqlite3db_delete(TestDir, TestDbName)
test "Smoke test - Complete format" & preset():
let genesis_validators_root = hexToDigest"0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673"
block: # export
let db = SlashingProtectionDB.init(
genesis_validators_root,
TestDir,
TestDbName
)
defer:
db.close()
sqlite3db_delete(TestDir, TestDbName)
let pubkey = ValidatorPubKey
.fromHex"0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed"
.get()
check:
db.db_v2.registerBlock(
pubkey,
Slot 81952,
hexToDigest"0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b"
).isOk()
# db.registerBlock(
# pubkey,
# Slot 81951,
# fakeRoot(65535)
# )
db.db_v2.registerAttestation(
pubkey,
source = Epoch 2290,
target = Epoch 3007,
hexToDigest"0x587d6a4f59a58fe24f406e0502413e77fe1babddee641fda30034ed37ecc884d"
).isOk()
db.db_v2.registerAttestation(
pubkey,
source = Epoch 2290,
target = Epoch 3008,
fakeRoot(65535)
).isOk()
db.exportSlashingInterchange(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")
block: # import - zero root db
let db2 = SlashingProtectionDB.init(
Eth2Digest(),
TestDir,
TestDbName
)
defer:
db2.close()
sqlite3db_delete(TestDir, TestDbName)
doAssert siSuccess == db2.importSlashingInterchange(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")
db2.exportSlashingInterchange(currentSourcePath.parentDir/"test_complete_export_slashing_protection_roundtrip1.json")
block: # import - same root db
let db3 = SlashingProtectionDB.init(
genesis_validators_root,
TestDir,
TestDbName
)
defer:
db3.close()
sqlite3db_delete(TestDir, TestDbName)
doAssert siSuccess == db3.importSlashingInterchange(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")
db3.exportSlashingInterchange(currentSourcePath.parentDir/"test_complete_export_slashing_protection_roundtrip2.json")
test "Smoke test - Complete format - Invalid database is refused" & preset():
block: # import - invalid root db
let invalid_genvalroot = hexToDigest"0x1234"
let db4 = SlashingProtectionDB.init(
invalid_genvalroot,
TestDir,
TestDbName
)
defer:
db4.close()
sqlite3db_delete(TestDir, TestDbName)
doAssert siFailure == db4.importSlashingInterchange(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")