@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
282 lines • 12.4 kB
JavaScript
import { BYTES_PER_FIELD_ELEMENT, BYTES_PER_LOGS_BLOOM, CONSOLIDATION_REQUEST_TYPE, DEPOSIT_REQUEST_TYPE, FIELD_ELEMENTS_PER_BLOB, ForkSeq, WITHDRAWAL_REQUEST_TYPE, } from "@lodestar/params";
import { ssz } from "@lodestar/types";
import { bytesToData, dataToBytes, numToQuantity, quantityToBigint, quantityToNum, } from "../../eth1/provider/utils.js";
import { isExecutionRequestType, } from "./interface.js";
export function serializeExecutionPayload(fork, data) {
const payload = {
parentHash: bytesToData(data.parentHash),
feeRecipient: bytesToData(data.feeRecipient),
stateRoot: bytesToData(data.stateRoot),
receiptsRoot: bytesToData(data.receiptsRoot),
logsBloom: bytesToData(data.logsBloom),
prevRandao: bytesToData(data.prevRandao),
blockNumber: numToQuantity(data.blockNumber),
gasLimit: numToQuantity(data.gasLimit),
gasUsed: numToQuantity(data.gasUsed),
timestamp: numToQuantity(data.timestamp),
extraData: bytesToData(data.extraData),
baseFeePerGas: numToQuantity(data.baseFeePerGas),
blockHash: bytesToData(data.blockHash),
transactions: data.transactions.map((tran) => bytesToData(tran)),
};
// Capella adds withdrawals to the ExecutionPayload
if (ForkSeq[fork] >= ForkSeq.capella) {
const { withdrawals } = data;
payload.withdrawals = withdrawals.map(serializeWithdrawal);
}
// DENEB adds blobGasUsed & excessBlobGas to the ExecutionPayload
if (ForkSeq[fork] >= ForkSeq.deneb) {
const { blobGasUsed, excessBlobGas } = data;
payload.blobGasUsed = numToQuantity(blobGasUsed);
payload.excessBlobGas = numToQuantity(excessBlobGas);
}
// No changes in Electra
return payload;
}
export function serializeVersionedHashes(vHashes) {
return vHashes.map(bytesToData);
}
export function hasPayloadValue(response) {
return response.blockValue !== undefined;
}
export function parseExecutionPayload(fork, response) {
let data;
let executionPayloadValue;
let blobsBundle;
let executionRequests;
let shouldOverrideBuilder;
if (hasPayloadValue(response)) {
executionPayloadValue = quantityToBigint(response.blockValue);
data = response.executionPayload;
blobsBundle = response.blobsBundle ? parseBlobsBundle(response.blobsBundle) : undefined;
executionRequests = response.executionRequests
? deserializeExecutionRequests(response.executionRequests)
: undefined;
shouldOverrideBuilder = response.shouldOverrideBuilder ?? false;
}
else {
data = response;
// Just set it to zero as default
executionPayloadValue = BigInt(0);
blobsBundle = undefined;
executionRequests = undefined;
shouldOverrideBuilder = false;
}
const executionPayload = {
parentHash: dataToBytes(data.parentHash, 32),
feeRecipient: dataToBytes(data.feeRecipient, 20),
stateRoot: dataToBytes(data.stateRoot, 32),
receiptsRoot: dataToBytes(data.receiptsRoot, 32),
logsBloom: dataToBytes(data.logsBloom, BYTES_PER_LOGS_BLOOM),
prevRandao: dataToBytes(data.prevRandao, 32),
blockNumber: quantityToNum(data.blockNumber),
gasLimit: quantityToNum(data.gasLimit),
gasUsed: quantityToNum(data.gasUsed),
timestamp: quantityToNum(data.timestamp),
extraData: dataToBytes(data.extraData, null),
baseFeePerGas: quantityToBigint(data.baseFeePerGas),
blockHash: dataToBytes(data.blockHash, 32),
transactions: data.transactions.map((tran) => dataToBytes(tran, null)),
};
if (ForkSeq[fork] >= ForkSeq.capella) {
const { withdrawals } = data;
// Geth can also reply with null
if (withdrawals == null) {
throw Error(`withdrawals missing for ${fork} >= capella executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`);
}
executionPayload.withdrawals = withdrawals.map((w) => deserializeWithdrawal(w));
}
// DENEB adds excessBlobGas to the ExecutionPayload
if (ForkSeq[fork] >= ForkSeq.deneb) {
const { blobGasUsed, excessBlobGas } = data;
if (blobGasUsed == null) {
throw Error(`blobGasUsed missing for ${fork} >= deneb executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`);
}
if (excessBlobGas == null) {
throw Error(`excessBlobGas missing for ${fork} >= deneb executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`);
}
executionPayload.blobGasUsed = quantityToBigint(blobGasUsed);
executionPayload.excessBlobGas = quantityToBigint(excessBlobGas);
}
// No changes in Electra
return { executionPayload, executionPayloadValue, blobsBundle, executionRequests, shouldOverrideBuilder };
}
export function serializePayloadAttributes(data) {
return {
timestamp: numToQuantity(data.timestamp),
prevRandao: bytesToData(data.prevRandao),
suggestedFeeRecipient: data.suggestedFeeRecipient,
withdrawals: data.withdrawals?.map(serializeWithdrawal),
parentBeaconBlockRoot: data.parentBeaconBlockRoot ? bytesToData(data.parentBeaconBlockRoot) : undefined,
};
}
export function serializeBeaconBlockRoot(data) {
return bytesToData(data);
}
export function deserializePayloadAttributes(data) {
return {
timestamp: quantityToNum(data.timestamp),
prevRandao: dataToBytes(data.prevRandao, 32),
// DATA is anyway a hex string, so we can just track it as a hex string to
// avoid any conversions
suggestedFeeRecipient: data.suggestedFeeRecipient,
withdrawals: data.withdrawals?.map((withdrawal) => deserializeWithdrawal(withdrawal)),
parentBeaconBlockRoot: data.parentBeaconBlockRoot ? dataToBytes(data.parentBeaconBlockRoot, 32) : undefined,
};
}
export function parseBlobsBundle(data) {
return {
// As of Nov 17th 2022 according to Dan's tests Geth returns null if no blobs in block
commitments: (data.commitments ?? []).map((kzg) => dataToBytes(kzg, 48)),
blobs: (data.blobs ?? []).map((blob) => dataToBytes(blob, BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB)),
proofs: (data.proofs ?? []).map((kzg) => dataToBytes(kzg, 48)),
};
}
export function serializeBlobsBundle(data) {
return {
commitments: data.commitments.map((kzg) => bytesToData(kzg)),
blobs: data.blobs.map((blob) => bytesToData(blob)),
proofs: data.blobs.map((proof) => bytesToData(proof)),
};
}
export function serializeWithdrawal(withdrawal) {
return {
index: numToQuantity(withdrawal.index),
validatorIndex: numToQuantity(withdrawal.validatorIndex),
address: bytesToData(withdrawal.address),
// Both CL and EL now deal in Gwei, just little-endian to big-endian conversion required
amount: numToQuantity(withdrawal.amount),
};
}
export function deserializeWithdrawal(serialized) {
return {
index: quantityToNum(serialized.index),
validatorIndex: quantityToNum(serialized.validatorIndex),
address: dataToBytes(serialized.address, 20),
// Both CL and EL now deal in Gwei, just big-endian to little-endian conversion required
amount: quantityToBigint(serialized.amount),
};
}
/**
* Prepend a single-byte requestType to requestsBytes
*/
function prefixRequests(requestsBytes, requestType) {
const prefixedRequests = new Uint8Array(1 + requestsBytes.length);
prefixedRequests[0] = requestType;
prefixedRequests.set(requestsBytes, 1);
return prefixedRequests;
}
function serializeDepositRequests(depositRequests) {
const requestsBytes = ssz.electra.DepositRequests.serialize(depositRequests);
return bytesToData(prefixRequests(requestsBytes, DEPOSIT_REQUEST_TYPE));
}
function deserializeDepositRequests(serialized) {
return ssz.electra.DepositRequests.deserialize(dataToBytes(serialized, null));
}
function serializeWithdrawalRequests(withdrawalRequests) {
const requestsBytes = ssz.electra.WithdrawalRequests.serialize(withdrawalRequests);
return bytesToData(prefixRequests(requestsBytes, WITHDRAWAL_REQUEST_TYPE));
}
function deserializeWithdrawalRequests(serialized) {
return ssz.electra.WithdrawalRequests.deserialize(dataToBytes(serialized, null));
}
function serializeConsolidationRequests(consolidationRequests) {
const requestsBytes = ssz.electra.ConsolidationRequests.serialize(consolidationRequests);
return bytesToData(prefixRequests(requestsBytes, CONSOLIDATION_REQUEST_TYPE));
}
function deserializeConsolidationRequests(serialized) {
return ssz.electra.ConsolidationRequests.deserialize(dataToBytes(serialized, null));
}
/**
* This is identical to get_execution_requests_list in
* https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/electra/beacon-chain.md#new-get_execution_requests_list
*/
export function serializeExecutionRequests(executionRequests) {
const { deposits, withdrawals, consolidations } = executionRequests;
const result = [];
if (deposits.length !== 0) {
result.push(serializeDepositRequests(deposits));
}
if (withdrawals.length !== 0) {
result.push(serializeWithdrawalRequests(withdrawals));
}
if (consolidations.length !== 0) {
result.push(serializeConsolidationRequests(consolidations));
}
return result;
}
export function deserializeExecutionRequests(serialized) {
const result = {
deposits: [],
withdrawals: [],
consolidations: [],
};
if (serialized.length === 0) {
return result;
}
let prevRequestType;
for (let prefixedRequests of serialized) {
// Slice out 0x so it is easier to extract request type
if (prefixedRequests.startsWith("0x")) {
prefixedRequests = prefixedRequests.slice(2);
}
const currentRequestType = parseInt(prefixedRequests.substring(0, 2), 16);
if (!isExecutionRequestType(currentRequestType)) {
throw Error(`Invalid request type currentRequestType=${prefixedRequests.substring(0, 2)}`);
}
const requests = prefixedRequests.slice(2);
if (requests.length === 0) {
throw Error(`Request with empty data must be excluded from execution requests currentRequestType=${currentRequestType}`);
}
if (prevRequestType !== undefined && prevRequestType >= currentRequestType) {
throw Error(`Current request type must be larger than previous request type prevRequestType=${prevRequestType} currentRequestType=${currentRequestType}`);
}
switch (currentRequestType) {
case DEPOSIT_REQUEST_TYPE: {
result.deposits = deserializeDepositRequests(requests);
break;
}
case WITHDRAWAL_REQUEST_TYPE: {
result.withdrawals = deserializeWithdrawalRequests(requests);
break;
}
case CONSOLIDATION_REQUEST_TYPE: {
result.consolidations = deserializeConsolidationRequests(requests);
break;
}
}
prevRequestType = currentRequestType;
}
return result;
}
export function deserializeExecutionPayloadBody(data) {
return data
? {
transactions: data.transactions.map((tran) => dataToBytes(tran, null)),
withdrawals: data.withdrawals ? data.withdrawals.map(deserializeWithdrawal) : null,
}
: null;
}
export function serializeExecutionPayloadBody(data) {
return data
? {
transactions: data.transactions.map((tran) => bytesToData(tran)),
withdrawals: data.withdrawals ? data.withdrawals.map(serializeWithdrawal) : null,
}
: null;
}
export function deserializeBlobAndProofs(data) {
return data
? {
blob: dataToBytes(data.blob, BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB),
proof: dataToBytes(data.proof, 48),
}
: null;
}
export function assertReqSizeLimit(blockHashesReqCount, count) {
if (blockHashesReqCount > count) {
throw new Error(`Requested blocks must not be > ${count}`);
}
return;
}
//# sourceMappingURL=types.js.map