MEV block proposal (#3883)
* MEV validator registration * add nearby canary to detect new beacon chain forks * remove special MEV graffiti * web3signer support * fix trace logging * Nim 1.2 needs raises Defect * use template rather than proc in REST JSON parsing * use --payload-builder-enable and --payload-builder-url * explicitly default MEV to disabled * explicitly empty default value for payload builder URL * revert attestation pool to unstable version
This commit is contained in:
parent
17bf42316e
commit
d62d13a23c
|
@ -66,6 +66,7 @@ type
|
|||
lightClientPool*: ref LightClientPool
|
||||
exitPool*: ref ExitPool
|
||||
eth1Monitor*: Eth1Monitor
|
||||
payloadBuilderRestClient*: RestClientRef
|
||||
restServer*: RestServerRef
|
||||
keymanagerServer*: RestServerRef
|
||||
keymanagerToken*: Option[string]
|
||||
|
|
|
@ -541,6 +541,18 @@ type
|
|||
desc: "Suggested fee recipient"
|
||||
name: "suggested-fee-recipient" .}: Option[Address]
|
||||
|
||||
payloadBuilderEnable* {.
|
||||
hidden
|
||||
desc: "Enable external payload builder"
|
||||
defaultValue: false
|
||||
name: "payload-builder-enable" .}: bool
|
||||
|
||||
payloadBuilderUrl* {.
|
||||
hidden
|
||||
desc: "Payload builder URL"
|
||||
defaultValue: ""
|
||||
name: "payload-builder-url" .}: string
|
||||
|
||||
of BNStartUpCmd.createTestnet:
|
||||
testnetDepositsFile* {.
|
||||
desc: "A LaunchPad deposits file for the genesis state validators"
|
||||
|
|
|
@ -770,6 +770,17 @@ proc init*(T: type BeaconNode,
|
|||
max(Moment.init(bellatrixEpochTime, Second),
|
||||
Moment.now)
|
||||
|
||||
let payloadBuilderRestClient =
|
||||
if config.payloadBuilderEnable:
|
||||
RestClientRef.new(
|
||||
config.payloadBuilderUrl,
|
||||
httpFlags = {HttpClientFlag.NewConnectionAlways}).valueOr:
|
||||
warn "Payload builder REST client setup failed",
|
||||
payloadBuilderUrl = config.payloadBuilderUrl
|
||||
nil
|
||||
else:
|
||||
nil
|
||||
|
||||
let node = BeaconNode(
|
||||
nickname: nickname,
|
||||
graffitiBytes: if config.graffiti.isSome: config.graffiti.get
|
||||
|
@ -780,6 +791,7 @@ proc init*(T: type BeaconNode,
|
|||
config: config,
|
||||
attachedValidators: validatorPool,
|
||||
eth1Monitor: eth1Monitor,
|
||||
payloadBuilderRestClient: payloadBuilderRestClient,
|
||||
restServer: restServer,
|
||||
keymanagerServer: keymanagerServer,
|
||||
keymanagerToken: keymanagerToken,
|
||||
|
@ -801,7 +813,7 @@ proc init*(T: type BeaconNode,
|
|||
|
||||
node
|
||||
|
||||
func strictVerification(node: BeaconNode, slot: Slot) =
|
||||
func verifyFinalization(node: BeaconNode, slot: Slot) =
|
||||
# Epoch must be >= 4 to check finalization
|
||||
const SETTLING_TIME_OFFSET = 1'u64
|
||||
let epoch = slot.epoch()
|
||||
|
@ -1376,7 +1388,7 @@ proc onSlotStart(node: BeaconNode, wallTime: BeaconTime,
|
|||
wallSlot.epoch.toGaugeValue - finalizedEpoch.toGaugeValue)
|
||||
|
||||
if node.config.strictVerification:
|
||||
strictVerification(node, wallSlot)
|
||||
verifyFinalization(node, wallSlot)
|
||||
|
||||
node.consensusManager[].updateHead(wallSlot)
|
||||
|
||||
|
|
|
@ -320,6 +320,21 @@ proc installApiHandlers*(node: SigningNode) =
|
|||
validator.data.privateKey)
|
||||
signature = cooked.toValidatorSig().toHex()
|
||||
signatureResponse(Http200, signature)
|
||||
of Web3SignerRequestKind.ValidatorRegistration:
|
||||
let
|
||||
forkInfo = request.forkInfo.get()
|
||||
cooked = get_builder_signature(
|
||||
forkInfo.fork, ValidatorRegistrationV1(
|
||||
fee_recipient:
|
||||
ExecutionAddress(data: distinctBase(Eth1Address.fromHex(
|
||||
request.validatorRegistration.feeRecipient))),
|
||||
gas_limit: request.validatorRegistration.gasLimit,
|
||||
timestamp: request.validatorRegistration.timestamp,
|
||||
pubkey: request.validatorRegistration.pubkey,
|
||||
),
|
||||
validator.data.privateKey)
|
||||
signature = cooked.toValidatorSig().toHex()
|
||||
signatureResponse(Http200, signature)
|
||||
|
||||
proc validate(key: string, value: string): int =
|
||||
case key
|
||||
|
|
|
@ -819,14 +819,10 @@ template unrecognizedFieldWarning =
|
|||
fieldName, typeName = typetraits.name(typeof value)
|
||||
|
||||
## ForkedBeaconBlock
|
||||
proc readValue*[BlockType: Web3SignerForkedBeaconBlock|ForkedBeaconBlock](
|
||||
reader: var JsonReader[RestJson],
|
||||
value: var BlockType) {.raises: [IOError, SerializationError, Defect].} =
|
||||
var
|
||||
version: Option[BeaconBlockFork]
|
||||
data: Option[JsonString]
|
||||
|
||||
for fieldName in readObjectFields(reader):
|
||||
template prepareForkedBlockReading(
|
||||
reader: var JsonReader[RestJson], value: untyped,
|
||||
version: var Option[BeaconBlockFork], data: var Option[JsonString]) =
|
||||
for fieldName {.inject.} in readObjectFields(reader):
|
||||
case fieldName
|
||||
of "version":
|
||||
if version.isSome():
|
||||
|
@ -855,6 +851,15 @@ proc readValue*[BlockType: Web3SignerForkedBeaconBlock|ForkedBeaconBlock](
|
|||
if data.isNone():
|
||||
reader.raiseUnexpectedValue("Field data is missing")
|
||||
|
||||
proc readValue*[BlockType: ForkedBeaconBlock](
|
||||
reader: var JsonReader[RestJson],
|
||||
value: var BlockType) {.raises: [IOError, SerializationError, Defect].} =
|
||||
var
|
||||
version: Option[BeaconBlockFork]
|
||||
data: Option[JsonString]
|
||||
|
||||
prepareForkedBlockReading(reader, value, version, data)
|
||||
|
||||
case version.get():
|
||||
of BeaconBlockFork.Phase0:
|
||||
let res =
|
||||
|
@ -893,6 +898,58 @@ proc readValue*[BlockType: Web3SignerForkedBeaconBlock|ForkedBeaconBlock](
|
|||
reader.raiseUnexpectedValue("Incorrect bellatrix block format")
|
||||
value = ForkedBeaconBlock.init(res.get()).BlockType
|
||||
|
||||
proc readValue*[BlockType: Web3SignerForkedBeaconBlock](
|
||||
reader: var JsonReader[RestJson],
|
||||
value: var BlockType) {.raises: [IOError, SerializationError, Defect].} =
|
||||
var
|
||||
version: Option[BeaconBlockFork]
|
||||
data: Option[JsonString]
|
||||
|
||||
prepareForkedBlockReading(reader, value, version, data)
|
||||
|
||||
case version.get():
|
||||
of BeaconBlockFork.Phase0:
|
||||
let res =
|
||||
try:
|
||||
some(RestJson.decode(string(data.get()),
|
||||
phase0.BeaconBlock,
|
||||
requireAllFields = true,
|
||||
allowUnknownFields = true))
|
||||
except SerializationError:
|
||||
none[phase0.BeaconBlock]()
|
||||
if res.isNone():
|
||||
reader.raiseUnexpectedValue("Incorrect phase0 block format")
|
||||
value = Web3SignerForkedBeaconBlock(
|
||||
kind: BeaconBlockFork.Phase0,
|
||||
phase0Data: res.get())
|
||||
of BeaconBlockFork.Altair:
|
||||
let res =
|
||||
try:
|
||||
some(RestJson.decode(string(data.get()),
|
||||
altair.BeaconBlock,
|
||||
requireAllFields = true,
|
||||
allowUnknownFields = true))
|
||||
except SerializationError:
|
||||
none[altair.BeaconBlock]()
|
||||
if res.isNone():
|
||||
reader.raiseUnexpectedValue("Incorrect altair block format")
|
||||
value = Web3SignerForkedBeaconBlock(
|
||||
kind: BeaconBlockFork.Altair,
|
||||
altairData: res.get())
|
||||
of BeaconBlockFork.Bellatrix:
|
||||
let res =
|
||||
try:
|
||||
some(RestJson.decode(string(data.get()),
|
||||
BeaconBlockHeader,
|
||||
requireAllFields = true,
|
||||
allowUnknownFields = true))
|
||||
except SerializationError:
|
||||
none[BeaconBlockHeader]()
|
||||
if res.isNone():
|
||||
reader.raiseUnexpectedValue("Incorrect bellatrix block format")
|
||||
value = Web3SignerForkedBeaconBlock(
|
||||
kind: BeaconBlockFork.Bellatrix,
|
||||
bellatrixData: res.get())
|
||||
|
||||
proc writeValue*[BlockType: Web3SignerForkedBeaconBlock|ForkedBeaconBlock](
|
||||
writer: var JsonWriter[RestJson],
|
||||
|
@ -1441,6 +1498,9 @@ proc writeValue*(writer: var JsonWriter[RestJson],
|
|||
writer.writeField("fork_info", value.forkInfo.get())
|
||||
if isSome(value.signingRoot):
|
||||
writer.writeField("signingRoot", value.signingRoot)
|
||||
|
||||
# https://github.com/ConsenSys/web3signer/blob/41834a927088f1bde7a097e17d19e954d0058e54/core/src/main/resources/openapi-specs/eth2/signing/schemas.yaml#L421-L425 (branch v22.7.0)
|
||||
# It's the "beacon_block" field even when it's not a block, but a header
|
||||
writer.writeField("beacon_block", value.beaconBlock)
|
||||
of Web3SignerRequestKind.Deposit:
|
||||
writer.writeField("type", "DEPOSIT")
|
||||
|
@ -1489,6 +1549,15 @@ proc writeValue*(writer: var JsonWriter[RestJson],
|
|||
writer.writeField("signingRoot", value.signingRoot)
|
||||
writer.writeField("contribution_and_proof",
|
||||
value.syncCommitteeContributionAndProof)
|
||||
of Web3SignerRequestKind.ValidatorRegistration:
|
||||
# https://consensys.github.io/web3signer/web3signer-eth2.html#operation/ETH2_SIGN
|
||||
doAssert(value.forkInfo.isSome(),
|
||||
"forkInfo should be set for this type of request")
|
||||
writer.writeField("type", "VALIDATOR_REGISTRATION")
|
||||
writer.writeField("fork_info", value.forkInfo.get())
|
||||
if isSome(value.signingRoot):
|
||||
writer.writeField("signingRoot", value.signingRoot)
|
||||
writer.writeField("validator_registration", value.validatorRegistration)
|
||||
writer.endRecord()
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson],
|
||||
|
@ -1532,6 +1601,8 @@ proc readValue*(reader: var JsonReader[RestJson],
|
|||
Web3SignerRequestKind.SyncCommitteeSelectionProof
|
||||
of "SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF":
|
||||
Web3SignerRequestKind.SyncCommitteeContributionAndProof
|
||||
of "VALIDATOR_REGISTRATION":
|
||||
Web3SignerRequestKind.ValidatorRegistration
|
||||
else:
|
||||
reader.raiseUnexpectedValue("Unexpected `type` value")
|
||||
)
|
||||
|
@ -1625,6 +1696,8 @@ proc readValue*(reader: var JsonReader[RestJson],
|
|||
forkInfo: forkInfo, signingRoot: signingRoot, blck: data
|
||||
)
|
||||
of Web3SignerRequestKind.BlockV2:
|
||||
# https://github.com/ConsenSys/web3signer/blob/41834a927088f1bde7a097e17d19e954d0058e54/core/src/main/resources/openapi-specs/eth2/signing/schemas.yaml#L421-L425 (branch v22.7.0)
|
||||
# It's the "beacon_block" field even when it's not a block, but a header
|
||||
if dataName != "beacon_block":
|
||||
reader.raiseUnexpectedValue("Field `beacon_block` is missing")
|
||||
if forkInfo.isNone():
|
||||
|
@ -1740,6 +1813,25 @@ proc readValue*(reader: var JsonReader[RestJson],
|
|||
forkInfo: forkInfo, signingRoot: signingRoot,
|
||||
syncCommitteeContributionAndProof: data
|
||||
)
|
||||
of Web3SignerRequestKind.ValidatorRegistration:
|
||||
if dataName != "validator_registration":
|
||||
reader.raiseUnexpectedValue(
|
||||
"Field `validator_registration` is missing")
|
||||
if forkInfo.isNone():
|
||||
reader.raiseUnexpectedValue("Field `fork_info` is missing")
|
||||
let data =
|
||||
block:
|
||||
let res =
|
||||
decodeJsonString(Web3SignerValidatorRegistration, data.get())
|
||||
if res.isErr():
|
||||
reader.raiseUnexpectedValue(
|
||||
"Incorrect field `validator_registration` format")
|
||||
res.get()
|
||||
Web3SignerRequest(
|
||||
kind: Web3SignerRequestKind.ValidatorRegistration,
|
||||
forkInfo: forkInfo, signingRoot: signingRoot,
|
||||
validatorRegistration: data
|
||||
)
|
||||
|
||||
## RemoteKeystoreStatus
|
||||
proc writeValue*(writer: var JsonWriter[RestJson],
|
||||
|
|
|
@ -486,10 +486,20 @@ type
|
|||
serializedFieldName: "beacon_block_root".}: Eth2Digest
|
||||
slot*: Slot
|
||||
|
||||
# https://consensys.github.io/web3signer/web3signer-eth2.html#operation/ETH2_SIGN
|
||||
Web3SignerValidatorRegistration* = object
|
||||
feeRecipient* {.
|
||||
serializedFieldName: "fee_recipient".}: string
|
||||
gasLimit* {.
|
||||
serializedFieldName: "gas_limit".}: uint64
|
||||
timestamp*: uint64
|
||||
pubkey*: ValidatorPubKey
|
||||
|
||||
Web3SignerRequestKind* {.pure.} = enum
|
||||
AggregationSlot, AggregateAndProof, Attestation, Block, BlockV2,
|
||||
Deposit, RandaoReveal, VoluntaryExit, SyncCommitteeMessage,
|
||||
SyncCommitteeSelectionProof, SyncCommitteeContributionAndProof
|
||||
SyncCommitteeSelectionProof, SyncCommitteeContributionAndProof,
|
||||
ValidatorRegistration
|
||||
|
||||
Web3SignerRequest* = object
|
||||
signingRoot*: Option[Eth2Digest]
|
||||
|
@ -528,6 +538,10 @@ type
|
|||
of Web3SignerRequestKind.SyncCommitteeContributionAndProof:
|
||||
syncCommitteeContributionAndProof* {.
|
||||
serializedFieldName: "contribution_and_proof".}: ContributionAndProof
|
||||
of Web3SignerRequestKind.ValidatorRegistration:
|
||||
validatorRegistration* {.
|
||||
serializedFieldName: "validator_registration".}:
|
||||
Web3SignerValidatorRegistration
|
||||
|
||||
GetBlockResponse* = DataEnclosedObject[phase0.SignedBeaconBlock]
|
||||
GetStateResponse* = DataEnclosedObject[phase0.BeaconState]
|
||||
|
@ -774,6 +788,26 @@ func init*(t: typedesc[Web3SignerRequest], fork: Fork,
|
|||
syncCommitteeContributionAndProof: data
|
||||
)
|
||||
|
||||
from stew/byteutils import to0xHex
|
||||
|
||||
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
|
||||
genesis_validators_root: Eth2Digest,
|
||||
data: ValidatorRegistrationV1,
|
||||
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
|
||||
): Web3SignerRequest =
|
||||
Web3SignerRequest(
|
||||
kind: Web3SignerRequestKind.ValidatorRegistration,
|
||||
forkInfo: some(Web3SignerForkInfo(
|
||||
fork: fork, genesis_validators_root: genesis_validators_root
|
||||
)),
|
||||
signingRoot: signingRoot,
|
||||
validatorRegistration: Web3SignerValidatorRegistration(
|
||||
feeRecipient: data.fee_recipient.data.to0xHex,
|
||||
gasLimit: data.gas_limit,
|
||||
timestamp: data.timestamp,
|
||||
pubkey: data.pubkey)
|
||||
)
|
||||
|
||||
func init*(t: typedesc[RestSyncCommitteeMessage],
|
||||
slot: Slot,
|
||||
beacon_block_root: Eth2Digest,
|
||||
|
|
|
@ -15,7 +15,8 @@ import
|
|||
chronicles,
|
||||
../extras,
|
||||
"."/[block_id, eth2_merkleization, eth2_ssz_serialization, presets],
|
||||
./datatypes/[phase0, altair, bellatrix]
|
||||
./datatypes/[phase0, altair, bellatrix],
|
||||
./mev/bellatrix_mev
|
||||
|
||||
export
|
||||
extras, block_id, phase0, altair, bellatrix, eth2_merkleization,
|
||||
|
@ -110,7 +111,11 @@ type
|
|||
of BeaconBlockFork.Altair: altairData*: altair.BeaconBlock
|
||||
of BeaconBlockFork.Bellatrix: bellatrixData*: bellatrix.BeaconBlock
|
||||
|
||||
Web3SignerForkedBeaconBlock* {.borrow: `.`} = distinct ForkedBeaconBlock
|
||||
Web3SignerForkedBeaconBlock* = object
|
||||
case kind*: BeaconBlockFork
|
||||
of BeaconBlockFork.Phase0: phase0Data*: phase0.BeaconBlock
|
||||
of BeaconBlockFork.Altair: altairData*: altair.BeaconBlock
|
||||
of BeaconBlockFork.Bellatrix: bellatrixData*: BeaconBlockHeader
|
||||
|
||||
ForkedTrustedBeaconBlock* = object
|
||||
case kind*: BeaconBlockFork
|
||||
|
@ -452,11 +457,10 @@ template withBlck*(
|
|||
func proposer_index*(x: ForkedBeaconBlock): uint64 =
|
||||
withBlck(x): blck.proposer_index
|
||||
|
||||
func hash_tree_root*(x: ForkedBeaconBlock): Eth2Digest =
|
||||
func hash_tree_root*(x: ForkedBeaconBlock | Web3SignerForkedBeaconBlock):
|
||||
Eth2Digest =
|
||||
withBlck(x): hash_tree_root(blck)
|
||||
|
||||
func hash_tree_root*(x: Web3SignerForkedBeaconBlock): Eth2Digest {.borrow.}
|
||||
|
||||
template getForkedBlockField*(
|
||||
x: ForkedSignedBeaconBlock |
|
||||
ForkedMsgTrustedSignedBeaconBlock |
|
||||
|
@ -522,7 +526,7 @@ template withStateAndBlck*(
|
|||
body
|
||||
|
||||
func toBeaconBlockHeader*(
|
||||
blck: SomeForkyBeaconBlock): BeaconBlockHeader =
|
||||
blck: SomeForkyBeaconBlock | BlindedBeaconBlock): BeaconBlockHeader =
|
||||
## Reduce a given `BeaconBlock` to its `BeaconBlockHeader`.
|
||||
BeaconBlockHeader(
|
||||
slot: blck.slot,
|
||||
|
|
|
@ -26,7 +26,7 @@ type
|
|||
signature*: ValidatorSig
|
||||
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/builder.md#builderbid
|
||||
BuilderBid = object
|
||||
BuilderBid* = object
|
||||
header*: ExecutionPayloadHeader
|
||||
value*: UInt256
|
||||
pubkey*: ValidatorPubKey
|
||||
|
@ -67,5 +67,5 @@ const
|
|||
DOMAIN_APPLICATION_BUILDER* = DomainType([byte 0x00, 0x00, 0x00, 0x01])
|
||||
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#constants
|
||||
EPOCHS_PER_VALIDATOR_REGISTRATION_SUBMISSION* = 1.Epoch
|
||||
EPOCHS_PER_VALIDATOR_REGISTRATION_SUBMISSION* = 1
|
||||
BUILDER_PROPOSAL_DELAY_TOLERANCE* = 1.seconds
|
||||
|
|
|
@ -39,5 +39,4 @@ proc submitBlindedBlock*(body: SignedBlindedBeaconBlock
|
|||
proc checkBuilderStatus*(): RestPlainResponse {.
|
||||
rest, endpoint: "/eth/v1/builder/status",
|
||||
meth: MethodGet.}
|
||||
## https://github.com/ethereum/builder-specs/blob/v0.1.0/apis/builder/status.yaml
|
||||
## https://github.com/ethereum/builder-specs/blob/v0.2.0/apis/builder/status.yaml
|
||||
|
|
|
@ -320,7 +320,6 @@ proc get_contribution_and_proof_signature*(
|
|||
|
||||
blsSign(privkey, signing_root.data)
|
||||
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.1/specs/altair/validator.md#aggregation-selection
|
||||
func is_sync_committee_aggregator*(signature: ValidatorSig): bool =
|
||||
let
|
||||
|
@ -336,3 +335,25 @@ proc verify_contribution_and_proof_signature*(
|
|||
fork, genesis_validators_root, msg)
|
||||
|
||||
blsVerify(pubkey, signing_root.data, signature)
|
||||
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/builder.md#signing
|
||||
func compute_builder_signing_root*(
|
||||
fork: Fork, msg: BuilderBid | ValidatorRegistrationV1): Eth2Digest =
|
||||
# Uses genesis fork version regardless
|
||||
doAssert fork.current_version == fork.previous_version
|
||||
|
||||
let domain = get_domain(
|
||||
fork, DOMAIN_APPLICATION_BUILDER, GENESIS_EPOCH, ZERO_HASH)
|
||||
compute_signing_root(msg, domain)
|
||||
|
||||
proc get_builder_signature*(
|
||||
fork: Fork, msg: ValidatorRegistrationV1, privkey: ValidatorPrivKey):
|
||||
CookedSig =
|
||||
let signing_root = compute_builder_signing_root(fork, msg)
|
||||
blsSign(privkey, signing_root.data)
|
||||
|
||||
proc verify_builder_signature*(
|
||||
fork: Fork, msg: BuilderBid,
|
||||
pubkey: ValidatorPubKey | CookedPubKey, signature: SomeSig): bool =
|
||||
let signing_root = compute_builder_signing_root(fork, msg)
|
||||
blsVerify(pubkey, signing_root.data, signature)
|
||||
|
|
|
@ -518,7 +518,10 @@ proc makeBeaconBlock*(
|
|||
# TODO:
|
||||
# `verificationFlags` is needed only in tests and can be
|
||||
# removed if we don't use invalid signatures there
|
||||
verificationFlags: UpdateFlags = {}): Result[ForkedBeaconBlock, cstring] =
|
||||
verificationFlags: UpdateFlags = {},
|
||||
transactions_root: Opt[Eth2Digest] = Opt.none Eth2Digest,
|
||||
execution_payload_root: Opt[Eth2Digest] = Opt.none Eth2Digest):
|
||||
Result[ForkedBeaconBlock, cstring] =
|
||||
## Create a block for the given state. The latest block applied to it will
|
||||
## be used for the parent_root value, and the slot will be take from
|
||||
## state.slot meaning process_slots must be called up to the slot for which
|
||||
|
@ -540,6 +543,30 @@ proc makeBeaconBlock*(
|
|||
rollback(state)
|
||||
return err(res.error())
|
||||
|
||||
# Override for MEV
|
||||
if transactions_root.isSome and execution_payload_root.isSome:
|
||||
withState(state):
|
||||
static: doAssert high(BeaconStateFork) == BeaconStateFork.Bellatrix
|
||||
when stateFork == BeaconStateFork.Bellatrix:
|
||||
state.data.latest_execution_payload_header.transactions_root =
|
||||
transactions_root.get
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.1/specs/bellatrix/beacon-chain.md#beaconblockbody
|
||||
# Effectively hash_tree_root(ExecutionPayload) with the beacon block
|
||||
# body, with the execution payload replaced by the execution payload
|
||||
# header. htr(payload) == htr(payload header), so substitute.
|
||||
state.data.latest_block_header.body_root = hash_tree_root(
|
||||
[hash_tree_root(randao_reveal),
|
||||
hash_tree_root(eth1_data),
|
||||
hash_tree_root(graffiti),
|
||||
hash_tree_root(exits.proposer_slashings),
|
||||
hash_tree_root(exits.attester_slashings),
|
||||
hash_tree_root(List[Attestation, Limit MAX_ATTESTATIONS](attestations)),
|
||||
hash_tree_root(List[Deposit, Limit MAX_DEPOSITS](deposits)),
|
||||
hash_tree_root(exits.voluntary_exits),
|
||||
hash_tree_root(sync_aggregate),
|
||||
execution_payload_root.get])
|
||||
|
||||
state.`kind Data`.root = hash_tree_root(state.`kind Data`.data)
|
||||
blck.`kind Data`.state_root = state.`kind Data`.root
|
||||
|
||||
|
|
|
@ -863,7 +863,7 @@ func process_registry_updates*(
|
|||
# https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.1/specs/bellatrix/beacon-chain.md#slashings
|
||||
func get_adjusted_total_slashing_balance*(
|
||||
state: ForkyBeaconState, total_balance: Gwei): Gwei =
|
||||
let multiplier =
|
||||
const multiplier =
|
||||
# tradeoff here about interleaving phase0/altair, but for these
|
||||
# single-constant changes...
|
||||
when state is phase0.BeaconState:
|
||||
|
|
|
@ -44,11 +44,10 @@ import
|
|||
../sszdump, ../sync/sync_manager,
|
||||
../gossip_processing/block_processor,
|
||||
".."/[conf, beacon_clock, beacon_node],
|
||||
"."/[slashing_protection, validator_pool, keystore_management]
|
||||
"."/[slashing_protection, validator_pool, keystore_management],
|
||||
".."/spec/mev/rest_bellatrix_mev_calls
|
||||
|
||||
from eth/async_utils import awaitWithTimeout
|
||||
from web3/engine_api import ForkchoiceUpdatedResponse
|
||||
from web3/engine_api_types import PayloadExecutionStatus
|
||||
|
||||
# Metrics for tracking attestation and beacon block loss
|
||||
const delayBuckets = [-Inf, -4.0, -2.0, -1.0, -0.5, -0.1, -0.05,
|
||||
|
@ -308,6 +307,8 @@ proc getBlockProposalEth1Data*(node: BeaconNode,
|
|||
state, finalizedEpochRef.eth1_data,
|
||||
finalizedEpochRef.eth1_deposit_index)
|
||||
|
||||
from web3/engine_api import ForkchoiceUpdatedResponse
|
||||
|
||||
# TODO: This copies the entire BeaconState on each call
|
||||
proc forkchoice_updated(state: bellatrix.BeaconState,
|
||||
head_block_hash: Eth2Digest,
|
||||
|
@ -343,6 +344,8 @@ proc get_execution_payload(
|
|||
asConsensusExecutionPayload(
|
||||
await execution_engine.getPayload(payload_id.get))
|
||||
|
||||
from web3/engine_api_types import PayloadExecutionStatus
|
||||
|
||||
proc getExecutionPayload(
|
||||
node: BeaconNode, proposalState: auto,
|
||||
epoch: Epoch,
|
||||
|
@ -413,12 +416,14 @@ proc getExecutionPayload(
|
|||
msg = err.msg
|
||||
return empty_execution_payload
|
||||
|
||||
proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
||||
randao_reveal: ValidatorSig,
|
||||
validator_index: ValidatorIndex,
|
||||
graffiti: GraffitiBytes,
|
||||
head: BlockRef, slot: Slot
|
||||
): Future[ForkedBlockResult] {.async.} =
|
||||
proc makeBeaconBlockForHeadAndSlot*(
|
||||
node: BeaconNode, randao_reveal: ValidatorSig,
|
||||
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
|
||||
slot: Slot,
|
||||
execution_payload: Opt[ExecutionPayload] = Opt.none(ExecutionPayload),
|
||||
transactions_root: Opt[Eth2Digest] = Opt.none(Eth2Digest),
|
||||
execution_payload_root: Opt[Eth2Digest] = Opt.none(Eth2Digest)):
|
||||
Future[ForkedBlockResult] {.async.} =
|
||||
# Advance state to the slot that we're proposing for
|
||||
let
|
||||
proposalState = assignClone(node.dag.headState)
|
||||
|
@ -461,12 +466,15 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
|||
SyncAggregate.init()
|
||||
else:
|
||||
node.syncCommitteeMsgPool[].produceSyncAggregate(head.root),
|
||||
if slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or
|
||||
if executionPayload.isSome:
|
||||
executionPayload.get
|
||||
elif slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or
|
||||
not (
|
||||
is_merge_transition_complete(proposalState.bellatrixData.data) or
|
||||
((not node.eth1Monitor.isNil) and
|
||||
node.eth1Monitor.terminalBlockHash.isSome)):
|
||||
default(bellatrix.ExecutionPayload)
|
||||
# https://github.com/nim-lang/Nim/issues/19802
|
||||
(static(default(bellatrix.ExecutionPayload)))
|
||||
else:
|
||||
let pubkey = node.dag.validatorKey(validator_index)
|
||||
(await getExecutionPayload(
|
||||
|
@ -475,7 +483,17 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
|||
# TODO https://github.com/nim-lang/Nim/issues/19802
|
||||
if pubkey.isSome: pubkey.get.toPubKey else: default(ValidatorPubKey))),
|
||||
noRollback, # Temporary state - no need for rollback
|
||||
cache)
|
||||
cache,
|
||||
transactions_root =
|
||||
if transactions_root.isSome:
|
||||
Opt.some transactions_root.get
|
||||
else:
|
||||
Opt.none(Eth2Digest),
|
||||
execution_payload_root =
|
||||
if execution_payload_root.isSome:
|
||||
Opt.some execution_payload_root.get
|
||||
else:
|
||||
Opt.none Eth2Digest)
|
||||
if res.isErr():
|
||||
# This is almost certainly a bug, but it's complex enough that there's a
|
||||
# small risk it might happen even when most proposals succeed - thus we
|
||||
|
@ -489,6 +507,226 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
|||
head = shortLog(head),
|
||||
slot
|
||||
|
||||
proc getBlindedExecutionPayload(
|
||||
node: BeaconNode, slot: Slot, executionBlockRoot: Eth2Digest,
|
||||
pubkey: ValidatorPubKey):
|
||||
Future[Result[ExecutionPayloadHeader, cstring]] {.async.} =
|
||||
if node.payloadBuilderRestClient.isNil:
|
||||
return err "getBlindedBeaconBlock: nil REST client"
|
||||
|
||||
let blindedHeader = await node.payloadBuilderRestClient.getHeader(
|
||||
slot, executionBlockRoot, pubkey)
|
||||
|
||||
const httpOk = 200
|
||||
if blindedHeader.status != httpOk:
|
||||
return err "getBlindedExecutionPayload: non-200 HTTP response"
|
||||
else:
|
||||
if not verify_builder_signature(
|
||||
node.dag.cfg.genesisFork, blindedHeader.data.data.message,
|
||||
blindedHeader.data.data.message.pubkey,
|
||||
blindedHeader.data.data.signature):
|
||||
return err "getBlindedExecutionPayload: signature verification failed"
|
||||
|
||||
return ok blindedHeader.data.data.message.header
|
||||
|
||||
import std/macros
|
||||
|
||||
func getFieldNames(x: typedesc[auto]): seq[string] {.compileTime.} =
|
||||
var res: seq[string]
|
||||
for name, _ in fieldPairs(default(x)):
|
||||
res.add name
|
||||
res
|
||||
|
||||
macro copyFields(
|
||||
dst: untyped, src: untyped, fieldNames: static[seq[string]]): untyped =
|
||||
result = newStmtList()
|
||||
for name in fieldNames:
|
||||
if name notin [
|
||||
# These fields are the ones which vary between the blinded and
|
||||
# unblinded objects, and can't simply be copied.
|
||||
"transactions_root", "execution_payload",
|
||||
"execution_payload_header", "body"]:
|
||||
result.add newAssignment(
|
||||
newDotExpr(dst, ident(name)), newDotExpr(src, ident(name)))
|
||||
|
||||
proc getBlindedBeaconBlock(
|
||||
node: BeaconNode, slot: Slot, head: BlockRef, validator: AttachedValidator,
|
||||
validator_index: ValidatorIndex, forkedBlock: ForkedBeaconBlock,
|
||||
executionPayloadHeader: ExecutionPayloadHeader):
|
||||
Future[Result[SignedBlindedBeaconBlock, string]] {.async.} =
|
||||
const
|
||||
blckFields = getFieldNames(typeof(forkedBlock.bellatrixData))
|
||||
blckBodyFields = getFieldNames(typeof(forkedBlock.bellatrixData.body))
|
||||
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#block-proposal
|
||||
var blindedBlock: SignedBlindedBeaconBlock
|
||||
|
||||
copyFields(blindedBlock.message, forkedBlock.bellatrixData, blckFields)
|
||||
copyFields(
|
||||
blindedBlock.message.body, forkedBlock.bellatrixData.body, blckBodyFields)
|
||||
blindedBlock.message.body.execution_payload_header = executionPayloadHeader
|
||||
|
||||
# Check with slashing protection before submitBlindedBlock
|
||||
let
|
||||
fork = node.dag.forkAtEpoch(slot.epoch)
|
||||
genesis_validators_root = node.dag.genesis_validators_root
|
||||
blockRoot = hash_tree_root(blindedBlock.message)
|
||||
signing_root = compute_block_signing_root(
|
||||
fork, genesis_validators_root, slot, blockRoot)
|
||||
notSlashable = node.attachedValidators
|
||||
.slashingProtection
|
||||
.registerBlock(validator_index, validator.pubkey, slot, signing_root)
|
||||
|
||||
if notSlashable.isErr:
|
||||
warn "Slashing protection activated for MEV block",
|
||||
validator = validator.pubkey,
|
||||
slot = slot,
|
||||
existingProposal = notSlashable.error
|
||||
return err("MEV proposal would be slashable: " & $notSlashable.error)
|
||||
|
||||
blindedBlock.signature =
|
||||
block:
|
||||
let res = await validator.getBlockSignature(
|
||||
fork, genesis_validators_root, slot, blockRoot, blindedBlock.message)
|
||||
if res.isErr():
|
||||
return err("Unable to sign block: " & res.error())
|
||||
res.get()
|
||||
|
||||
return ok blindedBlock
|
||||
|
||||
proc proposeBlockMEV(
|
||||
node: BeaconNode, head: BlockRef, validator: AttachedValidator, slot: Slot,
|
||||
randao: ValidatorSig, validator_index: ValidatorIndex):
|
||||
Future[Opt[BlockRef]] {.async.} =
|
||||
let
|
||||
executionBlockRoot = node.dag.loadExecutionBlockRoot(head)
|
||||
executionPayloadHeader = awaitWithTimeout(
|
||||
node.getBlindedExecutionPayload(
|
||||
slot, executionBlockRoot, validator.pubkey),
|
||||
BUILDER_PROPOSAL_DELAY_TOLERANCE):
|
||||
Result[ExecutionPayloadHeader, cstring].err(
|
||||
"getBlindedExecutionPayload timed out")
|
||||
|
||||
if executionPayloadHeader.isErr:
|
||||
debug "proposeBlockMEV: getBlindedExecutionPayload failed",
|
||||
error = executionPayloadHeader.error
|
||||
# Haven't committed to the MEV block, so allow EL fallback.
|
||||
return Opt.none BlockRef
|
||||
|
||||
# When creating this block, need to ensure it uses the MEV-provided execution
|
||||
# payload, both to avoid repeated calls to network services and to ensure the
|
||||
# consistency of this block (e.g., its state root being correct). Since block
|
||||
# processing does not work directly using blinded blocks, fix up transactions
|
||||
# root after running the state transition function on an otherwise equivalent
|
||||
# non-blinded block without transactions.
|
||||
var shimExecutionPayload: ExecutionPayload
|
||||
copyFields(
|
||||
shimExecutionPayload, executionPayloadHeader.get,
|
||||
getFieldNames(ExecutionPayloadHeader))
|
||||
|
||||
let newBlock = await makeBeaconBlockForHeadAndSlot(
|
||||
node, randao, validator_index, node.graffitiBytes, head, slot,
|
||||
execution_payload = Opt.some shimExecutionPayload,
|
||||
transactions_root = Opt.some executionPayloadHeader.get.transactions_root,
|
||||
execution_payload_root =
|
||||
Opt.some hash_tree_root(executionPayloadHeader.get))
|
||||
|
||||
if newBlock.isErr():
|
||||
# Haven't committed to the MEV block, so allow EL fallback.
|
||||
return Opt.none BlockRef # already logged elsewhere!
|
||||
|
||||
let forkedBlck = newBlock.get()
|
||||
|
||||
# This is only substantively asynchronous with a remote key signer
|
||||
let blindedBlock = awaitWithTimeout(
|
||||
node.getBlindedBeaconBlock(
|
||||
slot, head, validator, validator_index, forkedBlck,
|
||||
executionPayloadHeader.get),
|
||||
500.milliseconds):
|
||||
Result[SignedBlindedBeaconBlock, string].err "getBlindedBlock timed out"
|
||||
|
||||
if blindedBlock.isOk:
|
||||
# By time submitBlindedBlock is called, must already have done slashing
|
||||
# protection check
|
||||
let unblindedPayload =
|
||||
try:
|
||||
await node.payloadBuilderRestClient.submitBlindedBlock(
|
||||
blindedBlock.get)
|
||||
# From here on, including error paths, disallow local EL production by
|
||||
# returning Opt.some, regardless of whether on head or newBlock.
|
||||
except RestDecodingError as exc:
|
||||
info "proposeBlockMEV: REST recoding error",
|
||||
slot, head = shortLog(head), validator_index, blindedBlock,
|
||||
error = exc.msg
|
||||
return Opt.some head
|
||||
except CatchableError as exc:
|
||||
info "proposeBlockMEV: exception in submitBlindedBlock",
|
||||
slot, head = shortLog(head), validator_index, blindedBlock,
|
||||
error = exc.msg
|
||||
return Opt.some head
|
||||
|
||||
const httpOk = 200
|
||||
if unblindedPayload.status == httpOk:
|
||||
if hash_tree_root(
|
||||
blindedBlock.get.message.body.execution_payload_header) !=
|
||||
hash_tree_root(unblindedPayload.data.data):
|
||||
debug "proposeBlockMEV: unblinded payload doesn't match blinded payload",
|
||||
blindedPayload =
|
||||
blindedBlock.get.message.body.execution_payload_header
|
||||
else:
|
||||
# Signature provided is consistent with unblinded execution payload,
|
||||
# so construct full beacon block
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#block-proposal
|
||||
var signedBlock = bellatrix.SignedBeaconBlock(
|
||||
signature: blindedBlock.get.signature)
|
||||
copyFields(
|
||||
signedBlock.message, blindedBlock.get.message,
|
||||
getFieldNames(typeof(signedBlock.message)))
|
||||
copyFields(
|
||||
signedBlock.message.body, blindedBlock.get.message.body,
|
||||
getFieldNames(typeof(signedBlock.message.body)))
|
||||
signedBlock.message.body.execution_payload = unblindedPayload.data.data
|
||||
|
||||
signedBlock.root = hash_tree_root(signedBlock.message)
|
||||
|
||||
doAssert signedBlock.root == hash_tree_root(blindedBlock.get.message)
|
||||
|
||||
debug "proposeBlockMEV: proposing unblinded block",
|
||||
blck = shortLog(signedBlock)
|
||||
|
||||
let newBlockRef =
|
||||
(await node.router.routeSignedBeaconBlock(signedBlock)).valueOr:
|
||||
# submitBlindedBlock has run, so don't allow fallback to run
|
||||
return Opt.some head # Errors logged in router
|
||||
|
||||
if newBlockRef.isNone():
|
||||
return Opt.some head # Validation errors logged in router
|
||||
|
||||
notice "Block proposed (MEV)",
|
||||
blockRoot = shortLog(signedBlock.root), blck = shortLog(signedBlock),
|
||||
signature = shortLog(signedBlock.signature), validator = shortLog(validator)
|
||||
|
||||
beacon_blocks_proposed.inc()
|
||||
|
||||
return Opt.some newBlockRef.get()
|
||||
else:
|
||||
debug "proposeBlockMEV: submitBlindedBlock failed",
|
||||
slot, head = shortLog(head), validator_index, blindedBlock,
|
||||
payloadStatus = unblindedPayload.status
|
||||
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#proposer-slashing
|
||||
# This means if a validator publishes a signature for a
|
||||
# `BlindedBeaconBlock` (via a dissemination of a
|
||||
# `SignedBlindedBeaconBlock`) then the validator **MUST** not use the
|
||||
# local build process as a fallback, even in the event of some failure
|
||||
# with the external buildernetwork.
|
||||
return Opt.some head
|
||||
else:
|
||||
info "proposeBlockMEV: getBlindedBeaconBlock failed",
|
||||
slot, head = shortLog(head), validator_index, blindedBlock,
|
||||
error = blindedBlock.error
|
||||
return Opt.none BlockRef
|
||||
|
||||
proc proposeBlock(node: BeaconNode,
|
||||
validator: AttachedValidator,
|
||||
validator_index: ValidatorIndex,
|
||||
|
@ -516,7 +754,22 @@ proc proposeBlock(node: BeaconNode,
|
|||
return head
|
||||
res.get()
|
||||
|
||||
newBlock = await makeBeaconBlockForHeadAndSlot(
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#responsibilites-during-the-merge-transition
|
||||
# "Honest validators will not utilize the external builder network until
|
||||
# after the transition from the proof-of-work chain to the proof-of-stake
|
||||
# beacon chain has been finalized by the proof-of-stake validators."
|
||||
if node.config.payloadBuilderEnable and
|
||||
not node.dag.loadExecutionBlockRoot(node.dag.finalizedHead.blck).isZero:
|
||||
let newBlockMEV = await node.proposeBlockMEV(
|
||||
head, validator, slot, randao, validator_index)
|
||||
|
||||
if newBlockMEV.isSome:
|
||||
# This might be equivalent to the `head` passed in, but it signals that
|
||||
# `submitBlindedBlock` ran, so don't do anything else. Otherwise, it is
|
||||
# fine to try again with the local EL.
|
||||
return newBlockMEV.get
|
||||
|
||||
let newBlock = await makeBeaconBlockForHeadAndSlot(
|
||||
node, randao, validator_index, node.graffitiBytes, head, slot)
|
||||
|
||||
if newBlock.isErr():
|
||||
|
@ -918,6 +1171,80 @@ proc updateValidatorMetrics*(node: BeaconNode) =
|
|||
node.attachedValidatorBalanceTotal = total
|
||||
attached_validator_balance_total.set(total.toGaugeValue)
|
||||
|
||||
from std/times import epochTime
|
||||
|
||||
proc getValidatorRegistration(
|
||||
node: BeaconNode, validator: AttachedValidator):
|
||||
Future[Result[SignedValidatorRegistrationV1, string]] {.async.} =
|
||||
# Stand-in, reasonable default
|
||||
const gasLimit = 30000000
|
||||
|
||||
let feeRecipient =
|
||||
node.config.getSuggestedFeeRecipient(validator.pubkey).valueOr:
|
||||
node.config.defaultFeeRecipient
|
||||
var validatorRegistration = SignedValidatorRegistrationV1(
|
||||
message: ValidatorRegistrationV1(
|
||||
fee_recipient: ExecutionAddress(data: distinctBase(feeRecipient)),
|
||||
gas_limit: gasLimit,
|
||||
timestamp: epochTime().uint64,
|
||||
pubkey: validator.pubkey))
|
||||
|
||||
let signature = await validator.getBuilderSignature(
|
||||
node.dag.cfg.genesisFork, validatorRegistration.message)
|
||||
|
||||
debug "getValidatorRegistration: registering",
|
||||
validatorRegistration
|
||||
|
||||
if signature.isErr:
|
||||
return err signature.error
|
||||
|
||||
validatorRegistration.signature = signature.get
|
||||
|
||||
return ok validatorRegistration
|
||||
|
||||
proc registerValidators(node: BeaconNode) {.async.} =
|
||||
try:
|
||||
if (not node.config.payloadBuilderEnable) or
|
||||
node.currentSlot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH:
|
||||
return
|
||||
elif node.config.payloadBuilderEnable and
|
||||
node.payloadBuilderRestClient.isNil:
|
||||
warn "registerValidators: node.config.payloadBuilderEnable and node.payloadBuilderRestClient.isNil"
|
||||
return
|
||||
|
||||
const HttpOk = 200
|
||||
|
||||
let restBuilderStatus = await node.payloadBuilderRestClient.checkBuilderStatus
|
||||
if restBuilderStatus.status != HttpOk:
|
||||
warn "registerValidators: specified builder not available",
|
||||
builderUrl = node.config.payloadBuilderUrl,
|
||||
builderStatus = restBuilderStatus
|
||||
return
|
||||
|
||||
# TODO split this across slots of epoch to support larger numbers of
|
||||
# validators
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#validator-registration
|
||||
var validatorRegistrations: seq[SignedValidatorRegistrationV1]
|
||||
for validator in node.attachedValidators[].validators.values:
|
||||
let validatorRegistration =
|
||||
await node.getValidatorRegistration(validator)
|
||||
if validatorRegistration.isErr:
|
||||
debug "registerValidators: validatorRegistration failed",
|
||||
validatorRegistration
|
||||
continue
|
||||
|
||||
validatorRegistrations.add validatorRegistration.get
|
||||
|
||||
let registerValidatorResult =
|
||||
await node.payloadBuilderRestClient.registerValidator(
|
||||
validatorRegistrations)
|
||||
if HttpOk != registerValidatorResult.status:
|
||||
warn "registerValidators: Couldn't register validator with MEV builder",
|
||||
registerValidatorResult
|
||||
except CatchableError as exc:
|
||||
warn "registerValidators: exception",
|
||||
error = exc.msg
|
||||
|
||||
proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
|
||||
## Perform validator duties - create blocks, vote and aggregate existing votes
|
||||
if node.attachedValidators[].count == 0:
|
||||
|
@ -980,6 +1307,13 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
|
|||
|
||||
curSlot += 1
|
||||
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#registration-dissemination
|
||||
# This specification suggests validators re-submit to builder software every
|
||||
# `EPOCHS_PER_VALIDATOR_REGISTRATION_SUBMISSION` epochs.
|
||||
if slot.is_epoch and
|
||||
slot.epoch mod EPOCHS_PER_VALIDATOR_REGISTRATION_SUBMISSION == 0:
|
||||
asyncSpawn node.registerValidators()
|
||||
|
||||
let
|
||||
newHead = await handleProposal(node, head, slot)
|
||||
didSubmitBlock = (newHead != head)
|
||||
|
|
|
@ -210,7 +210,8 @@ proc signData(v: AttachedValidator,
|
|||
# https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.1/specs/phase0/validator.md#signature
|
||||
proc getBlockSignature*(v: AttachedValidator, fork: Fork,
|
||||
genesis_validators_root: Eth2Digest, slot: Slot,
|
||||
block_root: Eth2Digest, blck: ForkedBeaconBlock
|
||||
block_root: Eth2Digest,
|
||||
blck: ForkedBeaconBlock | BlindedBeaconBlock
|
||||
): Future[SignatureResult] {.async.} =
|
||||
return
|
||||
case v.kind
|
||||
|
@ -220,8 +221,32 @@ proc getBlockSignature*(v: AttachedValidator, fork: Fork,
|
|||
fork, genesis_validators_root, slot, block_root,
|
||||
v.data.privateKey).toValidatorSig())
|
||||
of ValidatorKind.Remote:
|
||||
when blck is BlindedBeaconBlock:
|
||||
let request = Web3SignerRequest.init(
|
||||
fork, genesis_validators_root, blck.Web3SignerForkedBeaconBlock)
|
||||
fork, genesis_validators_root,
|
||||
Web3SignerForkedBeaconBlock(
|
||||
kind: BeaconBlockFork.Bellatrix,
|
||||
bellatrixData: blck.toBeaconBlockHeader))
|
||||
await v.signData(request)
|
||||
else:
|
||||
let
|
||||
web3SignerBlock =
|
||||
case blck.kind
|
||||
of BeaconBlockFork.Phase0:
|
||||
Web3SignerForkedBeaconBlock(
|
||||
kind: BeaconBlockFork.Phase0,
|
||||
phase0Data: blck.phase0Data)
|
||||
of BeaconBlockFork.Altair:
|
||||
Web3SignerForkedBeaconBlock(
|
||||
kind: BeaconBlockFork.Altair,
|
||||
altairData: blck.altairData)
|
||||
of BeaconBlockFork.Bellatrix:
|
||||
Web3SignerForkedBeaconBlock(
|
||||
kind: BeaconBlockFork.Bellatrix,
|
||||
bellatrixData: blck.bellatrixData.toBeaconBlockHeader)
|
||||
|
||||
request = Web3SignerRequest.init(
|
||||
fork, genesis_validators_root, web3SignerBlock)
|
||||
await v.signData(request)
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.1/specs/phase0/validator.md#aggregate-signature
|
||||
|
@ -363,3 +388,17 @@ proc getSlotSignature*(v: AttachedValidator, fork: Fork,
|
|||
|
||||
v.slotSignature = some((slot, signature.get))
|
||||
return signature
|
||||
|
||||
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/builder.md#signing
|
||||
proc getBuilderSignature*(v: AttachedValidator, fork: Fork,
|
||||
validatorRegistration: ValidatorRegistrationV1):
|
||||
Future[SignatureResult] {.async.} =
|
||||
return
|
||||
case v.kind
|
||||
of ValidatorKind.Local:
|
||||
SignatureResult.ok(get_builder_signature(
|
||||
fork, validatorRegistration, v.data.privateKey).toValidatorSig())
|
||||
of ValidatorKind.Remote:
|
||||
let request = Web3SignerRequest.init(
|
||||
fork, ZERO_HASH, validatorRegistration)
|
||||
await v.signData(request)
|
||||
|
|
Loading…
Reference in New Issue