/
blockfactory.go
374 lines (300 loc) · 14.2 KB
/
blockfactory.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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
package tangleold
import (
"fmt"
"sync"
"time"
"github.com/cockroachdb/errors"
"github.com/iotaledger/hive.go/core/crypto/ed25519"
"github.com/iotaledger/hive.go/core/identity"
"github.com/iotaledger/hive.go/core/kvstore"
"github.com/iotaledger/goshimmer/packages/core/epoch"
"github.com/iotaledger/goshimmer/packages/core/ledger/utxo"
"github.com/iotaledger/goshimmer/packages/core/tangleold/payload"
"github.com/iotaledger/goshimmer/packages/node/clock"
)
const storeSequenceInterval = 100
// region BlockFactory ///////////////////////////////////////////////////////////////////////////////////////////////
// BlockFactory acts as a factory to create new blocks.
type BlockFactory struct {
Events *BlockFactoryEvents
tangle *Tangle
sequence *kvstore.Sequence
localIdentity *identity.LocalIdentity
selector TipSelector
referencesFunc ReferencesFunc
ReferenceProvider *ReferenceProvider
powTimeout time.Duration
worker Worker
workerMutex sync.RWMutex
}
// NewBlockFactory creates a new block factory.
func NewBlockFactory(tangle *Tangle, selector TipSelector, referencesFunc ...ReferencesFunc) *BlockFactory {
sequence, err := kvstore.NewSequence(tangle.Options.Store, []byte(DBSequenceNumber), storeSequenceInterval)
if err != nil {
panic(fmt.Sprintf("could not create block sequence number: %v", err))
}
referenceProvider := NewReferenceProvider(tangle)
f := referenceProvider.References
if len(referencesFunc) != 0 {
f = referencesFunc[0]
}
return &BlockFactory{
Events: NewBlockFactoryEvents(),
tangle: tangle,
sequence: sequence,
localIdentity: tangle.Options.Identity,
selector: selector,
referencesFunc: f,
ReferenceProvider: referenceProvider,
worker: ZeroWorker,
powTimeout: 0 * time.Second,
}
}
// SetWorker sets the PoW worker to be used for the blocks.
func (f *BlockFactory) SetWorker(worker Worker) {
f.workerMutex.Lock()
defer f.workerMutex.Unlock()
f.worker = worker
}
// SetTimeout sets the timeout for PoW.
func (f *BlockFactory) SetTimeout(timeout time.Duration) {
f.powTimeout = timeout
}
// IssuePayload creates a new block including sequence number and tip selection and returns it.
func (f *BlockFactory) IssuePayload(p payload.Payload, parentsCount ...int) (*Block, error) {
blk, err := f.issuePayload(p, nil, parentsCount...)
if err != nil {
f.Events.Error.Trigger(errors.Errorf("block could not be issued: %w", err))
return nil, err
}
f.Events.BlockConstructed.Trigger(&BlockConstructedEvent{blk})
return blk, nil
}
// IssuePayloadWithReferences creates a new block with the references submit.
func (f *BlockFactory) IssuePayloadWithReferences(p payload.Payload, references ParentBlockIDs, parentsCount ...int) (*Block, error) {
blk, err := f.issuePayload(p, references, parentsCount...)
if err != nil {
f.Events.Error.Trigger(errors.Errorf("block could not be issued: %w", err))
return nil, err
}
f.Events.BlockConstructed.Trigger(&BlockConstructedEvent{blk})
return blk, nil
}
// issuePayload create a new block. If there are any supplied references, it uses them. Otherwise, uses tip selection.
// It also triggers the BlockConstructed event once it's done, which is for example used by the plugins to listen for
// blocks that shall be attached to the tangle.
func (f *BlockFactory) issuePayload(p payload.Payload, references ParentBlockIDs, parentsCountOpt ...int) (*Block, error) {
parentsCount := 2
if len(parentsCountOpt) > 0 {
parentsCount = parentsCountOpt[0]
}
payloadBytes, err := p.Bytes()
if err != nil {
return nil, errors.Errorf("could not serialize payload: %w", err)
}
payloadLen := len(payloadBytes)
if payloadLen > payload.MaxSize {
return nil, errors.Errorf("maximum payload size of %d bytes exceeded", payloadLen)
}
sequenceNumber, err := f.sequence.Next()
if err != nil {
return nil, errors.Errorf("could not create sequence number: %w", err)
}
issuerPublicKey := f.localIdentity.PublicKey()
epochCommitment, lastConfirmedEpochIndex, epochCommitmentErr := f.tangle.Options.CommitmentFunc()
if epochCommitmentErr != nil {
err = errors.Errorf("cannot retrieve epoch commitment: %w", epochCommitmentErr)
f.Events.Error.Trigger(err)
return nil, err
}
// select tips, perform PoW and prepare references
references, nonce, issuingTime, err := f.selectTipsAndPerformPoW(p, references, parentsCount, issuerPublicKey, sequenceNumber, lastConfirmedEpochIndex, epochCommitment)
if err != nil {
return nil, errors.Errorf("could not select tips and perform PoW: %w", err)
}
// create the signature
signature, err := f.sign(references, issuingTime, issuerPublicKey, sequenceNumber, p, nonce, lastConfirmedEpochIndex, epochCommitment)
if err != nil {
return nil, errors.Errorf("signing failed: %w", err)
}
blk, err := NewBlockWithValidation(
references,
issuingTime,
issuerPublicKey,
sequenceNumber,
p,
nonce,
signature,
lastConfirmedEpochIndex,
epochCommitment,
)
if err != nil {
return nil, errors.Errorf("there is a problem with the block syntax: %w", err)
}
_ = blk.DetermineID()
return blk, nil
}
func (f *BlockFactory) selectTipsAndPerformPoW(p payload.Payload, providedReferences ParentBlockIDs, parentsCount int, issuerPublicKey ed25519.PublicKey, sequenceNumber uint64, lastConfirmedEpoch epoch.Index, epochCommittment *epoch.ECRecord) (references ParentBlockIDs, nonce uint64, issuingTime time.Time, err error) {
// Perform PoW with given information if there are references provided.
if !providedReferences.IsEmpty() {
issuingTime = f.getIssuingTime(providedReferences[StrongParentType])
nonce, err = f.doPOW(providedReferences, issuingTime, issuerPublicKey, sequenceNumber, p, lastConfirmedEpoch, epochCommittment)
if err != nil {
return providedReferences, nonce, issuingTime, errors.Errorf("PoW failed: %w", err)
}
return providedReferences, nonce, issuingTime, nil
}
// TODO: once we get rid of PoW we need to set another timeout here that allows to specify for how long we try to select tips if there are no valid references.
// This in turn should remove the invalid references from the tips bit by bit until there are valid strong parents again.
startTime := time.Now()
for run := true; run; run = err != nil && time.Since(startTime) < f.powTimeout {
strongParents := f.tips(p, parentsCount)
issuingTime = f.getIssuingTime(strongParents)
references, err = f.referencesFunc(p, strongParents, issuingTime)
// If none of the strong parents are possible references, we have to try again.
if err != nil {
f.Events.Error.Trigger(errors.Errorf("references could not be created: %w", err))
continue
}
// Make sure that there's no duplicate between strong and weak parents.
for strongParent := range references[StrongParentType] {
delete(references[WeakParentType], strongParent)
}
// fill up weak references with weak references to liked missing conflicts
if _, exists := references[WeakParentType]; !exists {
references[WeakParentType] = NewBlockIDs()
}
references[WeakParentType].AddAll(f.ReferenceProvider.ReferencesToMissingConflicts(issuingTime, MaxParentsCount-len(references[WeakParentType])))
if len(references[WeakParentType]) == 0 {
delete(references, WeakParentType)
}
nonce, err = f.doPOW(references, issuingTime, issuerPublicKey, sequenceNumber, p, lastConfirmedEpoch, epochCommittment)
}
if err != nil {
return nil, 0, time.Time{}, errors.Errorf("pow failed: %w", err)
}
return references, nonce, issuingTime, nil
}
func (f *BlockFactory) getIssuingTime(parents BlockIDs) time.Time {
issuingTime := clock.SyncedTime()
// due to the ParentAge check we must ensure that we set the right issuing time.
for parent := range parents {
f.tangle.Storage.Block(parent).Consume(func(blk *Block) {
if blk.ID() != EmptyBlockID && !blk.IssuingTime().Before(issuingTime) {
issuingTime = blk.IssuingTime()
}
})
}
return issuingTime
}
func (f *BlockFactory) tips(p payload.Payload, parentsCount int) (parents BlockIDs) {
parents = f.selector.Tips(p, parentsCount)
tx, ok := p.(utxo.Transaction)
if !ok {
return parents
}
// If the block is issuing a transaction and is a double spend, we add it in parallel to the earliest attachment
// to prevent a double spend from being issued in its past cone.
if conflictingTransactions := f.tangle.Ledger.Utils.ConflictingTransactions(tx.ID()); !conflictingTransactions.IsEmpty() {
if earliestAttachment := f.EarliestAttachment(conflictingTransactions); earliestAttachment != nil {
return earliestAttachment.ParentsByType(StrongParentType)
}
}
return parents
}
func (f *BlockFactory) EarliestAttachment(transactionIDs utxo.TransactionIDs, earliestAttachmentMustBeBooked ...bool) (earliestAttachment *Block) {
var earliestIssuingTime time.Time
for it := transactionIDs.Iterator(); it.HasNext(); {
f.tangle.Storage.Attachments(it.Next()).Consume(func(attachment *Attachment) {
f.tangle.Storage.Block(attachment.BlockID()).Consume(func(block *Block) {
f.tangle.Storage.BlockMetadata(attachment.BlockID()).Consume(func(blockMetadata *BlockMetadata) {
if ((len(earliestAttachmentMustBeBooked) > 0 && !earliestAttachmentMustBeBooked[0]) || blockMetadata.IsBooked()) &&
(earliestAttachment == nil || block.IssuingTime().Before(earliestIssuingTime)) {
earliestAttachment = block
earliestIssuingTime = block.IssuingTime()
}
})
})
})
}
return earliestAttachment
}
func (f *BlockFactory) LatestAttachment(transactionID utxo.TransactionID) (latestAttachment *Block) {
var latestIssuingTime time.Time
f.tangle.Storage.Attachments(transactionID).Consume(func(attachment *Attachment) {
f.tangle.Storage.Block(attachment.BlockID()).Consume(func(block *Block) {
f.tangle.Storage.BlockMetadata(attachment.BlockID()).Consume(func(blockMetadata *BlockMetadata) {
if blockMetadata.IsBooked() && block.IssuingTime().After(latestIssuingTime) {
latestAttachment = block
latestIssuingTime = block.IssuingTime()
}
})
})
})
return latestAttachment
}
// Shutdown closes the BlockFactory and persists the sequence number.
func (f *BlockFactory) Shutdown() {
if err := f.sequence.Release(); err != nil {
f.Events.Error.Trigger(fmt.Errorf("could not release block sequence number: %w", err))
}
}
// doPOW performs pow on the block and returns a nonce.
func (f *BlockFactory) doPOW(references ParentBlockIDs, issuingTime time.Time, key ed25519.PublicKey, seq uint64, blockPayload payload.Payload, latestConfirmedEpoch epoch.Index, epochCommitment *epoch.ECRecord) (uint64, error) {
// create a dummy block to simplify marshaling
block := NewBlock(references, issuingTime, key, seq, blockPayload, 0, ed25519.EmptySignature, latestConfirmedEpoch, epochCommitment)
dummy, err := block.Bytes()
if err != nil {
return 0, err
}
f.workerMutex.RLock()
defer f.workerMutex.RUnlock()
return f.worker.DoPOW(dummy)
}
func (f *BlockFactory) sign(references ParentBlockIDs, issuingTime time.Time, key ed25519.PublicKey, seq uint64, blockPayload payload.Payload, nonce uint64, latestConfirmedEpoch epoch.Index, epochCommitment *epoch.ECRecord) (ed25519.Signature, error) {
// create a dummy block to simplify marshaling
dummy := NewBlock(references, issuingTime, key, seq, blockPayload, nonce, ed25519.EmptySignature, latestConfirmedEpoch, epochCommitment)
dummyBytes, err := dummy.Bytes()
if err != nil {
return ed25519.EmptySignature, err
}
contentLength := len(dummyBytes) - len(dummy.Signature())
return f.localIdentity.Sign(dummyBytes[:contentLength]), nil
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region TipSelector //////////////////////////////////////////////////////////////////////////////////////////////////
// A TipSelector selects two tips, parent2 and parent1, for a new block to attach to.
type TipSelector interface {
Tips(p payload.Payload, countParents int) (parents BlockIDs)
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region TipSelectorFunc //////////////////////////////////////////////////////////////////////////////////////////////
// The TipSelectorFunc type is an adapter to allow the use of ordinary functions as tip selectors.
type TipSelectorFunc func(p payload.Payload, countParents int) (parents BlockIDs)
// Tips calls f().
func (f TipSelectorFunc) Tips(p payload.Payload, countParents int) (parents BlockIDs) {
return f(p, countParents)
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region Worker ///////////////////////////////////////////////////////////////////////////////////////////////////////
// A Worker performs the PoW for the provided block in serialized byte form.
type Worker interface {
DoPOW([]byte) (nonce uint64, err error)
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region WorkerFunc ///////////////////////////////////////////////////////////////////////////////////////////////////
// The WorkerFunc type is an adapter to allow the use of ordinary functions as a PoW performer.
type WorkerFunc func([]byte) (uint64, error)
// DoPOW calls f(blk).
func (f WorkerFunc) DoPOW(blk []byte) (uint64, error) {
return f(blk)
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region ZeroWorker ///////////////////////////////////////////////////////////////////////////////////////////////////
// ZeroWorker is a PoW worker that always returns 0 as the nonce.
var ZeroWorker = WorkerFunc(func([]byte) (uint64, error) { return 0, nil })
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region PrepareLikeReferences ///////////////////////////////////////////////////////////////////////////////////////////////////
// ReferencesFunc is a function type that returns like references a given set of parents of a Block.
type ReferencesFunc func(payload payload.Payload, strongParents BlockIDs, issuingTime time.Time) (references ParentBlockIDs, err error)
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////