github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/broadcast/manager.go (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package broadcast 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 24 "github.com/kaleido-io/firefly/internal/batch" 25 "github.com/kaleido-io/firefly/internal/config" 26 "github.com/kaleido-io/firefly/internal/data" 27 "github.com/kaleido-io/firefly/internal/i18n" 28 "github.com/kaleido-io/firefly/internal/log" 29 "github.com/kaleido-io/firefly/pkg/blockchain" 30 "github.com/kaleido-io/firefly/pkg/database" 31 "github.com/kaleido-io/firefly/pkg/dataexchange" 32 "github.com/kaleido-io/firefly/pkg/fftypes" 33 "github.com/kaleido-io/firefly/pkg/identity" 34 "github.com/kaleido-io/firefly/pkg/publicstorage" 35 ) 36 37 type Manager interface { 38 BroadcastDatatype(ctx context.Context, ns string, datatype *fftypes.Datatype) (msg *fftypes.Message, err error) 39 BroadcastNamespace(ctx context.Context, ns *fftypes.Namespace) (msg *fftypes.Message, err error) 40 BroadcastDefinition(ctx context.Context, def fftypes.Definition, signingIdentity *fftypes.Identity, tag fftypes.SystemTag) (msg *fftypes.Message, err error) 41 BroadcastMessage(ctx context.Context, ns string, in *fftypes.MessageInput) (out *fftypes.Message, err error) 42 GetNodeSigningIdentity(ctx context.Context) (*fftypes.Identity, error) 43 HandleSystemBroadcast(ctx context.Context, msg *fftypes.Message, data []*fftypes.Data) (valid bool, err error) 44 Start() error 45 WaitStop() 46 } 47 48 type broadcastManager struct { 49 ctx context.Context 50 database database.Plugin 51 identity identity.Plugin 52 data data.Manager 53 blockchain blockchain.Plugin 54 exchange dataexchange.Plugin 55 publicstorage publicstorage.Plugin 56 batch batch.Manager 57 } 58 59 func NewBroadcastManager(ctx context.Context, di database.Plugin, ii identity.Plugin, dm data.Manager, bi blockchain.Plugin, dx dataexchange.Plugin, pi publicstorage.Plugin, ba batch.Manager) (Manager, error) { 60 if di == nil || ii == nil || dm == nil || bi == nil || dx == nil || pi == nil || ba == nil { 61 return nil, i18n.NewError(ctx, i18n.MsgInitializationNilDepError) 62 } 63 bm := &broadcastManager{ 64 ctx: ctx, 65 database: di, 66 identity: ii, 67 data: dm, 68 blockchain: bi, 69 exchange: dx, 70 publicstorage: pi, 71 batch: ba, 72 } 73 bo := batch.Options{ 74 BatchMaxSize: config.GetUint(config.BroadcastBatchSize), 75 BatchTimeout: config.GetDuration(config.BroadcastBatchTimeout), 76 DisposeTimeout: config.GetDuration(config.BroadcastBatchAgentTimeout), 77 } 78 ba.RegisterDispatcher([]fftypes.MessageType{ 79 fftypes.MessageTypeBroadcast, 80 fftypes.MessageTypeDefinition, 81 }, bm.dispatchBatch, bo) 82 return bm, nil 83 } 84 85 func (bm *broadcastManager) GetNodeSigningIdentity(ctx context.Context) (*fftypes.Identity, error) { 86 orgIdentity := config.GetString(config.OrgIdentity) 87 id, err := bm.identity.Resolve(ctx, orgIdentity) 88 if err != nil { 89 return nil, err 90 } 91 return id, nil 92 } 93 94 func (bm *broadcastManager) dispatchBatch(ctx context.Context, batch *fftypes.Batch, pins []*fftypes.Bytes32) error { 95 96 // Serialize the full payload, which has already been sealed for us by the BatchManager 97 payload, err := json.Marshal(batch) 98 if err != nil { 99 return i18n.WrapError(ctx, err, i18n.MsgSerializationFailed) 100 } 101 102 // Write it to IPFS to get a payload reference hash (might not be the sha256 data hash). 103 // The payload ref will be persisted back to the batch, as well as being used in the TX 104 var publicstorageID string 105 batch.PayloadRef, publicstorageID, err = bm.publicstorage.PublishData(ctx, bytes.NewReader(payload)) 106 if err != nil { 107 return err 108 } 109 110 return bm.database.RunAsGroup(ctx, func(ctx context.Context) error { 111 return bm.submitTXAndUpdateDB(ctx, batch, pins, publicstorageID) 112 }) 113 } 114 115 func (bm *broadcastManager) submitTXAndUpdateDB(ctx context.Context, batch *fftypes.Batch, contexts []*fftypes.Bytes32, publicstorageID string) error { 116 117 id, err := bm.identity.Resolve(ctx, batch.Author) 118 if err == nil { 119 err = bm.blockchain.VerifyIdentitySyntax(ctx, id) 120 } 121 if err != nil { 122 log.L(ctx).Errorf("Invalid signing identity '%s': %s", batch.Author, err) 123 return err 124 } 125 126 tx := &fftypes.Transaction{ 127 ID: batch.Payload.TX.ID, 128 Subject: fftypes.TransactionSubject{ 129 Type: fftypes.TransactionTypeBatchPin, 130 Namespace: batch.Namespace, 131 Signer: id.OnChain, // The transaction records on the on-chain identity 132 Reference: batch.ID, 133 }, 134 Created: fftypes.Now(), 135 Status: fftypes.OpStatusPending, 136 } 137 tx.Hash = tx.Subject.Hash() 138 err = bm.database.UpsertTransaction(ctx, tx, true, false /* should be new, or idempotent replay */) 139 if err != nil { 140 return err 141 } 142 143 // Update the batch to store the payloadRef 144 err = bm.database.UpdateBatch(ctx, batch.ID, database.BatchQueryFactory.NewUpdate(ctx).Set("payloadref", batch.PayloadRef)) 145 if err != nil { 146 return err 147 } 148 149 // Write the batch pin to the blockchain 150 blockchainTrackingID, err := bm.blockchain.SubmitBatchPin(ctx, nil, id, &blockchain.BatchPin{ 151 Namespace: batch.Namespace, 152 TransactionID: batch.Payload.TX.ID, 153 BatchID: batch.ID, 154 BatchHash: batch.Hash, 155 BatchPaylodRef: batch.PayloadRef, 156 Contexts: contexts, 157 }) 158 if err != nil { 159 return err 160 } 161 162 // The pending blockchain transaction 163 op := fftypes.NewTXOperation( 164 bm.blockchain, 165 batch.Payload.TX.ID, 166 blockchainTrackingID, 167 fftypes.OpTypeBlockchainBatchPin, 168 fftypes.OpStatusPending, 169 "") 170 if err := bm.database.UpsertOperation(ctx, op, false); err != nil { 171 return err 172 } 173 174 // The completed PublicStorage upload 175 op = fftypes.NewTXOperation( 176 bm.publicstorage, 177 batch.Payload.TX.ID, 178 publicstorageID, 179 fftypes.OpTypePublicStorageBatchBroadcast, 180 fftypes.OpStatusSucceeded, // Note we performed the action synchronously above 181 "") 182 return bm.database.UpsertOperation(ctx, op, false) 183 } 184 185 func (bm *broadcastManager) broadcastMessageCommon(ctx context.Context, msg *fftypes.Message) (err error) { 186 187 // Seal the message 188 if err = msg.Seal(ctx); err != nil { 189 return err 190 } 191 192 // Store the message - this asynchronously triggers the next step in process 193 return bm.database.InsertMessageLocal(ctx, msg) 194 } 195 196 func (bm *broadcastManager) Start() error { 197 return nil 198 } 199 200 func (bm *broadcastManager) WaitStop() { 201 // No go routines 202 }