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