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