add helpers for processing EL block header to `libnimbus_lc.a` (#5199)
To obtain the correct `transactions_root` and `withdrawals_root`, it is necessary to process execution block header. Light client updates don't contain the correct MPT roots.
This commit is contained in:
parent
2c424cac87
commit
d7afa1c78a
|
@ -1023,6 +1023,75 @@ ETH_RESULT_USE_CHECK
|
|||
int ETHExecutionPayloadHeaderGetExcessBlobGas(
|
||||
const ETHExecutionPayloadHeader *execution);
|
||||
|
||||
/**
|
||||
* Execution block header.
|
||||
*/
|
||||
typedef struct ETHExecutionBlockHeader ETHExecutionBlockHeader;
|
||||
|
||||
/**
|
||||
* Verifies that a JSON execution block header is valid and that it matches
|
||||
* the given `executionHash`.
|
||||
*
|
||||
* - The JSON-RPC `eth_getBlockByHash` with params `[executionHash, false]`
|
||||
* may be used to obtain execution block header data for a given execution
|
||||
* block hash. Pass the response's `result` property to `blockHeaderJson`.
|
||||
*
|
||||
* - The execution block header must be destroyed with
|
||||
* `ETHExecutionBlockHeaderDestroy` once no longer needed,
|
||||
* to release memory.
|
||||
*
|
||||
* @param executionHash Execution block hash.
|
||||
* @param blockHeaderJson Buffer with JSON encoded header. NULL-terminated.
|
||||
*
|
||||
* @return Pointer to an initialized execution block header - If successful.
|
||||
* @return `NULL` - If the given `blockHeaderJson` is malformed or incompatible.
|
||||
*
|
||||
* @see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash
|
||||
*/
|
||||
ETH_RESULT_USE_CHECK
|
||||
ETHExecutionBlockHeader *ETHExecutionBlockHeaderCreateFromJson(
|
||||
const ETHRoot *executionHash,
|
||||
const char *blockHeaderJson);
|
||||
|
||||
/**
|
||||
* Destroys an execution block header.
|
||||
*
|
||||
* - The execution block header must no longer be used after destruction.
|
||||
*
|
||||
* @param executionBlockHeader Execution block header.
|
||||
*/
|
||||
void ETHExecutionBlockHeaderDestroy(ETHExecutionBlockHeader *executionBlockHeader);
|
||||
|
||||
/**
|
||||
* Obtains the transactions MPT root of a given execution block header.
|
||||
*
|
||||
* - The returned value is allocated in the given execution block header.
|
||||
* It must neither be released nor written to, and the execution block
|
||||
* header must not be released while the returned value is in use.
|
||||
*
|
||||
* @param executionBlockHeader Execution block header.
|
||||
*
|
||||
* @return Execution transactions root.
|
||||
*/
|
||||
ETH_RESULT_USE_CHECK
|
||||
const ETHRoot *ETHExecutionBlockHeaderGetTransactionsRoot(
|
||||
const ETHExecutionBlockHeader *executionBlockHeader);
|
||||
|
||||
/**
|
||||
* Obtains the withdrawals MPT root of a given execution block header.
|
||||
*
|
||||
* - The returned value is allocated in the given execution block header.
|
||||
* It must neither be released nor written to, and the execution block
|
||||
* header must not be released while the returned value is in use.
|
||||
*
|
||||
* @param executionBlockHeader Execution block header.
|
||||
*
|
||||
* @return Execution withdrawals root.
|
||||
*/
|
||||
ETH_RESULT_USE_CHECK
|
||||
const ETHRoot *ETHExecutionBlockHeaderGetWithdrawalsRoot(
|
||||
const ETHExecutionBlockHeader *executionBlockHeader);
|
||||
|
||||
#if __has_feature(nullability)
|
||||
#pragma clang assume_nonnull end
|
||||
#endif
|
||||
|
|
|
@ -8,7 +8,13 @@
|
|||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[json, times],
|
||||
stew/saturation_arith,
|
||||
eth/common/eth_types_rlp,
|
||||
eth/p2p/discoveryv5/random2,
|
||||
json_rpc/jsonmarshal,
|
||||
web3/ethtypes,
|
||||
../el/el_manager,
|
||||
../spec/eth2_apis/[eth2_rest_serialization, rest_light_client_calls],
|
||||
../spec/[helpers, light_client_sync],
|
||||
../sync/light_client_sync_helpers,
|
||||
|
@ -1147,22 +1153,173 @@ func ETHExecutionPayloadHeaderGetBaseFeePerGas(
|
|||
|
||||
func ETHExecutionPayloadHeaderGetBlobGasUsed(
|
||||
execution: ptr ExecutionPayloadHeader): cint {.exported.} =
|
||||
## Obtains the data gas used of a given execution payload header.
|
||||
## Obtains the blob gas used of a given execution payload header.
|
||||
##
|
||||
## Parameters:
|
||||
## * `execution` - Execution payload header.
|
||||
##
|
||||
## Returns:
|
||||
## * Data gas used.
|
||||
## * Blob gas used.
|
||||
execution[].blob_gas_used.cint
|
||||
|
||||
func ETHExecutionPayloadHeaderGetExcessBlobGas(
|
||||
execution: ptr ExecutionPayloadHeader): cint {.exported.} =
|
||||
## Obtains the excess data gas of a given execution payload header.
|
||||
## Obtains the excess blob gas of a given execution payload header.
|
||||
##
|
||||
## Parameters:
|
||||
## * `execution` - Execution payload header.
|
||||
##
|
||||
## Returns:
|
||||
## * Excess data gas.
|
||||
## * Excess blob gas.
|
||||
execution[].excess_blob_gas.cint
|
||||
|
||||
type ETHExecutionBlockHeader = object
|
||||
txRoot: Eth2Digest
|
||||
withdrawalsRoot: Eth2Digest
|
||||
|
||||
proc ETHExecutionBlockHeaderCreateFromJson(
|
||||
executionHash: ptr Eth2Digest,
|
||||
blockHeaderJson: cstring): ptr ETHExecutionBlockHeader {.exported.} =
|
||||
## Verifies that a JSON execution block header is valid and that it matches
|
||||
## the given `executionHash`.
|
||||
##
|
||||
## * The JSON-RPC `eth_getBlockByHash` with params `[executionHash, false]`
|
||||
## may be used to obtain execution block header data for a given execution
|
||||
## block hash. Pass the response's `result` property to `blockHeaderJson`.
|
||||
##
|
||||
## * The execution block header must be destroyed with
|
||||
## `ETHExecutionBlockHeaderDestroy` once no longer needed,
|
||||
## to release memory.
|
||||
##
|
||||
## Parameters:
|
||||
## * `executionHash` - Execution block hash.
|
||||
## * `blockHeaderJson` - Buffer with JSON encoded header. NULL-terminated.
|
||||
##
|
||||
## Returns:
|
||||
## * Pointer to an initialized execution block header - If successful.
|
||||
## * `NULL` - If the given `blockHeaderJson` is malformed or incompatible.
|
||||
##
|
||||
## See:
|
||||
## * https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash
|
||||
let node =
|
||||
try:
|
||||
parseJson($blockHeaderJson)
|
||||
except Exception:
|
||||
return nil
|
||||
var bdata: BlockObject
|
||||
try:
|
||||
fromJson(node, argName = "", bdata)
|
||||
except KeyError, ValueError:
|
||||
return nil
|
||||
if bdata == nil:
|
||||
return nil
|
||||
|
||||
# Sanity check
|
||||
if bdata.hash.asEth2Digest != executionHash[]:
|
||||
return nil
|
||||
|
||||
# Check fork consistency
|
||||
static: doAssert totalSerializedFields(BlockObject) == 25,
|
||||
"Only update this number once code is adjusted to check new fields!"
|
||||
if bdata.baseFeePerGas.isNone and (
|
||||
bdata.withdrawals.isSome or bdata.withdrawalsRoot.isSome or
|
||||
bdata.blobGasUsed.isSome or bdata.excessBlobGas.isSome):
|
||||
return nil
|
||||
if bdata.withdrawalsRoot.isNone and (
|
||||
bdata.blobGasUsed.isSome or bdata.excessBlobGas.isSome):
|
||||
return nil
|
||||
if bdata.withdrawals.isSome != bdata.withdrawalsRoot.isSome:
|
||||
return nil
|
||||
if bdata.blobGasUsed.isSome != bdata.excessBlobGas.isSome:
|
||||
return nil
|
||||
|
||||
# Construct block header
|
||||
static: # `GasInt` is signed. We only use it for hashing.
|
||||
doAssert sizeof(int64) == sizeof(bdata.gasLimit)
|
||||
doAssert sizeof(int64) == sizeof(bdata.gasUsed)
|
||||
if distinctBase(bdata.timestamp) > int64.high.uint64:
|
||||
return nil
|
||||
if bdata.nonce.isNone:
|
||||
return nil
|
||||
let blockHeader = ExecutionBlockHeader(
|
||||
parentHash: bdata.parentHash.asEth2Digest,
|
||||
ommersHash: bdata.sha3Uncles.asEth2Digest,
|
||||
coinbase: distinctBase(bdata.miner),
|
||||
stateRoot: bdata.stateRoot.asEth2Digest,
|
||||
txRoot: bdata.transactionsRoot.asEth2Digest,
|
||||
receiptRoot: bdata.receiptsRoot.asEth2Digest,
|
||||
bloom: distinctBase(bdata.logsBloom),
|
||||
difficulty: bdata.difficulty,
|
||||
blockNumber: distinctBase(bdata.number).u256,
|
||||
gasLimit: cast[int64](bdata.gasLimit),
|
||||
gasUsed: cast[int64](bdata.gasUsed),
|
||||
timestamp: fromUnix(int64.saturate distinctBase(bdata.timestamp)),
|
||||
extraData: distinctBase(bdata.extraData),
|
||||
mixDigest: bdata.mixHash.asEth2Digest,
|
||||
nonce: distinctBase(bdata.nonce.get),
|
||||
fee: bdata.baseFeePerGas,
|
||||
withdrawalsRoot:
|
||||
if bdata.withdrawalsRoot.isSome:
|
||||
some(bdata.withdrawalsRoot.get.asEth2Digest)
|
||||
else:
|
||||
none(ExecutionHash256),
|
||||
blobGasUsed:
|
||||
if bdata.blobGasUsed.isSome:
|
||||
some distinctBase(bdata.blobGasUsed.get)
|
||||
else:
|
||||
none(uint64),
|
||||
excessBlobGas:
|
||||
if bdata.excessBlobGas.isSome:
|
||||
some distinctBase(bdata.excessBlobGas.get)
|
||||
else:
|
||||
none(uint64))
|
||||
if rlpHash(blockHeader) != executionHash[]:
|
||||
return nil
|
||||
|
||||
let executionBlockHeader = ETHExecutionBlockHeader.new()
|
||||
executionBlockHeader[] = ETHExecutionBlockHeader(
|
||||
txRoot: blockHeader.txRoot,
|
||||
withdrawalsRoot: blockHeader.withdrawalsRoot.get(ZERO_HASH))
|
||||
executionBlockHeader.toUnmanagedPtr()
|
||||
|
||||
proc ETHExecutionBlockHeaderDestroy(
|
||||
executionBlockHeader: ptr ETHExecutionBlockHeader) {.exported.} =
|
||||
## Destroys an execution block header.
|
||||
##
|
||||
## * The execution block header must no longer be used after destruction.
|
||||
##
|
||||
## Parameters:
|
||||
## * `executionBlockHeader` - Execution block header.
|
||||
executionBlockHeader.destroy()
|
||||
|
||||
func ETHExecutionBlockHeaderGetTransactionsRoot(
|
||||
executionBlockHeader: ptr ETHExecutionBlockHeader
|
||||
): ptr Eth2Digest {.exported.} =
|
||||
## Obtains the transactions MPT root of a given execution block header.
|
||||
##
|
||||
## * The returned value is allocated in the given execution block header.
|
||||
## It must neither be released nor written to, and the execution block
|
||||
## header must not be released while the returned value is in use.
|
||||
##
|
||||
## Parameters:
|
||||
## * `executionBlockHeader` - Execution block header.
|
||||
##
|
||||
## Returns:
|
||||
## * Execution transactions root.
|
||||
addr executionBlockHeader[].txRoot
|
||||
|
||||
func ETHExecutionBlockHeaderGetWithdrawalsRoot(
|
||||
executionBlockHeader: ptr ETHExecutionBlockHeader
|
||||
): ptr Eth2Digest {.exported.} =
|
||||
## Obtains the withdrawals MPT root of a given execution block header.
|
||||
##
|
||||
## * The returned value is allocated in the given execution block header.
|
||||
## It must neither be released nor written to, and the execution block
|
||||
## header must not be released while the returned value is in use.
|
||||
##
|
||||
## Parameters:
|
||||
## * `executionBlockHeader` - Execution block header.
|
||||
##
|
||||
## Returns:
|
||||
## * Execution withdrawals root.
|
||||
addr executionBlockHeader[].withdrawalsRoot
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -344,11 +344,41 @@ int main(void)
|
|||
int safetyThreshold = ETHLightClientStoreGetSafetyThreshold(store);
|
||||
printf("- safety_threshold: %d\n", safetyThreshold);
|
||||
|
||||
ETHLightClientHeader *copiedHeader =
|
||||
ETHLightClientHeaderCreateCopy(ETHLightClientStoreGetFinalizedHeader(store));
|
||||
|
||||
ETHLightClientStoreDestroy(store);
|
||||
ETHBeaconClockDestroy(beaconClock);
|
||||
ETHForkDigestsDestroy(forkDigests);
|
||||
ETHRootDestroy(genesisValRoot);
|
||||
ETHConsensusConfigDestroy(cfg);
|
||||
ETHRandomNumberDestroy(rng);
|
||||
|
||||
ETHRoot *copiedExecutionHash = ETHLightClientHeaderCopyExecutionHash(copiedHeader, cfg);
|
||||
void *blockHeaderJson = readEntireFile(
|
||||
__DIR__ "/test_files/executionBlockHeader.json", /* numBytes: */ NULL);
|
||||
ETHExecutionBlockHeader *executionBlockHeader =
|
||||
ETHExecutionBlockHeaderCreateFromJson(copiedExecutionHash, blockHeaderJson);
|
||||
check(executionBlockHeader);
|
||||
free(blockHeaderJson);
|
||||
ETHRootDestroy(copiedExecutionHash);
|
||||
ETHLightClientHeaderDestroy(copiedHeader);
|
||||
|
||||
printf("- finalized_header (execution block header):\n");
|
||||
|
||||
const ETHRoot *executionTransactionsRoot =
|
||||
ETHExecutionBlockHeaderGetTransactionsRoot(executionBlockHeader);
|
||||
printf(" - transactions_root: ");
|
||||
printHexString(executionTransactionsRoot, sizeof *executionTransactionsRoot);
|
||||
printf("\n");
|
||||
|
||||
const ETHRoot *executionWithdrawalsRoot =
|
||||
ETHExecutionBlockHeaderGetWithdrawalsRoot(executionBlockHeader);
|
||||
printf(" - withdrawals_root: ");
|
||||
printHexString(executionWithdrawalsRoot, sizeof *executionWithdrawalsRoot);
|
||||
printf("\n");
|
||||
|
||||
ETHExecutionBlockHeaderDestroy(executionBlockHeader);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -32,8 +32,9 @@ func toEther*(gwei: Gwei): Ether =
|
|||
(gwei div ETH_TO_GWEI).Ether
|
||||
|
||||
type
|
||||
ExecutionHash256* = eth_types.Hash256
|
||||
ExecutionWithdrawal = eth_types.Withdrawal
|
||||
ExecutionBlockHeader = eth_types.BlockHeader
|
||||
ExecutionBlockHeader* = eth_types.BlockHeader
|
||||
|
||||
FinalityCheckpoints* = object
|
||||
justified*: Checkpoint
|
||||
|
@ -383,7 +384,7 @@ func compute_timestamp_at_slot*(state: ForkyBeaconState, slot: Slot): uint64 =
|
|||
state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT
|
||||
|
||||
proc computeTransactionsTrieRoot*(
|
||||
payload: ForkyExecutionPayload): Hash256 =
|
||||
payload: ForkyExecutionPayload): ExecutionHash256 =
|
||||
if payload.transactions.len == 0:
|
||||
return EMPTY_ROOT_HASH
|
||||
|
||||
|
@ -405,7 +406,8 @@ func toExecutionWithdrawal*(
|
|||
|
||||
# https://eips.ethereum.org/EIPS/eip-4895
|
||||
proc computeWithdrawalsTrieRoot*(
|
||||
payload: capella.ExecutionPayload | deneb.ExecutionPayload): Hash256 =
|
||||
payload: capella.ExecutionPayload | deneb.ExecutionPayload
|
||||
): ExecutionHash256 =
|
||||
if payload.withdrawals.len == 0:
|
||||
return EMPTY_ROOT_HASH
|
||||
|
||||
|
@ -429,7 +431,7 @@ proc payloadToBlockHeader*(
|
|||
when typeof(payload).toFork >= ConsensusFork.Capella:
|
||||
some payload.computeWithdrawalsTrieRoot()
|
||||
else:
|
||||
none(Hash256)
|
||||
none(ExecutionHash256)
|
||||
blobGasUsed =
|
||||
when typeof(payload).toFork >= ConsensusFork.Deneb:
|
||||
some payload.blob_gas_used
|
||||
|
|
Loading…
Reference in New Issue