github.com/xiaqingdoc/fabric@v2.1.1+incompatible/core/chaincode/chaincode_support.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package chaincode 8 9 import ( 10 "bytes" 11 "time" 12 "unicode/utf8" 13 14 "github.com/golang/protobuf/proto" 15 pb "github.com/hyperledger/fabric-protos-go/peer" 16 "github.com/hyperledger/fabric/common/util" 17 "github.com/hyperledger/fabric/core/chaincode/extcc" 18 "github.com/hyperledger/fabric/core/chaincode/lifecycle" 19 "github.com/hyperledger/fabric/core/common/ccprovider" 20 "github.com/hyperledger/fabric/core/container/ccintf" 21 "github.com/hyperledger/fabric/core/ledger" 22 "github.com/hyperledger/fabric/core/peer" 23 "github.com/hyperledger/fabric/core/scc" 24 "github.com/pkg/errors" 25 ) 26 27 const ( 28 // InitializedKeyName is the reserved key in a chaincode's namespace which 29 // records the ID of the chaincode which initialized the namespace. 30 // In this way, we can enforce Init exactly once semantics, whenever 31 // the backing chaincode bytes change (but not be required to re-initialize 32 // the chaincode say, when endorsement policy changes). 33 InitializedKeyName = "\x00" + string(utf8.MaxRune) + "initialized" 34 ) 35 36 // Runtime is used to manage chaincode runtime instances. 37 type Runtime interface { 38 Build(ccid string) (*ccintf.ChaincodeServerInfo, error) 39 Start(ccid string, ccinfo *ccintf.PeerConnection) error 40 Stop(ccid string) error 41 Wait(ccid string) (int, error) 42 } 43 44 // Launcher is used to launch chaincode runtimes. 45 type Launcher interface { 46 Launch(ccid string, streamHandler extcc.StreamHandler) error 47 Stop(ccid string) error 48 } 49 50 // Lifecycle provides a way to retrieve chaincode definitions and the packages necessary to run them 51 type Lifecycle interface { 52 // ChaincodeEndorsementInfo looks up the chaincode info in the given channel. It is the responsibility 53 // of the implementation to add appropriate read dependencies for the information returned. 54 ChaincodeEndorsementInfo(channelID, chaincodeName string, qe ledger.SimpleQueryExecutor) (*lifecycle.ChaincodeEndorsementInfo, error) 55 } 56 57 // ChaincodeSupport responsible for providing interfacing with chaincodes from the Peer. 58 type ChaincodeSupport struct { 59 ACLProvider ACLProvider 60 AppConfig ApplicationConfigRetriever 61 BuiltinSCCs scc.BuiltinSCCs 62 DeployedCCInfoProvider ledger.DeployedChaincodeInfoProvider 63 ExecuteTimeout time.Duration 64 InstallTimeout time.Duration 65 HandlerMetrics *HandlerMetrics 66 HandlerRegistry *HandlerRegistry 67 Keepalive time.Duration 68 Launcher Launcher 69 Lifecycle Lifecycle 70 Peer *peer.Peer 71 Runtime Runtime 72 TotalQueryLimit int 73 UserRunsCC bool 74 } 75 76 // Launch starts executing chaincode if it is not already running. This method 77 // blocks until the peer side handler gets into ready state or encounters a fatal 78 // error. If the chaincode is already running, it simply returns. 79 func (cs *ChaincodeSupport) Launch(ccid string) (*Handler, error) { 80 if h := cs.HandlerRegistry.Handler(ccid); h != nil { 81 return h, nil 82 } 83 84 if err := cs.Launcher.Launch(ccid, cs); err != nil { 85 return nil, errors.Wrapf(err, "could not launch chaincode %s", ccid) 86 } 87 88 h := cs.HandlerRegistry.Handler(ccid) 89 if h == nil { 90 return nil, errors.Errorf("claimed to start chaincode container for %s but could not find handler", ccid) 91 } 92 93 return h, nil 94 } 95 96 // LaunchInProc is a stopgap solution to be called by the inproccontroller to allow system chaincodes to register 97 func (cs *ChaincodeSupport) LaunchInProc(ccid string) <-chan struct{} { 98 launchStatus, ok := cs.HandlerRegistry.Launching(ccid) 99 if ok { 100 chaincodeLogger.Panicf("attempted to launch a system chaincode which has already been launched") 101 } 102 103 return launchStatus.Done() 104 } 105 106 // HandleChaincodeStream implements ccintf.HandleChaincodeStream for all vms to call with appropriate stream 107 func (cs *ChaincodeSupport) HandleChaincodeStream(stream ccintf.ChaincodeStream) error { 108 handler := &Handler{ 109 Invoker: cs, 110 Keepalive: cs.Keepalive, 111 Registry: cs.HandlerRegistry, 112 ACLProvider: cs.ACLProvider, 113 TXContexts: NewTransactionContexts(), 114 ActiveTransactions: NewActiveTransactions(), 115 BuiltinSCCs: cs.BuiltinSCCs, 116 QueryResponseBuilder: &QueryResponseGenerator{MaxResultLimit: 100}, 117 UUIDGenerator: UUIDGeneratorFunc(util.GenerateUUID), 118 LedgerGetter: cs.Peer, 119 DeployedCCInfoProvider: cs.DeployedCCInfoProvider, 120 AppConfig: cs.AppConfig, 121 Metrics: cs.HandlerMetrics, 122 TotalQueryLimit: cs.TotalQueryLimit, 123 } 124 125 return handler.ProcessStream(stream) 126 } 127 128 // Register the bidi stream entry point called by chaincode to register with the Peer. 129 func (cs *ChaincodeSupport) Register(stream pb.ChaincodeSupport_RegisterServer) error { 130 return cs.HandleChaincodeStream(stream) 131 } 132 133 // ExecuteLegacyInit is a temporary method which should be removed once the old style lifecycle 134 // is entirely deprecated. Ideally one release after the introduction of the new lifecycle. 135 // It does not attempt to start the chaincode based on the information from lifecycle, but instead 136 // accepts the container information directly in the form of a ChaincodeDeploymentSpec. 137 func (cs *ChaincodeSupport) ExecuteLegacyInit(txParams *ccprovider.TransactionParams, ccName, ccVersion string, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) { 138 // FIXME: this is a hack, we shouldn't construct the 139 // ccid manually but rather let lifecycle construct it 140 // for us. However this is legacy code that will disappear 141 // so it is acceptable for now (FAB-14627) 142 ccid := ccName + ":" + ccVersion 143 144 h, err := cs.Launch(ccid) 145 if err != nil { 146 return nil, nil, err 147 } 148 149 resp, err := cs.execute(pb.ChaincodeMessage_INIT, txParams, ccName, input, h) 150 return processChaincodeExecutionResult(txParams.TxID, ccName, resp, err) 151 } 152 153 // Execute invokes chaincode and returns the original response. 154 func (cs *ChaincodeSupport) Execute(txParams *ccprovider.TransactionParams, chaincodeName string, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) { 155 resp, err := cs.Invoke(txParams, chaincodeName, input) 156 return processChaincodeExecutionResult(txParams.TxID, chaincodeName, resp, err) 157 } 158 159 func processChaincodeExecutionResult(txid, ccName string, resp *pb.ChaincodeMessage, err error) (*pb.Response, *pb.ChaincodeEvent, error) { 160 if err != nil { 161 return nil, nil, errors.Wrapf(err, "failed to execute transaction %s", txid) 162 } 163 if resp == nil { 164 return nil, nil, errors.Errorf("nil response from transaction %s", txid) 165 } 166 167 if resp.ChaincodeEvent != nil { 168 resp.ChaincodeEvent.ChaincodeId = ccName 169 resp.ChaincodeEvent.TxId = txid 170 } 171 172 switch resp.Type { 173 case pb.ChaincodeMessage_COMPLETED: 174 res := &pb.Response{} 175 err := proto.Unmarshal(resp.Payload, res) 176 if err != nil { 177 return nil, nil, errors.Wrapf(err, "failed to unmarshal response for transaction %s", txid) 178 } 179 return res, resp.ChaincodeEvent, nil 180 181 case pb.ChaincodeMessage_ERROR: 182 return nil, resp.ChaincodeEvent, errors.Errorf("transaction returned with failure: %s", resp.Payload) 183 184 default: 185 return nil, nil, errors.Errorf("unexpected response type %d for transaction %s", resp.Type, txid) 186 } 187 } 188 189 // Invoke will invoke chaincode and return the message containing the response. 190 // The chaincode will be launched if it is not already running. 191 func (cs *ChaincodeSupport) Invoke(txParams *ccprovider.TransactionParams, chaincodeName string, input *pb.ChaincodeInput) (*pb.ChaincodeMessage, error) { 192 ccid, cctype, err := cs.CheckInvocation(txParams, chaincodeName, input) 193 if err != nil { 194 return nil, errors.WithMessage(err, "invalid invocation") 195 } 196 197 h, err := cs.Launch(ccid) 198 if err != nil { 199 return nil, err 200 } 201 202 return cs.execute(cctype, txParams, chaincodeName, input, h) 203 } 204 205 // CheckInvocation inspects the parameters of an invocation and determines if, how, and to where a that invocation should be routed. 206 // First, we ensure that the target namespace is defined on the channel and invokable on this peer, according to the lifecycle implementation. 207 // Then, if the chaincode definition requires it, this function enforces 'init exactly once' semantics. 208 // Finally, it returns the chaincode ID to route to and the message type of the request (normal transation, or init). 209 func (cs *ChaincodeSupport) CheckInvocation(txParams *ccprovider.TransactionParams, chaincodeName string, input *pb.ChaincodeInput) (ccid string, cctype pb.ChaincodeMessage_Type, err error) { 210 chaincodeLogger.Debugf("[%s] getting chaincode data for %s on channel %s", shorttxid(txParams.TxID), chaincodeName, txParams.ChannelID) 211 cii, err := cs.Lifecycle.ChaincodeEndorsementInfo(txParams.ChannelID, chaincodeName, txParams.TXSimulator) 212 if err != nil { 213 logDevModeError(cs.UserRunsCC) 214 return "", 0, errors.Wrapf(err, "[channel %s] failed to get chaincode container info for %s", txParams.ChannelID, chaincodeName) 215 } 216 217 needsInitialization := false 218 if cii.EnforceInit { 219 220 value, err := txParams.TXSimulator.GetState(chaincodeName, InitializedKeyName) 221 if err != nil { 222 return "", 0, errors.WithMessage(err, "could not get 'initialized' key") 223 } 224 225 needsInitialization = !bytes.Equal(value, []byte(cii.Version)) 226 } 227 228 // Note, IsInit is a new field for v2.0 and should only be set for invocations of non-legacy chaincodes. 229 // Any invocation of a legacy chaincode with IsInit set will fail. This is desirable, as the old 230 // InstantiationPolicy contract enforces which users may call init. 231 if input.IsInit { 232 if !cii.EnforceInit { 233 return "", 0, errors.Errorf("chaincode '%s' does not require initialization but called as init", chaincodeName) 234 } 235 236 if !needsInitialization { 237 return "", 0, errors.Errorf("chaincode '%s' is already initialized but called as init", chaincodeName) 238 } 239 240 err = txParams.TXSimulator.SetState(chaincodeName, InitializedKeyName, []byte(cii.Version)) 241 if err != nil { 242 return "", 0, errors.WithMessage(err, "could not set 'initialized' key") 243 } 244 245 return cii.ChaincodeID, pb.ChaincodeMessage_INIT, nil 246 } 247 248 if needsInitialization { 249 return "", 0, errors.Errorf("chaincode '%s' has not been initialized for this version, must call as init first", chaincodeName) 250 } 251 252 return cii.ChaincodeID, pb.ChaincodeMessage_TRANSACTION, nil 253 } 254 255 // execute executes a transaction and waits for it to complete until a timeout value. 256 func (cs *ChaincodeSupport) execute(cctyp pb.ChaincodeMessage_Type, txParams *ccprovider.TransactionParams, namespace string, input *pb.ChaincodeInput, h *Handler) (*pb.ChaincodeMessage, error) { 257 input.Decorations = txParams.ProposalDecorations 258 259 payload, err := proto.Marshal(input) 260 if err != nil { 261 return nil, errors.WithMessage(err, "failed to create chaincode message") 262 } 263 264 ccMsg := &pb.ChaincodeMessage{ 265 Type: cctyp, 266 Payload: payload, 267 Txid: txParams.TxID, 268 ChannelId: txParams.ChannelID, 269 } 270 271 timeout := cs.executeTimeout(namespace, input) 272 ccresp, err := h.Execute(txParams, namespace, ccMsg, timeout) 273 if err != nil { 274 return nil, errors.WithMessage(err, "error sending") 275 } 276 277 return ccresp, nil 278 } 279 280 func (cs *ChaincodeSupport) executeTimeout(namespace string, input *pb.ChaincodeInput) time.Duration { 281 operation := chaincodeOperation(input.Args) 282 switch { 283 case namespace == "lscc" && operation == "install": 284 return maxDuration(cs.InstallTimeout, cs.ExecuteTimeout) 285 case namespace == lifecycle.LifecycleNamespace && operation == lifecycle.InstallChaincodeFuncName: 286 return maxDuration(cs.InstallTimeout, cs.ExecuteTimeout) 287 default: 288 return cs.ExecuteTimeout 289 } 290 } 291 292 func maxDuration(durations ...time.Duration) time.Duration { 293 var result time.Duration 294 for _, d := range durations { 295 if d > result { 296 result = d 297 } 298 } 299 return result 300 } 301 302 func chaincodeOperation(args [][]byte) string { 303 if len(args) == 0 { 304 return "" 305 } 306 return string(args[0]) 307 } 308 309 func logDevModeError(userRunsCC bool) { 310 if userRunsCC { 311 chaincodeLogger.Error("You are attempting to perform an action other than Deploy on Chaincode that is not ready and you are in developer mode. Did you forget to Deploy your chaincode?") 312 } 313 }