Skip to content
This repository was archived by the owner on Jan 24, 2025. It is now read-only.

Commit 3822ac2

Browse files
authored
Merge pull request #834 from iotaledger/fix/orphanage2
Fix: Orphanage
2 parents 04445a0 + e20d126 commit 3822ac2

File tree

12 files changed

+225
-431
lines changed

12 files changed

+225
-431
lines changed

pkg/protocol/engine/blocks/block.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package blocks
22

33
import (
44
"fmt"
5+
"sync/atomic"
56
"time"
67

78
"github.com/iotaledger/hive.go/ds"
@@ -11,10 +12,20 @@ import (
1112
"github.com/iotaledger/hive.go/stringify"
1213
"github.com/iotaledger/iota-core/pkg/core/account"
1314
"github.com/iotaledger/iota-core/pkg/model"
15+
"github.com/iotaledger/iota-core/pkg/protocol/engine/mempool"
1416
iotago "github.com/iotaledger/iota.go/v4"
1517
)
1618

1719
type Block struct {
20+
// ParentsBooked is triggered when all parents of the block are booked.
21+
ParentsBooked reactive.Event
22+
23+
// PayloadDependenciesAvailable is triggered when the dependencies of the block's payload are available.
24+
PayloadDependenciesAvailable reactive.Event
25+
26+
// SignedTransactionMetadata contains the signed transaction metadata of the block.
27+
SignedTransactionMetadata reactive.Variable[mempool.SignedTransactionMetadata]
28+
1829
// BlockDAG block
1930
missing bool
2031
missingBlockID iotago.BlockID
@@ -76,6 +87,10 @@ func (r *rootBlock) String() string {
7687

7788
func newEmptyBlock() *Block {
7889
return &Block{
90+
ParentsBooked: reactive.NewEvent(),
91+
PayloadDependenciesAvailable: reactive.NewEvent(),
92+
SignedTransactionMetadata: reactive.NewVariable[mempool.SignedTransactionMetadata](),
93+
7994
witnesses: ds.NewSet[account.SeatIndex](),
8095
spenderIDs: ds.NewSet[iotago.TransactionID](),
8196
payloadSpenderIDs: ds.NewSet[iotago.TransactionID](),
@@ -112,6 +127,8 @@ func NewRootBlock(blockID iotago.BlockID, commitmentID iotago.CommitmentID, issu
112127
b.scheduled = true
113128

114129
// This should be true since we commit and evict on acceptance.
130+
b.ParentsBooked.Set(true)
131+
b.PayloadDependenciesAvailable.Set(true)
115132
b.solid.Init(true)
116133
b.booked.Init(true)
117134
b.weightPropagated.Init(true)
@@ -663,3 +680,30 @@ func (b *Block) ModelBlock() *model.Block {
663680
func (b *Block) WorkScore() iotago.WorkScore {
664681
return b.workScore
665682
}
683+
684+
func (b *Block) WaitForPayloadDependencies(dependencies ds.Set[mempool.StateMetadata]) {
685+
if dependencies == nil || dependencies.Size() == 0 {
686+
b.PayloadDependenciesAvailable.Trigger()
687+
688+
return
689+
}
690+
691+
var unreferencedOutputCount atomic.Int32
692+
unreferencedOutputCount.Store(int32(dependencies.Size()))
693+
694+
dependencies.Range(func(dependency mempool.StateMetadata) {
695+
dependencyReady := false
696+
697+
dependency.OnAccepted(func() {
698+
dependency.OnInclusionSlotUpdated(func(_ iotago.SlotIndex, inclusionSlot iotago.SlotIndex) {
699+
if !dependencyReady && inclusionSlot <= b.ID().Slot() {
700+
dependencyReady = true
701+
702+
if unreferencedOutputCount.Add(-1) == 0 {
703+
b.PayloadDependenciesAvailable.Trigger()
704+
}
705+
}
706+
})
707+
})
708+
})
709+
}

pkg/protocol/engine/booker/inmemorybooker/booker.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,14 @@ func (b *Booker) Init(ledger ledger.Ledger, loadBlockFromStorage func(iotago.Blo
9696
// Queue checks if payload is solid and then sets up the block to react to its parents.
9797
func (b *Booker) Queue(block *blocks.Block) error {
9898
signedTransactionMetadata, containsTransaction := b.ledger.AttachTransaction(block)
99-
10099
if !containsTransaction {
101100
b.setupBlock(block)
102-
return nil
103-
}
104101

105-
if signedTransactionMetadata == nil {
102+
return nil
103+
} else if signedTransactionMetadata == nil {
106104
return ierrors.Errorf("transaction in block %s was not attached", block.ID())
107105
}
106+
block.SignedTransactionMetadata.Set(signedTransactionMetadata)
108107

109108
// Based on the assumption that we always fork and the UTXO and Tangle past cones are always fully known.
110109
signedTransactionMetadata.OnSignaturesValid(func() {
@@ -137,6 +136,12 @@ func (b *Booker) Queue(block *blocks.Block) error {
137136
func (b *Booker) Reset() { /* nothing to reset but comply with interface */ }
138137

139138
func (b *Booker) setupBlock(block *blocks.Block) {
139+
var payloadDependencies, directlyReferencedPayloadDependencies ds.Set[mempool.StateMetadata]
140+
if signedTransactionMetadata := block.SignedTransactionMetadata.Get(); signedTransactionMetadata != nil && signedTransactionMetadata.SignaturesInvalid() == nil && !signedTransactionMetadata.TransactionMetadata().IsInvalid() {
141+
payloadDependencies = signedTransactionMetadata.TransactionMetadata().Inputs()
142+
directlyReferencedPayloadDependencies = ds.NewSet[mempool.StateMetadata]()
143+
}
144+
140145
var unbookedParentsCount atomic.Int32
141146
unbookedParentsCount.Store(int32(len(block.Parents())))
142147

@@ -149,13 +154,15 @@ func (b *Booker) setupBlock(block *blocks.Block) {
149154
}
150155

151156
parentBlock.Booked().OnUpdateOnce(func(_ bool, _ bool) {
152-
if unbookedParentsCount.Add(-1) == 0 {
153-
if err := b.book(block); err != nil {
154-
if block.SetInvalid() {
155-
b.events.BlockInvalid.Trigger(block, ierrors.Wrap(err, "failed to book block"))
156-
}
157+
if directlyReferencedPayloadDependencies != nil {
158+
if parentTransactionMetadata := parentBlock.SignedTransactionMetadata.Get(); parentTransactionMetadata != nil {
159+
directlyReferencedPayloadDependencies.AddAll(parentTransactionMetadata.TransactionMetadata().Outputs())
157160
}
158161
}
162+
163+
if unbookedParentsCount.Add(-1) == 0 {
164+
block.ParentsBooked.Trigger()
165+
}
159166
})
160167

161168
parentBlock.Invalid().OnUpdateOnce(func(_ bool, _ bool) {
@@ -164,6 +171,22 @@ func (b *Booker) setupBlock(block *blocks.Block) {
164171
}
165172
})
166173
})
174+
175+
block.ParentsBooked.OnTrigger(func() {
176+
if directlyReferencedPayloadDependencies != nil {
177+
payloadDependencies.DeleteAll(directlyReferencedPayloadDependencies)
178+
}
179+
180+
block.WaitForPayloadDependencies(payloadDependencies)
181+
})
182+
183+
block.PayloadDependenciesAvailable.OnTrigger(func() {
184+
if err := b.book(block); err != nil {
185+
if block.SetInvalid() {
186+
b.events.BlockInvalid.Trigger(block, ierrors.Wrap(err, "failed to book block"))
187+
}
188+
}
189+
})
167190
}
168191

169192
func (b *Booker) book(block *blocks.Block) error {

pkg/protocol/engine/ledger/tests/state.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ type MockedState struct {
1010
id iotago.OutputID
1111
output *MockedOutput
1212
creationSlot iotago.SlotIndex
13+
slotBooked iotago.SlotIndex
1314
}
1415

1516
func NewMockedState(transactionID iotago.TransactionID, index uint16) *MockedState {
1617
return &MockedState{
1718
id: iotago.OutputIDFromTransactionIDAndIndex(transactionID, index),
1819
output: &MockedOutput{},
1920
creationSlot: iotago.SlotIndex(0),
21+
slotBooked: iotago.SlotIndex(0),
2022
}
2123
}
2224

@@ -44,6 +46,10 @@ func (m *MockedState) SlotCreated() iotago.SlotIndex {
4446
return m.creationSlot
4547
}
4648

49+
func (m *MockedState) SlotBooked() iotago.SlotIndex {
50+
return m.slotBooked
51+
}
52+
4753
func (m *MockedState) String() string {
4854
return "MockedOutput(" + m.id.ToHex() + ")"
4955
}

pkg/protocol/engine/mempool/signed_transaction_metadata.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ type SignedTransactionMetadata interface {
1111

1212
OnSignaturesInvalid(callback func(err error)) (unsubscribe func())
1313

14+
SignaturesInvalid() error
15+
1416
TransactionMetadata() TransactionMetadata
1517

1618
Attachments() []iotago.BlockID

pkg/protocol/engine/mempool/state.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ type State interface {
1515

1616
// Whether the state is read only.
1717
IsReadOnly() bool
18+
19+
// SlotBooked returns the slot index of the state if it is booked.
20+
SlotBooked() iotago.SlotIndex
1821
}
1922

2023
// A thin wrapper around a resolved commitment.
@@ -34,6 +37,10 @@ func (s CommitmentInputState) IsReadOnly() bool {
3437
return true
3538
}
3639

40+
func (s CommitmentInputState) SlotBooked() iotago.SlotIndex {
41+
return s.Commitment.Slot
42+
}
43+
3744
func CommitmentInputStateFromCommitment(commitment *iotago.Commitment) CommitmentInputState {
3845
return CommitmentInputState{
3946
Commitment: commitment,

pkg/protocol/engine/mempool/state_metadata.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
)
77

88
type StateMetadata interface {
9+
CreatingTransaction() TransactionMetadata
10+
911
State() State
1012

1113
SpenderIDs() reactive.Set[iotago.TransactionID]
@@ -16,5 +18,9 @@ type StateMetadata interface {
1618

1719
OnAcceptedSpenderUpdated(callback func(spender TransactionMetadata))
1820

21+
InclusionSlot() iotago.SlotIndex
22+
23+
OnInclusionSlotUpdated(callback func(prevSlot iotago.SlotIndex, newSlot iotago.SlotIndex))
24+
1925
inclusionFlags
2026
}

pkg/protocol/engine/mempool/v1/mempool.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,9 +466,12 @@ func (m *MemPool[VoteRank]) requestState(stateRef mempool.StateReference, waitIf
466466
request := m.resolveState(stateRef)
467467

468468
request.OnSuccess(func(state mempool.State) {
469+
inclusionSlot := state.SlotBooked()
470+
469471
// The output was resolved from the ledger, meaning it was actually persisted as it was accepted and
470472
// committed: otherwise we would have found it in cache or the request would have never resolved.
471473
stateMetadata := NewStateMetadata(state)
474+
stateMetadata.inclusionSlot.Set(&inclusionSlot)
472475
stateMetadata.accepted.Set(true)
473476

474477
p.Resolve(stateMetadata)

pkg/protocol/engine/mempool/v1/signed_transaction_metadata.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ func (s *SignedTransactionMetadata) OnSignaturesInvalid(callback func(error)) (u
5050
})
5151
}
5252

53+
func (s *SignedTransactionMetadata) SignaturesInvalid() error {
54+
return s.signaturesInvalid.Get()
55+
}
56+
5357
func (s *SignedTransactionMetadata) OnSignaturesValid(callback func()) (unsubscribe func()) {
5458
return s.signaturesValid.OnTrigger(callback)
5559
}

pkg/protocol/engine/mempool/v1/state_metadata.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ import (
1212
)
1313

1414
type StateMetadata struct {
15-
state mempool.State
15+
state mempool.State
16+
source *TransactionMetadata
1617

1718
// lifecycle
1819
spenderCount uint64
1920
spent *promise.Event
2021
doubleSpent *promise.Event
2122
spendAccepted reactive.Variable[*TransactionMetadata]
2223
spendCommitted reactive.Variable[*TransactionMetadata]
24+
inclusionSlot reactive.Variable[*iotago.SlotIndex]
2325
allSpendersRemoved *event.Event
2426

2527
spenderIDs reactive.DerivedSet[iotago.TransactionID]
@@ -29,12 +31,14 @@ type StateMetadata struct {
2931

3032
func NewStateMetadata(state mempool.State, optSource ...*TransactionMetadata) *StateMetadata {
3133
return (&StateMetadata{
32-
state: state,
34+
state: state,
35+
source: lo.First(optSource),
3336

3437
spent: promise.NewEvent(),
3538
doubleSpent: promise.NewEvent(),
3639
spendAccepted: reactive.NewVariable[*TransactionMetadata](),
3740
spendCommitted: reactive.NewVariable[*TransactionMetadata](),
41+
inclusionSlot: reactive.NewVariable[*iotago.SlotIndex](),
3842
allSpendersRemoved: event.New(),
3943

4044
spenderIDs: reactive.NewDerivedSet[iotago.TransactionID](),
@@ -51,6 +55,16 @@ func (s *StateMetadata) setup(optSource ...*TransactionMetadata) *StateMetadata
5155

5256
s.spenderIDs.InheritFrom(source.spenderIDs)
5357

58+
source.earliestIncludedValidAttachment.OnUpdate(func(_, newValue iotago.BlockID) {
59+
s.inclusionSlot.Compute(func(currentValue *iotago.SlotIndex) *iotago.SlotIndex {
60+
if newSlot := newValue.Slot(); currentValue == nil || newSlot < *currentValue {
61+
return &newSlot
62+
}
63+
64+
return currentValue
65+
})
66+
})
67+
5468
source.OnAccepted(func() { s.accepted.Set(true) })
5569
source.OnRejected(func() { s.rejected.Trigger() })
5670
source.OnCommittedSlotUpdated(lo.Void(s.committedSlot.Set))
@@ -59,6 +73,14 @@ func (s *StateMetadata) setup(optSource ...*TransactionMetadata) *StateMetadata
5973
return s
6074
}
6175

76+
func (s *StateMetadata) CreatingTransaction() mempool.TransactionMetadata {
77+
if s.source == nil {
78+
return nil
79+
}
80+
81+
return s.source
82+
}
83+
6284
func (s *StateMetadata) State() mempool.State {
6385
return s.state
6486
}
@@ -113,6 +135,23 @@ func (s *StateMetadata) HasNoSpenders() bool {
113135
return atomic.LoadUint64(&s.spenderCount) == 0
114136
}
115137

138+
func (s *StateMetadata) InclusionSlot() iotago.SlotIndex {
139+
return *s.inclusionSlot.Get()
140+
}
141+
142+
func (s *StateMetadata) OnInclusionSlotUpdated(callback func(prevID iotago.SlotIndex, newID iotago.SlotIndex)) {
143+
s.inclusionSlot.OnUpdate(func(oldValue *iotago.SlotIndex, newValue *iotago.SlotIndex) {
144+
switch {
145+
case oldValue == nil:
146+
callback(iotago.SlotIndex(0), *newValue)
147+
case newValue == nil:
148+
callback(*oldValue, iotago.SlotIndex(0))
149+
default:
150+
callback(*oldValue, *newValue)
151+
}
152+
})
153+
}
154+
116155
func (s *StateMetadata) increaseSpenderCount() {
117156
if spenderCount := atomic.AddUint64(&s.spenderCount, 1); spenderCount == 1 {
118157
s.spent.Trigger()

0 commit comments

Comments
 (0)