github.com/ubiq/go-ethereum@v3.0.1+incompatible/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 "context" 21 "encoding/binary" 22 "errors" 23 "fmt" 24 "math/big" 25 "time" 26 27 "github.com/ubiq/go-ubiq/common" 28 "github.com/ubiq/go-ubiq/common/bitutil" 29 "github.com/ubiq/go-ubiq/core" 30 "github.com/ubiq/go-ubiq/core/rawdb" 31 "github.com/ubiq/go-ubiq/core/types" 32 "github.com/ubiq/go-ubiq/ethdb" 33 "github.com/ubiq/go-ubiq/log" 34 "github.com/ubiq/go-ubiq/params" 35 "github.com/ubiq/go-ubiq/rlp" 36 "github.com/ubiq/go-ubiq/trie" 37 ) 38 39 // IndexerConfig includes a set of configs for chain indexers. 40 type IndexerConfig struct { 41 // The block frequency for creating CHTs. 42 ChtSize uint64 43 44 // A special auxiliary field represents client's chtsize for server config, otherwise represents server's chtsize. 45 PairChtSize uint64 46 47 // The number of confirmations needed to generate/accept a canonical hash help trie. 48 ChtConfirms uint64 49 50 // The block frequency for creating new bloom bits. 51 BloomSize uint64 52 53 // The number of confirmation needed before a bloom section is considered probably final and its rotated bits 54 // are calculated. 55 BloomConfirms uint64 56 57 // The block frequency for creating BloomTrie. 58 BloomTrieSize uint64 59 60 // The number of confirmations needed to generate/accept a bloom trie. 61 BloomTrieConfirms uint64 62 } 63 64 var ( 65 // DefaultServerIndexerConfig wraps a set of configs as a default indexer config for server side. 66 DefaultServerIndexerConfig = &IndexerConfig{ 67 ChtSize: params.CHTFrequencyServer, 68 PairChtSize: params.CHTFrequencyClient, 69 ChtConfirms: params.HelperTrieProcessConfirmations, 70 BloomSize: params.BloomBitsBlocks, 71 BloomConfirms: params.BloomConfirms, 72 BloomTrieSize: params.BloomTrieFrequency, 73 BloomTrieConfirms: params.HelperTrieProcessConfirmations, 74 } 75 // DefaultClientIndexerConfig wraps a set of configs as a default indexer config for client side. 76 DefaultClientIndexerConfig = &IndexerConfig{ 77 ChtSize: params.CHTFrequencyClient, 78 PairChtSize: params.CHTFrequencyServer, 79 ChtConfirms: params.HelperTrieConfirmations, 80 BloomSize: params.BloomBitsBlocksClient, 81 BloomConfirms: params.HelperTrieConfirmations, 82 BloomTrieSize: params.BloomTrieFrequency, 83 BloomTrieConfirms: params.HelperTrieConfirmations, 84 } 85 // TestServerIndexerConfig wraps a set of configs as a test indexer config for server side. 86 TestServerIndexerConfig = &IndexerConfig{ 87 ChtSize: 64, 88 PairChtSize: 512, 89 ChtConfirms: 4, 90 BloomSize: 64, 91 BloomConfirms: 4, 92 BloomTrieSize: 512, 93 BloomTrieConfirms: 4, 94 } 95 // TestClientIndexerConfig wraps a set of configs as a test indexer config for client side. 96 TestClientIndexerConfig = &IndexerConfig{ 97 ChtSize: 512, 98 PairChtSize: 64, 99 ChtConfirms: 32, 100 BloomSize: 512, 101 BloomConfirms: 32, 102 BloomTrieSize: 512, 103 BloomTrieConfirms: 32, 104 } 105 ) 106 107 // trustedCheckpoints associates each known checkpoint with the genesis hash of the chain it belongs to 108 var trustedCheckpoints = map[common.Hash]*params.TrustedCheckpoint{ 109 params.MainnetGenesisHash: params.MainnetTrustedCheckpoint, 110 params.TestnetGenesisHash: params.TestnetTrustedCheckpoint, 111 } 112 113 var ( 114 ErrNoTrustedCht = errors.New("no trusted canonical hash trie") 115 ErrNoTrustedBloomTrie = errors.New("no trusted bloom trie") 116 ErrNoHeader = errors.New("header not found") 117 chtPrefix = []byte("chtRoot-") // chtPrefix + chtNum (uint64 big endian) -> trie root hash 118 ChtTablePrefix = "cht-" 119 ) 120 121 // ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format 122 type ChtNode struct { 123 Hash common.Hash 124 Td *big.Int 125 } 126 127 // GetChtRoot reads the CHT root associated to the given section from the database 128 // Note that sectionIdx is specified according to LES/1 CHT section size. 129 func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { 130 var encNumber [8]byte 131 binary.BigEndian.PutUint64(encNumber[:], sectionIdx) 132 data, _ := db.Get(append(append(chtPrefix, encNumber[:]...), sectionHead.Bytes()...)) 133 return common.BytesToHash(data) 134 } 135 136 // StoreChtRoot writes the CHT root associated to the given section into the database 137 // Note that sectionIdx is specified according to LES/1 CHT section size. 138 func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { 139 var encNumber [8]byte 140 binary.BigEndian.PutUint64(encNumber[:], sectionIdx) 141 db.Put(append(append(chtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) 142 } 143 144 // ChtIndexerBackend implements core.ChainIndexerBackend. 145 type ChtIndexerBackend struct { 146 diskdb, trieTable ethdb.Database 147 odr OdrBackend 148 triedb *trie.Database 149 section, sectionSize uint64 150 lastHash common.Hash 151 trie *trie.Trie 152 } 153 154 // NewChtIndexer creates a Cht chain indexer 155 func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64) *core.ChainIndexer { 156 trieTable := ethdb.NewTable(db, ChtTablePrefix) 157 backend := &ChtIndexerBackend{ 158 diskdb: db, 159 odr: odr, 160 trieTable: trieTable, 161 triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down 162 sectionSize: size, 163 } 164 return core.NewChainIndexer(db, ethdb.NewTable(db, "chtIndex-"), backend, size, confirms, time.Millisecond*100, "cht") 165 } 166 167 // fetchMissingNodes tries to retrieve the last entry of the latest trusted CHT from the 168 // ODR backend in order to be able to add new entries and calculate subsequent root hashes 169 func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { 170 batch := c.trieTable.NewBatch() 171 r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1, Config: c.odr.IndexerConfig()} 172 for { 173 err := c.odr.Retrieve(ctx, r) 174 switch err { 175 case nil: 176 r.Proof.Store(batch) 177 return batch.Write() 178 case ErrNoPeers: 179 // if there are no peers to serve, retry later 180 select { 181 case <-ctx.Done(): 182 return ctx.Err() 183 case <-time.After(time.Second * 10): 184 // stay in the loop and try again 185 } 186 default: 187 return err 188 } 189 } 190 } 191 192 // Reset implements core.ChainIndexerBackend 193 func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { 194 var root common.Hash 195 if section > 0 { 196 root = GetChtRoot(c.diskdb, section-1, lastSectionHead) 197 } 198 var err error 199 c.trie, err = trie.New(root, c.triedb) 200 201 if err != nil && c.odr != nil { 202 err = c.fetchMissingNodes(ctx, section, root) 203 if err == nil { 204 c.trie, err = trie.New(root, c.triedb) 205 } 206 } 207 208 c.section = section 209 return err 210 } 211 212 // Process implements core.ChainIndexerBackend 213 func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) error { 214 hash, num := header.Hash(), header.Number.Uint64() 215 c.lastHash = hash 216 217 td := rawdb.ReadTd(c.diskdb, hash, num) 218 if td == nil { 219 panic(nil) 220 } 221 var encNumber [8]byte 222 binary.BigEndian.PutUint64(encNumber[:], num) 223 data, _ := rlp.EncodeToBytes(ChtNode{hash, td}) 224 c.trie.Update(encNumber[:], data) 225 return nil 226 } 227 228 // Commit implements core.ChainIndexerBackend 229 func (c *ChtIndexerBackend) Commit() error { 230 root, err := c.trie.Commit(nil) 231 if err != nil { 232 return err 233 } 234 c.triedb.Commit(root, false) 235 236 if ((c.section+1)*c.sectionSize)%params.CHTFrequencyClient == 0 { 237 log.Info("Storing CHT", "section", c.section*c.sectionSize/params.CHTFrequencyClient, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root)) 238 } 239 StoreChtRoot(c.diskdb, c.section, c.lastHash, root) 240 return nil 241 } 242 243 var ( 244 bloomTriePrefix = []byte("bltRoot-") // bloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash 245 BloomTrieTablePrefix = "blt-" 246 ) 247 248 // GetBloomTrieRoot reads the BloomTrie root assoctiated to the given section from the database 249 func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { 250 var encNumber [8]byte 251 binary.BigEndian.PutUint64(encNumber[:], sectionIdx) 252 data, _ := db.Get(append(append(bloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...)) 253 return common.BytesToHash(data) 254 } 255 256 // StoreBloomTrieRoot writes the BloomTrie root assoctiated to the given section into the database 257 func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { 258 var encNumber [8]byte 259 binary.BigEndian.PutUint64(encNumber[:], sectionIdx) 260 db.Put(append(append(bloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) 261 } 262 263 // BloomTrieIndexerBackend implements core.ChainIndexerBackend 264 type BloomTrieIndexerBackend struct { 265 diskdb, trieTable ethdb.Database 266 triedb *trie.Database 267 odr OdrBackend 268 section uint64 269 parentSize uint64 270 size uint64 271 bloomTrieRatio uint64 272 trie *trie.Trie 273 sectionHeads []common.Hash 274 } 275 276 // NewBloomTrieIndexer creates a BloomTrie chain indexer 277 func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64) *core.ChainIndexer { 278 trieTable := ethdb.NewTable(db, BloomTrieTablePrefix) 279 backend := &BloomTrieIndexerBackend{ 280 diskdb: db, 281 odr: odr, 282 trieTable: trieTable, 283 triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down 284 parentSize: parentSize, 285 size: size, 286 } 287 backend.bloomTrieRatio = size / parentSize 288 backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio) 289 return core.NewChainIndexer(db, ethdb.NewTable(db, "bltIndex-"), backend, size, 0, time.Millisecond*100, "bloomtrie") 290 } 291 292 // fetchMissingNodes tries to retrieve the last entries of the latest trusted bloom trie from the 293 // ODR backend in order to be able to add new entries and calculate subsequent root hashes 294 func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { 295 indexCh := make(chan uint, types.BloomBitLength) 296 type res struct { 297 nodes *NodeSet 298 err error 299 } 300 resCh := make(chan res, types.BloomBitLength) 301 for i := 0; i < 20; i++ { 302 go func() { 303 for bitIndex := range indexCh { 304 r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIndexList: []uint64{section - 1}, Config: b.odr.IndexerConfig()} 305 for { 306 if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers { 307 // if there are no peers to serve, retry later 308 select { 309 case <-ctx.Done(): 310 resCh <- res{nil, ctx.Err()} 311 return 312 case <-time.After(time.Second * 10): 313 // stay in the loop and try again 314 } 315 } else { 316 resCh <- res{r.Proofs, err} 317 break 318 } 319 } 320 } 321 }() 322 } 323 324 for i := uint(0); i < types.BloomBitLength; i++ { 325 indexCh <- i 326 } 327 close(indexCh) 328 batch := b.trieTable.NewBatch() 329 for i := uint(0); i < types.BloomBitLength; i++ { 330 res := <-resCh 331 if res.err != nil { 332 return res.err 333 } 334 res.nodes.Store(batch) 335 } 336 return batch.Write() 337 } 338 339 // Reset implements core.ChainIndexerBackend 340 func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { 341 var root common.Hash 342 if section > 0 { 343 root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead) 344 } 345 var err error 346 b.trie, err = trie.New(root, b.triedb) 347 if err != nil && b.odr != nil { 348 err = b.fetchMissingNodes(ctx, section, root) 349 if err == nil { 350 b.trie, err = trie.New(root, b.triedb) 351 } 352 } 353 b.section = section 354 return err 355 } 356 357 // Process implements core.ChainIndexerBackend 358 func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Header) error { 359 num := header.Number.Uint64() - b.section*b.size 360 if (num+1)%b.parentSize == 0 { 361 b.sectionHeads[num/b.parentSize] = header.Hash() 362 } 363 return nil 364 } 365 366 // Commit implements core.ChainIndexerBackend 367 func (b *BloomTrieIndexerBackend) Commit() error { 368 var compSize, decompSize uint64 369 370 for i := uint(0); i < types.BloomBitLength; i++ { 371 var encKey [10]byte 372 binary.BigEndian.PutUint16(encKey[0:2], uint16(i)) 373 binary.BigEndian.PutUint64(encKey[2:10], b.section) 374 var decomp []byte 375 for j := uint64(0); j < b.bloomTrieRatio; j++ { 376 data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j]) 377 if err != nil { 378 return err 379 } 380 decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSize/8)) 381 if err2 != nil { 382 return err2 383 } 384 decomp = append(decomp, decompData...) 385 } 386 comp := bitutil.CompressBytes(decomp) 387 388 decompSize += uint64(len(decomp)) 389 compSize += uint64(len(comp)) 390 if len(comp) > 0 { 391 b.trie.Update(encKey[:], comp) 392 } else { 393 b.trie.Delete(encKey[:]) 394 } 395 } 396 root, err := b.trie.Commit(nil) 397 if err != nil { 398 return err 399 } 400 b.triedb.Commit(root, false) 401 402 sectionHead := b.sectionHeads[b.bloomTrieRatio-1] 403 log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize)) 404 StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root) 405 return nil 406 }