package tangle import ( "github.com/dgraph-io/badger" "github.com/iotaledger/goshimmer/packages/database" "github.com/iotaledger/goshimmer/packages/datastructure" "github.com/iotaledger/goshimmer/packages/errors" "github.com/iotaledger/goshimmer/packages/model/value_transaction" "github.com/iotaledger/goshimmer/packages/node" "github.com/iotaledger/goshimmer/packages/typeutils" "github.com/iotaledger/iota.go/trinary" ) // region public api /////////////////////////////////////////////////////////////////////////////////////////////////// func GetTransaction(transactionHash trinary.Trytes, computeIfAbsent ...func(trinary.Trytes) *value_transaction.ValueTransaction) (result *value_transaction.ValueTransaction, err errors.IdentifiableError) { if cacheResult := transactionCache.ComputeIfAbsent(transactionHash, func() interface{} { if transaction, dbErr := getTransactionFromDatabase(transactionHash); dbErr != nil { err = dbErr return nil } else if transaction != nil { return transaction } else { if len(computeIfAbsent) >= 1 { return computeIfAbsent[0](transactionHash) } return nil } }); !typeutils.IsInterfaceNil(cacheResult) { result = cacheResult.(*value_transaction.ValueTransaction) } return } func ContainsTransaction(transactionHash trinary.Trytes) (result bool, err errors.IdentifiableError) { if transactionCache.Contains(transactionHash) { result = true } else { result, err = databaseContainsTransaction(transactionHash) } return } func StoreTransaction(transaction *value_transaction.ValueTransaction) { transactionCache.Set(transaction.GetHash(), transaction) } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// // region lru cache //////////////////////////////////////////////////////////////////////////////////////////////////// var transactionCache = datastructure.NewLRUCache(TRANSACTION_CACHE_SIZE, &datastructure.LRUCacheOptions{ EvictionCallback: onEvictTransaction, }) func onEvictTransaction(_ interface{}, value interface{}) { if evictedTransaction := value.(*value_transaction.ValueTransaction); evictedTransaction.GetModified() { go func(evictedTransaction *value_transaction.ValueTransaction) { if err := storeTransactionInDatabase(evictedTransaction); err != nil { panic(err) } }(evictedTransaction) } } const ( TRANSACTION_CACHE_SIZE = 50000 ) // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// // region database ///////////////////////////////////////////////////////////////////////////////////////////////////// var transactionDatabase database.Database func configureTransactionDatabase(plugin *node.Plugin) { if db, err := database.Get("transaction"); err != nil { panic(err) } else { transactionDatabase = db } } func storeTransactionInDatabase(transaction *value_transaction.ValueTransaction) errors.IdentifiableError { if transaction.GetModified() { if err := transactionDatabase.Set(typeutils.StringToBytes(transaction.GetHash()), transaction.MetaTransaction.GetBytes()); err != nil { return ErrDatabaseError.Derive(err, "failed to store transaction") } transaction.SetModified(false) } return nil } func getTransactionFromDatabase(transactionHash trinary.Trytes) (*value_transaction.ValueTransaction, errors.IdentifiableError) { txData, err := transactionDatabase.Get(typeutils.StringToBytes(transactionHash)) if err != nil { if err == badger.ErrKeyNotFound { return nil, nil } else { return nil, ErrDatabaseError.Derive(err, "failed to retrieve transaction") } } return value_transaction.FromBytes(txData), nil } func databaseContainsTransaction(transactionHash trinary.Trytes) (bool, errors.IdentifiableError) { if contains, err := transactionDatabase.Contains(typeutils.StringToBytes(transactionHash)); err != nil { return contains, ErrDatabaseError.Derive(err, "failed to check if the transaction exists") } else { return contains, nil } } // endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////