// Copyright the Hyperledger Fabric contributors. All rights reserved. // SPDX-License-Identifier: Apache-2.0 // Package shimtest provides a mock of the ChaincodeStubInterface for // unit testing chaincode. // // Deprecated: ShimTest will be removed in a future release. // Future development should make use of the ChaincodeStub Interface // for generating mocks package shimtest import ( "container/list" "errors" "fmt" "strings" "unicode/utf8" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/timestamp" "github.com/hyperledger/fabric-chaincode-go/shim" "github.com/hyperledger/fabric-protos-go/ledger/queryresult" pb "github.com/hyperledger/fabric-protos-go/peer" ) const ( minUnicodeRuneValue = 0 //U+0000 compositeKeyNamespace = "\x00" ) // MockStub is an implementation of ChaincodeStubInterface for unit testing chaincode. // Use this instead of ChaincodeStub in your chaincode's unit test calls to Init or Invoke. type MockStub struct { // arguments the stub was called with args [][]byte // transientMap TransientMap map[string][]byte // A pointer back to the chaincode that will invoke this, set by constructor. // If a peer calls this stub, the chaincode will be invoked from here. cc shim.Chaincode // A nice name that can be used for logging Name string // State keeps name value pairs State map[string][]byte // Keys stores the list of mapped values in lexical order Keys *list.List // registered list of other MockStub chaincodes that can be called from this MockStub Invokables map[string]*MockStub // stores a transaction uuid while being Invoked / Deployed // TODO if a chaincode uses recursion this may need to be a stack of TxIDs or possibly a reference counting map TxID string TxTimestamp *timestamp.Timestamp // mocked signedProposal signedProposal *pb.SignedProposal // stores a channel ID of the proposal ChannelID string PvtState map[string]map[string][]byte // stores per-key endorsement policy, first map index is the collection, second map index is the key EndorsementPolicies map[string]map[string][]byte // channel to store ChaincodeEvents ChaincodeEventsChannel chan *pb.ChaincodeEvent Creator []byte Decorations map[string][]byte } // GetTxID ... func (stub *MockStub) GetTxID() string { return stub.TxID } // GetChannelID ... func (stub *MockStub) GetChannelID() string { return stub.ChannelID } // GetArgs ... func (stub *MockStub) GetArgs() [][]byte { return stub.args } // GetStringArgs ... func (stub *MockStub) GetStringArgs() []string { args := stub.GetArgs() strargs := make([]string, 0, len(args)) for _, barg := range args { strargs = append(strargs, string(barg)) } return strargs } // GetFunctionAndParameters ... func (stub *MockStub) GetFunctionAndParameters() (function string, params []string) { allargs := stub.GetStringArgs() function = "" params = []string{} if len(allargs) >= 1 { function = allargs[0] params = allargs[1:] } return } // MockTransactionStart Used to indicate to a chaincode that it is part of a transaction. // This is important when chaincodes invoke each other. // MockStub doesn't support concurrent transactions at present. func (stub *MockStub) MockTransactionStart(txid string) { stub.TxID = txid stub.setSignedProposal(&pb.SignedProposal{}) stub.setTxTimestamp(ptypes.TimestampNow()) } // MockTransactionEnd End a mocked transaction, clearing the UUID. func (stub *MockStub) MockTransactionEnd(uuid string) { stub.signedProposal = nil stub.TxID = "" } // MockPeerChaincode Register another MockStub chaincode with this MockStub. // invokableChaincodeName is the name of a chaincode. // otherStub is a MockStub of the chaincode, already initialized. // channel is the name of a channel on which another MockStub is called. func (stub *MockStub) MockPeerChaincode(invokableChaincodeName string, otherStub *MockStub, channel string) { // Internally we use chaincode name as a composite name if channel != "" { invokableChaincodeName = invokableChaincodeName + "/" + channel } stub.Invokables[invokableChaincodeName] = otherStub } // MockInit Initialise this chaincode, also starts and ends a transaction. func (stub *MockStub) MockInit(uuid string, args [][]byte) pb.Response { stub.args = args stub.MockTransactionStart(uuid) res := stub.cc.Init(stub) stub.MockTransactionEnd(uuid) return res } // MockInvoke Invoke this chaincode, also starts and ends a transaction. func (stub *MockStub) MockInvoke(uuid string, args [][]byte) pb.Response { stub.args = args stub.MockTransactionStart(uuid) res := stub.cc.Invoke(stub) stub.MockTransactionEnd(uuid) return res } // GetDecorations ... func (stub *MockStub) GetDecorations() map[string][]byte { return stub.Decorations } // MockInvokeWithSignedProposal Invoke this chaincode, also starts and ends a transaction. func (stub *MockStub) MockInvokeWithSignedProposal(uuid string, args [][]byte, sp *pb.SignedProposal) pb.Response { stub.args = args stub.MockTransactionStart(uuid) stub.signedProposal = sp res := stub.cc.Invoke(stub) stub.MockTransactionEnd(uuid) return res } // GetPrivateData ... func (stub *MockStub) GetPrivateData(collection string, key string) ([]byte, error) { m, in := stub.PvtState[collection] if !in { return nil, nil } return m[key], nil } // GetPrivateDataHash ... func (stub *MockStub) GetPrivateDataHash(collection, key string) ([]byte, error) { return nil, errors.New("Not Implemented") } // PutPrivateData ... func (stub *MockStub) PutPrivateData(collection string, key string, value []byte) error { m, in := stub.PvtState[collection] if !in { stub.PvtState[collection] = make(map[string][]byte) m, in = stub.PvtState[collection] } m[key] = value return nil } // DelPrivateData ... func (stub *MockStub) DelPrivateData(collection string, key string) error { return errors.New("Not Implemented") } // PurgePrivateData ... func (stub *MockStub) PurgePrivateData(collection string, key string) error { return errors.New("Not Implemented") } // GetPrivateDataByRange ... func (stub *MockStub) GetPrivateDataByRange(collection, startKey, endKey string) (shim.StateQueryIteratorInterface, error) { return nil, errors.New("Not Implemented") } // GetPrivateDataByPartialCompositeKey ... func (stub *MockStub) GetPrivateDataByPartialCompositeKey(collection, objectType string, attributes []string) (shim.StateQueryIteratorInterface, error) { return nil, errors.New("Not Implemented") } // GetPrivateDataQueryResult ... func (stub *MockStub) GetPrivateDataQueryResult(collection, query string) (shim.StateQueryIteratorInterface, error) { // Not implemented since the mock engine does not have a query engine. // However, a very simple query engine that supports string matching // could be implemented to test that the framework supports queries return nil, errors.New("Not Implemented") } // GetState retrieves the value for a given key from the ledger func (stub *MockStub) GetState(key string) ([]byte, error) { value := stub.State[key] return value, nil } // PutState writes the specified `value` and `key` into the ledger. func (stub *MockStub) PutState(key string, value []byte) error { if stub.TxID == "" { err := errors.New("cannot PutState without a transactions - call stub.MockTransactionStart()?") return err } // If the value is nil or empty, delete the key if len(value) == 0 { return stub.DelState(key) } stub.State[key] = value // insert key into ordered list of keys for elem := stub.Keys.Front(); elem != nil; elem = elem.Next() { elemValue := elem.Value.(string) comp := strings.Compare(key, elemValue) if comp < 0 { // key < elem, insert it before elem stub.Keys.InsertBefore(key, elem) break } else if comp == 0 { // keys exists, no need to change break } else { // comp > 0 // key > elem, keep looking unless this is the end of the list if elem.Next() == nil { stub.Keys.PushBack(key) break } } } // special case for empty Keys list if stub.Keys.Len() == 0 { stub.Keys.PushFront(key) } return nil } // DelState removes the specified `key` and its value from the ledger. func (stub *MockStub) DelState(key string) error { delete(stub.State, key) for elem := stub.Keys.Front(); elem != nil; elem = elem.Next() { if strings.Compare(key, elem.Value.(string)) == 0 { stub.Keys.Remove(elem) } } return nil } // GetStateByRange ... func (stub *MockStub) GetStateByRange(startKey, endKey string) (shim.StateQueryIteratorInterface, error) { if err := validateSimpleKeys(startKey, endKey); err != nil { return nil, err } return NewMockStateRangeQueryIterator(stub, startKey, endKey), nil } //To ensure that simple keys do not go into composite key namespace, //we validate simplekey to check whether the key starts with 0x00 (which //is the namespace for compositeKey). This helps in avoding simple/composite //key collisions. func validateSimpleKeys(simpleKeys ...string) error { for _, key := range simpleKeys { if len(key) > 0 && key[0] == compositeKeyNamespace[0] { return fmt.Errorf(`first character of the key [%s] contains a null character which is not allowed`, key) } } return nil } // GetQueryResult function can be invoked by a chaincode to perform a // rich query against state database. Only supported by state database implementations // that support rich query. The query string is in the syntax of the underlying // state database. An iterator is returned which can be used to iterate (next) over // the query result set func (stub *MockStub) GetQueryResult(query string) (shim.StateQueryIteratorInterface, error) { // Not implemented since the mock engine does not have a query engine. // However, a very simple query engine that supports string matching // could be implemented to test that the framework supports queries return nil, errors.New("not implemented") } // GetHistoryForKey function can be invoked by a chaincode to return a history of // key values across time. GetHistoryForKey is intended to be used for read-only queries. func (stub *MockStub) GetHistoryForKey(key string) (shim.HistoryQueryIteratorInterface, error) { return nil, errors.New("not implemented") } // GetStateByPartialCompositeKey function can be invoked by a chaincode to query the // state based on a given partial composite key. This function returns an // iterator which can be used to iterate over all composite keys whose prefix // matches the given partial composite key. This function should be used only for // a partial composite key. For a full composite key, an iter with empty response // would be returned. func (stub *MockStub) GetStateByPartialCompositeKey(objectType string, attributes []string) (shim.StateQueryIteratorInterface, error) { partialCompositeKey, err := stub.CreateCompositeKey(objectType, attributes) if err != nil { return nil, err } return NewMockStateRangeQueryIterator(stub, partialCompositeKey, partialCompositeKey+string(utf8.MaxRune)), nil } // CreateCompositeKey combines the list of attributes // to form a composite key. func (stub *MockStub) CreateCompositeKey(objectType string, attributes []string) (string, error) { return shim.CreateCompositeKey(objectType, attributes) } // SplitCompositeKey splits the composite key into attributes // on which the composite key was formed. func (stub *MockStub) SplitCompositeKey(compositeKey string) (string, []string, error) { return splitCompositeKey(compositeKey) } func splitCompositeKey(compositeKey string) (string, []string, error) { componentIndex := 1 components := []string{} for i := 1; i < len(compositeKey); i++ { if compositeKey[i] == minUnicodeRuneValue { components = append(components, compositeKey[componentIndex:i]) componentIndex = i + 1 } } return components[0], components[1:], nil } // GetStateByRangeWithPagination ... func (stub *MockStub) GetStateByRangeWithPagination(startKey, endKey string, pageSize int32, bookmark string) (shim.StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) { return nil, nil, nil } // GetStateByPartialCompositeKeyWithPagination ... func (stub *MockStub) GetStateByPartialCompositeKeyWithPagination(objectType string, keys []string, pageSize int32, bookmark string) (shim.StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) { return nil, nil, nil } // GetQueryResultWithPagination ... func (stub *MockStub) GetQueryResultWithPagination(query string, pageSize int32, bookmark string) (shim.StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) { return nil, nil, nil } // InvokeChaincode locally calls the specified chaincode `Invoke`. // E.g. stub1.InvokeChaincode("othercc", funcArgs, channel) // Before calling this make sure to create another MockStub stub2, call shim.NewMockStub("othercc", Chaincode) // and register it with stub1 by calling stub1.MockPeerChaincode("othercc", stub2, channel) func (stub *MockStub) InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response { // Internally we use chaincode name as a composite name if channel != "" { chaincodeName = chaincodeName + "/" + channel } // TODO "args" here should possibly be a serialized pb.ChaincodeInput otherStub := stub.Invokables[chaincodeName] // function, strings := getFuncArgs(args) res := otherStub.MockInvoke(stub.TxID, args) return res } // GetCreator ... func (stub *MockStub) GetCreator() ([]byte, error) { return stub.Creator, nil } // SetTransient set TransientMap to mockStub func (stub *MockStub) SetTransient(tMap map[string][]byte) error { if stub.signedProposal == nil { return fmt.Errorf("signedProposal is not initialized") } payloadByte, err := proto.Marshal(&pb.ChaincodeProposalPayload{ TransientMap: tMap, }) if err != nil { return err } proposalByte, err := proto.Marshal(&pb.Proposal{ Payload: payloadByte, }) if err != nil { return err } stub.signedProposal.ProposalBytes = proposalByte stub.TransientMap = tMap return nil } // GetTransient ... func (stub *MockStub) GetTransient() (map[string][]byte, error) { return stub.TransientMap, nil } // GetBinding Not implemented ... func (stub *MockStub) GetBinding() ([]byte, error) { return nil, nil } // GetSignedProposal Not implemented ... func (stub *MockStub) GetSignedProposal() (*pb.SignedProposal, error) { return stub.signedProposal, nil } func (stub *MockStub) setSignedProposal(sp *pb.SignedProposal) { stub.signedProposal = sp } // GetArgsSlice Not implemented ... func (stub *MockStub) GetArgsSlice() ([]byte, error) { return nil, nil } func (stub *MockStub) setTxTimestamp(time *timestamp.Timestamp) { stub.TxTimestamp = time } // GetTxTimestamp ... func (stub *MockStub) GetTxTimestamp() (*timestamp.Timestamp, error) { if stub.TxTimestamp == nil { return nil, errors.New("TxTimestamp not set") } return stub.TxTimestamp, nil } // SetEvent ... func (stub *MockStub) SetEvent(name string, payload []byte) error { stub.ChaincodeEventsChannel <- &pb.ChaincodeEvent{EventName: name, Payload: payload} return nil } // SetStateValidationParameter ... func (stub *MockStub) SetStateValidationParameter(key string, ep []byte) error { return stub.SetPrivateDataValidationParameter("", key, ep) } // GetStateValidationParameter ... func (stub *MockStub) GetStateValidationParameter(key string) ([]byte, error) { return stub.GetPrivateDataValidationParameter("", key) } // SetPrivateDataValidationParameter ... func (stub *MockStub) SetPrivateDataValidationParameter(collection, key string, ep []byte) error { m, in := stub.EndorsementPolicies[collection] if !in { stub.EndorsementPolicies[collection] = make(map[string][]byte) m, in = stub.EndorsementPolicies[collection] } m[key] = ep return nil } // GetPrivateDataValidationParameter ... func (stub *MockStub) GetPrivateDataValidationParameter(collection, key string) ([]byte, error) { m, in := stub.EndorsementPolicies[collection] if !in { return nil, nil } return m[key], nil } // NewMockStub Constructor to initialise the internal State map func NewMockStub(name string, cc shim.Chaincode) *MockStub { s := new(MockStub) s.Name = name s.cc = cc s.State = make(map[string][]byte) s.PvtState = make(map[string]map[string][]byte) s.EndorsementPolicies = make(map[string]map[string][]byte) s.Invokables = make(map[string]*MockStub) s.Keys = list.New() s.ChaincodeEventsChannel = make(chan *pb.ChaincodeEvent, 100) //define large capacity for non-blocking setEvent calls. s.Decorations = make(map[string][]byte) return s } /***************************** Range Query Iterator *****************************/ // MockStateRangeQueryIterator ... type MockStateRangeQueryIterator struct { Closed bool Stub *MockStub StartKey string EndKey string Current *list.Element } // HasNext returns true if the range query iterator contains additional keys // and values. func (iter *MockStateRangeQueryIterator) HasNext() bool { if iter.Closed { // previously called Close() return false } if iter.Current == nil { return false } current := iter.Current for current != nil { // if this is an open-ended query for all keys, return true if iter.StartKey == "" && iter.EndKey == "" { return true } comp1 := strings.Compare(current.Value.(string), iter.StartKey) comp2 := strings.Compare(current.Value.(string), iter.EndKey) if comp1 >= 0 { if comp2 < 0 { return true } return false } current = current.Next() } return false } // Next returns the next key and value in the range query iterator. func (iter *MockStateRangeQueryIterator) Next() (*queryresult.KV, error) { if iter.Closed == true { err := errors.New("MockStateRangeQueryIterator.Next() called after Close()") return nil, err } if iter.HasNext() == false { err := errors.New("MockStateRangeQueryIterator.Next() called when it does not HaveNext()") return nil, err } for iter.Current != nil { comp1 := strings.Compare(iter.Current.Value.(string), iter.StartKey) comp2 := strings.Compare(iter.Current.Value.(string), iter.EndKey) // compare to start and end keys. or, if this is an open-ended query for // all keys, it should always return the key and value if (comp1 >= 0 && comp2 < 0) || (iter.StartKey == "" && iter.EndKey == "") { key := iter.Current.Value.(string) value, err := iter.Stub.GetState(key) iter.Current = iter.Current.Next() return &queryresult.KV{Key: key, Value: value}, err } iter.Current = iter.Current.Next() } err := errors.New("MockStateRangeQueryIterator.Next() went past end of range") return nil, err } // Close closes the range query iterator. This should be called when done // reading from the iterator to free up resources. func (iter *MockStateRangeQueryIterator) Close() error { if iter.Closed == true { err := errors.New("MockStateRangeQueryIterator.Close() called after Close()") return err } iter.Closed = true return nil } // NewMockStateRangeQueryIterator ... func NewMockStateRangeQueryIterator(stub *MockStub, startKey string, endKey string) *MockStateRangeQueryIterator { iter := new(MockStateRangeQueryIterator) iter.Closed = false iter.Stub = stub iter.StartKey = startKey iter.EndKey = endKey iter.Current = stub.Keys.Front() return iter } func getBytes(function string, args []string) [][]byte { bytes := make([][]byte, 0, len(args)+1) bytes = append(bytes, []byte(function)) for _, s := range args { bytes = append(bytes, []byte(s)) } return bytes } func getFuncArgs(bytes [][]byte) (string, []string) { function := string(bytes[0]) args := make([]string, len(bytes)-1) for i := 1; i < len(bytes); i++ { args[i-1] = string(bytes[i]) } return function, args }