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  }