github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/pkg/fftypes/message.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 fftypes 18 19 import ( 20 "context" 21 "crypto/sha256" 22 "encoding/json" 23 24 "github.com/kaleido-io/firefly/internal/i18n" 25 ) 26 27 const ( 28 // DefaultTopic will be set as the topic of any messages set without a topic 29 DefaultTopic = "default" 30 ) 31 32 // MessageType is the fundamental type of a message 33 type MessageType = LowerCasedType 34 35 const ( 36 // MessageTypeDefinition is a message broadcasting a definition of a system type, pre-defined by firefly (namespaces, members, data definitions, etc.) 37 MessageTypeDefinition MessageType = "definition" 38 // MessageTypeBroadcast is a broadcast message, meaning it is intended to be visible by all parties in the network 39 MessageTypeBroadcast MessageType = "broadcast" 40 // MessageTypePrivate is a private message, meaning it is only sent explicitly to individual parties in the network 41 MessageTypePrivate MessageType = "private" 42 // MessageTypeGroupInit is a special private message that contains the definition of the group 43 MessageTypeGroupInit MessageType = "groupinit" 44 ) 45 46 // MessageHeader contains all fields that contribute to the hash 47 // The order of the serialization mut not change, once released 48 type MessageHeader struct { 49 ID *UUID `json:"id,omitempty"` 50 CID *UUID `json:"cid,omitempty"` 51 Type MessageType `json:"type"` 52 TxType TransactionType `json:"txtype,omitempty"` 53 Author string `json:"author,omitempty"` 54 Created *FFTime `json:"created,omitempty"` 55 Namespace string `json:"namespace,omitempty"` 56 Group *Bytes32 `json:"group,omitempty"` 57 Topics FFNameArray `json:"topic,omitempty"` 58 Tag string `json:"tag,omitempty"` 59 DataHash *Bytes32 `json:"datahash,omitempty"` 60 } 61 62 // Message is the envelope by which coordinated data exchange can happen between parties in the network 63 // Data is passed by reference in these messages, and a chain of hashes covering the data and the 64 // details of the message, provides a verification against tampering. 65 type Message struct { 66 Header MessageHeader `json:"header"` 67 Hash *Bytes32 `json:"hash,omitempty"` 68 BatchID *UUID `json:"batchID,omitempty"` 69 Confirmed *FFTime `json:"confirmed,omitempty"` 70 Data DataRefs `json:"data"` 71 Pins FFNameArray `json:"pins,omitempty"` 72 Local bool `json:"local,omitempty"` 73 Sequence int64 `json:"-"` // Local database sequence used internally for batch assembly 74 } 75 76 // MessageInput allows API users to submit values in-line in the payload submitted, which 77 // will be broken out and stored separately during the call. 78 type MessageInput struct { 79 Message 80 InputData InputData `json:"data"` 81 Group *InputGroup `json:"group,omitempty"` 82 } 83 84 // InputGroup declares a group in-line for auotmatic resolution, without having to define a group up-front 85 type InputGroup struct { 86 Name string `json:"name,omitempty"` 87 Ledger *UUID `json:"ledger,omitempty"` 88 Members []MemberInput `json:"members"` 89 } 90 91 // InputData is an array of data references or values 92 type InputData []*DataRefOrValue 93 94 // DataRefOrValue allows a value to be specified in-line in the data array of an input 95 // message, avoiding the need for a multiple API calls. 96 type DataRefOrValue struct { 97 DataRef 98 99 Validator ValidatorType `json:"validator,omitempty"` 100 Datatype *DatatypeRef `json:"datatype,omitempty"` 101 Value Byteable `json:"value,omitempty"` 102 } 103 104 // MessageRef is a lightweight data structure that can be used to refer to a message 105 type MessageRef struct { 106 ID *UUID `json:"id,omitempty"` 107 Sequence int64 `json:"sequence,omitempty"` 108 Hash *Bytes32 `json:"hash,omitempty"` 109 } 110 111 func (h *MessageHeader) Hash() *Bytes32 { 112 b, _ := json.Marshal(&h) 113 var b32 Bytes32 = sha256.Sum256(b) 114 return &b32 115 } 116 117 func (m *Message) Seal(ctx context.Context) (err error) { 118 if len(m.Header.Topics) == 0 { 119 m.Header.Topics = []string{DefaultTopic} 120 } 121 if m.Header.ID == nil { 122 m.Header.ID = NewUUID() 123 } 124 if m.Header.Created == nil { 125 m.Header.Created = Now() 126 } 127 m.Confirmed = nil 128 if m.Data == nil { 129 m.Data = DataRefs{} 130 } 131 err = m.DupDataCheck(ctx) 132 if err == nil { 133 m.Header.DataHash = m.Data.Hash() 134 m.Hash = m.Header.Hash() 135 } 136 return err 137 } 138 139 func (m *Message) DupDataCheck(ctx context.Context) (err error) { 140 dupCheck := make(map[string]bool) 141 for i, d := range m.Data { 142 if d.ID == nil || d.Hash == nil { 143 return i18n.NewError(ctx, i18n.MsgNilDataReferenceSealFail, i) 144 } 145 if dupCheck[d.ID.String()] || dupCheck[d.Hash.String()] { 146 return i18n.NewError(ctx, i18n.MsgDupDataReferenceSealFail, i) 147 } 148 dupCheck[d.ID.String()] = true 149 dupCheck[d.Hash.String()] = true 150 } 151 return nil 152 } 153 154 func (m *Message) Verify(ctx context.Context) error { 155 if err := m.Header.Topics.Validate(ctx, "header.topics"); err != nil { 156 return err 157 } 158 if m.Header.Tag != "" { 159 if err := ValidateFFNameField(ctx, m.Header.Tag, "header.tag"); err != nil { 160 return err 161 } 162 } 163 err := m.DupDataCheck(ctx) 164 if err != nil { 165 return err 166 } 167 if m.Hash == nil || m.Header.DataHash == nil { 168 return i18n.NewError(ctx, i18n.MsgVerifyFailedNilHashes) 169 } 170 headerHash := m.Header.Hash() 171 dataHash := m.Data.Hash() 172 if *m.Hash != *headerHash || *m.Header.DataHash != *dataHash { 173 return i18n.NewError(ctx, i18n.MsgVerifyFailedInvalidHashes, m.Hash.String(), headerHash.String(), m.Header.DataHash.String(), dataHash.String()) 174 } 175 return nil 176 } 177 178 func (m *Message) LocalSequence() int64 { 179 return m.Sequence 180 }