diff --git a/packages/bitutils/bitmask.go b/packages/bitutils/bitmask.go new file mode 100644 index 0000000000000000000000000000000000000000..ac3a34bb6dcf1406f85464c944014337caeb0e2d --- /dev/null +++ b/packages/bitutils/bitmask.go @@ -0,0 +1,15 @@ +package bitutils + +type BitMask byte + +func (bitmask BitMask) SetFlag(pos uint) BitMask { + return bitmask | (1 << pos) +} + +func (bitmask BitMask) ClearFlag(pos uint) BitMask { + return bitmask & ^(1 << pos) +} + +func (bitmask BitMask) HasFlag(pos uint) bool { + return (bitmask & (1 << pos) > 0) +} \ No newline at end of file diff --git a/packages/bitutils/bitmask_test.go b/packages/bitutils/bitmask_test.go new file mode 100644 index 0000000000000000000000000000000000000000..38dfb40839793724f40291c70d86e7b86e7865b4 --- /dev/null +++ b/packages/bitutils/bitmask_test.go @@ -0,0 +1,35 @@ +package bitutils + +import ( + "testing" +) + +func TestBitmask(t *testing.T) { + var b BitMask + + if b.HasFlag(0) { + t.Error("flag at pos 0 should not be set") + } + if b.HasFlag(1) { + t.Error("flag at pos 1 should not be set") + } + + b = b.SetFlag(0) + if !b.HasFlag(0) { + t.Error("flag at pos 0 should be set") + } + b = b.SetFlag(1) + if !b.HasFlag(1) { + t.Error("flag at pos 1 should be set") + } + + b = b.ClearFlag(0) + if b.HasFlag(0) { + t.Error("flag at pos 0 should not be set") + } + b = b.ClearFlag(1) + if b.HasFlag(1) { + t.Error("flag at pos 1 should not be set") + } +} + diff --git a/packages/ternary/ternary.go b/packages/ternary/ternary.go index 3261e1bf212b66c7d0978627447f4e1a58c8cae5..84034c5b7c678a05d19e03ea867fd220502d809b 100644 --- a/packages/ternary/ternary.go +++ b/packages/ternary/ternary.go @@ -1,11 +1,26 @@ package ternary +import ( + "reflect" + "unsafe" +) + // a Trit can have the values 0, 1 and -1 type Trit = int8 -// a Trinary consists out of many Trits +// Trits consists out of many Trits type Trits []Trit +// Trinary is a string representation of the Trits +type Trinary string + +// simply changes the type of this Trinary to a byte array without copying any data +func (trinary Trinary) CastToBytes() []byte { + hdr := (*reflect.StringHeader)(unsafe.Pointer(&trinary)) + + return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: hdr.Data, Len: hdr.Len, Cap: hdr.Len})) +} + func (this Trits) ToBytes() []byte { tritsLength := len(this) bytesLength := (tritsLength + NUMBER_OF_TRITS_IN_A_BYTE - 1) / NUMBER_OF_TRITS_IN_A_BYTE @@ -62,4 +77,8 @@ func (this Trits) ToUint64() uint64 { func (this Trits) ToString() string { return TritsToString(this, 0, len(this)) -} \ No newline at end of file +} + +func (this Trits) ToTrinary() Trinary { + return Trinary(TritsToString(this, 0, len(this))) +} diff --git a/packages/timeutil/sleep.go b/packages/timeutil/sleep.go new file mode 100644 index 0000000000000000000000000000000000000000..68facd320d528cede103eb981fff18d206b9d154 --- /dev/null +++ b/packages/timeutil/sleep.go @@ -0,0 +1,16 @@ +package timeutil + +import ( + "github.com/iotaledger/goshimmer/packages/daemon" + "time" +) + +func Sleep(interval time.Duration) bool { + select { + case <-daemon.ShutdownSignal: + return false + + case <-time.After(interval): + return true + } +} \ No newline at end of file diff --git a/packages/transaction/constants.go b/packages/transaction/constants.go index 6d632b6077adaa0fd064a8f7f77d7056068de635..594890c09a6ee1ded5b8e16409a7125adb305a9d 100644 --- a/packages/transaction/constants.go +++ b/packages/transaction/constants.go @@ -12,7 +12,6 @@ const ( TRUNK_TRANSACTION_HASH_SIZE = 243 BRANCH_TRANSACTION_HASH_SIZE = 243 TAG_SIZE = 81 - NODE_ID_SIZE = 243 NONCE_SIZE = 81 // offsets of the transaction fields @@ -26,8 +25,7 @@ const ( TRUNK_TRANSACTION_HASH_OFFSET = BUNDLE_HASH_END BRANCH_TRANSACTION_HASH_OFFSET = TRUNK_TRANSACTION_HASH_END TAG_OFFSET = BRANCH_TRANSACTION_HASH_END - NODE_ID_OFFSET = TAG_END - NONCE_OFFSET = NODE_ID_END + NONCE_OFFSET = TAG_END // ends of the transaction fields SIGNATURE_MESSAGE_FRAGMENT_END = SIGNATURE_MESSAGE_FRAGMENT_OFFSET + SIGNATURE_MESSAGE_FRAGMENT_SIZE @@ -40,7 +38,6 @@ const ( TRUNK_TRANSACTION_HASH_END = TRUNK_TRANSACTION_HASH_OFFSET + TRUNK_TRANSACTION_HASH_SIZE BRANCH_TRANSACTION_HASH_END = BRANCH_TRANSACTION_HASH_OFFSET + BRANCH_TRANSACTION_HASH_SIZE TAG_END = TAG_OFFSET + TAG_SIZE - NODE_ID_END = NODE_ID_OFFSET + NODE_ID_SIZE NONCE_END = NONCE_OFFSET + NONCE_SIZE // the full size of a transaction diff --git a/packages/transaction/transaction.go b/packages/transaction/transaction.go index 2fc31aba22768cdf840ed2b5e2a1dc48c4fb1cdc..871f6b2924484bbdfa6d9a2c48d88c53cfccac47 100644 --- a/packages/transaction/transaction.go +++ b/packages/transaction/transaction.go @@ -8,15 +8,14 @@ import ( type Transaction struct { SignatureMessageFragment ternary.Trits Address ternary.Trits - Value int64 - Timestamp uint64 - CurrentIndex uint64 - LatestIndex uint64 + Value ternary.Trits + Timestamp ternary.Trits + CurrentIndex ternary.Trits + LatestIndex ternary.Trits BundleHash ternary.Trits TrunkTransactionHash ternary.Trits BranchTransactionHash ternary.Trits Tag ternary.Trits - NodeId ternary.Trits Nonce ternary.Trits Hash ternary.Trits @@ -31,15 +30,14 @@ func FromTrits(trits ternary.Trits, optionalHash ...ternary.Trits) *Transaction transaction := &Transaction{ SignatureMessageFragment: trits[SIGNATURE_MESSAGE_FRAGMENT_OFFSET:SIGNATURE_MESSAGE_FRAGMENT_END], Address: trits[ADDRESS_OFFSET:ADDRESS_END], - Value: trits[VALUE_OFFSET:VALUE_END].ToInt64(), - Timestamp: trits[TIMESTAMP_OFFSET:TIMESTAMP_END].ToUint64(), - CurrentIndex: trits[CURRENT_INDEX_OFFSET:CURRENT_INDEX_END].ToUint64(), - LatestIndex: trits[LATEST_INDEX_OFFSET:LATEST_INDEX_END].ToUint64(), + Value: trits[VALUE_OFFSET:VALUE_END], + Timestamp: trits[TIMESTAMP_OFFSET:TIMESTAMP_END], + CurrentIndex: trits[CURRENT_INDEX_OFFSET:CURRENT_INDEX_END], + LatestIndex: trits[LATEST_INDEX_OFFSET:LATEST_INDEX_END], BundleHash: trits[BUNDLE_HASH_OFFSET:BUNDLE_HASH_END], TrunkTransactionHash: trits[TRUNK_TRANSACTION_HASH_OFFSET:TRUNK_TRANSACTION_HASH_END], BranchTransactionHash: trits[BRANCH_TRANSACTION_HASH_OFFSET:BRANCH_TRANSACTION_HASH_END], Tag: trits[TAG_OFFSET:TAG_END], - NodeId: trits[NODE_ID_OFFSET:NODE_ID_END], Nonce: trits[NONCE_OFFSET:NONCE_END], Hash: hash, diff --git a/packages/typeconversion/typeconversion.go b/packages/typeconversion/typeconversion.go index e820a9b66019d0a5f9c724aa9f9c47cc39d9632c..729b48d96a1cb1606cf131d6f558df1428f3970e 100644 --- a/packages/typeconversion/typeconversion.go +++ b/packages/typeconversion/typeconversion.go @@ -9,4 +9,10 @@ func BytesToString(b []byte) string { bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) return *(*string)(unsafe.Pointer(&reflect.StringHeader{bh.Data, bh.Len})) +} + +func StringToBytes(str string) []byte { + hdr := (*reflect.StringHeader)(unsafe.Pointer(&str)) + + return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: hdr.Data, Len: hdr.Len, Cap: hdr.Len})) } \ No newline at end of file diff --git a/plugins/analysis/client/plugin.go b/plugins/analysis/client/plugin.go index c4c3970d009ca403711c37c286b5968427b09133..663bf6cba2bbdfaeacd4af175425da8c10131ef0 100644 --- a/plugins/analysis/client/plugin.go +++ b/plugins/analysis/client/plugin.go @@ -6,6 +6,7 @@ import ( "github.com/iotaledger/goshimmer/packages/events" "github.com/iotaledger/goshimmer/packages/network" "github.com/iotaledger/goshimmer/packages/node" + "github.com/iotaledger/goshimmer/packages/timeutil" "github.com/iotaledger/goshimmer/plugins/analysis/types/addnode" "github.com/iotaledger/goshimmer/plugins/analysis/types/connectnodes" "github.com/iotaledger/goshimmer/plugins/analysis/types/disconnectnodes" @@ -21,17 +22,26 @@ import ( func Run(plugin *node.Plugin) { daemon.BackgroundWorker(func() { shuttingDown := false + for !shuttingDown { - if conn, err := net.Dial("tcp", *SERVER_ADDRESS.Value); err != nil { - plugin.LogDebug("Could not connect to reporting server: " + err.Error()) - } else { - managedConn := network.NewManagedConnection(conn) - eventDispatchers := getEventDispatchers(managedConn) + select { + case <-daemon.ShutdownSignal: + return + + default: + if conn, err := net.Dial("tcp", *SERVER_ADDRESS.Value); err != nil { + plugin.LogDebug("Could not connect to reporting server: " + err.Error()) - reportCurrentStatus(eventDispatchers) - setupHooks(managedConn, eventDispatchers) + timeutil.Sleep(1 * time.Second) + } else { + managedConn := network.NewManagedConnection(conn) + eventDispatchers := getEventDispatchers(managedConn) - shuttingDown = keepConnectionAlive(managedConn) + reportCurrentStatus(eventDispatchers) + setupHooks(managedConn, eventDispatchers) + + shuttingDown = keepConnectionAlive(managedConn) + } } } }) diff --git a/plugins/tangle/api.go b/plugins/tangle/api.go new file mode 100644 index 0000000000000000000000000000000000000000..c184762e75f348a8755156296d889d1f94f098e8 --- /dev/null +++ b/plugins/tangle/api.go @@ -0,0 +1,46 @@ +package tangle + +import ( + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/ternary" +) + +// region transaction api ////////////////////////////////////////////////////////////////////////////////////////////// + +func GetTransaction(transactionHash ternary.Trinary) (*Transaction, errors.IdentifiableError) { + if transaction := getTransactionFromMemPool(transactionHash); transaction != nil { + return transaction, nil + } + + return getTransactionFromDatabase(transactionHash) +} + +func ContainsTransaction(transactionHash ternary.Trinary) (bool, errors.IdentifiableError) { + if memPoolContainsTransaction(transactionHash) { + return true, nil + } + + return databaseContainsTransaction(transactionHash) +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region transactionmetadata api ////////////////////////////////////////////////////////////////////////////////////// + +func GetTransactionMetadata(transactionHash ternary.Trinary) (*TransactionMetadata, errors.IdentifiableError) { + if transaction := getTransactionFromMemPool(transactionHash); transaction != nil { + return transaction.GetMetaData() + } + + return getTransactionMetadataFromDatabase(transactionHash) +} + +func ContainsTransactionMetadata(transactionHash ternary.Trinary) (bool, errors.IdentifiableError) { + if memPoolContainsTransaction(transactionHash) { + return true, nil + } + + return databaseContainsTransactionMetadata(transactionHash) +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/plugins/tangle/database.go b/plugins/tangle/database.go new file mode 100644 index 0000000000000000000000000000000000000000..88fca65165a8eab6f97540216046668ada7b2e43 --- /dev/null +++ b/plugins/tangle/database.go @@ -0,0 +1,89 @@ +package tangle + +import ( + "fmt" + "github.com/dgraph-io/badger" + "github.com/iotaledger/goshimmer/packages/database" + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/node" + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/transaction" +) + +// region plugin module setup ////////////////////////////////////////////////////////////////////////////////////////// + +func configureDatabase(plugin *node.Plugin) { + if db, err := database.Get("transaction"); err != nil { + panic(err) + } else { + transactionDatabase = db + } + + if db, err := database.Get("transactionMetadata"); err != nil { + panic(err) + } else { + transactionMetadataDatabase = db + } +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region internal utility functions /////////////////////////////////////////////////////////////////////////////////// + +func getTransactionFromDatabase(transactionHash ternary.Trinary) (*Transaction, errors.IdentifiableError) { + txData, err := transactionDatabase.Get(transactionHash.CastToBytes()) + if err != nil { + if err == badger.ErrKeyNotFound { + return nil, nil + } else { + return nil, ErrDatabaseError.Derive(err, "failed to retrieve transaction") + } + } + + return &Transaction{ + rawTransaction: transaction.FromBytes(txData), + }, nil +} + +func databaseContainsTransaction(transactionHash ternary.Trinary) (bool, errors.IdentifiableError) { + if contains, err := transactionDatabase.Contains(transactionHash.CastToBytes()); err != nil { + return contains, ErrDatabaseError.Derive(err, "failed to check if the transaction exists") + } else { + return contains, 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") + } + } + + if false { + fmt.Println(txMetadata) + } + + return &TransactionMetadata{}, 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 + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/tangle/errors.go b/plugins/tangle/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..809e6a6587827ee71d979f76d9e30640753967be --- /dev/null +++ b/plugins/tangle/errors.go @@ -0,0 +1,9 @@ +package tangle + +import "github.com/iotaledger/goshimmer/packages/errors" + +var ( + ErrDatabaseError = errors.Wrap(errors.New("database error"), "failed to access the database") + ErrUnmarshalFailed = errors.Wrap(errors.New("unmarshall failed"), "input data is corrupted") + ErrMarshallFailed = errors.Wrap(errors.New("marshal failed"), "the source object contains invalid values") +) diff --git a/plugins/tangle/events.go b/plugins/tangle/events.go new file mode 100644 index 0000000000000000000000000000000000000000..cc58a4898181793fa9f5f79c6957693ebba73743 --- /dev/null +++ b/plugins/tangle/events.go @@ -0,0 +1,17 @@ +package tangle + +import ( + "github.com/iotaledger/goshimmer/packages/events" +) + +var Events = pluginEvents{ + TransactionStored: events.NewEvent(transactionCaller), + TransactionSolid: events.NewEvent(transactionCaller), +} + +type pluginEvents struct { + TransactionStored *events.Event + TransactionSolid *events.Event +} + +func transactionCaller(handler interface{}, params ...interface{}) { handler.(func(*Transaction))(params[0].(*Transaction)) } diff --git a/plugins/tangle/mempool.go b/plugins/tangle/mempool.go new file mode 100644 index 0000000000000000000000000000000000000000..7f80d5bcaf54e95bda754e114bfd6e55ba93941a --- /dev/null +++ b/plugins/tangle/mempool.go @@ -0,0 +1,130 @@ +package tangle + +import ( + "github.com/iotaledger/goshimmer/packages/daemon" + "github.com/iotaledger/goshimmer/packages/events" + "github.com/iotaledger/goshimmer/packages/node" + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/transaction" + "github.com/iotaledger/goshimmer/plugins/gossip" + "sync" + "time" +) + +// region plugin module setup ////////////////////////////////////////////////////////////////////////////////////////// + +func configureMemPool(plugin *node.Plugin) { + gossip.Events.ReceiveTransaction.Attach(events.NewClosure(func(transaction *transaction.Transaction) { + memPoolQueue <- &Transaction{rawTransaction: transaction} + })) +} + +func runMemPool(plugin *node.Plugin) { + plugin.LogInfo("Starting Mempool ...") + + daemon.BackgroundWorker(createMemPoolWorker(plugin)) +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region internal utility functions /////////////////////////////////////////////////////////////////////////////////// + +func createMemPoolWorker(plugin *node.Plugin) func() { + return func() { + plugin.LogSuccess("Starting Mempool ... done") + + shuttingDown := false + + for !shuttingDown { + flushTimer := time.After(MEMPOOL_FLUSH_INTERVAL) + + select { + case <-daemon.ShutdownSignal: + plugin.LogInfo("Stopping Mempool ...") + + shuttingDown = true + + continue + + case <-flushTimer: + // store transactions in database + + case tx := <-memPoolQueue: + // skip transactions that we have processed already + if transactionStoredAlready, err := ContainsTransaction(tx.GetHash()); err != nil { + plugin.LogFailure(err.Error()) + + return + } else if transactionStoredAlready { + continue + } + + // store tx in memPool + memPoolMutex.Lock() + memPool[tx.GetHash()] = tx + memPoolMutex.Unlock() + + // update solidity of transactions + _, err := UpdateSolidity(tx) + if err != nil { + plugin.LogFailure(err.Error()) + + return + } + + go func() { + <-time.After(1 * time.Minute) + + err := tx.Store() + if err != nil { + plugin.LogFailure(err.Error()) + } + + memPoolMutex.Lock() + delete(memPool, tx.GetHash()) + memPoolMutex.Unlock() + }() + } + } + + plugin.LogSuccess("Stopping Mempool ... done") + } +} + +func getTransactionFromMemPool(transactionHash ternary.Trinary) *Transaction { + memPoolMutex.RLock() + defer memPoolMutex.RUnlock() + + if cacheEntry, exists := memPool[transactionHash]; exists { + return cacheEntry + } + + return nil +} + +func memPoolContainsTransaction(transactionHash ternary.Trinary) bool { + memPoolMutex.RLock() + defer memPoolMutex.RUnlock() + + _, exists := memPool[transactionHash] + + return exists +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region constants and variables ////////////////////////////////////////////////////////////////////////////////////// + +var memPoolQueue = make(chan *Transaction, MEM_POOL_QUEUE_SIZE) + +var memPool = make(map[ternary.Trinary]*Transaction) + +var memPoolMutex sync.RWMutex + +const ( + MEM_POOL_QUEUE_SIZE = 1000 + + MEMPOOL_FLUSH_INTERVAL = 500 * time.Millisecond +) + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/tangle/plugin.go b/plugins/tangle/plugin.go index 556dda07205e263aa036a883070e259c322be51a..e41f763217f87c7c15097bb5b435e7043ec36b2a 100644 --- a/plugins/tangle/plugin.go +++ b/plugins/tangle/plugin.go @@ -1,64 +1,20 @@ package tangle import ( - "fmt" - "github.com/iotaledger/goshimmer/packages/database" - "github.com/iotaledger/goshimmer/packages/events" "github.com/iotaledger/goshimmer/packages/node" - "github.com/iotaledger/goshimmer/packages/transaction" - "github.com/iotaledger/goshimmer/plugins/gossip" ) // region plugin module setup ////////////////////////////////////////////////////////////////////////////////////////// var PLUGIN = node.NewPlugin("Tangle", configure, run) -func configure(node *node.Plugin) { - if db, err := database.Get("transactions"); err != nil { - panic(err) - } else { - transactionDatabase = db - } - - gossip.Events.ReceiveTransaction.Attach(events.NewClosure(func(transaction *transaction.Transaction) { - if transactionStoredAlready, err := transactionDatabase.Contains(transaction.Hash.ToBytes()); err != nil { - panic(err) - } else { - if !transactionStoredAlready { - - fmt.Println(transaction.Hash.ToString()) - // process transaction - } - } - })) +func configure(plugin *node.Plugin) { + configureDatabase(plugin) + configureMemPool(plugin) } -func run(node *node.Plugin) { - /* - if accountability.OWN_ID.StringIdentifier == "72f84aaee02af4542672cb25aceb9d9e458ef2a3" { - go func() { - txCounter := 0 - - for { - txCounter++ - - dummyTx := make([]byte, transaction.MARSHALLED_TOTAL_SIZE / ternary.NUMBER_OF_TRITS_IN_A_BYTE) - for i := 0; i < 1620; i++ { - dummyTx[i] = byte((i + txCounter) % 128) - } - - gossip.SendTransaction(transaction.FromBytes(dummyTx)) - - <- time.After(1000 * time.Millisecond) - } - }() - }*/ +func run(plugin *node.Plugin) { + runMemPool(plugin) } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region constants and variables ////////////////////////////////////////////////////////////////////////////////////// - -var transactionDatabase database.Database - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/tangle/solidifier.go b/plugins/tangle/solidifier.go new file mode 100644 index 0000000000000000000000000000000000000000..4fa57cc29885645c32f4fea38fbc9e4dea1ba6a9 --- /dev/null +++ b/plugins/tangle/solidifier.go @@ -0,0 +1,57 @@ +package tangle + +import ( + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/ternary" +) + +func UpdateSolidity(transaction *Transaction) (bool, errors.IdentifiableError) { + // abort if transaction is solid already + txMetadata, err := transaction.GetMetaData() + if err != nil { + return false, err + } else if txMetadata.GetSolid() { + return true, nil + } + + // check solidity of branch transaction if it is not genesis + if branchTransactionHash := transaction.GetBranchTransactionHash(); branchTransactionHash != ternary.Trinary("999999") { + // abort if branch transaction is missing + branchTransaction, err := GetTransaction(branchTransactionHash) + if err != nil { + return false, err + } else if branchTransaction == nil { + return false, nil + } + + // abort if branch transaction is not solid + if branchTransactionMetadata, err := branchTransaction.GetMetaData(); err != nil { + return false, err + } else if !branchTransactionMetadata.GetSolid() { + return false, nil + } + } + + // check solidity of branch transaction if it is not genesis + if trunkTransactionHash := transaction.GetBranchTransactionHash(); trunkTransactionHash != ternary.Trinary("999999") { + // abort if trunk transaction is missing + trunkTransaction, err := GetTransaction(trunkTransactionHash) + if err != nil { + return false, err + } else if trunkTransaction == nil { + return false, nil + } + + // abort if trunk transaction is not solid + if trunkTransactionMetadata, err := trunkTransaction.GetMetaData(); err != nil { + return false, err + } else if !trunkTransactionMetadata.GetSolid() { + return false, nil + } + } + + // propagate solidity to all approvers + txMetadata.SetSolid(true) + + return true, nil +} \ No newline at end of file diff --git a/plugins/tangle/transaction.go b/plugins/tangle/transaction.go new file mode 100644 index 0000000000000000000000000000000000000000..034eb0d177a613a82a05789bd0d57c05cd429021 --- /dev/null +++ b/plugins/tangle/transaction.go @@ -0,0 +1,644 @@ +package tangle + +import ( + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/transaction" + "sync" +) + +// region type definition and constructor ////////////////////////////////////////////////////////////////////////////// + +type Transaction struct { + // wrapped objects + rawTransaction *transaction.Transaction + rawMetaData *TransactionMetadata + rawMetaDataMutex sync.RWMutex + + // mapped raw transaction properties + hash *ternary.Trinary + hashMutex sync.RWMutex + signatureMessageFragment *ternary.Trinary + signatureMessageFragmentMutex sync.RWMutex + address *ternary.Trinary + addressMutex sync.RWMutex + value *int64 + valueMutex sync.RWMutex + timestamp *uint64 + timestampMutex sync.RWMutex + currentIndex *uint64 + currentIndexMutex sync.RWMutex + latestIndex *uint64 + latestIndexMutex sync.RWMutex + bundleHash *ternary.Trinary + bundleHashMutex sync.RWMutex + trunkTransactionHash *ternary.Trinary + trunkTransactionHashMutex sync.RWMutex + branchTransactionHash *ternary.Trinary + branchTransactionHashMutex sync.RWMutex + tag *ternary.Trinary + tagMutex sync.RWMutex + nonce *ternary.Trinary + nonceMutex sync.RWMutex + + // additional runtime specific metadata + modified bool + modifiedMutex sync.RWMutex +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region getters and setters ////////////////////////////////////////////////////////////////////////////////////////// + +func (transaction *Transaction) GetMetaData() (*TransactionMetadata, errors.IdentifiableError) { + transaction.rawMetaDataMutex.RLock() + if transaction.rawMetaData == nil { + transaction.rawMetaDataMutex.RUnlock() + transaction.rawMetaDataMutex.Lock() + defer transaction.rawMetaDataMutex.Unlock() + if transaction.rawMetaData == nil { + if metaData, err := getTransactionMetadataFromDatabase(transaction.GetHash()); err != nil { + return nil, err + } else if metaData == nil { + transaction.rawMetaData = NewTransactionMetadata(transaction.GetHash()) + } else { + transaction.rawMetaData = metaData + } + + return transaction.rawMetaData, nil + } + } + + defer transaction.rawMetaDataMutex.RUnlock() + + return transaction.rawMetaData, nil +} + +// getter for the hash (supports concurrency) +func (transaction *Transaction) GetHash() ternary.Trinary { + transaction.hashMutex.RLock() + if transaction.hash == nil { + transaction.hashMutex.RUnlock() + transaction.hashMutex.Lock() + defer transaction.hashMutex.Unlock() + if transaction.hash == nil { + return transaction.parseHash() + } + } + + defer transaction.hashMutex.RUnlock() + + return *transaction.hash +} + +// setter for the hash (supports concurrency) +func (transaction *Transaction) SetHash(hash ternary.Trinary) { + transaction.hashMutex.RLock() + if transaction.hash == nil || *transaction.hash != hash { + transaction.hashMutex.RUnlock() + transaction.hashMutex.Lock() + defer transaction.hashMutex.Unlock() + if transaction.hash == nil || *transaction.hash != hash { + *transaction.hash = hash + + transaction.SetModified(true) + } + } else { + transaction.hashMutex.RUnlock() + } +} + +// restores the hash from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseHash() ternary.Trinary { + transaction.hashMutex.Lock() + defer transaction.hashMutex.Unlock() + + return transaction.parseHash() +} + +// parses the hash from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseHash() ternary.Trinary { + *transaction.hash = transaction.rawTransaction.Hash.ToTrinary() + + return *transaction.hash +} + +// getter for the address (supports concurrency) +func (transaction *Transaction) GetAddress() ternary.Trinary { + transaction.addressMutex.RLock() + if transaction.address == nil { + transaction.addressMutex.RUnlock() + transaction.addressMutex.Lock() + defer transaction.addressMutex.Unlock() + if transaction.address == nil { + return transaction.parseAddress() + } + } + + defer transaction.addressMutex.RUnlock() + + return *transaction.address +} + +// setter for the address (supports concurrency) +func (transaction *Transaction) SetAddress(address ternary.Trinary) { + transaction.addressMutex.RLock() + if transaction.address == nil || *transaction.address != address { + transaction.addressMutex.RUnlock() + transaction.addressMutex.Lock() + defer transaction.addressMutex.Unlock() + if transaction.address == nil || *transaction.address != address { + *transaction.address = address + + transaction.SetModified(true) + } + } else { + transaction.addressMutex.RUnlock() + } +} + +// restores the address from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseAddress() ternary.Trinary { + transaction.addressMutex.Lock() + defer transaction.addressMutex.Unlock() + + return transaction.parseAddress() +} + +// parses the address from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseAddress() ternary.Trinary { + *transaction.address = transaction.rawTransaction.Hash.ToTrinary() + + return *transaction.address +} + +// getter for the value (supports concurrency) +func (transaction *Transaction) GetValue() int64 { + transaction.valueMutex.RLock() + if transaction.value == nil { + transaction.valueMutex.RUnlock() + transaction.valueMutex.Lock() + defer transaction.valueMutex.Unlock() + if transaction.value == nil { + return transaction.parseValue() + } + } + + defer transaction.valueMutex.RUnlock() + + return *transaction.value +} + +// setter for the value (supports concurrency) +func (transaction *Transaction) SetValue(value int64) { + transaction.valueMutex.RLock() + if transaction.value == nil || *transaction.value != value { + transaction.valueMutex.RUnlock() + transaction.valueMutex.Lock() + defer transaction.valueMutex.Unlock() + if transaction.value == nil || *transaction.value != value { + *transaction.value = value + + transaction.SetModified(true) + } + } else { + transaction.valueMutex.RUnlock() + } +} + +// restores the value from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseValue() int64 { + transaction.valueMutex.Lock() + defer transaction.valueMutex.Unlock() + + return transaction.parseValue() +} + +// parses the value from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseValue() int64 { + *transaction.value = transaction.rawTransaction.Value.ToInt64() + + return *transaction.value +} + +// getter for the timestamp (supports concurrency) +func (transaction *Transaction) GetTimestamp() uint64 { + transaction.timestampMutex.RLock() + if transaction.timestamp == nil { + transaction.timestampMutex.RUnlock() + transaction.timestampMutex.Lock() + defer transaction.timestampMutex.Unlock() + if transaction.timestamp == nil { + return transaction.parseTimestamp() + } + } + + defer transaction.timestampMutex.RUnlock() + + return *transaction.timestamp +} + +// setter for the timestamp (supports concurrency) +func (transaction *Transaction) SetTimestamp(timestamp uint64) { + transaction.timestampMutex.RLock() + if transaction.timestamp == nil || *transaction.timestamp != timestamp { + transaction.timestampMutex.RUnlock() + transaction.timestampMutex.Lock() + defer transaction.timestampMutex.Unlock() + if transaction.timestamp == nil || *transaction.timestamp != timestamp { + *transaction.timestamp = timestamp + + transaction.SetModified(true) + } + } else { + transaction.timestampMutex.RUnlock() + } +} + +// restores the timestamp from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseTimestamp() uint64 { + transaction.timestampMutex.Lock() + defer transaction.timestampMutex.Unlock() + + return transaction.parseTimestamp() +} + +// parses the timestamp from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseTimestamp() uint64 { + *transaction.timestamp = transaction.rawTransaction.Timestamp.ToUint64() + + return *transaction.timestamp +} + +// getter for the currentIndex (supports concurrency) +func (transaction *Transaction) GetCurrentIndex() uint64 { + transaction.currentIndexMutex.RLock() + if transaction.currentIndex == nil { + transaction.currentIndexMutex.RUnlock() + transaction.currentIndexMutex.Lock() + defer transaction.currentIndexMutex.Unlock() + if transaction.currentIndex == nil { + return transaction.parseCurrentIndex() + } + } + + defer transaction.currentIndexMutex.RUnlock() + + return *transaction.currentIndex +} + +// setter for the currentIndex (supports concurrency) +func (transaction *Transaction) SetCurrentIndex(currentIndex uint64) { + transaction.currentIndexMutex.RLock() + if transaction.currentIndex == nil || *transaction.currentIndex != currentIndex { + transaction.currentIndexMutex.RUnlock() + transaction.currentIndexMutex.Lock() + defer transaction.currentIndexMutex.Unlock() + if transaction.currentIndex == nil || *transaction.currentIndex != currentIndex { + *transaction.currentIndex = currentIndex + + transaction.SetModified(true) + } + } else { + transaction.currentIndexMutex.RUnlock() + } +} + +// restores the currentIndex from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseCurrentIndex() uint64 { + transaction.currentIndexMutex.Lock() + defer transaction.currentIndexMutex.Unlock() + + return transaction.parseCurrentIndex() +} + +// parses the currentIndex from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseCurrentIndex() uint64 { + *transaction.currentIndex = transaction.rawTransaction.CurrentIndex.ToUint64() + + return *transaction.currentIndex +} + +// getter for the latestIndex (supports concurrency) +func (transaction *Transaction) GetLatestIndex() uint64 { + transaction.latestIndexMutex.RLock() + if transaction.latestIndex == nil { + transaction.latestIndexMutex.RUnlock() + transaction.latestIndexMutex.Lock() + defer transaction.latestIndexMutex.Unlock() + if transaction.latestIndex == nil { + return transaction.parseLatestIndex() + } + } + + defer transaction.latestIndexMutex.RUnlock() + + return *transaction.latestIndex +} + +// setter for the latestIndex (supports concurrency) +func (transaction *Transaction) SetLatestIndex(latestIndex uint64) { + transaction.latestIndexMutex.RLock() + if transaction.latestIndex == nil || *transaction.latestIndex != latestIndex { + transaction.latestIndexMutex.RUnlock() + transaction.latestIndexMutex.Lock() + defer transaction.latestIndexMutex.Unlock() + if transaction.latestIndex == nil || *transaction.latestIndex != latestIndex { + *transaction.latestIndex = latestIndex + + transaction.SetModified(true) + } + } else { + transaction.latestIndexMutex.RUnlock() + } +} + +// restores the latestIndex from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseLatestIndex() uint64 { + transaction.latestIndexMutex.Lock() + defer transaction.latestIndexMutex.Unlock() + + return transaction.parseLatestIndex() +} + +// parses the latestIndex from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseLatestIndex() uint64 { + *transaction.latestIndex = transaction.rawTransaction.LatestIndex.ToUint64() + + return *transaction.latestIndex +} + +// getter for the bundleHash (supports concurrency) +func (transaction *Transaction) GetBundleHash() ternary.Trinary { + transaction.bundleHashMutex.RLock() + if transaction.bundleHash == nil { + transaction.bundleHashMutex.RUnlock() + transaction.bundleHashMutex.Lock() + defer transaction.bundleHashMutex.Unlock() + if transaction.bundleHash == nil { + return transaction.parseBundleHash() + } + } + + defer transaction.bundleHashMutex.RUnlock() + + return *transaction.bundleHash +} + +// setter for the bundleHash (supports concurrency) +func (transaction *Transaction) SetBundleHash(bundleHash ternary.Trinary) { + transaction.bundleHashMutex.RLock() + if transaction.bundleHash == nil || *transaction.bundleHash != bundleHash { + transaction.bundleHashMutex.RUnlock() + transaction.bundleHashMutex.Lock() + defer transaction.bundleHashMutex.Unlock() + if transaction.bundleHash == nil || *transaction.bundleHash != bundleHash { + *transaction.bundleHash = bundleHash + + transaction.SetModified(true) + } + } else { + transaction.bundleHashMutex.RUnlock() + } +} + +// restores the bundleHash from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseBundleHash() ternary.Trinary { + transaction.bundleHashMutex.Lock() + defer transaction.bundleHashMutex.Unlock() + + return transaction.parseBundleHash() +} + +// parses the bundleHash from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseBundleHash() ternary.Trinary { + *transaction.bundleHash = transaction.rawTransaction.BundleHash.ToTrinary() + + return *transaction.bundleHash +} + +// getter for the trunkTransactionHash (supports concurrency) +func (transaction *Transaction) GetTrunkTransactionHash() ternary.Trinary { + transaction.trunkTransactionHashMutex.RLock() + if transaction.trunkTransactionHash == nil { + transaction.trunkTransactionHashMutex.RUnlock() + transaction.trunkTransactionHashMutex.Lock() + defer transaction.trunkTransactionHashMutex.Unlock() + if transaction.trunkTransactionHash == nil { + return transaction.parseTrunkTransactionHash() + } + } + + defer transaction.trunkTransactionHashMutex.RUnlock() + + return *transaction.trunkTransactionHash +} + +// setter for the trunkTransactionHash (supports concurrency) +func (transaction *Transaction) SetTrunkTransactionHash(trunkTransactionHash ternary.Trinary) { + transaction.trunkTransactionHashMutex.RLock() + if transaction.trunkTransactionHash == nil || *transaction.trunkTransactionHash != trunkTransactionHash { + transaction.trunkTransactionHashMutex.RUnlock() + transaction.trunkTransactionHashMutex.Lock() + defer transaction.trunkTransactionHashMutex.Unlock() + if transaction.trunkTransactionHash == nil || *transaction.trunkTransactionHash != trunkTransactionHash { + *transaction.trunkTransactionHash = trunkTransactionHash + + transaction.SetModified(true) + } + } else { + transaction.trunkTransactionHashMutex.RUnlock() + } +} + +// restores the trunkTransactionHash from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseTrunkTransactionHash() ternary.Trinary { + transaction.trunkTransactionHashMutex.Lock() + defer transaction.trunkTransactionHashMutex.Unlock() + + return transaction.parseTrunkTransactionHash() +} + +// parses the trunkTransactionHash from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseTrunkTransactionHash() ternary.Trinary { + *transaction.trunkTransactionHash = transaction.rawTransaction.TrunkTransactionHash.ToTrinary() + + return *transaction.trunkTransactionHash +} + +// getter for the branchTransactionHash (supports concurrency) +func (transaction *Transaction) GetBranchTransactionHash() ternary.Trinary { + transaction.branchTransactionHashMutex.RLock() + if transaction.branchTransactionHash == nil { + transaction.branchTransactionHashMutex.RUnlock() + transaction.branchTransactionHashMutex.Lock() + defer transaction.branchTransactionHashMutex.Unlock() + if transaction.branchTransactionHash == nil { + return transaction.parseBranchTransactionHash() + } + } + + defer transaction.branchTransactionHashMutex.RUnlock() + + return *transaction.branchTransactionHash +} + +// setter for the branchTransactionHash (supports concurrency) +func (transaction *Transaction) SetBranchTransactionHash(branchTransactionHash ternary.Trinary) { + transaction.branchTransactionHashMutex.RLock() + if transaction.branchTransactionHash == nil || *transaction.branchTransactionHash != branchTransactionHash { + transaction.branchTransactionHashMutex.RUnlock() + transaction.branchTransactionHashMutex.Lock() + defer transaction.branchTransactionHashMutex.Unlock() + if transaction.branchTransactionHash == nil || *transaction.branchTransactionHash != branchTransactionHash { + *transaction.branchTransactionHash = branchTransactionHash + + transaction.SetModified(true) + } + } else { + transaction.branchTransactionHashMutex.RUnlock() + } +} + +// restores the branchTransactionHash from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseBranchTransactionHash() ternary.Trinary { + transaction.branchTransactionHashMutex.Lock() + defer transaction.branchTransactionHashMutex.Unlock() + + return transaction.parseBranchTransactionHash() +} + +// parses the branchTransactionHash from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseBranchTransactionHash() ternary.Trinary { + *transaction.branchTransactionHash = transaction.rawTransaction.BranchTransactionHash.ToTrinary() + + return *transaction.branchTransactionHash +} + +// getter for the tag (supports concurrency) +func (transaction *Transaction) GetTag() ternary.Trinary { + transaction.tagMutex.RLock() + if transaction.tag == nil { + transaction.tagMutex.RUnlock() + transaction.tagMutex.Lock() + defer transaction.tagMutex.Unlock() + if transaction.tag == nil { + return transaction.parseTag() + } + } + + defer transaction.tagMutex.RUnlock() + + return *transaction.tag +} + +// setter for the tag (supports concurrency) +func (transaction *Transaction) SetTag(tag ternary.Trinary) { + transaction.tagMutex.RLock() + if transaction.tag == nil || *transaction.tag != tag { + transaction.tagMutex.RUnlock() + transaction.tagMutex.Lock() + defer transaction.tagMutex.Unlock() + if transaction.tag == nil || *transaction.tag != tag { + *transaction.tag = tag + + transaction.SetModified(true) + } + } else { + transaction.tagMutex.RUnlock() + } +} + +// restores the tag from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseTag() ternary.Trinary { + transaction.tagMutex.Lock() + defer transaction.tagMutex.Unlock() + + return transaction.parseTag() +} + +// parses the tag from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseTag() ternary.Trinary { + *transaction.tag = transaction.rawTransaction.Tag.ToTrinary() + + return *transaction.tag +} + +// getter for the nonce (supports concurrency) +func (transaction *Transaction) GetNonce() ternary.Trinary { + transaction.nonceMutex.RLock() + if transaction.nonce == nil { + transaction.nonceMutex.RUnlock() + transaction.nonceMutex.Lock() + defer transaction.nonceMutex.Unlock() + if transaction.nonce == nil { + return transaction.parseNonce() + } + } + + defer transaction.nonceMutex.RUnlock() + + return *transaction.nonce +} + +// setter for the nonce (supports concurrency) +func (transaction *Transaction) SetNonce(nonce ternary.Trinary) { + transaction.nonceMutex.RLock() + if transaction.nonce == nil || *transaction.nonce != nonce { + transaction.nonceMutex.RUnlock() + transaction.nonceMutex.Lock() + defer transaction.nonceMutex.Unlock() + if transaction.nonce == nil || *transaction.nonce != nonce { + *transaction.nonce = nonce + + transaction.SetModified(true) + } + } else { + transaction.nonceMutex.RUnlock() + } +} + +// restores the nonce from the underlying raw transaction (supports concurrency) +func (transaction *Transaction) ParseNonce() ternary.Trinary { + transaction.nonceMutex.Lock() + defer transaction.nonceMutex.Unlock() + + return transaction.parseNonce() +} + +// parses the nonce from the underlying raw transaction (without locking - internal usage) +func (transaction *Transaction) parseNonce() ternary.Trinary { + *transaction.nonce = transaction.rawTransaction.Nonce.ToTrinary() + + return *transaction.nonce +} + +// returns true if the transaction contains unsaved changes (supports concurrency) +func (transaction *Transaction) GetModified() bool { + transaction.modifiedMutex.RLock() + defer transaction.modifiedMutex.RUnlock() + + return transaction.modified +} + +// sets the modified flag which controls if a transaction is going to be saved (supports concurrency) +func (transaction *Transaction) SetModified(modified bool) { + transaction.modifiedMutex.Lock() + defer transaction.modifiedMutex.Unlock() + + transaction.modified = modified +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region database functions /////////////////////////////////////////////////////////////////////////////////////////// + +func (transaction *Transaction) Store() errors.IdentifiableError { + if err := transactionDatabase.Set(transaction.rawTransaction.Hash.ToBytes(), transaction.rawTransaction.Bytes); err != nil { + return ErrDatabaseError.Derive(err, "failed to store the transaction") + } + + return nil +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/tangle/transaction_metadata.go b/plugins/tangle/transaction_metadata.go new file mode 100644 index 0000000000000000000000000000000000000000..ce0e7f08ddf00be51c8365aa1d4a39bf10cba000 --- /dev/null +++ b/plugins/tangle/transaction_metadata.go @@ -0,0 +1,287 @@ +package tangle + +import ( + "github.com/iotaledger/goshimmer/packages/bitutils" + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/typeconversion" + "sync" + "time" +) + +// 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 NewTransactionMetadata(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) { + 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) + } + } else { + metaData.solidMutex.RUnlock() + } +} + +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 marshalling functions //////////////////////////////////////////////////////////////////////////////////////// + +func (metadata *TransactionMetadata) Marshal() ([]byte, errors.IdentifiableError) { + marshalledMetadata := make([]byte, MARSHALLED_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(marshalledMetadata[MARSHALLED_HASH_START:MARSHALLED_HASH_END], metadata.hash.CastToBytes()) + + marshalledReceivedTime, err := metadata.receivedTime.MarshalBinary() + if err != nil { + return nil, ErrMarshallFailed.Derive(err, "failed to marshal received time") + } + copy(marshalledMetadata[MARSHALLED_RECEIVED_TIME_START:MARSHALLED_RECEIVED_TIME_END], marshalledReceivedTime) + + 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) + } + marshalledMetadata[MARSHALLED_FLAGS_START] = byte(booleanFlags) + + return marshalledMetadata, 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(typeconversion.BytesToString(data[MARSHALLED_HASH_START:MARSHALLED_HASH_END])) + + if err := metadata.receivedTime.UnmarshalBinary(data[MARSHALLED_RECEIVED_TIME_START:MARSHALLED_RECEIVED_TIME_END]); err != nil { + return ErrUnmarshalFailed.Derive(err, "could not unmarshal the received time") + } + + booleanFlags := bitutils.BitMask(data[MARSHALLED_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 /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region database functions /////////////////////////////////////////////////////////////////////////////////////////// + +func (metadata *TransactionMetadata) Store() 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 the transaction") + } + + metadata.SetModified(false) + } + + return nil +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region constants and variables ////////////////////////////////////////////////////////////////////////////////////// + +const ( + MARSHALLED_HASH_START = 0 + MARSHALLED_RECEIVED_TIME_START = MARSHALLED_HASH_END + MARSHALLED_FLAGS_START = MARSHALLED_RECEIVED_TIME_END + + MARSHALLED_HASH_END = MARSHALLED_HASH_START + MARSHALLED_HASH_SIZE + MARSHALLED_RECEIVED_TIME_END = MARSHALLED_RECEIVED_TIME_START + MARSHALLED_RECEIVED_TIME_SIZE + MARSHALLED_FLAGS_END = MARSHALLED_FLAGS_START + MARSHALLED_FLAGS_SIZE + + MARSHALLED_HASH_SIZE = 81 + MARSHALLED_RECEIVED_TIME_SIZE = 15 + MARSHALLED_FLAGS_SIZE = 1 + + MARSHALLED_TOTAL_SIZE = MARSHALLED_FLAGS_END +) + +// endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////