diff --git a/packages/model/approvers/approvers.go b/packages/model/approvers/approvers.go index 0633e0f55a9a88b41fac9a04ab88a8d74b68420d..3f74b5ea7cebf3b1a6b62e2707b4c136296d058f 100644 --- a/packages/model/approvers/approvers.go +++ b/packages/model/approvers/approvers.go @@ -55,6 +55,13 @@ func (approvers *Approvers) GetHash() (result ternary.Trinary) { return } +func (approvers *Approvers) GetModified() bool { + return true +} + +func (approvers *Approvers) SetModified(modified bool) { +} + func (approvers *Approvers) Marshal() (result []byte) { result = make([]byte, MARSHALED_APPROVERS_MIN_SIZE+len(approvers.hashes)*MARSHALED_APPROVERS_HASH_SIZE) diff --git a/packages/model/transactionmetadata/const.go b/packages/model/transactionmetadata/const.go new file mode 100644 index 0000000000000000000000000000000000000000..100a51ddaafa9632f30ed2d5d7888f116860df01 --- /dev/null +++ b/packages/model/transactionmetadata/const.go @@ -0,0 +1,21 @@ +package transactionmetadata + +// region constants and variables ////////////////////////////////////////////////////////////////////////////////////// + +const ( + MARSHALED_HASH_START = 0 + MARSHALED_RECEIVED_TIME_START = MARSHALED_HASH_END + MARSHALED_FLAGS_START = MARSHALED_RECEIVED_TIME_END + + MARSHALED_HASH_END = MARSHALED_HASH_START + MARSHALED_HASH_SIZE + MARSHALED_RECEIVED_TIME_END = MARSHALED_RECEIVED_TIME_START + MARSHALED_RECEIVED_TIME_SIZE + MARSHALED_FLAGS_END = MARSHALED_FLAGS_START + MARSHALED_FLAGS_SIZE + + MARSHALED_HASH_SIZE = 81 + MARSHALED_RECEIVED_TIME_SIZE = 15 + MARSHALED_FLAGS_SIZE = 1 + + MARSHALED_TOTAL_SIZE = MARSHALED_FLAGS_END +) + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/model/transactionmetadata/errors.go b/packages/model/transactionmetadata/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..b8cfc2239971e878bf9943290b83194bb7972a2a --- /dev/null +++ b/packages/model/transactionmetadata/errors.go @@ -0,0 +1,12 @@ +package transactionmetadata + +import "github.com/iotaledger/goshimmer/packages/errors" + +// region errors /////////////////////////////////////////////////////////////////////////////////////////////////////// + +var ( + ErrUnmarshalFailed = errors.Wrap(errors.New("unmarshall failed"), "input data is corrupted") + ErrMarshallFailed = errors.Wrap(errors.New("marshal failed"), "the source object contains invalid values") +) + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/model/transactionmetadata/transactionmetadata.go b/packages/model/transactionmetadata/transactionmetadata.go new file mode 100644 index 0000000000000000000000000000000000000000..df255a22d3f39b75f09113e20fd7b2b969ddb0cc --- /dev/null +++ b/packages/model/transactionmetadata/transactionmetadata.go @@ -0,0 +1,251 @@ +package transactionmetadata + +import ( + "sync" + "time" + + "github.com/iotaledger/goshimmer/packages/bitutils" + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/typeutils" +) + +// region type definition and constructor ////////////////////////////////////////////////////////////////////////////// + +type TransactionMetadata struct { + hash ternary.Trinary + hashMutex sync.RWMutex + receivedTime time.Time + receivedTimeMutex sync.RWMutex + solid bool + solidMutex sync.RWMutex + liked bool + likedMutex sync.RWMutex + finalized bool + finalizedMutex sync.RWMutex + modified bool + modifiedMutex sync.RWMutex +} + +func New(hash ternary.Trinary) *TransactionMetadata { + return &TransactionMetadata{ + hash: hash, + receivedTime: time.Now(), + solid: false, + liked: false, + finalized: false, + modified: true, + } +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region getters and setters ////////////////////////////////////////////////////////////////////////////////////////// + +func (metadata *TransactionMetadata) GetHash() ternary.Trinary { + metadata.hashMutex.RLock() + defer metadata.hashMutex.RUnlock() + + return metadata.hash +} + +func (metadata *TransactionMetadata) SetHash(hash ternary.Trinary) { + metadata.hashMutex.RLock() + if metadata.hash != hash { + metadata.hashMutex.RUnlock() + metadata.hashMutex.Lock() + defer metadata.hashMutex.Unlock() + if metadata.hash != hash { + metadata.hash = hash + + metadata.SetModified(true) + } + } else { + metadata.hashMutex.RUnlock() + } +} + +func (metadata *TransactionMetadata) GetReceivedTime() time.Time { + metadata.receivedTimeMutex.RLock() + defer metadata.receivedTimeMutex.RUnlock() + + return metadata.receivedTime +} + +func (metadata *TransactionMetadata) SetReceivedTime(receivedTime time.Time) { + metadata.receivedTimeMutex.RLock() + if metadata.receivedTime != receivedTime { + metadata.receivedTimeMutex.RUnlock() + metadata.receivedTimeMutex.Lock() + defer metadata.receivedTimeMutex.Unlock() + if metadata.receivedTime != receivedTime { + metadata.receivedTime = receivedTime + + metadata.SetModified(true) + } + } else { + metadata.receivedTimeMutex.RUnlock() + } +} + +func (metadata *TransactionMetadata) GetSolid() bool { + metadata.solidMutex.RLock() + defer metadata.solidMutex.RUnlock() + + return metadata.solid +} + +func (metadata *TransactionMetadata) SetSolid(solid bool) bool { + metadata.solidMutex.RLock() + if metadata.solid != solid { + metadata.solidMutex.RUnlock() + metadata.solidMutex.Lock() + defer metadata.solidMutex.Unlock() + if metadata.solid != solid { + metadata.solid = solid + + metadata.SetModified(true) + + return true + } + } else { + metadata.solidMutex.RUnlock() + } + + return false +} + +func (metadata *TransactionMetadata) GetLiked() bool { + metadata.likedMutex.RLock() + defer metadata.likedMutex.RUnlock() + + return metadata.liked +} + +func (metadata *TransactionMetadata) SetLiked(liked bool) { + metadata.likedMutex.RLock() + if metadata.liked != liked { + metadata.likedMutex.RUnlock() + metadata.likedMutex.Lock() + defer metadata.likedMutex.Unlock() + if metadata.liked != liked { + metadata.liked = liked + + metadata.SetModified(true) + } + } else { + metadata.likedMutex.RUnlock() + } +} + +func (metadata *TransactionMetadata) GetFinalized() bool { + metadata.finalizedMutex.RLock() + defer metadata.finalizedMutex.RUnlock() + + return metadata.finalized +} + +func (metadata *TransactionMetadata) SetFinalized(finalized bool) { + metadata.finalizedMutex.RLock() + if metadata.finalized != finalized { + metadata.finalizedMutex.RUnlock() + metadata.finalizedMutex.Lock() + defer metadata.finalizedMutex.Unlock() + if metadata.finalized != finalized { + metadata.finalized = finalized + + metadata.SetModified(true) + } + } else { + metadata.finalizedMutex.RUnlock() + } +} + +// returns true if the transaction contains unsaved changes (supports concurrency) +func (metadata *TransactionMetadata) GetModified() bool { + metadata.modifiedMutex.RLock() + defer metadata.modifiedMutex.RUnlock() + + return metadata.modified +} + +// sets the modified flag which controls if a transaction is going to be saved (supports concurrency) +func (metadata *TransactionMetadata) SetModified(modified bool) { + metadata.modifiedMutex.Lock() + defer metadata.modifiedMutex.Unlock() + + metadata.modified = modified +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region marshaling functions ///////////////////////////////////////////////////////////////////////////////////////// + +func (metadata *TransactionMetadata) Marshal() ([]byte, errors.IdentifiableError) { + marshaledMetadata := make([]byte, MARSHALED_TOTAL_SIZE) + + metadata.receivedTimeMutex.RLock() + defer metadata.receivedTimeMutex.RUnlock() + metadata.solidMutex.RLock() + defer metadata.solidMutex.RUnlock() + metadata.likedMutex.RLock() + defer metadata.likedMutex.RUnlock() + metadata.finalizedMutex.RLock() + defer metadata.finalizedMutex.RUnlock() + + copy(marshaledMetadata[MARSHALED_HASH_START:MARSHALED_HASH_END], metadata.hash.CastToBytes()) + + marshaledReceivedTime, err := metadata.receivedTime.MarshalBinary() + if err != nil { + return nil, ErrMarshallFailed.Derive(err, "failed to marshal received time") + } + copy(marshaledMetadata[MARSHALED_RECEIVED_TIME_START:MARSHALED_RECEIVED_TIME_END], marshaledReceivedTime) + + var booleanFlags bitutils.BitMask + if metadata.solid { + booleanFlags = booleanFlags.SetFlag(0) + } + if metadata.liked { + booleanFlags = booleanFlags.SetFlag(1) + } + if metadata.finalized { + booleanFlags = booleanFlags.SetFlag(2) + } + marshaledMetadata[MARSHALED_FLAGS_START] = byte(booleanFlags) + + return marshaledMetadata, nil +} + +func (metadata *TransactionMetadata) Unmarshal(data []byte) errors.IdentifiableError { + metadata.hashMutex.Lock() + defer metadata.hashMutex.Unlock() + metadata.receivedTimeMutex.Lock() + defer metadata.receivedTimeMutex.Unlock() + metadata.solidMutex.Lock() + defer metadata.solidMutex.Unlock() + metadata.likedMutex.Lock() + defer metadata.likedMutex.Unlock() + metadata.finalizedMutex.Lock() + defer metadata.finalizedMutex.Unlock() + + metadata.hash = ternary.Trinary(typeutils.BytesToString(data[MARSHALED_HASH_START:MARSHALED_HASH_END])) + + if err := metadata.receivedTime.UnmarshalBinary(data[MARSHALED_RECEIVED_TIME_START:MARSHALED_RECEIVED_TIME_END]); err != nil { + return ErrUnmarshalFailed.Derive(err, "could not unmarshal the received time") + } + + booleanFlags := bitutils.BitMask(data[MARSHALED_FLAGS_START]) + if booleanFlags.HasFlag(0) { + metadata.solid = true + } + if booleanFlags.HasFlag(1) { + metadata.liked = true + } + if booleanFlags.HasFlag(2) { + metadata.finalized = true + } + + return nil +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/tangle/api.go b/plugins/tangle/api.go deleted file mode 100644 index 3e40d0caa2ca91f45ff0d617d7ba178182b6963f..0000000000000000000000000000000000000000 --- a/plugins/tangle/api.go +++ /dev/null @@ -1,101 +0,0 @@ -package tangle - -import ( - "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/ternary" -) - -// region transaction api ////////////////////////////////////////////////////////////////////////////////////////////// - -var transactionCache = datastructure.NewLRUCache(TRANSACTION_CACHE_SIZE, &datastructure.LRUCacheOptions{ - EvictionCallback: func(key interface{}, value interface{}) { - go func(evictedTransaction *value_transaction.ValueTransaction) { - if err := storeTransactionInDatabase(evictedTransaction); err != nil { - panic(err) - } - }(value.(*value_transaction.ValueTransaction)) - }, -}) - -func StoreTransaction(transaction *value_transaction.ValueTransaction) { - transactionCache.Set(transaction.GetHash(), transaction) -} - -func GetTransaction(transactionHash ternary.Trinary, computeIfAbsent ...func(ternary.Trinary) *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 - } - }); cacheResult != nil { - result = cacheResult.(*value_transaction.ValueTransaction) - } - - return -} - -func ContainsTransaction(transactionHash ternary.Trinary) (result bool, err errors.IdentifiableError) { - if transactionCache.Get(transactionHash) != nil { - result = true - } else { - result, err = databaseContainsTransaction(transactionHash) - } - - return -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region transactionmetadata api ////////////////////////////////////////////////////////////////////////////////////// - -var metadataCache = datastructure.NewLRUCache(METADATA_CACHE_SIZE, &datastructure.LRUCacheOptions{ - EvictionCallback: func(key interface{}, value interface{}) { - go func(evictedMetadata *TransactionMetadata) { - if err := storeTransactionMetadataInDatabase(evictedMetadata); err != nil { - panic(err) - } - }(value.(*TransactionMetadata)) - }, -}) - -func GetTransactionMetadata(transactionHash ternary.Trinary, computeIfAbsent ...func(ternary.Trinary) *TransactionMetadata) (result *TransactionMetadata, err errors.IdentifiableError) { - if transactionMetadata := metadataCache.ComputeIfAbsent(transactionHash, func() interface{} { - if result, err = getTransactionMetadataFromDatabase(transactionHash); err == nil && result == nil && len(computeIfAbsent) >= 1 { - result = computeIfAbsent[0](transactionHash) - } - - return result - }); transactionMetadata != nil && transactionMetadata.(*TransactionMetadata) != nil { - result = transactionMetadata.(*TransactionMetadata) - } - - return -} - -func ContainsTransactionMetadata(transactionHash ternary.Trinary) (result bool, err errors.IdentifiableError) { - if metadataCache.Get(transactionHash) != nil { - result = true - } else { - result, err = databaseContainsTransactionMetadata(transactionHash) - } - - return -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -const ( - TRANSACTION_CACHE_SIZE = 10000 - METADATA_CACHE_SIZE = 10000 -) diff --git a/plugins/tangle/approvers.go b/plugins/tangle/approvers.go index 93d8877ec08498f66d2f4076f312a9d4ade06b43..72c659d97e6534de27869f0b4867cfff74525b07 100644 --- a/plugins/tangle/approvers.go +++ b/plugins/tangle/approvers.go @@ -2,31 +2,33 @@ 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/approvers" + "github.com/iotaledger/goshimmer/packages/node" "github.com/iotaledger/goshimmer/packages/ternary" ) // region global public api //////////////////////////////////////////////////////////////////////////////////////////// -var approversCache = datastructure.NewLRUCache(METADATA_CACHE_SIZE) - -func StoreApprovers(approvers *approvers.Approvers) { - hash := approvers.GetHash() - - approversCache.Set(hash, approvers) -} - func GetApprovers(transactionHash ternary.Trinary, computeIfAbsent ...func(ternary.Trinary) *approvers.Approvers) (result *approvers.Approvers, err errors.IdentifiableError) { - if appr := approversCache.ComputeIfAbsent(transactionHash, func() interface{} { - if result, err = getApproversFromDatabase(transactionHash); err == nil && result == nil && len(computeIfAbsent) >= 1 { - result = computeIfAbsent[0](transactionHash) + if cacheResult := approversCache.ComputeIfAbsent(transactionHash, func() interface{} { + if dbApprovers, dbErr := getApproversFromDatabase(transactionHash); dbErr != nil { + err = dbErr + + return nil + } else if dbApprovers != nil { + return dbApprovers + } else { + if len(computeIfAbsent) >= 1 { + return computeIfAbsent[0](transactionHash) + } + + return nil } - - return result - }); appr != nil && appr.(*approvers.Approvers) != nil { - result = appr.(*approvers.Approvers) + }); cacheResult != nil && cacheResult.(*approvers.Approvers) != nil { + result = cacheResult.(*approvers.Approvers) } return @@ -42,29 +44,80 @@ func ContainsApprovers(transactionHash ternary.Trinary) (result bool, err errors return } -func getApproversFromDatabase(transactionHash ternary.Trinary) (result *approvers.Approvers, err errors.IdentifiableError) { - approversData, dbErr := approversDatabase.Get(transactionHash.CastToBytes()) - if dbErr != nil { - if dbErr != badger.ErrKeyNotFound { - err = ErrDatabaseError.Derive(err, "failed to retrieve transaction") +func StoreApprovers(approvers *approvers.Approvers) { + approversCache.Set(approvers.GetHash(), approvers) +} + +// region lru cache //////////////////////////////////////////////////////////////////////////////////////////////////// + +var approversCache = datastructure.NewLRUCache(APPROVERS_CACHE_SIZE, &datastructure.LRUCacheOptions{ + EvictionCallback: onEvictApprovers, +}) + +func onEvictApprovers(_ interface{}, value interface{}) { + if evictedApprovers := value.(*approvers.Approvers); evictedApprovers.GetModified() { + go func(evictedApprovers *approvers.Approvers) { + if err := storeApproversInDatabase(evictedApprovers); err != nil { + panic(err) + } + }(evictedApprovers) + } +} + +const ( + APPROVERS_CACHE_SIZE = 50000 +) + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region database ///////////////////////////////////////////////////////////////////////////////////////////////////// + +var approversDatabase database.Database + +func configureApproversDatabase(plugin *node.Plugin) { + if db, err := database.Get("approvers"); err != nil { + panic(err) + } else { + approversDatabase = db + } +} + +func storeApproversInDatabase(approvers *approvers.Approvers) errors.IdentifiableError { + if approvers.GetModified() { + if err := transactionMetadataDatabase.Set(approvers.GetHash().CastToBytes(), approvers.Marshal()); err != nil { + return ErrDatabaseError.Derive(err, "failed to store transaction metadata") } - return + approvers.SetModified(false) } - result = approvers.New(transactionHash) + return nil +} + +func getApproversFromDatabase(transactionHash ternary.Trinary) (*approvers.Approvers, errors.IdentifiableError) { + approversData, err := approversDatabase.Get(transactionHash.CastToBytes()) + if err != nil { + if err == badger.ErrKeyNotFound { + return nil, nil + } else { + return nil, ErrDatabaseError.Derive(err, "failed to retrieve approvers") + } + } + + var result approvers.Approvers if err = result.Unmarshal(approversData); err != nil { - result = nil + panic(err) } - return + return &result, nil } func databaseContainsApprovers(transactionHash ternary.Trinary) (bool, errors.IdentifiableError) { - result, err := approversDatabase.Contains(transactionHash.CastToBytes()) - if err != nil { - return false, ErrDatabaseError.Derive(err, "failed to check if the transaction exists") + if contains, err := approversDatabase.Contains(transactionHash.CastToBytes()); err != nil { + return false, ErrDatabaseError.Derive(err, "failed to check if the approvers exists") + } else { + return contains, nil } - - return result, nil } + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/tangle/plugin.go b/plugins/tangle/plugin.go index 113969bdb2f51e1a6cda38c31e2538eea620b9f3..00f2dfaea6bc8481e11720ed9d8124f11e40ee93 100644 --- a/plugins/tangle/plugin.go +++ b/plugins/tangle/plugin.go @@ -9,7 +9,9 @@ import ( var PLUGIN = node.NewPlugin("Tangle", configure, run) func configure(plugin *node.Plugin) { - configureDatabase(plugin) + configureTransactionDatabase(plugin) + configureTransactionMetaDataDatabase(plugin) + configureApproversDatabase(plugin) configureSolidifier(plugin) } diff --git a/plugins/tangle/solidifier.go b/plugins/tangle/solidifier.go index 405f3dd07294d57918d51d187c102941cb43c4f6..7b9e6c8c755d9ed62f8705cef3ca6fce900d756b 100644 --- a/plugins/tangle/solidifier.go +++ b/plugins/tangle/solidifier.go @@ -1,10 +1,12 @@ package tangle import ( + "github.com/iotaledger/goshimmer/packages/daemon" "github.com/iotaledger/goshimmer/packages/errors" "github.com/iotaledger/goshimmer/packages/events" "github.com/iotaledger/goshimmer/packages/model/approvers" "github.com/iotaledger/goshimmer/packages/model/meta_transaction" + "github.com/iotaledger/goshimmer/packages/model/transactionmetadata" "github.com/iotaledger/goshimmer/packages/model/value_transaction" "github.com/iotaledger/goshimmer/packages/node" "github.com/iotaledger/goshimmer/packages/ternary" @@ -23,37 +25,19 @@ func configureSolidifier(plugin *node.Plugin) { for i := 0; i < NUMBER_OF_WORKERS; i++ { go func() { for { - rawTransaction := <-tasksChan - - processMetaTransaction(plugin, rawTransaction) + select { + case <-daemon.ShutdownSignal: + return + case rawTransaction := <-tasksChan: + processMetaTransaction(plugin, rawTransaction) + } } }() } gossip.Events.ReceiveTransaction.Attach(events.NewClosure(func(rawTransaction *meta_transaction.MetaTransaction) { tasksChan <- rawTransaction - - //go processMetaTransaction(plugin, rawTransaction) })) - /* - for i := 0; i < NUMBER_OF_WORKERS; i++ { - go func() { - for transaction := range solidifierChan { - select { - case <-daemon.ShutdownSignal: - return - - default: - // update the solidity flags of this transaction and its approvers - if _, err := IsSolid(transaction); err != nil { - plugin.LogFailure(err.Error()) - - return - } - } - } - }() - }*/ } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -61,7 +45,7 @@ func configureSolidifier(plugin *node.Plugin) { // Checks and updates the solid flag of a single transaction. func checkSolidity(transaction *value_transaction.ValueTransaction) (result bool, err errors.IdentifiableError) { // abort if transaction is solid already - txMetadata, metaDataErr := GetTransactionMetadata(transaction.GetHash(), NewTransactionMetadata) + txMetadata, metaDataErr := GetTransactionMetadata(transaction.GetHash(), transactionmetadata.New) if metaDataErr != nil { err = metaDataErr @@ -81,7 +65,7 @@ func checkSolidity(transaction *value_transaction.ValueTransaction) (result bool return } else if branchTransaction == nil { return - } else if branchTransactionMetadata, branchErr := GetTransactionMetadata(branchTransaction.GetHash(), NewTransactionMetadata); branchErr != nil { + } else if branchTransactionMetadata, branchErr := GetTransactionMetadata(branchTransaction.GetHash(), transactionmetadata.New); branchErr != nil { err = branchErr return @@ -98,7 +82,7 @@ func checkSolidity(transaction *value_transaction.ValueTransaction) (result bool return } else if trunkTransaction == nil { return - } else if trunkTransactionMetadata, trunkErr := GetTransactionMetadata(trunkTransaction.GetHash(), NewTransactionMetadata); trunkErr != nil { + } else if trunkTransactionMetadata, trunkErr := GetTransactionMetadata(trunkTransaction.GetHash(), transactionmetadata.New); trunkErr != nil { err = trunkErr return diff --git a/plugins/tangle/approvers_test.go b/plugins/tangle/solidifier_test.go similarity index 97% rename from plugins/tangle/approvers_test.go rename to plugins/tangle/solidifier_test.go index d5f7cfccb0db544d44c45a20205658952bcc0f66..7bfbe02b14cc09f957c8d8144412ffa33572a03e 100644 --- a/plugins/tangle/approvers_test.go +++ b/plugins/tangle/solidifier_test.go @@ -12,7 +12,7 @@ import ( func TestSolidifier(t *testing.T) { // initialize plugin - configureDatabase(nil) + configureApproversDatabase(nil) configureSolidifier(nil) // create transactions and chain them together diff --git a/plugins/tangle/database.go b/plugins/tangle/transaction.go similarity index 53% rename from plugins/tangle/database.go rename to plugins/tangle/transaction.go index 511d52d2cb5bc3d053a5ca96c11cfd6841c33a6a..22913eddb7fce9566648a781ae3fdf441e3518d5 100644 --- a/plugins/tangle/database.go +++ b/plugins/tangle/transaction.go @@ -3,37 +3,87 @@ 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/ternary" + "github.com/iotaledger/goshimmer/packages/typeutils" ) -// region plugin module setup ////////////////////////////////////////////////////////////////////////////////////////// +// region public api /////////////////////////////////////////////////////////////////////////////////////////////////// -func configureDatabase(plugin *node.Plugin) { - if db, err := database.Get("transaction"); err != nil { - panic(err) - } else { - transactionDatabase = db +func GetTransaction(transactionHash ternary.Trinary, computeIfAbsent ...func(ternary.Trinary) *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) } - if db, err := database.Get("transactionMetadata"); err != nil { - panic(err) + return +} + +func ContainsTransaction(transactionHash ternary.Trinary) (result bool, err errors.IdentifiableError) { + if transactionCache.Contains(transactionHash) { + result = true } else { - transactionMetadataDatabase = db + result, err = databaseContainsTransaction(transactionHash) } - if db, err := database.Get("approvers"); err != nil { - panic(err) - } else { - approversDatabase = db + 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 internal utility functions /////////////////////////////////////////////////////////////////////////////////// +// 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() { @@ -68,57 +118,4 @@ func databaseContainsTransaction(transactionHash ternary.Trinary) (bool, errors. } } -func storeTransactionMetadataInDatabase(metadata *TransactionMetadata) errors.IdentifiableError { - if metadata.GetModified() { - marshalledMetadata, err := metadata.Marshal() - if err != nil { - return err - } - - if err := transactionMetadataDatabase.Set(metadata.GetHash().CastToBytes(), marshalledMetadata); err != nil { - return ErrDatabaseError.Derive(err, "failed to store transaction metadata") - } - - metadata.SetModified(false) - } - - return nil -} - -func getTransactionMetadataFromDatabase(transactionHash ternary.Trinary) (*TransactionMetadata, errors.IdentifiableError) { - txMetadata, err := transactionMetadataDatabase.Get(transactionHash.CastToBytes()) - if err != nil { - if err == badger.ErrKeyNotFound { - return nil, nil - } else { - return nil, ErrDatabaseError.Derive(err, "failed to retrieve transaction") - } - } - - var result TransactionMetadata - if err := result.Unmarshal(txMetadata); err != nil { - panic(err) - } - - return &result, nil -} - -func databaseContainsTransactionMetadata(transactionHash ternary.Trinary) (bool, errors.IdentifiableError) { - if contains, err := transactionMetadataDatabase.Contains(transactionHash.CastToBytes()); err != nil { - return contains, ErrDatabaseError.Derive(err, "failed to check if the transaction metadata exists") - } else { - return contains, nil - } -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region constants and variables ////////////////////////////////////////////////////////////////////////////////////// - -var transactionDatabase database.Database - -var transactionMetadataDatabase database.Database - -var approversDatabase database.Database - // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/tangle/transaction_metadata.go b/plugins/tangle/transaction_metadata.go index 7798e0396857691537b9423f7fa2faad25053cad..b7d056d17e9161417aed77fd357d01d67e2196bb 100644 --- a/plugins/tangle/transaction_metadata.go +++ b/plugins/tangle/transaction_metadata.go @@ -1,275 +1,130 @@ package tangle import ( - "sync" - "time" - - "github.com/iotaledger/goshimmer/packages/bitutils" + "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/transactionmetadata" + "github.com/iotaledger/goshimmer/packages/node" "github.com/iotaledger/goshimmer/packages/ternary" "github.com/iotaledger/goshimmer/packages/typeutils" ) -// region type definition and constructor ////////////////////////////////////////////////////////////////////////////// +// region public api /////////////////////////////////////////////////////////////////////////////////////////////////// -type TransactionMetadata struct { - hash ternary.Trinary - hashMutex sync.RWMutex - receivedTime time.Time - receivedTimeMutex sync.RWMutex - solid bool - solidMutex sync.RWMutex - liked bool - likedMutex sync.RWMutex - finalized bool - finalizedMutex sync.RWMutex - modified bool - modifiedMutex sync.RWMutex -} +func GetTransactionMetadata(transactionHash ternary.Trinary, computeIfAbsent ...func(ternary.Trinary) *transactionmetadata.TransactionMetadata) (result *transactionmetadata.TransactionMetadata, err errors.IdentifiableError) { + if cacheResult := transactionMetadataCache.ComputeIfAbsent(transactionHash, func() interface{} { + if transactionMetadata, dbErr := getTransactionMetadataFromDatabase(transactionHash); dbErr != nil { + err = dbErr -func NewTransactionMetadata(hash ternary.Trinary) *TransactionMetadata { - return &TransactionMetadata{ - hash: hash, - receivedTime: time.Now(), - solid: false, - liked: false, - finalized: false, - modified: true, - } -} + return nil + } else if transactionMetadata != nil { + return transactionMetadata + } else { + if len(computeIfAbsent) >= 1 { + return computeIfAbsent[0](transactionHash) + } -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region getters and setters ////////////////////////////////////////////////////////////////////////////////////////// - -func (metadata *TransactionMetadata) GetHash() ternary.Trinary { - metadata.hashMutex.RLock() - defer metadata.hashMutex.RUnlock() - - return metadata.hash -} - -func (metadata *TransactionMetadata) SetHash(hash ternary.Trinary) { - metadata.hashMutex.RLock() - if metadata.hash != hash { - metadata.hashMutex.RUnlock() - metadata.hashMutex.Lock() - defer metadata.hashMutex.Unlock() - if metadata.hash != hash { - metadata.hash = hash - - metadata.SetModified(true) + return nil } - } else { - metadata.hashMutex.RUnlock() + }); !typeutils.IsInterfaceNil(cacheResult) { + result = cacheResult.(*transactionmetadata.TransactionMetadata) } -} -func (metadata *TransactionMetadata) GetReceivedTime() time.Time { - metadata.receivedTimeMutex.RLock() - defer metadata.receivedTimeMutex.RUnlock() - - return metadata.receivedTime + return } -func (metadata *TransactionMetadata) SetReceivedTime(receivedTime time.Time) { - metadata.receivedTimeMutex.RLock() - if metadata.receivedTime != receivedTime { - metadata.receivedTimeMutex.RUnlock() - metadata.receivedTimeMutex.Lock() - defer metadata.receivedTimeMutex.Unlock() - if metadata.receivedTime != receivedTime { - metadata.receivedTime = receivedTime - - metadata.SetModified(true) - } +func ContainsTransactionMetadata(transactionHash ternary.Trinary) (result bool, err errors.IdentifiableError) { + if transactionMetadataCache.Contains(transactionHash) { + result = true } else { - metadata.receivedTimeMutex.RUnlock() + result, err = databaseContainsTransactionMetadata(transactionHash) } -} -func (metadata *TransactionMetadata) GetSolid() bool { - metadata.solidMutex.RLock() - defer metadata.solidMutex.RUnlock() - - return metadata.solid + return } -func (metadata *TransactionMetadata) SetSolid(solid bool) bool { - metadata.solidMutex.RLock() - if metadata.solid != solid { - metadata.solidMutex.RUnlock() - metadata.solidMutex.Lock() - defer metadata.solidMutex.Unlock() - if metadata.solid != solid { - metadata.solid = solid - - metadata.SetModified(true) - - return true - } - } else { - metadata.solidMutex.RUnlock() - } - - return false +func StoreTransactionMetadata(transactionMetadata *transactionmetadata.TransactionMetadata) { + transactionMetadataCache.Set(transactionMetadata.GetHash(), transactionMetadata) } -func (metadata *TransactionMetadata) GetLiked() bool { - metadata.likedMutex.RLock() - defer metadata.likedMutex.RUnlock() +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - return metadata.liked -} +// region lru cache //////////////////////////////////////////////////////////////////////////////////////////////////// -func (metadata *TransactionMetadata) SetLiked(liked bool) { - metadata.likedMutex.RLock() - if metadata.liked != liked { - metadata.likedMutex.RUnlock() - metadata.likedMutex.Lock() - defer metadata.likedMutex.Unlock() - if metadata.liked != liked { - metadata.liked = liked +var transactionMetadataCache = datastructure.NewLRUCache(TRANSACTION_METADATA_CACHE_SIZE, &datastructure.LRUCacheOptions{ + EvictionCallback: onEvictTransactionMetadata, +}) - metadata.SetModified(true) - } - } else { - metadata.likedMutex.RUnlock() +func onEvictTransactionMetadata(_ interface{}, value interface{}) { + if evictedTransactionMetadata := value.(*transactionmetadata.TransactionMetadata); evictedTransactionMetadata.GetModified() { + go func(evictedTransactionMetadata *transactionmetadata.TransactionMetadata) { + if err := storeTransactionMetadataInDatabase(evictedTransactionMetadata); err != nil { + panic(err) + } + }(evictedTransactionMetadata) } } -func (metadata *TransactionMetadata) GetFinalized() bool { - metadata.finalizedMutex.RLock() - defer metadata.finalizedMutex.RUnlock() +const ( + TRANSACTION_METADATA_CACHE_SIZE = 50000 +) + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - return metadata.finalized -} +// region database ///////////////////////////////////////////////////////////////////////////////////////////////////// -func (metadata *TransactionMetadata) SetFinalized(finalized bool) { - metadata.finalizedMutex.RLock() - if metadata.finalized != finalized { - metadata.finalizedMutex.RUnlock() - metadata.finalizedMutex.Lock() - defer metadata.finalizedMutex.Unlock() - if metadata.finalized != finalized { - metadata.finalized = finalized +var transactionMetadataDatabase database.Database - metadata.SetModified(true) - } +func configureTransactionMetaDataDatabase(plugin *node.Plugin) { + if db, err := database.Get("transactionMetadata"); err != nil { + panic(err) } else { - metadata.finalizedMutex.RUnlock() + transactionMetadataDatabase = db } } -// returns true if the transaction contains unsaved changes (supports concurrency) -func (metadata *TransactionMetadata) GetModified() bool { - metadata.modifiedMutex.RLock() - defer metadata.modifiedMutex.RUnlock() +func storeTransactionMetadataInDatabase(metadata *transactionmetadata.TransactionMetadata) errors.IdentifiableError { + if metadata.GetModified() { + if marshaledMetadata, err := metadata.Marshal(); err != nil { + return err + } else { + if err := transactionMetadataDatabase.Set(metadata.GetHash().CastToBytes(), marshaledMetadata); err != nil { + return ErrDatabaseError.Derive(err, "failed to store transaction metadata") + } - return metadata.modified -} - -// sets the modified flag which controls if a transaction is going to be saved (supports concurrency) -func (metadata *TransactionMetadata) SetModified(modified bool) { - metadata.modifiedMutex.Lock() - defer metadata.modifiedMutex.Unlock() + metadata.SetModified(false) + } + } - metadata.modified = modified + return nil } -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region marshaling functions //////////////////////////////////////////////////////////////////////////////////////// - -func (metadata *TransactionMetadata) Marshal() ([]byte, errors.IdentifiableError) { - marshaledMetadata := make([]byte, MARSHALED_TOTAL_SIZE) - - metadata.receivedTimeMutex.RLock() - defer metadata.receivedTimeMutex.RUnlock() - metadata.solidMutex.RLock() - defer metadata.solidMutex.RUnlock() - metadata.likedMutex.RLock() - defer metadata.likedMutex.RUnlock() - metadata.finalizedMutex.RLock() - defer metadata.finalizedMutex.RUnlock() - - copy(marshaledMetadata[MARSHALED_HASH_START:MARSHALED_HASH_END], metadata.hash.CastToBytes()) - - marshaledReceivedTime, err := metadata.receivedTime.MarshalBinary() +func getTransactionMetadataFromDatabase(transactionHash ternary.Trinary) (*transactionmetadata.TransactionMetadata, errors.IdentifiableError) { + txMetadata, err := transactionMetadataDatabase.Get(transactionHash.CastToBytes()) if err != nil { - return nil, ErrMarshallFailed.Derive(err, "failed to marshal received time") + if err == badger.ErrKeyNotFound { + return nil, nil + } else { + return nil, ErrDatabaseError.Derive(err, "failed to retrieve transaction") + } } - copy(marshaledMetadata[MARSHALED_RECEIVED_TIME_START:MARSHALED_RECEIVED_TIME_END], marshaledReceivedTime) - var booleanFlags bitutils.BitMask - if metadata.solid { - booleanFlags = booleanFlags.SetFlag(0) - } - if metadata.liked { - booleanFlags = booleanFlags.SetFlag(1) + var result transactionmetadata.TransactionMetadata + if err := result.Unmarshal(txMetadata); err != nil { + panic(err) } - if metadata.finalized { - booleanFlags = booleanFlags.SetFlag(2) - } - marshaledMetadata[MARSHALED_FLAGS_START] = byte(booleanFlags) - return marshaledMetadata, nil + return &result, nil } -func (metadata *TransactionMetadata) Unmarshal(data []byte) errors.IdentifiableError { - metadata.hashMutex.Lock() - defer metadata.hashMutex.Unlock() - metadata.receivedTimeMutex.Lock() - defer metadata.receivedTimeMutex.Unlock() - metadata.solidMutex.Lock() - defer metadata.solidMutex.Unlock() - metadata.likedMutex.Lock() - defer metadata.likedMutex.Unlock() - metadata.finalizedMutex.Lock() - defer metadata.finalizedMutex.Unlock() - - metadata.hash = ternary.Trinary(typeutils.BytesToString(data[MARSHALED_HASH_START:MARSHALED_HASH_END])) - - if err := metadata.receivedTime.UnmarshalBinary(data[MARSHALED_RECEIVED_TIME_START:MARSHALED_RECEIVED_TIME_END]); err != nil { - return ErrUnmarshalFailed.Derive(err, "could not unmarshal the received time") - } - - booleanFlags := bitutils.BitMask(data[MARSHALED_FLAGS_START]) - if booleanFlags.HasFlag(0) { - metadata.solid = true - } - if booleanFlags.HasFlag(1) { - metadata.liked = true - } - if booleanFlags.HasFlag(2) { - metadata.finalized = true +func databaseContainsTransactionMetadata(transactionHash ternary.Trinary) (bool, errors.IdentifiableError) { + if contains, err := transactionMetadataDatabase.Contains(transactionHash.CastToBytes()); err != nil { + return contains, ErrDatabaseError.Derive(err, "failed to check if the transaction metadata exists") + } else { + return contains, nil } - - return nil } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region database functions /////////////////////////////////////////////////////////////////////////////////////////// - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region constants and variables ////////////////////////////////////////////////////////////////////////////////////// - -const ( - MARSHALED_HASH_START = 0 - MARSHALED_RECEIVED_TIME_START = MARSHALED_HASH_END - MARSHALED_FLAGS_START = MARSHALED_RECEIVED_TIME_END - - MARSHALED_HASH_END = MARSHALED_HASH_START + MARSHALED_HASH_SIZE - MARSHALED_RECEIVED_TIME_END = MARSHALED_RECEIVED_TIME_START + MARSHALED_RECEIVED_TIME_SIZE - MARSHALED_FLAGS_END = MARSHALED_FLAGS_START + MARSHALED_FLAGS_SIZE - - MARSHALED_HASH_SIZE = 81 - MARSHALED_RECEIVED_TIME_SIZE = 15 - MARSHALED_FLAGS_SIZE = 1 - - MARSHALED_TOTAL_SIZE = MARSHALED_FLAGS_END -) - -// endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////