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