github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/core/atxi.go (about) 1 package core 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "math" 9 "os" 10 "os/signal" 11 "sort" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/ethereumproject/go-ethereum/common" 17 "github.com/ethereumproject/go-ethereum/core/types" 18 "github.com/ethereumproject/go-ethereum/ethdb" 19 "github.com/ethereumproject/go-ethereum/logger" 20 "github.com/ethereumproject/go-ethereum/logger/glog" 21 ) 22 23 var ( 24 errAtxiNotEnabled = errors.New("atxi not intialized") 25 errAtxiInvalidUse = errors.New("invalid parameters passed to ATXI") 26 27 txAddressIndexPrefix = []byte("atx-") 28 txAddressBookmarkKey = []byte("ATXIBookmark") 29 ) 30 31 type AtxiT struct { 32 Db ethdb.Database 33 AutoMode bool 34 Progress *AtxiProgressT 35 Step uint64 36 } 37 38 type AtxiProgressT struct { 39 Start, Stop, Current uint64 40 LastError error 41 } 42 43 func dbGetATXIBookmark(db ethdb.Database) uint64 { 44 v, err := db.Get(txAddressBookmarkKey) 45 if err != nil || v == nil { 46 return 0 47 } 48 i := binary.LittleEndian.Uint64(v) 49 return i 50 } 51 52 func (a *AtxiT) GetATXIBookmark() uint64 { 53 return dbGetATXIBookmark(a.Db) 54 } 55 56 func dbSetATXIBookmark(db ethdb.Database, i uint64) error { 57 bn := make([]byte, 8) 58 binary.LittleEndian.PutUint64(bn, i) 59 return db.Put(txAddressBookmarkKey, bn) 60 } 61 62 func (a *AtxiT) SetATXIBookmark(i uint64) error { 63 return dbSetATXIBookmark(a.Db, i) 64 } 65 66 // formatAddrTxIterator formats the index key prefix iterator, eg. atx-<address> 67 func formatAddrTxIterator(address common.Address) (iteratorPrefix []byte) { 68 iteratorPrefix = append(iteratorPrefix, txAddressIndexPrefix...) 69 iteratorPrefix = append(iteratorPrefix, address.Bytes()...) 70 return 71 } 72 73 // formatAddrTxBytesIndex formats the index key, eg. atx-<addr><blockNumber><t|f><s|c|><txhash> 74 // The values for these arguments should be of determinate length and format, see test TestFormatAndResolveAddrTxBytesKey 75 // for example. 76 func formatAddrTxBytesIndex(address, blockNumber, direction, kindof, txhash []byte) (key []byte) { 77 key = make([]byte, 0, 66) // 66 is the total capacity of the key = prefix(4)+addr(20)+blockNumber(8)+dir(1)+kindof(1)+txhash(32) 78 key = append(key, txAddressIndexPrefix...) 79 key = append(key, address...) 80 key = append(key, blockNumber...) 81 key = append(key, direction...) 82 key = append(key, kindof...) 83 key = append(key, txhash...) 84 return 85 } 86 87 // resolveAddrTxBytes resolves the index key to individual []byte values 88 func resolveAddrTxBytes(key []byte) (address, blockNumber, direction, kindof, txhash []byte) { 89 // prefix = key[:4] 90 address = key[4:24] // common.AddressLength = 20 91 blockNumber = key[24:32] // uint64 via little endian 92 direction = key[32:33] // == key[32] (1 byte) 93 kindof = key[33:34] 94 txhash = key[34:] 95 return 96 } 97 98 // WriteBlockAddTxIndexes writes atx-indexes for a given block. 99 func WriteBlockAddTxIndexes(indexDb ethdb.Database, block *types.Block) error { 100 batch := indexDb.NewBatch() 101 if _, err := putBlockAddrTxsToBatch(batch, block); err != nil { 102 return err 103 } 104 return batch.Write() 105 } 106 107 // putBlockAddrTxsToBatch formats and puts keys for a given block to a db Batch. 108 // Batch can be written afterward if no errors, ie. batch.Write() 109 func putBlockAddrTxsToBatch(putBatch ethdb.Batch, block *types.Block) (txsCount int, err error) { 110 for _, tx := range block.Transactions() { 111 txsCount++ 112 113 from, err := tx.From() 114 if err != nil { 115 return txsCount, err 116 } 117 to := tx.To() 118 // s: standard 119 // c: contract 120 txKindOf := []byte("s") 121 if to == nil || to.IsEmpty() { 122 to = &common.Address{} 123 txKindOf = []byte("c") 124 } 125 126 // Note that len 8 because uint64 guaranteed <= 8 bytes. 127 bn := make([]byte, 8) 128 binary.LittleEndian.PutUint64(bn, block.NumberU64()) 129 130 if err := putBatch.Put(formatAddrTxBytesIndex(from.Bytes(), bn, []byte("f"), txKindOf, tx.Hash().Bytes()), nil); err != nil { 131 return txsCount, err 132 } 133 if err := putBatch.Put(formatAddrTxBytesIndex(to.Bytes(), bn, []byte("t"), txKindOf, tx.Hash().Bytes()), nil); err != nil { 134 return txsCount, err 135 } 136 } 137 return txsCount, nil 138 } 139 140 type atxi struct { 141 blockN uint64 142 tx string 143 } 144 type sortableAtxis []atxi 145 146 // Len implements sort.Sort interface. 147 func (s sortableAtxis) Len() int { 148 return len(s) 149 } 150 151 // Less implements sort.Sort interface. 152 // By default newer transactions by blockNumber are first. 153 func (s sortableAtxis) Less(i, j int) bool { 154 return s[i].blockN > s[j].blockN 155 } 156 157 // Swap implements sort.Sort interface. 158 func (s sortableAtxis) Swap(i, j int) { 159 s[i], s[j] = s[j], s[i] 160 } 161 func (s sortableAtxis) TxStrings() []string { 162 var out = make([]string, 0, len(s)) 163 for _, str := range s { 164 out = append(out, str.tx) 165 } 166 return out 167 } 168 169 func BuildAddrTxIndex(bc *BlockChain, chainDB, indexDB ethdb.Database, startIndex, stopIndex, step uint64) error { 170 if bc.atxi == nil { 171 return errors.New("atxi not enabled for blockchain") 172 } 173 // initialize progress T if not yet 174 if bc.atxi.Progress == nil { 175 bc.atxi.Progress = &AtxiProgressT{} 176 } 177 // Use persistent placeholder in case start not spec'd 178 if startIndex == math.MaxUint64 { 179 startIndex = dbGetATXIBookmark(indexDB) 180 } 181 if step == math.MaxUint64 { 182 step = 10000 183 } 184 if stopIndex == 0 || stopIndex == math.MaxUint64 { 185 stopIndex = bc.CurrentBlock().NumberU64() 186 if n := bc.CurrentFastBlock().NumberU64(); n > stopIndex { 187 stopIndex = n 188 } 189 } 190 191 if stopIndex <= startIndex { 192 bc.atxi.Progress.LastError = fmt.Errorf("start must be prior to (smaller than) or equal to stop, got start=%d stop=%d", startIndex, stopIndex) 193 return bc.atxi.Progress.LastError 194 } 195 196 var block *types.Block 197 blockIndex := startIndex 198 block = bc.GetBlockByNumber(blockIndex) 199 if block == nil { 200 err := fmt.Errorf("block %d is nil", blockIndex) 201 bc.atxi.Progress.LastError = err 202 glog.Error(err) 203 return err 204 } 205 // sigc is a single-val channel for listening to program interrupt 206 var sigc = make(chan os.Signal, 1) 207 signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) 208 defer signal.Stop(sigc) 209 210 startTime := time.Now() 211 totalTxCount := uint64(0) 212 glog.D(logger.Error).Infoln("Address/tx indexing (atxi) start:", startIndex, "stop:", stopIndex, "step:", step) 213 bc.atxi.Progress.LastError = nil 214 bc.atxi.Progress.Current = startIndex 215 bc.atxi.Progress.Start = startIndex 216 bc.atxi.Progress.Stop = stopIndex 217 breaker := false 218 for i := startIndex; i < stopIndex; i = i + step { 219 if i+step > stopIndex { 220 step = stopIndex - i 221 breaker = true 222 } 223 224 stepStartTime := time.Now() 225 226 // It may seem weird to pass i, i+step, and step, but its just a "coincidence" 227 // The function could accepts a smaller step for batch putting (in this case, step), 228 // or a larger stopBlock (i+step), but this is just how this cmd is using the fn now 229 // We could mess around a little with exploring batch optimization... 230 txsCount, err := bc.WriteBlockAddrTxIndexesBatch(indexDB, i, i+step, step) 231 if err != nil { 232 bc.atxi.Progress.LastError = err 233 return err 234 } 235 totalTxCount += uint64(txsCount) 236 237 bc.atxi.Progress.Current = i + step 238 if bc.atxi.AutoMode { 239 if err := dbSetATXIBookmark(indexDB, bc.atxi.Progress.Current); err != nil { 240 bc.atxi.Progress.LastError = err 241 return err 242 } 243 } 244 245 glog.D(logger.Error).Infof("atxi-build: block %d / %d txs: %d took: %v %.2f bps %.2f txps", i+step, stopIndex, txsCount, time.Since(stepStartTime).Round(time.Millisecond), float64(step)/time.Since(stepStartTime).Seconds(), float64(txsCount)/time.Since(stepStartTime).Seconds()) 246 glog.V(logger.Info).Infof("atxi-build: block %d / %d txs: %d took: %v %.2f bps %.2f txps", i+step, stopIndex, txsCount, time.Since(stepStartTime).Round(time.Millisecond), float64(step)/time.Since(stepStartTime).Seconds(), float64(txsCount)/time.Since(stepStartTime).Seconds()) 247 248 // Listen for interrupts, nonblocking 249 select { 250 case s := <-sigc: 251 glog.D(logger.Info).Warnln("atxi build", "got interrupt:", s, "quitting") 252 return nil 253 default: 254 } 255 256 if breaker { 257 break 258 } 259 } 260 261 if bc.atxi.AutoMode { 262 if err := dbSetATXIBookmark(indexDB, stopIndex); err != nil { 263 bc.atxi.Progress.LastError = err 264 return err 265 } 266 } 267 268 // Print summary 269 totalBlocksF := float64(stopIndex - startIndex) 270 totalTxsF := float64(totalTxCount) 271 took := time.Since(startTime) 272 glog.D(logger.Error).Infof(`Finished atxi-build in %v: %d blocks (~ %.2f blocks/sec), %d txs (~ %.2f txs/sec)`, 273 took.Round(time.Second), 274 stopIndex-startIndex, 275 totalBlocksF/took.Seconds(), 276 totalTxCount, 277 totalTxsF/took.Seconds(), 278 ) 279 280 bc.atxi.Progress.LastError = nil 281 return nil 282 } 283 284 func (bc *BlockChain) GetATXIBuildProgress() (*AtxiProgressT, error) { 285 if bc.atxi == nil { 286 return nil, errors.New("atxi not enabled") 287 } 288 return bc.atxi.Progress, nil 289 } 290 291 // GetAddrTxs gets the indexed transactions for a given account address. 292 // 'reverse' means "oldest first" 293 func GetAddrTxs(db ethdb.Database, address common.Address, blockStartN uint64, blockEndN uint64, direction string, kindof string, paginationStart int, paginationEnd int, reverse bool) (txs []string, err error) { 294 errWithReason := func(e error, s string) error { 295 return fmt.Errorf("%v: %s", e, s) 296 } 297 298 // validate params 299 if len(direction) > 0 && !strings.Contains("btf", direction[:1]) { 300 err = errWithReason(errAtxiInvalidUse, "Address transactions list signature requires direction param to be empty string or [b|t|f] prefix (eg. both, to, or from)") 301 return 302 } 303 if len(kindof) > 0 && !strings.Contains("bsc", kindof[:1]) { 304 err = errWithReason(errAtxiInvalidUse, "Address transactions list signature requires 'kind of' param to be empty string or [s|c] prefix (eg. both, standard, or contract)") 305 return 306 } 307 if paginationStart > 0 && paginationEnd > 0 && paginationStart > paginationEnd { 308 err = errWithReason(errAtxiInvalidUse, "Pagination start must be less than or equal to pagination end params") 309 return 310 } 311 if paginationStart < 0 { 312 paginationStart = 0 313 } 314 315 // Have to cast to LevelDB to use iterator. Yuck. 316 ldb, ok := db.(*ethdb.LDBDatabase) 317 if !ok { 318 err = errWithReason(errors.New("internal interface error; please file a bug report"), "could not cast eth db to level db") 319 return nil, nil 320 } 321 322 // This will be the returnable. 323 var hashes []string 324 325 // Map direction -> byte 326 var wantDirectionB byte = 'b' 327 if len(direction) > 0 { 328 wantDirectionB = direction[0] 329 } 330 var wantKindOf byte = 'b' 331 if len(kindof) > 0 { 332 wantKindOf = kindof[0] 333 } 334 335 // Create address prefix for iteration. 336 prefix := ethdb.NewBytesPrefix(formatAddrTxIterator(address)) 337 it := ldb.NewIteratorRange(prefix) 338 339 var atxis sortableAtxis 340 341 for it.Next() { 342 key := it.Key() 343 344 _, blockNum, torf, k, txh := resolveAddrTxBytes(key) 345 346 bn := binary.LittleEndian.Uint64(blockNum) 347 348 // If atxi is smaller than blockstart, skip 349 if blockStartN > 0 && bn < blockStartN { 350 continue 351 } 352 // If atxi is greater than blockend, skip 353 if blockEndN > 0 && bn > blockEndN { 354 continue 355 } 356 // Ensure matching direction if spec'd 357 if wantDirectionB != 'b' && wantDirectionB != torf[0] { 358 continue 359 } 360 // Ensure filter for/agnostic transaction kind of (contract, standard, both) 361 if wantKindOf != 'b' && wantKindOf != k[0] { 362 continue 363 } 364 if len(hashes) > 0 { 365 366 } 367 tx := common.ToHex(txh) 368 atxis = append(atxis, atxi{blockN: bn, tx: tx}) 369 } 370 it.Release() 371 err = it.Error() 372 if err != nil { 373 return 374 } 375 376 handleSorting := func(s sortableAtxis) sortableAtxis { 377 if len(s) <= 1 { 378 return s 379 } 380 sort.Sort(s) // newest txs (by blockNumber) latest 381 if reverse { 382 for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { 383 s[i], s[j] = s[j], s[i] 384 } 385 } 386 if paginationStart > len(s) { 387 paginationStart = len(s) 388 } 389 if paginationEnd < 0 || paginationEnd > len(s) { 390 paginationEnd = len(s) 391 } 392 return s[paginationStart:paginationEnd] 393 } 394 txs = handleSorting(atxis).TxStrings() 395 return 396 } 397 398 // RmAddrTx removes all atxi indexes for a given tx in case of a transaction removal, eg. 399 // in the case of chain reorg. 400 // It isn't an elegant function, but not a top priority for optimization because of 401 // expected infrequency of it's being called. 402 func RmAddrTx(db ethdb.Database, tx *types.Transaction) error { 403 if tx == nil { 404 return nil 405 } 406 407 ldb, ok := db.(*ethdb.LDBDatabase) 408 if !ok { 409 return nil 410 } 411 412 txH := tx.Hash() 413 from, err := tx.From() 414 if err != nil { 415 return err 416 } 417 418 removals := [][]byte{} 419 420 // TODO: not DRY, could be refactored 421 pre := ethdb.NewBytesPrefix(formatAddrTxIterator(from)) 422 it := ldb.NewIteratorRange(pre) 423 for it.Next() { 424 key := it.Key() 425 _, _, _, _, txh := resolveAddrTxBytes(key) 426 if bytes.Compare(txH.Bytes(), txh) == 0 { 427 removals = append(removals, key) 428 break // because there can be only one 429 } 430 } 431 it.Release() 432 if e := it.Error(); e != nil { 433 return e 434 } 435 436 to := tx.To() 437 if to != nil { 438 toRef := *to 439 pre := ethdb.NewBytesPrefix(formatAddrTxIterator(toRef)) 440 it := ldb.NewIteratorRange(pre) 441 for it.Next() { 442 key := it.Key() 443 _, _, _, _, txh := resolveAddrTxBytes(key) 444 if bytes.Compare(txH.Bytes(), txh) == 0 { 445 removals = append(removals, key) 446 break // because there can be only one 447 } 448 } 449 it.Release() 450 if e := it.Error(); e != nil { 451 return e 452 } 453 } 454 455 for _, r := range removals { 456 if err := db.Delete(r); err != nil { 457 return err 458 } 459 } 460 return nil 461 }