Skip to content
Snippets Groups Projects
Unverified Commit 7db8e8e1 authored by Jonas Theis's avatar Jonas Theis Committed by GitHub
Browse files

TipManager & ValueObject factory (#417)


* Add TipManager with naive locking and some basic tests

* Add ValueObjectFactory

* Bones

* Refactor TipManager to be able to handle tips in the context of branches

* Add unit tests

* Add more tests

* Feat: initial commit

* Feat: added setPreferred to TransactionMetadata

* Feat: added a Conflicting() method to the transactionMetadata

* Fix: fixed logic bug

* Feat: refactored fcob

* Refactor: refactored additional code

* Fix: fixed a bug in ForeachConsumers

* Refactor: cleaned up code

* Feat: implemented FCOB consensus into the valuetransfer dapp

* Refactor: refactored FCOB

* Docs: added some additional comments

* Docs: fixed comments

* Refactor: commit before branch change

* Feat: added PayloadLiked Event

* Refactor: fixed some missing comments + added liked to marshal

* Feat: reworked the preferred and liked propagation

* Refactor: cleaned up some logic

* Refactor: simplified code

* Refactor: cleaned up more stuff :P

* Refactor: refactor

* Feat: moved test + refactored fcob

* Fix: fixed a few bugs in liked propagation

* Rewrite TipManager to maintain a simple flat map of tips. The value transfers plugin needs to call the corresponding functions AddTip and RemoveTip when a value object is liked or disliked, respectively.

* adapt to new hive.go version

* upgrade hive.go

* Feat: started implementing a wallet

* Feat: extended wallet files

* Integrate tipmanager on tangle events

* Rename TipCount to Size

Co-authored-by: default avatarWolfgang Welz <welzwo@gmail.com>
Co-authored-by: default avatarHans Moog <hm@mkjc.net>
parent 0355aee5
No related branches found
No related tags found
No related merge requests found
package valuetransfers
import (
"sync"
"time"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tipmanager"
"github.com/iotaledger/goshimmer/plugins/database"
"github.com/iotaledger/hive.go/daemon"
"github.com/iotaledger/hive.go/events"
......@@ -42,6 +45,12 @@ var (
// log holds a reference to the logger used by this app.
log *logger.Logger
tipManager *tipmanager.TipManager
tipManagerOnce sync.Once
valueObjectFactory *tangle.ValueObjectFactory
valueObjectFactoryOnce sync.Once
)
func configure(_ *node.Plugin) {
......@@ -54,6 +63,19 @@ func configure(_ *node.Plugin) {
log.Error(err)
}))
// initialize tip manager and value object factory
tipManager = TipManager()
valueObjectFactory = ValueObjectFactory()
Tangle.Events.PayloadLiked.Attach(events.NewClosure(func(cachedPayload *payload.CachedPayload, cachedMetadata *tangle.CachedPayloadMetadata) {
cachedMetadata.Release()
cachedPayload.Consume(tipManager.AddTip)
}))
Tangle.Events.PayloadDisliked.Attach(events.NewClosure(func(cachedPayload *payload.CachedPayload, cachedMetadata *tangle.CachedPayloadMetadata) {
cachedMetadata.Release()
cachedPayload.Consume(tipManager.RemoveTip)
}))
// configure FCOB consensus rules
FCOB = consensus.NewFCOB(Tangle, AverageNetworkDelay)
FCOB.Events.Vote.Attach(events.NewClosure(func(id string, initOpn vote.Opinion) {
......@@ -110,3 +132,19 @@ func onReceiveMessageFromMessageLayer(cachedMessage *message.CachedMessage, cach
Tangle.AttachPayload(valuePayload)
}
// TipManager returns the TipManager singleton.
func TipManager() *tipmanager.TipManager {
tipManagerOnce.Do(func() {
tipManager = tipmanager.New()
})
return tipManager
}
// ValueObjectFactory returns the ValueObjectFactory singleton.
func ValueObjectFactory() *tangle.ValueObjectFactory {
valueObjectFactoryOnce.Do(func() {
valueObjectFactory = tangle.NewValueObjectFactory(TipManager())
})
return valueObjectFactory
}
package tangle
import (
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tipmanager"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
"github.com/iotaledger/hive.go/events"
)
// ValueObjectFactory acts as a factory to create new value objects.
type ValueObjectFactory struct {
tipManager *tipmanager.TipManager
Events *ValueObjectFactoryEvents
}
// NewValueObjectFactory creates a new ValueObjectFactory.
func NewValueObjectFactory(tipManager *tipmanager.TipManager) *ValueObjectFactory {
return &ValueObjectFactory{
tipManager: tipManager,
Events: &ValueObjectFactoryEvents{
ValueObjectConstructed: events.NewEvent(valueObjectConstructedEvent),
},
}
}
// IssueTransaction creates a new value object including tip selection and returns it.
// It also triggers the ValueObjectConstructed event once it's done.
func (v *ValueObjectFactory) IssueTransaction(tx *transaction.Transaction) *payload.Payload {
parent1, parent2 := v.tipManager.Tips()
valueObject := payload.New(parent1, parent2, tx)
v.Events.ValueObjectConstructed.Trigger(valueObject)
return valueObject
}
// ValueObjectFactoryEvents represent events happening on a ValueObjectFactory.
type ValueObjectFactoryEvents struct {
// Fired when a value object is built including tips.
ValueObjectConstructed *events.Event
}
func valueObjectConstructedEvent(handler interface{}, params ...interface{}) {
handler.(func(*transaction.Transaction))(params[0].(*transaction.Transaction))
}
package tipmanager
import (
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
"github.com/iotaledger/hive.go/events"
)
// Events represents events happening on the TipManager.
type Events struct {
// Fired when a tip is added.
TipAdded *events.Event
// Fired when a tip is removed.
TipRemoved *events.Event
}
func payloadIDEvent(handler interface{}, params ...interface{}) {
handler.(func(payload.ID))(params[0].(payload.ID))
}
package tipmanager
import (
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
"github.com/iotaledger/goshimmer/packages/binary/datastructure"
"github.com/iotaledger/hive.go/events"
)
// TipManager manages liked tips and emits events for their removal and addition.
type TipManager struct {
// tips are all currently liked tips.
tips *datastructure.RandomMap
Events Events
}
// New creates a new TipManager.
func New() *TipManager {
return &TipManager{
tips: datastructure.NewRandomMap(),
Events: Events{
TipAdded: events.NewEvent(payloadIDEvent),
TipRemoved: events.NewEvent(payloadIDEvent),
},
}
}
// AddTip adds the given value object as a tip.
func (t *TipManager) AddTip(valueObject *payload.Payload) {
objectID := valueObject.ID()
parent1ID := valueObject.TrunkID()
parent2ID := valueObject.BranchID()
if t.tips.Set(objectID, objectID) {
t.Events.TipAdded.Trigger(objectID)
}
// remove parents
if _, deleted := t.tips.Delete(parent1ID); deleted {
t.Events.TipRemoved.Trigger(parent1ID)
}
if _, deleted := t.tips.Delete(parent2ID); deleted {
t.Events.TipRemoved.Trigger(parent2ID)
}
}
// RemoveTip removes the given value object as a tip.
func (t *TipManager) RemoveTip(valueObject *payload.Payload) {
objectID := valueObject.ID()
if _, deleted := t.tips.Delete(objectID); deleted {
t.Events.TipRemoved.Trigger(objectID)
}
}
// Tips returns two randomly selected tips.
func (t *TipManager) Tips() (parent1ObjectID, parent2ObjectID payload.ID) {
tip := t.tips.RandomEntry()
if tip == nil {
parent1ObjectID = payload.GenesisID
parent2ObjectID = payload.GenesisID
return
}
parent1ObjectID = tip.(payload.ID)
if t.tips.Size() == 1 {
parent2ObjectID = parent1ObjectID
return
}
parent2ObjectID = t.tips.RandomEntry().(payload.ID)
// select 2 distinct tips if there's more than 1 tip available
for parent1ObjectID == parent2ObjectID && t.tips.Size() > 1 {
parent2ObjectID = t.tips.RandomEntry().(payload.ID)
}
return
}
// Size returns the total liked tips.
func (t *TipManager) Size() int {
return t.tips.Size()
}
package tipmanager
import (
"sync"
"sync/atomic"
"testing"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
"github.com/iotaledger/hive.go/events"
"github.com/stretchr/testify/assert"
)
// TestTipManager tests the functionality of the TipManager.
func TestTipManager(t *testing.T) {
tipManager := New()
// check if first tips point to genesis
parent1, parent2 := tipManager.Tips()
assert.Equal(t, payload.GenesisID, parent1)
assert.Equal(t, payload.GenesisID, parent2)
// create value object and add it
v := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction())
tipManager.AddTip(v)
// check count
assert.Equal(t, 1, tipManager.Size())
// check if both reference it
parent1, parent2 = tipManager.Tips()
assert.Equal(t, v.ID(), parent1)
assert.Equal(t, v.ID(), parent2)
// create value object and add it
v2 := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction())
tipManager.AddTip(v2)
// check count
assert.Equal(t, 2, tipManager.Size())
// attach new value object to previous 2 tips
parent1, parent2 = tipManager.Tips()
assert.Contains(t, []payload.ID{v.ID(), v2.ID()}, parent1)
assert.Contains(t, []payload.ID{v.ID(), v2.ID()}, parent2)
v3 := payload.New(parent1, parent2, createDummyTransaction())
tipManager.AddTip(v3)
// check that parents are removed
assert.Equal(t, 1, tipManager.Size())
parent1, parent2 = tipManager.Tips()
assert.Equal(t, v3.ID(), parent1)
assert.Equal(t, v3.ID(), parent2)
}
// TestTipManagerParallel tests the TipManager's functionality by adding and selecting tips concurrently.
func TestTipManagerConcurrent(t *testing.T) {
numThreads := 10
numTips := 100
numSelected := 10
var tipsAdded uint64
countTipAdded := events.NewClosure(func(valueObjectID payload.ID) {
atomic.AddUint64(&tipsAdded, 1)
})
var tipsRemoved uint64
countTipRemoved := events.NewClosure(func(valueObjectID payload.ID) {
atomic.AddUint64(&tipsRemoved, 1)
})
var wg sync.WaitGroup
tipManager := New()
tipManager.Events.TipAdded.Attach(countTipAdded)
tipManager.Events.TipRemoved.Attach(countTipRemoved)
for t := 0; t < numThreads; t++ {
wg.Add(1)
go func() {
defer wg.Done()
tips := make(map[payload.ID]struct{})
// add a bunch of tips
for i := 0; i < numTips; i++ {
v := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction())
tipManager.AddTip(v)
tips[v.ID()] = struct{}{}
}
// add a bunch of tips that reference previous tips
for i := 0; i < numSelected; i++ {
v := payload.New(randomTip(tips), randomTip(tips), createDummyTransaction())
tipManager.AddTip(v)
}
}()
}
wg.Wait()
// check if count matches and corresponding events have been triggered
assert.EqualValues(t, numTips*numThreads+numSelected*numThreads, tipsAdded)
assert.EqualValues(t, 2*numSelected*numThreads, tipsRemoved)
assert.EqualValues(t, numTips*numThreads-numSelected*numThreads, tipManager.Size())
}
func randomTip(tips map[payload.ID]struct{}) payload.ID {
var tip payload.ID
for k := range tips {
tip = k
}
delete(tips, tip)
return tip
}
func createDummyTransaction() *transaction.Transaction {
return transaction.New(
// inputs
transaction.NewInputs(
transaction.NewOutputID(address.Random(), transaction.RandomID()),
transaction.NewOutputID(address.Random(), transaction.RandomID()),
),
// outputs
transaction.NewOutputs(map[address.Address][]*balance.Balance{
address.Random(): {
balance.New(balance.ColorIOTA, 1337),
},
}),
)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment