From b0ba1c9a564ef8b93aab83a5173307dbbcd5722d Mon Sep 17 00:00:00 2001 From: Hans Moog <hm@mkjc.net> Date: Mon, 10 Jun 2019 16:36:22 +0200 Subject: [PATCH] Feat: several features added - lru cache - soldifier for tangle - ... --- packages/datastructure/lru_cache.go | 150 +++-- packages/datastructure/lru_cache_test.go | 55 +- packages/events/event_test.go | 38 ++ packages/iac/area.go | 38 ++ packages/iac/errors.go | 8 + packages/iac/iac.go | 25 + packages/iac/olc_conversion.go | 56 ++ packages/ternary/ternary.go | 89 ++- packages/transaction/transaction.go | 86 +-- plugins/tangle/api.go | 82 ++- plugins/tangle/approvers.go | 172 ++++- plugins/tangle/approvers_test.go | 73 +++ plugins/tangle/database.go | 117 ++-- plugins/tangle/mempool.go | 130 ---- plugins/tangle/plugin.go | 8 +- plugins/tangle/solidifier.go | 203 ++++-- plugins/tangle/transaction.go | 769 ++++++++++++----------- plugins/tangle/transaction_metadata.go | 395 ++++++------ 18 files changed, 1463 insertions(+), 1031 deletions(-) create mode 100644 packages/events/event_test.go create mode 100644 packages/iac/area.go create mode 100644 packages/iac/errors.go create mode 100644 packages/iac/iac.go create mode 100644 packages/iac/olc_conversion.go create mode 100644 plugins/tangle/approvers_test.go delete mode 100644 plugins/tangle/mempool.go diff --git a/packages/datastructure/lru_cache.go b/packages/datastructure/lru_cache.go index 7b4e2a2d..a9e9c406 100644 --- a/packages/datastructure/lru_cache.go +++ b/packages/datastructure/lru_cache.go @@ -10,48 +10,46 @@ type lruCacheElement struct { } type LRUCache struct { - directory map[interface{}]*DoublyLinkedListEntry - doublyLinkedList *DoublyLinkedList - capacity int - size int - processingCallback bool - mutex sync.RWMutex + directory map[interface{}]*DoublyLinkedListEntry + doublyLinkedList *DoublyLinkedList + capacity int + size int + mutex sync.RWMutex } func NewLRUCache(capacity int) *LRUCache { return &LRUCache{ - directory: make(map[interface{}]*DoublyLinkedListEntry), + directory: make(map[interface{}]*DoublyLinkedListEntry, capacity), doublyLinkedList: &DoublyLinkedList{}, capacity: capacity, } } func (cache *LRUCache) Set(key interface{}, value interface{}) { - if !cache.processingCallback { - cache.mutex.Lock() - defer cache.mutex.Unlock() - } + cache.mutex.Lock() + defer cache.mutex.Unlock() - element, exists := cache.directory[key] - if exists { - if !cache.processingCallback { - element.GetValue().(*lruCacheElement).value = value + cache.set(key, value) +} - cache.doublyLinkedList.mutex.Lock() - defer cache.doublyLinkedList.mutex.Unlock() - } else { - element.value.(*lruCacheElement).value = value - } +func (cache *LRUCache) set(key interface{}, value interface{}) { + directory := cache.directory + + if element, exists := directory[key]; exists { + element.value.(*lruCacheElement).value = value cache.promoteElement(element) } else { - cache.directory[key] = cache.doublyLinkedList.AddFirst(&lruCacheElement{key: key, value: value}) + linkedListEntry := &DoublyLinkedListEntry{value: &lruCacheElement{key: key, value: value}} + + cache.doublyLinkedList.addFirstEntry(linkedListEntry) + directory[key] = linkedListEntry if cache.size == cache.capacity { - if element, err := cache.doublyLinkedList.RemoveLast(); err != nil { + if element, err := cache.doublyLinkedList.removeLastEntry(); err != nil { panic(err) } else { - delete(cache.directory, element.(*lruCacheElement).key) + delete(directory, element.value.(*lruCacheElement).key) } } else { cache.size++ @@ -59,41 +57,83 @@ func (cache *LRUCache) Set(key interface{}, value interface{}) { } } -func (cache *LRUCache) Contains(key interface{}, optionalCallback ... func(interface{}, bool)) bool { - var callback func(interface{}, bool) +func (cache *LRUCache) ComputeIfAbsent(key interface{}, callback func() interface{}) (result interface{}) { + cache.mutex.Lock() + defer cache.mutex.Unlock() - if len(optionalCallback) >= 1 { - if !cache.processingCallback { - cache.mutex.Lock() - defer cache.mutex.Unlock() - } + if element, exists := cache.directory[key]; exists { + cache.promoteElement(element) - callback = optionalCallback[0] + result = element.GetValue().(*lruCacheElement).value } else { - if !cache.processingCallback { - cache.mutex.RLock() - defer cache.mutex.RUnlock() + if result = callback(); result != nil { + cache.set(key, result) } } - var elementValue interface{} - element, exists := cache.directory[key] - if exists { - cache.doublyLinkedList.mutex.Lock() - defer cache.doublyLinkedList.mutex.Unlock() + return +} + +// Calls the callback if an entry with the given key exists. +// The result of the callback is written back into the cache. +// If the callback returns nil the entry is removed from the cache. +// Returns the updated entry. +func (cache *LRUCache) ComputeIfPresent(key interface{}, callback func(value interface{}) interface{}) (result interface{}) { + cache.mutex.Lock() + defer cache.mutex.Unlock() + + if entry, exists := cache.directory[key]; exists { + result = entry.GetValue().(*lruCacheElement).value + + if result = callback(result); result != nil { + cache.set(key, result) + } else { + if err := cache.doublyLinkedList.removeEntry(entry); err != nil { + panic(err) + } + delete(cache.directory, key) + + cache.size-- + } + } else { + result = nil + } + + return +} + +func (cache *LRUCache) Contains(key interface{}) bool { + cache.mutex.RLock() + if element, exists := cache.directory[key]; exists { + cache.mutex.RUnlock() + cache.mutex.Lock() + defer cache.mutex.Unlock() cache.promoteElement(element) - elementValue = element.GetValue().(*lruCacheElement).value + return true + } else { + cache.mutex.RUnlock() + + return false } +} - if callback != nil { - cache.processingCallback = true - callback(elementValue, exists) - cache.processingCallback = false +func (cache *LRUCache) Get(key interface{}) (result interface{}) { + cache.mutex.RLock() + if element, exists := cache.directory[key]; exists { + cache.mutex.RUnlock() + cache.mutex.Lock() + defer cache.mutex.Unlock() + + cache.promoteElement(element) + + result = element.GetValue().(*lruCacheElement).value + } else { + cache.mutex.RUnlock() } - return exists + return } func (cache *LRUCache) GetCapacity() int { @@ -111,21 +151,13 @@ func (cache *LRUCache) GetSize() int { } func (cache *LRUCache) Delete(key interface{}) bool { - if !cache.processingCallback { - cache.mutex.RLock() - } + cache.mutex.RLock() entry, exists := cache.directory[key] if exists { - if !cache.processingCallback { - cache.mutex.RUnlock() - - cache.mutex.Lock() - defer cache.mutex.Unlock() - - cache.doublyLinkedList.mutex.Lock() - defer cache.doublyLinkedList.mutex.Unlock() - } + cache.mutex.RUnlock() + cache.mutex.Lock() + defer cache.mutex.Unlock() if err := cache.doublyLinkedList.removeEntry(entry); err != nil { panic(err) @@ -137,9 +169,7 @@ func (cache *LRUCache) Delete(key interface{}) bool { return true } - if !cache.processingCallback { - cache.mutex.RUnlock() - } + cache.mutex.RUnlock() return false } diff --git a/packages/datastructure/lru_cache_test.go b/packages/datastructure/lru_cache_test.go index 129dd4ea..ac5dbd74 100644 --- a/packages/datastructure/lru_cache_test.go +++ b/packages/datastructure/lru_cache_test.go @@ -7,17 +7,11 @@ import ( func TestLRUCache(t *testing.T) { cache := NewLRUCache(5) - cache.Contains("test", func(elem interface{}, contains bool) { - if !contains { - cache.Set("test", 12) - } + cache.ComputeIfAbsent("test", func() interface{} { + return 12 }) - if !cache.Contains("test", func(elem interface{}, contains bool) { - if !contains || elem != 12 { - t.Error("the cache contains the wrong element") - } - }) { + if cache.Get("test") != 12 { t.Error("the cache does not contain the added elements") } @@ -44,7 +38,7 @@ func TestLRUCache(t *testing.T) { t.Error("the size should be 5") } - if cache.Contains("test") { + if cache.Get("test") != nil { t.Error("'test' should have been dropped") } @@ -55,28 +49,24 @@ func TestLRUCache(t *testing.T) { t.Error("the size should be 5") } - if !cache.Contains("a") { + if cache.Get("a") == nil { t.Error("'a' should not have been dropped") } - if cache.Contains("b") { + if cache.Get("b") != nil { t.Error("'b' should have been dropped") } - cache.Contains("tust", func(elem interface{}, contains bool) { - if !contains { - cache.Set("tust", 1337) - } + cache.ComputeIfAbsent("tust", func() interface{} { + return 1337 }) if cache.GetSize() != 5 { t.Error("the size should be 5") } - cache.Contains("a", func(value interface{}, exists bool) { - if exists { - cache.Delete("a") - } - }) + if cache.Get("a") != nil { + cache.Delete("a") + } if cache.GetSize() != 4 { t.Error("the size should be 4") } @@ -87,16 +77,21 @@ func TestLRUCache(t *testing.T) { } } -func BenchmarkLRUCache(b *testing.B) { - cache := NewLRUCache(10000) +func TestLRUCache_ComputeIfPresent(t *testing.T) { + cache := NewLRUCache(5) + cache.Set(8, 9) - b.ResetTimer() + cache.ComputeIfPresent(8, func(value interface{}) interface{} { + return 88 + }) + if cache.Get(8) != 88 || cache.GetSize() != 1 { + t.Error("cache was not updated correctly") + } - for i := 0; i < b.N; i++ { - cache.Contains(i, func(val interface{}, exists bool) { - if !exists { - cache.Set(i, i) - } - }) + cache.ComputeIfPresent(8, func(value interface{}) interface{} { + return nil + }) + if cache.Get(8) != nil || cache.GetSize() != 0 { + t.Error("cache was not updated correctly") } } diff --git a/packages/events/event_test.go b/packages/events/event_test.go new file mode 100644 index 00000000..2b0f6b7d --- /dev/null +++ b/packages/events/event_test.go @@ -0,0 +1,38 @@ +package events + +import ( + "fmt" + "strconv" +) + +// define how the event converts the generic parameters to the typed params - ugly but go has no generics :( +func intStringCaller(handler interface{}, params ...interface{}) { + handler.(func(int, string))(params[0].(int), params[1].(string)) +} + +func ExampleHelloWorld() { + // create event object (usually made accessible through a public struct that holds all the different event types) + event := NewEvent(intStringCaller) + + // we have to wrap a function in a closure to make it identifiable + closure1 := NewClosure(func(param1 int, param2 string) { + fmt.Println("#1 " + param2 + ": " + strconv.Itoa(param1)) + }) + + // multiple subscribers can attach to an event (closures can be inlined) + event.Attach(closure1) + event.Attach(NewClosure(func(param1 int, param2 string) { + fmt.Println("#2 " + param2 + ": " + strconv.Itoa(param1)) + })) + + // trigger the event + event.Trigger(1, "Hello World") + + // unsubscribe the first closure and trigger again + event.Detach(closure1) + event.Trigger(1, "Hello World") + + // Unordered output: #1 Hello World: 1 + // #2 Hello World: 1 + // #2 Hello World: 1 +} diff --git a/packages/iac/area.go b/packages/iac/area.go new file mode 100644 index 00000000..49fd6e57 --- /dev/null +++ b/packages/iac/area.go @@ -0,0 +1,38 @@ +package iac + +import ( + "math" + + olc "github.com/google/open-location-code/go" + "github.com/iotaledger/goshimmer/packages/ternary" +) + +type Area struct { + olc.CodeArea + IACCode ternary.Trinary + OLCCode string +} + +func (area *Area) Distance(other *Area) float64 { + lat1, lng1 := area.Center() + lat2, lng2 := other.Center() + + return distance(lat1, lng1, lat2, lng2) +} + +func distance(lat1, lon1, lat2, lon2 float64) float64 { + la1 := lat1 * math.Pi / 180 + lo1 := lon1 * math.Pi / 180 + la2 := lat2 * math.Pi / 180 + lo2 := lon2 * math.Pi / 180 + + return 2 * EARTH_RADIUS_IN_METERS * math.Asin(math.Sqrt(hsin(la2-la1)+math.Cos(la1)*math.Cos(la2)*hsin(lo2-lo1))) +} + +func hsin(theta float64) float64 { + return math.Pow(math.Sin(theta/2), 2) +} + +const ( + EARTH_RADIUS_IN_METERS = 6371000 +) diff --git a/packages/iac/errors.go b/packages/iac/errors.go new file mode 100644 index 00000000..021c0a0b --- /dev/null +++ b/packages/iac/errors.go @@ -0,0 +1,8 @@ +package iac + +import "github.com/iotaledger/goshimmer/packages/errors" + +var ( + ErrConversionFailed = errors.New("conversion between IAC and internal OLC format failed") + ErrDecodeFailed = errors.Wrap(errors.New("decoding error"), "failed to decode the IAC") +) diff --git a/packages/iac/iac.go b/packages/iac/iac.go new file mode 100644 index 00000000..4d8153c4 --- /dev/null +++ b/packages/iac/iac.go @@ -0,0 +1,25 @@ +package iac + +import ( + olc "github.com/google/open-location-code/go" + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/ternary" +) + +func Decode(trinary ternary.Trinary) (result *Area, err errors.IdentifiableError) { + if olcCode, conversionErr := OLCCodeFromTrinary(trinary); err != nil { + err = conversionErr + } else { + if codeArea, olcErr := olc.Decode(olcCode); olcErr == nil { + result = &Area{ + IACCode: trinary, + OLCCode: olcCode, + CodeArea: codeArea, + } + } else { + err = ErrDecodeFailed.Derive(olcErr, "failed to decode the IAC") + } + } + + return +} diff --git a/packages/iac/olc_conversion.go b/packages/iac/olc_conversion.go new file mode 100644 index 00000000..ef0cea48 --- /dev/null +++ b/packages/iac/olc_conversion.go @@ -0,0 +1,56 @@ +package iac + +import ( + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/ternary" +) + +var ( + OLC_ALPHABET = []rune{'2', '3', '4', '5', '6', '7', '8', '9', 'C', 'F', 'G', 'H', 'J', 'M', 'P', 'Q', 'R', 'V', 'W', 'X'} + IAC_ALPHABET = []rune{'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'W', 'Y', 'Z'} + OLC_TO_IAC_MAP = make(map[rune]rune, 22) + IAC_TO_OLC_MAP = make(map[rune]rune, 22) +) + +const ( + OLC_SEPARATOR = '+' + OLC_PADDING = '0' + IAC_SEPARATOR = '9' + IAC_PADDING = 'A' +) + +func init() { + for pos, char := range OLC_ALPHABET { + OLC_TO_IAC_MAP[char] = IAC_ALPHABET[pos] + IAC_TO_OLC_MAP[IAC_ALPHABET[pos]] = char + } + + OLC_TO_IAC_MAP[OLC_SEPARATOR] = IAC_SEPARATOR + OLC_TO_IAC_MAP[OLC_PADDING] = IAC_PADDING + IAC_TO_OLC_MAP[IAC_SEPARATOR] = OLC_SEPARATOR + IAC_TO_OLC_MAP[IAC_PADDING] = OLC_PADDING +} + +func TrinaryFromOLCCode(code string) (result ternary.Trinary, err errors.IdentifiableError) { + for _, char := range code { + if translatedChar, exists := OLC_TO_IAC_MAP[char]; exists { + result += ternary.Trinary(translatedChar) + } else { + err = ErrConversionFailed.Derive("invalid character in input") + } + } + + return +} + +func OLCCodeFromTrinary(trinary ternary.Trinary) (result string, err errors.IdentifiableError) { + for _, char := range trinary { + if translatedChar, exists := IAC_TO_OLC_MAP[char]; exists { + result += string(translatedChar) + } else { + err = ErrConversionFailed.Derive("invalid character in input") + } + } + + return +} diff --git a/packages/ternary/ternary.go b/packages/ternary/ternary.go index a94517fc..3b8fa426 100644 --- a/packages/ternary/ternary.go +++ b/packages/ternary/ternary.go @@ -1,8 +1,8 @@ package ternary import ( - "reflect" - "unsafe" + "reflect" + "unsafe" ) // a Trit can have the values 0, 1 and -1 @@ -16,79 +16,78 @@ 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)) + hdr := (*reflect.StringHeader)(unsafe.Pointer(&trinary)) - return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: hdr.Data, Len: hdr.Len, Cap: hdr.Len})) + return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: hdr.Data, Len: hdr.Len, Cap: hdr.Len})) } func (trinary Trinary) ToTrits() Trits { - trits := make(Trits, len(trinary) * 3) - for _, char := range trinary { - trits = append(trits, TRYTES_TO_TRITS_MAP[char]...) - } + trits := make(Trits, len(trinary)*3) + for _, char := range trinary { + trits = append(trits, TRYTES_TO_TRITS_MAP[char]...) + } - return trits + return trits } - func (this Trits) ToBytes() []byte { - tritsLength := len(this) - bytesLength := (tritsLength + NUMBER_OF_TRITS_IN_A_BYTE - 1) / NUMBER_OF_TRITS_IN_A_BYTE + tritsLength := len(this) + bytesLength := (tritsLength + NUMBER_OF_TRITS_IN_A_BYTE - 1) / NUMBER_OF_TRITS_IN_A_BYTE - bytes := make([]byte, bytesLength) - radix := int8(3) + bytes := make([]byte, bytesLength) + radix := int8(3) - tritIdx := bytesLength * NUMBER_OF_TRITS_IN_A_BYTE - for byteNum := bytesLength - 1; byteNum >= 0; byteNum-- { - var value int8 = 0 + tritIdx := bytesLength * NUMBER_OF_TRITS_IN_A_BYTE + for byteNum := bytesLength - 1; byteNum >= 0; byteNum-- { + var value int8 = 0 - for i := 0; i < NUMBER_OF_TRITS_IN_A_BYTE; i++ { - tritIdx-- + for i := 0; i < NUMBER_OF_TRITS_IN_A_BYTE; i++ { + tritIdx-- - if tritIdx < tritsLength { - value = value * radix + this[tritIdx] - } - } - bytes[byteNum] = byte(value) - } + if tritIdx < tritsLength { + value = value*radix + this[tritIdx] + } + } + bytes[byteNum] = byte(value) + } - return bytes + return bytes } func (this Trits) TrailingZeroes() int { - zeros := 0 - index := len(this) - 1 - for this[index] == 0 { - zeros++ + zeros := 0 + index := len(this) - 1 + for this[index] == 0 { + zeros++ - index-- - } + index-- + } - return zeros + return zeros } func (this Trits) ToInt64() int64 { - var val int64 - for i := len(this) - 1; i >= 0; i-- { - val = val * 3 + int64(this[i]) - } + var val int64 + for i := len(this) - 1; i >= 0; i-- { + val = val*3 + int64(this[i]) + } - return val + return val } func (this Trits) ToUint64() uint64 { - var val uint64 - for i := len(this) - 1; i >= 0; i-- { - val = val * 3 + uint64(this[i]) - } + var val uint64 + for i := len(this) - 1; i >= 0; i-- { + val = val*3 + uint64(this[i]) + } - return val + return val } func (this Trits) ToString() string { - return TritsToString(this, 0, len(this)) + return TritsToString(this, 0, len(this)) } func (this Trits) ToTrinary() Trinary { - return Trinary(TritsToString(this, 0, len(this))) + return Trinary(TritsToString(this, 0, len(this))) } diff --git a/packages/transaction/transaction.go b/packages/transaction/transaction.go index 871f6b29..9d703309 100644 --- a/packages/transaction/transaction.go +++ b/packages/transaction/transaction.go @@ -1,56 +1,56 @@ package transaction import ( - "github.com/iotaledger/goshimmer/packages/curl" - "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/curl" + "github.com/iotaledger/goshimmer/packages/ternary" ) type Transaction struct { - SignatureMessageFragment ternary.Trits - Address ternary.Trits - Value ternary.Trits - Timestamp ternary.Trits - CurrentIndex ternary.Trits - LatestIndex ternary.Trits - BundleHash ternary.Trits - TrunkTransactionHash ternary.Trits - BranchTransactionHash ternary.Trits - Tag ternary.Trits - Nonce ternary.Trits - - Hash ternary.Trits - WeightMagnitude int - Bytes []byte - Trits ternary.Trits + SignatureMessageFragment ternary.Trits + Address ternary.Trits + Value ternary.Trits + Timestamp ternary.Trits + CurrentIndex ternary.Trits + LatestIndex ternary.Trits + BundleHash ternary.Trits + TrunkTransactionHash ternary.Trits + BranchTransactionHash ternary.Trits + Tag ternary.Trits + Nonce ternary.Trits + + Hash ternary.Trits + WeightMagnitude int + Bytes []byte + Trits ternary.Trits } func FromTrits(trits ternary.Trits, optionalHash ...ternary.Trits) *Transaction { - hash := <- curl.CURLP81.Hash(trits) - - transaction := &Transaction{ - SignatureMessageFragment: trits[SIGNATURE_MESSAGE_FRAGMENT_OFFSET:SIGNATURE_MESSAGE_FRAGMENT_END], - Address: trits[ADDRESS_OFFSET:ADDRESS_END], - 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], - Nonce: trits[NONCE_OFFSET:NONCE_END], - - Hash: hash, - WeightMagnitude: hash.TrailingZeroes(), - Trits: trits, - } - - return transaction + hash := <-curl.CURLP81.Hash(trits) + + transaction := &Transaction{ + SignatureMessageFragment: trits[SIGNATURE_MESSAGE_FRAGMENT_OFFSET:SIGNATURE_MESSAGE_FRAGMENT_END], + Address: trits[ADDRESS_OFFSET:ADDRESS_END], + 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], + Nonce: trits[NONCE_OFFSET:NONCE_END], + + Hash: hash, + WeightMagnitude: hash.TrailingZeroes(), + Trits: trits, + } + + return transaction } func FromBytes(bytes []byte) *Transaction { - transaction := FromTrits(ternary.BytesToTrits(bytes)[:MARSHALLED_TOTAL_SIZE]) - transaction.Bytes = bytes + transaction := FromTrits(ternary.BytesToTrits(bytes)[:MARSHALLED_TOTAL_SIZE]) + transaction.Bytes = bytes - return transaction -} \ No newline at end of file + return transaction +} diff --git a/plugins/tangle/api.go b/plugins/tangle/api.go index c184762e..2a69fcc5 100644 --- a/plugins/tangle/api.go +++ b/plugins/tangle/api.go @@ -1,46 +1,82 @@ package tangle import ( - "github.com/iotaledger/goshimmer/packages/errors" - "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/datastructure" + "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 - } +var transactionCache = datastructure.NewLRUCache(TRANSACTION_CACHE_SIZE) - return getTransactionFromDatabase(transactionHash) +func GetTransaction(transactionHash ternary.Trinary, computeIfAbsent ...func(ternary.Trinary) *Transaction) (result *Transaction, 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.(*Transaction) + } + + return } -func ContainsTransaction(transactionHash ternary.Trinary) (bool, errors.IdentifiableError) { - if memPoolContainsTransaction(transactionHash) { - return true, nil - } +func ContainsTransaction(transactionHash ternary.Trinary) (result bool, err errors.IdentifiableError) { + if transactionCache.Get(transactionHash) != nil { + result = true + } else { + result, err = databaseContainsTransaction(transactionHash) + } - return databaseContainsTransaction(transactionHash) + return } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// // region transactionmetadata api ////////////////////////////////////////////////////////////////////////////////////// -func GetTransactionMetadata(transactionHash ternary.Trinary) (*TransactionMetadata, errors.IdentifiableError) { - if transaction := getTransactionFromMemPool(transactionHash); transaction != nil { - return transaction.GetMetaData() - } +var metadataCache = datastructure.NewLRUCache(METADATA_CACHE_SIZE) + +func GetTransactionMetadata(transactionHash ternary.Trinary) (result *TransactionMetadata, err errors.IdentifiableError) { + result = metadataCache.ComputeIfAbsent(transactionHash, func() interface{} { + if metadata, dbErr := getTransactionMetadataFromDatabase(transactionHash); dbErr != nil { + err = dbErr + + return nil + } else if metadata != nil { + return metadata + } - return getTransactionMetadataFromDatabase(transactionHash) + return nil + }).(*TransactionMetadata) + + return } -func ContainsTransactionMetadata(transactionHash ternary.Trinary) (bool, errors.IdentifiableError) { - if memPoolContainsTransaction(transactionHash) { - return true, nil - } +func ContainsTransactionMetadata(transactionHash ternary.Trinary) (result bool, err errors.IdentifiableError) { + if metadataCache.Get(transactionHash) != nil { + result = true + } else { + result, err = databaseContainsTransactionMetadata(transactionHash) + } - return databaseContainsTransactionMetadata(transactionHash) + return } -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// \ No newline at end of file +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const ( + TRANSACTION_CACHE_SIZE = 1000 + METADATA_CACHE_SIZE = 1000 +) diff --git a/plugins/tangle/approvers.go b/plugins/tangle/approvers.go index 3606330f..b7480dce 100644 --- a/plugins/tangle/approvers.go +++ b/plugins/tangle/approvers.go @@ -1,10 +1,53 @@ package tangle import ( - "github.com/iotaledger/goshimmer/packages/ternary" + "encoding/binary" + "strconv" "sync" + + "github.com/dgraph-io/badger" + "github.com/iotaledger/goshimmer/packages/datastructure" + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/typeconversion" ) +// region global public api //////////////////////////////////////////////////////////////////////////////////////////// + +var approversCache = datastructure.NewLRUCache(METADATA_CACHE_SIZE) + +func StoreApprovers(approvers *Approvers) { + hash := approvers.GetHash() + + approversCache.Set(hash, approvers) +} + +func GetApprovers(transactionHash ternary.Trinary, computeIfAbsent ...func(ternary.Trinary) *Approvers) (result *Approvers, err errors.IdentifiableError) { + if approvers := approversCache.ComputeIfAbsent(transactionHash, func() (result interface{}) { + if result, err = getApproversFromDatabase(transactionHash); err == nil && (result == nil || result.(*Approvers) == nil) && len(computeIfAbsent) >= 1 { + result = computeIfAbsent[0](transactionHash) + } + + return + }); approvers != nil && approvers.(*Approvers) != nil { + result = approvers.(*Approvers) + } + + return +} + +func ContainsApprovers(transactionHash ternary.Trinary) (result bool, err errors.IdentifiableError) { + if approversCache.Contains(transactionHash) { + result = true + } else { + result, err = databaseContainsApprovers(transactionHash) + } + + return +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + type Approvers struct { hash ternary.Trinary hashes map[ternary.Trinary]bool @@ -24,27 +67,100 @@ func NewApprovers(hash ternary.Trinary) *Approvers { func (approvers *Approvers) Add(transactionHash ternary.Trinary) { approvers.hashesMutex.Lock() - defer approvers.hashesMutex.Unlock() - approvers.add(transactionHash) + approvers.hashesMutex.Unlock() } func (approvers *Approvers) Remove(approverHash ternary.Trinary) { approvers.hashesMutex.Lock() - defer approvers.hashesMutex.Unlock() - approvers.remove(approverHash) + approvers.hashesMutex.Unlock() } -func (approvers *Approvers) GetHashes() []ternary.Trinary { +func (approvers *Approvers) GetHashes() (result []ternary.Trinary) { approvers.hashesMutex.RLock() - defer approvers.hashesMutex.RUnlock() + result = approvers.getHashes() + approvers.hashesMutex.RUnlock() + + return +} + +func (approvers *Approvers) GetHash() (result ternary.Trinary) { + approvers.hashesMutex.RLock() + result = approvers.hash + approvers.hashesMutex.RUnlock() - return approvers.getHashes() + return +} + +func (approvers *Approvers) Marshal() (result []byte) { + result = make([]byte, MARSHALLED_APPROVERS_MIN_SIZE+len(approvers.hashes)*MARSHALLED_APPROVERS_HASH_SIZE) + + approvers.hashesMutex.RLock() + + binary.BigEndian.PutUint64(result[MARSHALLED_APPROVERS_HASHES_COUNT_START:MARSHALLED_APPROVERS_HASHES_COUNT_END], uint64(len(approvers.hashes))) + + copy(result[MARSHALLED_APPROVERS_HASH_START:MARSHALLED_APPROVERS_HASH_END], approvers.hash.CastToBytes()) + + i := 0 + for hash, _ := range approvers.hashes { + var HASH_START = MARSHALLED_APPROVERS_HASHES_START + i*(MARSHALLED_APPROVERS_HASH_SIZE) + var HASH_END = HASH_START * MARSHALLED_APPROVERS_HASH_SIZE + + copy(result[HASH_START:HASH_END], hash.CastToBytes()) + + i++ + } + + approvers.hashesMutex.RUnlock() + + return +} + +func (approvers *Approvers) Unmarshal(data []byte) (err errors.IdentifiableError) { + dataLen := len(data) + + if dataLen <= MARSHALLED_APPROVERS_MIN_SIZE { + return ErrMarshallFailed.Derive(errors.New("unmarshall failed"), "marshalled approvers are too short") + } + + hashesCount := binary.BigEndian.Uint64(data[MARSHALLED_APPROVERS_HASHES_COUNT_START:MARSHALLED_APPROVERS_HASHES_COUNT_END]) + + if dataLen <= MARSHALLED_APPROVERS_MIN_SIZE+int(hashesCount)*MARSHALLED_APPROVERS_HASH_SIZE { + return ErrMarshallFailed.Derive(errors.New("unmarshall failed"), "marshalled approvers are too short for "+strconv.FormatUint(hashesCount, 10)+" approvers") + } + + approvers.hashesMutex.Lock() + + approvers.hash = ternary.Trinary(typeconversion.BytesToString(data[MARSHALLED_APPROVERS_HASH_START:MARSHALLED_APPROVERS_HASH_END])) + approvers.hashes = make(map[ternary.Trinary]bool, hashesCount) + for i := uint64(0); i < hashesCount; i++ { + var HASH_START = MARSHALLED_APPROVERS_HASHES_START + i*(MARSHALLED_APPROVERS_HASH_SIZE) + var HASH_END = HASH_START * MARSHALLED_APPROVERS_HASH_SIZE + + approvers.hashes[ternary.Trinary(typeconversion.BytesToString(data[HASH_START:HASH_END]))] = true + } + + approvers.hashesMutex.Unlock() + + return } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// +const ( + MARSHALLED_APPROVERS_HASHES_COUNT_START = 0 + MARSHALLED_APPROVERS_HASH_START = MARSHALLED_APPROVERS_HASHES_COUNT_END + MARSHALLED_APPROVERS_HASHES_START = MARSHALLED_APPROVERS_HASH_END + + MARSHALLED_APPROVERS_HASHES_COUNT_END = MARSHALLED_APPROVERS_HASHES_COUNT_START + MARSHALLED_APPROVERS_HASHES_COUNT_SIZE + MARSHALLED_APPROVERS_HASH_END = MARSHALLED_APPROVERS_HASH_START + MARSHALLED_APPROVERS_HASH_SIZE + + MARSHALLED_APPROVERS_HASHES_COUNT_SIZE = 8 + MARSHALLED_APPROVERS_HASH_SIZE = 81 + MARSHALLED_APPROVERS_MIN_SIZE = MARSHALLED_APPROVERS_HASHES_COUNT_SIZE + MARSHALLED_APPROVERS_HASH_SIZE +) + // region private methods without locking ////////////////////////////////////////////////////////////////////////////// func (approvers *Approvers) add(transactionHash ternary.Trinary) { @@ -61,31 +177,51 @@ func (approvers *Approvers) remove(approverHash ternary.Trinary) { } } -func (approvers *Approvers) getHashes() []ternary.Trinary { - hashes := make([]ternary.Trinary, len(approvers.hashes)) +func (approvers *Approvers) getHashes() (result []ternary.Trinary) { + result = make([]ternary.Trinary, len(approvers.hashes)) counter := 0 for hash, _ := range approvers.hashes { - hashes[counter] = hash + result[counter] = hash counter++ } - return hashes + return } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// func (approvers *Approvers) Store(approverHash ternary.Trinary) { approvers.hashesMutex.Lock() - approvers.hashesMutex.RUnlock() - approvers.modified = false + approvers.hashesMutex.Unlock() } -func (approvers *Approvers) Marshal() []byte { - approvers.hashesMutex.RLock() - defer approvers.hashesMutex.RUnlock() +func getApproversFromDatabase(transactionHash ternary.Trinary) (result *Approvers, err errors.IdentifiableError) { + approversData, dbErr := approversDatabase.Get(transactionHash.CastToBytes()) + if dbErr != nil { + if dbErr == badger.ErrKeyNotFound { + err = nil + } else { + err = ErrDatabaseError.Derive(err, "failed to retrieve transaction") + } + + return + } + + result = NewApprovers(transactionHash) + if err = result.Unmarshal(approversData); err != nil { + result = nil + } + + return +} - return make([]byte, 0) +func databaseContainsApprovers(transactionHash ternary.Trinary) (bool, errors.IdentifiableError) { + if result, err := approversDatabase.Contains(transactionHash.CastToBytes()); err != nil { + return false, ErrDatabaseError.Derive(err, "failed to check if the transaction exists") + } else { + return result, nil + } } diff --git a/plugins/tangle/approvers_test.go b/plugins/tangle/approvers_test.go new file mode 100644 index 00000000..fffd889a --- /dev/null +++ b/plugins/tangle/approvers_test.go @@ -0,0 +1,73 @@ +package tangle + +import ( + "fmt" + "testing" + "time" + + "github.com/iotaledger/goshimmer/packages/events" + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/transaction" + "github.com/iotaledger/goshimmer/plugins/gossip" +) + +func TestGetApprovers(t *testing.T) { + configureDatabase(nil) + + approvers, err := GetApprovers(ternary.Trinary("AA"), NewApprovers) + + approvers.Add(ternary.Trinary("FF")) + + fmt.Println(approvers) + fmt.Println(err) + + approvers1, err1 := GetApprovers(ternary.Trinary("AA")) + + fmt.Println(approvers1) + fmt.Println(err1) +} + +func TestSolidifier(t *testing.T) { + configureDatabase(nil) + configureSolidifier(nil) + + txData := make([]byte, 1572) + txData[1571] = 1 + + tx := transaction.FromBytes(txData) + + txData[1571] = 2 + tx1 := transaction.FromBytes(txData) + tx1.BranchTransactionHash = tx.Hash + + txData[1571] = 3 + tx2 := transaction.FromBytes(txData) + tx2.BranchTransactionHash = tx1.Hash + + txData[1571] = 4 + tx3 := transaction.FromBytes(txData) + tx3.BranchTransactionHash = tx2.Hash + + fmt.Println(tx.Hash.ToString()) + fmt.Println(tx1.Hash.ToString()) + fmt.Println(tx2.Hash.ToString()) + fmt.Println(tx3.Hash.ToString()) + + fmt.Println("============") + + Events.TransactionSolid.Attach(events.NewClosure(func(transaction *Transaction) { + fmt.Println("SOLID: " + transaction.GetHash()) + })) + + gossip.Events.ReceiveTransaction.Trigger(tx) + gossip.Events.ReceiveTransaction.Trigger(tx1) + gossip.Events.ReceiveTransaction.Trigger(tx3) + + fmt.Println("...") + + time.Sleep(1 * time.Second) + + gossip.Events.ReceiveTransaction.Trigger(tx2) + + time.Sleep(1 * time.Second) +} diff --git a/plugins/tangle/database.go b/plugins/tangle/database.go index 88fca651..fbfdc548 100644 --- a/plugins/tangle/database.go +++ b/plugins/tangle/database.go @@ -1,29 +1,36 @@ 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" + "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 - } + 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 + } + + if db, err := database.Get("approvers"); err != nil { + panic(err) + } else { + approversDatabase = db + } } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -31,51 +38,51 @@ func configureDatabase(plugin *node.Plugin) { // 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 + 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 - } + 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 + 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 - } + 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 /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -86,4 +93,6 @@ var transactionDatabase database.Database var transactionMetadataDatabase database.Database +var approversDatabase database.Database + // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/tangle/mempool.go b/plugins/tangle/mempool.go deleted file mode 100644 index 7f80d5bc..00000000 --- a/plugins/tangle/mempool.go +++ /dev/null @@ -1,130 +0,0 @@ -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 e41f7632..113969bd 100644 --- a/plugins/tangle/plugin.go +++ b/plugins/tangle/plugin.go @@ -1,7 +1,7 @@ package tangle import ( - "github.com/iotaledger/goshimmer/packages/node" + "github.com/iotaledger/goshimmer/packages/node" ) // region plugin module setup ////////////////////////////////////////////////////////////////////////////////////////// @@ -9,12 +9,12 @@ import ( var PLUGIN = node.NewPlugin("Tangle", configure, run) func configure(plugin *node.Plugin) { - configureDatabase(plugin) - configureMemPool(plugin) + configureDatabase(plugin) + configureSolidifier(plugin) } func run(plugin *node.Plugin) { - runMemPool(plugin) + // this plugin has no background workers } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/tangle/solidifier.go b/plugins/tangle/solidifier.go index 4fa57cc2..8f7033cb 100644 --- a/plugins/tangle/solidifier.go +++ b/plugins/tangle/solidifier.go @@ -1,57 +1,156 @@ package tangle import ( - "github.com/iotaledger/goshimmer/packages/errors" - "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/errors" + "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" ) -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 +// region plugin module setup ////////////////////////////////////////////////////////////////////////////////////////// + +func configureSolidifier(plugin *node.Plugin) { + gossip.Events.ReceiveTransaction.Attach(events.NewClosure(func(rawTransaction *transaction.Transaction) { + go processRawTransaction(plugin, rawTransaction) + })) +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Checks and updates the solid flag of a single transaction. +func checkSolidity(transaction *Transaction) (result bool, err errors.IdentifiableError) { + // abort if transaction is solid already + txMetadata, metaDataErr := transaction.GetMetaData() + if err != nil { + err = metaDataErr + + return + } else if txMetadata.GetSolid() { + result = true + + return + } + + // check solidity of branch transaction if it is not genesis + if branchTransactionHash := transaction.GetBranchTransactionHash(); branchTransactionHash != TRANSACTION_NULL_HASH { + // abort if branch transaction is missing + if branchTransaction, branchErr := GetTransaction(branchTransactionHash); err != nil { + err = branchErr + + return + } else if branchTransaction == nil { + return + } else if branchTransactionMetadata, branchErr := branchTransaction.GetMetaData(); branchErr != nil { + err = branchErr + + return + } else if !branchTransactionMetadata.GetSolid() { + return + } + } + + // check solidity of branch transaction if it is not genesis + if trunkTransactionHash := transaction.GetBranchTransactionHash(); trunkTransactionHash != TRANSACTION_NULL_HASH { + if trunkTransaction, trunkErr := GetTransaction(trunkTransactionHash); trunkErr != nil { + err = trunkErr + + return + } else if trunkTransaction == nil { + return + } else if trunkTransactionMetadata, trunkErr := trunkTransaction.GetMetaData(); trunkErr != nil { + err = trunkErr + + return + } else if !trunkTransactionMetadata.GetSolid() { + return + } + } + + // mark transaction as solid and trigger event + if txMetadata.SetSolid(true) { + Events.TransactionSolid.Trigger(transaction) + } + + result = true + + return +} + +// Checks and updates the solid flag of a transaction and its approvers (future cone). +func IsSolid(transaction *Transaction) (bool, errors.IdentifiableError) { + if isSolid, err := checkSolidity(transaction); err != nil { + return false, err + } else if isSolid { + if err := propagateSolidity(transaction.GetHash()); err != nil { + return false, err + } + } + + return false, nil +} + +func propagateSolidity(transactionHash ternary.Trinary) errors.IdentifiableError { + if approvers, err := GetApprovers(transactionHash, NewApprovers); err != nil { + return err + } else { + for _, approverHash := range approvers.GetHashes() { + if approver, err := GetTransaction(approverHash); err != nil { + return err + } else if approver != nil { + if isSolid, err := checkSolidity(approver); err != nil { + return err + } else if isSolid { + if err := propagateSolidity(approver.GetHash()); err != nil { + return err + } + } + } + } + } + + return nil +} + +func processRawTransaction(plugin *node.Plugin, rawTransaction *transaction.Transaction) { + var newTransaction bool + if tx, err := GetTransaction(rawTransaction.Hash.ToTrinary(), func(transactionHash ternary.Trinary) *Transaction { + newTransaction = true + + return &Transaction{rawTransaction: rawTransaction} + }); err != nil { + plugin.LogFailure(err.Error()) + } else if newTransaction { + go processTransaction(plugin, tx) + } +} + +func processTransaction(plugin *node.Plugin, transaction *Transaction) { + transactionHash := transaction.GetHash() + + // register tx as approver for trunk + if trunkApprovers, err := GetApprovers(transaction.GetTrunkTransactionHash(), NewApprovers); err != nil { + plugin.LogFailure(err.Error()) + + return + } else { + trunkApprovers.Add(transactionHash) + } + + // register tx as approver for branch + if branchApprovers, err := GetApprovers(transaction.GetBranchTransactionHash(), NewApprovers); err != nil { + plugin.LogFailure(err.Error()) + + return + } else { + branchApprovers.Add(transactionHash) + } + + // update the solidity flags of this transaction and its approvers + if _, err := IsSolid(transaction); err != nil { + plugin.LogFailure(err.Error()) + + return + } +} diff --git a/plugins/tangle/transaction.go b/plugins/tangle/transaction.go index 034eb0d1..08f9c351 100644 --- a/plugins/tangle/transaction.go +++ b/plugins/tangle/transaction.go @@ -1,49 +1,54 @@ package tangle import ( - "github.com/iotaledger/goshimmer/packages/errors" - "github.com/iotaledger/goshimmer/packages/ternary" - "github.com/iotaledger/goshimmer/packages/transaction" - "sync" + "sync" + + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/ternary" + "github.com/iotaledger/goshimmer/packages/transaction" ) // 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 + // 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 +} + +func NewTransaction(rawTransaction *transaction.Transaction) *Transaction { + return &Transaction{rawTransaction: rawTransaction} } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -51,582 +56,588 @@ type Transaction struct { // 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 - } + 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 - } - } + return transaction.rawMetaData, nil + } + } - defer transaction.rawMetaDataMutex.RUnlock() + defer transaction.rawMetaDataMutex.RUnlock() - return transaction.rawMetaData, nil + 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() - } - } + 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() + defer transaction.hashMutex.RUnlock() - return *transaction.hash + 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.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() - } + 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() + transaction.hashMutex.Lock() + defer transaction.hashMutex.Unlock() - return transaction.parseHash() + 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() +func (transaction *Transaction) parseHash() (result ternary.Trinary) { + result = transaction.rawTransaction.Hash.ToTrinary() + + transaction.hash = &result - return *transaction.hash + return } // 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() - } - } + 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() + defer transaction.addressMutex.RUnlock() - return *transaction.address + 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.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() - } + 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() + transaction.addressMutex.Lock() + defer transaction.addressMutex.Unlock() - return transaction.parseAddress() + 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() + *transaction.address = transaction.rawTransaction.Hash.ToTrinary() - return *transaction.address + 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() - } - } + 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() + defer transaction.valueMutex.RUnlock() - return *transaction.value + 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.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() - } + 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() + transaction.valueMutex.Lock() + defer transaction.valueMutex.Unlock() - return transaction.parseValue() + 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() + *transaction.value = transaction.rawTransaction.Value.ToInt64() - return *transaction.value + 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() - } - } + 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() + defer transaction.timestampMutex.RUnlock() - return *transaction.timestamp + 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.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() - } + 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() + transaction.timestampMutex.Lock() + defer transaction.timestampMutex.Unlock() - return transaction.parseTimestamp() + 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() + *transaction.timestamp = transaction.rawTransaction.Timestamp.ToUint64() - return *transaction.timestamp + 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() - } - } + 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() + defer transaction.currentIndexMutex.RUnlock() - return *transaction.currentIndex + 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.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() - } + 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() + transaction.currentIndexMutex.Lock() + defer transaction.currentIndexMutex.Unlock() - return transaction.parseCurrentIndex() + 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() + *transaction.currentIndex = transaction.rawTransaction.CurrentIndex.ToUint64() - return *transaction.currentIndex + 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() - } - } + 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() + defer transaction.latestIndexMutex.RUnlock() - return *transaction.latestIndex + 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.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() - } + 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() + transaction.latestIndexMutex.Lock() + defer transaction.latestIndexMutex.Unlock() - return transaction.parseLatestIndex() + 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() + *transaction.latestIndex = transaction.rawTransaction.LatestIndex.ToUint64() - return *transaction.latestIndex + 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() - } - } + 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() + defer transaction.bundleHashMutex.RUnlock() - return *transaction.bundleHash + 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.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() - } + 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() + transaction.bundleHashMutex.Lock() + defer transaction.bundleHashMutex.Unlock() - return transaction.parseBundleHash() + 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() + *transaction.bundleHash = transaction.rawTransaction.BundleHash.ToTrinary() - return *transaction.bundleHash + 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() - } - } + 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() + defer transaction.trunkTransactionHashMutex.RUnlock() - return *transaction.trunkTransactionHash + 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.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() - } + 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() + transaction.trunkTransactionHashMutex.Lock() + defer transaction.trunkTransactionHashMutex.Unlock() - return transaction.parseTrunkTransactionHash() + 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() +func (transaction *Transaction) parseTrunkTransactionHash() (result ternary.Trinary) { + result = transaction.rawTransaction.TrunkTransactionHash.ToTrinary() + + transaction.trunkTransactionHash = &result - return *transaction.trunkTransactionHash + return } // 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() - } - } + 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() + defer transaction.branchTransactionHashMutex.RUnlock() - return *transaction.branchTransactionHash + 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.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() - } + 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() + transaction.branchTransactionHashMutex.Lock() + defer transaction.branchTransactionHashMutex.Unlock() - return transaction.parseBranchTransactionHash() + 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() +func (transaction *Transaction) parseBranchTransactionHash() (result ternary.Trinary) { + result = transaction.rawTransaction.BranchTransactionHash.ToTrinary() - return *transaction.branchTransactionHash + transaction.branchTransactionHash = &result + + return } // 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() - } - } + 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() + defer transaction.tagMutex.RUnlock() - return *transaction.tag + 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.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() - } + 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() + transaction.tagMutex.Lock() + defer transaction.tagMutex.Unlock() - return transaction.parseTag() + 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() + *transaction.tag = transaction.rawTransaction.Tag.ToTrinary() - return *transaction.tag + 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() - } - } + 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() + defer transaction.nonceMutex.RUnlock() - return *transaction.nonce + 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.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() - } + 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() + transaction.nonceMutex.Lock() + defer transaction.nonceMutex.Unlock() - return transaction.parseNonce() + 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() + *transaction.nonce = transaction.rawTransaction.Nonce.ToTrinary() - return *transaction.nonce + 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() + transaction.modifiedMutex.RLock() + defer transaction.modifiedMutex.RUnlock() - return transaction.modified + 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.modifiedMutex.Lock() + defer transaction.modifiedMutex.Unlock() - transaction.modified = modified + transaction.modified = modified } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -634,11 +645,15 @@ func (transaction *Transaction) SetModified(modified bool) { // 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") - } + if err := transactionDatabase.Set(transaction.rawTransaction.Hash.ToBytes(), transaction.rawTransaction.Bytes); err != nil { + return ErrDatabaseError.Derive(err, "failed to store the transaction") + } - return nil + return nil } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const ( + TRANSACTION_NULL_HASH = ternary.Trinary("999999999999999999999999999999999999999999999999999999999999999999999999999999999") +) diff --git a/plugins/tangle/transaction_metadata.go b/plugins/tangle/transaction_metadata.go index ce0e7f08..97869f88 100644 --- a/plugins/tangle/transaction_metadata.go +++ b/plugins/tangle/transaction_metadata.go @@ -1,40 +1,41 @@ 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" + "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/typeconversion" ) // 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 + 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, - } + return &TransactionMetadata{ + hash: hash, + receivedTime: time.Now(), + solid: false, + liked: false, + finalized: false, + modified: true, + } } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -42,134 +43,138 @@ func NewTransactionMetadata(hash ternary.Trinary) *TransactionMetadata { // region getters and setters ////////////////////////////////////////////////////////////////////////////////////////// func (metaData *TransactionMetadata) GetHash() ternary.Trinary { - metaData.hashMutex.RLock() - defer metaData.hashMutex.RUnlock() + metaData.hashMutex.RLock() + defer metaData.hashMutex.RUnlock() - return metaData.hash + 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() - } + 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() + metaData.receivedTimeMutex.RLock() + defer metaData.receivedTimeMutex.RUnlock() - return metaData.receivedTime + 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() - } + 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() + metaData.solidMutex.RLock() + defer metaData.solidMutex.RUnlock() - return metaData.solid + 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) 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() + metaData.likedMutex.RLock() + defer metaData.likedMutex.RUnlock() - return metaData.liked + 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() - } + 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() + metaData.finalizedMutex.RLock() + defer metaData.finalizedMutex.RUnlock() - return metaData.finalized + 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() - } + 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() + metadata.modifiedMutex.RLock() + defer metadata.modifiedMutex.RUnlock() - return metadata.modified + 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.modifiedMutex.Lock() + defer metadata.modifiedMutex.Unlock() - metadata.modified = modified + metadata.modified = modified } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -177,70 +182,70 @@ func (metadata *TransactionMetadata) SetModified(modified bool) { // 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 + 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 + 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 /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -248,20 +253,20 @@ func (metadata *TransactionMetadata) Unmarshal(data []byte) errors.IdentifiableE // region database functions /////////////////////////////////////////////////////////////////////////////////////////// func (metadata *TransactionMetadata) Store() errors.IdentifiableError { - if metadata.GetModified() { - marshalledMetadata, err := metadata.Marshal() - if err != nil { - return err - } + 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") - } + if err := transactionMetadataDatabase.Set(metadata.GetHash().CastToBytes(), marshalledMetadata); err != nil { + return ErrDatabaseError.Derive(err, "failed to store the transaction") + } - metadata.SetModified(false) - } + metadata.SetModified(false) + } - return nil + return nil } // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -269,19 +274,19 @@ func (metadata *TransactionMetadata) Store() errors.IdentifiableError { // 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_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_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_HASH_SIZE = 81 + MARSHALLED_RECEIVED_TIME_SIZE = 15 + MARSHALLED_FLAGS_SIZE = 1 - MARSHALLED_TOTAL_SIZE = MARSHALLED_FLAGS_END + MARSHALLED_TOTAL_SIZE = MARSHALLED_FLAGS_END ) // endregion //////////////////////////////////////////////////////////////////////////////////////////////////////////// -- GitLab