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 }