github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/privatemessaging/privatemessaging.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 privatemessaging 18 19 import ( 20 "context" 21 "encoding/json" 22 23 "github.com/kaleido-io/firefly/internal/batch" 24 "github.com/kaleido-io/firefly/internal/config" 25 "github.com/kaleido-io/firefly/internal/data" 26 "github.com/kaleido-io/firefly/internal/i18n" 27 "github.com/kaleido-io/firefly/internal/log" 28 "github.com/kaleido-io/firefly/internal/retry" 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/karlseguin/ccache" 35 ) 36 37 type Manager interface { 38 GroupManager 39 40 Start() error 41 SendMessage(ctx context.Context, ns string, in *fftypes.MessageInput) (out *fftypes.Message, err error) 42 } 43 44 type privateMessaging struct { 45 groupManager 46 47 ctx context.Context 48 database database.Plugin 49 identity identity.Plugin 50 exchange dataexchange.Plugin 51 blockchain blockchain.Plugin 52 batch batch.Manager 53 data data.Manager 54 retry retry.Retry 55 localNodeName string 56 localOrgIdentity string 57 opCorrelationRetries int 58 } 59 60 func NewPrivateMessaging(ctx context.Context, di database.Plugin, ii identity.Plugin, dx dataexchange.Plugin, bi blockchain.Plugin, ba batch.Manager, dm data.Manager) (Manager, error) { 61 if di == nil || ii == nil || dx == nil || bi == nil || ba == nil || dm == nil { 62 return nil, i18n.NewError(ctx, i18n.MsgInitializationNilDepError) 63 } 64 65 pm := &privateMessaging{ 66 ctx: ctx, 67 database: di, 68 identity: ii, 69 exchange: dx, 70 blockchain: bi, 71 batch: ba, 72 data: dm, 73 localNodeName: config.GetString(config.NodeName), 74 localOrgIdentity: config.GetString(config.OrgIdentity), 75 groupManager: groupManager{ 76 database: di, 77 data: dm, 78 groupCacheTTL: config.GetDuration(config.GroupCacheTTL), 79 }, 80 retry: retry.Retry{ 81 InitialDelay: config.GetDuration(config.PrivateMessagingRetryInitDelay), 82 MaximumDelay: config.GetDuration(config.PrivateMessagingRetryMaxDelay), 83 Factor: config.GetFloat64(config.PrivateMessagingRetryFactor), 84 }, 85 opCorrelationRetries: config.GetInt(config.PrivateMessagingOpCorrelationRetries), 86 } 87 pm.groupManager.groupCache = ccache.New( 88 // We use a LRU cache with a size-aware max 89 ccache.Configure(). 90 MaxSize(config.GetByteSize(config.GroupCacheSize)), 91 ) 92 93 bo := batch.Options{ 94 BatchMaxSize: config.GetUint(config.PrivateMessagingBatchSize), 95 BatchTimeout: config.GetDuration(config.PrivateMessagingBatchTimeout), 96 DisposeTimeout: config.GetDuration(config.PrivateMessagingBatchAgentTimeout), 97 } 98 99 ba.RegisterDispatcher([]fftypes.MessageType{ 100 fftypes.MessageTypeGroupInit, 101 fftypes.MessageTypePrivate, 102 }, pm.dispatchBatch, bo) 103 104 return pm, nil 105 } 106 107 func (pm *privateMessaging) Start() error { 108 return pm.exchange.Start() 109 } 110 111 func (pm *privateMessaging) dispatchBatch(ctx context.Context, batch *fftypes.Batch, contexts []*fftypes.Bytes32) error { 112 113 // Serialize the full payload, which has already been sealed for us by the BatchManager 114 payload, err := json.Marshal(batch) 115 if err != nil { 116 return i18n.WrapError(ctx, err, i18n.MsgSerializationFailed) 117 } 118 119 // Retrieve the group 120 nodes, err := pm.groupManager.getGroupNodes(ctx, batch.Group) 121 if err != nil { 122 return err 123 } 124 125 return pm.database.RunAsGroup(ctx, func(ctx context.Context) error { 126 return pm.sendAndSubmitBatch(ctx, batch, nodes, payload, contexts) 127 }) 128 } 129 130 func (pm *privateMessaging) sendAndSubmitBatch(ctx context.Context, batch *fftypes.Batch, nodes []*fftypes.Node, payload fftypes.Byteable, contexts []*fftypes.Bytes32) (err error) { 131 l := log.L(ctx) 132 133 id, err := pm.identity.Resolve(ctx, batch.Author) 134 if err == nil { 135 err = pm.blockchain.VerifyIdentitySyntax(ctx, id) 136 } 137 if err != nil { 138 log.L(ctx).Errorf("Invalid signing identity '%s': %s", batch.Author, err) 139 return err 140 } 141 142 // Write it to the dataexchange for each member 143 for i, node := range nodes { 144 l.Infof("Sending batch %s:%s to group=%s node=%s (%d/%d)", batch.Namespace, batch.ID, batch.Group, node.ID, i+1, len(nodes)) 145 146 trackingID, err := pm.exchange.SendMessage(ctx, node, payload) 147 if err != nil { 148 return err 149 } 150 151 op := fftypes.NewTXOperation( 152 pm.exchange, 153 batch.Payload.TX.ID, 154 trackingID, 155 fftypes.OpTypeDataExchangeBatchSend, 156 fftypes.OpStatusPending, 157 node.ID.String()) 158 if err = pm.database.UpsertOperation(ctx, op, false); err != nil { 159 return err 160 } 161 162 } 163 164 return pm.writeTransaction(ctx, id, batch, contexts) 165 } 166 167 func (pm *privateMessaging) writeTransaction(ctx context.Context, signingID *fftypes.Identity, batch *fftypes.Batch, contexts []*fftypes.Bytes32) error { 168 169 tx := &fftypes.Transaction{ 170 ID: batch.Payload.TX.ID, 171 Subject: fftypes.TransactionSubject{ 172 Type: fftypes.TransactionTypeBatchPin, 173 Signer: signingID.OnChain, 174 Namespace: batch.Namespace, 175 Reference: batch.ID, 176 }, 177 Created: fftypes.Now(), 178 Status: fftypes.OpStatusPending, 179 } 180 tx.Hash = tx.Subject.Hash() 181 err := pm.database.UpsertTransaction(ctx, tx, true, false /* should be new, or idempotent replay */) 182 if err != nil { 183 return err 184 } 185 186 // Write the batch pin to the blockchain 187 blockchainTrackingID, err := pm.blockchain.SubmitBatchPin(ctx, nil, signingID, &blockchain.BatchPin{ 188 Namespace: batch.Namespace, 189 TransactionID: batch.Payload.TX.ID, 190 BatchID: batch.ID, 191 BatchPaylodRef: batch.PayloadRef, 192 BatchHash: batch.Hash, 193 Contexts: contexts, 194 }) 195 if err != nil { 196 return err 197 } 198 199 // The pending blockchain transaction 200 op := fftypes.NewTXOperation( 201 pm.blockchain, 202 batch.Payload.TX.ID, 203 blockchainTrackingID, 204 fftypes.OpTypeBlockchainBatchPin, 205 fftypes.OpStatusPending, 206 "") 207 208 return pm.database.UpsertOperation(ctx, op, false) 209 }