/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package protoutil import ( "crypto/sha256" "encoding/hex" "time" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric-protos-go/peer" "github.com/pkg/errors" ) // CreateChaincodeProposal creates a proposal from given input. // It returns the proposal and the transaction id associated to the proposal func CreateChaincodeProposal(typ common.HeaderType, channelID string, cis *peer.ChaincodeInvocationSpec, creator []byte) (*peer.Proposal, string, error) { return CreateChaincodeProposalWithTransient(typ, channelID, cis, creator, nil) } // CreateChaincodeProposalWithTransient creates a proposal from given input // It returns the proposal and the transaction id associated to the proposal func CreateChaincodeProposalWithTransient(typ common.HeaderType, channelID string, cis *peer.ChaincodeInvocationSpec, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) { // generate a random nonce nonce, err := getRandomNonce() if err != nil { return nil, "", err } // compute txid txid := ComputeTxID(nonce, creator) return CreateChaincodeProposalWithTxIDNonceAndTransient(txid, typ, channelID, cis, nonce, creator, transientMap) } // CreateChaincodeProposalWithTxIDAndTransient creates a proposal from given // input. It returns the proposal and the transaction id associated with the // proposal func CreateChaincodeProposalWithTxIDAndTransient(typ common.HeaderType, channelID string, cis *peer.ChaincodeInvocationSpec, creator []byte, txid string, transientMap map[string][]byte) (*peer.Proposal, string, error) { // generate a random nonce nonce, err := getRandomNonce() if err != nil { return nil, "", err } // compute txid unless provided by tests if txid == "" { txid = ComputeTxID(nonce, creator) } return CreateChaincodeProposalWithTxIDNonceAndTransient(txid, typ, channelID, cis, nonce, creator, transientMap) } // CreateChaincodeProposalWithTxIDNonceAndTransient creates a proposal from // given input func CreateChaincodeProposalWithTxIDNonceAndTransient(txid string, typ common.HeaderType, channelID string, cis *peer.ChaincodeInvocationSpec, nonce, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) { ccHdrExt := &peer.ChaincodeHeaderExtension{ChaincodeId: cis.ChaincodeSpec.ChaincodeId} ccHdrExtBytes, err := proto.Marshal(ccHdrExt) if err != nil { return nil, "", errors.Wrap(err, "error marshaling ChaincodeHeaderExtension") } cisBytes, err := proto.Marshal(cis) if err != nil { return nil, "", errors.Wrap(err, "error marshaling ChaincodeInvocationSpec") } ccPropPayload := &peer.ChaincodeProposalPayload{Input: cisBytes, TransientMap: transientMap} ccPropPayloadBytes, err := proto.Marshal(ccPropPayload) if err != nil { return nil, "", errors.Wrap(err, "error marshaling ChaincodeProposalPayload") } // TODO: epoch is now set to zero. This must be changed once we // get a more appropriate mechanism to handle it in. var epoch uint64 timestamp, err := ptypes.TimestampProto(time.Now().UTC()) if err != nil { return nil, "", errors.Wrap(err, "error validating Timestamp") } hdr := &common.Header{ ChannelHeader: MarshalOrPanic( &common.ChannelHeader{ Type: int32(typ), TxId: txid, Timestamp: timestamp, ChannelId: channelID, Extension: ccHdrExtBytes, Epoch: epoch, }, ), SignatureHeader: MarshalOrPanic( &common.SignatureHeader{ Nonce: nonce, Creator: creator, }, ), } hdrBytes, err := proto.Marshal(hdr) if err != nil { return nil, "", err } prop := &peer.Proposal{ Header: hdrBytes, Payload: ccPropPayloadBytes, } return prop, txid, nil } // GetBytesProposalResponsePayload gets proposal response payload func GetBytesProposalResponsePayload(hash []byte, response *peer.Response, result []byte, event []byte, ccid *peer.ChaincodeID) ([]byte, error) { cAct := &peer.ChaincodeAction{ Events: event, Results: result, Response: response, ChaincodeId: ccid, } cActBytes, err := proto.Marshal(cAct) if err != nil { return nil, errors.Wrap(err, "error marshaling ChaincodeAction") } prp := &peer.ProposalResponsePayload{ Extension: cActBytes, ProposalHash: hash, } prpBytes, err := proto.Marshal(prp) return prpBytes, errors.Wrap(err, "error marshaling ProposalResponsePayload") } // GetBytesChaincodeProposalPayload gets the chaincode proposal payload func GetBytesChaincodeProposalPayload(cpp *peer.ChaincodeProposalPayload) ([]byte, error) { cppBytes, err := proto.Marshal(cpp) return cppBytes, errors.Wrap(err, "error marshaling ChaincodeProposalPayload") } // GetBytesResponse gets the bytes of Response func GetBytesResponse(res *peer.Response) ([]byte, error) { resBytes, err := proto.Marshal(res) return resBytes, errors.Wrap(err, "error marshaling Response") } // GetBytesChaincodeEvent gets the bytes of ChaincodeEvent func GetBytesChaincodeEvent(event *peer.ChaincodeEvent) ([]byte, error) { eventBytes, err := proto.Marshal(event) return eventBytes, errors.Wrap(err, "error marshaling ChaincodeEvent") } // GetBytesChaincodeActionPayload get the bytes of ChaincodeActionPayload from // the message func GetBytesChaincodeActionPayload(cap *peer.ChaincodeActionPayload) ([]byte, error) { capBytes, err := proto.Marshal(cap) return capBytes, errors.Wrap(err, "error marshaling ChaincodeActionPayload") } // GetBytesProposalResponse gets proposal bytes response func GetBytesProposalResponse(pr *peer.ProposalResponse) ([]byte, error) { respBytes, err := proto.Marshal(pr) return respBytes, errors.Wrap(err, "error marshaling ProposalResponse") } // GetBytesHeader get the bytes of Header from the message func GetBytesHeader(hdr *common.Header) ([]byte, error) { bytes, err := proto.Marshal(hdr) return bytes, errors.Wrap(err, "error marshaling Header") } // GetBytesSignatureHeader get the bytes of SignatureHeader from the message func GetBytesSignatureHeader(hdr *common.SignatureHeader) ([]byte, error) { bytes, err := proto.Marshal(hdr) return bytes, errors.Wrap(err, "error marshaling SignatureHeader") } // GetBytesTransaction get the bytes of Transaction from the message func GetBytesTransaction(tx *peer.Transaction) ([]byte, error) { bytes, err := proto.Marshal(tx) return bytes, errors.Wrap(err, "error unmarshalling Transaction") } // GetBytesPayload get the bytes of Payload from the message func GetBytesPayload(payl *common.Payload) ([]byte, error) { bytes, err := proto.Marshal(payl) return bytes, errors.Wrap(err, "error marshaling Payload") } // GetBytesEnvelope get the bytes of Envelope from the message func GetBytesEnvelope(env *common.Envelope) ([]byte, error) { bytes, err := proto.Marshal(env) return bytes, errors.Wrap(err, "error marshaling Envelope") } // GetActionFromEnvelope extracts a ChaincodeAction message from a // serialized Envelope // TODO: fix function name as per FAB-11831 func GetActionFromEnvelope(envBytes []byte) (*peer.ChaincodeAction, error) { env, err := GetEnvelopeFromBlock(envBytes) if err != nil { return nil, err } return GetActionFromEnvelopeMsg(env) } func GetActionFromEnvelopeMsg(env *common.Envelope) (*peer.ChaincodeAction, error) { payl, err := UnmarshalPayload(env.Payload) if err != nil { return nil, err } tx, err := UnmarshalTransaction(payl.Data) if err != nil { return nil, err } if len(tx.Actions) == 0 { return nil, errors.New("at least one TransactionAction required") } _, respPayload, err := GetPayloads(tx.Actions[0]) return respPayload, err } // CreateProposalFromCISAndTxid returns a proposal given a serialized identity // and a ChaincodeInvocationSpec func CreateProposalFromCISAndTxid(txid string, typ common.HeaderType, channelID string, cis *peer.ChaincodeInvocationSpec, creator []byte) (*peer.Proposal, string, error) { nonce, err := getRandomNonce() if err != nil { return nil, "", err } return CreateChaincodeProposalWithTxIDNonceAndTransient(txid, typ, channelID, cis, nonce, creator, nil) } // CreateProposalFromCIS returns a proposal given a serialized identity and a // ChaincodeInvocationSpec func CreateProposalFromCIS(typ common.HeaderType, channelID string, cis *peer.ChaincodeInvocationSpec, creator []byte) (*peer.Proposal, string, error) { return CreateChaincodeProposal(typ, channelID, cis, creator) } // CreateGetChaincodesProposal returns a GETCHAINCODES proposal given a // serialized identity func CreateGetChaincodesProposal(channelID string, creator []byte) (*peer.Proposal, string, error) { ccinp := &peer.ChaincodeInput{Args: [][]byte{[]byte("getchaincodes")}} lsccSpec := &peer.ChaincodeInvocationSpec{ ChaincodeSpec: &peer.ChaincodeSpec{ Type: peer.ChaincodeSpec_GOLANG, ChaincodeId: &peer.ChaincodeID{Name: "lscc"}, Input: ccinp, }, } return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, channelID, lsccSpec, creator) } // CreateGetInstalledChaincodesProposal returns a GETINSTALLEDCHAINCODES // proposal given a serialized identity func CreateGetInstalledChaincodesProposal(creator []byte) (*peer.Proposal, string, error) { ccinp := &peer.ChaincodeInput{Args: [][]byte{[]byte("getinstalledchaincodes")}} lsccSpec := &peer.ChaincodeInvocationSpec{ ChaincodeSpec: &peer.ChaincodeSpec{ Type: peer.ChaincodeSpec_GOLANG, ChaincodeId: &peer.ChaincodeID{Name: "lscc"}, Input: ccinp, }, } return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, "", lsccSpec, creator) } // CreateInstallProposalFromCDS returns a install proposal given a serialized // identity and a ChaincodeDeploymentSpec func CreateInstallProposalFromCDS(ccpack proto.Message, creator []byte) (*peer.Proposal, string, error) { return createProposalFromCDS("", ccpack, creator, "install") } // CreateDeployProposalFromCDS returns a deploy proposal given a serialized // identity and a ChaincodeDeploymentSpec func CreateDeployProposalFromCDS( channelID string, cds *peer.ChaincodeDeploymentSpec, creator []byte, policy []byte, escc []byte, vscc []byte, collectionConfig []byte) (*peer.Proposal, string, error) { if collectionConfig == nil { return createProposalFromCDS(channelID, cds, creator, "deploy", policy, escc, vscc) } return createProposalFromCDS(channelID, cds, creator, "deploy", policy, escc, vscc, collectionConfig) } // CreateUpgradeProposalFromCDS returns a upgrade proposal given a serialized // identity and a ChaincodeDeploymentSpec func CreateUpgradeProposalFromCDS( channelID string, cds *peer.ChaincodeDeploymentSpec, creator []byte, policy []byte, escc []byte, vscc []byte, collectionConfig []byte) (*peer.Proposal, string, error) { if collectionConfig == nil { return createProposalFromCDS(channelID, cds, creator, "upgrade", policy, escc, vscc) } return createProposalFromCDS(channelID, cds, creator, "upgrade", policy, escc, vscc, collectionConfig) } // createProposalFromCDS returns a deploy or upgrade proposal given a // serialized identity and a ChaincodeDeploymentSpec func createProposalFromCDS(channelID string, msg proto.Message, creator []byte, propType string, args ...[]byte) (*peer.Proposal, string, error) { // in the new mode, cds will be nil, "deploy" and "upgrade" are instantiates. var ccinp *peer.ChaincodeInput var b []byte var err error if msg != nil { b, err = proto.Marshal(msg) if err != nil { return nil, "", err } } switch propType { case "deploy": fallthrough case "upgrade": cds, ok := msg.(*peer.ChaincodeDeploymentSpec) if !ok || cds == nil { return nil, "", errors.New("invalid message for creating lifecycle chaincode proposal") } Args := [][]byte{[]byte(propType), []byte(channelID), b} Args = append(Args, args...) ccinp = &peer.ChaincodeInput{Args: Args} case "install": ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}} } // wrap the deployment in an invocation spec to lscc... lsccSpec := &peer.ChaincodeInvocationSpec{ ChaincodeSpec: &peer.ChaincodeSpec{ Type: peer.ChaincodeSpec_GOLANG, ChaincodeId: &peer.ChaincodeID{Name: "lscc"}, Input: ccinp, }, } // ...and get the proposal for it return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, channelID, lsccSpec, creator) } // ComputeTxID computes TxID as the Hash computed // over the concatenation of nonce and creator. func ComputeTxID(nonce, creator []byte) string { // TODO: Get the Hash function to be used from // channel configuration hasher := sha256.New() hasher.Write(nonce) hasher.Write(creator) return hex.EncodeToString(hasher.Sum(nil)) } // CheckTxID checks that txid is equal to the Hash computed // over the concatenation of nonce and creator. func CheckTxID(txid string, nonce, creator []byte) error { computedTxID := ComputeTxID(nonce, creator) if txid != computedTxID { return errors.Errorf("invalid txid. got [%s], expected [%s]", txid, computedTxID) } return nil } // InvokedChaincodeName takes the proposal bytes of a SignedProposal, and unpacks it all the way down, // until either an error is encountered, or the chaincode name is found. This is useful primarily // for chaincodes which wish to know the chaincode name originally invoked, in order to deny cc2cc // invocations (or, perhaps to deny direct invocations and require cc2cc). func InvokedChaincodeName(proposalBytes []byte) (string, error) { proposal := &peer.Proposal{} err := proto.Unmarshal(proposalBytes, proposal) if err != nil { return "", errors.WithMessage(err, "could not unmarshal proposal") } proposalPayload := &peer.ChaincodeProposalPayload{} err = proto.Unmarshal(proposal.Payload, proposalPayload) if err != nil { return "", errors.WithMessage(err, "could not unmarshal chaincode proposal payload") } cis := &peer.ChaincodeInvocationSpec{} err = proto.Unmarshal(proposalPayload.Input, cis) if err != nil { return "", errors.WithMessage(err, "could not unmarshal chaincode invocation spec") } if cis.ChaincodeSpec == nil { return "", errors.Errorf("chaincode spec is nil") } if cis.ChaincodeSpec.ChaincodeId == nil { return "", errors.Errorf("chaincode id is nil") } return cis.ChaincodeSpec.ChaincodeId.Name, nil }