UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

282 lines • 12.4 kB
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