gitlab.com/yannislg/go-pulse@v0.0.0-20210722055913-a3e24e95638d/core/state/snapshot/snapshot_test.go (about) 1 // Copyright 2019 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 snapshot 18 19 import ( 20 "fmt" 21 "math/big" 22 "math/rand" 23 "testing" 24 25 "github.com/VictoriaMetrics/fastcache" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/core/rawdb" 28 "github.com/ethereum/go-ethereum/rlp" 29 ) 30 31 // randomHash generates a random blob of data and returns it as a hash. 32 func randomHash() common.Hash { 33 var hash common.Hash 34 if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { 35 panic(err) 36 } 37 return hash 38 } 39 40 // randomAccount generates a random account and returns it RLP encoded. 41 func randomAccount() []byte { 42 root := randomHash() 43 a := Account{ 44 Balance: big.NewInt(rand.Int63()), 45 Nonce: rand.Uint64(), 46 Root: root[:], 47 CodeHash: emptyCode[:], 48 } 49 data, _ := rlp.EncodeToBytes(a) 50 return data 51 } 52 53 // randomAccountSet generates a set of random accounts with the given strings as 54 // the account address hashes. 55 func randomAccountSet(hashes ...string) map[common.Hash][]byte { 56 accounts := make(map[common.Hash][]byte) 57 for _, hash := range hashes { 58 accounts[common.HexToHash(hash)] = randomAccount() 59 } 60 return accounts 61 } 62 63 // Tests that if a disk layer becomes stale, no active external references will 64 // be returned with junk data. This version of the test flattens every diff layer 65 // to check internal corner case around the bottom-most memory accumulator. 66 func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { 67 // Create an empty base layer and a snapshot tree out of it 68 base := &diskLayer{ 69 diskdb: rawdb.NewMemoryDatabase(), 70 root: common.HexToHash("0x01"), 71 cache: fastcache.New(1024 * 500), 72 } 73 snaps := &Tree{ 74 layers: map[common.Hash]snapshot{ 75 base.root: base, 76 }, 77 } 78 // Retrieve a reference to the base and commit a diff on top 79 ref := snaps.Snapshot(base.root) 80 81 accounts := map[common.Hash][]byte{ 82 common.HexToHash("0xa1"): randomAccount(), 83 } 84 if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { 85 t.Fatalf("failed to create a diff layer: %v", err) 86 } 87 if n := len(snaps.layers); n != 2 { 88 t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2) 89 } 90 // Commit the diff layer onto the disk and ensure it's persisted 91 if err := snaps.Cap(common.HexToHash("0x02"), 0); err != nil { 92 t.Fatalf("failed to merge diff layer onto disk: %v", err) 93 } 94 // Since the base layer was modified, ensure that data retrieval on the external reference fail 95 if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { 96 t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) 97 } 98 if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { 99 t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) 100 } 101 if n := len(snaps.layers); n != 1 { 102 t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 1) 103 fmt.Println(snaps.layers) 104 } 105 } 106 107 // Tests that if a disk layer becomes stale, no active external references will 108 // be returned with junk data. This version of the test retains the bottom diff 109 // layer to check the usual mode of operation where the accumulator is retained. 110 func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { 111 // Create an empty base layer and a snapshot tree out of it 112 base := &diskLayer{ 113 diskdb: rawdb.NewMemoryDatabase(), 114 root: common.HexToHash("0x01"), 115 cache: fastcache.New(1024 * 500), 116 } 117 snaps := &Tree{ 118 layers: map[common.Hash]snapshot{ 119 base.root: base, 120 }, 121 } 122 // Retrieve a reference to the base and commit two diffs on top 123 ref := snaps.Snapshot(base.root) 124 125 accounts := map[common.Hash][]byte{ 126 common.HexToHash("0xa1"): randomAccount(), 127 } 128 if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { 129 t.Fatalf("failed to create a diff layer: %v", err) 130 } 131 if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { 132 t.Fatalf("failed to create a diff layer: %v", err) 133 } 134 if n := len(snaps.layers); n != 3 { 135 t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) 136 } 137 // Commit the diff layer onto the disk and ensure it's persisted 138 defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) 139 aggregatorMemoryLimit = 0 140 141 if err := snaps.Cap(common.HexToHash("0x03"), 2); err != nil { 142 t.Fatalf("failed to merge diff layer onto disk: %v", err) 143 } 144 // Since the base layer was modified, ensure that data retrievald on the external reference fail 145 if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { 146 t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) 147 } 148 if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { 149 t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) 150 } 151 if n := len(snaps.layers); n != 2 { 152 t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) 153 fmt.Println(snaps.layers) 154 } 155 } 156 157 // Tests that if a diff layer becomes stale, no active external references will 158 // be returned with junk data. This version of the test flattens every diff layer 159 // to check internal corner case around the bottom-most memory accumulator. 160 func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { 161 // Create an empty base layer and a snapshot tree out of it 162 base := &diskLayer{ 163 diskdb: rawdb.NewMemoryDatabase(), 164 root: common.HexToHash("0x01"), 165 cache: fastcache.New(1024 * 500), 166 } 167 snaps := &Tree{ 168 layers: map[common.Hash]snapshot{ 169 base.root: base, 170 }, 171 } 172 // Commit two diffs on top and retrieve a reference to the bottommost 173 accounts := map[common.Hash][]byte{ 174 common.HexToHash("0xa1"): randomAccount(), 175 } 176 if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { 177 t.Fatalf("failed to create a diff layer: %v", err) 178 } 179 if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { 180 t.Fatalf("failed to create a diff layer: %v", err) 181 } 182 if n := len(snaps.layers); n != 3 { 183 t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) 184 } 185 ref := snaps.Snapshot(common.HexToHash("0x02")) 186 187 // Flatten the diff layer into the bottom accumulator 188 if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil { 189 t.Fatalf("failed to flatten diff layer into accumulator: %v", err) 190 } 191 // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail 192 if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { 193 t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) 194 } 195 if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { 196 t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) 197 } 198 if n := len(snaps.layers); n != 2 { 199 t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) 200 fmt.Println(snaps.layers) 201 } 202 } 203 204 // Tests that if a diff layer becomes stale, no active external references will 205 // be returned with junk data. This version of the test retains the bottom diff 206 // layer to check the usual mode of operation where the accumulator is retained. 207 func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { 208 // Create an empty base layer and a snapshot tree out of it 209 base := &diskLayer{ 210 diskdb: rawdb.NewMemoryDatabase(), 211 root: common.HexToHash("0x01"), 212 cache: fastcache.New(1024 * 500), 213 } 214 snaps := &Tree{ 215 layers: map[common.Hash]snapshot{ 216 base.root: base, 217 }, 218 } 219 // Commit three diffs on top and retrieve a reference to the bottommost 220 accounts := map[common.Hash][]byte{ 221 common.HexToHash("0xa1"): randomAccount(), 222 } 223 if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { 224 t.Fatalf("failed to create a diff layer: %v", err) 225 } 226 if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { 227 t.Fatalf("failed to create a diff layer: %v", err) 228 } 229 if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, accounts, nil); err != nil { 230 t.Fatalf("failed to create a diff layer: %v", err) 231 } 232 if n := len(snaps.layers); n != 4 { 233 t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 4) 234 } 235 ref := snaps.Snapshot(common.HexToHash("0x02")) 236 237 // Doing a Cap operation with many allowed layers should be a no-op 238 exp := len(snaps.layers) 239 if err := snaps.Cap(common.HexToHash("0x04"), 2000); err != nil { 240 t.Fatalf("failed to flatten diff layer into accumulator: %v", err) 241 } 242 if got := len(snaps.layers); got != exp { 243 t.Errorf("layers modified, got %d exp %d", got, exp) 244 } 245 // Flatten the diff layer into the bottom accumulator 246 if err := snaps.Cap(common.HexToHash("0x04"), 2); err != nil { 247 t.Fatalf("failed to flatten diff layer into accumulator: %v", err) 248 } 249 // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail 250 if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { 251 t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) 252 } 253 if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { 254 t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) 255 } 256 if n := len(snaps.layers); n != 3 { 257 t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 3) 258 fmt.Println(snaps.layers) 259 } 260 } 261 262 // TestPostCapBasicDataAccess tests some functionality regarding capping/flattening. 263 func TestPostCapBasicDataAccess(t *testing.T) { 264 // setAccount is a helper to construct a random account entry and assign it to 265 // an account slot in a snapshot 266 setAccount := func(accKey string) map[common.Hash][]byte { 267 return map[common.Hash][]byte{ 268 common.HexToHash(accKey): randomAccount(), 269 } 270 } 271 // Create a starting base layer and a snapshot tree out of it 272 base := &diskLayer{ 273 diskdb: rawdb.NewMemoryDatabase(), 274 root: common.HexToHash("0x01"), 275 cache: fastcache.New(1024 * 500), 276 } 277 snaps := &Tree{ 278 layers: map[common.Hash]snapshot{ 279 base.root: base, 280 }, 281 } 282 // The lowest difflayer 283 snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil) 284 snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil) 285 snaps.Update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), nil, setAccount("0xb2"), nil) 286 287 snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) 288 snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil) 289 290 // checkExist verifies if an account exiss in a snapshot 291 checkExist := func(layer *diffLayer, key string) error { 292 if data, _ := layer.Account(common.HexToHash(key)); data == nil { 293 return fmt.Errorf("expected %x to exist, got nil", common.HexToHash(key)) 294 } 295 return nil 296 } 297 // shouldErr checks that an account access errors as expected 298 shouldErr := func(layer *diffLayer, key string) error { 299 if data, err := layer.Account(common.HexToHash(key)); err == nil { 300 return fmt.Errorf("expected error, got data %x", data) 301 } 302 return nil 303 } 304 // check basics 305 snap := snaps.Snapshot(common.HexToHash("0xb3")).(*diffLayer) 306 307 if err := checkExist(snap, "0xa1"); err != nil { 308 t.Error(err) 309 } 310 if err := checkExist(snap, "0xb2"); err != nil { 311 t.Error(err) 312 } 313 if err := checkExist(snap, "0xb3"); err != nil { 314 t.Error(err) 315 } 316 // Cap to a bad root should fail 317 if err := snaps.Cap(common.HexToHash("0x1337"), 0); err == nil { 318 t.Errorf("expected error, got none") 319 } 320 // Now, merge the a-chain 321 snaps.Cap(common.HexToHash("0xa3"), 0) 322 323 // At this point, a2 got merged into a1. Thus, a1 is now modified, and as a1 is 324 // the parent of b2, b2 should no longer be able to iterate into parent. 325 326 // These should still be accessible 327 if err := checkExist(snap, "0xb2"); err != nil { 328 t.Error(err) 329 } 330 if err := checkExist(snap, "0xb3"); err != nil { 331 t.Error(err) 332 } 333 // But these would need iteration into the modified parent 334 if err := shouldErr(snap, "0xa1"); err != nil { 335 t.Error(err) 336 } 337 if err := shouldErr(snap, "0xa2"); err != nil { 338 t.Error(err) 339 } 340 if err := shouldErr(snap, "0xa3"); err != nil { 341 t.Error(err) 342 } 343 // Now, merge it again, just for fun. It should now error, since a3 344 // is a disk layer 345 if err := snaps.Cap(common.HexToHash("0xa3"), 0); err == nil { 346 t.Error("expected error capping the disk layer, got none") 347 } 348 }