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  }