github.com/yimialmonte/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 }