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  }