/
utils.go
184 lines (157 loc) · 7.98 KB
/
utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package tangleold
import (
"time"
"github.com/cockroachdb/errors"
"github.com/iotaledger/hive.go/core/generics/set"
"github.com/iotaledger/hive.go/core/generics/walker"
"github.com/iotaledger/goshimmer/packages/core/ledger"
"github.com/iotaledger/goshimmer/packages/core/ledger/utxo"
)
// region utils ////////////////////////////////////////////////////////////////////////////////////////////////////////
// Utils is a Tangle component that bundles methods that can be used to interact with the Tangle, that do not belong
// into public API.
type Utils struct {
tangle *Tangle
}
// NewUtils is the constructor of the Utils component.
func NewUtils(tangle *Tangle) (utils *Utils) {
return &Utils{
tangle: tangle,
}
}
// region walkers //////////////////////////////////////////////////////////////////////////////////////////////////////
// WalkBlockID is a generic Tangle walker that executes a custom callback for every visited BlockID, starting from
// the given entry points. It accepts an optional boolean parameter which can be set to true if a Block should be
// visited more than once following different paths. The callback receives a Walker object as the last parameter which
// can be used to control the behavior of the walk similar to how a "Context" is used in some parts of the stdlib.
func (u *Utils) WalkBlockID(callback func(blockID BlockID, walker *walker.Walker[BlockID]), entryPoints BlockIDs, revisitElements ...bool) {
if len(entryPoints) == 0 {
return
}
blockIDWalker := walker.New[BlockID](revisitElements...)
for blockID := range entryPoints {
blockIDWalker.Push(blockID)
}
for blockIDWalker.HasNext() {
callback(blockIDWalker.Next(), blockIDWalker)
}
}
// WalkBlock is a generic Tangle walker that executes a custom callback for every visited Block, starting from
// the given entry points. It accepts an optional boolean parameter which can be set to true if a Block should be
// visited more than once following different paths. The callback receives a Walker object as the last parameter which
// can be used to control the behavior of the walk similar to how a "Context" is used in some parts of the stdlib.
func (u *Utils) WalkBlock(callback func(block *Block, walker *walker.Walker[BlockID]), entryPoints BlockIDs, revisitElements ...bool) {
u.WalkBlockID(func(blockID BlockID, walker *walker.Walker[BlockID]) {
u.tangle.Storage.Block(blockID).Consume(func(block *Block) {
callback(block, walker)
})
}, entryPoints, revisitElements...)
}
// WalkBlockMetadata is a generic Tangle walker that executes a custom callback for every visited BlockMetadata,
// starting from the given entry points. It accepts an optional boolean parameter which can be set to true if a Block
// should be visited more than once following different paths. The callback receives a Walker object as the last
// parameter which can be used to control the behavior of the walk similar to how a "Context" is used in some parts of
// the stdlib.
func (u *Utils) WalkBlockMetadata(callback func(blockMetadata *BlockMetadata, walker *walker.Walker[BlockID]), entryPoints BlockIDs, revisitElements ...bool) {
u.WalkBlockID(func(blockID BlockID, walker *walker.Walker[BlockID]) {
u.tangle.Storage.BlockMetadata(blockID).Consume(func(blockMetadata *BlockMetadata) {
callback(blockMetadata, walker)
})
}, entryPoints, revisitElements...)
}
// WalkBlockAndMetadata is a generic Tangle walker that executes a custom callback for every visited Block and
// BlockMetadata, starting from the given entry points. It accepts an optional boolean parameter which can be set to
// true if a Block should be visited more than once following different paths. The callback receives a Walker object
// as the last parameter which can be used to control the behavior of the walk similar to how a "Context" is used in
// some parts of the stdlib.
func (u *Utils) WalkBlockAndMetadata(callback func(block *Block, blockMetadata *BlockMetadata, walker *walker.Walker[BlockID]), entryPoints BlockIDs, revisitElements ...bool) {
u.WalkBlockID(func(blockID BlockID, walker *walker.Walker[BlockID]) {
u.tangle.Storage.Block(blockID).Consume(func(block *Block) {
u.tangle.Storage.BlockMetadata(blockID).Consume(func(blockMetadata *BlockMetadata) {
callback(block, blockMetadata, walker)
})
})
}, entryPoints, revisitElements...)
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region structural checks ////////////////////////////////////////////////////////////////////////////////////////////
// checkBookedParents check if block parents are booked and add then to bookedParents. If we find attachmentBlockId in the parents we stop and return true.
func (u *Utils) checkBookedParents(block *Block, attachmentBlockID BlockID, getParents func(*Block) BlockIDs) (bool, BlockIDs) {
bookedParents := NewBlockIDs()
for parentID := range getParents(block) {
var parentBooked bool
u.tangle.Storage.BlockMetadata(parentID).Consume(func(parentMetadata *BlockMetadata) {
parentBooked = parentMetadata.IsBooked()
})
if !parentBooked {
continue
}
// First check all of the parents to avoid unnecessary checks and possible walking.
if attachmentBlockID == parentID {
return true, bookedParents
}
bookedParents.Add(parentID)
}
return false, bookedParents
}
// ApprovingBlockIDs returns the BlockIDs that approve a given Block. It accepts an optional ChildType to
// filter the Children.
func (u *Utils) ApprovingBlockIDs(blockID BlockID, optionalChildType ...ChildType) (approvingBlockIDs BlockIDs) {
approvingBlockIDs = NewBlockIDs()
u.tangle.Storage.Children(blockID, optionalChildType...).Consume(func(child *Child) {
approvingBlockIDs.Add(child.ChildBlockID())
})
return
}
// AllConflictsLiked returns true if all the passed conflicts are liked.
func (u *Utils) AllConflictsLiked(conflictIDs *set.AdvancedSet[utxo.TransactionID]) bool {
for it := conflictIDs.Iterator(); it.HasNext(); {
if !u.tangle.OTVConsensusManager.ConflictLiked(it.Next()) {
return false
}
}
return true
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ComputeIfTransaction computes the given callback if the given blockID contains a transaction.
func (u *Utils) ComputeIfTransaction(blockID BlockID, compute func(utxo.TransactionID)) (computed bool) {
u.tangle.Storage.Block(blockID).Consume(func(block *Block) {
if tx, ok := block.Payload().(utxo.Transaction); ok {
transactionID := tx.ID()
compute(transactionID)
computed = true
}
})
return
}
// FirstAttachment returns the BlockID and timestamp of the first (oldest) attachment of a given transaction.
func (u *Utils) FirstAttachment(transactionID utxo.TransactionID) (oldestAttachmentTime time.Time, oldestAttachmentBlockID BlockID, err error) {
oldestAttachmentTime = time.Unix(0, 0)
oldestAttachmentBlockID = EmptyBlockID
if !u.tangle.Storage.Attachments(transactionID).Consume(func(attachment *Attachment) {
u.tangle.Storage.Block(attachment.BlockID()).Consume(func(block *Block) {
if oldestAttachmentTime.Unix() == 0 || block.IssuingTime().Before(oldestAttachmentTime) {
oldestAttachmentTime = block.IssuingTime()
oldestAttachmentBlockID = block.ID()
}
})
}) {
err = errors.Errorf("could not find any attachments of transaction: %s", transactionID.String())
}
return
}
// ConfirmedConsumer returns the confirmed transactionID consuming the given outputID.
func (u *Utils) ConfirmedConsumer(outputID utxo.OutputID) (consumerID utxo.TransactionID) {
// default to no consumer, i.e. Genesis
consumerID = utxo.EmptyTransactionID
u.tangle.Ledger.Storage.CachedConsumers(outputID).Consume(func(consumer *ledger.Consumer) {
if consumerID != utxo.EmptyTransactionID {
return
}
if u.tangle.ConfirmationOracle.IsTransactionConfirmed(consumer.TransactionID()) {
consumerID = consumer.TransactionID()
}
})
return
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////