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