fabric-docs-i18n
fabric-docs-i18n copied to clipboard
[ja_JP] Tutorials / Writing Your First Chaincode
Original HTML: https://hyperledger-fabric.readthedocs.io/en/release-2.5/chaincode4ade.html Original Source: https://github.com/hyperledger/fabric/blob/e1e8e2e52aa4fc543360d245fe6554a0eaf81183/docs/source/chaincode4ade.rst
diff --git a/docs/source/chaincode4ade.rst b/docs/source/chaincode4ade.rst
index 44253bdc4..28b4ba480 100644
--- a/docs/source/chaincode4ade.rst
+++ b/docs/source/chaincode4ade.rst
@@ -72,7 +72,7 @@ To keep things simple, let's use the following command:
.. code:: bash
// atcc is shorthand for asset transfer chaincode
- mkdir atcc && cd atcc
+ mkdir atcc && cd atcc
Now, let's create the module and the source file that we'll fill in with code:
@@ -95,28 +95,35 @@ fabric contract api package and define our SmartContract.
import (
"fmt"
+ "encoding/json"
"log"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// SmartContract provides functions for managing an Asset
- type SmartContract struct {
- contractapi.Contract
- }
+ type SmartContract struct {
+ contractapi.Contract
+ }
Next, let's add a struct ``Asset`` to represent simple assets on the ledger.
Note the JSON annotations, which will be used to marshal the asset to JSON which is stored on the ledger.
+JSON though is not a deterministic data format - the order of elements can change, whilst still representing the same data semantically.
+The challenge, therefore, is to be able to generate a consistent set of JSON.
+Below is also shown a good approach to achieve consistency which consists of creating an asset object struct following alphabetic order.
.. code:: go
// Asset describes basic details of what makes up a simple asset
- type Asset struct {
- ID string `json:"ID"`
- Color string `json:"color"`
- Size int `json:"size"`
- Owner string `json:"owner"`
- AppraisedValue int `json:"appraisedValue"`
- }
+ // Insert struct field in alphabetic order => to achieve determinism accross languages
+ // golang keeps the order when marshal to json but doesn't order automatically
+
+ type Asset struct {
+ AppraisedValue int `json:"AppraisedValue"`
+ Color string `json:"Color"`
+ ID string `json:"ID"`
+ Owner string `json:"Owner"`
+ Size int `json:"Size"`
+ }
Initializing the Chaincode
^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -126,31 +133,31 @@ Next, we'll implement the ``InitLedger`` function to populate the ledger with so
.. code:: go
// InitLedger adds a base set of assets to the ledger
- func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
- assets := []Asset{
- {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
- {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
- {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
- {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
- {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
- {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
- }
-
- for _, asset := range assets {
- assetJSON, err := json.Marshal(asset)
- if err != nil {
+ func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
+ assets := []Asset{
+ {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
+ {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
+ {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
+ {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
+ {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
+ {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
+ }
+
+ for _, asset := range assets {
+ assetJSON, err := json.Marshal(asset)
+ if err != nil {
return err
- }
+ }
- err = ctx.GetStub().PutState(asset.ID, assetJSON)
- if err != nil {
+ err = ctx.GetStub().PutState(asset.ID, assetJSON)
+ if err != nil {
return fmt.Errorf("failed to put to world state. %v", err)
- }
}
-
- return nil
}
+ return nil
+ }
+
Next, we write a function to create an asset on the ledger that does not yet exist. When writing chaincode, it
is a good idea to check for the existence of something on the ledger prior to taking an action on it, as is demonstrated
in the ``CreateAsset`` function below.
@@ -159,29 +166,29 @@ in the ``CreateAsset`` function below.
.. code:: go
// CreateAsset issues a new asset to the world state with given details.
- func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- if exists {
- return fmt.Errorf("the asset %s already exists", id)
- }
-
- asset := Asset{
- ID: id,
- Color: color,
- Size: size,
- Owner: owner,
- AppraisedValue: appraisedValue,
- }
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
+ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
+ exists, err := s.AssetExists(ctx, id)
+ if err != nil {
+ return err
+ }
+ if exists {
+ return fmt.Errorf("the asset %s already exists", id)
+ }
- return ctx.GetStub().PutState(id, assetJSON)
+ asset := Asset{
+ ID: id,
+ Color: color,
+ Size: size,
+ Owner: owner,
+ AppraisedValue: appraisedValue,
}
+ assetJSON, err := json.Marshal(asset)
+ if err != nil {
+ return err
+ }
+
+ return ctx.GetStub().PutState(id, assetJSON)
+ }
Now that we have populated the ledger with some initial assets and created an asset,
let's write a function ``ReadAsset`` that allows us to read an asset from the ledger.
@@ -189,72 +196,72 @@ let's write a function ``ReadAsset`` that allows us to read an asset from the le
.. code:: go
// ReadAsset returns the asset stored in the world state with given id.
- func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return nil, fmt.Errorf("failed to read from world state: %v", err)
- }
- if assetJSON == nil {
- return nil, fmt.Errorf("the asset %s does not exist", id)
- }
-
- var asset Asset
- err = json.Unmarshal(assetJSON, &asset)
- if err != nil {
- return nil, err
- }
+ func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
+ assetJSON, err := ctx.GetStub().GetState(id)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read from world state: %v", err)
+ }
+ if assetJSON == nil {
+ return nil, fmt.Errorf("the asset %s does not exist", id)
+ }
- return &asset, nil
+ var asset Asset
+ err = json.Unmarshal(assetJSON, &asset)
+ if err != nil {
+ return nil, err
}
+ return &asset, nil
+ }
+
Now that we have assets on our ledger we can interact with, let's write a chaincode function
``UpdateAsset`` that allows us to update attributes of the asset that we are allowed to change.
.. code:: go
// UpdateAsset updates an existing asset in the world state with provided parameters.
- func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- if !exists {
- return fmt.Errorf("the asset %s does not exist", id)
- }
-
- // overwriting original asset with new asset
- asset := Asset{
- ID: id,
- Color: color,
- Size: size,
- Owner: owner,
- AppraisedValue: appraisedValue,
- }
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
+ func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
+ exists, err := s.AssetExists(ctx, id)
+ if err != nil {
+ return err
+ }
+ if !exists {
+ return fmt.Errorf("the asset %s does not exist", id)
+ }
- return ctx.GetStub().PutState(id, assetJSON)
+ // overwriting original asset with new asset
+ asset := Asset{
+ ID: id,
+ Color: color,
+ Size: size,
+ Owner: owner,
+ AppraisedValue: appraisedValue,
+ }
+ assetJSON, err := json.Marshal(asset)
+ if err != nil {
+ return err
}
+ return ctx.GetStub().PutState(id, assetJSON)
+ }
+
There may be cases where we need the ability to delete an asset from the ledger,
so let's write a ``DeleteAsset`` function to handle that requirement.
.. code:: go
// DeleteAsset deletes an given asset from the world state.
- func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- if !exists {
- return fmt.Errorf("the asset %s does not exist", id)
- }
+ func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
+ exists, err := s.AssetExists(ctx, id)
+ if err != nil {
+ return err
+ }
+ if !exists {
+ return fmt.Errorf("the asset %s does not exist", id)
+ }
- return ctx.GetStub().DelState(id)
- }
+ return ctx.GetStub().DelState(id)
+ }
We said earlier that is was a good idea to check to see if an asset exists before
@@ -263,34 +270,34 @@ taking an action on it, so let's write a function called ``AssetExists`` to impl
.. code:: go
// AssetExists returns true when asset with given ID exists in world state
- func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return false, fmt.Errorf("failed to read from world state: %v", err)
- }
+ func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
+ assetJSON, err := ctx.GetStub().GetState(id)
+ if err != nil {
+ return false, fmt.Errorf("failed to read from world state: %v", err)
+ }
- return assetJSON != nil, nil
- }
+ return assetJSON != nil, nil
+ }
Next, we'll write a function we'll call ``TransferAsset`` that enables the transfer of an asset from one owner to another.
.. code:: go
// TransferAsset updates the owner field of asset with given id in world state.
- func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {
- asset, err := s.ReadAsset(ctx, id)
- if err != nil {
- return err
- }
+ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {
+ asset, err := s.ReadAsset(ctx, id)
+ if err != nil {
+ return err
+ }
- asset.Owner = newOwner
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
+ asset.Owner = newOwner
+ assetJSON, err := json.Marshal(asset)
+ if err != nil {
+ return err
+ }
- return ctx.GetStub().PutState(id, assetJSON)
- }
+ return ctx.GetStub().PutState(id, assetJSON)
+ }
Let's write a function we'll call ``GetAllAssets`` that enables the querying of the ledger to
return all of the assets on the ledger.
@@ -298,33 +305,33 @@ return all of the assets on the ledger.
.. code:: go
// GetAllAssets returns all assets found in world state
- func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
- // range query with empty string for startKey and endKey does an
- // open-ended query of all assets in the chaincode namespace.
- resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
+ func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
+ // range query with empty string for startKey and endKey does an
+ // open-ended query of all assets in the chaincode namespace.
+ resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
+ if err != nil {
+ return nil, err
+ }
+ defer resultsIterator.Close()
+
+ var assets []*Asset
+ for resultsIterator.HasNext() {
+ queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
- defer resultsIterator.Close()
-
- var assets []*Asset
- for resultsIterator.HasNext() {
- queryResponse, err := resultsIterator.Next()
- if err != nil {
- return nil, err
- }
-
- var asset Asset
- err = json.Unmarshal(queryResponse.Value, &asset)
- if err != nil {
- return nil, err
- }
- assets = append(assets, &asset)
- }
- return assets, nil
+ var asset Asset
+ err = json.Unmarshal(queryResponse.Value, &asset)
+ if err != nil {
+ return nil, err
+ }
+ assets = append(assets, &asset)
}
+ return assets, nil
+ }
+
.. _Chaincode Sample:
@@ -332,7 +339,7 @@ return all of the assets on the ledger.
to keep this tutorial as clear and straightforward as possible. In a
real-world implementation, it is likely that packages will be segmented
where a ``main`` package imports the chaincode package to allow for easy unit testing.
- To see what this looks like, see the asset-transfer `Go chaincode <https://github.com/hyperledger/fabric-samples/tree/master/asset-transfer-basic/chaincode-go>`__
+ To see what this looks like, see the asset-transfer `Go chaincode <https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic/chaincode-go>`__
in fabric-samples. If you look at ``assetTransfer.go``, you will see that
it contains ``package main`` and imports ``package chaincode`` defined in ``smartcontract.go`` and
located at ``fabric-samples/asset-transfer-basic/chaincode-go/chaincode/``.
@@ -359,193 +366,193 @@ function. Here's the whole chaincode program source.
)
// SmartContract provides functions for managing an Asset
- type SmartContract struct {
- contractapi.Contract
- }
+ type SmartContract struct {
+ contractapi.Contract
+ }
// Asset describes basic details of what makes up a simple asset
- type Asset struct {
- ID string `json:"ID"`
- Color string `json:"color"`
- Size int `json:"size"`
- Owner string `json:"owner"`
- AppraisedValue int `json:"appraisedValue"`
- }
+ type Asset struct {
+ ID string `json:"ID"`
+ Color string `json:"color"`
+ Size int `json:"size"`
+ Owner string `json:"owner"`
+ AppraisedValue int `json:"appraisedValue"`
+ }
// InitLedger adds a base set of assets to the ledger
- func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
- assets := []Asset{
- {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
- {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
- {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
- {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
- {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
- {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
- }
-
- for _, asset := range assets {
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- err = ctx.GetStub().PutState(asset.ID, assetJSON)
- if err != nil {
- return fmt.Errorf("failed to put to world state. %v", err)
- }
- }
-
- return nil
+ func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
+ assets := []Asset{
+ {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
+ {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
+ {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
+ {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
+ {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
+ {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
}
- // CreateAsset issues a new asset to the world state with given details.
- func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
- exists, err := s.AssetExists(ctx, id)
+ for _, asset := range assets {
+ assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
- if exists {
- return fmt.Errorf("the asset %s already exists", id)
- }
- asset := Asset{
- ID: id,
- Color: color,
- Size: size,
- Owner: owner,
- AppraisedValue: appraisedValue,
- }
- assetJSON, err := json.Marshal(asset)
+ err = ctx.GetStub().PutState(asset.ID, assetJSON)
if err != nil {
- return err
+ return fmt.Errorf("failed to put to world state. %v", err)
}
+ }
- return ctx.GetStub().PutState(id, assetJSON)
+ return nil
+ }
+
+ // CreateAsset issues a new asset to the world state with given details.
+ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
+ exists, err := s.AssetExists(ctx, id)
+ if err != nil {
+ return err
+ }
+ if exists {
+ return fmt.Errorf("the asset %s already exists", id)
}
- // ReadAsset returns the asset stored in the world state with given id.
- func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return nil, fmt.Errorf("failed to read from world state: %v", err)
- }
- if assetJSON == nil {
- return nil, fmt.Errorf("the asset %s does not exist", id)
- }
+ asset := Asset{
+ ID: id,
+ Color: color,
+ Size: size,
+ Owner: owner,
+ AppraisedValue: appraisedValue,
+ }
+ assetJSON, err := json.Marshal(asset)
+ if err != nil {
+ return err
+ }
- var asset Asset
- err = json.Unmarshal(assetJSON, &asset)
- if err != nil {
- return nil, err
- }
+ return ctx.GetStub().PutState(id, assetJSON)
+ }
- return &asset, nil
+ // ReadAsset returns the asset stored in the world state with given id.
+ func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
+ assetJSON, err := ctx.GetStub().GetState(id)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read from world state: %v", err)
+ }
+ if assetJSON == nil {
+ return nil, fmt.Errorf("the asset %s does not exist", id)
}
- // UpdateAsset updates an existing asset in the world state with provided parameters.
- func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- if !exists {
- return fmt.Errorf("the asset %s does not exist", id)
- }
+ var asset Asset
+ err = json.Unmarshal(assetJSON, &asset)
+ if err != nil {
+ return nil, err
+ }
- // overwriting original asset with new asset
- asset := Asset{
- ID: id,
- Color: color,
- Size: size,
- Owner: owner,
- AppraisedValue: appraisedValue,
- }
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
+ return &asset, nil
+ }
- return ctx.GetStub().PutState(id, assetJSON)
+ // UpdateAsset updates an existing asset in the world state with provided parameters.
+ func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
+ exists, err := s.AssetExists(ctx, id)
+ if err != nil {
+ return err
+ }
+ if !exists {
+ return fmt.Errorf("the asset %s does not exist", id)
}
- // DeleteAsset deletes an given asset from the world state.
- func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- if !exists {
- return fmt.Errorf("the asset %s does not exist", id)
- }
+ // overwriting original asset with new asset
+ asset := Asset{
+ ID: id,
+ Color: color,
+ Size: size,
+ Owner: owner,
+ AppraisedValue: appraisedValue,
+ }
+ assetJSON, err := json.Marshal(asset)
+ if err != nil {
+ return err
+ }
+
+ return ctx.GetStub().PutState(id, assetJSON)
+ }
- return ctx.GetStub().DelState(id)
+ // DeleteAsset deletes an given asset from the world state.
+ func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
+ exists, err := s.AssetExists(ctx, id)
+ if err != nil {
+ return err
+ }
+ if !exists {
+ return fmt.Errorf("the asset %s does not exist", id)
}
- // AssetExists returns true when asset with given ID exists in world state
- func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return false, fmt.Errorf("failed to read from world state: %v", err)
- }
+ return ctx.GetStub().DelState(id)
+ }
- return assetJSON != nil, nil
+ // AssetExists returns true when asset with given ID exists in world state
+ func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
+ assetJSON, err := ctx.GetStub().GetState(id)
+ if err != nil {
+ return false, fmt.Errorf("failed to read from world state: %v", err)
}
- // TransferAsset updates the owner field of asset with given id in world state.
- func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {
- asset, err := s.ReadAsset(ctx, id)
- if err != nil {
- return err
- }
+ return assetJSON != nil, nil
+ }
- asset.Owner = newOwner
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
+ // TransferAsset updates the owner field of asset with given id in world state.
+ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {
+ asset, err := s.ReadAsset(ctx, id)
+ if err != nil {
+ return err
+ }
- return ctx.GetStub().PutState(id, assetJSON)
+ asset.Owner = newOwner
+ assetJSON, err := json.Marshal(asset)
+ if err != nil {
+ return err
}
+ return ctx.GetStub().PutState(id, assetJSON)
+ }
+
// GetAllAssets returns all assets found in world state
- func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
- // range query with empty string for startKey and endKey does an
- // open-ended query of all assets in the chaincode namespace.
- resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
+ func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
+ // range query with empty string for startKey and endKey does an
+ // open-ended query of all assets in the chaincode namespace.
+ resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
+ if err != nil {
+ return nil, err
+ }
+ defer resultsIterator.Close()
+
+ var assets []*Asset
+ for resultsIterator.HasNext() {
+ queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
- defer resultsIterator.Close()
-
- var assets []*Asset
- for resultsIterator.HasNext() {
- queryResponse, err := resultsIterator.Next()
- if err != nil {
- return nil, err
- }
-
- var asset Asset
- err = json.Unmarshal(queryResponse.Value, &asset)
- if err != nil {
- return nil, err
- }
- assets = append(assets, &asset)
- }
- return assets, nil
- }
-
- func main() {
- assetChaincode, err := contractapi.NewChaincode(&SmartContract{})
+ var asset Asset
+ err = json.Unmarshal(queryResponse.Value, &asset)
if err != nil {
- log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
+ return nil, err
}
+ assets = append(assets, &asset)
+ }
- if err := assetChaincode.Start(); err != nil {
- log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
- }
+ return assets, nil
+ }
+
+ func main() {
+ assetChaincode, err := contractapi.NewChaincode(&SmartContract{})
+ if err != nil {
+ log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
}
+ if err := assetChaincode.Start(); err != nil {
+ log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
+ }
+ }
+
Chaincode access control
------------------------
@@ -586,5 +593,57 @@ Once dependencies are vendored in your chaincode directory, ``peer chaincode pac
and ``peer chaincode install`` operations will then include code associated with the
dependencies into the chaincode package.
+JSON determinism
+----------------
+Being able to predictably handle data formats is critical, and also the ability to search the data held within the blockchain.
+
+Technical Problem
+^^^^^^^^^^^^^^^^^
+The format of the data that is stored in Fabric is at the discretion of the user.
+The lowest level API accepts a byte array and stores that - what this represents is not a concern to Fabric.
+The important thing is when simulating transactions, given the same inputs chaincode gives the same byte array.
+Otherwise, the endorsements may not all match and the transaction will either not be submitted or will be invalidated.
+
+JSON is often used as the data format to store data on the ledger, and is required if using CouchDB queries.
+
+JSON though is not a deterministic data format - the order of elements can change,
+whilst still representing the same data semantically. The challenge, therefore, is to be able to generate a consistent set of JSON.
+
+A solution
+^^^^^^^^^^
+Generate a consistent set of ``JSON`` across multiple languages.
+Each language have different features and libraries that you can use to convert an object to JSON.
+The best approach to achieve determinism across different languages is to choose a canonical way as a common guideline to format JSON.
+In order to get a consistent hash across languages you can format JSON in alphabetic order.
+
+Golang
+^^^^^^
+In Golang the ``encoding/json`` package is utilized to serialise a Struct Object into JSON.
+More specifically the ``Marshal`` function is used, the latter marshals maps in sorted key order and keeps structs in the order that the fields are declared.
+Since structs are marshaled in field declaration order, follow alphabetic order when defining a new structure.
+
+Node.js
+^^^^^^^
+In Javascript, when serialising object into JSON, the function ``JSON.stringify()`` is commonly used.
+However, to achieve consistent results, a deterministic version of JSON.stringify() is needed; in this way it is possible to get a consistent hash from stringified results.
+``json-stringify-deterministic`` is a good library to do so and can be used combined with ``sort-keys-recursive`` to attain alphabetic order too.
+`Here <https://www.npmjs.com/package/json-stringify-deterministic>`_ for a more in-depth tutorial.
+
+Java
+^^^^
+Java provides several libraries to serialize an object into a JSON string. However not all of them provide consistency and ordering.
+The ``Gson`` library, for example, does not provide any consistency and should therefore be avoided for this application. On the other hand,
+the ``Genson`` library is a good fit for our purpose as it produces consistent JSON in alphabetic oreder.
+
+You can find a good exemplification of this practise on the `asset-transfer-basic <https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic>`_ chaincodes.
+
+.. Note::
+ This is only one of the many approaches which we think can be effective.
+ When serialising you can utilise various methods to achieve consistency; nevertheless,
+ considering the different characteristics of the programming languages used in Fabric,
+ the alphabetic approach represents an easy and efficient solution to the problem.
+ In conclusion, feel free to employ a different method if it best suites your needs.
+ P.S. Don’t forget to let us know in the comments if you used a different approach.
+
.. Licensed under Creative Commons Attribution 4.0 International License
https://creativecommons.org/licenses/by/4.0/