Chaincode is a program, written in Go that implements a
prescribed interface. Eventually, other programming languages such as Java,
will be supported. Chaincode runs in a secured Docker container isolated from
the endorsing peer process. Chaincode initializes and manages the ledger state
through transactions submitted by applications.

A chaincode typically handles business logic agreed to by members of the
network, so it similar to a “smart contract”. Ledger state created by
a chaincode is scoped exclusively to that chaincode and can’t be accessed
directly by another chaincode. Given the appropriate permission, a chaincode may
invoke another chaincode to access its state within the same network.

In the following sections, we will explore chaincode through the eyes of an
application developer. We’ll present a simple chaincode sample application
and walk through the purpose of each method in the Chaincode Shim API.

Every chaincode program must implement the
Chaincode interface
whose methods are called in response to received transactions.
In particular the Init method is called when a
chaincode receives an instantiate or upgrade transaction so that the
chaincode may perform any necessary initialization, including initialization of
application state. The Invoke method is called in response to receiving an
invoke transaction to process transaction proposals.

The other interface in the chaincode “shim” APIs is the
ChaincodeStubInterface
which is used to access and modify the ledger, and to make invocations between
chaincodes.

In this tutorial, we will demonstrate the use of these APIs by implementing a
simple chaincode application that manages simple “assets”.

First, let’s start with some housekeeping. As with every chaincode, it implements the
Chaincode interface
in particular, Init and Invoke functions. So, let’s add the go import
statements for the necessary dependencies for our chaincode. We’ll import the
chaincode shim package and the
peer protobuf package.
Next, let’s add a struct SimpleAsset as a receiver for Chaincode shim functions.

Note that chaincode upgrade also calls this function. When writing a
chaincode that will upgrade an existing one, make sure to modify the Init
function appropriately. In particular, provide an empty “Init” method if there’s
no “migration” or nothing to be initialized as part of the upgrade.

//Initiscalledduringchaincodeinstantiationtoinitializeany//data.Notethatchaincodeupgradealsocallsthisfunctiontoreset//ortomigratedata,sobecarefultoavoidascenariowhereyou//inadvertentlyclobberyourledger's data!func(t*SimpleAsset)Init(stubshim.ChaincodeStubInterface)peer.Response{//Gettheargsfromthetransactionproposalargs:=stub.GetStringArgs()iflen(args)!=2{returnshim.Error("Incorrect arguments. Expecting a key and a value")}}

Next, now that we have established that the call is valid, we’ll store the
initial state in the ledger. To do this, we will call
ChaincodeStubInterface.PutState
with the key and value passed in as the arguments. Assuming all went well,
return a peer.Response object that indicates the initialization was a success.

//Initiscalledduringchaincodeinstantiationtoinitializeany//data.Notethatchaincodeupgradealsocallsthisfunctiontoreset//ortomigratedata,sobecarefultoavoidascenariowhereyou//inadvertentlyclobberyourledger's data!func(t*SimpleAsset)Init(stubshim.ChaincodeStubInterface)peer.Response{//Gettheargsfromthetransactionproposalargs:=stub.GetStringArgs()iflen(args)!=2{returnshim.Error("Incorrect arguments. Expecting a key and a value")}//Setupanyvariablesorassetsherebycallingstub.PutState()//Westorethekeyandthevalueontheledgererr:=stub.PutState(args[0],[]byte(args[1]))iferr!=nil{returnshim.Error(fmt.Sprintf("Failed to create asset: %s",args[0]))}returnshim.Success(nil)}

As with the Init function above, we need to extract the arguments from the
ChaincodeStubInterface. The Invoke function’s arguments will be the
name of the chaincode application function to invoke. In our case, our application
will simply have two functions: set and get, that allow the value of an
asset to be set or its current state to be retrieved. We first call
ChaincodeStubInterface.GetFunctionAndParameters
to extract the function name and the parameters to that chaincode application
function.

Next, we’ll validate the function name as being either set or get, and
invoke those chaincode application functions, returning an appropriate
response via the shim.Success or shim.Error functions that will
serialize the response into a gRPC protobuf message.

As noted, our chaincode application implements two functions that can be
invoked via the Invoke function. Let’s implement those functions now.
Note that as we mentioned above, to access the ledger’s state, we will leverage
the ChaincodeStubInterface.PutState
and ChaincodeStubInterface.GetState
functions of the chaincode shim API.

//Setstorestheasset(bothkeyandvalue)ontheledger.Ifthekeyexists,//itwilloverridethevaluewiththenewonefuncset(stubshim.ChaincodeStubInterface,args[]string)(string,error){iflen(args)!=2{return"",fmt.Errorf("Incorrect arguments. Expecting a key and a value")}err:=stub.PutState(args[0],[]byte(args[1]))iferr!=nil{return"",fmt.Errorf("Failed to set asset: %s",args[0])}returnargs[1],nil}//Getreturnsthevalueofthespecifiedassetkeyfuncget(stubshim.ChaincodeStubInterface,args[]string)(string,error){iflen(args)!=1{return"",fmt.Errorf("Incorrect arguments. Expecting a key")}value,err:=stub.GetState(args[0])iferr!=nil{return"",fmt.Errorf("Failed to get asset: %s with error: %s",args[0],err)}ifvalue==nil{return"",fmt.Errorf("Asset not found: %s",args[0])}returnstring(value),nil}

Finally, we need to add the main function, which will call the
shim.Start
function. Here’s the whole chaincode program source.

packagemainimport("fmt""github.com/hyperledger/fabric/core/chaincode/shim""github.com/hyperledger/fabric/protos/peer")//SimpleAssetimplementsasimplechaincodetomanageanassettypeSimpleAssetstruct{}//Initiscalledduringchaincodeinstantiationtoinitializeany//data.Notethatchaincodeupgradealsocallsthisfunctiontoreset//ortomigratedata.func(t*SimpleAsset)Init(stubshim.ChaincodeStubInterface)peer.Response{//Gettheargsfromthetransactionproposalargs:=stub.GetStringArgs()iflen(args)!=2{returnshim.Error("Incorrect arguments. Expecting a key and a value")}//Setupanyvariablesorassetsherebycallingstub.PutState()//Westorethekeyandthevalueontheledgererr:=stub.PutState(args[0],[]byte(args[1]))iferr!=nil{returnshim.Error(fmt.Sprintf("Failed to create asset: %s",args[0]))}returnshim.Success(nil)}//Invokeiscalledpertransactiononthechaincode.Eachtransactionis//eithera'get'ora'set'ontheassetcreatedbyInitfunction.TheSet//methodmaycreateanewassetbyspecifyinganewkey-valuepair.func(t*SimpleAsset)Invoke(stubshim.ChaincodeStubInterface)peer.Response{//Extractthefunctionandargsfromthetransactionproposalfn,args:=stub.GetFunctionAndParameters()varresultstringvarerrerroriffn=="set"{result,err=set(stub,args)}else{//assume'get'eveniffnisnilresult,err=get(stub,args)}iferr!=nil{returnshim.Error(err.Error())}//Returntheresultassuccesspayloadreturnshim.Success([]byte(result))}//Setstorestheasset(bothkeyandvalue)ontheledger.Ifthekeyexists,//itwilloverridethevaluewiththenewonefuncset(stubshim.ChaincodeStubInterface,args[]string)(string,error){iflen(args)!=2{return"",fmt.Errorf("Incorrect arguments. Expecting a key and a value")}err:=stub.PutState(args[0],[]byte(args[1]))iferr!=nil{return"",fmt.Errorf("Failed to set asset: %s",args[0])}returnargs[1],nil}//Getreturnsthevalueofthespecifiedassetkeyfuncget(stubshim.ChaincodeStubInterface,args[]string)(string,error){iflen(args)!=1{return"",fmt.Errorf("Incorrect arguments. Expecting a key")}value,err:=stub.GetState(args[0])iferr!=nil{return"",fmt.Errorf("Failed to get asset: %s with error: %s",args[0],err)}ifvalue==nil{return"",fmt.Errorf("Asset not found: %s",args[0])}returnstring(value),nil}//mainfunctionstartsupthechaincodeinthecontainerduringinstantiatefuncmain(){iferr:=shim.Start(new(SimpleAsset));err!=nil{fmt.Printf("Error starting SimpleAsset chaincode: %s",err)}}

Normally chaincodes are started and maintained by peer. However in “dev
mode”, chaincode is built and started by the user. This mode is useful
during chaincode development phase for rapid code/build/run/debug cycle
turnaround.

We start “dev mode” by leveraging pre-generated orderer and channel artifacts for
a sample dev network. As such, the user can immediately jump into the process
of compiling chaincode and driving calls.

We need four Docker images in order for “dev mode” to run against the supplied
docker compose script. If you installed the fabric-samples repo clone and
followed the instructions to download-platform-specific-binaries, then
you should have the necessary Docker images installed locally.

注解

If you choose to manually pull the images then you must retag them as
latest.

Issue a dockerimages command to reveal your local Docker Registry. You
should see something similar to following:

The above starts the network with the SingleSampleMSPSolo orderer profile and
launches the peer in “dev mode”. It also launches two additional containers -
one for the chaincode environment and a CLI to interact with the chaincode. The
commands for create and join channel are embedded in the CLI container, so we
can jump immediately to the chaincode calls.

The chaincode is started with peer and chaincode logs indicating successful registration with the peer.
Note that at this stage the chaincode is not associated with any channel. This is done in subsequent steps
using the instantiate command.

Even though you are in --peer-chaincodedev mode, you still have to install the
chaincode so the life-cycle system chaincode can go through its checks normally.
This requirement may be removed in future when in --peer-chaincodedev mode.

By default, we mount only sacc. However, you can easily test different
chaincodes by adding them to the chaincode subdirectory and relaunching
your network. At this point they will be accessible in your chaincode container.