github.com/koko1123/flow-go-1@v0.29.6/storage/badger/transaction_results.go (about)

     1  package badger
     2  
     3  import (
     4  	"encoding/binary"
     5  	"encoding/hex"
     6  	"fmt"
     7  
     8  	"github.com/dgraph-io/badger/v3"
     9  
    10  	"github.com/koko1123/flow-go-1/model/flow"
    11  	"github.com/koko1123/flow-go-1/module"
    12  	"github.com/koko1123/flow-go-1/module/metrics"
    13  	"github.com/koko1123/flow-go-1/storage"
    14  	"github.com/koko1123/flow-go-1/storage/badger/operation"
    15  )
    16  
    17  type TransactionResults struct {
    18  	db         *badger.DB
    19  	cache      *Cache
    20  	indexCache *Cache
    21  	blockCache *Cache
    22  }
    23  
    24  func KeyFromBlockIDTransactionID(blockID flow.Identifier, txID flow.Identifier) string {
    25  	return fmt.Sprintf("%x%x", blockID, txID)
    26  }
    27  
    28  func KeyFromBlockIDIndex(blockID flow.Identifier, txIndex uint32) string {
    29  	idData := make([]byte, 4) //uint32 fits into 4 bytes
    30  	binary.BigEndian.PutUint32(idData, txIndex)
    31  	return fmt.Sprintf("%x%x", blockID, idData)
    32  }
    33  
    34  func KeyFromBlockID(blockID flow.Identifier) string {
    35  	return blockID.String()
    36  }
    37  
    38  func KeyToBlockIDTransactionID(key string) (flow.Identifier, flow.Identifier, error) {
    39  	blockIDStr := key[:64]
    40  	txIDStr := key[64:]
    41  	blockID, err := flow.HexStringToIdentifier(blockIDStr)
    42  	if err != nil {
    43  		return flow.ZeroID, flow.ZeroID, fmt.Errorf("could not get block ID: %w", err)
    44  	}
    45  
    46  	txID, err := flow.HexStringToIdentifier(txIDStr)
    47  	if err != nil {
    48  		return flow.ZeroID, flow.ZeroID, fmt.Errorf("could not get transaction id: %w", err)
    49  	}
    50  
    51  	return blockID, txID, nil
    52  }
    53  
    54  func KeyToBlockIDIndex(key string) (flow.Identifier, uint32, error) {
    55  	blockIDStr := key[:64]
    56  	indexStr := key[64:]
    57  	blockID, err := flow.HexStringToIdentifier(blockIDStr)
    58  	if err != nil {
    59  		return flow.ZeroID, 0, fmt.Errorf("could not get block ID: %w", err)
    60  	}
    61  
    62  	txIndexBytes, err := hex.DecodeString(indexStr)
    63  	if err != nil {
    64  		return flow.ZeroID, 0, fmt.Errorf("could not get transaction index: %w", err)
    65  	}
    66  	if len(txIndexBytes) != 4 {
    67  		return flow.ZeroID, 0, fmt.Errorf("could not get transaction index - invalid length: %d", len(txIndexBytes))
    68  	}
    69  
    70  	txIndex := binary.BigEndian.Uint32(txIndexBytes)
    71  
    72  	return blockID, txIndex, nil
    73  }
    74  
    75  func KeyToBlockID(key string) (flow.Identifier, error) {
    76  
    77  	blockID, err := flow.HexStringToIdentifier(key)
    78  	if err != nil {
    79  		return flow.ZeroID, fmt.Errorf("could not get block ID: %w", err)
    80  	}
    81  
    82  	return blockID, err
    83  }
    84  
    85  func NewTransactionResults(collector module.CacheMetrics, db *badger.DB, transactionResultsCacheSize uint) *TransactionResults {
    86  	retrieve := func(key interface{}) func(tx *badger.Txn) (interface{}, error) {
    87  		var txResult flow.TransactionResult
    88  		return func(tx *badger.Txn) (interface{}, error) {
    89  
    90  			blockID, txID, err := KeyToBlockIDTransactionID(key.(string))
    91  			if err != nil {
    92  				return nil, fmt.Errorf("could not convert key: %w", err)
    93  			}
    94  
    95  			err = operation.RetrieveTransactionResult(blockID, txID, &txResult)(tx)
    96  			if err != nil {
    97  				return nil, handleError(err, flow.TransactionResult{})
    98  			}
    99  			return txResult, nil
   100  		}
   101  	}
   102  	retrieveIndex := func(key interface{}) func(tx *badger.Txn) (interface{}, error) {
   103  		var txResult flow.TransactionResult
   104  		return func(tx *badger.Txn) (interface{}, error) {
   105  
   106  			blockID, txIndex, err := KeyToBlockIDIndex(key.(string))
   107  			if err != nil {
   108  				return nil, fmt.Errorf("could not convert index key: %w", err)
   109  			}
   110  
   111  			err = operation.RetrieveTransactionResultByIndex(blockID, txIndex, &txResult)(tx)
   112  			if err != nil {
   113  				return nil, handleError(err, flow.TransactionResult{})
   114  			}
   115  			return txResult, nil
   116  		}
   117  	}
   118  	retrieveForBlock := func(key interface{}) func(tx *badger.Txn) (interface{}, error) {
   119  		var txResults []flow.TransactionResult
   120  		return func(tx *badger.Txn) (interface{}, error) {
   121  
   122  			blockID, err := KeyToBlockID(key.(string))
   123  			if err != nil {
   124  				return nil, fmt.Errorf("could not convert index key: %w", err)
   125  			}
   126  
   127  			err = operation.LookupTransactionResultsByBlockIDUsingIndex(blockID, &txResults)(tx)
   128  			if err != nil {
   129  				return nil, handleError(err, flow.TransactionResult{})
   130  			}
   131  			return txResults, nil
   132  		}
   133  	}
   134  	return &TransactionResults{
   135  		db: db,
   136  		cache: newCache(collector, metrics.ResourceTransactionResults,
   137  			withLimit(transactionResultsCacheSize),
   138  			withStore(noopStore),
   139  			withRetrieve(retrieve),
   140  		),
   141  		indexCache: newCache(collector, metrics.ResourceTransactionResultIndices,
   142  			withLimit(transactionResultsCacheSize),
   143  			withStore(noopStore),
   144  			withRetrieve(retrieveIndex),
   145  		),
   146  		blockCache: newCache(collector, metrics.ResourceTransactionResultIndices,
   147  			withLimit(transactionResultsCacheSize),
   148  			withStore(noopStore),
   149  			withRetrieve(retrieveForBlock),
   150  		),
   151  	}
   152  }
   153  
   154  // BatchStore will store the transaction results for the given block ID in a batch
   155  func (tr *TransactionResults) BatchStore(blockID flow.Identifier, transactionResults []flow.TransactionResult, batch storage.BatchStorage) error {
   156  	writeBatch := batch.GetWriter()
   157  
   158  	for i, result := range transactionResults {
   159  		err := operation.BatchInsertTransactionResult(blockID, &result)(writeBatch)
   160  		if err != nil {
   161  			return fmt.Errorf("cannot batch insert tx result: %w", err)
   162  		}
   163  
   164  		err = operation.BatchIndexTransactionResult(blockID, uint32(i), &result)(writeBatch)
   165  		if err != nil {
   166  			return fmt.Errorf("cannot batch index tx result: %w", err)
   167  		}
   168  	}
   169  
   170  	batch.OnSucceed(func() {
   171  		for i, result := range transactionResults {
   172  			key := KeyFromBlockIDTransactionID(blockID, result.TransactionID)
   173  			// cache for each transaction, so that it's faster to retrieve
   174  			tr.cache.Insert(key, result)
   175  
   176  			index := uint32(i)
   177  
   178  			keyIndex := KeyFromBlockIDIndex(blockID, index)
   179  			tr.indexCache.Insert(keyIndex, result)
   180  		}
   181  
   182  		key := KeyFromBlockID(blockID)
   183  		tr.blockCache.Insert(key, transactionResults)
   184  	})
   185  	return nil
   186  }
   187  
   188  // ByBlockIDTransactionID returns the runtime transaction result for the given block ID and transaction ID
   189  func (tr *TransactionResults) ByBlockIDTransactionID(blockID flow.Identifier, txID flow.Identifier) (*flow.TransactionResult, error) {
   190  	tx := tr.db.NewTransaction(false)
   191  	defer tx.Discard()
   192  	key := KeyFromBlockIDTransactionID(blockID, txID)
   193  	val, err := tr.cache.Get(key)(tx)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	transactionResult, ok := val.(flow.TransactionResult)
   198  	if !ok {
   199  		return nil, fmt.Errorf("could not convert transaction result: %w", err)
   200  	}
   201  	return &transactionResult, nil
   202  }
   203  
   204  // ByBlockIDTransactionIndex returns the runtime transaction result for the given block ID and transaction index
   205  func (tr *TransactionResults) ByBlockIDTransactionIndex(blockID flow.Identifier, txIndex uint32) (*flow.TransactionResult, error) {
   206  	tx := tr.db.NewTransaction(false)
   207  	defer tx.Discard()
   208  	key := KeyFromBlockIDIndex(blockID, txIndex)
   209  	val, err := tr.indexCache.Get(key)(tx)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	transactionResult, ok := val.(flow.TransactionResult)
   214  	if !ok {
   215  		return nil, fmt.Errorf("could not convert transaction result: %w", err)
   216  	}
   217  	return &transactionResult, nil
   218  }
   219  
   220  // ByBlockID gets all transaction results for a block, ordered by transaction index
   221  func (tr *TransactionResults) ByBlockID(blockID flow.Identifier) ([]flow.TransactionResult, error) {
   222  	tx := tr.db.NewTransaction(false)
   223  	defer tx.Discard()
   224  	key := KeyFromBlockID(blockID)
   225  	val, err := tr.blockCache.Get(key)(tx)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	transactionResults, ok := val.([]flow.TransactionResult)
   230  	if !ok {
   231  		return nil, fmt.Errorf("could not convert transaction result: %w", err)
   232  	}
   233  	return transactionResults, nil
   234  }
   235  
   236  // RemoveByBlockID removes transaction results by block ID
   237  func (tr *TransactionResults) RemoveByBlockID(blockID flow.Identifier) error {
   238  	return tr.db.Update(operation.RemoveTransactionResultsByBlockID(blockID))
   239  }
   240  
   241  // BatchRemoveByBlockID batch removes transaction results by block ID
   242  func (tr *TransactionResults) BatchRemoveByBlockID(blockID flow.Identifier, batch storage.BatchStorage) error {
   243  	writeBatch := batch.GetWriter()
   244  	return tr.db.View(operation.BatchRemoveTransactionResultsByBlockID(blockID, writeBatch))
   245  }