Skip to content
Snippets Groups Projects
Commit 1f931e81 authored by Hans Moog's avatar Hans Moog
Browse files

Refactor: refactored tangle plugin

parent ba474922
No related branches found
No related tags found
No related merge requests found
......@@ -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)
......
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 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
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 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
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 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
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
)
......@@ -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 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
......@@ -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)
}
......
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
......
......@@ -12,7 +12,7 @@ import (
func TestSolidifier(t *testing.T) {
// initialize plugin
configureDatabase(nil)
configureApproversDatabase(nil)
configureSolidifier(nil)
// create transactions and chain them together
......
......@@ -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 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////////////////////////////////
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment