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

Feat: several features added

- lru cache
- soldifier for tangle
- ...
parent 3235104b
No related branches found
No related tags found
No related merge requests found
Showing
with 1463 additions and 1031 deletions
......@@ -14,44 +14,42 @@ type LRUCache struct {
doublyLinkedList *DoublyLinkedList
capacity int
size int
processingCallback bool
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.set(key, value)
}
element, exists := cache.directory[key]
if exists {
if !cache.processingCallback {
element.GetValue().(*lruCacheElement).value = value
func (cache *LRUCache) set(key interface{}, value interface{}) {
directory := cache.directory
cache.doublyLinkedList.mutex.Lock()
defer cache.doublyLinkedList.mutex.Unlock()
} else {
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 element, exists := cache.directory[key]; exists {
cache.promoteElement(element)
result = element.GetValue().(*lruCacheElement).value
} else {
if result = callback(); result != nil {
cache.set(key, result)
}
}
return
}
if len(optionalCallback) >= 1 {
if !cache.processingCallback {
// 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)
callback = optionalCallback[0]
cache.size--
}
} else {
if !cache.processingCallback {
cache.mutex.RLock()
defer cache.mutex.RUnlock()
result = nil
}
return
}
var elementValue interface{}
element, exists := cache.directory[key]
if exists {
cache.doublyLinkedList.mutex.Lock()
defer cache.doublyLinkedList.mutex.Unlock()
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,22 +151,14 @@ func (cache *LRUCache) GetSize() int {
}
func (cache *LRUCache) Delete(key interface{}) bool {
if !cache.processingCallback {
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()
}
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()
}
return false
}
......
......@@ -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 {
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)
b.ResetTimer()
func TestLRUCache_ComputeIfPresent(t *testing.T) {
cache := NewLRUCache(5)
cache.Set(8, 9)
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 88
})
if cache.Get(8) != 88 || cache.GetSize() != 1 {
t.Error("cache was not updated correctly")
}
cache.ComputeIfPresent(8, func(value interface{}) interface{} {
return nil
})
if cache.Get(8) != nil || cache.GetSize() != 0 {
t.Error("cache was not updated correctly")
}
}
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
}
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
)
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")
)
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
}
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
}
......@@ -30,7 +30,6 @@ func (trinary Trinary) ToTrits() 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
......
package tangle
import (
"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)
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 getTransactionFromDatabase(transactionHash)
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 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
const (
TRANSACTION_CACHE_SIZE = 1000
METADATA_CACHE_SIZE = 1000
)
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() (result []ternary.Trinary) {
approvers.hashesMutex.RLock()
result = approvers.getHashes()
approvers.hashesMutex.RUnlock()
return
}
func (approvers *Approvers) GetHashes() []ternary.Trinary {
func (approvers *Approvers) GetHash() (result ternary.Trinary) {
approvers.hashesMutex.RLock()
defer approvers.hashesMutex.RUnlock()
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
}
}
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)
}
......@@ -2,6 +2,7 @@ package tangle
import (
"fmt"
"github.com/dgraph-io/badger"
"github.com/iotaledger/goshimmer/packages/database"
"github.com/iotaledger/goshimmer/packages/errors"
......@@ -24,6 +25,12 @@ func configureDatabase(plugin *node.Plugin) {
} else {
transactionMetadataDatabase = db
}
if db, err := database.Get("approvers"); err != nil {
panic(err)
} else {
approversDatabase = db
}
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
......@@ -86,4 +93,6 @@ var transactionDatabase database.Database
var transactionMetadataDatabase database.Database
var approversDatabase database.Database
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
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 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
......@@ -10,11 +10,11 @@ var PLUGIN = node.NewPlugin("Tangle", configure, run)
func configure(plugin *node.Plugin) {
configureDatabase(plugin)
configureMemPool(plugin)
configureSolidifier(plugin)
}
func run(plugin *node.Plugin) {
runMemPool(plugin)
// this plugin has no background workers
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
......@@ -2,56 +2,155 @@ package tangle
import (
"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) {
// 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, err := transaction.GetMetaData()
txMetadata, metaDataErr := transaction.GetMetaData()
if err != nil {
return false, err
err = metaDataErr
return
} else if txMetadata.GetSolid() {
return true, nil
result = true
return
}
// check solidity of branch transaction if it is not genesis
if branchTransactionHash := transaction.GetBranchTransactionHash(); branchTransactionHash != ternary.Trinary("999999") {
if branchTransactionHash := transaction.GetBranchTransactionHash(); branchTransactionHash != TRANSACTION_NULL_HASH {
// abort if branch transaction is missing
branchTransaction, err := GetTransaction(branchTransactionHash)
if err != nil {
return false, err
if branchTransaction, branchErr := GetTransaction(branchTransactionHash); err != nil {
err = branchErr
return
} else if branchTransaction == nil {
return false, nil
}
return
} else if branchTransactionMetadata, branchErr := branchTransaction.GetMetaData(); branchErr != nil {
err = branchErr
// abort if branch transaction is not solid
if branchTransactionMetadata, err := branchTransaction.GetMetaData(); err != nil {
return false, err
return
} else if !branchTransactionMetadata.GetSolid() {
return false, nil
return
}
}
// 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
if trunkTransactionHash := transaction.GetBranchTransactionHash(); trunkTransactionHash != TRANSACTION_NULL_HASH {
if trunkTransaction, trunkErr := GetTransaction(trunkTransactionHash); trunkErr != nil {
err = trunkErr
return
} else if trunkTransaction == nil {
return false, 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
}
// abort if trunk transaction is not solid
if trunkTransactionMetadata, err := trunkTransaction.GetMetaData(); err != nil {
// 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 !trunkTransactionMetadata.GetSolid() {
} 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
}
// propagate solidity to all approvers
txMetadata.SetSolid(true)
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 true, nil
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
}
}
package tangle
import (
"sync"
"github.com/iotaledger/goshimmer/packages/errors"
"github.com/iotaledger/goshimmer/packages/ternary"
"github.com/iotaledger/goshimmer/packages/transaction"
"sync"
)
// region type definition and constructor //////////////////////////////////////////////////////////////////////////////
......@@ -46,6 +47,10 @@ type Transaction struct {
modifiedMutex sync.RWMutex
}
func NewTransaction(rawTransaction *transaction.Transaction) *Transaction {
return &Transaction{rawTransaction: rawTransaction}
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region getters and setters //////////////////////////////////////////////////////////////////////////////////////////
......@@ -117,10 +122,12 @@ func (transaction *Transaction) ParseHash() ternary.Trinary {
}
// 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()
return *transaction.hash
transaction.hash = &result
return
}
// getter for the address (supports concurrency)
......@@ -460,10 +467,12 @@ func (transaction *Transaction) ParseTrunkTransactionHash() ternary.Trinary {
}
// 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()
return *transaction.trunkTransactionHash
transaction.trunkTransactionHash = &result
return
}
// getter for the branchTransactionHash (supports concurrency)
......@@ -509,10 +518,12 @@ func (transaction *Transaction) ParseBranchTransactionHash() ternary.Trinary {
}
// 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)
......@@ -642,3 +653,7 @@ func (transaction *Transaction) Store() errors.IdentifiableError {
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
const (
TRANSACTION_NULL_HASH = ternary.Trinary("999999999999999999999999999999999999999999999999999999999999999999999999999999999")
)
package tangle
import (
"sync"
"time"
"github.com/iotaledger/goshimmer/packages/bitutils"
"github.com/iotaledger/goshimmer/packages/errors"
"github.com/iotaledger/goshimmer/packages/ternary"
"github.com/iotaledger/goshimmer/packages/typeconversion"
"sync"
"time"
)
// region type definition and constructor //////////////////////////////////////////////////////////////////////////////
......@@ -94,7 +95,7 @@ func (metaData *TransactionMetadata) GetSolid() bool {
return metaData.solid
}
func (metaData *TransactionMetadata) SetSolid(solid bool) {
func (metaData *TransactionMetadata) SetSolid(solid bool) bool {
metaData.solidMutex.RLock()
if metaData.solid != solid {
metaData.solidMutex.RUnlock()
......@@ -104,10 +105,14 @@ func (metaData *TransactionMetadata) SetSolid(solid bool) {
metaData.solid = solid
metaData.SetModified(true)
return true
}
} else {
metaData.solidMutex.RUnlock()
}
return false
}
func (metaData *TransactionMetadata) GetLiked() bool {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment