github.com/ewagmig/fabric@v2.1.1+incompatible/core/tx/endorser/parser.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package endorsertx
     8  
     9  import (
    10  	"regexp"
    11  
    12  	"github.com/hyperledger/fabric-protos-go/peer"
    13  
    14  	"github.com/hyperledger/fabric/pkg/tx"
    15  	"github.com/hyperledger/fabric/protoutil"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  var (
    20  	// ChannelAllowedChars tracks the permitted characters for a channel name
    21  	ChannelAllowedChars = "[a-z][a-z0-9.-]*"
    22  	// MaxLength tracks the maximum length of a channel name
    23  	MaxLength = 249
    24  )
    25  
    26  // EndorserTx represents a parsed common.Envelope protobuf
    27  type EndorserTx struct {
    28  	ComputedTxID string
    29  	ChannelID    string
    30  	ChaincodeID  *peer.ChaincodeID
    31  	Creator      []byte
    32  	Response     *peer.Response
    33  	Events       []byte
    34  	Results      []byte
    35  	Endorsements []*peer.Endorsement
    36  	Type         int32
    37  	Version      int32
    38  	Epoch        uint64
    39  	Nonce        []byte
    40  }
    41  
    42  func unmarshalEndorserTx(txenv *tx.Envelope) (*EndorserTx, error) {
    43  
    44  	if len(txenv.ChannelHeader.Extension) == 0 {
    45  		return nil, errors.New("empty header extension")
    46  	}
    47  
    48  	hdrExt, err := protoutil.UnmarshalChaincodeHeaderExtension(
    49  		txenv.ChannelHeader.Extension,
    50  	)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	if len(txenv.Data) == 0 {
    56  		return nil, errors.New("nil payload data")
    57  	}
    58  
    59  	tx, err := protoutil.UnmarshalTransaction(txenv.Data)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	if len(tx.GetActions()) != 1 {
    65  		return nil, errors.Errorf("only one transaction action is supported, %d were present", len(tx.GetActions()))
    66  	}
    67  
    68  	txAction := tx.GetActions()[0]
    69  
    70  	if txAction == nil {
    71  		return nil, errors.New("nil action")
    72  	}
    73  
    74  	if len(txAction.Payload) == 0 {
    75  		return nil, errors.New("empty ChaincodeActionPayload")
    76  	}
    77  
    78  	ccActionPayload, err := protoutil.UnmarshalChaincodeActionPayload(txAction.Payload)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	if ccActionPayload.Action == nil {
    84  		return nil, errors.New("nil ChaincodeEndorsedAction")
    85  	}
    86  
    87  	if len(ccActionPayload.Action.ProposalResponsePayload) == 0 {
    88  		return nil, errors.New("empty ProposalResponsePayload")
    89  	}
    90  
    91  	proposalResponsePayload, err := protoutil.UnmarshalProposalResponsePayload(
    92  		ccActionPayload.Action.ProposalResponsePayload,
    93  	)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	if len(proposalResponsePayload.Extension) == 0 {
    99  		return nil, errors.New("nil Extension")
   100  	}
   101  
   102  	ccAction, err := protoutil.UnmarshalChaincodeAction(proposalResponsePayload.Extension)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	computedTxID := protoutil.ComputeTxID(
   108  		txenv.SignatureHeader.Nonce,
   109  		txenv.SignatureHeader.Creator,
   110  	)
   111  
   112  	return &EndorserTx{
   113  		ComputedTxID: computedTxID,
   114  		ChannelID:    txenv.ChannelHeader.ChannelId,
   115  		Creator:      txenv.SignatureHeader.Creator,
   116  		Response:     ccAction.Response,
   117  		Events:       ccAction.Events,
   118  		Results:      ccAction.Results,
   119  		Endorsements: ccActionPayload.Action.Endorsements,
   120  		ChaincodeID:  hdrExt.ChaincodeId,
   121  		Type:         txenv.ChannelHeader.Type,
   122  		Version:      txenv.ChannelHeader.Version,
   123  		Epoch:        txenv.ChannelHeader.Epoch,
   124  		Nonce:        txenv.SignatureHeader.Nonce,
   125  	}, nil
   126  }
   127  
   128  func (e *EndorserTx) validate() error {
   129  
   130  	if e.Epoch != 0 {
   131  		return errors.Errorf("invalid epoch in ChannelHeader. Expected 0, got [%d]", e.Epoch)
   132  	}
   133  
   134  	if e.Version != 0 {
   135  		return errors.Errorf("invalid version in ChannelHeader. Expected 0, got [%d]", e.Version)
   136  	}
   137  
   138  	if err := ValidateChannelID(e.ChannelID); err != nil {
   139  		return err
   140  	}
   141  
   142  	if len(e.Nonce) == 0 {
   143  		return errors.New("empty nonce")
   144  	}
   145  
   146  	if len(e.Creator) == 0 {
   147  		return errors.New("empty creator")
   148  	}
   149  
   150  	if e.ChaincodeID == nil {
   151  		return errors.New("nil ChaincodeId")
   152  	}
   153  
   154  	if e.ChaincodeID.Name == "" {
   155  		return errors.New("empty chaincode name in chaincode id")
   156  	}
   157  
   158  	// TODO FAB-16170: check proposal hash
   159  
   160  	// TODO FAB-16170: verify that txid matches the one in the header
   161  
   162  	// TODO FAB-16170: check that header in the tx action and channel header match bitwise
   163  
   164  	return nil
   165  }
   166  
   167  // UnmarshalEndorserTxAndValidate receives a tx.Envelope containing
   168  // a partially unmarshalled endorser transaction and returns an EndorserTx
   169  // instance (or an error)
   170  func UnmarshalEndorserTxAndValidate(txenv *tx.Envelope) (*EndorserTx, error) {
   171  	etx, err := unmarshalEndorserTx(txenv)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	err = etx.validate()
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	return etx, nil
   182  }
   183  
   184  // ValidateChannelID makes sure that proposed channel IDs comply with the
   185  // following restrictions:
   186  //      1. Contain only lower case ASCII alphanumerics, dots '.', and dashes '-'
   187  //      2. Are shorter than 250 characters.
   188  //      3. Start with a letter
   189  //
   190  // This is the intersection of the Kafka restrictions and CouchDB restrictions
   191  // with the following exception: '.' is converted to '_' in the CouchDB naming
   192  // This is to accommodate existing channel names with '.', especially in the
   193  // behave tests which rely on the dot notation for their sluggification.
   194  //
   195  // note: this function is a copy of the same in common/configtx/validator.go
   196  //
   197  func ValidateChannelID(channelID string) error {
   198  	re, _ := regexp.Compile(ChannelAllowedChars)
   199  	// Length
   200  	if len(channelID) <= 0 {
   201  		return errors.Errorf("channel ID illegal, cannot be empty")
   202  	}
   203  	if len(channelID) > MaxLength {
   204  		return errors.Errorf("channel ID illegal, cannot be longer than %d", MaxLength)
   205  	}
   206  
   207  	// Illegal characters
   208  	matched := re.FindString(channelID)
   209  	if len(matched) != len(channelID) {
   210  		return errors.Errorf("'%s' contains illegal characters", channelID)
   211  	}
   212  
   213  	return nil
   214  }