Skip to content

Commit 10fd1b2

Browse files
author
Jason Yellick
committed
FAB-14016 Wire new init check
This CR causes the chaincode package to enforce the new init semantics. It enforces that: * If the old lifecycle is in use, normal invocations are never treated as init. * If the new lifecycle is in use and the chaincod definition requires init, normal invocations of the function named 'init' are treated as init, and the chaincode must be initialized exactly once for each version of the chaincode. Change-Id: Ic838bbcdf1290e22bbcf33a0c6fd07bf9524f85e Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
1 parent 7ac72a5 commit 10fd1b2

17 files changed

+2066
-1095
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package chaincode_test
8+
9+
import (
10+
"fmt"
11+
12+
"github.com/hyperledger/fabric/core/chaincode"
13+
"github.com/hyperledger/fabric/core/chaincode/fake"
14+
"github.com/hyperledger/fabric/core/chaincode/mock"
15+
"github.com/hyperledger/fabric/core/common/ccprovider"
16+
pb "github.com/hyperledger/fabric/protos/peer"
17+
18+
. "github.com/onsi/ginkgo"
19+
. "github.com/onsi/gomega"
20+
)
21+
22+
var _ = Describe("ChaincodeSupport", func() {
23+
var (
24+
chaincodeSupport *chaincode.ChaincodeSupport
25+
26+
fakeApplicationConfigRetriever *fake.ApplicationConfigRetriever
27+
fakeApplicationConfig *mock.ApplicationConfig
28+
fakeApplicationCapabilities *mock.ApplicationCapabilities
29+
)
30+
31+
BeforeEach(func() {
32+
fakeApplicationCapabilities = &mock.ApplicationCapabilities{}
33+
fakeApplicationCapabilities.LifecycleV20Returns(true)
34+
35+
fakeApplicationConfig = &mock.ApplicationConfig{}
36+
fakeApplicationConfig.CapabilitiesReturns(fakeApplicationCapabilities)
37+
38+
fakeApplicationConfigRetriever = &fake.ApplicationConfigRetriever{}
39+
fakeApplicationConfigRetriever.GetApplicationConfigReturns(fakeApplicationConfig, true)
40+
41+
chaincodeSupport = &chaincode.ChaincodeSupport{
42+
AppConfig: fakeApplicationConfigRetriever,
43+
}
44+
})
45+
46+
Describe("CheckInit", func() {
47+
var (
48+
txParams *ccprovider.TransactionParams
49+
cccid *ccprovider.CCContext
50+
input *pb.ChaincodeInput
51+
52+
fakeSimulator *mock.TxSimulator
53+
)
54+
55+
BeforeEach(func() {
56+
fakeSimulator = &mock.TxSimulator{}
57+
fakeSimulator.GetStateReturns([]byte("old-cc-version"), nil)
58+
59+
txParams = &ccprovider.TransactionParams{
60+
ChannelID: "channel-id",
61+
TXSimulator: fakeSimulator,
62+
}
63+
64+
cccid = &ccprovider.CCContext{
65+
Name: "cc-name",
66+
Version: "cc-version",
67+
InitRequired: true,
68+
}
69+
70+
input = &pb.ChaincodeInput{
71+
Args: [][]byte{[]byte("init")},
72+
}
73+
})
74+
75+
It("indicates that it is init", func() {
76+
isInit, err := chaincodeSupport.CheckInit(txParams, cccid, input)
77+
Expect(err).NotTo(HaveOccurred())
78+
Expect(isInit).To(BeTrue())
79+
80+
Expect(fakeSimulator.GetStateCallCount()).To(Equal(1))
81+
namespace, key := fakeSimulator.GetStateArgsForCall(0)
82+
Expect(namespace).To(Equal("cc-name"))
83+
Expect(key).To(Equal("\x00\x00initialized"))
84+
85+
Expect(fakeSimulator.SetStateCallCount()).To(Equal(1))
86+
namespace, key, value := fakeSimulator.SetStateArgsForCall(0)
87+
Expect(namespace).To(Equal("cc-name"))
88+
Expect(key).To(Equal("\x00\x00initialized"))
89+
Expect(value).To(Equal([]byte("cc-version")))
90+
})
91+
92+
Context("when the version is not changed", func() {
93+
BeforeEach(func() {
94+
cccid.Version = "old-cc-version"
95+
})
96+
97+
It("returns an error", func() {
98+
_, err := chaincodeSupport.CheckInit(txParams, cccid, input)
99+
Expect(err).To(MatchError("chaincode 'cc-name' is already initialized but 'init' called"))
100+
})
101+
102+
Context("when the invocation is not 'init'", func() {
103+
BeforeEach(func() {
104+
input.Args = [][]byte{[]byte("my-func")}
105+
})
106+
107+
It("returns that it is not an init", func() {
108+
isInit, err := chaincodeSupport.CheckInit(txParams, cccid, input)
109+
Expect(err).NotTo(HaveOccurred())
110+
Expect(isInit).To(BeFalse())
111+
Expect(fakeSimulator.GetStateCallCount()).To(Equal(1))
112+
Expect(fakeSimulator.SetStateCallCount()).To(Equal(0))
113+
})
114+
115+
Context("when the invocation contains no arguments", func() {
116+
BeforeEach(func() {
117+
input.Args = nil
118+
})
119+
120+
It("returns that it is an init", func() {
121+
isInit, err := chaincodeSupport.CheckInit(txParams, cccid, input)
122+
Expect(err).NotTo(HaveOccurred())
123+
Expect(isInit).To(BeFalse())
124+
})
125+
})
126+
127+
})
128+
})
129+
130+
Context("when init is not required", func() {
131+
BeforeEach(func() {
132+
cccid.InitRequired = false
133+
})
134+
135+
It("returns that it is not init", func() {
136+
isInit, err := chaincodeSupport.CheckInit(txParams, cccid, input)
137+
Expect(err).NotTo(HaveOccurred())
138+
Expect(isInit).To(BeFalse())
139+
Expect(fakeSimulator.GetStateCallCount()).To(Equal(0))
140+
Expect(fakeSimulator.SetStateCallCount()).To(Equal(0))
141+
})
142+
})
143+
144+
Context("when the new lifecycle is not enabled", func() {
145+
BeforeEach(func() {
146+
fakeApplicationCapabilities.LifecycleV20Returns(false)
147+
})
148+
149+
It("returns that it is not init", func() {
150+
isInit, err := chaincodeSupport.CheckInit(txParams, cccid, input)
151+
Expect(err).NotTo(HaveOccurred())
152+
Expect(isInit).To(BeFalse())
153+
Expect(fakeSimulator.GetStateCallCount()).To(Equal(0))
154+
Expect(fakeSimulator.SetStateCallCount()).To(Equal(0))
155+
})
156+
})
157+
158+
Context("when the invocation is channel-less", func() {
159+
BeforeEach(func() {
160+
txParams.ChannelID = ""
161+
})
162+
163+
It("returns it is not an init", func() {
164+
isInit, err := chaincodeSupport.CheckInit(txParams, cccid, input)
165+
Expect(err).NotTo(HaveOccurred())
166+
Expect(isInit).To(BeFalse())
167+
Expect(fakeSimulator.GetStateCallCount()).To(Equal(0))
168+
Expect(fakeSimulator.SetStateCallCount()).To(Equal(0))
169+
})
170+
})
171+
172+
Context("when the application config cannot be retrieved", func() {
173+
BeforeEach(func() {
174+
fakeApplicationConfigRetriever.GetApplicationConfigReturns(nil, false)
175+
})
176+
177+
It("returns an error", func() {
178+
_, err := chaincodeSupport.CheckInit(txParams, cccid, input)
179+
Expect(err).To(MatchError("could not retrieve application config for channel 'channel-id'"))
180+
})
181+
})
182+
183+
Context("when the invocation is not 'init'", func() {
184+
BeforeEach(func() {
185+
input.Args = [][]byte{[]byte("my-func")}
186+
})
187+
188+
It("returns an error", func() {
189+
_, err := chaincodeSupport.CheckInit(txParams, cccid, input)
190+
Expect(err).To(MatchError("chaincode 'cc-name' has not been initialized for this version, must call 'init' first"))
191+
})
192+
})
193+
194+
Context("when the txsimulator cannot get state", func() {
195+
BeforeEach(func() {
196+
fakeSimulator.GetStateReturns(nil, fmt.Errorf("get-state-error"))
197+
})
198+
199+
It("wraps and returns the error", func() {
200+
_, err := chaincodeSupport.CheckInit(txParams, cccid, input)
201+
Expect(err).To(MatchError("could not get 'initialized' key: get-state-error"))
202+
})
203+
})
204+
205+
Context("when the txsimulator cannot set state", func() {
206+
BeforeEach(func() {
207+
fakeSimulator.SetStateReturns(fmt.Errorf("set-state-error"))
208+
})
209+
210+
It("wraps and returns the error", func() {
211+
_, err := chaincodeSupport.CheckInit(txParams, cccid, input)
212+
Expect(err).To(MatchError("could not set 'initialized' key: set-state-error"))
213+
})
214+
})
215+
})
216+
})

core/chaincode/chaincode_suite_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package chaincode_test
99
import (
1010
"testing"
1111

12+
"github.com/hyperledger/fabric/common/channelconfig"
1213
commonledger "github.com/hyperledger/fabric/common/ledger"
1314
"github.com/hyperledger/fabric/core/chaincode"
1415
"github.com/hyperledger/fabric/core/common/privdata"
@@ -146,3 +147,13 @@ type applicationConfigRetriever interface {
146147
type collectionStore interface {
147148
privdata.CollectionStore
148149
}
150+
151+
//go:generate counterfeiter -o mock/application_capabilities.go --fake-name ApplicationCapabilities . applicationCapabilities
152+
type applicationCapabilities interface {
153+
channelconfig.ApplicationCapabilities
154+
}
155+
156+
//go:generate counterfeiter -o mock/application_config.go --fake-name ApplicationConfig . applicationConfig
157+
type applicationConfig interface {
158+
channelconfig.Application
159+
}

core/chaincode/chaincode_support.go

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0
77
package chaincode
88

99
import (
10+
"bytes"
1011
"fmt"
1112
"time"
1213

@@ -23,6 +24,19 @@ import (
2324
"github.com/pkg/errors"
2425
)
2526

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\x00initialized"
34+
35+
// InitFunctionName is the reserved name that an invoker may specify to
36+
// trigger invocation of the chaincode init function.
37+
InitFunctionName = "init"
38+
)
39+
2640
// Runtime is used to manage chaincode runtime instances.
2741
type Runtime interface {
2842
Start(ccci *ccprovider.ChaincodeContainerInfo, codePackage []byte) error
@@ -55,7 +69,7 @@ type ChaincodeSupport struct {
5569
Launcher Launcher
5670
SystemCCProvider sysccprovider.SystemChaincodeProvider
5771
Lifecycle Lifecycle
58-
appConfig ApplicationConfigRetriever
72+
AppConfig ApplicationConfigRetriever
5973
HandlerMetrics *HandlerMetrics
6074
LaunchMetrics *LaunchMetrics
6175
DeployedCCInfoProvider ledger.DeployedChaincodeInfoProvider
@@ -86,7 +100,7 @@ func NewChaincodeSupport(
86100
ACLProvider: aclProvider,
87101
SystemCCProvider: SystemCCProvider,
88102
Lifecycle: lifecycle,
89-
appConfig: appConfig,
103+
AppConfig: appConfig,
90104
HandlerMetrics: NewHandlerMetrics(metricsProvider),
91105
LaunchMetrics: NewLaunchMetrics(metricsProvider),
92106
DeployedCCInfoProvider: deployedCCInfoProvider,
@@ -188,7 +202,7 @@ func (cs *ChaincodeSupport) HandleChaincodeStream(stream ccintf.ChaincodeStream)
188202
UUIDGenerator: UUIDGeneratorFunc(util.GenerateUUID),
189203
LedgerGetter: peer.Default,
190204
DeployedCCInfoProvider: cs.DeployedCCInfoProvider,
191-
AppConfig: cs.appConfig,
205+
AppConfig: cs.AppConfig,
192206
Metrics: cs.HandlerMetrics,
193207
}
194208

@@ -291,21 +305,72 @@ func (cs *ChaincodeSupport) Invoke(txParams *ccprovider.TransactionParams, cccid
291305
return nil, err
292306
}
293307

294-
// TODO add Init exactly once semantics here once new lifecycle
295-
// is available. Enforced if the target channel is using the new lifecycle
296-
//
297-
// First, the function name of the chaincode to invoke should be checked. If it is
298-
// "init", then consider this invocation to be of type pb.ChaincodeMessage_INIT,
299-
// otherwise consider it to be of type pb.ChaincodeMessage_TRANSACTION,
300-
//
301-
// Secondly, A check should be made whether the chaincode has been
302-
// inited, then, if true, only allow cctyp pb.ChaincodeMessage_TRANSACTION,
303-
// otherwise, only allow cctype pb.ChaincodeMessage_INIT,
308+
isInit, err := cs.CheckInit(txParams, cccid, input)
309+
if err != nil {
310+
return nil, err
311+
}
312+
304313
cctype := pb.ChaincodeMessage_TRANSACTION
314+
if isInit {
315+
cctype = pb.ChaincodeMessage_INIT
316+
}
305317

306318
return cs.execute(cctype, txParams, cccid, input, h)
307319
}
308320

321+
func (cs *ChaincodeSupport) CheckInit(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput) (bool, error) {
322+
if txParams.ChannelID == "" {
323+
// Channel-less invocations must be for SCCs, so, we ignore them for now
324+
return false, nil
325+
}
326+
327+
ac, ok := cs.AppConfig.GetApplicationConfig(txParams.ChannelID)
328+
if !ok {
329+
return false, errors.Errorf("could not retrieve application config for channel '%s'", txParams.ChannelID)
330+
}
331+
332+
if !ac.Capabilities().LifecycleV20() {
333+
return false, nil
334+
}
335+
336+
isInit := false
337+
338+
if len(input.Args) != 0 {
339+
isInit = string(input.Args[0]) == InitFunctionName
340+
}
341+
342+
if !cccid.InitRequired {
343+
// If Init is not required, treat this as a normal invocation
344+
// i.e. execute Invoke with 'init' as the function name
345+
return false, nil
346+
}
347+
348+
// At this point, we know we must enforce init exactly once semantics
349+
350+
value, err := txParams.TXSimulator.GetState(cccid.Name, InitializedKeyName)
351+
if err != nil {
352+
return false, errors.WithMessage(err, "could not get 'initialized' key")
353+
}
354+
355+
needsInitialization := !bytes.Equal(value, []byte(cccid.Version))
356+
357+
switch {
358+
case !isInit && !needsInitialization:
359+
return false, nil
360+
case !isInit && needsInitialization:
361+
return false, errors.Errorf("chaincode '%s' has not been initialized for this version, must call 'init' first", cccid.Name)
362+
case isInit && !needsInitialization:
363+
return false, errors.Errorf("chaincode '%s' is already initialized but 'init' called", cccid.Name)
364+
default:
365+
// isInit && needsInitialization:
366+
err = txParams.TXSimulator.SetState(cccid.Name, InitializedKeyName, []byte(cccid.Version))
367+
if err != nil {
368+
return false, errors.WithMessage(err, "could not set 'initialized' key")
369+
}
370+
return true, nil
371+
}
372+
}
373+
309374
// execute executes a transaction and waits for it to complete until a timeout value.
310375
func (cs *ChaincodeSupport) execute(cctyp pb.ChaincodeMessage_Type, txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput, h *Handler) (*pb.ChaincodeMessage, error) {
311376
input.Decorations = txParams.ProposalDecorations

0 commit comments

Comments
 (0)