Newer
Older
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
package tangle
import (
"fmt"
"sync"
"time"
"github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/identity"
"github.com/iotaledger/hive.go/kvstore"
)
const storeSequenceInterval = 100
var (
// ZeroWorker is a PoW worker that always returns 0 as the nonce.
ZeroWorker = WorkerFunc(func([]byte) (uint64, error) { return 0, nil })
)
// A TipSelector selects two tips, parent2 and parent1, for a new message to attach to.
type TipSelector interface {
Tips() (parent1 MessageID, parent2 MessageID)
}
// A Worker performs the PoW for the provided message in serialized byte form.
type Worker interface {
DoPOW([]byte) (nonce uint64, err error)
}
// MessageFactory acts as a factory to create new messages.
type MessageFactory struct {
Events *MessageFactoryEvents
sequence *kvstore.Sequence
localIdentity *identity.LocalIdentity
selector TipSelector
worker Worker
workerMutex sync.RWMutex
issuanceMutex sync.Mutex
}
// NewMessageFactory creates a new message factory.
func NewMessageFactory(store kvstore.KVStore, sequenceKey []byte, localIdentity *identity.LocalIdentity, selector TipSelector) *MessageFactory {
sequence, err := kvstore.NewSequence(store, sequenceKey, storeSequenceInterval)
if err != nil {
panic(fmt.Sprintf("could not create message sequence number: %v", err))
}
return &MessageFactory{
Events: newMessageFactoryEvents(),
sequence: sequence,
localIdentity: localIdentity,
selector: selector,
worker: ZeroWorker,
}
}
// SetWorker sets the PoW worker to be used for the messages.
func (f *MessageFactory) SetWorker(worker Worker) {
f.workerMutex.Lock()
defer f.workerMutex.Unlock()
f.worker = worker
}
// IssuePayload creates a new message including sequence number and tip selection and returns it.
// It also triggers the MessageConstructed event once it's done, which is for example used by the plugins to listen for
// messages that shall be attached to the tangle.
func (f *MessageFactory) IssuePayload(p Payload) (*Message, error) {
payloadLen := len(p.Bytes())
if payloadLen > MaxPayloadSize {
err := fmt.Errorf("%w: %d bytes", ErrMaxPayloadSizeExceeded, payloadLen)
f.Events.Error.Trigger(err)
return nil, err
}
f.issuanceMutex.Lock()
defer f.issuanceMutex.Unlock()
sequenceNumber, err := f.sequence.Next()
if err != nil {
err = fmt.Errorf("could not create sequence number: %w", err)
f.Events.Error.Trigger(err)
return nil, err
}
parent1ID, parent2ID := f.selector.Tips()
issuingTime := time.Now()
issuerPublicKey := f.localIdentity.PublicKey()
// do the PoW
nonce, err := f.doPOW(parent1ID, parent2ID, issuingTime, issuerPublicKey, sequenceNumber, p)
if err != nil {
err = fmt.Errorf("pow failed: %w", err)
f.Events.Error.Trigger(err)
return nil, err
}
// create the signature
signature := f.sign(parent1ID, parent2ID, issuingTime, issuerPublicKey, sequenceNumber, p, nonce)
msg := NewMessage(
parent1ID,
parent2ID,
issuingTime,
issuerPublicKey,
sequenceNumber,
p,
nonce,
signature,
)
f.Events.MessageConstructed.Trigger(msg)
return msg, nil
}
// Shutdown closes the MessageFactory and persists the sequence number.
func (f *MessageFactory) Shutdown() {
if err := f.sequence.Release(); err != nil {
f.Events.Error.Trigger(fmt.Errorf("could not release message sequence number: %w", err))
}
}
func (f *MessageFactory) doPOW(parent1ID MessageID, parent2ID MessageID, issuingTime time.Time, key ed25519.PublicKey, seq uint64, payload Payload) (uint64, error) {
// create a dummy message to simplify marshaling
dummy := NewMessage(parent1ID, parent2ID, issuingTime, key, seq, payload, 0, ed25519.EmptySignature).Bytes()
f.workerMutex.RLock()
defer f.workerMutex.RUnlock()
return f.worker.DoPOW(dummy)
}
func (f *MessageFactory) sign(parent1ID MessageID, parent2ID MessageID, issuingTime time.Time, key ed25519.PublicKey, seq uint64, payload Payload, nonce uint64) ed25519.Signature {
// create a dummy message to simplify marshaling
dummy := NewMessage(parent1ID, parent2ID, issuingTime, key, seq, payload, nonce, ed25519.EmptySignature)
dummyBytes := dummy.Bytes()
contentLength := len(dummyBytes) - len(dummy.Signature())
return f.localIdentity.Sign(dummyBytes[:contentLength])
}
// The TipSelectorFunc type is an adapter to allow the use of ordinary functions as tip selectors.
type TipSelectorFunc func() (MessageID, MessageID)
// Tips calls f().
func (f TipSelectorFunc) Tips() (MessageID, MessageID) {
return f()
}
// 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(msg).
func (f WorkerFunc) DoPOW(msg []byte) (uint64, error) {
return f(msg)
}