|
| 1 | +from copy import deepcopy |
| 2 | +from random import random, randint, choice |
| 3 | + |
| 4 | +import pytest |
| 5 | + |
| 6 | +from plenum.common.util import randomString |
| 7 | +from state.db.persistent_db import PersistentDB |
| 8 | +from state.trie.pruning_trie import Trie, rlp_encode |
| 9 | +from storage.kv_in_memory import KeyValueStorageInMemory |
| 10 | + |
| 11 | + |
| 12 | +def gen_test_data(num_keys, max_key_size=64, max_val_size=256): |
| 13 | + return {randomString(max_key_size).encode(): |
| 14 | + rlp_encode([randomString(max_val_size)]) for i in range(num_keys)} |
| 15 | + |
| 16 | + |
| 17 | +def test_verify_proof(): |
| 18 | + node_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 19 | + client_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 20 | + |
| 21 | + node_trie.update('k1'.encode(), rlp_encode(['v1'])) |
| 22 | + node_trie.update('k2'.encode(), rlp_encode(['v2'])) |
| 23 | + |
| 24 | + root_hash_0 = node_trie.root_hash |
| 25 | + p0 = node_trie.produce_spv_proof('k2'.encode()) |
| 26 | + p0.append(deepcopy(node_trie.root_node)) |
| 27 | + p00 = deepcopy(p0) |
| 28 | + assert client_trie.verify_spv_proof(root_hash_0, 'k2'.encode(), |
| 29 | + rlp_encode(['v2']), p0) |
| 30 | + assert p00 == p0 |
| 31 | + |
| 32 | + node_trie.update('k3'.encode(), rlp_encode(['v3'])) |
| 33 | + node_trie.update('k4'.encode(), rlp_encode(['v4'])) |
| 34 | + node_trie.update('x1'.encode(), rlp_encode(['y1'])) |
| 35 | + node_trie.update('x2'.encode(), rlp_encode(['y2'])) |
| 36 | + root_hash_1 = node_trie.root_hash |
| 37 | + |
| 38 | + # Generate 1 proof and then verify that proof |
| 39 | + p1 = node_trie.produce_spv_proof('k1'.encode()) |
| 40 | + p1.append(node_trie.root_node) |
| 41 | + assert client_trie.verify_spv_proof(root_hash_1, 'k1'.encode(), |
| 42 | + rlp_encode(['v1']), p1) |
| 43 | + |
| 44 | + p2 = node_trie.produce_spv_proof('x2'.encode()) |
| 45 | + p2.append(node_trie.root_node) |
| 46 | + assert client_trie.verify_spv_proof(root_hash_1, 'x2'.encode(), |
| 47 | + rlp_encode(['y2']), p2) |
| 48 | + |
| 49 | + # Generate more than 1 proof and then verify all proofs |
| 50 | + |
| 51 | + p3 = node_trie.produce_spv_proof('k3'.encode()) |
| 52 | + p3.append(node_trie.root_node) |
| 53 | + |
| 54 | + p4 = node_trie.produce_spv_proof('x1'.encode()) |
| 55 | + p4.append(node_trie.root_node) |
| 56 | + |
| 57 | + assert client_trie.verify_spv_proof(root_hash_1, 'k3'.encode(), |
| 58 | + rlp_encode(['v3']), p3) |
| 59 | + assert client_trie.verify_spv_proof(root_hash_1, 'x1'.encode(), |
| 60 | + rlp_encode(['y1']), p4) |
| 61 | + |
| 62 | + # Proof is correct but value is different |
| 63 | + assert not client_trie.verify_spv_proof(root_hash_1, 'x1'.encode(), |
| 64 | + rlp_encode(['y99']), p4) |
| 65 | + |
| 66 | + # Verify same proof again |
| 67 | + assert client_trie.verify_spv_proof(root_hash_1, 'k3'.encode(), |
| 68 | + rlp_encode(['v3']), p3) |
| 69 | + |
| 70 | + assert p00 == p0 |
| 71 | + assert client_trie.verify_spv_proof(root_hash_0, 'k2'.encode(), |
| 72 | + rlp_encode(['v2']), p0) |
| 73 | + |
| 74 | + |
| 75 | +def test_verify_proof_generated_using_helper(): |
| 76 | + node_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 77 | + client_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 78 | + |
| 79 | + node_trie.update('k1'.encode(), rlp_encode(['v1'])) |
| 80 | + node_trie.update('k2'.encode(), rlp_encode(['v2'])) |
| 81 | + |
| 82 | + root_hash_0 = node_trie.root_hash |
| 83 | + p0 = node_trie.generate_state_proof('k2'.encode()) |
| 84 | + assert client_trie.verify_spv_proof(root_hash_0, 'k2'.encode(), |
| 85 | + rlp_encode(['v2']), p0) |
| 86 | + |
| 87 | + node_trie.update('k3'.encode(), rlp_encode(['v3'])) |
| 88 | + node_trie.update('k4'.encode(), rlp_encode(['v4'])) |
| 89 | + node_trie.update('x1'.encode(), rlp_encode(['y1'])) |
| 90 | + node_trie.update('x2'.encode(), rlp_encode(['y2'])) |
| 91 | + root_hash_1 = node_trie.root_hash |
| 92 | + |
| 93 | + # Generate 1 proof and then verify that proof |
| 94 | + p1 = node_trie.generate_state_proof('k1'.encode()) |
| 95 | + assert client_trie.verify_spv_proof(root_hash_1, 'k1'.encode(), |
| 96 | + rlp_encode(['v1']), p1) |
| 97 | + |
| 98 | + p2 = node_trie.generate_state_proof('x2'.encode()) |
| 99 | + assert client_trie.verify_spv_proof(root_hash_1, 'x2'.encode(), |
| 100 | + rlp_encode(['y2']), p2) |
| 101 | + |
| 102 | + # Generate more than 1 proof and then verify all proofs |
| 103 | + |
| 104 | + p3 = node_trie.generate_state_proof('k3'.encode()) |
| 105 | + |
| 106 | + p4 = node_trie.generate_state_proof('x1'.encode()) |
| 107 | + |
| 108 | + assert client_trie.verify_spv_proof(root_hash_1, 'k3'.encode(), |
| 109 | + rlp_encode(['v3']), p3) |
| 110 | + assert client_trie.verify_spv_proof(root_hash_1, 'x1'.encode(), |
| 111 | + rlp_encode(['y1']), p4) |
| 112 | + |
| 113 | + # Proof is correct but value is different |
| 114 | + assert not client_trie.verify_spv_proof(root_hash_1, 'x1'.encode(), |
| 115 | + rlp_encode(['y99']), p4) |
| 116 | + |
| 117 | + # Verify same proof again |
| 118 | + assert client_trie.verify_spv_proof(root_hash_1, 'k3'.encode(), |
| 119 | + rlp_encode(['v3']), p3) |
| 120 | + |
| 121 | + assert client_trie.verify_spv_proof(root_hash_0, 'k2'.encode(), |
| 122 | + rlp_encode(['v2']), p0) |
| 123 | + |
| 124 | + # Proof generated using non-existent key fails verification |
| 125 | + p5 = node_trie.generate_state_proof('x909'.encode()) |
| 126 | + assert not client_trie.verify_spv_proof(root_hash_1, 'x909'.encode(), |
| 127 | + rlp_encode(['y909']), p5) |
| 128 | + |
| 129 | + |
| 130 | +def test_verify_proof_random_data(): |
| 131 | + """ |
| 132 | + Add some key value pairs in trie. Generate and verify proof for them. |
| 133 | + :return: |
| 134 | + """ |
| 135 | + num_keys = 100 |
| 136 | + test_data = gen_test_data(num_keys) |
| 137 | + partitions = 4 |
| 138 | + partition_size = num_keys // partitions |
| 139 | + keys = [list(list(test_data.keys())[i:i + partition_size]) |
| 140 | + for i in range(0, len(test_data), partition_size)] |
| 141 | + |
| 142 | + node_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 143 | + client_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 144 | + root_hashes = [] |
| 145 | + proofs = [] |
| 146 | + for i in range(0, partitions): |
| 147 | + for k in keys[i]: |
| 148 | + node_trie.update(k, test_data[k]) |
| 149 | + |
| 150 | + root_hashes.append(node_trie.root_hash) |
| 151 | + proofs.append({k: node_trie.generate_state_proof(k) for k in keys[i]}) |
| 152 | + assert all([client_trie.verify_spv_proof(root_hashes[i], |
| 153 | + k, test_data[k], |
| 154 | + proofs[i][k]) for k in keys[i]]) |
| 155 | + |
| 156 | + # Pick any keys from any partition and verify the already generated proof |
| 157 | + for _ in range(400): |
| 158 | + p = randint(0, partitions - 1) |
| 159 | + key = choice(keys[p]) |
| 160 | + assert client_trie.verify_spv_proof(root_hashes[p], key, |
| 161 | + test_data[key], proofs[p][key]) |
| 162 | + |
| 163 | + # Pick any key randomly, generate new proof corresponding to current root |
| 164 | + # and verify proof |
| 165 | + all_keys = [k for i in keys for k in i] |
| 166 | + root_hash = node_trie.root_hash |
| 167 | + for _ in range(400): |
| 168 | + key = choice(all_keys) |
| 169 | + proof = node_trie.generate_state_proof(key) |
| 170 | + assert client_trie.verify_spv_proof(root_hash, key, |
| 171 | + test_data[key], proof) |
| 172 | + |
| 173 | + |
| 174 | +def test_proof_serialize_deserialize(): |
| 175 | + node_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 176 | + client_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 177 | + keys = {k.encode(): [rlp_encode([v]), ] for k, v in |
| 178 | + [('k1', 'v1'), ('k2', 'v2'), ('k35', 'v55'), ('k70', 'v99')]} |
| 179 | + |
| 180 | + for k, v in keys.items(): |
| 181 | + node_trie.update(k, v[0]) |
| 182 | + |
| 183 | + for k in keys: |
| 184 | + keys[k].append(node_trie.generate_state_proof(k, serialize=True)) |
| 185 | + |
| 186 | + for k in keys: |
| 187 | + prf = keys[k][1] |
| 188 | + assert isinstance(prf, bytes) |
| 189 | + assert client_trie.verify_spv_proof(node_trie.root_hash, k, keys[k][0], |
| 190 | + prf, serialized=True) |
| 191 | + |
| 192 | + |
| 193 | +@pytest.mark.skip(reason='Need to check if need/possible to build proof for an ' |
| 194 | + 'arbitrary old root') |
| 195 | +def test_proof_specific_root(): |
| 196 | + node_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 197 | + client_trie = Trie(PersistentDB(KeyValueStorageInMemory())) |
| 198 | + |
| 199 | + node_trie.update('k1'.encode(), rlp_encode(['v1'])) |
| 200 | + node_trie.update('k2'.encode(), rlp_encode(['v2'])) |
| 201 | + node_trie.update('x3'.encode(), rlp_encode(['v3'])) |
| 202 | + |
| 203 | + root_hash_0 = node_trie.root_hash |
| 204 | + root_node_0 = node_trie.root_node |
| 205 | + |
| 206 | + node_trie.update('x4'.encode(), rlp_encode(['v5'])) |
| 207 | + node_trie.update('y99'.encode(), rlp_encode(['v6'])) |
| 208 | + node_trie.update('x5'.encode(), rlp_encode(['v7'])) |
| 209 | + |
| 210 | + # root_hash_1 = node_trie.root_hash |
| 211 | + # root_node_1 = node_trie.root_node |
| 212 | + |
| 213 | + k, v = 'k1'.encode(), rlp_encode(['v1']) |
| 214 | + old_root_proof = node_trie.generate_state_proof(k, root=root_node_0) |
| 215 | + assert client_trie.verify_spv_proof(root_hash_0, k, v, old_root_proof) |
0 commit comments