batch-verify BLS to execution change messages (#4637)
This commit is contained in:
parent
3d3d17aad9
commit
a382498cfe
|
@ -260,7 +260,7 @@ proc addHeadBlock*(
|
||||||
var sigs: seq[SignatureSet]
|
var sigs: seq[SignatureSet]
|
||||||
if (let e = sigs.collectSignatureSets(
|
if (let e = sigs.collectSignatureSets(
|
||||||
signedBlock, dag.db.immutableValidators,
|
signedBlock, dag.db.immutableValidators,
|
||||||
dag.clearanceState, cache); e.isErr()):
|
dag.clearanceState, dag.cfg.genesisFork(), cache); e.isErr()):
|
||||||
# A PublicKey or Signature isn't on the BLS12-381 curve
|
# A PublicKey or Signature isn't on the BLS12-381 curve
|
||||||
info "Unable to load signature sets",
|
info "Unable to load signature sets",
|
||||||
err = e.error()
|
err = e.error()
|
||||||
|
|
|
@ -15,6 +15,8 @@ import
|
||||||
../spec/[helpers, state_transition_block],
|
../spec/[helpers, state_transition_block],
|
||||||
"."/[attestation_pool, blockchain_dag]
|
"."/[attestation_pool, blockchain_dag]
|
||||||
|
|
||||||
|
from ../spec/beaconstate import check_bls_to_execution_change
|
||||||
|
|
||||||
export base, deques, blockchain_dag
|
export base, deques, blockchain_dag
|
||||||
|
|
||||||
const
|
const
|
||||||
|
@ -184,7 +186,7 @@ proc validateValidatorChangeMessage(
|
||||||
proc validateValidatorChangeMessage(
|
proc validateValidatorChangeMessage(
|
||||||
cfg: RuntimeConfig, state: ForkyBeaconState,
|
cfg: RuntimeConfig, state: ForkyBeaconState,
|
||||||
msg: SignedBLSToExecutionChange): bool =
|
msg: SignedBLSToExecutionChange): bool =
|
||||||
check_bls_to_execution_change(cfg, state, msg).isOk
|
check_bls_to_execution_change(cfg.genesisFork, state, msg, {}).isOk
|
||||||
|
|
||||||
proc getValidatorChangeMessagesForBlock(
|
proc getValidatorChangeMessagesForBlock(
|
||||||
subpool: var Deque, cfg: RuntimeConfig, state: ForkyBeaconState,
|
subpool: var Deque, cfg: RuntimeConfig, state: ForkyBeaconState,
|
||||||
|
|
|
@ -456,3 +456,39 @@ proc scheduleContributionChecks*(
|
||||||
contribution.beacon_block_root, contributionKey, contributionSig)
|
contribution.beacon_block_root, contributionKey, contributionSig)
|
||||||
|
|
||||||
ok((aggregatorFut, proofFut, contributionFut, contributionSig))
|
ok((aggregatorFut, proofFut, contributionFut, contributionSig))
|
||||||
|
|
||||||
|
proc scheduleBlsToExecutionChangeCheck*(
|
||||||
|
batchCrypto: ref BatchCrypto,
|
||||||
|
genesisFork: Fork, genesis_validators_root: Eth2Digest,
|
||||||
|
signedBLSToExecutionChange: SignedBLSToExecutionChange): Result[tuple[
|
||||||
|
blsToExecutionFut: Future[BatchResult],
|
||||||
|
sig: CookedSig], cstring] =
|
||||||
|
## Schedule crypto verification of all signatures in a
|
||||||
|
## SignedBLSToExecutionChange message
|
||||||
|
##
|
||||||
|
## The buffer is processed:
|
||||||
|
## - when eager processing is enabled and the batch is full
|
||||||
|
## - otherwise after 10ms (BatchAttAccumTime)
|
||||||
|
##
|
||||||
|
## This returns an error if crypto sanity checks failed
|
||||||
|
## and a future with the deferred check otherwise.
|
||||||
|
|
||||||
|
# Must be genesis fork
|
||||||
|
doAssert genesis_fork.previous_version == genesis_fork.current_version
|
||||||
|
|
||||||
|
let
|
||||||
|
# Only called when matching already-known withdrawal credentials, so it's
|
||||||
|
# resistant to allowing loadWithCache DoSing
|
||||||
|
validatorChangePubkey =
|
||||||
|
signedBLSToExecutionChange.message.from_bls_pubkey.loadWithCache.valueOr:
|
||||||
|
return err("scheduleBlsToExecutionChangeCheck: cannot load BLS to withdrawals pubkey")
|
||||||
|
|
||||||
|
validatorChangeSig = signedBLSToExecutionChange.signature.load().valueOr:
|
||||||
|
return err("scheduleBlsToExecutionChangeCheck: invalid validator change signature")
|
||||||
|
validatorChangeFut = batchCrypto.withBatch("scheduleContributionAndProofChecks.contribution"):
|
||||||
|
bls_to_execution_change_signature_set(
|
||||||
|
genesis_fork, genesis_validators_root,
|
||||||
|
signedBLSToExecutionChange.message,
|
||||||
|
validatorChangePubkey, validatorChangeSig)
|
||||||
|
|
||||||
|
ok((validatorChangeFut, validatorChangeSig))
|
||||||
|
|
|
@ -403,15 +403,17 @@ proc processSignedAggregateAndProof*(
|
||||||
err(v.error())
|
err(v.error())
|
||||||
|
|
||||||
proc processBlsToExecutionChange*(
|
proc processBlsToExecutionChange*(
|
||||||
self: var Eth2Processor, src: MsgSource,
|
self: ref Eth2Processor, src: MsgSource,
|
||||||
blsToExecutionChange: SignedBLSToExecutionChange): ValidationRes =
|
blsToExecutionChange: SignedBLSToExecutionChange):
|
||||||
|
Future[ValidationRes] {.async.} =
|
||||||
logScope:
|
logScope:
|
||||||
blsToExecutionChange = shortLog(blsToExecutionChange)
|
blsToExecutionChange = shortLog(blsToExecutionChange)
|
||||||
|
|
||||||
debug "BLS to execution change received"
|
debug "BLS to execution change received"
|
||||||
|
|
||||||
let v = self.validatorChangePool[].validateBlsToExecutionChange(
|
let v = await self.validatorChangePool[].validateBlsToExecutionChange(
|
||||||
blsToExecutionChange, self.getCurrentBeaconTime().slotOrZero.epoch)
|
self.batchCrypto, blsToExecutionChange,
|
||||||
|
self.getCurrentBeaconTime().slotOrZero.epoch)
|
||||||
|
|
||||||
if v.isOk():
|
if v.isOk():
|
||||||
trace "BLS to execution change validated"
|
trace "BLS to execution change validated"
|
||||||
|
@ -422,7 +424,7 @@ proc processBlsToExecutionChange*(
|
||||||
debug "Dropping BLS to execution change", validationError = v.error
|
debug "Dropping BLS to execution change", validationError = v.error
|
||||||
beacon_attester_slashings_dropped.inc(1, [$v.error[0]])
|
beacon_attester_slashings_dropped.inc(1, [$v.error[0]])
|
||||||
|
|
||||||
v
|
return v
|
||||||
|
|
||||||
proc processAttesterSlashing*(
|
proc processAttesterSlashing*(
|
||||||
self: var Eth2Processor, src: MsgSource,
|
self: var Eth2Processor, src: MsgSource,
|
||||||
|
|
|
@ -814,8 +814,9 @@ proc validateAggregate*(
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/p2p-interface.md#bls_to_execution_change
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/p2p-interface.md#bls_to_execution_change
|
||||||
proc validateBlsToExecutionChange*(
|
proc validateBlsToExecutionChange*(
|
||||||
pool: ValidatorChangePool, signed_address_change: SignedBLSToExecutionChange,
|
pool: ValidatorChangePool, batchCrypto: ref BatchCrypto,
|
||||||
wallEpoch: Epoch): Result[void, ValidationError] =
|
signed_address_change: SignedBLSToExecutionChange,
|
||||||
|
wallEpoch: Epoch): Future[Result[void, ValidationError]] {.async.} =
|
||||||
# [IGNORE] `current_epoch >= CAPELLA_FORK_EPOCH`, where `current_epoch` is
|
# [IGNORE] `current_epoch >= CAPELLA_FORK_EPOCH`, where `current_epoch` is
|
||||||
# defined by the current wall-clock time.
|
# defined by the current wall-clock time.
|
||||||
if not (wallEpoch >= pool.dag.cfg.CAPELLA_FORK_EPOCH):
|
if not (wallEpoch >= pool.dag.cfg.CAPELLA_FORK_EPOCH):
|
||||||
|
@ -834,11 +835,28 @@ proc validateBlsToExecutionChange*(
|
||||||
return errIgnore("validateBlsToExecutionChange: can't validate against pre-Capella state")
|
return errIgnore("validateBlsToExecutionChange: can't validate against pre-Capella state")
|
||||||
else:
|
else:
|
||||||
let res = check_bls_to_execution_change(
|
let res = check_bls_to_execution_change(
|
||||||
pool.dag.cfg, forkyState.data, signed_address_change)
|
pool.dag.cfg.genesisFork, forkyState.data, signed_address_change,
|
||||||
|
{skipBlsValidation})
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
return errReject(res.error)
|
return errReject(res.error)
|
||||||
|
|
||||||
ok()
|
# BLS to execution change signatures are batch-verified
|
||||||
|
let deferredCrypto = batchCrypto.scheduleBlsToExecutionChangeCheck(
|
||||||
|
pool.dag.cfg.genesisFork, pool.dag.genesis_validators_root,
|
||||||
|
signed_address_change)
|
||||||
|
if deferredCrypto.isErr():
|
||||||
|
return checkedReject(deferredCrypto.error)
|
||||||
|
|
||||||
|
let (cryptoFut, sig) = deferredCrypto.get()
|
||||||
|
case await cryptoFut
|
||||||
|
of BatchResult.Invalid:
|
||||||
|
return checkedReject("validateBlsToExecutionChange: invalid signature")
|
||||||
|
of BatchResult.Timeout:
|
||||||
|
return errIgnore("validateBlsToExecutionChange: timeout checking signature")
|
||||||
|
of BatchResult.Valid:
|
||||||
|
discard # keep going only in this case
|
||||||
|
|
||||||
|
return ok()
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/p2p-interface.md#attester_slashing
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/p2p-interface.md#attester_slashing
|
||||||
proc validateAttesterSlashing*(
|
proc validateAttesterSlashing*(
|
||||||
|
|
|
@ -1543,11 +1543,13 @@ proc installMessageValidators(node: BeaconNode) =
|
||||||
installSyncCommitteeeValidators(forkDigests.eip4844)
|
installSyncCommitteeeValidators(forkDigests.eip4844)
|
||||||
|
|
||||||
template installBlsToExecutionChangeValidators(digest: auto) =
|
template installBlsToExecutionChangeValidators(digest: auto) =
|
||||||
node.network.addValidator(
|
node.network.addAsyncValidator(
|
||||||
getBlsToExecutionChangeTopic(digest),
|
getBlsToExecutionChangeTopic(digest),
|
||||||
proc(msg: SignedBLSToExecutionChange): ValidationResult =
|
proc(msg: SignedBLSToExecutionChange):
|
||||||
|
Future[ValidationResult] {.async.} =
|
||||||
return toValidationResult(
|
return toValidationResult(
|
||||||
node.processor[].processBlsToExecutionChange(MsgSource.gossip, msg)))
|
await node.processor.processBlsToExecutionChange(
|
||||||
|
MsgSource.gossip, msg)))
|
||||||
|
|
||||||
installBlsToExecutionChangeValidators(forkDigests.capella)
|
installBlsToExecutionChangeValidators(forkDigests.capella)
|
||||||
if node.dag.cfg.DENEB_FORK_EPOCH != FAR_FUTURE_EPOCH:
|
if node.dag.cfg.DENEB_FORK_EPOCH != FAR_FUTURE_EPOCH:
|
||||||
|
|
|
@ -696,6 +696,35 @@ proc check_attestation*(
|
||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/beacon-chain.md#new-process_bls_to_execution_change
|
||||||
|
proc check_bls_to_execution_change*(
|
||||||
|
genesisFork: Fork, state: capella.BeaconState | eip4844.BeaconState,
|
||||||
|
signed_address_change: SignedBLSToExecutionChange, flags: UpdateFlags):
|
||||||
|
Result[void, cstring] =
|
||||||
|
let address_change = signed_address_change.message
|
||||||
|
|
||||||
|
if not (address_change.validator_index < state.validators.lenu64):
|
||||||
|
return err("process_bls_to_execution_change: invalid validator index")
|
||||||
|
|
||||||
|
var withdrawal_credentials =
|
||||||
|
state.validators.item(address_change.validator_index).withdrawal_credentials
|
||||||
|
|
||||||
|
if not (withdrawal_credentials.data[0] == BLS_WITHDRAWAL_PREFIX):
|
||||||
|
return err("process_bls_to_execution_change: invalid withdrawal prefix")
|
||||||
|
|
||||||
|
if not (withdrawal_credentials.data.toOpenArray(1, 31) ==
|
||||||
|
eth2digest(address_change.from_bls_pubkey.blob).data.toOpenArray(1, 31)):
|
||||||
|
return err("process_bls_to_execution_change: invalid withdrawal credentials")
|
||||||
|
|
||||||
|
doAssert flags + {skipBlsValidation} == {skipBlsValidation}
|
||||||
|
if skipBlsValidation notin flags and
|
||||||
|
not verify_bls_to_execution_change_signature(
|
||||||
|
genesisFork, state.genesis_validators_root, signed_address_change,
|
||||||
|
address_change.from_bls_pubkey, signed_address_change.signature):
|
||||||
|
return err("process_bls_to_execution_change: invalid signature")
|
||||||
|
|
||||||
|
ok()
|
||||||
|
|
||||||
func get_proposer_reward*(state: ForkyBeaconState,
|
func get_proposer_reward*(state: ForkyBeaconState,
|
||||||
attestation: SomeAttestation,
|
attestation: SomeAttestation,
|
||||||
base_reward_per_increment: Gwei,
|
base_reward_per_increment: Gwei,
|
||||||
|
|
|
@ -368,12 +368,13 @@ proc verify_builder_signature*(
|
||||||
blsVerify(pubkey, signing_root.data, signature)
|
blsVerify(pubkey, signing_root.data, signature)
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/beacon-chain.md#new-process_bls_to_execution_change
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/beacon-chain.md#new-process_bls_to_execution_change
|
||||||
func compute_bls_to_execution_change_signing_root(
|
func compute_bls_to_execution_change_signing_root*(
|
||||||
genesisFork: Fork, genesis_validators_root: Eth2Digest,
|
genesisFork: Fork, genesis_validators_root: Eth2Digest,
|
||||||
msg: BLSToExecutionChange): Eth2Digest =
|
msg: BLSToExecutionChange): Eth2Digest =
|
||||||
# So the epoch doesn't matter when calling get_domain
|
# So the epoch doesn't matter when calling get_domain
|
||||||
doAssert genesisFork.previous_version == genesisFork.current_version
|
doAssert genesisFork.previous_version == genesisFork.current_version
|
||||||
|
|
||||||
|
# Fork-agnostic domain since address changes are valid across forks
|
||||||
let domain = get_domain(
|
let domain = get_domain(
|
||||||
genesisFork, DOMAIN_BLS_TO_EXECUTION_CHANGE, GENESIS_EPOCH,
|
genesisFork, DOMAIN_BLS_TO_EXECUTION_CHANGE, GENESIS_EPOCH,
|
||||||
genesis_validators_root)
|
genesis_validators_root)
|
||||||
|
|
|
@ -202,11 +202,21 @@ func contribution_and_proof_signature_set*(
|
||||||
|
|
||||||
SignatureSet.init(pubkey, signing_root, signature)
|
SignatureSet.init(pubkey, signing_root, signature)
|
||||||
|
|
||||||
func collectSignatureSets*(
|
func bls_to_execution_change_signature_set*(
|
||||||
|
genesisFork: Fork, genesis_validators_root: Eth2Digest,
|
||||||
|
msg: BLSToExecutionChange,
|
||||||
|
pubkey: CookedPubKey, signature: CookedSig): SignatureSet =
|
||||||
|
let signing_root = compute_bls_to_execution_change_signing_root(
|
||||||
|
genesisFork, genesis_validators_root, msg)
|
||||||
|
|
||||||
|
SignatureSet.init(pubkey, signing_root, signature)
|
||||||
|
|
||||||
|
proc collectSignatureSets*(
|
||||||
sigs: var seq[SignatureSet],
|
sigs: var seq[SignatureSet],
|
||||||
signed_block: ForkySignedBeaconBlock,
|
signed_block: ForkySignedBeaconBlock,
|
||||||
validatorKeys: auto,
|
validatorKeys: auto,
|
||||||
state: ForkedHashedBeaconState,
|
state: ForkedHashedBeaconState,
|
||||||
|
genesis_fork: Fork,
|
||||||
cache: var StateCache): Result[void, cstring] =
|
cache: var StateCache): Result[void, cstring] =
|
||||||
## Collect all signature verifications that process_block would normally do
|
## Collect all signature verifications that process_block would normally do
|
||||||
## except deposits, in one go.
|
## except deposits, in one go.
|
||||||
|
@ -218,7 +228,8 @@ func collectSignatureSets*(
|
||||||
## - Attester slashings
|
## - Attester slashings
|
||||||
## - Attestations
|
## - Attestations
|
||||||
## - VoluntaryExits
|
## - VoluntaryExits
|
||||||
## - SyncCommittee (altair+)
|
## - SyncCommittee (Altair+)
|
||||||
|
## - BLS to execution changes (Capella+)
|
||||||
##
|
##
|
||||||
## We do not include deposits as they can be invalid while still leaving the
|
## We do not include deposits as they can be invalid while still leaving the
|
||||||
## block valid
|
## block valid
|
||||||
|
@ -235,6 +246,8 @@ func collectSignatureSets*(
|
||||||
return err("collectSignatureSets: invalid proposer index")
|
return err("collectSignatureSets: invalid proposer index")
|
||||||
epoch = signed_block.message.slot.epoch()
|
epoch = signed_block.message.slot.epoch()
|
||||||
|
|
||||||
|
doAssert genesis_fork.previous_version == genesis_fork.current_version
|
||||||
|
|
||||||
# 1. Block proposer
|
# 1. Block proposer
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
sigs.add block_signature_set(
|
sigs.add block_signature_set(
|
||||||
|
@ -395,6 +408,27 @@ func collectSignatureSets*(
|
||||||
signed_block.message.body.sync_aggregate.sync_committee_signature.load().valueOr do:
|
signed_block.message.body.sync_aggregate.sync_committee_signature.load().valueOr do:
|
||||||
return err("collectSignatureSets: cannot load signature"))
|
return err("collectSignatureSets: cannot load signature"))
|
||||||
|
|
||||||
|
block:
|
||||||
|
# 8. BLS to execution changes
|
||||||
|
when typeof(signed_block).toFork() >= ConsensusFork.Capella:
|
||||||
|
withState(state):
|
||||||
|
when stateFork >= ConsensusFork.Capella:
|
||||||
|
for bls_change in signed_block.message.body.bls_to_execution_changes:
|
||||||
|
let sig = bls_change.signature.load.valueOr:
|
||||||
|
return err("collectSignatureSets: cannot load BLS to execution change signature")
|
||||||
|
|
||||||
|
# Otherwise, expensive loadWithCache can be spammed with irrelevant pubkeys
|
||||||
|
? check_bls_to_execution_change(
|
||||||
|
genesis_fork, forkyState.data, bls_change, {skipBlsValidation})
|
||||||
|
|
||||||
|
let validator_pubkey =
|
||||||
|
bls_change.message.from_bls_pubkey.loadWithCache.valueOr:
|
||||||
|
return err("collectSignatureSets: cannot load BLS to execution change pubkey")
|
||||||
|
|
||||||
|
sigs.add bls_to_execution_change_signature_set(
|
||||||
|
genesis_fork, genesis_validators_root, bls_change.message,
|
||||||
|
validator_pubkey, sig)
|
||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
proc batchVerify*(verifier: var BatchVerifier, sigs: openArray[SignatureSet]): bool =
|
proc batchVerify*(verifier: var BatchVerifier, sigs: openArray[SignatureSet]): bool =
|
||||||
|
|
|
@ -387,36 +387,11 @@ proc process_voluntary_exit*(
|
||||||
? initiate_validator_exit(cfg, state, exited_validator, cache)
|
? initiate_validator_exit(cfg, state, exited_validator, cache)
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/capella/beacon-chain.md#new-process_bls_to_execution_change
|
|
||||||
proc check_bls_to_execution_change*(
|
|
||||||
cfg: RuntimeConfig, state: capella.BeaconState | eip4844.BeaconState,
|
|
||||||
signed_address_change: SignedBLSToExecutionChange): Result[void, cstring] =
|
|
||||||
let address_change = signed_address_change.message
|
|
||||||
|
|
||||||
if not (address_change.validator_index < state.validators.lenu64):
|
|
||||||
return err("process_bls_to_execution_change: invalid validator index")
|
|
||||||
|
|
||||||
var withdrawal_credentials =
|
|
||||||
state.validators.item(address_change.validator_index).withdrawal_credentials
|
|
||||||
|
|
||||||
if not (withdrawal_credentials.data[0] == BLS_WITHDRAWAL_PREFIX):
|
|
||||||
return err("process_bls_to_execution_change: invalid withdrawal prefix")
|
|
||||||
|
|
||||||
if not (withdrawal_credentials.data.toOpenArray(1, 31) ==
|
|
||||||
eth2digest(address_change.from_bls_pubkey.blob).data.toOpenArray(1, 31)):
|
|
||||||
return err("process_bls_to_execution_change: invalid withdrawal credentials")
|
|
||||||
|
|
||||||
if not verify_bls_to_execution_change_signature(
|
|
||||||
cfg.genesisFork, state.genesis_validators_root, signed_address_change,
|
|
||||||
address_change.from_bls_pubkey, signed_address_change.signature):
|
|
||||||
return err("process_bls_to_execution_change: invalid signature")
|
|
||||||
|
|
||||||
ok()
|
|
||||||
|
|
||||||
proc process_bls_to_execution_change*(
|
proc process_bls_to_execution_change*(
|
||||||
cfg: RuntimeConfig, state: var (capella.BeaconState | eip4844.BeaconState),
|
cfg: RuntimeConfig, state: var (capella.BeaconState | eip4844.BeaconState),
|
||||||
signed_address_change: SignedBLSToExecutionChange): Result[void, cstring] =
|
signed_address_change: SignedBLSToExecutionChange): Result[void, cstring] =
|
||||||
? check_bls_to_execution_change(cfg, state, signed_address_change)
|
? check_bls_to_execution_change(
|
||||||
|
cfg.genesisFork, state, signed_address_change, {})
|
||||||
let address_change = signed_address_change.message
|
let address_change = signed_address_change.message
|
||||||
var withdrawal_credentials =
|
var withdrawal_credentials =
|
||||||
state.validators.item(address_change.validator_index).withdrawal_credentials
|
state.validators.item(address_change.validator_index).withdrawal_credentials
|
||||||
|
|
|
@ -459,7 +459,7 @@ proc routeBlsToExecutionChange*(
|
||||||
bls_to_execution_change: SignedBLSToExecutionChange):
|
bls_to_execution_change: SignedBLSToExecutionChange):
|
||||||
Future[SendResult] {.async.} =
|
Future[SendResult] {.async.} =
|
||||||
block:
|
block:
|
||||||
let res = router[].processor[].processBlsToExecutionChange(
|
let res = await router.processor.processBlsToExecutionChange(
|
||||||
MsgSource.api, bls_to_execution_change)
|
MsgSource.api, bls_to_execution_change)
|
||||||
if not res.isGoodForSending:
|
if not res.isGoodForSending:
|
||||||
warn "BLS to execution change request failed validation",
|
warn "BLS to execution change request failed validation",
|
||||||
|
|
Loading…
Reference in New Issue