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