Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic liveness threshold to TipSelection #307

Merged
merged 31 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6fd065d
Feat: Added dynamic liveness threshold
hmoog Aug 29, 2023
840b6ec
Feat: added TODO
hmoog Aug 29, 2023
c730ef0
Feat: prepared logic to be ready for new protocol parameters
hmoog Aug 30, 2023
54a2c31
Feat: upgraded iota.go and implemented formula
hmoog Sep 4, 2023
13155ac
Merge branch 'develop' of github.com:iotaledger/iota-core into feat/d…
hmoog Sep 4, 2023
fe609af
Refactor: reverted changes
hmoog Sep 4, 2023
19417f9
added formula
hmoog Sep 5, 2023
8f8c471
started adding tests
hmoog Sep 5, 2023
d99b623
Feat: upgraded hive.go
hmoog Sep 5, 2023
0a9bb0a
Feat: cleaned up provider
hmoog Sep 5, 2023
b1611e5
Feat: made DynamicLivenessThreshold function public
hmoog Sep 5, 2023
e9b882a
Fix: fixed build errors
hmoog Sep 5, 2023
234e6ca
Fix: fixed more refactor issues
hmoog Sep 5, 2023
0ff84f6
Refactor: refactored code
hmoog Sep 5, 2023
30e3ce9
Feat: added lazy initialization to tipselection
hmoog Sep 5, 2023
3effce8
Fix: fixed presets
hmoog Sep 5, 2023
e2b43a4
Feat: added experimental Wait method for initialization
hmoog Sep 5, 2023
a5da8a3
Feat: moved utility method to hive
hmoog Sep 5, 2023
6a037b8
Update go.mod
jonastheis Sep 8, 2023
1302ff1
Merge remote-tracking branch 'origin' into feat/dynamic-liveness-thre…
jonastheis Sep 8, 2023
acc4279
Update go.mod
jonastheis Sep 8, 2023
4bf765b
Feat: added tests for tipselection
hmoog Sep 8, 2023
82e3a35
Fix issue where root blocks where added to tip manager
jonastheis Sep 11, 2023
2ab3dd8
Add TestDynamicLivenessThreshold
jonastheis Sep 11, 2023
da409b7
Merge remote-tracking branch 'origin/develop' into feat/dynamic-liven…
jonastheis Sep 15, 2023
743c1d7
Update to latest go.mod
jonastheis Sep 15, 2023
bf58d1e
Merge remote-tracking branch 'origin/develop' into feat/dynamic-liven…
jonastheis Sep 27, 2023
2dfbd4e
Update go.mod
jonastheis Sep 27, 2023
76bb2dd
Update to latest iota.go
jonastheis Sep 28, 2023
93f30cf
Merge remote-tracking branch 'origin/develop' into feat/dynamic-liven…
jonastheis Sep 28, 2023
44742d1
Update to latest iota.go
jonastheis Sep 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ require (
github.com/iotaledger/hive.go/stringify v0.0.0-20230926122307-d671b36a4a65
github.com/iotaledger/inx-app v1.0.0-rc.3.0.20230927140518-622f63be6182
github.com/iotaledger/inx/go v1.0.0-rc.2.0.20230927140257-bfa0bb0af2bd
github.com/iotaledger/iota.go/v4 v4.0.0-20230927125610-ddf51789ec4d
github.com/iotaledger/iota.go/v4 v4.0.0-20230928093005-fe74b3839c5d
github.com/labstack/echo/v4 v4.11.1
github.com/labstack/gommon v0.4.0
github.com/libp2p/go-libp2p v0.30.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ github.com/iotaledger/inx-app v1.0.0-rc.3.0.20230927140518-622f63be6182 h1:lQikt
github.com/iotaledger/inx-app v1.0.0-rc.3.0.20230927140518-622f63be6182/go.mod h1:q24QEsS887ZWJVX76w2kwSgC84KS7wIKOy1otuqZ2ZM=
github.com/iotaledger/inx/go v1.0.0-rc.2.0.20230927140257-bfa0bb0af2bd h1:nFG3Zq/zFA4KhBYFX2IezX1C74zfE0DqCt0LrgTa9Ig=
github.com/iotaledger/inx/go v1.0.0-rc.2.0.20230927140257-bfa0bb0af2bd/go.mod h1:c5778OnWpLq108YE+Eb2m8Ri/t/4ydV0TvI/Sy5YivQ=
github.com/iotaledger/iota.go/v4 v4.0.0-20230927125610-ddf51789ec4d h1:lhYbBhVORcS2LLNviaO/yTmom1suDskJLA1wSvvsLiU=
github.com/iotaledger/iota.go/v4 v4.0.0-20230927125610-ddf51789ec4d/go.mod h1:wR9xBbsofns9hFyRHFZ2bDYIb861qsfmQPVMBKcPvDo=
github.com/iotaledger/iota.go/v4 v4.0.0-20230928093005-fe74b3839c5d h1:YvE8rOrBJQu9wryUAOwEEl4mu91rpocO23qKpffKdi8=
github.com/iotaledger/iota.go/v4 v4.0.0-20230928093005-fe74b3839c5d/go.mod h1:wR9xBbsofns9hFyRHFZ2bDYIb861qsfmQPVMBKcPvDo=
github.com/ipfs/boxo v0.10.0 h1:tdDAxq8jrsbRkYoF+5Rcqyeb91hgWe2hp7iLu7ORZLY=
github.com/ipfs/boxo v0.10.0/go.mod h1:Fg+BnfxZ0RPzR0nOodzdIq3A7KgoWAOWsEIImrIQdBM=
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func NewTestFramework(test *testing.T) *TestFramework {
iotago.NewV3ProtocolParameters(
iotago.WithNetworkOptions("TestJungle", "tgl"),
iotago.WithSupplyOptions(10000, 0, 0, 0, 0, 0, 0),
iotago.WithLivenessOptions(1, 1, 2, 8),
iotago.WithLivenessOptions(10, 10, 1, 2, 8),
),
)

Expand Down
7 changes: 7 additions & 0 deletions pkg/protocol/engine/blocks/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,13 @@ func (b *Block) AddWitness(seat account.SeatIndex) (added bool) {
return b.witnesses.Add(seat)
}

func (b *Block) WitnessCount() int {
b.mutex.RLock()
defer b.mutex.RUnlock()

return b.witnesses.Size()
}

func (b *Block) Witnesses() []account.SeatIndex {
b.mutex.RLock()
defer b.mutex.RUnlock()
Expand Down
44 changes: 33 additions & 11 deletions pkg/protocol/engine/tipmanager/tests/testframework.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,26 @@ import (
type TestFramework struct {
Instance *tipmanagerv1.TipManager

blockIDsByAlias map[string]iotago.BlockID
blocksByID map[iotago.BlockID]*blocks.Block
test *testing.T
blockIDsByAlias map[string]iotago.BlockID
tipMetadataByAlias map[string]tipmanager.TipMetadata
blocksByID map[iotago.BlockID]*blocks.Block
test *testing.T

API iotago.API
}

func NewTestFramework(test *testing.T) *TestFramework {
t := &TestFramework{
blockIDsByAlias: make(map[string]iotago.BlockID),
blocksByID: make(map[iotago.BlockID]*blocks.Block),
test: test,
blockIDsByAlias: make(map[string]iotago.BlockID),
tipMetadataByAlias: make(map[string]tipmanager.TipMetadata),
blocksByID: make(map[iotago.BlockID]*blocks.Block),
test: test,
API: tpkg.TestAPI,
}

t.blockIDsByAlias["Genesis"] = iotago.EmptyBlockID()

t.Instance = tipmanagerv1.NewTipManager(func(blockID iotago.BlockID) (block *blocks.Block, exists bool) {
t.Instance = tipmanagerv1.New(func(blockID iotago.BlockID) (block *blocks.Block, exists bool) {
block, exists = t.blocksByID[blockID]
return block, exists
})
Expand All @@ -43,11 +48,13 @@ func NewTestFramework(test *testing.T) *TestFramework {
}

func (t *TestFramework) AddBlock(alias string) tipmanager.TipMetadata {
return t.Instance.AddBlock(t.Block(alias))
t.tipMetadataByAlias[alias] = t.Instance.AddBlock(t.Block(alias))

return t.tipMetadataByAlias[alias]
}

func (t *TestFramework) CreateBlock(alias string, parents map[iotago.ParentsType][]string) *blocks.Block {
blockBuilder := builder.NewBasicBlockBuilder(tpkg.TestAPI)
func (t *TestFramework) CreateBlock(alias string, parents map[iotago.ParentsType][]string, optBlockBuilder ...func(*builder.BasicBlockBuilder)) *blocks.Block {
blockBuilder := builder.NewBasicBlockBuilder(t.API)
blockBuilder.IssuingTime(time.Now())

if strongParents, strongParentsExist := parents[iotago.StrongParentType]; strongParentsExist {
Expand All @@ -60,6 +67,10 @@ func (t *TestFramework) CreateBlock(alias string, parents map[iotago.ParentsType
blockBuilder.ShallowLikeParents(lo.Map(shallowLikeParents, t.BlockID))
}

if len(optBlockBuilder) > 0 {
optBlockBuilder[0](blockBuilder)
}

block, err := blockBuilder.Build()
require.NoError(t.test, err)

Expand All @@ -82,17 +93,28 @@ func (t *TestFramework) Block(alias string) *blocks.Block {
return block
}

func (t *TestFramework) TipMetadata(alias string) tipmanager.TipMetadata {
tipMetadata, tipMetadataExists := t.tipMetadataByAlias[alias]
require.True(t.test, tipMetadataExists)

return tipMetadata
}

func (t *TestFramework) BlockID(alias string) iotago.BlockID {
blockID, blockIDExists := t.blockIDsByAlias[alias]
require.True(t.test, blockIDExists, "blockID for alias '%s' does not exist", alias)

return blockID
}

func (t *TestFramework) AssertStrongTips(aliases ...string) {
func (t *TestFramework) RequireStrongTips(aliases ...string) {
for _, alias := range aliases {
require.True(t.test, ds.NewSet(lo.Map(t.Instance.StrongTips(), tipmanager.TipMetadata.ID)...).Has(t.BlockID(alias)), "strongTips does not contain block '%s'", alias)
}

require.Equal(t.test, len(aliases), len(t.Instance.StrongTips()), "strongTips size does not match")
}

func (t *TestFramework) RequireLivenessThresholdReached(alias string, expected bool) {
require.Equal(t.test, expected, t.TipMetadata(alias).LivenessThresholdReached().Get())
}
14 changes: 7 additions & 7 deletions pkg/protocol/engine/tipmanager/tests/tipmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ func TestTipManager(t *testing.T) {
})

tf.AddBlock("Bernd").TipPool().Set(tipmanager.StrongTipPool)
tf.AssertStrongTips("Bernd")
tf.RequireStrongTips("Bernd")

tf.AddBlock("Bernd1").TipPool().Set(tipmanager.StrongTipPool)
tf.AssertStrongTips("Bernd1")
tf.RequireStrongTips("Bernd1")

tf.AddBlock("Bernd1.1").TipPool().Set(tipmanager.StrongTipPool)
tf.AssertStrongTips("Bernd1", "Bernd1.1")
tf.RequireStrongTips("Bernd1", "Bernd1.1")
}

func Test_Orphanage(t *testing.T) {
Expand All @@ -44,15 +44,15 @@ func Test_Orphanage(t *testing.T) {
})

tf.AddBlock("A").TipPool().Set(tipmanager.StrongTipPool)
tf.AssertStrongTips("A")
tf.RequireStrongTips("A")

blockB := tf.AddBlock("B")
blockB.TipPool().Set(tipmanager.StrongTipPool)
tf.AssertStrongTips("A", "B")
tf.RequireStrongTips("A", "B")

tf.AddBlock("C").TipPool().Set(tipmanager.StrongTipPool)
tf.AssertStrongTips("C")
tf.RequireStrongTips("C")

blockB.LivenessThresholdReached().Trigger()
tf.AssertStrongTips("A")
tf.RequireStrongTips("A")
}
5 changes: 2 additions & 3 deletions pkg/protocol/engine/tipmanager/v1/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import (
"github.com/iotaledger/hive.go/lo"
"github.com/iotaledger/hive.go/runtime/event"
"github.com/iotaledger/hive.go/runtime/module"
"github.com/iotaledger/hive.go/runtime/options"
"github.com/iotaledger/iota-core/pkg/protocol/engine"
"github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager"
)

// NewProvider creates a new TipManager provider, that can be used to inject the component into an engine.
func NewProvider(opts ...options.Option[TipManager]) module.Provider[*engine.Engine, tipmanager.TipManager] {
func NewProvider() module.Provider[*engine.Engine, tipmanager.TipManager] {
return module.Provide(func(e *engine.Engine) tipmanager.TipManager {
t := NewTipManager(e.BlockCache.Block, opts...)
t := New(e.BlockCache.Block)

e.HookConstructed(func() {
tipWorker := e.Workers.CreatePool("AddTip", 2)
Expand Down
29 changes: 21 additions & 8 deletions pkg/protocol/engine/tipmanager/v1/tipmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/iotaledger/hive.go/lo"
"github.com/iotaledger/hive.go/runtime/event"
"github.com/iotaledger/hive.go/runtime/module"
"github.com/iotaledger/hive.go/runtime/options"
"github.com/iotaledger/hive.go/runtime/syncutils"
"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
"github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager"
Expand Down Expand Up @@ -40,15 +39,20 @@ type TipManager struct {
module.Module
}

// NewTipManager creates a new TipManager.
func NewTipManager(blockRetriever func(blockID iotago.BlockID) (block *blocks.Block, exists bool), opts ...options.Option[TipManager]) *TipManager {
return options.Apply(&TipManager{
// New creates a new TipManager.
func New(blockRetriever func(blockID iotago.BlockID) (block *blocks.Block, exists bool)) *TipManager {
t := &TipManager{
retrieveBlock: blockRetriever,
tipMetadataStorage: shrinkingmap.New[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]](),
strongTipSet: randommap.New[iotago.BlockID, *TipMetadata](),
weakTipSet: randommap.New[iotago.BlockID, *TipMetadata](),
blockAdded: event.New1[tipmanager.TipMetadata](),
}, opts, (*TipManager).TriggerConstructed)
}

t.TriggerConstructed()
t.TriggerInitialized()

return t
}

// AddBlock adds a Block to the TipManager and returns the TipMetadata if the Block was added successfully.
Expand Down Expand Up @@ -99,8 +103,11 @@ func (t *TipManager) Evict(slot iotago.SlotIndex) {
}
}

// Shutdown does nothing but is required by the module.Interface.
func (t *TipManager) Shutdown() {}
// Shutdown marks the TipManager as shutdown.
func (t *TipManager) Shutdown() {
t.TriggerShutdown()
t.TriggerStopped()
}

// setupBlockMetadata sets up the behavior of the given Block.
func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) {
Expand Down Expand Up @@ -139,7 +146,13 @@ func (t *TipManager) forEachParentByType(block *blocks.Block, consumer func(pare

for _, parent := range block.ParentsWithType() {
if metadataStorage := t.metadataStorage(parent.ID.Slot()); metadataStorage != nil {
if parentMetadata, created := metadataStorage.GetOrCreate(parent.ID, func() *TipMetadata { return NewBlockMetadata(lo.Return1(t.retrieveBlock(parent.ID))) }); parentMetadata.Block() != nil {
// Make sure we don't add rootblocks back to the tips.
parentBlock, exists := t.retrieveBlock(parent.ID)
if !exists || parentBlock.IsRootBlock() {
continue
}

if parentMetadata, created := metadataStorage.GetOrCreate(parent.ID, func() *TipMetadata { return NewBlockMetadata(parentBlock) }); parentMetadata.Block() != nil {
consumer(parent.Type, parentMetadata)

if created {
Expand Down
4 changes: 2 additions & 2 deletions pkg/protocol/engine/tipselection/tipselection.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ type TipSelection interface {
// SelectTips selects the tips that should be used as references for a new block.
SelectTips(count int) (references model.ParentReferences)

// SetLivenessThreshold sets the liveness threshold used for tip selection (it can only increase monotonically).
SetLivenessThreshold(threshold time.Time)
// SetAcceptanceTime updates the acceptance time of the TipSelection.
SetAcceptanceTime(acceptanceTime time.Time) (previousTime time.Time)

// Interface embeds the required methods of the module.Interface.
module.Interface
Expand Down
43 changes: 31 additions & 12 deletions pkg/protocol/engine/tipselection/v1/provider.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,54 @@
package tipselectionv1

import (
"math"
"time"

"github.com/iotaledger/hive.go/runtime/module"
"github.com/iotaledger/hive.go/runtime/options"
"github.com/iotaledger/iota-core/pkg/protocol/engine"
"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
"github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager"
"github.com/iotaledger/iota-core/pkg/protocol/engine/tipselection"
)

// NewProvider creates a new TipSelection provider, that can be used to inject the component into an engine.
func NewProvider(opts ...options.Option[TipSelection]) module.Provider[*engine.Engine, tipselection.TipSelection] {
return module.Provide(func(e *engine.Engine) tipselection.TipSelection {
t := New(e, e.TipManager, e.Ledger.ConflictDAG(), e.Ledger.MemPool(), e.EvictionState.LatestRootBlocks, opts...)
t := New(opts...)

e.HookConstructed(func() {
e.Ledger.HookInitialized(func() {
// wait for submodules to be constructed (so all of their properties are available)
module.OnAllConstructed(func() {
t.Construct(e.TipManager, e.Ledger.ConflictDAG(), e.Ledger.MemPool().TransactionMetadata, e.EvictionState.LatestRootBlocks, DynamicLivenessThreshold(e.SybilProtection.SeatManager().OnlineCommittee().Size))

e.Events.AcceptedBlockProcessed.Hook(func(block *blocks.Block) {
t.SetLivenessThreshold(block.IssuingTime().Add(-e.CurrentAPI().LivenessThresholdDuration()))
t.SetAcceptanceTime(block.IssuingTime())
})

t.conflictDAG = e.Ledger.ConflictDAG()
t.memPool = e.Ledger.MemPool()

t.TriggerInitialized()
})

e.TipManager.OnBlockAdded(t.classifyTip)
}, e.TipManager, e.Ledger, e.SybilProtection)
})

e.HookStopped(t.TriggerStopped)
e.HookShutdown(t.Shutdown)

return t
})
}

// DynamicLivenessThreshold returns a function that calculates the liveness threshold for a tip.
func DynamicLivenessThreshold(committeeSizeProvider func() int) func(tip tipmanager.TipMetadata) time.Duration {
return func(tip tipmanager.TipMetadata) time.Duration {
// We want to scale the liveness threshold based on the number of witnesses:
// 0 witnesses: approval modifier is 0 -> LivenessThresholdLowerBound
// <=1/3: scale linearly
// >1/3: approval modifier is 1 -> LivenessThresholdUpperBound
var (
params = tip.Block().ModelBlock().ProtocolBlock().API.ProtocolParameters()
livenessThresholdLowerBound = params.LivenessThresholdLowerBound()
livenessWindow = float64(params.LivenessThresholdUpperBound() - livenessThresholdLowerBound)
expectedWitnessCount = math.Ceil(float64(committeeSizeProvider()) / 3.0)
approvalModifier = math.Min(float64(tip.Block().WitnessCount())/expectedWitnessCount, 1.0)
)

return livenessThresholdLowerBound + time.Duration(approvalModifier*livenessWindow)
}
}
Loading
Loading