github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/core/rawdb/ancienttest/testsuite.go (about) 1 // Copyright 2024 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 ancienttest 18 19 import ( 20 "bytes" 21 "reflect" 22 "testing" 23 24 "github.com/ethereum/go-ethereum/ethdb" 25 "github.com/ethereum/go-ethereum/internal/testrand" 26 ) 27 28 // TestAncientSuite runs a suite of tests against an ancient database 29 // implementation. 30 func TestAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { 31 // Test basic read methods 32 t.Run("BasicRead", func(t *testing.T) { basicRead(t, newFn) }) 33 34 // Test batch read method 35 t.Run("BatchRead", func(t *testing.T) { batchRead(t, newFn) }) 36 37 // Test basic write methods 38 t.Run("BasicWrite", func(t *testing.T) { basicWrite(t, newFn) }) 39 40 // Test if data mutation is allowed after db write 41 t.Run("nonMutable", func(t *testing.T) { nonMutable(t, newFn) }) 42 } 43 44 func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { 45 var ( 46 db = newFn([]string{"a"}) 47 data = makeDataset(100, 32) 48 ) 49 defer db.Close() 50 51 db.ModifyAncients(func(op ethdb.AncientWriteOp) error { 52 for i := 0; i < len(data); i++ { 53 op.AppendRaw("a", uint64(i), data[i]) 54 } 55 return nil 56 }) 57 db.TruncateTail(10) 58 db.TruncateHead(90) 59 60 // Test basic tail and head retrievals 61 tail, err := db.Tail() 62 if err != nil || tail != 10 { 63 t.Fatal("Failed to retrieve tail") 64 } 65 ancient, err := db.Ancients() 66 if err != nil || ancient != 90 { 67 t.Fatal("Failed to retrieve ancient") 68 } 69 70 // Test the deleted items shouldn't be reachable 71 var cases = []struct { 72 start int 73 limit int 74 }{ 75 {0, 10}, 76 {90, 100}, 77 } 78 for _, c := range cases { 79 for i := c.start; i < c.limit; i++ { 80 exist, err := db.HasAncient("a", uint64(i)) 81 if err != nil { 82 t.Fatalf("Failed to check presence, %v", err) 83 } 84 if exist { 85 t.Fatalf("Item %d is already truncated", uint64(i)) 86 } 87 _, err = db.Ancient("a", uint64(i)) 88 if err == nil { 89 t.Fatal("Error is expected for non-existent item") 90 } 91 } 92 } 93 94 // Test the items in range should be reachable 95 for i := 10; i < 90; i++ { 96 exist, err := db.HasAncient("a", uint64(i)) 97 if err != nil { 98 t.Fatalf("Failed to check presence, %v", err) 99 } 100 if !exist { 101 t.Fatalf("Item %d is missing", uint64(i)) 102 } 103 blob, err := db.Ancient("a", uint64(i)) 104 if err != nil { 105 t.Fatalf("Failed to retrieve item, %v", err) 106 } 107 if !bytes.Equal(blob, data[i]) { 108 t.Fatalf("Unexpected item content, want: %v, got: %v", data[i], blob) 109 } 110 } 111 112 // Test the items in unknown table shouldn't be reachable 113 exist, err := db.HasAncient("b", uint64(0)) 114 if err != nil { 115 t.Fatalf("Failed to check presence, %v", err) 116 } 117 if exist { 118 t.Fatal("Item in unknown table shouldn't be found") 119 } 120 _, err = db.Ancient("b", uint64(0)) 121 if err == nil { 122 t.Fatal("Error is expected for unknown table") 123 } 124 } 125 126 func batchRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { 127 var ( 128 db = newFn([]string{"a"}) 129 data = makeDataset(100, 32) 130 ) 131 defer db.Close() 132 133 db.ModifyAncients(func(op ethdb.AncientWriteOp) error { 134 for i := 0; i < 100; i++ { 135 op.AppendRaw("a", uint64(i), data[i]) 136 } 137 return nil 138 }) 139 db.TruncateTail(10) 140 db.TruncateHead(90) 141 142 // Test the items in range should be reachable 143 var cases = []struct { 144 start uint64 145 count uint64 146 maxSize uint64 147 expStart int 148 expLimit int 149 }{ 150 // Items in range [10, 90) with no size limitation 151 { 152 10, 80, 0, 10, 90, 153 }, 154 // Items in range [10, 90) with 32 size cap, single item is expected 155 { 156 10, 80, 32, 10, 11, 157 }, 158 // Items in range [10, 90) with 31 size cap, single item is expected 159 { 160 10, 80, 31, 10, 11, 161 }, 162 // Items in range [10, 90) with 32*80 size cap, all items are expected 163 { 164 10, 80, 32 * 80, 10, 90, 165 }, 166 // Extra items above the last item are not returned 167 { 168 10, 90, 0, 10, 90, 169 }, 170 } 171 for i, c := range cases { 172 batch, err := db.AncientRange("a", c.start, c.count, c.maxSize) 173 if err != nil { 174 t.Fatalf("Failed to retrieve item in range, %v", err) 175 } 176 if !reflect.DeepEqual(batch, data[c.expStart:c.expLimit]) { 177 t.Fatalf("Case %d, Batch content is not matched", i) 178 } 179 } 180 181 // Test out-of-range / zero-size retrieval should be rejected 182 _, err := db.AncientRange("a", 0, 1, 0) 183 if err == nil { 184 t.Fatal("Out-of-range retrieval should be rejected") 185 } 186 _, err = db.AncientRange("a", 90, 1, 0) 187 if err == nil { 188 t.Fatal("Out-of-range retrieval should be rejected") 189 } 190 _, err = db.AncientRange("a", 10, 0, 0) 191 if err == nil { 192 t.Fatal("Zero-size retrieval should be rejected") 193 } 194 195 // Test item in unknown table shouldn't be reachable 196 _, err = db.AncientRange("b", 10, 1, 0) 197 if err == nil { 198 t.Fatal("Item in unknown table shouldn't be found") 199 } 200 } 201 202 func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { 203 var ( 204 db = newFn([]string{"a", "b"}) 205 dataA = makeDataset(100, 32) 206 dataB = makeDataset(100, 32) 207 ) 208 defer db.Close() 209 210 // The ancient write to tables should be aligned 211 _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error { 212 for i := 0; i < 100; i++ { 213 op.AppendRaw("a", uint64(i), dataA[i]) 214 } 215 return nil 216 }) 217 if err == nil { 218 t.Fatal("Unaligned ancient write should be rejected") 219 } 220 221 // Test normal ancient write 222 size, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error { 223 for i := 0; i < 100; i++ { 224 op.AppendRaw("a", uint64(i), dataA[i]) 225 op.AppendRaw("b", uint64(i), dataB[i]) 226 } 227 return nil 228 }) 229 if err != nil { 230 t.Fatalf("Failed to write ancient data %v", err) 231 } 232 wantSize := int64(6400) 233 if size != wantSize { 234 t.Fatalf("Ancient write size is not expected, want: %d, got: %d", wantSize, size) 235 } 236 237 // Write should work after head truncating 238 db.TruncateHead(90) 239 _, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error { 240 for i := 90; i < 100; i++ { 241 op.AppendRaw("a", uint64(i), dataA[i]) 242 op.AppendRaw("b", uint64(i), dataB[i]) 243 } 244 return nil 245 }) 246 if err != nil { 247 t.Fatalf("Failed to write ancient data %v", err) 248 } 249 250 // Write should work after truncating everything 251 db.TruncateTail(0) 252 _, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error { 253 for i := 0; i < 100; i++ { 254 op.AppendRaw("a", uint64(i), dataA[i]) 255 op.AppendRaw("b", uint64(i), dataB[i]) 256 } 257 return nil 258 }) 259 if err != nil { 260 t.Fatalf("Failed to write ancient data %v", err) 261 } 262 } 263 264 func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { 265 db := newFn([]string{"a"}) 266 defer db.Close() 267 268 // We write 100 zero-bytes to the freezer and immediately mutate the slice 269 db.ModifyAncients(func(op ethdb.AncientWriteOp) error { 270 data := make([]byte, 100) 271 op.AppendRaw("a", uint64(0), data) 272 for i := range data { 273 data[i] = 0xff 274 } 275 return nil 276 }) 277 // Now read it. 278 data, err := db.Ancient("a", uint64(0)) 279 if err != nil { 280 t.Fatal(err) 281 } 282 for k, v := range data { 283 if v != 0 { 284 t.Fatalf("byte %d != 0: %x", k, v) 285 } 286 } 287 } 288 289 // TestResettableAncientSuite runs a suite of tests against a resettable ancient 290 // database implementation. 291 func TestResettableAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.ResettableAncientStore) { 292 t.Run("Reset", func(t *testing.T) { 293 var ( 294 db = newFn([]string{"a"}) 295 data = makeDataset(100, 32) 296 ) 297 defer db.Close() 298 299 db.ModifyAncients(func(op ethdb.AncientWriteOp) error { 300 for i := 0; i < 100; i++ { 301 op.AppendRaw("a", uint64(i), data[i]) 302 } 303 return nil 304 }) 305 db.TruncateTail(10) 306 db.TruncateHead(90) 307 308 // Ancient write should work after resetting 309 db.Reset() 310 db.ModifyAncients(func(op ethdb.AncientWriteOp) error { 311 for i := 0; i < 100; i++ { 312 op.AppendRaw("a", uint64(i), data[i]) 313 } 314 return nil 315 }) 316 }) 317 } 318 319 func makeDataset(size, value int) [][]byte { 320 var vals [][]byte 321 for i := 0; i < size; i += 1 { 322 vals = append(vals, testrand.Bytes(value)) 323 } 324 return vals 325 }