Skip to content

Commit

Permalink
Use withdrawal credentials as default fee recipient (#4968)
Browse files Browse the repository at this point in the history
  • Loading branch information
tersec authored May 17, 2023
1 parent 40e8993 commit 74511f6
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 45 deletions.
6 changes: 3 additions & 3 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ OK: 5/5 Fail: 0/5 Skip: 0/5
OK: 3/3 Fail: 0/3 Skip: 0/3
## Fee recipient management [Beacon Node] [Preset: mainnet]
```diff
+ Configuring the fee recpient [Beacon Node] [Preset: mainnet] OK
+ Configuring the fee recipient [Beacon Node] [Preset: mainnet] OK
+ Invalid Authorization Header [Beacon Node] [Preset: mainnet] OK
+ Invalid Authorization Token [Beacon Node] [Preset: mainnet] OK
+ Missing Authorization header [Beacon Node] [Preset: mainnet] OK
+ Obtaining the fee recpient of a missing validator returns 404 [Beacon Node] [Preset: mainn OK
+ Obtaining the fee recpient of an unconfigured validator returns the suggested default [Bea OK
+ Obtaining the fee recipient of a missing validator returns 404 [Beacon Node] [Preset: main OK
+ Obtaining the fee recipient of an unconfigured validator returns the suggested default [Be OK
+ Setting the fee recipient on a missing validator creates a record for it [Beacon Node] [Pr OK
```
OK: 7/7 Fail: 0/7 Skip: 0/7
Expand Down
6 changes: 3 additions & 3 deletions beacon_chain/conf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1308,12 +1308,12 @@ proc loadEth2Network*(
template loadEth2Network*(config: BeaconNodeConf): Eth2NetworkMetadata =
loadEth2Network(config.eth2Network)

func defaultFeeRecipient*(conf: AnyConf): Eth1Address =
func defaultFeeRecipient*(conf: AnyConf): Opt[Eth1Address] =
if conf.suggestedFeeRecipient.isSome:
conf.suggestedFeeRecipient.get
Opt.some conf.suggestedFeeRecipient.get
else:
# https://github.com/nim-lang/Nim/issues/19802
(static(default(Eth1Address)))
(static(Opt.none Eth1Address))

proc loadJwtSecret*(
rng: var HmacDrbgContext,
Expand Down
32 changes: 26 additions & 6 deletions beacon_chain/consensus_object_pools/consensus_manager.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import
../el/el_manager,
../beacon_clock

from ../spec/beaconstate import get_expected_withdrawals
from ../spec/beaconstate import
get_expected_withdrawals, has_eth1_withdrawal_credential
from ../spec/datatypes/capella import Withdrawal
from ../spec/eth2_apis/dynamic_fee_recipients import
DynamicFeeRecipientsStore, getDynamicFeeRecipient
from ../validators/keystore_management import
KeymanagerHost, getSuggestedFeeRecipient, getSuggestedGasLimit
KeymanagerHost, getPerValidatorDefaultFeeRecipient, getSuggestedFeeRecipient,
getSuggestedGasLimit
from ../validators/action_tracker import ActionTracker, getNextProposalSlot

type
Expand Down Expand Up @@ -48,7 +50,7 @@ type
# ----------------------------------------------------------------
dynamicFeeRecipientsStore: ref DynamicFeeRecipientsStore
validatorsDir: string
defaultFeeRecipient: Eth1Address
defaultFeeRecipient: Opt[Eth1Address]
defaultGasLimit: uint64

# Tracking last proposal forkchoiceUpdated payload information
Expand All @@ -66,7 +68,7 @@ func new*(T: type ConsensusManager,
actionTracker: ActionTracker,
dynamicFeeRecipientsStore: ref DynamicFeeRecipientsStore,
validatorsDir: string,
defaultFeeRecipient: Eth1Address,
defaultFeeRecipient: Opt[Eth1Address],
defaultGasLimit: uint64
): ref ConsensusManager =
(ref ConsensusManager)(
Expand Down Expand Up @@ -296,10 +298,28 @@ proc getFeeRecipient*(
Opt.none(Eth1Address)

dynFeeRecipient.valueOr:
let
withdrawalAddress =
if validatorIdx.isSome:
withState(self.dag.headState):
if validatorIdx.get < forkyState.data.validators.lenu64:
let validator = forkyState.data.validators.item(validatorIdx.get)
if has_eth1_withdrawal_credential(validator):
var address: distinctBase(Eth1Address)
address[0..^1] = validator.withdrawal_credentials.data[12..^1]
Opt.some Eth1Address address
else:
Opt.none Eth1Address
else:
Opt.none Eth1Address
else:
Opt.none Eth1Address
defaultFeeRecipient = getPerValidatorDefaultFeeRecipient(
self.defaultFeeRecipient, withdrawalAddress)
self.validatorsDir.getSuggestedFeeRecipient(
pubkey, self.defaultFeeRecipient).valueOr:
pubkey, defaultFeeRecipient).valueOr:
# Ignore errors and use default - errors are logged in gsfr
self.defaultFeeRecipient
defaultFeeRecipient

proc getGasLimit*(
self: ConsensusManager, pubkey: ValidatorPubKey): uint64 =
Expand Down
14 changes: 10 additions & 4 deletions beacon_chain/rpc/rest_key_management_api.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2021-2022 Status Research & Development GmbH
# Copyright (c) 2021-2023 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).
Expand Down Expand Up @@ -126,7 +126,8 @@ proc handleAddRemoteValidatorReq(host: KeymanagerHost,
keystore: RemoteKeystore): RequestItemStatus =
let res = importKeystore(host.validatorPool[], host.validatorsDir, keystore)
if res.isOk:
host.addValidator(res.get())
host.addValidator(
res.get(), host.getValidatorWithdrawalAddress(keystore.pubkey))

RequestItemStatus(status: $KeystoreStatus.imported)
else:
Expand Down Expand Up @@ -193,7 +194,8 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
response.data.add(
RequestItemStatus(status: $KeystoreStatus.duplicate))
else:
host.addValidator(res.get())
host.addValidator(
res.get(), host.getValidatorWithdrawalAddress(res.get.pubkey))
response.data.add(
RequestItemStatus(status: $KeystoreStatus.imported))

Expand Down Expand Up @@ -333,7 +335,11 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) =
let
pubkey = pubkey.valueOr:
return keymanagerApiError(Http400, InvalidValidatorPublicKey)
ethaddress = host.getSuggestedFeeRecipient(pubkey)
perValidatorDefaultFeeRecipient = getPerValidatorDefaultFeeRecipient(
host.defaultFeeRecipient,
host.getValidatorWithdrawalAddress(pubkey))
ethaddress = host.getSuggestedFeeRecipient(
pubkey, perValidatorDefaultFeeRecipient)

return if ethaddress.isOk:
RestApiResponse.jsonResponse(ListFeeRecipientResponse(
Expand Down
25 changes: 20 additions & 5 deletions beacon_chain/validator_client/common.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2021-2022 Status Research & Development GmbH
# Copyright (c) 2021-2023 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).
Expand Down Expand Up @@ -693,9 +693,16 @@ proc currentSlot*(vc: ValidatorClientRef): Slot =
proc addValidator*(vc: ValidatorClientRef, keystore: KeystoreData) =
let
slot = vc.currentSlot()
withdrawalAddress =
if vc.keymanagerHost.isNil:
Opt.none Eth1Address
else:
vc.keymanagerHost[].getValidatorWithdrawalAddress(keystore.pubkey)
perValidatorDefaultFeeRecipient = getPerValidatorDefaultFeeRecipient(
vc.config.defaultFeeRecipient, withdrawalAddress)
feeRecipient = vc.config.validatorsDir.getSuggestedFeeRecipient(
keystore.pubkey, vc.config.defaultFeeRecipient).valueOr(
vc.config.defaultFeeRecipient)
keystore.pubkey, perValidatorDefaultFeeRecipient).valueOr(
perValidatorDefaultFeeRecipient)
gasLimit = vc.config.validatorsDir.getSuggestedGasLimit(
keystore.pubkey, vc.config.suggestedGasLimit).valueOr(
vc.config.suggestedGasLimit)
Expand Down Expand Up @@ -730,8 +737,16 @@ proc getFeeRecipient*(vc: ValidatorClientRef, pubkey: ValidatorPubKey,
if dynamicRecipient.isSome():
Opt.some(dynamicRecipient.get())
else:
let staticRecipient = getSuggestedFeeRecipient(
vc.config.validatorsDir, pubkey, vc.config.defaultFeeRecipient)
let
withdrawalAddress =
if vc.keymanagerHost.isNil:
Opt.none Eth1Address
else:
vc.keymanagerHost[].getValidatorWithdrawalAddress(pubkey)
perValidatorDefaultFeeRecipient = getPerValidatorDefaultFeeRecipient(
vc.config.defaultFeeRecipient, withdrawalAddress)
staticRecipient = getSuggestedFeeRecipient(
vc.config.validatorsDir, pubkey, perValidatorDefaultFeeRecipient)
if staticRecipient.isOk():
Opt.some(staticRecipient.get())
else:
Expand Down
62 changes: 51 additions & 11 deletions beacon_chain/validators/keystore_management.nim
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ type
keymanagerToken*: string
validatorsDir*: string
secretsDir*: string
defaultFeeRecipient*: Eth1Address
defaultFeeRecipient*: Opt[Eth1Address]
defaultGasLimit*: uint64
getValidatorAndIdxFn*: ValidatorPubKeyToDataFn
getBeaconTimeFn*: GetBeaconTimeFn
Expand All @@ -101,7 +101,7 @@ func init*(T: type KeymanagerHost,
keymanagerToken: string,
validatorsDir: string,
secretsDir: string,
defaultFeeRecipient: Eth1Address,
defaultFeeRecipient: Opt[Eth1Address],
defaultGasLimit: uint64,
getValidatorAndIdxFn: ValidatorPubKeyToDataFn,
getBeaconTimeFn: GetBeaconTimeFn): T =
Expand Down Expand Up @@ -744,9 +744,9 @@ func gasLimitPath(validatorsDir: string,
validatorsDir.validatorKeystoreDir(pubkey) / GasLimitFilename

proc getSuggestedFeeRecipient*(
validatorsDir: string,
pubkey: ValidatorPubKey,
defaultFeeRecipient: Eth1Address): Result[Eth1Address, ValidatorConfigFileStatus] =
validatorsDir: string, pubkey: ValidatorPubKey,
defaultFeeRecipient: Eth1Address):
Result[Eth1Address, ValidatorConfigFileStatus] =
# In this particular case, an error might be by design. If the file exists,
# but doesn't load or parse that's a more urgent matter to fix. Many people
# people might prefer, however, not to override their default suggested fee
Expand Down Expand Up @@ -1386,20 +1386,60 @@ proc setGasLimit*(host: KeymanagerHost,
io2.writeFile(validatorKeystoreDir / GasLimitFilename, $gasLimit)
.mapErr(proc(e: auto): string = "Failed to write gas limit file: " & $e)

from ".."/spec/beaconstate import has_eth1_withdrawal_credential

proc getValidatorWithdrawalAddress*(
host: KeymanagerHost, pubkey: ValidatorPubKey): Opt[Eth1Address] =
if host.getValidatorAndIdxFn.isNil:
Opt.none Eth1Address
else:
let validatorAndIndex = host.getValidatorAndIdxFn(pubkey)
if validatorAndIndex.isNone:
Opt.none Eth1Address
else:
template validator: auto = validatorAndIndex.get.validator
if has_eth1_withdrawal_credential(validator):
var address: distinctBase(Eth1Address)
address[0..^1] =
validator.withdrawal_credentials.data[12..^1]
Opt.some Eth1Address address
else:
Opt.none Eth1Address

func getPerValidatorDefaultFeeRecipient*(
defaultFeeRecipient: Opt[Eth1Address],
withdrawalAddress: Opt[Eth1Address]): Eth1Address =
defaultFeeRecipient.valueOr:
withdrawalAddress.valueOr:
(static(default(Eth1Address)))

proc getSuggestedFeeRecipient*(
host: KeymanagerHost,
pubkey: ValidatorPubKey): Result[Eth1Address, ValidatorConfigFileStatus] =
host.validatorsDir.getSuggestedFeeRecipient(pubkey, host.defaultFeeRecipient)
host: KeymanagerHost, pubkey: ValidatorPubKey,
defaultFeeRecipient: Eth1Address):
Result[Eth1Address, ValidatorConfigFileStatus] {.deprecated.} =
host.validatorsDir.getSuggestedFeeRecipient(pubkey, defaultFeeRecipient)

proc getSuggestedFeeRecipient(
host: KeyManagerHost, pubkey: ValidatorPubKey,
withdrawalAddress: Opt[Eth1Address]): Eth1Address =
# Enforce the gsfr(foo).valueOr(foo) pattern where feasible
let perValidatorDefaultFeeRecipient = getPerValidatorDefaultFeeRecipient(
host.defaultFeeRecipient, withdrawalAddress)
host.getSuggestedFeeRecipient(
pubkey, perValidatorDefaultFeeRecipient).valueOr:
perValidatorDefaultFeeRecipient

proc getSuggestedGasLimit*(
host: KeymanagerHost,
pubkey: ValidatorPubKey): Result[uint64, ValidatorConfigFileStatus] =
host.validatorsDir.getSuggestedGasLimit(pubkey, host.defaultGasLimit)

proc addValidator*(host: KeymanagerHost, keystore: KeystoreData) =
proc addValidator*(
host: KeymanagerHost, keystore: KeystoreData,
withdrawalAddress: Opt[Eth1Address]) =
let
feeRecipient = host.getSuggestedFeeRecipient(keystore.pubkey).valueOr(
host.defaultFeeRecipient)
feeRecipient = host.getSuggestedFeeRecipient(
keystore.pubkey, withdrawalAddress)
gasLimit = host.getSuggestedGasLimit(keystore.pubkey).valueOr(
host.defaultGasLimit)
v = host.validatorPool[].addValidator(keystore, feeRecipient, gasLimit)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_block_processor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ suite "Block processor" & preset():
consensusManager = ConsensusManager.new(
dag, attestationPool, quarantine, elManager, actionTracker,
newClone(DynamicFeeRecipientsStore.init()), "",
default(Eth1Address), defaultGasLimit)
Opt.some default(Eth1Address), defaultGasLimit)
state = newClone(dag.headState)
cache = StateCache()
b1 = addTestBlock(state[], cache).phase0Data
Expand Down
24 changes: 12 additions & 12 deletions tests/test_keymanager_api.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2021-2022 Status Research & Development GmbH
# Copyright (c) 2021-2023 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).
Expand Down Expand Up @@ -109,13 +109,13 @@ const
func specifiedFeeRecipient(x: int): Eth1Address =
copyMem(addr result, unsafeAddr x, sizeof x)

proc contains*(keylist: openArray[KeystoreInfo], key: ValidatorPubKey): bool =
func contains*(keylist: openArray[KeystoreInfo], key: ValidatorPubKey): bool =
for item in keylist:
if item.validating_pubkey == key:
return true
false

proc contains*(keylist: openArray[KeystoreInfo], key: string): bool =
func contains*(keylist: openArray[KeystoreInfo], key: string): bool =
let pubkey = ValidatorPubKey.fromHex(key).tryGet()
contains(keylist, pubkey)

Expand Down Expand Up @@ -363,7 +363,7 @@ proc listRemoteValidators(validatorsDir,
validatorsDir, secretsDir, err = err.msg
validators

proc `==`(a: seq[ValidatorPubKey],
func `==`(a: seq[ValidatorPubKey],
b: seq[KeystoreInfo | RemoteKeystoreInfo]): bool =
if len(a) != len(b):
return false
Expand Down Expand Up @@ -507,7 +507,7 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} =
testFlavour = " [" & keymanager.ident & "]" & preset()

suite "Serialization/deserialization" & testFlavour:
proc `==`(a, b: Kdf): bool =
func `==`(a, b: Kdf): bool =
if (a.function != b.function) or (a.message != b.message):
return false
case a.function
Expand All @@ -523,26 +523,26 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} =
(a.scryptParams.r == b.scryptParams.r) and
(seq[byte](a.scryptParams.salt) == seq[byte](b.scryptParams.salt))

proc `==`(a, b: Checksum): bool =
func `==`(a, b: Checksum): bool =
if a.function != b.function:
return false
case a.function
of ChecksumFunctionKind.sha256Checksum:
a.message.data == b.message.data

proc `==`(a, b: Cipher): bool =
func `==`(a, b: Cipher): bool =
if (a.function != b.function) or
(seq[byte](a.message) != seq[byte](b.message)):
return false
case a.function
of CipherFunctionKind.aes128CtrCipher:
seq[byte](a.params.iv) == seq[byte](b.params.iv)

proc `==`(a, b: Crypto): bool =
func `==`(a, b: Crypto): bool =
(a.kdf == b.kdf) and (a.checksum == b.checksum) and
(a.cipher == b.cipher)

proc `==`(a, b: Keystore): bool =
func `==`(a, b: Keystore): bool =
(a.crypto == b.crypto) and (a.pubkey == b.pubkey) and
(string(a.path) == string(b.path)) and
(a.description == b.description) and (a.uuid == b.uuid) and
Expand Down Expand Up @@ -1047,7 +1047,7 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} =
response.status == 403
responseJson["message"].getStr() == InvalidAuthorizationError

asyncTest "Obtaining the fee recpient of a missing validator returns 404" & testFlavour:
asyncTest "Obtaining the fee recipient of a missing validator returns 404" & testFlavour:
let
pubkey = ValidatorPubKey.fromHex(unusedPublicKeys[0]).expect("valid key")
response = await client.listFeeRecipientPlain(
Expand All @@ -1068,15 +1068,15 @@ proc runTests(keymanager: KeymanagerToTest) {.async.} =
check:
resultFromApi == feeRecipient

asyncTest "Obtaining the fee recpient of an unconfigured validator returns the suggested default" & testFlavour:
asyncTest "Obtaining the fee recipient of an unconfigured validator returns the suggested default" & testFlavour:
let
pubkey = ValidatorPubKey.fromHex(oldPublicKeys[0]).expect("valid key")
resultFromApi = await client.listFeeRecipient(pubkey, correctTokenValue)

check:
resultFromApi == defaultFeeRecipient

asyncTest "Configuring the fee recpient" & testFlavour:
asyncTest "Configuring the fee recipient" & testFlavour:
let
pubkey = ValidatorPubKey.fromHex(oldPublicKeys[1]).expect("valid key")
firstFeeRecipient = specifiedFeeRecipient(2)
Expand Down

0 comments on commit 74511f6

Please sign in to comment.