github.com/ethereum/go-ethereum@v1.16.1/triedb/pathdb/history_test.go (about) 1 // Copyright 2023 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package pathdb 18 19 import ( 20 "bytes" 21 "fmt" 22 "reflect" 23 "testing" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/core/rawdb" 27 "github.com/ethereum/go-ethereum/core/types" 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/internal/testrand" 30 "github.com/ethereum/go-ethereum/rlp" 31 ) 32 33 // randomStateSet generates a random state change set. 34 func randomStateSet(n int) (map[common.Address][]byte, map[common.Address]map[common.Hash][]byte) { 35 var ( 36 accounts = make(map[common.Address][]byte) 37 storages = make(map[common.Address]map[common.Hash][]byte) 38 ) 39 for i := 0; i < n; i++ { 40 addr := testrand.Address() 41 storages[addr] = make(map[common.Hash][]byte) 42 for j := 0; j < 3; j++ { 43 v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testrand.Bytes(32))) 44 storages[addr][testrand.Hash()] = v 45 } 46 account := generateAccount(types.EmptyRootHash) 47 accounts[addr] = types.SlimAccountRLP(account) 48 } 49 return accounts, storages 50 } 51 52 func makeHistory(rawStorageKey bool) *history { 53 accounts, storages := randomStateSet(3) 54 return newHistory(testrand.Hash(), types.EmptyRootHash, 0, accounts, storages, rawStorageKey) 55 } 56 57 func makeHistories(n int) []*history { 58 var ( 59 parent = types.EmptyRootHash 60 result []*history 61 ) 62 for i := 0; i < n; i++ { 63 root := testrand.Hash() 64 accounts, storages := randomStateSet(3) 65 h := newHistory(root, parent, uint64(i), accounts, storages, false) 66 parent = root 67 result = append(result, h) 68 } 69 return result 70 } 71 72 func TestEncodeDecodeHistory(t *testing.T) { 73 testEncodeDecodeHistory(t, false) 74 testEncodeDecodeHistory(t, true) 75 } 76 77 func testEncodeDecodeHistory(t *testing.T, rawStorageKey bool) { 78 var ( 79 m meta 80 dec history 81 obj = makeHistory(rawStorageKey) 82 ) 83 // check if meta data can be correctly encode/decode 84 blob := obj.meta.encode() 85 if err := m.decode(blob); err != nil { 86 t.Fatalf("Failed to decode %v", err) 87 } 88 if !reflect.DeepEqual(&m, obj.meta) { 89 t.Fatal("meta is mismatched") 90 } 91 92 // check if account/storage data can be correctly encode/decode 93 accountData, storageData, accountIndexes, storageIndexes := obj.encode() 94 if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { 95 t.Fatalf("Failed to decode, err: %v", err) 96 } 97 if !compareSet(dec.accounts, obj.accounts) { 98 t.Fatal("account data is mismatched") 99 } 100 if !compareStorages(dec.storages, obj.storages) { 101 t.Fatal("storage data is mismatched") 102 } 103 if !compareList(dec.accountList, obj.accountList) { 104 t.Fatal("account list is mismatched") 105 } 106 if !compareStorageList(dec.storageList, obj.storageList) { 107 t.Fatal("storage list is mismatched") 108 } 109 } 110 111 func checkHistory(t *testing.T, db ethdb.KeyValueReader, freezer ethdb.AncientReader, id uint64, root common.Hash, exist bool) { 112 blob := rawdb.ReadStateHistoryMeta(freezer, id) 113 if exist && len(blob) == 0 { 114 t.Fatalf("Failed to load trie history, %d", id) 115 } 116 if !exist && len(blob) != 0 { 117 t.Fatalf("Unexpected trie history, %d", id) 118 } 119 if exist && rawdb.ReadStateID(db, root) == nil { 120 t.Fatalf("Root->ID mapping is not found, %d", id) 121 } 122 if !exist && rawdb.ReadStateID(db, root) != nil { 123 t.Fatalf("Unexpected root->ID mapping, %d", id) 124 } 125 } 126 127 func checkHistoriesInRange(t *testing.T, db ethdb.KeyValueReader, freezer ethdb.AncientReader, from, to uint64, roots []common.Hash, exist bool) { 128 for i, j := from, 0; i <= to; i, j = i+1, j+1 { 129 checkHistory(t, db, freezer, i, roots[j], exist) 130 } 131 } 132 133 func TestTruncateHeadHistory(t *testing.T) { 134 var ( 135 roots []common.Hash 136 hs = makeHistories(10) 137 db = rawdb.NewMemoryDatabase() 138 freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false, false) 139 ) 140 defer freezer.Close() 141 142 for i := 0; i < len(hs); i++ { 143 accountData, storageData, accountIndex, storageIndex := hs[i].encode() 144 rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) 145 rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) 146 roots = append(roots, hs[i].meta.root) 147 } 148 for size := len(hs); size > 0; size-- { 149 pruned, err := truncateFromHead(db, freezer, uint64(size-1)) 150 if err != nil { 151 t.Fatalf("Failed to truncate from head %v", err) 152 } 153 if pruned != 1 { 154 t.Error("Unexpected pruned items", "want", 1, "got", pruned) 155 } 156 checkHistoriesInRange(t, db, freezer, uint64(size), uint64(10), roots[size-1:], false) 157 checkHistoriesInRange(t, db, freezer, uint64(1), uint64(size-1), roots[:size-1], true) 158 } 159 } 160 161 func TestTruncateTailHistory(t *testing.T) { 162 var ( 163 roots []common.Hash 164 hs = makeHistories(10) 165 db = rawdb.NewMemoryDatabase() 166 freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false, false) 167 ) 168 defer freezer.Close() 169 170 for i := 0; i < len(hs); i++ { 171 accountData, storageData, accountIndex, storageIndex := hs[i].encode() 172 rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) 173 rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) 174 roots = append(roots, hs[i].meta.root) 175 } 176 for newTail := 1; newTail < len(hs); newTail++ { 177 pruned, _ := truncateFromTail(db, freezer, uint64(newTail)) 178 if pruned != 1 { 179 t.Error("Unexpected pruned items", "want", 1, "got", pruned) 180 } 181 checkHistoriesInRange(t, db, freezer, uint64(1), uint64(newTail), roots[:newTail], false) 182 checkHistoriesInRange(t, db, freezer, uint64(newTail+1), uint64(10), roots[newTail:], true) 183 } 184 } 185 186 func TestTruncateTailHistories(t *testing.T) { 187 var cases = []struct { 188 limit uint64 189 expPruned int 190 maxPruned uint64 191 minUnpruned uint64 192 empty bool 193 }{ 194 { 195 1, 9, 9, 10, false, 196 }, 197 { 198 0, 10, 10, 0 /* no meaning */, true, 199 }, 200 { 201 10, 0, 0, 1, false, 202 }, 203 } 204 for i, c := range cases { 205 var ( 206 roots []common.Hash 207 hs = makeHistories(10) 208 db = rawdb.NewMemoryDatabase() 209 freezer, _ = rawdb.NewStateFreezer(t.TempDir()+fmt.Sprintf("%d", i), false, false) 210 ) 211 defer freezer.Close() 212 213 for i := 0; i < len(hs); i++ { 214 accountData, storageData, accountIndex, storageIndex := hs[i].encode() 215 rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) 216 rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) 217 roots = append(roots, hs[i].meta.root) 218 } 219 pruned, _ := truncateFromTail(db, freezer, uint64(10)-c.limit) 220 if pruned != c.expPruned { 221 t.Error("Unexpected pruned items", "want", c.expPruned, "got", pruned) 222 } 223 if c.empty { 224 checkHistoriesInRange(t, db, freezer, uint64(1), uint64(10), roots, false) 225 } else { 226 tail := 10 - int(c.limit) 227 checkHistoriesInRange(t, db, freezer, uint64(1), c.maxPruned, roots[:tail], false) 228 checkHistoriesInRange(t, db, freezer, c.minUnpruned, uint64(10), roots[tail:], true) 229 } 230 } 231 } 232 233 func TestTruncateOutOfRange(t *testing.T) { 234 var ( 235 hs = makeHistories(10) 236 db = rawdb.NewMemoryDatabase() 237 freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false, false) 238 ) 239 defer freezer.Close() 240 241 for i := 0; i < len(hs); i++ { 242 accountData, storageData, accountIndex, storageIndex := hs[i].encode() 243 rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) 244 rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) 245 } 246 truncateFromTail(db, freezer, uint64(len(hs)/2)) 247 248 // Ensure of-out-range truncations are rejected correctly. 249 head, _ := freezer.Ancients() 250 tail, _ := freezer.Tail() 251 252 cases := []struct { 253 mode int 254 target uint64 255 expErr error 256 }{ 257 {0, head, nil}, // nothing to delete 258 {0, head + 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, head+1)}, 259 {0, tail - 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, tail-1)}, 260 {1, tail, nil}, // nothing to delete 261 {1, head + 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, head+1)}, 262 {1, tail - 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, tail-1)}, 263 } 264 for _, c := range cases { 265 var gotErr error 266 if c.mode == 0 { 267 _, gotErr = truncateFromHead(db, freezer, c.target) 268 } else { 269 _, gotErr = truncateFromTail(db, freezer, c.target) 270 } 271 if !reflect.DeepEqual(gotErr, c.expErr) { 272 t.Errorf("Unexpected error, want: %v, got: %v", c.expErr, gotErr) 273 } 274 } 275 } 276 277 func compareSet[k comparable](a, b map[k][]byte) bool { 278 if len(a) != len(b) { 279 return false 280 } 281 for key, valA := range a { 282 valB, ok := b[key] 283 if !ok { 284 return false 285 } 286 if !bytes.Equal(valA, valB) { 287 return false 288 } 289 } 290 return true 291 } 292 293 func compareList[k comparable](a, b []k) bool { 294 if len(a) != len(b) { 295 return false 296 } 297 for i := 0; i < len(a); i++ { 298 if a[i] != b[i] { 299 return false 300 } 301 } 302 return true 303 } 304 305 func compareStorages(a, b map[common.Address]map[common.Hash][]byte) bool { 306 if len(a) != len(b) { 307 return false 308 } 309 for h, subA := range a { 310 subB, ok := b[h] 311 if !ok { 312 return false 313 } 314 if !compareSet(subA, subB) { 315 return false 316 } 317 } 318 return true 319 } 320 321 func compareStorageList(a, b map[common.Address][]common.Hash) bool { 322 if len(a) != len(b) { 323 return false 324 } 325 for h, la := range a { 326 lb, ok := b[h] 327 if !ok { 328 return false 329 } 330 if !compareList(la, lb) { 331 return false 332 } 333 } 334 return true 335 }