add helpers for processing transactions to `libnimbus_lc.a` (#5269)

It is useful to verify transactions data against `transactionsRoot`.
Add corresponding functionality to the light client library.
This commit is contained in:
Etan Kissling 2023-08-25 11:29:39 +02:00 committed by GitHub
parent c211a3849e
commit 991c31f42b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1484 additions and 52 deletions

View File

@ -1002,22 +1002,22 @@ const ETHUInt256 *ETHExecutionPayloadHeaderGetBaseFeePerGas(
const ETHExecutionPayloadHeader *execution);
/**
* Obtains the data gas used of a given execution payload header.
* Obtains the blob gas used of a given execution payload header.
*
* @param execution Execution payload header.
*
* @return Data gas used.
* @return Blob gas used.
*/
ETH_RESULT_USE_CHECK
int ETHExecutionPayloadHeaderGetBlobGasUsed(
const ETHExecutionPayloadHeader *execution);
/**
* Obtains the excess data gas of a given execution payload header.
* Obtains the excess blob gas of a given execution payload header.
*
* @param execution Execution payload header.
*
* @return Excess data gas.
* @return Excess blob gas.
*/
ETH_RESULT_USE_CHECK
int ETHExecutionPayloadHeaderGetExcessBlobGas(
@ -1034,7 +1034,7 @@ typedef struct ETHExecutionBlockHeader ETHExecutionBlockHeader;
*
* - 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`.
* block hash. Pass the response's `result` property as `blockHeaderJson`.
*
* - The execution block header must be destroyed with
* `ETHExecutionBlockHeaderDestroy` once no longer needed,
@ -1092,6 +1092,414 @@ ETH_RESULT_USE_CHECK
const ETHRoot *ETHExecutionBlockHeaderGetWithdrawalsRoot(
const ETHExecutionBlockHeader *executionBlockHeader);
/**
* Transaction sequence.
*/
typedef struct ETHTransactions ETHTransactions;
/**
* Verifies that JSON transactions data is valid and that it matches
* the given `transactionsRoot`.
*
* - The JSON-RPC `eth_getBlockByHash` with params `[executionHash, true]`
* may be used to obtain transactions data for a given execution
* block hash. Pass `result.transactions` as `transactionsJson`.
*
* - The transaction sequence must be destroyed with
* `ETHTransactionsDestroy` once no longer needed,
* to release memory.
*
* @param transactionsRoot Execution transactions root.
* @param transactionsJson Buffer with JSON transactions list. NULL-terminated.
*
* @return Pointer to an initialized transaction sequence - If successful.
* @return `NULL` - If the given `transactionsJson` is malformed or incompatible.
*
* @see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash
*/
ETH_RESULT_USE_CHECK
ETHTransactions *ETHTransactionsCreateFromJson(
const ETHRoot *transactionsRoot,
const char *transactionsJson);
/**
* Destroys a transaction sequence.
*
* - The transaction sequence must no longer be used after destruction.
*
* @param transactions Transaction sequence.
*/
void ETHTransactionsDestroy(ETHTransactions *transactions);
/**
* Indicates the total number of transactions in a transaction sequence.
*
* - Individual transactions may be investigated using `ETHTransactionsGet`.
*
* @param transactions Transaction sequence.
*
* @return Number of available transactions.
*/
ETH_RESULT_USE_CHECK
int ETHTransactionsGetCount(const ETHTransactions *transactions);
/**
* Transaction.
*/
typedef struct ETHTransaction ETHTransaction;
/**
* Obtains an individual transaction by sequential index
* in a transaction sequence.
*
* - The returned value is allocated in the given transaction sequence.
* It must neither be released nor written to, and the transaction
* sequence must not be released while the returned value is in use.
*
* @param transactions Transaction sequence.
* @param transactionIndex Sequential transaction index.
*
* @return Transaction.
*/
ETH_RESULT_USE_CHECK
const ETHTransaction *ETHTransactionsGet(
const ETHTransactions *transactions,
int transactionIndex);
/**
* Obtains the transaction hash of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return Transaction hash.
*/
ETH_RESULT_USE_CHECK
const ETHRoot *ETHTransactionGetHash(const ETHTransaction *transaction);
/**
* Obtains the chain ID of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return Chain ID.
*/
ETH_RESULT_USE_CHECK
const ETHUInt256 *ETHTransactionGetChainId(const ETHTransaction *transaction);
/**
* Obtains the from address of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return From execution address.
*/
ETH_RESULT_USE_CHECK
const ETHExecutionAddress *ETHTransactionGetFrom(const ETHTransaction *transaction);
/**
* Obtains the nonce of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return Nonce.
*/
ETH_RESULT_USE_CHECK
const uint64_t *ETHTransactionGetNonce(const ETHTransaction *transaction);
/**
* Obtains the max priority fee per gas of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return Max priority fee per gas.
*/
ETH_RESULT_USE_CHECK
const uint64_t *ETHTransactionGetMaxPriorityFeePerGas(const ETHTransaction *transaction);
/**
* Obtains the max fee per gas of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return Max fee per gas.
*/
ETH_RESULT_USE_CHECK
const uint64_t *ETHTransactionGetMaxFeePerGas(const ETHTransaction *transaction);
/**
* Obtains the gas of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return Gas.
*/
ETH_RESULT_USE_CHECK
const uint64_t *ETHTransactionGetGas(const ETHTransaction *transaction);
/**
* Indicates whether or not a transaction is creating a contract.
*
* @param transaction Transaction.
*
* @return Whether or not the transaction is creating a contract.
*/
ETH_RESULT_USE_CHECK
bool ETHTransactionIsCreatingContract(const ETHTransaction *transaction);
/**
* Obtains the to address of a transaction.
*
* - If the transaction is creating a contract, this function returns
* the address of the new contract.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return To execution address.
*/
ETH_RESULT_USE_CHECK
const ETHExecutionAddress *ETHTransactionGetTo(const ETHTransaction *transaction);
/**
* Obtains the value of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return Value.
*/
ETH_RESULT_USE_CHECK
const ETHUInt256 *ETHTransactionGetValue(const ETHTransaction *transaction);
/**
* Obtains the input of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
* @param[out] numBytes Length of buffer.
*
* @return Buffer with input.
*/
ETH_RESULT_USE_CHECK
const void *ETHTransactionGetInputBytes(
const ETHTransaction *transaction,
int *numBytes);
/**
* Transaction access list.
*/
typedef struct ETHAccessList ETHAccessList;
/**
* Obtains the access list of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return Transaction access list.
*/
ETH_RESULT_USE_CHECK
const ETHAccessList *ETHTransactionGetAccessList(const ETHTransaction *transaction);
/**
* Indicates the total number of access tuples in a transaction access list.
*
* - Individual access tuples may be investigated using `ETHAccessListGet`.
*
* @param accessList Transaction access list.
*
* @return Number of available access tuples.
*/
ETH_RESULT_USE_CHECK
int ETHAccessListGetCount(const ETHAccessList *accessList);
/**
* Access tuple.
*/
typedef struct ETHAccessTuple ETHAccessTuple;
/**
* Obtains an individual access tuple by sequential index
* in a transaction access list.
*
* - The returned value is allocated in the given transaction access list.
* It must neither be released nor written to, and the transaction
* access list must not be released while the returned value is in use.
*
* @param accessList Transaction access list.
* @param accessTupleIndex Sequential access tuple index.
*
* @return Access tuple.
*/
ETH_RESULT_USE_CHECK
const ETHAccessTuple *ETHAccessListGet(
const ETHAccessList *accessList,
int accessTupleIndex);
/**
* Obtains the address of an access tuple.
*
* - The returned value is allocated in the given access tuple.
* It must neither be released nor written to, and the access tuple
* must not be released while the returned value is in use.
*
* @param accessTuple Access tuple.
*
* @return Address.
*/
ETH_RESULT_USE_CHECK
const ETHExecutionAddress *ETHAccessTupleGetAddress(const ETHAccessTuple *accessTuple);
/**
* Indicates the total number of storage keys in an access tuple.
*
* - Individual storage keys may be investigated using
* `ETHAccessTupleGetStorageKey`.
*
* @param accessTuple Access tuple.
*
* @return Number of available storage keys.
*/
ETH_RESULT_USE_CHECK
int ETHAccessTupleGetNumStorageKeys(const ETHAccessTuple *accessTuple);
/**
* Obtains an individual storage key by sequential index
* in an access tuple.
*
* - The returned value is allocated in the given transaction access tuple.
* It must neither be released nor written to, and the transaction
* access tuple must not be released while the returned value is in use.
*
* @param accessTuple Access tuple.
* @param storageKeyIndex Sequential storage key index.
*
* @return Storage key.
*/
ETH_RESULT_USE_CHECK
const ETHRoot *ETHAccessTupleGetStorageKey(
const ETHAccessTuple *accessTuple,
int storageKeyIndex);
/**
* Obtains the max fee per blob gas of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
*
* @return Max fee per blob gas.
*/
ETH_RESULT_USE_CHECK
const uint64_t *ETHTransactionGetMaxFeePerBlobGas(const ETHTransaction *transaction);
/**
* Indicates the total number of blob versioned hashes of a transaction.
*
* - Individual blob versioned hashes may be investigated using
* `ETHTransactionGetBlobVersionedHash`.
*
* @param transaction Transaction.
*
* @return Number of available blob versioned hashes.
*/
ETH_RESULT_USE_CHECK
int ETHTransactionGetNumBlobVersionedHashes(const ETHTransaction *transaction);
/**
* Obtains an individual blob versioned hash by sequential index
* in a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
* @param versionedHashIndex Sequential blob versioned hash index.
*
* @return Blob versioned hash.
*/
ETH_RESULT_USE_CHECK
const ETHRoot *ETHTransactionGetBlobVersionedHash(
const ETHTransaction *transaction,
int versionedHashIndex);
/**
* Obtains the signature of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
* @param[out] numBytes Length of buffer.
*
* @return Buffer with signature.
*/
ETH_RESULT_USE_CHECK
const void *ETHTransactionGetSignatureBytes(
const ETHTransaction *transaction,
int *numBytes);
/**
* Obtains the raw byte representation of a transaction.
*
* - The returned value is allocated in the given transaction.
* It must neither be released nor written to, and the transaction
* must not be released while the returned value is in use.
*
* @param transaction Transaction.
* @param[out] numBytes Length of buffer.
*
* @return Buffer with raw transaction data.
*/
ETH_RESULT_USE_CHECK
const void *ETHTransactionGetBytes(
const ETHTransaction *transaction,
int *numBytes);
#if __has_feature(nullability)
#pragma clang assume_nonnull end
#endif

View File

@ -8,11 +8,15 @@
{.push raises: [].}
import
std/[json, times],
std/[json, sequtils, times],
stew/saturation_arith,
eth/common/eth_types_rlp,
eth/common/[eth_types_rlp, transaction],
eth/keys,
eth/p2p/discoveryv5/random2,
eth/rlp,
eth/trie/[db, hexary],
json_rpc/jsonmarshal,
secp256k1,
web3/ethtypes,
../el/el_manager,
../spec/eth2_apis/[eth2_rest_serialization, rest_light_client_calls],
@ -1174,7 +1178,7 @@ func ETHExecutionPayloadHeaderGetExcessBlobGas(
execution[].excess_blob_gas.cint
type ETHExecutionBlockHeader = object
txRoot: Eth2Digest
transactionsRoot: Eth2Digest
withdrawalsRoot: Eth2Digest
proc ETHExecutionBlockHeaderCreateFromJson(
@ -1185,7 +1189,7 @@ proc ETHExecutionBlockHeaderCreateFromJson(
##
## * 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`.
## block hash. Pass the response's `result` property as `blockHeaderJson`.
##
## * The execution block header must be destroyed with
## `ETHExecutionBlockHeaderDestroy` once no longer needed,
@ -1206,78 +1210,76 @@ proc ETHExecutionBlockHeaderCreateFromJson(
parseJson($blockHeaderJson)
except Exception:
return nil
var bdata: BlockObject
var data: BlockObject
try:
fromJson(node, argName = "", bdata)
fromJson(node, argName = "", data)
except KeyError, ValueError:
return nil
if bdata == nil:
if data == nil:
return nil
# Sanity check
if bdata.hash.asEth2Digest != executionHash[]:
if data.hash.asEth2Digest != executionHash[]:
return nil
# Check fork consistency
static: doAssert totalSerializedFields(BlockObject) == 26,
"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):
if data.baseFeePerGas.isNone and (
data.withdrawals.isSome or data.withdrawalsRoot.isSome or
data.blobGasUsed.isSome or data.excessBlobGas.isSome):
return nil
if bdata.withdrawalsRoot.isNone and (
bdata.blobGasUsed.isSome or bdata.excessBlobGas.isSome):
if data.withdrawalsRoot.isNone and (
data.blobGasUsed.isSome or data.excessBlobGas.isSome):
return nil
if bdata.withdrawals.isSome != bdata.withdrawalsRoot.isSome:
if data.withdrawals.isSome != data.withdrawalsRoot.isSome:
return nil
if bdata.blobGasUsed.isSome != bdata.excessBlobGas.isSome:
return nil
if bdata.parentBeaconBlockRoot.isSome != bdata.parentBeaconBlockRoot.isSome:
if data.blobGasUsed.isSome != data.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:
doAssert sizeof(int64) == sizeof(data.gasLimit)
doAssert sizeof(int64) == sizeof(data.gasUsed)
if distinctBase(data.timestamp) > int64.high.uint64:
return nil
if bdata.nonce.isNone:
if data.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,
parentHash: data.parentHash.asEth2Digest,
ommersHash: data.sha3Uncles.asEth2Digest,
coinbase: distinctBase(data.miner),
stateRoot: data.stateRoot.asEth2Digest,
txRoot: data.transactionsRoot.asEth2Digest,
receiptRoot: data.receiptsRoot.asEth2Digest,
bloom: distinctBase(data.logsBloom),
difficulty: data.difficulty,
blockNumber: distinctBase(data.number).u256,
gasLimit: cast[int64](data.gasLimit),
gasUsed: cast[int64](data.gasUsed),
timestamp: fromUnix(int64.saturate distinctBase(data.timestamp)),
extraData: distinctBase(data.extraData),
mixDigest: data.mixHash.asEth2Digest,
nonce: distinctBase(data.nonce.get),
fee: data.baseFeePerGas,
withdrawalsRoot:
if bdata.withdrawalsRoot.isSome:
some(bdata.withdrawalsRoot.get.asEth2Digest)
if data.withdrawalsRoot.isSome:
some(data.withdrawalsRoot.get.asEth2Digest)
else:
none(ExecutionHash256),
blobGasUsed:
if bdata.blobGasUsed.isSome:
some distinctBase(bdata.blobGasUsed.get)
if data.blobGasUsed.isSome:
some distinctBase(data.blobGasUsed.get)
else:
none(uint64),
excessBlobGas:
if bdata.excessBlobGas.isSome:
some distinctBase(bdata.excessBlobGas.get)
if data.excessBlobGas.isSome:
some distinctBase(data.excessBlobGas.get)
else:
none(uint64),
parentBeaconBlockRoot:
if bdata.parentBeaconBlockRoot.isSome:
some distinctBase(bdata.parentBeaconBlockRoot.get.asEth2Digest)
if data.parentBeaconBlockRoot.isSome:
some distinctBase(data.parentBeaconBlockRoot.get.asEth2Digest)
else:
none(ExecutionHash256))
if rlpHash(blockHeader) != executionHash[]:
@ -1285,7 +1287,7 @@ proc ETHExecutionBlockHeaderCreateFromJson(
let executionBlockHeader = ETHExecutionBlockHeader.new()
executionBlockHeader[] = ETHExecutionBlockHeader(
txRoot: blockHeader.txRoot,
transactionsRoot: blockHeader.txRoot,
withdrawalsRoot: blockHeader.withdrawalsRoot.get(ZERO_HASH))
executionBlockHeader.toUnmanagedPtr()
@ -1313,7 +1315,7 @@ func ETHExecutionBlockHeaderGetTransactionsRoot(
##
## Returns:
## * Execution transactions root.
addr executionBlockHeader[].txRoot
addr executionBlockHeader[].transactionsRoot
func ETHExecutionBlockHeaderGetWithdrawalsRoot(
executionBlockHeader: ptr ETHExecutionBlockHeader
@ -1330,3 +1332,645 @@ func ETHExecutionBlockHeaderGetWithdrawalsRoot(
## Returns:
## * Execution withdrawals root.
addr executionBlockHeader[].withdrawalsRoot
type
ETHAccessTuple = object
address: ExecutionAddress
storageKeys: seq[Eth2Digest]
DestinationType {.pure.} = enum
Regular,
Create
ETHTransaction = object
hash: Eth2Digest
chainId: UInt256
`from`: ExecutionAddress
nonce: uint64
maxPriorityFeePerGas: uint64
maxFeePerGas: uint64
gas: uint64
destinationType: DestinationType
to: ExecutionAddress
value: UInt256
input: seq[byte]
accessList: seq[ETHAccessTuple]
maxFeePerBlobGas: uint64
blobVersionedHashes: seq[Eth2Digest]
signature: seq[byte]
bytes: TypedTransaction
proc ETHTransactionsCreateFromJson(
transactionsRoot: ptr Eth2Digest,
transactionsJson: cstring): ptr seq[ETHTransaction] {.exported.} =
## Verifies that JSON transactions data is valid and that it matches
## the given `transactionsRoot`.
##
## * The JSON-RPC `eth_getBlockByHash` with params `[executionHash, true]`
## may be used to obtain transactions data for a given execution
## block hash. Pass `result.transactions` as `transactionsJson`.
##
## * The transaction sequence must be destroyed with
## `ETHTransactionsDestroy` once no longer needed,
## to release memory.
##
## Parameters:
## * `transactionsRoot` - Execution transactions root.
## * `transactionsJson` - Buffer with JSON transactions list. NULL-terminated.
##
## Returns:
## * Pointer to an initialized transaction sequence - If successful.
## * `NULL` - If the given `transactionsJson` is malformed or incompatible.
##
## See:
## * https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash
let node =
try:
parseJson($transactionsJson)
except Exception:
return nil
var datas: seq[TransactionObject]
try:
fromJson(node, argName = "", datas)
except KeyError, ValueError:
return nil
var txs = newSeqOfCap[ETHTransaction](datas.len)
for i, data in datas:
# Sanity check
if data.transactionIndex.isNone:
return nil
if distinctBase(data.transactionIndex.get) != i.uint64:
return nil
# Check fork consistency
static: doAssert totalSerializedFields(TransactionObject) == 21,
"Only update this number once code is adjusted to check new fields!"
let txType =
case data.`type`.get(0.Quantity):
of 0.Quantity:
if data.accessList.isSome or
data.maxFeePerGas.isSome or data.maxPriorityFeePerGas.isSome or
data.maxFeePerBlobGas.isSome or data.blobVersionedHashes.isSome:
return nil
TxLegacy
of 1.Quantity:
if data.chainId.isNone or data.accessList.isNone:
return nil
if data.maxFeePerGas.isSome or data.maxPriorityFeePerGas.isSome or
data.maxFeePerBlobGas.isSome or data.blobVersionedHashes.isSome:
return nil
TxEip2930
of 2.Quantity:
if data.chainId.isNone or data.accessList.isNone or
data.maxFeePerGas.isNone or data.maxPriorityFeePerGas.isNone:
return nil
if data.maxFeePerBlobGas.isSome or data.blobVersionedHashes.isSome:
return nil
TxEip1559
of 3.Quantity:
if data.to.isNone or data.chainId.isNone or data.accessList.isNone or
data.maxFeePerGas.isNone or data.maxPriorityFeePerGas.isNone or
data.maxFeePerBlobGas.isNone or data.blobVersionedHashes.isNone:
return nil
TxEip4844
else:
return nil
# Construct transaction
static:
doAssert sizeof(uint64) == sizeof(ChainId)
doAssert sizeof(int64) == sizeof(data.gasPrice)
doAssert sizeof(int64) == sizeof(data.maxPriorityFeePerGas.get)
doAssert sizeof(int64) == sizeof(data.maxFeePerBlobGas.get)
if data.chainId.get(default(UInt256)) > distinctBase(ChainId.high).u256:
return nil
if distinctBase(data.gasPrice) > int64.high.uint64:
return nil
if distinctBase(data.maxFeePerGas.get(0.Quantity)) > int64.high.uint64:
return nil
if distinctBase(data.maxPriorityFeePerGas.get(0.Quantity)) >
int64.high.uint64:
return nil
if distinctBase(data.maxFeePerBlobGas.get(0.Quantity)) >
int64.high.uint64:
return nil
if distinctBase(data.gas) > int64.high.uint64:
return nil
if data.v > int64.high.u256:
return nil
let
tx = ExecutionTransaction(
txType: txType,
chainId: data.chainId.get(default(UInt256)).truncate(uint64).ChainId,
nonce: distinctBase(data.nonce),
gasPrice: data.gasPrice.GasInt,
maxPriorityFee:
distinctBase(data.maxPriorityFeePerGas.get(data.gasPrice)).GasInt,
maxFee: distinctBase(data.maxFeePerGas.get(data.gasPrice)).GasInt,
gasLimit: distinctBase(data.gas).GasInt,
to:
if data.to.isSome:
some(distinctBase(data.to.get))
else:
none(EthAddress),
value: data.value,
payload: data.input,
accessList:
if data.accessList.isSome:
data.accessList.get.mapIt(AccessPair(
address: distinctBase(it.address),
storageKeys: it.storageKeys.mapIt(distinctBase(it))))
else:
@[],
maxFeePerBlobGas:
distinctBase(data.maxFeePerBlobGas.get(0.Quantity)).GasInt,
versionedHashes:
if data.blobVersionedHashes.isSome:
data.blobVersionedHashes.get.mapIt(
ExecutionHash256(data: distinctBase(it)))
else:
@[],
V: data.v.truncate(uint64).int64,
R: data.r,
S: data.s)
rlpBytes =
try:
rlp.encode(tx)
except RlpError:
raiseAssert "Unreachable"
hash = keccakHash(rlpBytes)
if data.hash.asEth2Digest != hash:
return nil
# Compute from execution address
var rawSig {.noinit.}: array[65, byte]
rawSig[0 ..< 32] = tx.R.toBytesBE()
rawSig[32 ..< 64] = tx.S.toBytesBE()
rawSig[64] =
if txType != TxLegacy:
tx.V.uint8
elif tx.V.isEven:
1
else:
0
let
sig = SkRecoverableSignature.fromRaw(rawSig).valueOr:
return nil
sigHash = SkMessage.fromBytes(tx.txHashNoSignature().data).valueOr:
return nil
fromPubkey = sig.recover(sigHash).valueOr:
return nil
fromAddress = keys.PublicKey(fromPubkey).toCanonicalAddress()
if distinctBase(data.`from`) != fromAddress:
return nil
# Compute to execution address
let
destinationType =
if tx.to.isSome:
DestinationType.Regular
else:
DestinationType.Create
toAddress =
case destinationType
of DestinationType.Regular:
tx.to.get
of DestinationType.Create:
var res {.noinit.}: array[20, byte]
res[0 ..< 20] = keccakHash(rlp.encodeList(fromAddress, tx.nonce))
.data.toOpenArray(12, 31)
res
txs.add ETHTransaction(
hash: keccakHash(rlpBytes),
chainId: distinctBase(tx.chainId).u256,
`from`: ExecutionAddress(data: fromAddress),
nonce: tx.nonce,
maxPriorityFeePerGas: tx.maxPriorityFee.uint64,
maxFeePerGas: tx.maxFee.uint64,
gas: tx.gasLimit.uint64,
destinationType: destinationType,
to: ExecutionAddress(data: toAddress),
value: tx.value,
input: tx.payload,
accessList: tx.accessList.mapIt(ETHAccessTuple(
address: ExecutionAddress(data: it.address),
storageKeys: it.storageKeys.mapIt(Eth2Digest(data: it)))),
maxFeePerBlobGas: tx.maxFeePerBlobGas.uint64,
blobVersionedHashes: tx.versionedHashes,
signature: @rawSig,
bytes: rlpBytes.TypedTransaction)
var tr = initHexaryTrie(newMemoryDB())
for i, transaction in txs:
try:
tr.put(rlp.encode(i), distinctBase(transaction.bytes))
except RlpError:
raiseAssert "Unreachable"
if tr.rootHash() != transactionsRoot[]:
return nil
let transactions = seq[ETHTransaction].new()
transactions[] = txs
transactions.toUnmanagedPtr()
proc ETHTransactionsDestroy(
transactions: ptr seq[ETHTransaction]) {.exported.} =
## Destroys a transaction sequence.
##
## * The transaction sequence must no longer be used after destruction.
##
## Parameters:
## * `transactions` - Transaction sequence.
transactions.destroy()
func ETHTransactionsGetCount(
transactions: ptr seq[ETHTransaction]): cint {.exported.} =
## Indicates the total number of transactions in a transaction sequence.
##
## * Individual transactions may be investigated using `ETHTransactionsGet`.
##
## Parameters:
## * `transactions` - Transaction sequence.
##
## Returns:
## * Number of available transactions.
transactions[].len.cint
func ETHTransactionsGet(
transactions: ptr seq[ETHTransaction],
transactionIndex: cint): ptr ETHTransaction {.exported.} =
## Obtains an individual transaction by sequential index
## in a transaction sequence.
##
## * The returned value is allocated in the given transaction sequence.
## It must neither be released nor written to, and the transaction
## sequence must not be released while the returned value is in use.
##
## Parameters:
## * `transactions` - Transaction sequence.
## * `transactionIndex` - Sequential transaction index.
##
## Returns:
## * Transaction.
addr transactions[][transactionIndex.int]
func ETHTransactionGetHash(
transaction: ptr ETHTransaction): ptr Eth2Digest {.exported.} =
## Obtains the transaction hash of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Transaction hash.
addr transaction[].hash
func ETHTransactionGetChainId(
transaction: ptr ETHTransaction): ptr UInt256 {.exported.} =
## Obtains the chain ID of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Chain ID.
addr transaction[].chainId
func ETHTransactionGetFrom(
transaction: ptr ETHTransaction): ptr ExecutionAddress {.exported.} =
## Obtains the from address of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * From execution address.
addr transaction[].`from`
func ETHTransactionGetNonce(
transaction: ptr ETHTransaction): ptr uint64 {.exported.} =
## Obtains the nonce of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Nonce.
addr transaction[].nonce
func ETHTransactionGetMaxPriorityFeePerGas(
transaction: ptr ETHTransaction): ptr uint64 {.exported.} =
## Obtains the max priority fee per gas of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Max priority fee per gas.
addr transaction[].maxPriorityFeePerGas
func ETHTransactionGetMaxFeePerGas(
transaction: ptr ETHTransaction): ptr uint64 {.exported.} =
## Obtains the max fee per gas of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Max fee per gas.
addr transaction[].maxFeePerGas
func ETHTransactionGetGas(
transaction: ptr ETHTransaction): ptr uint64 {.exported.} =
## Obtains the gas of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Gas.
addr transaction[].gas
func ETHTransactionIsCreatingContract(
transaction: ptr ETHTransaction): bool {.exported.} =
## Indicates whether or not a transaction is creating a contract.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Whether or not the transaction is creating a contract.
case transaction[].destinationType
of DestinationType.Regular:
false
of DestinationType.Create:
true
func ETHTransactionGetTo(
transaction: ptr ETHTransaction): ptr ExecutionAddress {.exported.} =
## Obtains the to address of a transaction.
##
## * If the transaction is creating a contract, this function returns
## the address of the new contract.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * To execution address.
addr transaction[].to
func ETHTransactionGetValue(
transaction: ptr ETHTransaction): ptr UInt256 {.exported.} =
## Obtains the value of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Value.
addr transaction[].value
func ETHTransactionGetInputBytes(
transaction: ptr ETHTransaction,
numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} =
## Obtains the input of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
## * `numBytes` [out] - Length of buffer.
##
## Returns:
## * Buffer with input.
numBytes[] = transaction[].input.len.cint
if transaction[].input.len == 0:
# https://github.com/nim-lang/Nim/issues/22389
const defaultInput: cstring = ""
return cast[ptr UncheckedArray[byte]](defaultInput)
cast[ptr UncheckedArray[byte]](addr transaction[].input[0])
func ETHTransactionGetAccessList(
transaction: ptr ETHTransaction): ptr seq[ETHAccessTuple] {.exported.} =
## Obtains the access list of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Transaction access list.
addr transaction[].accessList
func ETHAccessListGetCount(
accessList: ptr seq[ETHAccessTuple]): cint {.exported.} =
## Indicates the total number of access tuples in a transaction access list.
##
## * Individual access tuples may be investigated using `ETHAccessListGet`.
##
## Parameters:
## * `accessList` - Transaction access list.
##
## Returns:
## * Number of available access tuples.
accessList[].len.cint
func ETHAccessListGet(
accessList: ptr seq[ETHAccessTuple],
accessTupleIndex: cint): ptr ETHAccessTuple {.exported.} =
## Obtains an individual access tuple by sequential index
## in a transaction access list.
##
## * The returned value is allocated in the given transaction access list.
## It must neither be released nor written to, and the transaction
## access list must not be released while the returned value is in use.
##
## Parameters:
## * `accessList` - Transaction access list.
## * `accessTupleIndex` - Sequential access tuple index.
##
## Returns:
## * Access tuple.
addr accessList[][accessTupleIndex.int]
func ETHAccessTupleGetAddress(
accessTuple: ptr ETHAccessTuple): ptr ExecutionAddress {.exported.} =
## Obtains the address of an access tuple.
##
## * The returned value is allocated in the given access tuple.
## It must neither be released nor written to, and the access tuple
## must not be released while the returned value is in use.
##
## Parameters:
## * `accessTuple` - Access tuple.
##
## Returns:
## * Address.
addr accessTuple[].address
func ETHAccessTupleGetNumStorageKeys(
accessTuple: ptr ETHAccessTuple): cint {.exported.} =
## Indicates the total number of storage keys in an access tuple.
##
## * Individual storage keys may be investigated using
## `ETHAccessTupleGetStorageKey`.
##
## Parameters:
## * `accessTuple` - Access tuple.
##
## Returns:
## * Number of available storage keys.
accessTuple[].storageKeys.len.cint
func ETHAccessTupleGetStorageKey(
accessTuple: ptr ETHAccessTuple,
storageKeyIndex: cint): ptr Eth2Digest {.exported.} =
## Obtains an individual storage key by sequential index
## in an access tuple.
##
## * The returned value is allocated in the given transaction access tuple.
## It must neither be released nor written to, and the transaction
## access tuple must not be released while the returned value is in use.
##
## Parameters:
## * `accessTuple` - Access tuple.
## * `storageKeyIndex` - Sequential storage key index.
##
## Returns:
## * Storage key.
addr accessTuple[].storageKeys[storageKeyIndex.int]
func ETHTransactionGetMaxFeePerBlobGas(
transaction: ptr ETHTransaction): ptr uint64 {.exported.} =
## Obtains the max fee per blob gas of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Max fee per blob gas.
addr transaction[].maxFeePerBlobGas
func ETHTransactionGetNumBlobVersionedHashes(
transaction: ptr ETHTransaction): cint {.exported.} =
## Indicates the total number of blob versioned hashes of a transaction.
##
## * Individual blob versioned hashes may be investigated using
## `ETHTransactionGetBlobVersionedHash`.
##
## Parameters:
## * `transaction` - Transaction.
##
## Returns:
## * Number of available blob versioned hashes.
transaction[].blobVersionedHashes.len.cint
func ETHTransactionGetBlobVersionedHash(
transaction: ptr ETHTransaction,
versionedHashIndex: cint): ptr Eth2Digest {.exported.} =
## Obtains an individual blob versioned hash by sequential index
## in a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
## * `versionedHashIndex` - Sequential blob versioned hash index.
##
## Returns:
## * Blob versioned hash.
addr transaction[].blobVersionedHashes[versionedHashIndex.int]
func ETHTransactionGetSignatureBytes(
transaction: ptr ETHTransaction,
numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} =
## Obtains the signature of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
## * `numBytes` [out] - Length of buffer.
##
## Returns:
## * Buffer with signature.
numBytes[] = distinctBase(transaction[].signature).len.cint
if distinctBase(transaction[].signature).len == 0:
# https://github.com/nim-lang/Nim/issues/22389
const defaultBytes: cstring = ""
return cast[ptr UncheckedArray[byte]](defaultBytes)
cast[ptr UncheckedArray[byte]](addr distinctBase(transaction[].signature)[0])
func ETHTransactionGetBytes(
transaction: ptr ETHTransaction,
numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} =
## Obtains the raw byte representation of a transaction.
##
## * The returned value is allocated in the given transaction.
## It must neither be released nor written to, and the transaction
## must not be released while the returned value is in use.
##
## Parameters:
## * `transaction` - Transaction.
## * `numBytes` [out] - Length of buffer.
##
## Returns:
## * Buffer with raw transaction data.
numBytes[] = distinctBase(transaction[].bytes).len.cint
if distinctBase(transaction[].bytes).len == 0:
# https://github.com/nim-lang/Nim/issues/22389
const defaultBytes: cstring = ""
return cast[ptr UncheckedArray[byte]](defaultBytes)
cast[ptr UncheckedArray[byte]](addr distinctBase(transaction[].bytes)[0])

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,7 @@
* at your option. This file may not be copied, modified, or distributed except according to those terms.
*/
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
@ -96,6 +97,15 @@ static void printHexString(const void *bytes, int numBytes)
}
}
static void printHexStringReversed(const void *bytes, int numBytes)
{
const uint8_t *bytes_ = bytes;
printf("0x");
for (int i = numBytes - 1; i >= 0; i--) {
printf("%02x", bytes_[i]);
}
}
static void printGweiString(const ETHUInt256 *wei)
{
ETHUInt256 value;
@ -380,5 +390,125 @@ int main(void)
ETHExecutionBlockHeaderDestroy(executionBlockHeader);
ETHRoot sampleTransactionsRoot = {{
0x4e, 0x90, 0xdc, 0x06, 0xca, 0xd6, 0xa7, 0xc0,
0x57, 0xd2, 0xd7, 0x7f, 0x8f, 0x77, 0xd1, 0x45,
0xb4, 0x6f, 0xf3, 0xad, 0x9c, 0xa7, 0xe1, 0xef,
0x57, 0x11, 0x5f, 0xa8, 0xbf, 0xad, 0xfe, 0xe1,
}};
void *sampleTransactionsJson = readEntireFile(
__DIR__ "/test_files/transactions.json", /* numBytes: */ NULL);
ETHTransactions *transactions =
ETHTransactionsCreateFromJson(&sampleTransactionsRoot, sampleTransactionsJson);
check(transactions);
free(sampleTransactionsJson);
int numTransactions = ETHTransactionsGetCount(transactions);
printf("\nSample transactions:\n");
for (int transactionIndex = 0; transactionIndex < numTransactions; transactionIndex++) {
const ETHTransaction *transaction = ETHTransactionsGet(transactions, transactionIndex);
const ETHRoot *transactionHash = ETHTransactionGetHash(transaction);
printf("- ");
printHexString(transactionHash, sizeof *transactionHash);
printf("\n");
const ETHUInt256 *transactionChainId = ETHTransactionGetChainId(transaction);
printf(" - chain_id: ");
printHexStringReversed(transactionChainId, sizeof *transactionChainId);
printf("\n");
const ETHExecutionAddress *transactionFrom = ETHTransactionGetFrom(transaction);
printf(" - from: ");
printHexString(transactionFrom, sizeof *transactionFrom);
printf("\n");
const uint64_t *transactionNonce = ETHTransactionGetNonce(transaction);
printf(" - nonce: %" PRIu64 "\n", *transactionNonce);
const uint64_t *transactionMaxPriorityFeePerGas =
ETHTransactionGetMaxPriorityFeePerGas(transaction);
printf(" - max_priority_fee_per_gas: %" PRIu64 "\n", *transactionMaxPriorityFeePerGas);
const uint64_t *transactionMaxFeePerGas = ETHTransactionGetMaxFeePerGas(transaction);
printf(" - max_fee_per_gas: %" PRIu64 "\n", *transactionMaxFeePerGas);
const uint64_t *transactionGas = ETHTransactionGetGas(transaction);
printf(" - gas: %" PRIu64 "\n", *transactionGas);
bool transactionIsCreatingContract = ETHTransactionIsCreatingContract(transaction);
const ETHExecutionAddress *transactionTo = ETHTransactionGetTo(transaction);
if (transactionIsCreatingContract) {
printf(" - contract_address: ");
} else {
printf(" - to: ");
}
printHexString(transactionTo, sizeof *transactionTo);
printf("\n");
const ETHUInt256 *transactionValue = ETHTransactionGetValue(transaction);
printf(" - value: ");
printGweiString(transactionValue);
printf(" Gwei\n");
int numTransactionInputBytes;
const void *transactionInputBytes =
ETHTransactionGetInputBytes(transaction, &numTransactionInputBytes);
printf(" - input: ");
printHexString(transactionInputBytes, numTransactionInputBytes);
printf("\n");
const ETHAccessList *transactionAccessList = ETHTransactionGetAccessList(transaction);
printf(" - access_list:\n");
int numAccessTuples = ETHAccessListGetCount(transactionAccessList);
for (int accessTupleIndex = 0; accessTupleIndex < numAccessTuples; accessTupleIndex++) {
const ETHAccessTuple *accessTuple =
ETHAccessListGet(transactionAccessList, accessTupleIndex);
const ETHExecutionAddress *accessTupleAddress = ETHAccessTupleGetAddress(accessTuple);
printf(" - ");
printHexString(accessTupleAddress, sizeof *accessTupleAddress);
printf("\n");
int numStorageKeys = ETHAccessTupleGetNumStorageKeys(accessTuple);
for (int storageKeyIndex = 0; storageKeyIndex < numStorageKeys; storageKeyIndex++) {
const ETHRoot *storageKey =
ETHAccessTupleGetStorageKey(accessTuple, storageKeyIndex);
printf(" - ");
printHexString(storageKey, sizeof *storageKey);
printf("\n");
}
}
const uint64_t *transactionMaxFeePerBlobGas =
ETHTransactionGetMaxFeePerBlobGas(transaction);
printf(" - max_fee_per_blob_gas: %" PRIu64 "\n", *transactionMaxFeePerBlobGas);
printf(" - blob_versioned_hashes:\n");
int numBlobVersionedHashes = ETHTransactionGetNumBlobVersionedHashes(transaction);
for (int hashIndex = 0; hashIndex < numBlobVersionedHashes; hashIndex++) {
const ETHRoot *blobVersionedHash =
ETHTransactionGetBlobVersionedHash(transaction, hashIndex);
printf(" - ");
printHexString(blobVersionedHash, sizeof *blobVersionedHash);
printf("\n");
}
int numTransactionSignatureBytes;
const void *transactionSignatureBytes =
ETHTransactionGetSignatureBytes(transaction, &numTransactionSignatureBytes);
printf(" - signature: ");
printHexString(transactionSignatureBytes, numTransactionSignatureBytes);
printf("\n");
int numTransactionBytes;
const void *transactionBytes = ETHTransactionGetBytes(transaction, &numTransactionBytes);
printf(" - bytes: ");
printHexString(transactionBytes, numTransactionBytes);
printf("\n");
}
ETHTransactionsDestroy(transactions);
return 0;
}

View File

@ -32,6 +32,7 @@ func toEther*(gwei: Gwei): Ether =
type
ExecutionHash256* = eth_types.Hash256
ExecutionTransaction* = eth_types.Transaction
ExecutionWithdrawal = eth_types.Withdrawal
ExecutionBlockHeader* = eth_types.BlockHeader