Solana/Transactions/Block Hash and Timeliness

Example Transaction

To avoid readers constantly switching pages, I will paste the analyzed transaction at the beginning of each section.

{
    "signatures": [
        "3NPdLTf2Xp1XUu82VVVKgQoHfiUau3wGPTKAhbNzm8Rx5ebNQfHBzCGVsagXyQxRCeEiGr1jgr4Vn32UEAx1Aov3"
    ],
    "message": {
        "header": [
            1,
            0,
            1
        ],
        "account_keys": [
            "6ASf5EcmmEHTgDJ4X4ZT5vT6iHVJBXPg5AN5YoTCpGWt",
            "8pM1DN3RiT8vbom5u1sNryaNT1nyL8CTTW3b5PwWXRBH",
            "11111111111111111111111111111111"
        ],
        "recent_blockhash": "6vAwzjtGMrN3mJ8o7iGVDjMM46e2AnctqmjvLbqtESrx",
        "instructions": [
            {
                "program": 2,
                "account": [
                    0,
                    1
                ],
                "data": "3Bxs3zzLZLuLQEYX"
            }
        ]
    }
}

Recent Block Hash

In a Solana transaction, tx.message.recent_blockhash is a seemingly minor yet critically important field. It ensures that transactions don't remain valid indefinitely and prevents their repeated execution.

"recent_blockhash": "6vAwzjtGMrN3mJ8o7iGVDjMM46e2AnctqmjvLbqtESrx"

This value is obtained in real-time from a network node when the transaction is created. Here's the code to fetch it:

import pxsol

print(pxsol.rpc.get_latest_blockhash({})['blockhash'])
# 6vAwzjtGMrN3mJ8o7iGVDjMM46e2AnctqmjvLbqtESrx

Ncorporating the recent block hash into a transaction serves several purposes:

  1. Solana requires transactions to be submitted within a specific time window (typically a few minutes, depending on network configuration). If the recent block hash submitted with the transaction is too old, the transaction will be rejected.
  2. It prevents replay attacks. Each transaction is tied to a unique block hash, so even if the transaction content is identical, a different hash distinguishes it, avoiding duplicate execution.
  3. It anchors the transaction to a timeline, enabling the network to quickly order and verify transactions.

Exercise

Q: Try resending Ada's transaction and see what happens.

A:

import base64
import pxsol

tx = bytearray([
    0x01, 0x76, 0x7a, 0xe2, 0x66, 0x60, 0xc1, 0x42, 0x94, 0x1a, 0x59, 0x61, 0xf6, 0xde, 0xc7, 0x23,
    0x7c, 0xae, 0x73, 0x3e, 0xdf, 0xe6, 0x51, 0x7c, 0x37, 0xfb, 0xb8, 0x48, 0x1f, 0x46, 0xbb, 0xb5,
    0x3c, 0xe3, 0x00, 0xe7, 0x14, 0xb4, 0x78, 0x40, 0x14, 0x2c, 0x93, 0xa4, 0xe6, 0x60, 0x0c, 0x50,
    0xfd, 0xa9, 0x75, 0x60, 0xab, 0x64, 0x1d, 0xb0, 0xce, 0x19, 0x55, 0x9b, 0x25, 0x1d, 0x66, 0xdf,
    0x04, 0x01, 0x00, 0x01, 0x03, 0x4c, 0xb5, 0xab, 0xf6, 0xad, 0x79, 0xfb, 0xf5, 0xab, 0xbc, 0xca,
    0xfc, 0xc2, 0x69, 0xd8, 0x5c, 0xd2, 0x65, 0x1e, 0xd4, 0xb8, 0x85, 0xb5, 0x86, 0x9f, 0x24, 0x1a,
    0xed, 0xf0, 0xa5, 0xba, 0x29, 0x74, 0x22, 0xb9, 0x88, 0x75, 0x98, 0x06, 0x8e, 0x32, 0xc4, 0x44,
    0x8a, 0x94, 0x9a, 0xdb, 0x29, 0x0d, 0x0f, 0x4e, 0x35, 0xb9, 0xe0, 0x1b, 0x0e, 0xe5, 0xf1, 0xa1,
    0xe6, 0x00, 0xfe, 0x26, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xe9, 0x77, 0x4a, 0x3c, 0xad, 0x5c, 0x33, 0xf1, 0xfb, 0x6b,
    0x37, 0xa0, 0x3d, 0x4f, 0x00, 0x9a, 0x31, 0x09, 0x81, 0x18, 0xd2, 0xce, 0xae, 0xbf, 0x43, 0x0a,
    0xf3, 0x01, 0xad, 0x25, 0x0d, 0x01, 0x02, 0x02, 0x00, 0x01, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00,
    0xca, 0x9a, 0x3b, 0x00, 0x00, 0x00, 0x00,
])

pxsol.rpc.send_transaction(base64.b64encode(tx).decode(), {})

# Exception: {
#     'code': -32002,
#     'message': 'Transaction simulation failed: This transaction has already been processed',
#     'data': {
#         'accounts': None,
#         'err': 'AlreadyProcessed',
#         'innerInstructions': None,
#         'logs': [],
#         'replacementBlockhash': None,
#         'returnData': None,
#         'unitsConsumed': 0,
#     }
# }