Liteserver proof verification
Overview
This article assumes you are familiar with Merkle proof cells.
Block header
Suppose we have a known block ID:not runnable
header_proof
BoC.
After deserializing the BoC, we obtain the following cell:
not runnable
seqno
in the deserialized block matches the seqno
of the block we know. After that, we compute hash_1
for the single Merkle proof reference and compare it to our block hash.
Full block
In theliteserver.getBlock
method, proof verification is performed in Block header. However, it includes full cells instead of pruned branches for the value flow, state update, and block extra schemas.
Shard block
Shard proofs verify that a shard reference is stored in the MasterChain block provided to the liteserver. These proofs are necessary when calling the following methods:liteServer.getShardInfo
liteServer.getAccountState
liteServer.runSmcMethod
BlockIdExt
of the shard block:
not runnable
shard_descr
BoC can be used if the liteserver is trusted.
After deserializing the shard proof BoC, 2 root cells are obtained:
not runnable
check_block_header
function:
not runnable
not runnable
Hash_1
matches the known block hash and storing the new ShardState hash, proceed to validate the second shard proof
cell:
not runnable
9023AFE2
, corresponding to the ShardStateUnsplit TLB schema. This reference’s Hash_1
must match the hash stored in the previous step:
- Why? — We can trust the associated cell data because the block header proof is verified. Therefore, the new hash from the ShardState Merkle update is considered trusted. We must confirm the hashes match to validate the second cell’s data.
ShardStateUnsplit
-> custom
-> shard_hashes
-> 0 (shrdblk wc)
-> leaf
).
Account state
Next, let’s prove the state of accountEQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG
for the same MasterChain block referenced at the beginning of this article.
The liteserver response includes the MasterChain block ID, which must match the one sent to the liteserver, the shard block ID, and a shard_proof
BoC, which must be verified as described above, along with a proof
BoC and a state
BoC.
After verifying the shard_proof
, the proof
and state
cells must be deserialized. The proof
cell must contain exactly 2 root cells:
not runnable
root
is a Merkle proof for the shard block, whose hash we have already verified and trusted.
not runnable
shard_proof
verification, the check_block_header
function must validate the block cell and record the new StateUpdate
hash.
Next, deserialize the second root (state_cell
) and verify that its Hash_1
matches the previously recorded hash:
state_cell
can be trusted. Its structure is as follows:
The only Merkle proof reference has the prefix 9023AFE2
, which corresponds to the ShardStateUnsplit TLB schema.
Therefore, it must be deserialized accordingly.
account
field, which is of type ShardAccounts.
ShardAccounts
is a HashmapAugE, where the key is the address hash_part
, the value is type ShardAccount
, and the extra field is type DeepBalanceInfo
.
Parsing the address EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG
, we obtain the hash_part
equal to:
50368879097771769677871174881221998657607998794347754829932074327482686052226
We then use this key to retrieve the corresponding value from the Hashmap.
last_trans_hash
and last_trans_lt
, as they can be used later to retrieve the account’s transactions. Let’s examine the entire cell containing this data.
not runnable
Hash_1
of this pruned branch, which serves as the trusted account state hash:
8282d13bf66b9ace1fbf5d3abd1c59cc46d61af1d47af1665d3013d8f9e47488
.
The next step is to deserialize the state
BoC:
not runnable
8282d13bf66b9ace1fbf5d3abd1c59cc46d61af1d47af1665d3013d8f9e47488
.
Finally, the BoC is deserialized using the account TLB Scheme.
Account transactions
For the liteServer.getTransactions request, we must provide thelt
and hash
of the transaction to start from.
If we want to retrieve an account’s latest transactions, we can extract the lt
and hash
from the trusted ShardAccount
, as described above.
When the liteserver returns the transactions, it provides a BoC containing the requested number of transaction roots. Each root is a cell that should be deserialized using the transaction TLB scheme.
For the first transaction cell, verify that its hash matches the last_trans_hash
from the account state. Then, store the prev_trans_hash
field, compare it to the hash of the second transaction root, and continue the verification process in this manner.
Block transactions
Next, we query the liteserver for the list of transactions belonging to the block we started with at the beginning of this article. The liteserver response includesids
field with the transaction list and a proof
BoC.
The first step is to deserialize the proof
:
not runnable
block
-> extra
-> account_blocks
.
This field has the type ShardAccountBlocks, which is a HashmapAugE
, where:
- The key is the address
hash_part
. - The value is of type AccountBlock.
- The extra data is a
CurrencyCollection
.
ids
field:
account_blocks
we remembered and verify that their hashes match:
ids
field was optional — we could have retrieved all transactions directly from the account blocks.
However, verifying the transaction proofs becomes essential when using the liteServer.listBlockTransactionsExt method, and you must compare transaction hashes.
:::
Config
Request the following config params from the liteserver: 1, 4, 5, 7, 8, and 15 for liteServer.getConfigAll, where all parameters are returned, and the proof verification remains the same. The response includesstate_proof
and config_proof
.
First, deserialize the state_proof
cell:
not runnable
StateUpdate
.
Next, deserialize the config_proof
cell:
not runnable
Hash_1
from the Merkle proof (reference only) with the hash obtained from the check_block_header
function above. If they match, the cell can be trusted:
ShardStateUnsplit
scheme:
ShardStateUnsplit
-> custom
-> config
-> config
field, a Hashmap where the key is a ConfigParam
number and the value is a cell containing the parameter value.
After deserializing all parameters, we obtain: