github.com/tmoore22/go-ethereum@v1.10.22-0.20220814113424-76f4d8bc4994/light/postprocess.go (about) 1 // Copyright 2017 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 light 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/binary" 23 "errors" 24 "fmt" 25 "math/big" 26 "time" 27 28 mapset "github.com/deckarep/golang-set" 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/common/bitutil" 31 "github.com/ethereum/go-ethereum/core" 32 "github.com/ethereum/go-ethereum/core/rawdb" 33 "github.com/ethereum/go-ethereum/core/types" 34 "github.com/ethereum/go-ethereum/ethdb" 35 "github.com/ethereum/go-ethereum/log" 36 "github.com/ethereum/go-ethereum/params" 37 "github.com/ethereum/go-ethereum/rlp" 38 "github.com/ethereum/go-ethereum/trie" 39 ) 40 41 // IndexerConfig includes a set of configs for chain indexers. 42 type IndexerConfig struct { 43 // The block frequency for creating CHTs. 44 ChtSize uint64 45 46 // The number of confirmations needed to generate/accept a canonical hash help trie. 47 ChtConfirms uint64 48 49 // The block frequency for creating new bloom bits. 50 BloomSize uint64 51 52 // The number of confirmation needed before a bloom section is considered probably final and its rotated bits 53 // are calculated. 54 BloomConfirms uint64 55 56 // The block frequency for creating BloomTrie. 57 BloomTrieSize uint64 58 59 // The number of confirmations needed to generate/accept a bloom trie. 60 BloomTrieConfirms uint64 61 } 62 63 var ( 64 // DefaultServerIndexerConfig wraps a set of configs as a default indexer config for server side. 65 DefaultServerIndexerConfig = &IndexerConfig{ 66 ChtSize: params.CHTFrequency, 67 ChtConfirms: params.HelperTrieProcessConfirmations, 68 BloomSize: params.BloomBitsBlocks, 69 BloomConfirms: params.BloomConfirms, 70 BloomTrieSize: params.BloomTrieFrequency, 71 BloomTrieConfirms: params.HelperTrieProcessConfirmations, 72 } 73 // DefaultClientIndexerConfig wraps a set of configs as a default indexer config for client side. 74 DefaultClientIndexerConfig = &IndexerConfig{ 75 ChtSize: params.CHTFrequency, 76 ChtConfirms: params.HelperTrieConfirmations, 77 BloomSize: params.BloomBitsBlocksClient, 78 BloomConfirms: params.HelperTrieConfirmations, 79 BloomTrieSize: params.BloomTrieFrequency, 80 BloomTrieConfirms: params.HelperTrieConfirmations, 81 } 82 // TestServerIndexerConfig wraps a set of configs as a test indexer config for server side. 83 TestServerIndexerConfig = &IndexerConfig{ 84 ChtSize: 128, 85 ChtConfirms: 1, 86 BloomSize: 16, 87 BloomConfirms: 1, 88 BloomTrieSize: 128, 89 BloomTrieConfirms: 1, 90 } 91 // TestClientIndexerConfig wraps a set of configs as a test indexer config for client side. 92 TestClientIndexerConfig = &IndexerConfig{ 93 ChtSize: 128, 94 ChtConfirms: 8, 95 BloomSize: 128, 96 BloomConfirms: 8, 97 BloomTrieSize: 128, 98 BloomTrieConfirms: 8, 99 } 100 ) 101 102 var ( 103 errNoTrustedCht = errors.New("no trusted canonical hash trie") 104 errNoTrustedBloomTrie = errors.New("no trusted bloom trie") 105 errNoHeader = errors.New("header not found") 106 chtPrefix = []byte("chtRootV2-") // chtPrefix + chtNum (uint64 big endian) -> trie root hash 107 ChtTablePrefix = "cht-" 108 ) 109 110 // ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format 111 type ChtNode struct { 112 Hash common.Hash 113 Td *big.Int 114 } 115 116 // GetChtRoot reads the CHT root associated to the given section from the database 117 func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { 118 var encNumber [8]byte 119 binary.BigEndian.PutUint64(encNumber[:], sectionIdx) 120 data, _ := db.Get(append(append(chtPrefix, encNumber[:]...), sectionHead.Bytes()...)) 121 return common.BytesToHash(data) 122 } 123 124 // StoreChtRoot writes the CHT root associated to the given section into the database 125 func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { 126 var encNumber [8]byte 127 binary.BigEndian.PutUint64(encNumber[:], sectionIdx) 128 db.Put(append(append(chtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) 129 } 130 131 // ChtIndexerBackend implements core.ChainIndexerBackend. 132 type ChtIndexerBackend struct { 133 disablePruning bool 134 diskdb, trieTable ethdb.Database 135 odr OdrBackend 136 triedb *trie.Database 137 trieset mapset.Set 138 section, sectionSize uint64 139 lastHash common.Hash 140 trie *trie.Trie 141 } 142 143 // NewChtIndexer creates a Cht chain indexer 144 func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, disablePruning bool) *core.ChainIndexer { 145 trieTable := rawdb.NewTable(db, ChtTablePrefix) 146 backend := &ChtIndexerBackend{ 147 diskdb: db, 148 odr: odr, 149 trieTable: trieTable, 150 triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down 151 trieset: mapset.NewSet(), 152 sectionSize: size, 153 disablePruning: disablePruning, 154 } 155 return core.NewChainIndexer(db, rawdb.NewTable(db, "chtIndexV2-"), backend, size, confirms, time.Millisecond*100, "cht") 156 } 157 158 // fetchMissingNodes tries to retrieve the last entry of the latest trusted CHT from the 159 // ODR backend in order to be able to add new entries and calculate subsequent root hashes 160 func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { 161 batch := c.trieTable.NewBatch() 162 r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1, Config: c.odr.IndexerConfig()} 163 for { 164 err := c.odr.Retrieve(ctx, r) 165 switch err { 166 case nil: 167 r.Proof.Store(batch) 168 return batch.Write() 169 case ErrNoPeers: 170 // if there are no peers to serve, retry later 171 select { 172 case <-ctx.Done(): 173 return ctx.Err() 174 case <-time.After(time.Second * 10): 175 // stay in the loop and try again 176 } 177 default: 178 return err 179 } 180 } 181 } 182 183 // Reset implements core.ChainIndexerBackend 184 func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { 185 var root common.Hash 186 if section > 0 { 187 root = GetChtRoot(c.diskdb, section-1, lastSectionHead) 188 } 189 var err error 190 c.trie, err = trie.New(common.Hash{}, root, c.triedb) 191 192 if err != nil && c.odr != nil { 193 err = c.fetchMissingNodes(ctx, section, root) 194 if err == nil { 195 c.trie, err = trie.New(common.Hash{}, root, c.triedb) 196 } 197 } 198 c.section = section 199 return err 200 } 201 202 // Process implements core.ChainIndexerBackend 203 func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) error { 204 hash, num := header.Hash(), header.Number.Uint64() 205 c.lastHash = hash 206 207 td := rawdb.ReadTd(c.diskdb, hash, num) 208 if td == nil { 209 panic(nil) 210 } 211 var encNumber [8]byte 212 binary.BigEndian.PutUint64(encNumber[:], num) 213 data, _ := rlp.EncodeToBytes(ChtNode{hash, td}) 214 c.trie.Update(encNumber[:], data) 215 return nil 216 } 217 218 // Commit implements core.ChainIndexerBackend 219 func (c *ChtIndexerBackend) Commit() error { 220 root, nodes, err := c.trie.Commit(false) 221 if err != nil { 222 return err 223 } 224 // Commit trie changes into trie database in case it's not nil. 225 if nodes != nil { 226 if err := c.triedb.Update(trie.NewWithNodeSet(nodes)); err != nil { 227 return err 228 } 229 } 230 // Re-create trie with newly generated root and updated database. 231 c.trie, err = trie.New(common.Hash{}, root, c.triedb) 232 if err != nil { 233 return err 234 } 235 // Pruning historical trie nodes if necessary. 236 if !c.disablePruning { 237 // Flush the triedb and track the latest trie nodes. 238 c.trieset.Clear() 239 c.triedb.Commit(root, false, func(hash common.Hash) { c.trieset.Add(hash) }) 240 241 it := c.trieTable.NewIterator(nil, nil) 242 defer it.Release() 243 244 var ( 245 deleted int 246 remaining int 247 t = time.Now() 248 ) 249 for it.Next() { 250 trimmed := bytes.TrimPrefix(it.Key(), []byte(ChtTablePrefix)) 251 if !c.trieset.Contains(common.BytesToHash(trimmed)) { 252 c.trieTable.Delete(trimmed) 253 deleted += 1 254 } else { 255 remaining += 1 256 } 257 } 258 log.Debug("Prune historical CHT trie nodes", "deleted", deleted, "remaining", remaining, "elapsed", common.PrettyDuration(time.Since(t))) 259 } else { 260 c.triedb.Commit(root, false, nil) 261 } 262 log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root)) 263 StoreChtRoot(c.diskdb, c.section, c.lastHash, root) 264 return nil 265 } 266 267 // Prune implements core.ChainIndexerBackend which deletes all chain data 268 // (except hash<->number mappings) older than the specified threshold. 269 func (c *ChtIndexerBackend) Prune(threshold uint64) error { 270 // Short circuit if the light pruning is disabled. 271 if c.disablePruning { 272 return nil 273 } 274 t := time.Now() 275 // Always keep genesis header in database. 276 start, end := uint64(1), (threshold+1)*c.sectionSize 277 278 var batch = c.diskdb.NewBatch() 279 for { 280 numbers, hashes := rawdb.ReadAllCanonicalHashes(c.diskdb, start, end, 10240) 281 if len(numbers) == 0 { 282 break 283 } 284 for i := 0; i < len(numbers); i++ { 285 // Keep hash<->number mapping in database otherwise the hash based 286 // API(e.g. GetReceipt, GetLogs) will be broken. 287 // 288 // Storage size wise, the size of a mapping is ~41bytes. For one 289 // section is about 1.3MB which is acceptable. 290 // 291 // In order to totally get rid of this index, we need an additional 292 // flag to specify how many historical data light client can serve. 293 rawdb.DeleteCanonicalHash(batch, numbers[i]) 294 rawdb.DeleteBlockWithoutNumber(batch, hashes[i], numbers[i]) 295 } 296 if batch.ValueSize() > ethdb.IdealBatchSize { 297 if err := batch.Write(); err != nil { 298 return err 299 } 300 batch.Reset() 301 } 302 start = numbers[len(numbers)-1] + 1 303 } 304 if err := batch.Write(); err != nil { 305 return err 306 } 307 log.Debug("Prune history headers", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(t))) 308 return nil 309 } 310 311 var ( 312 bloomTriePrefix = []byte("bltRoot-") // bloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash 313 BloomTrieTablePrefix = "blt-" 314 ) 315 316 // GetBloomTrieRoot reads the BloomTrie root assoctiated to the given section from the database 317 func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { 318 var encNumber [8]byte 319 binary.BigEndian.PutUint64(encNumber[:], sectionIdx) 320 data, _ := db.Get(append(append(bloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...)) 321 return common.BytesToHash(data) 322 } 323 324 // StoreBloomTrieRoot writes the BloomTrie root assoctiated to the given section into the database 325 func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { 326 var encNumber [8]byte 327 binary.BigEndian.PutUint64(encNumber[:], sectionIdx) 328 db.Put(append(append(bloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) 329 } 330 331 // BloomTrieIndexerBackend implements core.ChainIndexerBackend 332 type BloomTrieIndexerBackend struct { 333 disablePruning bool 334 diskdb, trieTable ethdb.Database 335 triedb *trie.Database 336 trieset mapset.Set 337 odr OdrBackend 338 section uint64 339 parentSize uint64 340 size uint64 341 bloomTrieRatio uint64 342 trie *trie.Trie 343 sectionHeads []common.Hash 344 } 345 346 // NewBloomTrieIndexer creates a BloomTrie chain indexer 347 func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64, disablePruning bool) *core.ChainIndexer { 348 trieTable := rawdb.NewTable(db, BloomTrieTablePrefix) 349 backend := &BloomTrieIndexerBackend{ 350 diskdb: db, 351 odr: odr, 352 trieTable: trieTable, 353 triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down 354 trieset: mapset.NewSet(), 355 parentSize: parentSize, 356 size: size, 357 disablePruning: disablePruning, 358 } 359 backend.bloomTrieRatio = size / parentSize 360 backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio) 361 return core.NewChainIndexer(db, rawdb.NewTable(db, "bltIndex-"), backend, size, 0, time.Millisecond*100, "bloomtrie") 362 } 363 364 // fetchMissingNodes tries to retrieve the last entries of the latest trusted bloom trie from the 365 // ODR backend in order to be able to add new entries and calculate subsequent root hashes 366 func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { 367 indexCh := make(chan uint, types.BloomBitLength) 368 type res struct { 369 nodes *NodeSet 370 err error 371 } 372 resCh := make(chan res, types.BloomBitLength) 373 for i := 0; i < 20; i++ { 374 go func() { 375 for bitIndex := range indexCh { 376 r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIndexList: []uint64{section - 1}, Config: b.odr.IndexerConfig()} 377 for { 378 if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers { 379 // if there are no peers to serve, retry later 380 select { 381 case <-ctx.Done(): 382 resCh <- res{nil, ctx.Err()} 383 return 384 case <-time.After(time.Second * 10): 385 // stay in the loop and try again 386 } 387 } else { 388 resCh <- res{r.Proofs, err} 389 break 390 } 391 } 392 } 393 }() 394 } 395 for i := uint(0); i < types.BloomBitLength; i++ { 396 indexCh <- i 397 } 398 close(indexCh) 399 batch := b.trieTable.NewBatch() 400 for i := uint(0); i < types.BloomBitLength; i++ { 401 res := <-resCh 402 if res.err != nil { 403 return res.err 404 } 405 res.nodes.Store(batch) 406 } 407 return batch.Write() 408 } 409 410 // Reset implements core.ChainIndexerBackend 411 func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { 412 var root common.Hash 413 if section > 0 { 414 root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead) 415 } 416 var err error 417 b.trie, err = trie.New(common.Hash{}, root, b.triedb) 418 if err != nil && b.odr != nil { 419 err = b.fetchMissingNodes(ctx, section, root) 420 if err == nil { 421 b.trie, err = trie.New(common.Hash{}, root, b.triedb) 422 } 423 } 424 b.section = section 425 return err 426 } 427 428 // Process implements core.ChainIndexerBackend 429 func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Header) error { 430 num := header.Number.Uint64() - b.section*b.size 431 if (num+1)%b.parentSize == 0 { 432 b.sectionHeads[num/b.parentSize] = header.Hash() 433 } 434 return nil 435 } 436 437 // Commit implements core.ChainIndexerBackend 438 func (b *BloomTrieIndexerBackend) Commit() error { 439 var compSize, decompSize uint64 440 441 for i := uint(0); i < types.BloomBitLength; i++ { 442 var encKey [10]byte 443 binary.BigEndian.PutUint16(encKey[0:2], uint16(i)) 444 binary.BigEndian.PutUint64(encKey[2:10], b.section) 445 var decomp []byte 446 for j := uint64(0); j < b.bloomTrieRatio; j++ { 447 data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j]) 448 if err != nil { 449 return err 450 } 451 decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSize/8)) 452 if err2 != nil { 453 return err2 454 } 455 decomp = append(decomp, decompData...) 456 } 457 comp := bitutil.CompressBytes(decomp) 458 459 decompSize += uint64(len(decomp)) 460 compSize += uint64(len(comp)) 461 if len(comp) > 0 { 462 b.trie.Update(encKey[:], comp) 463 } else { 464 b.trie.Delete(encKey[:]) 465 } 466 } 467 root, nodes, err := b.trie.Commit(false) 468 if err != nil { 469 return err 470 } 471 // Commit trie changes into trie database in case it's not nil. 472 if nodes != nil { 473 if err := b.triedb.Update(trie.NewWithNodeSet(nodes)); err != nil { 474 return err 475 } 476 } 477 // Re-create trie with newly generated root and updated database. 478 b.trie, err = trie.New(common.Hash{}, root, b.triedb) 479 if err != nil { 480 return err 481 } 482 // Pruning historical trie nodes if necessary. 483 if !b.disablePruning { 484 // Flush the triedb and track the latest trie nodes. 485 b.trieset.Clear() 486 b.triedb.Commit(root, false, func(hash common.Hash) { b.trieset.Add(hash) }) 487 488 it := b.trieTable.NewIterator(nil, nil) 489 defer it.Release() 490 491 var ( 492 deleted int 493 remaining int 494 t = time.Now() 495 ) 496 for it.Next() { 497 trimmed := bytes.TrimPrefix(it.Key(), []byte(BloomTrieTablePrefix)) 498 if !b.trieset.Contains(common.BytesToHash(trimmed)) { 499 b.trieTable.Delete(trimmed) 500 deleted += 1 501 } else { 502 remaining += 1 503 } 504 } 505 log.Debug("Prune historical bloom trie nodes", "deleted", deleted, "remaining", remaining, "elapsed", common.PrettyDuration(time.Since(t))) 506 } else { 507 b.triedb.Commit(root, false, nil) 508 } 509 sectionHead := b.sectionHeads[b.bloomTrieRatio-1] 510 StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root) 511 log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize)) 512 513 return nil 514 } 515 516 // Prune implements core.ChainIndexerBackend which deletes all 517 // bloombits which older than the specified threshold. 518 func (b *BloomTrieIndexerBackend) Prune(threshold uint64) error { 519 // Short circuit if the light pruning is disabled. 520 if b.disablePruning { 521 return nil 522 } 523 start := time.Now() 524 for i := uint(0); i < types.BloomBitLength; i++ { 525 rawdb.DeleteBloombits(b.diskdb, i, 0, threshold*b.bloomTrieRatio+b.bloomTrieRatio) 526 } 527 log.Debug("Prune history bloombits", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(start))) 528 return nil 529 }