github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/events/persist_batch.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 events
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/kaleido-io/firefly/internal/log"
    23  	"github.com/kaleido-io/firefly/pkg/database"
    24  	"github.com/kaleido-io/firefly/pkg/fftypes"
    25  )
    26  
    27  func (em *eventManager) persistBatchFromBroadcast(ctx context.Context /* db TX context*/, batch *fftypes.Batch, onchainHash *fftypes.Bytes32, author string) error {
    28  	l := log.L(ctx)
    29  
    30  	// Verify the author matches
    31  	id, err := em.identity.Resolve(ctx, batch.Author)
    32  	if err != nil {
    33  		l.Errorf("Invalid batch '%s'. Author '%s' cound not be resolved: %s", batch.ID, batch.Author, err)
    34  		return nil // This is not retryable. skip this batch
    35  	}
    36  	if author != id.OnChain {
    37  		l.Errorf("Invalid batch '%s'. Author '%s' does not match transaction submitter '%s'", batch.ID, id.OnChain, author)
    38  		return nil // This is not retryable. skip this batch
    39  	}
    40  
    41  	if !onchainHash.Equals(batch.Hash) {
    42  		l.Errorf("Invalid batch '%s'. Hash in batch '%s' does not match transaction hash '%s'", batch.ID, batch.Hash, onchainHash)
    43  		return nil // This is not retryable. skip this batch
    44  	}
    45  
    46  	return em.persistBatch(ctx, batch)
    47  }
    48  
    49  // persistBatch performs very simple validation on each message/data element (hashes) and either persists
    50  // or discards them. Errors are returned only in the case of database failures, which should be retried.
    51  func (em *eventManager) persistBatch(ctx context.Context /* db TX context*/, batch *fftypes.Batch) error {
    52  	l := log.L(ctx)
    53  	now := fftypes.Now()
    54  
    55  	if batch.ID == nil || batch.Payload.TX.ID == nil {
    56  		l.Errorf("Invalid batch '%s'. Missing ID (%v) or payload ID (%v)", batch.ID, batch.ID, batch.Payload.TX.ID)
    57  		return nil // This is not retryable. skip this batch
    58  	}
    59  
    60  	// Verify the hash calculation
    61  	hash := batch.Payload.Hash()
    62  	if batch.Hash == nil || *batch.Hash != *hash {
    63  		l.Errorf("Invalid batch '%s'. Hash does not match payload. Found=%s Expected=%s", batch.ID, hash, batch.Hash)
    64  		return nil // This is not retryable. skip this batch
    65  	}
    66  
    67  	// Set confirmed on the batch (the messages should not be confirmed at this point - that's the aggregator's job)
    68  	batch.Confirmed = now
    69  
    70  	// Upsert the batch itself, ensuring the hash does not change
    71  	err := em.database.UpsertBatch(ctx, batch, true, false)
    72  	if err != nil {
    73  		if err == database.HashMismatch {
    74  			l.Errorf("Invalid batch '%s'. Batch hash mismatch with existing record", batch.ID)
    75  			return nil // This is not retryable. skip this batch
    76  		}
    77  		l.Errorf("Failed to insert batch '%s': %s", batch.ID, err)
    78  		return err // a peristence failure here is considered retryable (so returned)
    79  	}
    80  
    81  	// Insert the data entries
    82  	for i, data := range batch.Payload.Data {
    83  		if err = em.persistBatchData(ctx, batch, i, data); err != nil {
    84  			return err
    85  		}
    86  	}
    87  
    88  	// Insert the message entries
    89  	for i, msg := range batch.Payload.Messages {
    90  		if err = em.persistBatchMessage(ctx, batch, i, msg); err != nil {
    91  			return err
    92  		}
    93  	}
    94  
    95  	return nil
    96  
    97  }
    98  
    99  func (em *eventManager) persistBatchData(ctx context.Context /* db TX context*/, batch *fftypes.Batch, i int, data *fftypes.Data) error {
   100  	l := log.L(ctx)
   101  	l.Tracef("Batch %s data %d: %+v", batch.ID, i, data)
   102  
   103  	if data == nil {
   104  		l.Errorf("null data entry %d in batch '%s'", i, batch.ID)
   105  		return nil // skip data entry
   106  	}
   107  
   108  	hash := data.Value.Hash()
   109  	if data.Hash == nil || *data.Hash != *hash {
   110  		l.Errorf("Invalid data entry %d in batch '%s'. Hash does not match value. Found=%s Expected=%s", i, batch.ID, hash, data.Hash)
   111  		return nil // skip data entry
   112  	}
   113  
   114  	// Insert the data, ensuring the hash doesn't change
   115  	if err := em.database.UpsertData(ctx, data, true, false); err != nil {
   116  		if err == database.HashMismatch {
   117  			l.Errorf("Invalid data entry %d in batch '%s'. Hash mismatch with existing record with same UUID '%s' Hash=%s", i, batch.ID, data.ID, data.Hash)
   118  			return nil // This is not retryable. skip this data entry
   119  		}
   120  		l.Errorf("Failed to insert data entry %d in batch '%s': %s", i, batch.ID, err)
   121  		return err // a peristence failure here is considered retryable (so returned)
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func (em *eventManager) persistBatchMessage(ctx context.Context /* db TX context*/, batch *fftypes.Batch, i int, msg *fftypes.Message) error {
   128  	l := log.L(ctx)
   129  	l.Tracef("Batch %s message %d: %+v", batch.ID, i, msg)
   130  
   131  	if msg == nil {
   132  		l.Errorf("null message entry %d in batch '%s'", i, batch.ID)
   133  		return nil // skip entry
   134  	}
   135  
   136  	if msg.Header.Author != batch.Author {
   137  		l.Errorf("Mismatched author '%s' on message entry %d in batch '%s'", msg.Header.Author, i, batch.ID)
   138  		return nil // skip entry
   139  	}
   140  
   141  	err := msg.Verify(ctx)
   142  	if err != nil {
   143  		l.Errorf("Invalid message entry %d in batch '%s': %s", i, batch.ID, err)
   144  		return nil // skip message entry
   145  	}
   146  
   147  	// Insert the message, ensuring the hash doesn't change.
   148  	// We do not mark it as confirmed at this point, that's the job of the aggregator.
   149  	if err = em.database.UpsertMessage(ctx, msg, true, false); err != nil {
   150  		if err == database.HashMismatch {
   151  			l.Errorf("Invalid message entry %d in batch '%s'. Hash mismatch with existing record with same UUID '%s' Hash=%s", i, batch.ID, msg.Header.ID, msg.Hash)
   152  			return nil // This is not retryable. skip this data entry
   153  		}
   154  		l.Errorf("Failed to insert message entry %d in batch '%s': %s", i, batch.ID, err)
   155  		return err // a peristence failure here is considered retryable (so returned)
   156  	}
   157  
   158  	return nil
   159  }