diff --git a/packages/model/meta_transaction/constants.go b/packages/model/meta_transaction/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..3db3cb053683b7958a0d491fb92b8cd3990416db --- /dev/null +++ b/packages/model/meta_transaction/constants.go @@ -0,0 +1,29 @@ +package meta_transaction + +const ( + SHARD_MARKER_OFFSET = 0 + TRUNK_TRANSACTION_HASH_OFFSET = SHARD_MARKER_END + BRANCH_TRANSACTION_HASH_OFFSET = TRUNK_TRANSACTION_HASH_END + HEAD_OFFSET = BRANCH_TRANSACTION_HASH_END + TAIL_OFFSET = HEAD_END + TRANSACTION_TYPE_OFFSET = TAIL_END + DATA_OFFSET = TRANSACTION_TYPE_END + + SHARD_MARKER_SIZE = 12 + TRUNK_TRANSACTION_HASH_SIZE = 243 + BRANCH_TRANSACTION_HASH_SIZE = 243 + HEAD_SIZE = 1 + TAIL_SIZE = 1 + TRANSACTION_TYPE_SIZE = 22 + DATA_SIZE = 5000 + + SHARD_MARKER_END = SHARD_MARKER_OFFSET + SHARD_MARKER_SIZE + TRUNK_TRANSACTION_HASH_END = TRUNK_TRANSACTION_HASH_OFFSET + TRUNK_TRANSACTION_HASH_SIZE + BRANCH_TRANSACTION_HASH_END = BRANCH_TRANSACTION_HASH_OFFSET + BRANCH_TRANSACTION_HASH_SIZE + HEAD_END = HEAD_OFFSET + HEAD_SIZE + TAIL_END = TAIL_OFFSET + TAIL_SIZE + TRANSACTION_TYPE_END = TRANSACTION_TYPE_OFFSET + TRANSACTION_TYPE_SIZE + DATA_END = DATA_OFFSET + DATA_SIZE + + MARSHALLED_TOTAL_SIZE = DATA_END +) diff --git a/packages/model/meta_transaction/meta_transaction.go b/packages/model/meta_transaction/meta_transaction.go new file mode 100644 index 0000000000000000000000000000000000000000..ea8e83b8abca89924fb9bd09b8f96e76a1e33ef9 --- /dev/null +++ b/packages/model/meta_transaction/meta_transaction.go @@ -0,0 +1,480 @@ +package meta_transaction + +import ( + "sync" + + "github.com/iotaledger/goshimmer/packages/curl" + "github.com/iotaledger/goshimmer/packages/ternary" +) + +type MetaTransaction struct { + hash *ternary.Trinary + weightMagnitude int + + shardMarker *ternary.Trinary + trunkTransactionHash *ternary.Trinary + branchTransactionHash *ternary.Trinary + head *bool + tail *bool + transactionType *ternary.Trinary + data ternary.Trits + modified bool + + hasherMutex sync.RWMutex + hashMutex sync.RWMutex + shardMarkerMutex sync.RWMutex + trunkTransactionHashMutex sync.RWMutex + branchTransactionHashMutex sync.RWMutex + headMutex sync.RWMutex + tailMutex sync.RWMutex + transactionTypeMutex sync.RWMutex + dataMutex sync.RWMutex + bytesMutex sync.RWMutex + modifiedMutex sync.RWMutex + + trits ternary.Trits + bytes []byte +} + +func New() *MetaTransaction { + return FromTrits(make(ternary.Trits, MARSHALLED_TOTAL_SIZE)) +} + +func FromTrits(trits ternary.Trits) *MetaTransaction { + return &MetaTransaction{ + trits: trits, + } +} + +func FromBytes(bytes []byte) (result *MetaTransaction) { + result = FromTrits(ternary.BytesToTrits(bytes)[:MARSHALLED_TOTAL_SIZE]) + result.bytes = bytes + + return +} + +func (this *MetaTransaction) LockHasher() { + this.hasherMutex.RLock() +} + +func (this *MetaTransaction) UnlockHasher() { + this.hasherMutex.RUnlock() +} + +func (this *MetaTransaction) ReHash() { + this.hashMutex.Lock() + defer this.hashMutex.Unlock() + this.hash = nil + + this.bytesMutex.Lock() + defer this.bytesMutex.Unlock() + this.bytes = nil +} + +// retrieves the hash of the transaction +func (this *MetaTransaction) GetHash() (result ternary.Trinary) { + this.hashMutex.RLock() + if this.hash == nil { + this.hashMutex.RUnlock() + this.hashMutex.Lock() + defer this.hashMutex.Unlock() + if this.hash == nil { + this.hasherMutex.Lock() + this.parseHashRelatedDetails() + this.hasherMutex.Unlock() + } + } else { + defer this.hashMutex.RUnlock() + } + + result = *this.hash + + return +} + +// retrieves weight magnitude of the transaction (amount of pow invested) +func (this *MetaTransaction) GetWeightMagnitude() (result int) { + this.hashMutex.RLock() + if this.hash == nil { + this.hashMutex.RUnlock() + this.hashMutex.Lock() + defer this.hashMutex.Unlock() + if this.hash == nil { + this.hasherMutex.Lock() + this.parseHashRelatedDetails() + this.hasherMutex.Unlock() + } + } else { + defer this.hashMutex.RUnlock() + } + + result = this.weightMagnitude + + return +} + +// hashes the transaction using curl (without locking - internal usage) +func (this *MetaTransaction) parseHashRelatedDetails() { + hashTrits := <-curl.CURLP81.Hash(this.trits) + hashTrinary := hashTrits.ToTrinary() + + this.hash = &hashTrinary + this.weightMagnitude = hashTrits.TrailingZeroes() + + return +} + +// getter for the shard marker (supports concurrency) +func (this *MetaTransaction) GetShardMarker() (result ternary.Trinary) { + this.shardMarkerMutex.RLock() + if this.shardMarker == nil { + this.shardMarkerMutex.RUnlock() + this.shardMarkerMutex.Lock() + defer this.shardMarkerMutex.Unlock() + if this.shardMarker == nil { + shardMarker := this.trits[SHARD_MARKER_OFFSET:SHARD_MARKER_END].ToTrinary() + + this.shardMarker = &shardMarker + } + } else { + defer this.shardMarkerMutex.RUnlock() + } + + result = *this.shardMarker + + return +} + +// setter for the shard marker (supports concurrency) +func (this *MetaTransaction) SetShardMarker(shardMarker ternary.Trinary) bool { + this.shardMarkerMutex.RLock() + if this.shardMarker == nil || *this.shardMarker != shardMarker { + this.shardMarkerMutex.RUnlock() + this.shardMarkerMutex.Lock() + defer this.shardMarkerMutex.Unlock() + if this.shardMarker == nil || *this.shardMarker != shardMarker { + this.shardMarker = &shardMarker + + this.hasherMutex.RLock() + copy(this.trits[SHARD_MARKER_OFFSET:SHARD_MARKER_END], shardMarker.ToTrits()[:SHARD_MARKER_SIZE]) + this.hasherMutex.RUnlock() + + this.SetModified(true) + this.ReHash() + + return true + } + } else { + this.shardMarkerMutex.RUnlock() + } + + return false +} + +// getter for the bundleHash (supports concurrency) +func (this *MetaTransaction) GetTrunkTransactionHash() (result ternary.Trinary) { + this.trunkTransactionHashMutex.RLock() + if this.trunkTransactionHash == nil { + this.trunkTransactionHashMutex.RUnlock() + this.trunkTransactionHashMutex.Lock() + defer this.trunkTransactionHashMutex.Unlock() + if this.trunkTransactionHash == nil { + trunkTransactionHash := this.trits[TRUNK_TRANSACTION_HASH_OFFSET:TRUNK_TRANSACTION_HASH_END].ToTrinary() + + this.trunkTransactionHash = &trunkTransactionHash + } + } else { + defer this.trunkTransactionHashMutex.RUnlock() + } + + result = *this.trunkTransactionHash + + return +} + +// setter for the trunkTransactionHash (supports concurrency) +func (this *MetaTransaction) SetTrunkTransactionHash(trunkTransactionHash ternary.Trinary) bool { + this.trunkTransactionHashMutex.RLock() + if this.trunkTransactionHash == nil || *this.trunkTransactionHash != trunkTransactionHash { + this.trunkTransactionHashMutex.RUnlock() + this.trunkTransactionHashMutex.Lock() + defer this.trunkTransactionHashMutex.Unlock() + if this.trunkTransactionHash == nil || *this.trunkTransactionHash != trunkTransactionHash { + this.trunkTransactionHash = &trunkTransactionHash + + this.hasherMutex.RLock() + copy(this.trits[TRUNK_TRANSACTION_HASH_OFFSET:TRUNK_TRANSACTION_HASH_END], trunkTransactionHash.ToTrits()[:TRUNK_TRANSACTION_HASH_SIZE]) + this.hasherMutex.RUnlock() + + this.SetModified(true) + this.ReHash() + + return true + } + } else { + this.trunkTransactionHashMutex.RUnlock() + } + + return false +} + +// getter for the bundleHash (supports concurrency) +func (this *MetaTransaction) GetBranchTransactionHash() (result ternary.Trinary) { + this.branchTransactionHashMutex.RLock() + if this.branchTransactionHash == nil { + this.branchTransactionHashMutex.RUnlock() + this.branchTransactionHashMutex.Lock() + defer this.branchTransactionHashMutex.Unlock() + if this.branchTransactionHash == nil { + branchTransactionHash := this.trits[BRANCH_TRANSACTION_HASH_OFFSET:BRANCH_TRANSACTION_HASH_END].ToTrinary() + + this.branchTransactionHash = &branchTransactionHash + } + } else { + defer this.branchTransactionHashMutex.RUnlock() + } + + result = *this.branchTransactionHash + + return +} + +// setter for the trunkTransactionHash (supports concurrency) +func (this *MetaTransaction) SetBranchTransactionHash(branchTransactionHash ternary.Trinary) bool { + this.branchTransactionHashMutex.RLock() + if this.branchTransactionHash == nil || *this.branchTransactionHash != branchTransactionHash { + this.branchTransactionHashMutex.RUnlock() + this.branchTransactionHashMutex.Lock() + defer this.branchTransactionHashMutex.Unlock() + if this.branchTransactionHash == nil || *this.branchTransactionHash != branchTransactionHash { + this.branchTransactionHash = &branchTransactionHash + + this.hasherMutex.RLock() + copy(this.trits[BRANCH_TRANSACTION_HASH_OFFSET:BRANCH_TRANSACTION_HASH_END], branchTransactionHash.ToTrits()[:BRANCH_TRANSACTION_HASH_SIZE]) + this.hasherMutex.RUnlock() + + this.SetModified(true) + this.ReHash() + + return true + } + } else { + this.branchTransactionHashMutex.RUnlock() + } + + return false +} + +// getter for the head flag (supports concurrency) +func (this *MetaTransaction) GetHead() (result bool) { + this.headMutex.RLock() + if this.head == nil { + this.headMutex.RUnlock() + this.headMutex.Lock() + defer this.headMutex.Unlock() + if this.head == nil { + head := this.trits[HEAD_OFFSET] == 1 + + this.head = &head + } + } else { + defer this.headMutex.RUnlock() + } + + result = *this.head + + return +} + +// setter for the head flag (supports concurrency) +func (this *MetaTransaction) SetHead(head bool) bool { + this.headMutex.RLock() + if this.head == nil || *this.head != head { + this.headMutex.RUnlock() + this.headMutex.Lock() + defer this.headMutex.Unlock() + if this.head == nil || *this.head != head { + this.head = &head + + this.hasherMutex.RLock() + if head { + this.trits[HEAD_OFFSET] = 1 + } else { + this.trits[HEAD_OFFSET] = 0 + } + this.hasherMutex.RUnlock() + + this.SetModified(true) + this.ReHash() + + return true + } + } else { + this.headMutex.RUnlock() + } + + return false +} + +// getter for the tail flag (supports concurrency) +func (this *MetaTransaction) GetTail() (result bool) { + this.tailMutex.RLock() + if this.tail == nil { + this.tailMutex.RUnlock() + this.tailMutex.Lock() + defer this.tailMutex.Unlock() + if this.tail == nil { + tail := this.trits[TAIL_OFFSET] == 1 + + this.tail = &tail + } + } else { + defer this.tailMutex.RUnlock() + } + + result = *this.tail + + return +} + +// setter for the tail flag (supports concurrency) +func (this *MetaTransaction) SetTail(tail bool) bool { + this.tailMutex.RLock() + if this.tail == nil || *this.tail != tail { + this.tailMutex.RUnlock() + this.tailMutex.Lock() + defer this.tailMutex.Unlock() + if this.tail == nil || *this.tail != tail { + this.tail = &tail + + this.hasherMutex.RLock() + if tail { + this.trits[TAIL_OFFSET] = 1 + } else { + this.trits[TAIL_OFFSET] = 0 + } + this.hasherMutex.RUnlock() + + this.SetModified(true) + this.ReHash() + + return true + } + } else { + this.tailMutex.RUnlock() + } + + return false +} + +// getter for the transaction type (supports concurrency) +func (this *MetaTransaction) GetTransactionType() (result ternary.Trinary) { + this.transactionTypeMutex.RLock() + if this.transactionType == nil { + this.transactionTypeMutex.RUnlock() + this.transactionTypeMutex.Lock() + defer this.transactionTypeMutex.Unlock() + if this.transactionType == nil { + transactionType := this.trits[TRANSACTION_TYPE_OFFSET:TRANSACTION_TYPE_END].ToTrinary() + + this.transactionType = &transactionType + } + } else { + defer this.transactionTypeMutex.RUnlock() + } + + result = *this.transactionType + + return +} + +// setter for the transaction type (supports concurrency) +func (this *MetaTransaction) SetTransactionType(transactionType ternary.Trinary) bool { + this.transactionTypeMutex.RLock() + if this.transactionType == nil || *this.transactionType != transactionType { + this.transactionTypeMutex.RUnlock() + this.transactionTypeMutex.Lock() + defer this.transactionTypeMutex.Unlock() + if this.transactionType == nil || *this.transactionType != transactionType { + this.transactionType = &transactionType + + this.hasherMutex.RLock() + copy(this.trits[TRANSACTION_TYPE_OFFSET:TRANSACTION_TYPE_END], transactionType.ToTrits()[:TRANSACTION_TYPE_SIZE]) + this.hasherMutex.RUnlock() + + this.SetModified(true) + this.ReHash() + + return true + } + } else { + this.transactionTypeMutex.RUnlock() + } + + return false +} + +// getter for the data slice (supports concurrency) +func (this *MetaTransaction) GetData() (result ternary.Trits) { + this.dataMutex.RLock() + if this.data == nil { + this.dataMutex.RUnlock() + this.dataMutex.Lock() + defer this.dataMutex.Unlock() + if this.data == nil { + this.data = this.trits[DATA_OFFSET:DATA_END] + } + } else { + defer this.dataMutex.RUnlock() + } + + result = this.data + + return +} + +func (this *MetaTransaction) GetTrits() (result ternary.Trits) { + result = make(ternary.Trits, len(this.trits)) + + this.hasherMutex.Lock() + copy(result, this.trits) + this.hasherMutex.Unlock() + + return +} + +func (this *MetaTransaction) GetBytes() (result []byte) { + this.bytesMutex.RLock() + if this.bytes == nil { + this.bytesMutex.RUnlock() + this.bytesMutex.Lock() + defer this.bytesMutex.Unlock() + + this.hasherMutex.Lock() + this.bytes = this.trits.ToBytes() + this.hasherMutex.Unlock() + } else { + this.hasherMutex.RUnlock() + } + + result = make([]byte, len(this.bytes)) + copy(result, this.bytes) + + return +} + +// returns true if the transaction contains unsaved changes (supports concurrency) +func (this *MetaTransaction) GetModified() bool { + this.modifiedMutex.RLock() + defer this.modifiedMutex.RUnlock() + + return this.modified +} + +// sets the modified flag which controls if a transaction is going to be saved (supports concurrency) +func (this *MetaTransaction) SetModified(modified bool) { + this.modifiedMutex.Lock() + defer this.modifiedMutex.Unlock() + + this.modified = modified +} diff --git a/packages/model/meta_transaction/meta_transaction_test.go b/packages/model/meta_transaction/meta_transaction_test.go new file mode 100644 index 0000000000000000000000000000000000000000..dd54c2b2245cf96c0bf88bc17da799fb1c9c1def --- /dev/null +++ b/packages/model/meta_transaction/meta_transaction_test.go @@ -0,0 +1,54 @@ +package meta_transaction + +import ( + "fmt" + "sync" + "testing" + + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/magiconair/properties/assert" +) + +func TestMetaTransaction_SettersGetters(t *testing.T) { + shardMarker := ternary.Trinary("NPHTQORL9XKA") + trunkTransactionHash := ternary.Trinary("99999999999999999999999999999999999999999999999999999999999999999999999999999999A") + branchTransactionHash := ternary.Trinary("99999999999999999999999999999999999999999999999999999999999999999999999999999999B") + head := true + tail := true + transactionType := ternary.Trinary("9999999999999999999999") + + transaction := New() + transaction.SetShardMarker(shardMarker) + transaction.SetTrunkTransactionHash(trunkTransactionHash) + transaction.SetBranchTransactionHash(branchTransactionHash) + transaction.SetHead(head) + transaction.SetTail(tail) + transaction.SetTransactionType(transactionType) + + assert.Equal(t, transaction.GetWeightMagnitude(), 0) + assert.Equal(t, transaction.GetShardMarker(), shardMarker) + assert.Equal(t, transaction.GetTrunkTransactionHash(), trunkTransactionHash) + assert.Equal(t, transaction.GetBranchTransactionHash(), branchTransactionHash) + assert.Equal(t, transaction.GetHead(), head) + assert.Equal(t, transaction.GetTail(), tail) + assert.Equal(t, transaction.GetTransactionType(), transactionType) + assert.Equal(t, transaction.GetHash(), FromBytes(transaction.GetBytes()).GetHash()) + + fmt.Println(transaction.GetHash()) +} + +func BenchmarkMetaTransaction_GetHash(b *testing.B) { + var waitGroup sync.WaitGroup + + for i := 0; i < b.N; i++ { + go func() { + waitGroup.Add(1) + + New().GetHash() + + waitGroup.Done() + }() + } + + waitGroup.Wait() +} diff --git a/packages/model/value_transaction/value_transaction.go b/packages/model/value_transaction/value_transaction.go new file mode 100644 index 0000000000000000000000000000000000000000..337f003c2c1f5ad525d794305c12dbe10abae1a9 --- /dev/null +++ b/packages/model/value_transaction/value_transaction.go @@ -0,0 +1,22 @@ +package value_transaction + +import ( + "github.com/iotaledger/goshimmer/packages/model/meta_transaction" + "github.com/iotaledger/goshimmer/packages/ternary" +) + +type ValueTransaction struct { + *meta_transaction.MetaTransaction + + data ternary.Trits +} + +func New() (result *ValueTransaction) { + result = &ValueTransaction{ + MetaTransaction: meta_transaction.New(), + } + + result.data = result.MetaTransaction.GetData() + + return +} diff --git a/packages/model/value_transaction/value_transaction_test.go b/packages/model/value_transaction/value_transaction_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f81aa1ab02c1e79d988577c480c5d077f38bd1b3 --- /dev/null +++ b/packages/model/value_transaction/value_transaction_test.go @@ -0,0 +1,34 @@ +package value_transaction + +import ( + "fmt" + "testing" + + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/magiconair/properties/assert" +) + +func TestMetaTransaction_SettersGetters(t *testing.T) { + shardMarker := ternary.Trinary("NPHTQORL9XKA") + trunkTransactionHash := ternary.Trinary("99999999999999999999999999999999999999999999999999999999999999999999999999999999A") + branchTransactionHash := ternary.Trinary("99999999999999999999999999999999999999999999999999999999999999999999999999999999B") + head := true + tail := true + + transaction := New() + transaction.SetShardMarker(shardMarker) + transaction.SetTrunkTransactionHash(trunkTransactionHash) + transaction.SetBranchTransactionHash(branchTransactionHash) + transaction.SetHead(head) + transaction.SetTail(tail) + + assert.Equal(t, transaction.GetWeightMagnitude(), 0) + assert.Equal(t, transaction.GetShardMarker(), shardMarker) + assert.Equal(t, transaction.GetTrunkTransactionHash(), trunkTransactionHash) + assert.Equal(t, transaction.GetBranchTransactionHash(), branchTransactionHash) + assert.Equal(t, transaction.GetHead(), head) + assert.Equal(t, transaction.GetTail(), tail) + //assert.Equal(t, transaction.GetHash(), FromBytes(transaction.GetBytes()).GetHash()) + + fmt.Println(transaction.GetHash()) +}