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  }