lccc.go 10.8 KB
Newer Older
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
/*
Copyright IBM Corp. 2016 All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

		 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package lccc

import (
	"fmt"

	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric/core/chaincode"
	"github.com/hyperledger/fabric/core/chaincode/shim"
	pb "github.com/hyperledger/fabric/protos"
	"github.com/op/go-logging"
	"golang.org/x/net/context"
)

//The life cycle system chaincode manages chaincodes deployed
//on this peer. It manages chaincodes via Invoke proposals.
//     "Args":["deploy",<ChaincodeDeploymentSpec>]
//     "Args":["upgrade",<ChaincodeDeploymentSpec>]
//     "Args":["stop",<ChaincodeInvocationSpec>]
//     "Args":["start",<ChaincodeInvocationSpec>]

var logger = logging.MustGetLogger("lccc")

const (
	//CHAINCODETABLE prefix for chaincode tables
	CHAINCODETABLE = "chaincodes"

	//chaincode lifecyle commands

	//DEPLOY deploy command
	DEPLOY = "deploy"

	//chaincode query commands

	//GETCCINFO get chaincode
	GETCCINFO = "getid"
)

//---------- the LCCC -----------------

// LifeCycleSysCC implements chaincode lifecycle and policies aroud it
type LifeCycleSysCC struct {
}

//----------------errors---------------

//AlreadyRegisteredErr Already registered error
type AlreadyRegisteredErr string

func (f AlreadyRegisteredErr) Error() string {
	return fmt.Sprintf("%s already registered", string(f))
}

//InvalidFunctionErr invalid function error
type InvalidFunctionErr string

func (f InvalidFunctionErr) Error() string {
	return fmt.Sprintf("invalid function to lccc %s", string(f))
}

//InvalidArgsLenErr invalid arguments length error
type InvalidArgsLenErr int

func (i InvalidArgsLenErr) Error() string {
	return fmt.Sprintf("invalid number of argument to lccc %d", int(i))
}

//InvalidArgsErr invalid arguments error
type InvalidArgsErr int

func (i InvalidArgsErr) Error() string {
	return fmt.Sprintf("invalid argument (%d) to lccc", int(i))
}

//TXExistsErr transaction exists error
type TXExistsErr string

func (t TXExistsErr) Error() string {
	return fmt.Sprintf("transaction exists %s", string(t))
}

//TXNotFoundErr transaction not found error
type TXNotFoundErr string

func (t TXNotFoundErr) Error() string {
	return fmt.Sprintf("transaction not found %s", string(t))
}

//InvalidDeploymentSpecErr invalide chaincode deployment spec error
type InvalidDeploymentSpecErr string

func (f InvalidDeploymentSpecErr) Error() string {
	return fmt.Sprintf("Invalid deployment spec : %s", string(f))
}

//ChaincodeExistsErr chaincode exists error
type ChaincodeExistsErr string

func (t ChaincodeExistsErr) Error() string {
	return fmt.Sprintf("Chaincode exists %s", string(t))
}

//InvalidChainNameErr invalid function error
type InvalidChainNameErr string

func (f InvalidChainNameErr) Error() string {
	return fmt.Sprintf("invalid chain name %s", string(f))
}

//-------------- helper functions ------------------
//create the table to maintain list of chaincodes maintained in this
//blockchain.
func (lccc *LifeCycleSysCC) createChaincodeTable(stub shim.ChaincodeStubInterface, cctable string) error {
	// Create table one
	var colDefs []*shim.ColumnDefinition
	nameColDef := shim.ColumnDefinition{Name: "name",
		Type: shim.ColumnDefinition_STRING, Key: true}
	versColDef := shim.ColumnDefinition{Name: "version",
		Type: shim.ColumnDefinition_INT32, Key: false}

	//QUESTION - Should code be separately maintained ?
	codeDef := shim.ColumnDefinition{Name: "code",
		Type: shim.ColumnDefinition_BYTES, Key: false}
	colDefs = append(colDefs, &nameColDef)
	colDefs = append(colDefs, &versColDef)
	colDefs = append(colDefs, &codeDef)
	return stub.CreateTable(cctable, colDefs)
}

//register create the chaincode table. name can be used to different
//tables of chaincodes. This would provide the way to associate chaincodes
//with chains(and ledgers)
func (lccc *LifeCycleSysCC) register(stub shim.ChaincodeStubInterface, name string) error {
	ccname := CHAINCODETABLE + "-" + name

	row, err := stub.GetTable(ccname)
	if err == nil && row != nil { //table exists, do nothing
		return AlreadyRegisteredErr(name)
	}

	//there may be other err's but assume "not exists". Anything
	//more serious than that bound to show up
	err = lccc.createChaincodeTable(stub, ccname)

	return err
}

//create the chaincode on the given chain
func (lccc *LifeCycleSysCC) createChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string, cccode []byte) (*shim.Row, error) {
	var columns []*shim.Column

	nameCol := shim.Column{Value: &shim.Column_String_{String_: ccname}}
	versCol := shim.Column{Value: &shim.Column_Int32{Int32: 0}}
	codeCol := shim.Column{Value: &shim.Column_Bytes{Bytes: cccode}}

	columns = append(columns, &nameCol)
	columns = append(columns, &versCol)
	columns = append(columns, &codeCol)

	row := &shim.Row{Columns: columns}
	_, err := stub.InsertRow(CHAINCODETABLE+"-"+chainname, *row)
	if err != nil {
		return nil, fmt.Errorf("insertion of chaincode failed. %s", err)
	}
	return row, nil
}

//checks for existence of chaincode on the given chain
func (lccc *LifeCycleSysCC) getChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string) (shim.Row, bool, error) {
	var columns []shim.Column
	nameCol := shim.Column{Value: &shim.Column_String_{String_: ccname}}
	columns = append(columns, nameCol)

	row, err := stub.GetRow(CHAINCODETABLE+"-"+chainname, columns)
	if err != nil {
		return shim.Row{}, false, err
	}

	if len(row.Columns) > 0 {
		return row, true, nil
	}
	return row, false, nil
}

//getChaincodeDeploymentSpec returns a ChaincodeDeploymentSpec given args
func (lccc *LifeCycleSysCC) getChaincodeDeploymentSpec(code []byte) (*pb.ChaincodeDeploymentSpec, error) {
	cds := &pb.ChaincodeDeploymentSpec{}

	err := proto.Unmarshal(code, cds)
	if err != nil {
		return nil, InvalidDeploymentSpecErr(err.Error())
	}

	return cds, nil
}

//do access control
func (lccc *LifeCycleSysCC) acl(stub shim.ChaincodeStubInterface, chainname chaincode.ChainName, cds *pb.ChaincodeDeploymentSpec) error {
	return nil
}

//check validity of chain name
func (lccc *LifeCycleSysCC) isValidChainName(chainname string) bool {
	//TODO we probably need more checks and have
	if chainname == "" {
		return false
	}
	return true
}

//deploy the chaincode on to the chain
func (lccc *LifeCycleSysCC) deploy(stub shim.ChaincodeStubInterface, chainname string, cds *pb.ChaincodeDeploymentSpec) error {
	_, exists, err := lccc.getChaincode(stub, chainname, cds.ChaincodeSpec.ChaincodeID.Name)
	if exists {
		return ChaincodeExistsErr(cds.ChaincodeSpec.ChaincodeID.Name)
	}

	//TODO : this needs to be converted to another data structure to be handled
	//       by the chaincode framework (which currently handles "Transaction")
	t, err := lccc.toTransaction(cds)
	if err != nil {
		return fmt.Errorf("could not convert proposal to transaction %s", err)
	}

	//if unit testing, just return..we cannot do the actual deploy
	if _, ismock := stub.(*shim.MockStub); ismock {
		//we got this far just stop short of actual deploy for test purposes
		return nil
	}

	ctxt := context.Background()

	//TODO - create chaincode support for chainname, for now use DefaultChain
	//chaincodeSupport := chaincode.GetChain(chaincode.ChainName(chainname))
	chaincodeSupport := chaincode.GetChain(chaincode.DefaultChain)
	_, err = chaincodeSupport.Deploy(ctxt, t)
	if err != nil {
		return fmt.Errorf("Failed to deploy chaincode spec(%s)", err)
	}

	//launch and wait for ready
	_, _, err = chaincodeSupport.Launch(ctxt, t)
	if err != nil {
		return fmt.Errorf("%s", err)
	}

	/************ STOP WHEN WE ARE ON NEWLEDGER
	//stop now that we are done
	chaincodeSupport.Stop(ctxt, cds)
	**********/
	return nil
}

//this implements "deploy" Invoke transaction
func (lccc *LifeCycleSysCC) executeDeploy(stub shim.ChaincodeStubInterface, chainname string, code []byte) error {
	//lazy creation of chaincode table for chainname...its possible
	//there are chains without chaincodes
	if err := lccc.register(stub, chainname); err != nil {
		//if its already registered, ok... proceed
		if _, ok := err.(AlreadyRegisteredErr); !ok {
			return err
		}
	}

	cds, err := lccc.getChaincodeDeploymentSpec(code)

	if err != nil {
		return err
	}

	if err = lccc.acl(stub, chaincode.DefaultChain, cds); err != nil {
		return err
	}

	if err = lccc.deploy(stub, chainname, cds); err != nil {
		return err
	}

	_, err = lccc.createChaincode(stub, chainname, cds.ChaincodeSpec.ChaincodeID.Name, cds.CodePackage)

	return err
}

//TODO - this is temporary till we use Transaction in chaincode code
func (lccc *LifeCycleSysCC) toTransaction(cds *pb.ChaincodeDeploymentSpec) (*pb.Transaction, error) {
	return pb.NewChaincodeDeployTransaction(cds, cds.ChaincodeSpec.ChaincodeID.Name)
}

//-------------- the chaincode stub interface implementation ----------

//Init does nothing
func (lccc *LifeCycleSysCC) Init(stub shim.ChaincodeStubInterface) ([]byte, error) {
	return nil, nil
}

// Invoke implements lifecycle functions "deploy", "start", "stop", "upgrade".
// Deploy's arguments -  {[]byte("deploy"), []byte(<chainname>), <unmarshalled pb.ChaincodeDeploymentSpec>}
//
// Invoke also implements some query-like functions
// Get chaincode arguments -  {[]byte("getid"), []byte(<chainname>), []byte(<chaincodename>)}
func (lccc *LifeCycleSysCC) Invoke(stub shim.ChaincodeStubInterface) ([]byte, error) {
	args := stub.GetArgs()
	if len(args) < 1 {
		return nil, InvalidArgsLenErr(len(args))
	}

	function := string(args[0])

	switch function {
	case DEPLOY:
		if len(args) != 3 {
			return nil, InvalidArgsLenErr(len(args))
		}

		//chain the chaincode shoud be associated with. It
		//should be created with a register call
		chainname := string(args[1])

		if !lccc.isValidChainName(chainname) {
			return nil, InvalidChainNameErr(chainname)
		}

		//bytes corresponding to deployment spec
		code := args[2]

		err := lccc.executeDeploy(stub, chainname, code)

		return nil, err
	case GETCCINFO:
		if len(args) != 3 {
			return nil, InvalidArgsLenErr(len(args))
		}

		chain := string(args[1])
		ccname := string(args[2])
		//get chaincode given <chain, name>

		ccrow, exists, _ := lccc.getChaincode(stub, chain, ccname)
		if !exists {
			logger.Debug("ChaincodeID [%s/%s] does not exist", chain, ccname)
			return nil, TXNotFoundErr(chain + "/" + ccname)
		}

		return []byte(ccrow.Columns[1].GetString_()), nil
	}

	return nil, InvalidFunctionErr(function)
}

// Query is no longer implemented. Will be removed
func (lccc *LifeCycleSysCC) Query(stub shim.ChaincodeStubInterface) ([]byte, error) {
	return nil, nil
}