304 lines
10 KiB
Nim
304 lines
10 KiB
Nim
# beacon_chain
|
|
# Copyright (c) 2022-2024 Status Research & Development GmbH
|
|
# Licensed and distributed under either of
|
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
# * 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.
|
|
|
|
{.used.}
|
|
|
|
import
|
|
std/[algorithm, sequtils],
|
|
chronos/unittest2/asynctests,
|
|
presto, confutils,
|
|
../beacon_chain/validators/[validator_pool, keystore_management],
|
|
../beacon_chain/[conf, beacon_node]
|
|
|
|
func createPubKey(number: int8): ValidatorPubKey =
|
|
var res = ValidatorPubKey()
|
|
res.blob[0] = uint8(number)
|
|
res
|
|
|
|
func createLocal(pubkey: ValidatorPubKey): KeystoreData =
|
|
KeystoreData(kind: KeystoreKind.Local, pubkey: pubkey)
|
|
|
|
func createRemote(pubkey: ValidatorPubKey): KeystoreData =
|
|
KeystoreData(kind: KeystoreKind.Remote, pubkey: pubkey)
|
|
|
|
func createDynamic(url: Uri, pubkey: ValidatorPubKey): KeystoreData =
|
|
KeystoreData(kind: KeystoreKind.Remote, pubkey: pubkey,
|
|
remotes: @[RemoteSignerInfo(url: HttpHostUri(url))],
|
|
flags: {RemoteKeystoreFlag.DynamicKeystore})
|
|
|
|
const
|
|
remoteSignerUrl = Web3SignerUrl(url: parseUri("http://nimbus.team/signer1"))
|
|
|
|
func makeValidatorAndIndex(
|
|
index: ValidatorIndex, activation_epoch: Epoch): Opt[ValidatorAndIndex] =
|
|
Opt.some ValidatorAndIndex(
|
|
index: index,
|
|
validator: Validator(activation_epoch: activation_epoch)
|
|
)
|
|
|
|
func cmp(a, b: array[48, byte]): int =
|
|
for index, ch in a.pairs():
|
|
if ch < b[index]:
|
|
return -1
|
|
elif ch > b[index]:
|
|
return 1
|
|
0
|
|
|
|
func cmp(a, b: KeystoreData): int =
|
|
if (a.kind == b.kind) and (a.pubkey == b.pubkey):
|
|
if a.kind == KeystoreKind.Remote:
|
|
if a.flags == b.flags:
|
|
0
|
|
else:
|
|
card(a.flags) - card(b.flags)
|
|
else:
|
|
0
|
|
else:
|
|
cmp(a.pubkey.blob, b.pubkey.blob)
|
|
|
|
func checkResponse(a, b: openArray[KeystoreData]): bool =
|
|
if len(a) != len(b): return false
|
|
for index, item in a.pairs():
|
|
if cmp(item, b[index]) != 0:
|
|
return false
|
|
true
|
|
|
|
suite "Validator pool":
|
|
test "Doppelganger for genesis validator":
|
|
let
|
|
v = AttachedValidator(activationEpoch: FAR_FUTURE_EPOCH)
|
|
|
|
check:
|
|
not v.triggersDoppelganger(GENESIS_EPOCH) # no check
|
|
not v.doppelgangerReady(GENESIS_EPOCH.start_slot) # no activation
|
|
|
|
v.updateValidator(makeValidatorAndIndex(ValidatorIndex(1), GENESIS_EPOCH))
|
|
|
|
check:
|
|
not v.triggersDoppelganger(GENESIS_EPOCH) # no check
|
|
v.doppelgangerReady(GENESIS_EPOCH.start_slot) # ready in activation epoch
|
|
not v.doppelgangerReady((GENESIS_EPOCH + 1).start_slot) # old check
|
|
|
|
v.doppelgangerChecked(GENESIS_EPOCH)
|
|
|
|
check:
|
|
v.triggersDoppelganger(GENESIS_EPOCH) # checked, triggered
|
|
v.doppelgangerReady((GENESIS_EPOCH + 1).start_slot) # checked
|
|
v.doppelgangerReady((GENESIS_EPOCH + 2).start_slot) # 1 slot lag allowance
|
|
not v.doppelgangerReady((GENESIS_EPOCH + 2).start_slot + 1) # old check
|
|
|
|
test "Doppelganger for validator that activates in same epoch as check":
|
|
let
|
|
v = AttachedValidator(activationEpoch: FAR_FUTURE_EPOCH)
|
|
now = Epoch(10).start_slot()
|
|
|
|
check: # We don't know when validator activates so we wouldn't trigger
|
|
not v.triggersDoppelganger(GENESIS_EPOCH)
|
|
not v.triggersDoppelganger(now.epoch())
|
|
|
|
not v.doppelgangerReady(GENESIS_EPOCH.start_slot)
|
|
not v.doppelgangerReady(now)
|
|
|
|
v.updateValidator(makeValidatorAndIndex(ValidatorIndex(5), FAR_FUTURE_EPOCH))
|
|
|
|
check: # We still don't know when validator activates so we wouldn't trigger
|
|
not v.triggersDoppelganger(GENESIS_EPOCH)
|
|
not v.triggersDoppelganger(now.epoch())
|
|
|
|
not v.doppelgangerReady(GENESIS_EPOCH.start_slot)
|
|
not v.doppelgangerReady(now)
|
|
|
|
v.updateValidator(makeValidatorAndIndex(ValidatorIndex(5), now.epoch()))
|
|
|
|
check: # No check done yet
|
|
not v.triggersDoppelganger(GENESIS_EPOCH)
|
|
not v.triggersDoppelganger(now.epoch())
|
|
|
|
not v.doppelgangerReady(GENESIS_EPOCH.start_slot)
|
|
v.doppelgangerReady(now)
|
|
|
|
v.doppelgangerChecked(GENESIS_EPOCH)
|
|
|
|
check:
|
|
v.triggersDoppelganger(GENESIS_EPOCH)
|
|
not v.triggersDoppelganger(now.epoch())
|
|
|
|
not v.doppelgangerReady(GENESIS_EPOCH.start_slot)
|
|
v.doppelgangerReady(now)
|
|
|
|
asyncTest "Dynamic validator set: queryValidatorsSource() test":
|
|
proc makeJson(keys: openArray[ValidatorPubKey]): string =
|
|
var res = "["
|
|
res.add(keys.mapIt("\"0x" & it.toHex() & "\"").join(","))
|
|
res.add("]")
|
|
res
|
|
|
|
var testStage = 0
|
|
proc testValidate(pattern: string, value: string): int = 0
|
|
var router = RestRouter.init(testValidate)
|
|
router.api(MethodGet, "/api/v1/eth2/publicKeys") do () -> RestApiResponse:
|
|
case testStage
|
|
of 0:
|
|
let data = [createPubKey(1), createPubKey(2)].makeJson()
|
|
return RestApiResponse.response(data, Http200, "application/json")
|
|
of 1:
|
|
let data = [createPubKey(1)].makeJson()
|
|
return RestApiResponse.response(data, Http200, "application/json")
|
|
of 2:
|
|
var data: seq[ValidatorPubKey]
|
|
return RestApiResponse.response(data.makeJson(), Http200,
|
|
"application/json")
|
|
else:
|
|
return RestApiResponse.response("INCORRECT TEST STAGE", Http400,
|
|
"text/plain")
|
|
|
|
var sres = RestServerRef.new(router, initTAddress("127.0.0.1:0"))
|
|
let
|
|
server = sres.get()
|
|
serverAddress = server.server.instance.localAddress()
|
|
config =
|
|
try:
|
|
BeaconNodeConf.load(cmdLine =
|
|
mapIt(["--web3-signer-url=http://" & $serverAddress], it))
|
|
except Exception as exc:
|
|
raiseAssert exc.msg
|
|
web3SignerUrls = config.web3SignerUrls
|
|
|
|
server.start()
|
|
try:
|
|
block:
|
|
testStage = 0
|
|
let res = await queryValidatorsSource(web3SignerUrls[0])
|
|
check:
|
|
res.isOk()
|
|
checkResponse(
|
|
res.get(),
|
|
[
|
|
createDynamic(remoteSignerUrl.url, createPubKey(1)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(2))
|
|
])
|
|
block:
|
|
testStage = 1
|
|
let res = await queryValidatorsSource(web3SignerUrls[0])
|
|
check:
|
|
res.isOk()
|
|
checkResponse(res.get(), [createDynamic(remoteSignerUrl.url, createPubKey(1))])
|
|
block:
|
|
testStage = 2
|
|
let res = await queryValidatorsSource(web3SignerUrls[0])
|
|
check:
|
|
res.isOk()
|
|
len(res.get()) == 0
|
|
block:
|
|
testStage = 3
|
|
let res = await queryValidatorsSource(web3SignerUrls[0])
|
|
check:
|
|
res.isErr()
|
|
finally:
|
|
await server.closeWait()
|
|
|
|
test "Dynamic validator set: updateDynamicValidators() test":
|
|
let
|
|
fee = default(Eth1Address)
|
|
gas = 30000000'u64
|
|
|
|
proc checkPool(pool: ValidatorPool, expected: openArray[KeystoreData]) =
|
|
let
|
|
attachedKeystores =
|
|
block:
|
|
var res: seq[KeystoreData]
|
|
for validator in pool:
|
|
res.add(validator.data)
|
|
sorted(res, cmp)
|
|
sortedExpected = sorted(expected, cmp)
|
|
|
|
for index, value in attachedKeystores:
|
|
check cmp(value, sortedExpected[index]) == 0
|
|
|
|
var pool = (ref ValidatorPool)()
|
|
discard pool[].addValidator(createLocal(createPubKey(1)), fee, gas)
|
|
discard pool[].addValidator(createRemote(createPubKey(2)), fee, gas)
|
|
discard pool[].addValidator(createDynamic(remoteSignerUrl.url, createPubKey(3)), fee, gas)
|
|
|
|
proc addValidator(data: KeystoreData) {.gcsafe.} =
|
|
discard pool[].addValidator(data, fee, gas)
|
|
|
|
# Adding new dynamic keystores.
|
|
block:
|
|
let
|
|
expected = [
|
|
createLocal(createPubKey(1)),
|
|
createRemote(createPubKey(2)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(3)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(4)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(5))
|
|
]
|
|
keystores = [
|
|
createDynamic(remoteSignerUrl.url, createPubKey(3)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(4)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(5))
|
|
]
|
|
pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator)
|
|
pool[].checkPool(expected)
|
|
|
|
# Removing dynamic keystores.
|
|
block:
|
|
let
|
|
expected = [
|
|
createLocal(createPubKey(1)),
|
|
createRemote(createPubKey(2)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(3))
|
|
]
|
|
keystores = [
|
|
createDynamic(remoteSignerUrl.url, createPubKey(3)),
|
|
]
|
|
pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator)
|
|
pool[].checkPool(expected)
|
|
|
|
# Adding and removing keystores at same time.
|
|
block:
|
|
let
|
|
expected = [
|
|
createLocal(createPubKey(1)),
|
|
createRemote(createPubKey(2)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(4)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(5))
|
|
]
|
|
keystores = [
|
|
createDynamic(remoteSignerUrl.url, createPubKey(4)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(5))
|
|
]
|
|
pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator)
|
|
pool[].checkPool(expected)
|
|
|
|
# Adding dynamic keystores with keys which are static.
|
|
block:
|
|
let
|
|
expected = [
|
|
createLocal(createPubKey(1)),
|
|
createRemote(createPubKey(2)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(3))
|
|
]
|
|
keystores = [
|
|
createDynamic(remoteSignerUrl.url, createPubKey(1)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(2)),
|
|
createDynamic(remoteSignerUrl.url, createPubKey(3)),
|
|
]
|
|
pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator)
|
|
pool[].checkPool(expected)
|
|
|
|
# Empty response
|
|
block:
|
|
let
|
|
expected = [
|
|
createLocal(createPubKey(1)),
|
|
createRemote(createPubKey(2))
|
|
]
|
|
var keystores: seq[KeystoreData]
|
|
pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator)
|
|
pool[].checkPool(expected)
|