Transactions and Blocks

How transactions and blocks work in Blockchain Development Kit (BDK).

This subchapter explains the structure of blocks, transactions, and how both are properly parsed within BDK and AppLayer.

How transactions are parsed

No matter the type, there are two ways a transaction can be parsed from a bytes string:

  • Directly from RLP

    • Requires deriving the sender (from) address and a validity check using secp256k1

    • Not included in a block, which means it's a new transaction coming from the network

    • Equivalent to Ethereum's rawTransaction

  • Directly from the database

    • Considered trustworthy since it already went through the process above

    • Is included in a block, therefore it's part of the blockchain

Transaction structure

Depending on the type, a transaction will contain the following data fields once it's properly parsed:

  • (TxBlock) to - receiver address (the one that received the funds)

  • (Both) from - sender address (the one that sent the funds)

  • (Both) data - arbitrary data field (used in contracts)

  • (Both) chainId - unique blockchain ID where the transaction was made

  • (TxValidator) nHeight - height of the block where the transaction was included

  • (TxBlock) nonce - number of the transaction made from the sender address - always starts at 0, so a nonce of 4 means this is the fifth transaction from the address

  • (TxBlock) value - transaction value in its lowest unit

    • e.g. "satoshi" for BTC, "Wei" for ETH, etc. - "100000000" satoshis would be 1 BTC, "5000000000" Wei would be 0.000000005 ETH (or 5 gwei), so on and so forth

    • Since we're using an Ethereum-based format, we commonly refer to its terminology, so "value" is in "Wei"

  • (TxBlock) maxPriorityFeePerGas - value paid as an incentive for miners to include the transaction in the block, as per EIP-1559

    • This is implemented but actively ignored since we don't have "miners", therefore only maxFeePerGas is counted

  • (TxBlock) maxFeePerGas - value paid by every unit of gas, in Gwei (e.g. "15" = 15000000000 Wei)

    • The total transaction fee is calculated as (gasLimit * maxFeePerGas) - e.g. 21000 * 15000000000 = 0.000315 ETH

  • (TxBlock) gasLimit - maximum limit of gas units that the transaction will use, in Wei (e.g. "21000")

    • If the transaction uses more than this limit, it will automatically fail - the original transaction value won't be spent, but what was already spent as gas is lost

  • (Both) ECDSA signature (Elliptic Curve Digital Signature Algorithm) for validating transaction integrity, split in three:

    • r - first half of the ECDSA signature (32 hex bytes)

    • s - second half of the ECDSA signature (32 hex bytes)

    • v - recovery ID (1 hex byte)

  • (Both) hash - The transaction's own hash (including the signature), stored as a cache for performance reasons

Block structure

A block will contain the following conceptual structure:

  • Validator signature (the one responsible for creating and signing the block)

  • Validator public key (for verifying the signature)

  • The block's header, which is made of:

    • Previous block hash (a hash of the previous block's header, signed by the Validator)

    • "Randomness" (a random seed generated by RandomGen during the previous block's creation)

    • Validator Tx Merkle Tree (to verify the integrity of Validator transactions)

    • Block Tx Merkle Tree (to verify the integrity of Block transactions)

    • UNIX timestamp of the block, in microseconds

    • Block height (commonly known as nHeight)

  • List of Validator transactions

  • List of block transactions

  • A cached hash of the block's header

  • The total size of the block, in bytes

In practice, a block is simply a serialized bytes string, transmitted through the network and stored in the blockchain, so it's up to the code logic to properly parse it. Another way to see the block in a more "raw" format would be ("bytes" are in hex format, e.g. 0xFF = 1 byte):

Outside header:
  65 bytes - Validator signature
  65 bytes - Validator public key
Header (144 bytes):
  32 bytes - Previous block hash
  32 bytes - "Randomness"
  32 bytes - Validator Tx Merkle Root
  32 bytes - Block Tx Merkle Root
  8 bytes - Timestamp (in microseconds)
  8 bytes - Block height
Content:
  8 bytes - Offset of Validator transactions array
  [
    4 bytes - Block transaction size
    N bytes - Block transaction
    ...
  ]
  [
    4 bytes - Validator transaction size
    N bytes - Validator transaction
    ...
  ]

Blocks transmitted through the network must have the Validator signature, which is part of the block but is outside its structure. This is intentional, so the block header can be hashed and signed on its own without hashing the signature along with it.

Raw block analysis

A "raw" finalized block would look something like this:

5c37d504e9415c3b75afaa3ad24484382274bba31f10dcd268e554785d5b807500000181810eb6507a8b54dfbfe9f21d00000001000000aff8ad81be850c92a69c0082e18c94d586e7f844cea2f87f50152665bcbc2c279d8d7080b844a9059cbb00000000000000000000000026548521f99d0709f615aa0f766a7df60f99250b00000000000000000000000000000000000000000000002086ac351052600000830150f7a07e16328b7f3823abeb13d0cab11cdffaf967c9b2eaf3757c42606d6f2ecd8ce6a040684c94b289cdda2282...

Where:

  • Validator signature = 5c37d504e9415c3b75afaa3ad24484382274bba31f10dcd268e554785d5b807500000181810eb6507a8b54dfbfe9f21d00000001000000aff8ad81be850c92a69c

  • Previous block hash = 0082e18c94d586e7f844cea2f87f50152665bcbc2c279d8d7080b844a9059cbb

  • "Randomness" = 00000000000000000000000026548521f99d0709f615aa0f766a7df60f99250b

  • Validator Merkle Tree = 00000000000000000000000000000000000000000000002086ac351052600000

  • Block Merkle Tree = 830150f7a07e16328b7f3823abeb13d0cab11cdffaf967c9b2eaf3757c42606d

  • Timestamp = 6f2ecd8ce6a04068

  • Block height = 4c94b289cdda2282

  • Rest of the content = ...

Last updated