package messagefactory import ( "fmt" "sync" "time" "github.com/iotaledger/goshimmer/packages/binary/messagelayer/message" "github.com/iotaledger/goshimmer/packages/binary/messagelayer/payload" "github.com/iotaledger/hive.go/crypto/ed25519" "github.com/iotaledger/hive.go/identity" "github.com/iotaledger/hive.go/kvstore" ) const storeSequenceInterval = 100 // A TipSelector selects two tips, branch and trunk, for a new message to attach to. type TipSelector interface { Tips() (trunk message.Id, branch message.Id) } // A Worker performs the PoW for the provided message in serialized byte form. type Worker interface { DoPOW([]byte) (nonce uint64, err error) } // ZeroWorker is a PoW worker that always returns 0 as the nonce. var ZeroWorker = WorkerFunc(func([]byte) (uint64, error) { return 0, nil }) // MessageFactory acts as a factory to create new messages. type MessageFactory struct { Events *Events sequence *kvstore.Sequence localIdentity *identity.LocalIdentity selector TipSelector worker Worker workerMutex sync.RWMutex issuanceMutex sync.Mutex } // New creates a new message factory. func New(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: newEvents(), sequence: sequence, localIdentity: localIdentity, selector: selector, worker: ZeroWorker, } } // SetWorker sets the PoW worker to be used for the messages. func (m *MessageFactory) SetWorker(worker Worker) { m.workerMutex.Lock() defer m.workerMutex.Unlock() m.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 (m *MessageFactory) IssuePayload(payload payload.Payload) *message.Message { m.issuanceMutex.Lock() defer m.issuanceMutex.Unlock() sequenceNumber, err := m.sequence.Next() if err != nil { m.Events.Error.Trigger(fmt.Errorf("could not create sequence number: %w", err)) return nil } trunkID, branchID := m.selector.Tips() issuingTime := time.Now() issuerPublicKey := m.localIdentity.PublicKey() // do the PoW nonce, err := m.doPOW(trunkID, branchID, issuingTime, issuerPublicKey, sequenceNumber, payload) if err != nil { m.Events.Error.Trigger(fmt.Errorf("pow failed: %w", err)) return nil } // create the signature signature := m.sign(trunkID, branchID, issuingTime, issuerPublicKey, sequenceNumber, payload, nonce) msg := message.New( trunkID, branchID, issuingTime, issuerPublicKey, sequenceNumber, payload, nonce, signature, ) m.Events.MessageConstructed.Trigger(msg) return msg } // Shutdown closes the messageFactory and persists the sequence number. func (m *MessageFactory) Shutdown() { if err := m.sequence.Release(); err != nil { m.Events.Error.Trigger(fmt.Errorf("could not release message sequence number: %w", err)) } } func (m *MessageFactory) doPOW(trunkID message.Id, branchID message.Id, issuingTime time.Time, key ed25519.PublicKey, seq uint64, payload payload.Payload) (uint64, error) { // create a dummy message to simplify marshaling dummy := message.New(trunkID, branchID, issuingTime, key, seq, payload, 0, ed25519.EmptySignature).Bytes() m.workerMutex.RLock() defer m.workerMutex.RUnlock() return m.worker.DoPOW(dummy) } func (m *MessageFactory) sign(trunkID message.Id, branchID message.Id, issuingTime time.Time, key ed25519.PublicKey, seq uint64, payload payload.Payload, nonce uint64) ed25519.Signature { // create a dummy message to simplify marshaling dummy := message.New(trunkID, branchID, issuingTime, key, seq, payload, nonce, ed25519.EmptySignature) dummyBytes := dummy.Bytes() contentLength := len(dummyBytes) - len(dummy.Signature()) return m.localIdentity.Sign(dummyBytes[:contentLength]) } // The TipSelectorFunc type is an adapter to allow the use of ordinary functions as tip selectors. type TipSelectorFunc func() (message.Id, message.Id) // Tips calls f(). func (f TipSelectorFunc) Tips() (message.Id, message.Id) { 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) }