Skip to main content

Liteserver proof verification

Overview

This article assumes you are familiar with Merkle proof cells.
This article presents advanced examples of proof verification using liteservers. Verifying any data received from a node is essential for trustless interaction with the blockchain. However, this article covers only a portion of the trustless communication process with a liteserver. It assumes you have verified the block hash received from a liteserver or any other source. Block hash verification is a more advanced topic. It requires syncing key blocks, validating block signatures, or both. This topic will be covered in a future article. Nonetheless, even using only the examples provided here can significantly reduce the probability of accepting incorrect data from a liteserver.

Block header

Suppose we have a known block ID:
not runnable
<TL BlockIdExt [wc=-1, shard=-9223372036854775808, seqno=31220993, root_hash=51ed3b9e728e7c548b15a5e5ce988b4a74984c3f8374f3f1a52c7b1f46c26406, file_hash=d4fcdc692de1a252deb379cd25774842b733e6a96525adf82b8ffc41da667bf5] >
Request the corresponding block header from a liteserver. The liteserver’s response includes a header_proof BoC. After deserializing the BoC, we obtain the following cell:
not runnable
280[0351ED3B9E728E7C548B15A5E5CE988B4A74984C3F8374F3F1A52C7B1F46C264060016] -> {
	64[11EF55AAFFFFFF11] -> {
		640[9BC7A98700000000040101DC65010000000100FFFFFFFF000000000000000064B6C356000023D38BA64000000023D38BA64004886D00960007028101DC64FD01DC42BEC400000003000000000000002E] -> {
			608[000023D38B96FDC401DC650048A3971C46472B85C8D761060A6E7AE9F13A90CDDA815915A89597CFECB393A6B568807ADFB3C1C5EFC920907225175DB61CA384E4F8B313799E3CBB8B7B4085]
		},
		288[01018C6053C1185700C0FE4311D5CF8FA533EA0382E361A7B76D0CF299B75AC0356C0003],
		288[0101741100D622B0D5264BCDB86A14E36FC8C349B82AE49E037002EB07079EAD8B060015],
		288[01015720B6AEFCBF406209522895FAA6C0D10CC3315D90BCAF09791B19F595E86F8F0007]
	}
}
Deserialize the cell according to the block TLB scheme:
_ = {
  'global_id': -239,
  'info':
    {
      'version': 0,
      'not_master': 0,
      'after_merge': 0,
      'before_split': 0,
      'after_split': 0,
      'want_split': False,
      'want_merge': True,
      'key_block': False,
      'vert_seqno_incr': 0,
      'flags': 1,
      'seqno': 31220993,
      'vert_seqno': 1,
      'shard': {'shard_pfx_bits': 0, 'workchain_id': -1, 'shard_prefix': 0},
      'gen_utime': 1689699158,
      'start_lt': 39391488000000,
      'end_lt': 39391488000004,
      'gen_validator_list_hash_short': 2288844950,
      'gen_catchain_seqno': 459393,
      'min_ref_mc_seqno': 31220989,
      'prev_key_block_seqno': 31212222,
      'gen_software': {'version': 3, 'capabilities': 46},
      'master_ref': None,
      'prev_ref': {'type_': 'prev_blk_info', 'prev': {'end_lt': 39391487000004, 'seqno': 31220992, 'root_hash': b'H\xa3\x97\x1cFG+\x85\xc8\xd7a\x06\nnz\xe9\xf1:\x90\xcd\xda\x81Y\x15\xa8\x95\x97\xcf\xec\xb3\x93\xa6', 'file_hash': b'\xb5h\x80z\xdf\xb3\xc1\xc5\xef\xc9 \x90r%\x17]\xb6\x1c\xa3\x84\xe4\xf8\xb3\x13y\x9e<\xbb\x8b{@\x85'}},
      'prev_vert_ref': None
    },
  'value_flow': None,
  'state_update': None,
  'extra': None
}
Next, we need to verify that the 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.
assert h_proof.refs[0].get_hash(0) == block_id.root_hash
Now, we can trust all other data in the cell. Checking proof examples: Python, Kotlin, C++

Full block

In the liteserver.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
To request shard info from the liteserver for the MasterChain block mentioned above, we can execute the following code:
await client.raw_get_shard_info(master, wc=0)
The liteserver response contains the BlockIdExt of the shard block:
not runnable
<TL BlockIdExt [wc=0, shard=-9223372036854775808, seqno=36908135, root_hash=39e5cbca5bf69750b5d9897872c3a0d7a3e614e521c53e4de728fafed38dce27, file_hash=f1f0e5cdc4b8a12cf2438dcab60f4712d1dc04f3792b1d72f2500cbf640948b7] >
Shard proof BoC: The shard_descr BoC can be used if the liteserver is trusted. After deserializing the shard proof BoC, 2 root cells are obtained:
not runnable
[<Cell 280[0351ED3B9E728E7C548B15A5E5CE988B4A74984C3F8374F3F1A52C7B1F46C264060016] -> 1 refs>, <Cell 280[0332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F] -> 1 refs>]
The first root is a MasterChain block Merkle proof, which must be verified using the check_block_header function:
not runnable
280[0351ED3B9E728E7C548B15A5E5CE988B4A74984C3F8374F3F1A52C7B1F46C264060016] -> {
	64[11EF55AAFFFFFF11] -> {
		640[9BC7A98700000000040101DC65010000000100FFFFFFFF000000000000000064B6C356000023D38BA64000000023D38BA64004886D00960007028101DC64FD01DC42BEC400000003000000000000002E] -> {
			608[000023D38B96FDC401DC650048A3971C46472B85C8D761060A6E7AE9F13A90CDDA815915A89597CFECB393A6B568807ADFB3C1C5EFC920907225175DB61CA384E4F8B313799E3CBB8B7B4085]
		},
		288[01018C6053C1185700C0FE4311D5CF8FA533EA0382E361A7B76D0CF299B75AC0356C0003],
		552[0478E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E432BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F016F] -> {
			560[010378E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E46492304DFB6EF9149781871464AF686056A9627F882F60E3B24F8C944A75EBAF016F0014],
			560[010332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046DA58493CCB5DA3876129B0190F3C375E69E59C3AD9FF550BE708999DAD1F6F39016F0014]
		},
		288[01015720B6AEFCBF406209522895FAA6C0D10CC3315D90BCAF09791B19F595E86F8F0007]
	}
}
The cell
not runnable
552[0478E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E432BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F016F] -> {
    560[010378E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E46492304DFB6EF9149781871464AF686056A9627F882F60E3B24F8C944A75EBAF016F0014],
    560[010332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046DA58493CCB5DA3876129B0190F3C375E69E59C3AD9FF550BE708999DAD1F6F39016F0014]
}
Is a Merkle update of the ShardState TLB schema. The resulting new hash should be stored. After verifying that the only Merkle proof cell reference Hash_1 matches the known block hash and storing the new ShardState hash, proceed to validate the second shard proof cell:
not runnable
280[0332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F] -> {
	362[9023AFE2FFFFFF1100FFFFFFFF000000000000000001DC65010000000164B6C356000023D38BA6400401DC64FD40] -> {
		288[0101AFFE84CDD73951BCE07EEAAD120D00400295220D6F66F1163B5FA8668202D72B0001],
		288[0101FAED0DD3CA110ADA3D22980E3795D2BDF15450E9159892BBF330CDFD13A3B880016E],
		204[0000000000000000FFFFFFFFFFFFFFFF820CE9D9C3929379C820] -> {
			288[0101A5A7D24057D8643B2527709D986CDA3846ADCB3EDDC32D28EC21F69E17DBAAEF0001],
			288[0101DEAB5A5AAF79C5E24F8DCBBE51747D6804104F75F58ED5BED4702C353545C6AC0011]
		},
		342[CC26AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC23519B11EDDC69B7C] -> {
			9[D000] -> {
				878[50119963380EE3280800011E9C5CB7EE0000011E9C5CB7EE29CF2E5E52DFB4BA85AECC4BC3961D06BD1F30A7290E29F26F3947D7F69C6E713F8F872E6E25C50967921C6E55B07A38968EE0279BC958EB97928065FB204A45B88000381ABC00000000000000000EE327EB25B61A88] -> {
					74[43C9B67A721DCD650000]
				}
			},
			288[01015394592E3A3F1E3BC2D4249E993D0EC1E33CA18F49533991274EBC65276CD9A50011],
			766[0001AAA0161D000702816000047A7172DFB88800011E8B625908200EE215F71061846393A08C682E87BC3A12AFF2D246EB97A09164F5657F96F9A252EF71580FE5309A823F73F3C4C3F8AB73F5A85BBF204BFD22E68D36D0EFAB1818E7B428BC] -> {
				288[010150FCC05BD9723571B83316A5F650BE31EDB131D05FDC78D271486E5D4EF077E10019],
				288[0101E5BE728200B172CF7E2356CBA2AE1C6E2C790BE7C03CD7814C6E6FE3080B944B0011]
			},
			288[0101B20E36A3B36A4CDEE601106C642E90718B0A58DAF200753DBB3189F956B494B60001]
		}
	}
}
The Merkle proof reference in this cell has the prefix 9023AFE2, corresponding to the ShardStateUnsplit TLB schema. This reference’s Hash_1 must match the hash stored in the previous step:
"""
Here mc_block_cell is the first shard proof root and mc_state_root is the second one.
The check_block_header_proof function returns new hash of the ShardState Merkle Update.
"""

mc_state_hash = mc_state_root[0].get_hash(0)
state_hash = check_block_header_proof(mc_block_cell[0], blk.root_hash, True)

if mc_state_hash != state_hash:
    raise ProofError('mc state hashes mismatch')
  • 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.
Now, proceed to deserialize the second cell:
_ = {
    'global_id': -239,
    'shard_id': {'shard_pfx_bits': 0, 'workchain_id': -1, 'shard_prefix': 0},
    'seq_no': 31220993,
    'vert_seq_no': 1,
    'gen_utime': 1689699158,
    'gen_lt': 39391488000004,
    'min_ref_mc_seqno': 31220989,
    'out_msg_queue_info': <Cell 288[0101AFFE84CDD73951BCE07EEAAD120D00400295220D6F66F1163B5FA8668202D72B0001] -> 0 refs>,
    'before_split': 0,
    'accounts': <Cell 288[0101FAED0DD3CA110ADA3D22980E3795D2BDF15450E9159892BBF330CDFD13A3B880016E] -> 0 refs>,
    'overload_history': 0,
    'underload_history': 18446744073709551615,
    'total_balance': {'grams': 2364000148715550620, 'other': None},
    'total_validator_fees': {'grams': 0, 'other': None},
    'libraries': None,
    'master_ref': None,
    'custom': {
        'shard_hashes': {
            0: {'list': [{
                    'seq_no': 36908135,
                    'reg_mc_seqno': 31220993,
                    'start_lt': 39391487000000,
                    'end_lt': 39391487000005,
                    'root_hash': b"9\xe5\xcb\xca[\xf6\x97P\xb5\xd9\x89xr\xc3\xa0\xd7\xa3\xe6\x14\xe5!\xc5>M\xe7(\xfa\xfe\xd3\x8d\xce'",
                    'file_hash': b'\xf1\xf0\xe5\xcd\xc4\xb8\xa1,\xf2C\x8d\xca\xb6\x0fG\x12\xd1\xdc\x04\xf3y+\x1dr\xf2P\x0c\xbfd\tH\xb7',
                    'before_split': False,
                    'before_merge': False,
                    'want_split': False,
                    'want_merge': True,
                    'nx_cc_updated': False,
                    'flags': 0,
                    'next_catchain_seqno': 459607,
                    'next_validator_shard': 9223372036854775808,
                    'min_ref_mc_seqno': 31220989,
                    'gen_utime': 1689699153,
                    'split_merge_at': None,
                    'fees_collected': {'grams': 1016817575, 'other': None}, 'funds_created': {'grams': 1000000000, 'other': None}
                }]
            }
        },
        'config': {'config_addr': '5555555555555555555555555555555555555555555555555555555555555555', 'config': None},
        'flags': 1,
        'validator_info': {'validator_list_hash_short': 2862618141, 'catchain_seqno': 459393, 'nx_cc_updated': False},
        'prev_blocks': None,
        'after_key_block': True,
        'last_key_block': {'end_lt': 39382372000004, 'seqno': 31212222, 'root_hash': b'\xe2\x0c0\x8crt\x11\x8d\x05\xd0\xf7\x87BU\xfeZH\xddr\xf4\x12,\x9e\xac\xaf\xf2\xdf4J]\xee+', 'file_hash': b'\x01\xfc\xa6\x13PG\xee~x\x98\x7f\x15n~\xb5\x0bw\xe4\t\x7f\xa4\\\xd1\xa6\xda\x1d\xf5c\x03\x1c\xf6\x85'},
        'block_create_stats': {'type_': 'block_create_stats', 'counters': None},
        'global_balance': {'grams': 5089971531496870767, 'other': None}
    }
}
Since this cell is trusted, we can also trust the shard block data (ShardStateUnsplit -> custom -> shard_hashes -> 0 (shrdblk wc) -> leaf).

Account state

Next, let’s prove the state of account EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG 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

[<Cell 280[0339E5CBCA5BF69750B5D9897872C3A0D7A3E614E521C53E4DE728FAFED38DCE27001D] -> 1 refs>, <Cell 280[03F93FE5EDA41A6CE9ECB353FD589842BD3F5D5E73B846CB898525293FC742FD690219] -> 1 refs>]

The first root is a Merkle proof for the shard block, whose hash we have already verified and trusted.
not runnable
280[0339E5CBCA5BF69750B5D9897872C3A0D7A3E614E521C53E4DE728FAFED38DCE27001D] -> {
	64[11EF55AAFFFFFF11] -> {
		640[9BC7A98700000000840102332C67000000010000000000000000000000000064B6C351000023D38B96FDC0000023D38B96FDC5D41C6E3C0007035701DC64FD01DC42BEC400000003000000000000002E] -> {
			608[000023D38B69370401DC64FD2FA78EC529BCF9931E14F9D8B27EC1469290C0BAEF8256D657CE573B9679C5997431FCDA6BF2D0BE39344A9336CFE0AE9C844A88D2BD8022102E4012A760D4DB],
			608[000023D38B87BB8402332C662B4E96320F9D0AFB02E5D55B6B42C3349E33540620ECC07B399211FD56E4DE3E2555617CDDE457CD65A0AD033AAFC0C6C25DF716B04E455F49179668A46300DB]
		},
		288[0101CB54530AC857DF730E82EE239B2150528C6E5F6ED3678EAB6E1E789F0E3C7A530003],
		552[04F2AD1EDE336A68623DDABF36CB8FA405DBE70A38C453F711000F9A9F92592DB0F93FE5EDA41A6CE9ECB353FD589842BD3F5D5E73B846CB898525293FC742FD6902190219] -> {
			560[0103F2AD1EDE336A68623DDABF36CB8FA405DBE70A38C453F711000F9A9F92592DB04A4FF9713B206E420BAAEE4DD21FEBBEB426FCD9CE158DB2A56DCE9188FC313E0219001B],
			560[0103F93FE5EDA41A6CE9ECB353FD589842BD3F5D5E73B846CB898525293FC742FD6987D796744CA386906016C56921370D01F72CB004A1D7C294752AFE4446DA07BB0219001B]
		},
		288[0101D0CF03A1058C2FD6029288951051A0D82733953C1E9181A67C502CE59B180200000B]
	}
}
As in the 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:
proof_cells = Cell.from_boc(proof)
if len(proof_cells) != 2:
    raise ProofError('expected 2 root cells in account state proof')

state_cell = proof_cells[1]

state_hash = check_block_header_proof(proof_cells[0][0], shrd_blk.root_hash, True)

if state_cell[0].get_hash(0) != state_hash:
    raise ProofError('state hashes mismatch')
Once the hash matches, the 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.
_ = {
    'global_id': -239,
    'shard_id': {'shard_pfx_bits': 0, 'workchain_id': 0, 'shard_prefix': 0},
    'seq_no': 36908135,
    'vert_seq_no': 1,
    'gen_utime': 1689699153,
    'gen_lt': 39391487000005,
    'min_ref_mc_seqno': 31220989,
    'out_msg_queue_info': <Cell 288[010138F8D1C6E9F798A477D13AA26CB4D6CFE1A17949AC276B2F1E0CE037A521B9BC0001] -> 0 refs>,
    'before_split': 0,
    'accounts': (
        {
            50368879097771769677871174881221998657607998794347754829932074327482686052226: {
                'account': None,
                'last_trans_hash': b'd\x9bF\xacr\xe6\xe4\xd4\xc1);f\xd5\x8d\x9e\xd7\xa5I\x02\xbe\xef\xd9\x7f[\xffyw\xdd\x85\x99\x8b=',
                'last_trans_lt': 39330697000001,
                'cell': <Cell 320[649B46AC72E6E4D4C1293B66D58D9ED7A54902BEEFD97F5BFF7977DD85998B3D000023C564393441] -> 1 refs>
            }
        },
        [
            {'split_depth': 0, 'balance': {'grams': 5873792469, 'other': None}},
            {'split_depth': 0, 'balance': {'grams': 5991493155, 'other': None}},
            {'split_depth': 0, 'balance': {'grams': 63109456003, 'other': None}},
            {'split_depth': 0, 'balance': {'grams': 63822897549, 'other': None}},
            # ...
            {'split_depth': 0, 'balance': {'grams': 21778458402704, 'other': None}},
            {'split_depth': 0, 'balance': {'grams': 54074699968483, 'other': None}},
            {'split_depth': 0, 'balance': {'grams': 2725956214994157511, 'other': None}}
        ]
    ),
    'overload_history': 0,
    'underload_history': 18446744073709551615,
    'total_balance': {'grams': 2725956214994157511, 'other': None},
    'total_validator_fees': {'grams': 37646260890702444, 'other': None},
    'libraries': None,
    'master_ref': {'master': {'end_lt': 39391484000004, 'seqno': 31220989, 'root_hash': b'/\xa7\x8e\xc5)\xbc\xf9\x93\x1e\x14\xf9\xd8\xb2~\xc1F\x92\x90\xc0\xba\xef\x82V\xd6W\xceW;\x96y\xc5\x99', 'file_hash': b't1\xfc\xdak\xf2\xd0\xbe94J\x936\xcf\xe0\xae\x9c\x84J\x88\xd2\xbd\x80"\x10.@\x12\xa7`\xd4\xdb'}},
    'custom': None
}
We now need the 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.
_ = {
    50368879097771769677871174881221998657607998794347754829932074327482686052226: {
        'account': None,
        'last_trans_hash': b'd\x9bF\xacr\xe6\xe4\xd4\xc1);f\xd5\x8d\x9e\xd7\xa5I\x02\xbe\xef\xd9\x7f[\xffyw\xdd\x85\x99\x8b=',
        'last_trans_lt': 39330697000001,
        'cell': <Cell 320[649B46AC72E6E4D4C1293B66D58D9ED7A54902BEEFD97F5BFF7977DD85998B3D000023C564393441] -> 1 refs>
    }
}
We need to store the values of 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
320[649B46AC72E6E4D4C1293B66D58D9ED7A54902BEEFD97F5BFF7977DD85998B3D000023C564393441] -> {
	288[01018282D13BF66B9ACE1FBF5D3ABD1C59CC46D61AF1D47AF1665D3013D8F9E474880008]
}
This is a regular cell with level 1, containing a single reference — the pruned account data. We compute the 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
449[C006F5BC67986E06430961D9DF00433926A4CD92E597DDD8AA6043645AC20BD178222C859043259E0D9000008F1590E4D10D405786BD755300] -> {
	80[FF00F4A413F4BCF2C80B] -> {
		2[00] -> {
			4[40] -> {
				920[D001D0D3032171B0925F04E022D749C120925F04E002D31F218210706C7567BD22821064737472BDB0925F05E003FA403020FA4401C8CA07CBFFC9D0ED44D0810140D721F404305C810108F40A6FA131B3925F07E005D33FC8258210706C7567BA923830E30D03821064737472BA925F06E30D] -> {
					480[01FA00F40430F8276F2230500AA121BEF2E0508210706C7567831EB17080185004CB0526CF1658FA0219F400CB6917CB1F5260CB3F20C98040FB0006],
					552[5004810108F45930ED44D0810140D720C801CF16F400C9ED540172B08E23821064737472831EB17080185005CB055003CF1623FA0213CB6ACB1FCB3FC98040FB00925F03E2]
				},
				2[00] -> {
					2[00] -> {
						4[50] -> {
							242[B29DFB513420405035C87D010C00B23281F2FFF274006040423D029BE84C40],
							2[00] -> {
								97[ADCE76A26840206B90EB85FF80],
								97[AF1DF6A26840106B90EB858F80]
							}
						},
						68[B8C97ED44D0D70B1F0]
					},
					357[BD242B6F6A2684080A06B90FA0218470D4080847A4937D29910CE6903E9FF9837812801B7810148987159F3180]
				}
			},
			992[F28308D71820D31FD31FD31F02F823BBF264ED44D0D31FD31FD3FFF404D15143BAF2A15151BAF2A205F901541064F910F2A3F80024A4C8CB1F5240CB1F5230CBFF5210F400C9ED54F80F01D30721C0009F6C519320D74A96D307D402FB00E830E021C001E30021C002E30001C0039130E30D03A4C8CB1F12CB1FCBFF] -> {
				440[D207FA00D4D422F90005C8CA0715CBFFC9D077748018C8CB05CB0222CF165005FA0214CB6B12CCCCC973FB00C84014810108F451F2A702],
				448[810108D718FA00D33FC8542047810108F451F2A782106E6F746570748018C8CB05CB025006CF165004FA0214CB6A12CB1FCB3FC973FB0002],
				432[810108D718FA00D33F305224810108F459F2A782106473747270748018C8CB05CB025005CF165003FA0213CB6ACB1F12CB3FC973FB00],
				40[F400C9ED54]
			}
		}
	},
	321[000000E929A9A317C1B3226CE226D6D818BAFE82D3633AA0F06A6C677272D1F9B760FF0D0DCF56D800]
}
Compute its representation hash, and verify that it matches the trusted hash obtained from the pruned data: 8282d13bf66b9ace1fbf5d3abd1c59cc46d61af1d47af1665d3013d8f9e47488. Finally, the BoC is deserialized using the account TLB Scheme.
_ = {
    'addr': Address<EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG>,
    'storage_stat': {'used': {'cells': 22, 'bits': 5697, 'public_cells': None}, 'last_paid': 1689502130, 'due_payment': None},
    'storage': {
        'last_trans_lt': 39330697000003,
        'balance': {'grams': 5873792469, 'other': None},
        'state': {
            'type_': 'account_active',
            'state_init': {'split_depth': None, 'special': None, 'code': <Cell 80[FF00F4A413F4BCF2C80B] -> 1 refs>, 'data': <Cell 321[000000E929A9A317C1B3226CE226D6D818BAFE82D3633AA0F06A6C677272D1F9B760FF0D0DCF56D800] -> 0 refs>, 'library': None}
        }
    }
}
At this point, the account state data is verified and trusted.

Account transactions

For the liteServer.getTransactions request, we must provide the lt 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 includes ids field with the transaction list and a proof BoC. The first step is to deserialize the proof:
not runnable
280[0351ED3B9E728E7C548B15A5E5CE988B4A74984C3F8374F3F1A52C7B1F46C264060016] -> {
	64[11EF55AAFFFFFF11] -> {
		288[0101F8039FE65901BE422094ED29FA05DD4A9406708D7C54EBF7F6010F2E8A9DCBB10001],
		288[01018C6053C1185700C0FE4311D5CF8FA533EA0382E361A7B76D0CF299B75AC0356C0003],
		288[0101741100D622B0D5264BCDB86A14E36FC8C349B82AE49E037002EB07079EAD8B060015],
		545[4A33F6FD11224E018A0801116DBA929FAA60F8B9DFB39286C07FDE613D4F158E4031612597E23F312DA061732C2DB7C7C7F0BCA6295EF25D04F46FA21A055CF213A1270A80] -> {
			288[0101E057F7AA0545EF9E6BF187542A5141298303A33BA7C9CE26C71FFD9C7D2050600004],
			6[00],
			6[80] -> {
				9[4000] -> {
					605[BFB333333333333333333333333333333333333333333333333333333333333333029999999999999999999999999999999999999999999999999999999999999999CF800008F4E2E9900000] -> {
						9[5000] -> {
							288[01015EF0532AF460BCF3BECF1A94597C1EC04879E0F26BF58269D319121376AAD4730002]
						},
						9[4000] -> {
							288[0101B1E091FCB9DF53917EAA0CAE05041B3D0956242871E3CA8D6909D0AA31FF36040002]
						},
						520[7239A4AED4308E2E6AC11C880CCB29DFEE407A3E94FC1EDBDD4D29AF3B5DFEEE58A9B07203A0F457150A2BF7972DA7E2A79642DEBE792E919DE5E2FC284D2B158A]
					},
					607[BF955555555555555555555555555555555555555555555555555555555555555502AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0000008F4E2E99000C0] -> {
						288[0101924B5992DF95114196994A6D449D89E1C002CB96C14D11C4A667F843A3FAF4410002],
						520[72899B3A210DDD28D905C583FF8559BCF73D0CF0C05C11210BD7059BAB2AB453E03524184B116C9E39D9D5293179588F4B7D8F5D8192FEFE66B9FE40A71518DBC7]
					}
				}
			},
			288[01010FC5CF36DC84BC46E7175768AB3EC0F94988D454F2C496DC1AC32E638CD3C23D0005]
		}
	}
}
We must verify the block header proof to trust the cell’s contents. Once verified, we can deserialize it using the block TLB Scheme.
_ = {
    'global_id': -239,
    'info': None,
    'value_flow': None,
    'state_update': None,
    'extra': {
        'in_msg_descr': <Cell 288[0101E057F7AA0545EF9E6BF187542A5141298303A33BA7C9CE26C71FFD9C7D2050600004] -> 0 refs>,
        'out_msg_descr': ({}, [<Slice 5[00] -> 0 refs>]),
        'account_blocks': (
            {
                23158417847463239084714197001737581570653996933128112807891516801582625927987:  {
                    'account_addr': '3333333333333333333333333333333333333333333333333333333333333333',
                    'transactions': (
                        {
                            39391488000001: <Cell 288[01015EF0532AF460BCF3BECF1A94597C1EC04879E0F26BF58269D319121376AAD4730002] -> 0 refs>,
                            39391488000002: <Cell 288[0101B1E091FCB9DF53917EAA0CAE05041B3D0956242871E3CA8D6909D0AA31FF36040002] -> 0 refs>
                        },
                        [{'grams': 0, 'other': None}, {'grams': 0, 'other': None}, {'grams': 0, 'other': None}]
                    ),
                    'state_update': {'old_hash': b'9\xa4\xae\xd40\x8e.j\xc1\x1c\x88\x0c\xcb)\xdf\xee@z>\x94\xfc\x1e\xdb\xddM)\xaf;]\xfe\xeeX', 'new_hash': b'\xa9\xb0r\x03\xa0\xf4W\x15\n+\xf7\x97-\xa7\xe2\xa7\x96B\xde\xbey.\x91\x9d\xe5\xe2\xfc(M+\x15\x8a'}
                },
                38597363079105398474523661669562635951089994888546854679819194669304376546645: {
                    'account_addr': '5555555555555555555555555555555555555555555555555555555555555555',
                    'transactions': (
                        {
                            39391488000003: <Cell 288[0101924B5992DF95114196994A6D449D89E1C002CB96C14D11C4A667F843A3FAF4410002] -> 0 refs>
                        },
                    [{'grams': 0, 'other': None}]
                    ),
                    'state_update': {'old_hash': b'\x89\x9b:!\r\xdd(\xd9\x05\xc5\x83\xff\x85Y\xbc\xf7=\x0c\xf0\xc0\\\x11!\x0b\xd7\x05\x9b\xab*\xb4S\xe0', 'new_hash': b'5$\x18K\x11l\x9e9\xd9\xd5)1yX\x8fK}\x8f]\x81\x92\xfe\xfef\xb9\xfe@\xa7\x15\x18\xdb\xc7'}
                }
            },
            [{'grams': 0, 'other': None}, {'grams': 0, 'other': None}, {'grams': 0, 'other': None}]
        ),
        'rand_seed': b'\x11"N\x01\x8a\x08\x01\x11m\xba\x92\x9f\xaa`\xf8\xb9\xdf\xb3\x92\x86\xc0\x7f\xdea=O\x15\x8e@1a%',
        'created_by': b"\x97\xe2?1-\xa0as,-\xb7\xc7\xc7\xf0\xbc\xa6)^\xf2]\x04\xf4o\xa2\x1a\x05\\\xf2\x13\xa1'\n",
        'custom': None
    }
}
After successful deserialization, we should extract and remember the following field: 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.
_ = {
    23158417847463239084714197001737581570653996933128112807891516801582625927987:  {
        'account_addr': '3333333333333333333333333333333333333333333333333333333333333333',
        'transactions': (
            {
                39391488000001: <Cell 288[01015EF0532AF460BCF3BECF1A94597C1EC04879E0F26BF58269D319121376AAD4730002] -> 0 refs>,
                39391488000002: <Cell 288[0101B1E091FCB9DF53917EAA0CAE05041B3D0956242871E3CA8D6909D0AA31FF36040002] -> 0 refs>
            },
            [{'grams': 0, 'other': None}, {'grams': 0, 'other': None}, {'grams': 0, 'other': None}]
        ),
        'state_update': {'old_hash': b'9\xa4\xae\xd40\x8e.j\xc1\x1c\x88\x0c\xcb)\xdf\xee@z>\x94\xfc\x1e\xdb\xddM)\xaf;]\xfe\xeeX', 'new_hash': b'\xa9\xb0r\x03\xa0\xf4W\x15\n+\xf7\x97-\xa7\xe2\xa7\x96B\xde\xbey.\x91\x9d\xe5\xe2\xfc(M+\x15\x8a'}
    },
    38597363079105398474523661669562635951089994888546854679819194669304376546645: {
        'account_addr': '5555555555555555555555555555555555555555555555555555555555555555',
        'transactions': (
            {
                39391488000003: <Cell 288[0101924B5992DF95114196994A6D449D89E1C002CB96C14D11C4A667F843A3FAF4410002] -> 0 refs>
            },
        [{'grams': 0, 'other': None}]
        ),
        'state_update': {'old_hash': b'\x89\x9b:!\r\xdd(\xd9\x05\xc5\x83\xff\x85Y\xbc\xf7=\x0c\xf0\xc0\\\x11!\x0b\xd7\x05\x9b\xab*\xb4S\xe0', 'new_hash': b'5$\x18K\x11l\x9e9\xd9\xd5)1yX\x8fK}\x8f]\x81\x92\xfe\xfef\xb9\xfe@\xa7\x15\x18\xdb\xc7'}
    }
}
Now, let’s check the ids field:
[
    {'mode': 39, 'account': '3333333333333333333333333333333333333333333333333333333333333333', 'lt': 39391488000001, 'hash': '5ef0532af460bcf3becf1a94597c1ec04879e0f26bf58269d319121376aad473'},
    {'mode': 39, 'account': '3333333333333333333333333333333333333333333333333333333333333333', 'lt': 39391488000002, 'hash': 'b1e091fcb9df53917eaa0cae05041b3d0956242871e3ca8d6909d0aa31ff3604'},
    {'mode': 39, 'account': '5555555555555555555555555555555555555555555555555555555555555555', 'lt': 39391488000003, 'hash': '924b5992df95114196994a6d449d89e1c002cb96c14d11c4a667f843a3faf441'}
]
For each transaction, we should find its corresponding entry in the account_blocks we remembered and verify that their hashes match:
block_trs: dict = acc_block.get(int(tr['account'], 16)).transactions[0]
block_tr: Cell = block_trs.get(tr['lt'])
assert block_tr.get_hash(0) == tr['hash']
:::note In this example, checking the 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 includes state_proof and config_proof. First, deserialize the state_proof cell:
not runnable
280[0351ED3B9E728E7C548B15A5E5CE988B4A74984C3F8374F3F1A52C7B1F46C264060016] -> {
	64[11EF55AAFFFFFF11] -> {
		640[9BC7A98700000000040101DC65010000000100FFFFFFFF000000000000000064B6C356000023D38BA64000000023D38BA64004886D00960007028101DC64FD01DC42BEC400000003000000000000002E] -> {
			608[000023D38B96FDC401DC650048A3971C46472B85C8D761060A6E7AE9F13A90CDDA815915A89597CFECB393A6B568807ADFB3C1C5EFC920907225175DB61CA384E4F8B313799E3CBB8B7B4085]
		},
		288[01018C6053C1185700C0FE4311D5CF8FA533EA0382E361A7B76D0CF299B75AC0356C0003],
		552[0478E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E432BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F016F] -> {
			560[010378E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E46492304DFB6EF9149781871464AF686056A9627F882F60E3B24F8C944A75EBAF016F0014],
			560[010332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046DA58493CCB5DA3876129B0190F3C375E69E59C3AD9FF550BE708999DAD1F6F39016F0014]
		},
		288[01015720B6AEFCBF406209522895FAA6C0D10CC3315D90BCAF09791B19F595E86F8F0007]
	}
}
To do this, verify the block header proof and store the new hash from the StateUpdate. Next, deserialize the config_proof cell:
not runnable
280[0332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F] -> {
	362[9023AFE2FFFFFF1100FFFFFFFF000000000000000001DC65010000000164B6C356000023D38BA6400401DC64FD40] -> {
		288[0101AFFE84CDD73951BCE07EEAAD120D00400295220D6F66F1163B5FA8668202D72B0001],
		288[0101FAED0DD3CA110ADA3D22980E3795D2BDF15450E9159892BBF330CDFD13A3B880016E],
		204[0000000000000000FFFFFFFFFFFFFFFF820CE9D9C3929379C820] -> {
			288[0101A5A7D24057D8643B2527709D986CDA3846ADCB3EDDC32D28EC21F69E17DBAAEF0001],
			288[0101DEAB5A5AAF79C5E24F8DCBBE51747D6804104F75F58ED5BED4702C353545C6AC0011]
		},
		342[CC26AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC23519B11EDDC69B7C] -> {
			288[0101C7DAE90A1FCEAD235CACC318A048986B2E12D0F68C136845669E02C4E28F018D0002],
			2[00] -> {
				8[D8] -> {
					2[00] -> {
						2[00] -> {
							2[00] -> {
								2[00] -> {
									2[00] -> {
										2[00] -> {
											288[0101F89085ED347F5F928A0DF7B1271F906F6E1EF43D89B5912774C8B42D0E24AB120001],
											2[00] -> {
												256[3333333333333333333333333333333333333333333333333333333333333333]
											}
										},
										4[40] -> {
											256[0000000000000000000000000000000000000000000000000000000000000000]
										}
									},
									2[00] -> {
										2[00] -> {
											2[00] -> {
												256[E56754F83426F69B09267BD876AC97C44821345B7E266BD956A7BFBFB98DF35C]
											},
											2[00] -> {
												329[01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF800000008000000100]
											}
										},
										4[50] -> {
											1[80] -> {
												2[00] -> {
													83[BE000003BCB3670DC15540],
													83[BFFFFFFFBCBD1A94A20000]
												}
											}
										}
									}
								},
								2[00] -> {
									2[00] -> {
										2[00] -> {
											2[00] -> {
												104[C400000002000000000000002E]
											},
											288[0101C1F3C2ADA12BD901BBA1552C0C090CC3989649807C2B764D02548C1F664C20890007]
										},
										288[010187DADFBB3AE954E7F5472C46A729ED80AD087C5D9CEBB8D644D16DD73F88DF390009]
									},
									2[00] -> {
										288[01017CF937AF64AED1AB2CDD1435F8FF79F86E521320CC7B0CB30C9AAE81748124090002],
										2[00] -> {
											288[0101BEE8EB75C37500A75962E4FD99AFC62B3C9245948D2AC56061B0E21DDD6E9E840001],
											2[00] -> {
												128[00010000000080000000200000008000]
											}
										}
									}
								}
							},
							288[0101289F7704162F68EF3CC5B4865BD72067277E25B21514AB741396C54BD92294FA0009]
						},
						288[0101EF6962F43C1C86B216773B443F61829550DD9E956EE54EA3AC5C60E127DADD51000E]
					},
					288[0101112A0556A091DC4F72BD31FF2790783FB3238CE2AA41E1C137424D279664D7E3000A]
				},
				288[010124D21CF7AE96B1C55A1230E823DB0317CE24EC33E3BF2585C79605684304FAF20007]
			},
			766[0001AAA0161D000702816000047A7172DFB88800011E8B625908200EE215F71061846393A08C682E87BC3A12AFF2D246EB97A09164F5657F96F9A252EF71580FE5309A823F73F3C4C3F8AB73F5A85BBF204BFD22E68D36D0EFAB1818E7B428BC] -> {
				288[010150FCC05BD9723571B83316A5F650BE31EDB131D05FDC78D271486E5D4EF077E10019],
				288[0101E5BE728200B172CF7E2356CBA2AE1C6E2C790BE7C03CD7814C6E6FE3080B944B0011]
			},
			2[00] -> {
				83[BE000003BCB3670DC15540],
				83[BFFFFFFFBCBD1A94A20000]
			}
		}
	}
}
Compare the 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:
state_hash = check_block_header_proof(state_proof[0], block.root_hash, True)
if config_proof[0].get_hash(0) != state_hash:
    raise LiteClientError('hashes mismatch')
Next, deserialize the cell using the ShardStateUnsplit scheme:
_ = {
    'global_id': -239,
    'shard_id': {'shard_pfx_bits': 0, 'workchain_id': -1, 'shard_prefix': 0},
    'seq_no': 31220993,
    'vert_seq_no': 1,
    'gen_utime': 1689699158,
    'gen_lt': 39391488000004,
    'min_ref_mc_seqno': 31220989,
    'out_msg_queue_info': <Cell 288[0101AFFE84CDD73951BCE07EEAAD120D00400295220D6F66F1163B5FA8668202D72B0001] -> 0 refs>,
    'before_split': 0,
    'accounts': <Cell 288[0101FAED0DD3CA110ADA3D22980E3795D2BDF15450E9159892BBF330CDFD13A3B880016E] -> 0 refs>,
    'overload_history': 0,
    'underload_history': 18446744073709551615,
    'total_balance': {'grams': 2364000148715550620, 'other': None},
    'total_validator_fees': {'grams': 0, 'other': None},
    'libraries': None,
    'master_ref': None,
    'custom': {
        'shard_hashes': None,
        'config': {
            'config_addr': '5555555555555555555555555555555555555555555555555555555555555555',
            'config': {
                1: <Slice 256[3333333333333333333333333333333333333333333333333333333333333333] -> 0 refs>,
                4: <Slice 256[E56754F83426F69B09267BD876AC97C44821345B7E266BD956A7BFBFB98DF35C] -> 0 refs>,
                5: <Slice 329[01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF800000008000000100] -> 0 refs>,
                7: <Slice 1[80] -> 1 refs>,
                8: <Slice 104[C400000002000000000000002E] -> 0 refs>,
                15: <Slice 128[00010000000080000000200000008000] -> 0 refs>}
            },
            'flags': 1,
            'validator_info': {'validator_list_hash_short': 2862618141, 'catchain_seqno': 459393, 'nx_cc_updated': False},
            'prev_blocks': None,
            'after_key_block': True,
            'last_key_block': {'end_lt': 39382372000004, 'seqno': 31212222, 'root_hash': b'\xe2\x0c0\x8crt\x11\x8d\x05\xd0\xf7\x87BU\xfeZH\xddr\xf4\x12,\x9e\xac\xaf\xf2\xdf4J]\xee+', 'file_hash': b'\x01\xfc\xa6\x13PG\xee~x\x98\x7f\x15n~\xb5\x0bw\xe4\t\x7f\xa4\\\xd1\xa6\xda\x1d\xf5c\x03\x1c\xf6\x85'},
            'block_create_stats': {'type_': 'block_create_stats', 'counters': None},
            'global_balance': {'grams': 5089971531496870767, 'other': {239: 666666666666, 4294967279: 1000000000000}}
    }
}
Then, access the 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:
_ = {
    1: {
        'elector_addr': b'33333333333333333333333333333333',
    },
    4: {
        'dns_root_addr': b'\xe5gT\xf84&\xf6\x9b\t&{\xd8v\xac\x97\xc4H!4[~&k\xd9V\xa7\xbf\xbf\xb9\x8d\xf3\\',
    },
    5: {
        'blackhole_addr': b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff',
        'fee_burn_nom': 1,
        'fee_burn_denom': 2
    },
    7: {
        'to_mint': {'dict': {239: 666666666666, 4294967279: 1000000000000}}
    },
    8: {
        'version': 2,
        'capabilities': 46
    },
    15: {
        'validators_elected_for': 65536,
        'elections_start_before': 32768,
        'elections_end_before': 8192,
        'stake_held_for': 32768
    }
}
I