From eba912b7eac24823566c5b45e9e12f9d86c01fca Mon Sep 17 00:00:00 2001 From: Mike Zaccardo Date: Wed, 30 Nov 2016 11:43:16 -0500 Subject: [PATCH] Add interactive asset management demo This adds an expanded, interactive asset management demo that uses the preexisting example chaincode "asset_management.go". Rather than a single application which performs all of the asset administration, assignments, and transfers, three applications now exist which perform each of those operations. The third application offers a command line interface so the user can actually invoke specific asset transfers of his/her choosing. A detailed description of these applications and their usage can be found in the README. A video demonstration of the applications running can be viewed here: https://www.youtube.com/watch?v=PCZrFAKXlTE. Note that this video link is only present in this description and was not added to any of the committed files. Change-Id: I4da4bcf99c4829c59bd4659954013c4d42d9930f Signed-off-by: Mike Zaccardo --- .../go/asset_management_interactive/README.md | 169 ++++++++ .../asset_management_interactive/app1/app1.go | 103 +++++ .../app1/app1_internal.go | 323 ++++++++++++++ .../asset_management_interactive/app2/app2.go | 134 ++++++ .../app2/app2_internal.go | 397 ++++++++++++++++++ .../app2/assets.txt | 47 +++ .../asset_management_interactive/app3/app3.go | 195 +++++++++ .../app3/app3_internal.go | 385 +++++++++++++++++ .../app3/assets.txt | 47 +++ .../asset_management.go | 305 ++++++++++++++ .../go/asset_management_interactive/build.sh | 13 + 11 files changed, 2118 insertions(+) create mode 100644 examples/chaincode/go/asset_management_interactive/README.md create mode 100644 examples/chaincode/go/asset_management_interactive/app1/app1.go create mode 100644 examples/chaincode/go/asset_management_interactive/app1/app1_internal.go create mode 100644 examples/chaincode/go/asset_management_interactive/app2/app2.go create mode 100644 examples/chaincode/go/asset_management_interactive/app2/app2_internal.go create mode 100644 examples/chaincode/go/asset_management_interactive/app2/assets.txt create mode 100644 examples/chaincode/go/asset_management_interactive/app3/app3.go create mode 100644 examples/chaincode/go/asset_management_interactive/app3/app3_internal.go create mode 100644 examples/chaincode/go/asset_management_interactive/app3/assets.txt create mode 100644 examples/chaincode/go/asset_management_interactive/asset_management.go create mode 100755 examples/chaincode/go/asset_management_interactive/build.sh diff --git a/examples/chaincode/go/asset_management_interactive/README.md b/examples/chaincode/go/asset_management_interactive/README.md new file mode 100644 index 00000000000..833e9ba2486 --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/README.md @@ -0,0 +1,169 @@ +# Hyperledger Fabric - Interactive Asset Management App + +## Overview + +The *Interactive Asset Management App* consists of three separate applications which test the behavior of the standard *asset management* chaincode example. The apps each bootstrap a non-validating peer and construct fabric confidential transactions to deploy, invoke, and/or query the asset management chaincode as described below. + +In particular, we consider a scenario in which we have the following parties: + +1. *Alice* is the chaincode deployer, represented by `app1` +2. *Bob* is the chaincode administrator, represented by `app2` +3. *Charlie*, *Dave*, and *Edwina* are asset owners, each represented by a separate instance of `app3` + +that interact in the following way: + +1. *Alice* deploys and assigns the administrator role to *Bob* +2. *Bob* assigns the assets listed in [assets.txt](app2/assets.txt) to *Charlie*, *Dave*, and *Edwina* +3. *Charlie*, *Dave*, and *Edwina* can transfer the ownership of their assets between each other + +In the following sections, we describe in more detail the interactions +described above that the asset management app exercises. + +### Alice deploys and assigns the administrator role to Bob via `app1` + +The following actions take place: + +1. *Alice* obtains, via an out-of-band channel, the ECert of Bob, let us call these certificates *BobCert* +2. Alice constructs a deploy transaction, as described in *application-ACL.md*, setting the transaction metadata to *DER(BobCert)* +3. Alice submits the deploy transaction to the fabric network + +*Bob* is now the administrator of the chaincode. + +### Bob assigns ownership of all the assets via `app2` + +1. *Bob* obtains, via out-of-band channels, the ECerts of *Charlie*, *Dave*, and *Edwina*; let us call these certificates *CharlieCert*, *DaveCert*, and *EdwinaCert*, respectively +2. *Bob* constructs an invoke transaction, as described in *application-ACL.md* using *BobCert* to gain access, to invoke the *assign* function passing as parameters *('ASSET_NAME_HERE', Base64(DER(CharlieCert)))* +3. *Bob* submits the transaction to the fabric network +4. *Bob* repeates the above steps for every asset listed in [assets.txt](app2/assets.txt), rotating between *Charlie*, *Dave*, and *Edwina* as the assigned owner + +*Charlie*, *Dave*, and *Edwina* are now each the owners of 1/3 of the assets. + +Notice that, to assign ownership of assets, *Bob* has to use *BobCert* to authenticate his invocations. + +### Charlie, Dave, and Edwina transfer the ownership of their assets between each other via separate instances of `app3` + +1. *Charlie*, *Dave*, and *Edwina* each obtain, via out-of-band channels, the ECerts of the other owners; let us call these certificates *CharlieCert*, *DaveCert*, and *EdwinaCert*, respectively +2. Owner *X* constructs an invoke transaction, as described in *application-ACL.md* using *XCert*, to invoke the *transfer* function passing as parameters *('ASSET_NAME_HERE', Base64(DER(YCert)))*. +3. *X* submits the transaction to the fabric network. +4. These steps can be repeated for any asset to be transferred between any two users *X* -> *Y*, if and only if *X* is the current owner of the asset + +*Y* is now the owner of the asset. + +Notice that, to transfer the ownership of a given asset, user *X* has to use *XCert* to authenticate his/her invocations. + +## How To run the Interactive Asset Management App + +In order to run the Interactive Asset Management App, the following steps are required. + +### Setup the fabric network and create app's configuration file + +Follow the [create](https://github.com/hyperledger/fabric/blob/master/examples/chaincode/go/asset_management/app/README.md#create-apps-configuration-file) and [setup](https://github.com/hyperledger/fabric/blob/master/examples/chaincode/go/asset_management/app/README.md#setup-the-fabric-network) steps from the original asset management demo instructions. Note that the `core.yaml` that is described should be placed in this directory on the CLI node. + +### Build the apps + +Follow the first two instructions of the [run step](https://github.com/hyperledger/fabric/blob/master/examples/chaincode/go/asset_management/app/README.md#run-the-app) from the original asset management demo instructions, but change into the following directory instead: + +``` +cd $GOPATH/src/github.com/hyperldger/fabric/examples/chaincode/go/asset_management_interactive +``` + +Then build all three applications by issuing the following command: + +``` +./build.sh +``` + +### Run `app1` + +Run `app1` by issuing the following command: + +``` +cd app1 +./app1 +``` + +Wait for `app1` to complete, and note the output value of `ChaincodeName`, which needs to be supplied to the following applications. For the rest of these instructions, we will use `8bc69f84aa55195b9eba6aed6261a01ba528acd3413f37b34df580f96e0d8a525e0fe8d41f967b38f192f7a731d63596eedcbd08ee636afdadb96be02d5d150d` as the `ChaincodeName` value. + +### Run `app2` + +Run `app2` by issuing the following command: + +``` +cd ../app2 +./app2 8bc69f84aa55195b9eba6aed6261a01ba528acd3413f37b34df580f96e0d8a525e0fe8d41f967b38f192f7a731d63596eedcbd08ee636afdadb96be02d5d150d +``` + +### Run `app3` for each owner + +Run `app3` for each owner by issuing the following commands (note that a separate terminal window for each owner is recommended): + +``` +cd ../app3 +``` + +Run as *Charlie*: + +``` +./app3 8bc69f84aa55195b9eba6aed6261a01ba528acd3413f37b34df580f96e0d8a525e0fe8d41f967b38f192f7a731d63596eedcbd08ee636afdadb96be02d5d150d charlie +``` + +Run as *Dave*: + +``` +./app3 8bc69f84aa55195b9eba6aed6261a01ba528acd3413f37b34df580f96e0d8a525e0fe8d41f967b38f192f7a731d63596eedcbd08ee636afdadb96be02d5d150d dave +``` + +Run as *Edwina*: + +``` +./app3 8bc69f84aa55195b9eba6aed6261a01ba528acd3413f37b34df580f96e0d8a525e0fe8d41f967b38f192f7a731d63596eedcbd08ee636afdadb96be02d5d150d edwina +``` + +Each app will then provide a simple console interface for perfoming one of two actions: listing owned assets and transferring an asset to another owner. + +#### List Assets + +List an owner's assets by issuing the `list` command. For example, this is the expected output of *Charlie's* first `list`: + +``` +charlie$ list +... +'charlie' owns the following 16 assets: +'Lot01: BERNARD LEACH VASE WITH 'LEAPING FISH' DESIGN' +'Lot04: PETER LANYON TREVALGAN' +'Lot07: DAVID BOMBERG MOORISH RONDA, ANDALUCIA' +'Lot10: HAROLD GILMAN INTERIOR (MRS MOUNTER)' +'Lot13: WILLIAM SCOTT, R.A. GIRL SEATED AT A TABLE' +'Lot16: JACK BUTLER YEATS, R.H.A. SLEEP SOUND' +'Lot19: SIR EDUARDO PAOLOZZI, R.A. WEDDING OF THE ROCK AND DYNAMO' +'Lot22: JEAN-MICHEL BASQUIAT AIR POWER' +'Lot25: KENNETH ARMITAGE, R.A. MODEL FOR DIARCHY (SMALL VERSION)' +'Lot28: FRANK AUERBACH E.O.W'S RECLINING HEAD' +'Lot31: SIR EDUARDO PAOLOZZI, R.A. FORMS ON A BOW NO. 2' +'Lot34: MARCEL DUCHAMP WITH HIDDEN NOISE (A BRUIT SECRET)' +'Lot37: FRANCIS PICABIA MENDICA' +'Lot40: DAVID BOMBERG PLAZUELA DE LA PAZ, RONDA' +'Lot43: PATRICK CAULFIELD, R.A., C.B.E. FOYER' +'Lot46: DAMIEN HIRST UNTITLED FISH FOR DAVID' +``` + +#### Transfer Assets + +Transfer an asset to another owner by issuing the `transfer LotXX owner_here` command. For example, this is the command to transfer *Lot01* from *Charlie* to *Dave*. + +``` +charlie$ transfer Lot01 dave +... +'dave' is the new owner of 'Lot01: BERNARD LEACH VASE WITH 'LEAPING FISH' DESIGN'! +------------- Done! +``` + +## Compatibility + +This demo application has been successfully tested against the +[v0.6.0-preview](https://github.com/hyperledger/fabric/releases/tag/v0.6.0-preview) and +[v0.6.1-preview](https://github.com/hyperledger/fabric/releases/tag/v0.6.1-preview) releases. + +## Copyright Note + +The new source code files retain `Copyright IBM Corp.` because they are closely adopted from a preexisting example that contains an IBM copyright. \ No newline at end of file diff --git a/examples/chaincode/go/asset_management_interactive/app1/app1.go b/examples/chaincode/go/asset_management_interactive/app1/app1.go new file mode 100644 index 00000000000..f2e88075c5a --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/app1/app1.go @@ -0,0 +1,103 @@ +/* +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 main + +import ( + "os" + "time" + + "github.com/hyperledger/fabric/core/crypto" + pb "github.com/hyperledger/fabric/protos" + "github.com/op/go-logging" + "google.golang.org/grpc" +) + +var ( + // Logging + appLogger = logging.MustGetLogger("app") + + // NVP related objects + peerClientConn *grpc.ClientConn + serverClient pb.PeerClient + + // Alice is the deployer + alice crypto.Client + + // Bob is the administrator + bob crypto.Client + bobCert crypto.CertificateHandler +) + +func deploy() (err error) { + appLogger.Debug("------------- Alice wants to assign the administrator role to Bob;") + // Deploy: + // 1. Alice is the deployer of the chaincode; + // 2. Alice wants to assign the administrator role to Bob; + // 3. Alice obtains, via an out-of-band channel, the ECert of Bob, let us call this certificate *BobCert*; + // 4. Alice constructs a deploy transaction, as described in *application-ACL.md*, setting the transaction + // metadata to *DER(CharlieCert)*. + // 5. Alice submits th e transaction to the fabric network. + + resp, err := deployInternal(alice, bobCert) + if err != nil { + appLogger.Errorf("Failed deploying [%s]", err) + return + } + appLogger.Debugf("Resp [%s]", resp.String()) + appLogger.Debugf("Chaincode NAME: [%s]-[%s]", chaincodeName, string(resp.Msg)) + + appLogger.Debug("Wait 60 seconds") + time.Sleep(60 * time.Second) + + appLogger.Debug("------------- Done!") + return +} + +func testAssetManagementChaincode() (err error) { + // Deploy + err = deploy() + if err != nil { + appLogger.Errorf("Failed deploying [%s]", err) + return + } + + appLogger.Debug("Deployed!") + + closeCryptoClient(alice) + closeCryptoClient(bob) + + return +} + +func main() { + // Initialize a non-validating peer whose role is to submit + // transactions to the fabric network. + // A 'core.yaml' file is assumed to be available in the working directory. + if err := initNVP(); err != nil { + appLogger.Debugf("Failed initiliazing NVP [%s]", err) + os.Exit(-1) + } + + // Enable fabric 'confidentiality' + confidentiality(true) + + // Exercise the 'asset_management' chaincode + if err := testAssetManagementChaincode(); err != nil { + appLogger.Debugf("Failed testing asset management chaincode [%s]", err) + os.Exit(-2) + } +} diff --git a/examples/chaincode/go/asset_management_interactive/app1/app1_internal.go b/examples/chaincode/go/asset_management_interactive/app1/app1_internal.go new file mode 100644 index 00000000000..22ddc138d2a --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/app1/app1_internal.go @@ -0,0 +1,323 @@ +/* +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 main + +import ( + "encoding/base64" + "errors" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/core/chaincode" + "github.com/hyperledger/fabric/core/chaincode/platforms" + "github.com/hyperledger/fabric/core/config" + "github.com/hyperledger/fabric/core/container" + "github.com/hyperledger/fabric/core/crypto" + "github.com/hyperledger/fabric/core/peer" + "github.com/hyperledger/fabric/core/util" + pb "github.com/hyperledger/fabric/protos" + "github.com/op/go-logging" + "github.com/spf13/viper" + "golang.org/x/net/context" +) + +var ( + confidentialityOn bool + + confidentialityLevel pb.ConfidentialityLevel + chaincodeName string +) + +func initNVP() (err error) { + if err = initPeerClient(); err != nil { + appLogger.Debugf("Failed deploying [%s]", err) + return + + } + if err = initCryptoClients(); err != nil { + appLogger.Debugf("Failed deploying [%s]", err) + return + } + + return +} + +func initPeerClient() (err error) { + config.SetupTestConfig(".") + viper.Set("ledger.blockchain.deploy-system-chaincode", "false") + viper.Set("peer.validator.validity-period.verification", "false") + + peerClientConn, err = peer.NewPeerClientConnection() + if err != nil { + fmt.Printf("error connection to server at host:port = %s\n", viper.GetString("peer.address")) + return + } + serverClient = pb.NewPeerClient(peerClientConn) + + // Logging + var formatter = logging.MustStringFormatter( + `%{color}[%{module}] %{shortfunc} [%{shortfile}] -> %{level:.4s} %{id:03x}%{color:reset} %{message}`, + ) + logging.SetFormatter(formatter) + + return +} + +func initCryptoClients() error { + crypto.Init() + + // Initialize the clients mapping alice, bob, charlie and dave + // to identities already defined in 'membersrvc.yaml' + + // Alice as jim + if err := crypto.RegisterClient("jim", nil, "jim", "6avZQLwcUe9b"); err != nil { + return err + } + var err error + alice, err = crypto.InitClient("jim", nil) + if err != nil { + return err + } + + // Bob as lukas + if err := crypto.RegisterClient("lukas", nil, "lukas", "NPKYL39uKbkj"); err != nil { + return err + } + bob, err = crypto.InitClient("lukas", nil) + if err != nil { + return err + } + + bobCert, err = bob.GetEnrollmentCertificateHandler() + if err != nil { + appLogger.Errorf("Failed getting Bob TCert [%s]", err) + return err + } + + return nil +} + +func closeCryptoClient(client crypto.Client) { + crypto.CloseClient(client) +} + +func processTransaction(tx *pb.Transaction) (*pb.Response, error) { + return serverClient.ProcessTransaction(context.Background(), tx) +} + +func confidentiality(enabled bool) { + confidentialityOn = enabled + + if confidentialityOn { + confidentialityLevel = pb.ConfidentialityLevel_CONFIDENTIAL + } else { + confidentialityLevel = pb.ConfidentialityLevel_PUBLIC + } +} + +func deployInternal(deployer crypto.Client, adminCert crypto.CertificateHandler) (resp *pb.Response, err error) { + // Prepare the spec. The metadata includes the identity of the administrator + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Path: "github.com/hyperledger/fabric/examples/chaincode/go/asset_management"}, + //ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: &pb.ChaincodeInput{Args: util.ToChaincodeArgs("init")}, + Metadata: adminCert.GetCertificate(), + ConfidentialityLevel: confidentialityLevel, + } + + // First build the deployment spec + cds, err := getChaincodeBytes(spec) + if err != nil { + return nil, fmt.Errorf("Error getting deployment spec: %s ", err) + } + + // Now create the Transactions message and send to Peer. + transaction, err := deployer.NewChaincodeDeployTransaction(cds, cds.ChaincodeSpec.ChaincodeID.Name) + if err != nil { + return nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + resp, err = processTransaction(transaction) + + appLogger.Debugf("resp [%s]", resp.String()) + + chaincodeName = cds.ChaincodeSpec.ChaincodeID.Name + appLogger.Debugf("ChaincodeName [%s]", chaincodeName) + + return +} + +func assignOwnershipInternal(invoker crypto.Client, invokerCert crypto.CertificateHandler, asset string, newOwnerCert crypto.CertificateHandler) (resp *pb.Response, err error) { + // Get a transaction handler to be used to submit the execute transaction + // and bind the chaincode access control logic using the binding + submittingCertHandler, err := invoker.GetTCertificateHandlerNext() + if err != nil { + return nil, err + } + txHandler, err := submittingCertHandler.GetTransactionHandler() + if err != nil { + return nil, err + } + binding, err := txHandler.GetBinding() + if err != nil { + return nil, err + } + + chaincodeInput := &pb.ChaincodeInput{ + Args: util.ToChaincodeArgs("assign", asset, base64.StdEncoding.EncodeToString(newOwnerCert.GetCertificate())), + } + chaincodeInputRaw, err := proto.Marshal(chaincodeInput) + if err != nil { + return nil, err + } + + // Access control. Administrator signs chaincodeInputRaw || binding to confirm his identity + sigma, err := invokerCert.Sign(append(chaincodeInputRaw, binding...)) + if err != nil { + return nil, err + } + + // Prepare spec and submit + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: chaincodeInput, + Metadata: sigma, // Proof of identity + ConfidentialityLevel: confidentialityLevel, + } + + chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + // Now create the Transactions message and send to Peer. + transaction, err := txHandler.NewChaincodeExecute(chaincodeInvocationSpec, util.GenerateUUID()) + if err != nil { + return nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + return processTransaction(transaction) +} + +func transferOwnershipInternal(owner crypto.Client, ownerCert crypto.CertificateHandler, asset string, newOwnerCert crypto.CertificateHandler) (resp *pb.Response, err error) { + // Get a transaction handler to be used to submit the execute transaction + // and bind the chaincode access control logic using the binding + + submittingCertHandler, err := owner.GetTCertificateHandlerNext() + if err != nil { + return nil, err + } + txHandler, err := submittingCertHandler.GetTransactionHandler() + if err != nil { + return nil, err + } + binding, err := txHandler.GetBinding() + if err != nil { + return nil, err + } + + chaincodeInput := &pb.ChaincodeInput{ + Args: util.ToChaincodeArgs("transfer", asset, base64.StdEncoding.EncodeToString(newOwnerCert.GetCertificate())), + } + chaincodeInputRaw, err := proto.Marshal(chaincodeInput) + if err != nil { + return nil, err + } + + // Access control. Owner signs chaincodeInputRaw || binding to confirm his identity + sigma, err := ownerCert.Sign(append(chaincodeInputRaw, binding...)) + if err != nil { + return nil, err + } + + // Prepare spec and submit + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: chaincodeInput, + Metadata: sigma, // Proof of identity + ConfidentialityLevel: confidentialityLevel, + } + + chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + // Now create the Transactions message and send to Peer. + transaction, err := txHandler.NewChaincodeExecute(chaincodeInvocationSpec, util.GenerateUUID()) + if err != nil { + return nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + return processTransaction(transaction) + +} + +func whoIsTheOwner(invoker crypto.Client, asset string) (transaction *pb.Transaction, resp *pb.Response, err error) { + chaincodeInput := &pb.ChaincodeInput{Args: util.ToChaincodeArgs("query", asset)} + + // Prepare spec and submit + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: chaincodeInput, + ConfidentialityLevel: confidentialityLevel, + } + + chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + // Now create the Transactions message and send to Peer. + transaction, err = invoker.NewChaincodeQuery(chaincodeInvocationSpec, util.GenerateUUID()) + if err != nil { + return nil, nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + resp, err = processTransaction(transaction) + return +} + +func getChaincodeBytes(spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) { + mode := viper.GetString("chaincode.mode") + var codePackageBytes []byte + if mode != chaincode.DevModeUserRunsChaincode { + appLogger.Debugf("Received build request for chaincode spec: %v", spec) + var err error + if err = checkSpec(spec); err != nil { + return nil, err + } + + codePackageBytes, err = container.GetChaincodePackageBytes(spec) + if err != nil { + err = fmt.Errorf("Error getting chaincode package bytes: %s", err) + appLogger.Errorf("%s", err) + return nil, err + } + } + chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes} + return chaincodeDeploymentSpec, nil +} + +func checkSpec(spec *pb.ChaincodeSpec) error { + // Don't allow nil value + if spec == nil { + return errors.New("Expected chaincode specification, nil received") + } + + platform, err := platforms.Find(spec.Type) + if err != nil { + return fmt.Errorf("Failed to determine platform type: %s", err) + } + + return platform.ValidateSpec(spec) +} diff --git a/examples/chaincode/go/asset_management_interactive/app2/app2.go b/examples/chaincode/go/asset_management_interactive/app2/app2.go new file mode 100644 index 00000000000..997801ee528 --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/app2/app2.go @@ -0,0 +1,134 @@ +/* +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 main + +import ( + "os" + "time" + + "github.com/hyperledger/fabric/core/crypto" + pb "github.com/hyperledger/fabric/protos" + "github.com/op/go-logging" + "google.golang.org/grpc" +) + +var ( + // Logging + appLogger = logging.MustGetLogger("app") + + // NVP related objects + peerClientConn *grpc.ClientConn + serverClient pb.PeerClient + + // Bob is the administrator + bob crypto.Client + bobCert crypto.CertificateHandler + + // Charlie, Dave, and Edwina are owners + charlie crypto.Client + charlieCert crypto.CertificateHandler + + dave crypto.Client + daveCert crypto.CertificateHandler + + edwina crypto.Client + edwinaCert crypto.CertificateHandler + + assets map[string]string + lotNums []string +) + +func assignOwnership() (err error) { + appLogger.Debug("------------- Bob wants to assign the asset owners to all of the assets...") + + i := 0 + var ownerCert crypto.CertificateHandler + + for _, lotNum := range lotNums { + assetName := assets[lotNum] + + if i%3 == 0 { + appLogger.Debugf("Assigning ownership of asset '[%s]: [%s]' to '[%s]'", lotNum, assetName, "Charlie") + ownerCert = charlieCert + } else if i%3 == 1 { + appLogger.Debugf("Assigning ownership of asset '[%s]: [%s]' to '[%s]'", lotNum, assetName, "Dave") + ownerCert = daveCert + } else { + appLogger.Debugf("Assigning ownership of asset '[%s]: [%s]' to '[%s]'", lotNum, assetName, "Edwina") + ownerCert = edwinaCert + } + + resp, err := assignOwnershipInternal(bob, bobCert, assetName, ownerCert) + if err != nil { + appLogger.Errorf("Failed assigning ownership [%s]", err) + return err + } + appLogger.Debugf("Resp [%s]", resp.String()) + + i++ + } + + appLogger.Debug("Wait 60 seconds...") + time.Sleep(60 * time.Second) + + appLogger.Debug("------------- Done!") + return +} + +func testAssetManagementChaincode() (err error) { + // Assign + err = assignOwnership() + if err != nil { + appLogger.Errorf("Failed assigning ownership [%s]", err) + return + } + + appLogger.Debug("Assigned ownership!") + + closeCryptoClient(bob) + closeCryptoClient(charlie) + closeCryptoClient(dave) + closeCryptoClient(edwina) + + return +} + +func main() { + if len(os.Args) != 2 { + appLogger.Errorf("Error -- A ChaincodeName must be specified.") + os.Exit(-1) + } + + chaincodeName = os.Args[1] + + // Initialize a non-validating peer whose role is to submit + // transactions to the fabric network. + // A 'core.yaml' file is assumed to be available in the working directory. + if err := initNVP(); err != nil { + appLogger.Debugf("Failed initiliazing NVP [%s]", err) + os.Exit(-1) + } + + // Enable fabric 'confidentiality' + confidentiality(true) + + // Exercise the 'asset_management' chaincode + if err := testAssetManagementChaincode(); err != nil { + appLogger.Debugf("Failed testing asset management chaincode [%s]", err) + os.Exit(-2) + } +} diff --git a/examples/chaincode/go/asset_management_interactive/app2/app2_internal.go b/examples/chaincode/go/asset_management_interactive/app2/app2_internal.go new file mode 100644 index 00000000000..3743c375459 --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/app2/app2_internal.go @@ -0,0 +1,397 @@ +/* +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 main + +import ( + "bufio" + "encoding/base64" + "errors" + "fmt" + "os" + + "strings" + + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/core/chaincode" + "github.com/hyperledger/fabric/core/chaincode/platforms" + "github.com/hyperledger/fabric/core/config" + "github.com/hyperledger/fabric/core/container" + "github.com/hyperledger/fabric/core/crypto" + "github.com/hyperledger/fabric/core/peer" + "github.com/hyperledger/fabric/core/util" + pb "github.com/hyperledger/fabric/protos" + "github.com/op/go-logging" + "github.com/spf13/viper" + "golang.org/x/net/context" +) + +var ( + confidentialityOn bool + + confidentialityLevel pb.ConfidentialityLevel + chaincodeName string +) + +func initNVP() (err error) { + if err = initPeerClient(); err != nil { + appLogger.Debugf("Failed deploying [%s]", err) + return + + } + if err = initCryptoClients(); err != nil { + appLogger.Debugf("Failed deploying [%s]", err) + return + } + + if err = readAssets(); err != nil { + appLogger.Debugf("Failed reading assets [%s]", err) + return + } + + return +} + +func initPeerClient() (err error) { + config.SetupTestConfig(".") + viper.Set("ledger.blockchain.deploy-system-chaincode", "false") + viper.Set("peer.validator.validity-period.verification", "false") + + peerClientConn, err = peer.NewPeerClientConnection() + if err != nil { + fmt.Printf("error connection to server at host:port = %s\n", viper.GetString("peer.address")) + return + } + serverClient = pb.NewPeerClient(peerClientConn) + + // Logging + var formatter = logging.MustStringFormatter( + `%{color}[%{module}] %{shortfunc} [%{shortfile}] -> %{level:.4s} %{id:03x}%{color:reset} %{message}`, + ) + logging.SetFormatter(formatter) + + return +} + +func initCryptoClients() error { + crypto.Init() + + // Initialize the clients mapping bob, charlie, dave, and edwina + // to identities already defined in 'membersrvc.yaml' + + // Bob as lukas + if err := crypto.RegisterClient("lukas", nil, "lukas", "NPKYL39uKbkj"); err != nil { + return err + } + var err error + bob, err = crypto.InitClient("lukas", nil) + if err != nil { + return err + } + + // Charlie as diego + if err := crypto.RegisterClient("diego", nil, "diego", "DRJ23pEQl16a"); err != nil { + return err + } + charlie, err = crypto.InitClient("diego", nil) + if err != nil { + return err + } + + // Dave as binhn + if err := crypto.RegisterClient("binhn", nil, "binhn", "7avZQLwcUe9q"); err != nil { + return err + } + dave, err = crypto.InitClient("binhn", nil) + if err != nil { + return err + } + + // Edwina as test_user0 + if err := crypto.RegisterClient("test_user0", nil, "test_user0", "MS9qrN8hFjlE"); err != nil { + return err + } + edwina, err = crypto.InitClient("test_user0", nil) + if err != nil { + return err + } + + bobCert, err = bob.GetEnrollmentCertificateHandler() + if err != nil { + appLogger.Errorf("Failed getting Bob ECert [%s]", err) + return err + } + + charlieCert, err = charlie.GetEnrollmentCertificateHandler() + if err != nil { + appLogger.Errorf("Failed getting Charlie ECert [%s]", err) + return err + } + + daveCert, err = dave.GetEnrollmentCertificateHandler() + if err != nil { + appLogger.Errorf("Failed getting Dave ECert [%s]", err) + return err + } + + edwinaCert, err = edwina.GetEnrollmentCertificateHandler() + if err != nil { + appLogger.Errorf("Failed getting Edwina ECert [%s]", err) + return err + } + + return nil +} + +func readAssets() error { + assets = make(map[string]string) + lotNums = make([]string, 0, 47) + + file, err := os.Open("assets.txt") + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + assetLine := scanner.Text() + assetParts := strings.Split(assetLine, ";") + + lotNum := assetParts[0] + assetName := assetParts[1] + + assets[lotNum] = assetName + lotNums = append(lotNums, lotNum) + } + + if err := scanner.Err(); err != nil { + return err + } + + return nil +} + +func closeCryptoClient(client crypto.Client) { + crypto.CloseClient(client) +} + +func processTransaction(tx *pb.Transaction) (*pb.Response, error) { + return serverClient.ProcessTransaction(context.Background(), tx) +} + +func confidentiality(enabled bool) { + confidentialityOn = enabled + + if confidentialityOn { + confidentialityLevel = pb.ConfidentialityLevel_CONFIDENTIAL + } else { + confidentialityLevel = pb.ConfidentialityLevel_PUBLIC + } +} + +func deployInternal(deployer crypto.Client, adminCert crypto.CertificateHandler) (resp *pb.Response, err error) { + // Prepare the spec. The metadata includes the identity of the administrator + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Path: "github.com/hyperledger/fabric/examples/chaincode/go/asset_management"}, + //ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: &pb.ChaincodeInput{Args: util.ToChaincodeArgs("init")}, + Metadata: adminCert.GetCertificate(), + ConfidentialityLevel: confidentialityLevel, + } + + // First build the deployment spec + cds, err := getChaincodeBytes(spec) + if err != nil { + return nil, fmt.Errorf("Error getting deployment spec: %s ", err) + } + + // Now create the Transactions message and send to Peer. + transaction, err := deployer.NewChaincodeDeployTransaction(cds, cds.ChaincodeSpec.ChaincodeID.Name) + if err != nil { + return nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + resp, err = processTransaction(transaction) + + appLogger.Debugf("resp [%s]", resp.String()) + + chaincodeName = cds.ChaincodeSpec.ChaincodeID.Name + appLogger.Debugf("ChaincodeName [%s]", chaincodeName) + + return +} + +func assignOwnershipInternal(invoker crypto.Client, invokerCert crypto.CertificateHandler, asset string, newOwnerCert crypto.CertificateHandler) (resp *pb.Response, err error) { + // Get a transaction handler to be used to submit the execute transaction + // and bind the chaincode access control logic using the binding + submittingCertHandler, err := invoker.GetTCertificateHandlerNext() + if err != nil { + return nil, err + } + txHandler, err := submittingCertHandler.GetTransactionHandler() + if err != nil { + return nil, err + } + binding, err := txHandler.GetBinding() + if err != nil { + return nil, err + } + + chaincodeInput := &pb.ChaincodeInput{ + Args: util.ToChaincodeArgs("assign", asset, base64.StdEncoding.EncodeToString(newOwnerCert.GetCertificate())), + } + chaincodeInputRaw, err := proto.Marshal(chaincodeInput) + if err != nil { + return nil, err + } + + // Access control. Administrator signs chaincodeInputRaw || binding to confirm his identity + sigma, err := invokerCert.Sign(append(chaincodeInputRaw, binding...)) + if err != nil { + return nil, err + } + + // Prepare spec and submit + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: chaincodeInput, + Metadata: sigma, // Proof of identity + ConfidentialityLevel: confidentialityLevel, + } + + chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + // Now create the Transactions message and send to Peer. + transaction, err := txHandler.NewChaincodeExecute(chaincodeInvocationSpec, util.GenerateUUID()) + if err != nil { + return nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + return processTransaction(transaction) +} + +func transferOwnershipInternal(owner crypto.Client, ownerCert crypto.CertificateHandler, asset string, newOwnerCert crypto.CertificateHandler) (resp *pb.Response, err error) { + // Get a transaction handler to be used to submit the execute transaction + // and bind the chaincode access control logic using the binding + + submittingCertHandler, err := owner.GetTCertificateHandlerNext() + if err != nil { + return nil, err + } + txHandler, err := submittingCertHandler.GetTransactionHandler() + if err != nil { + return nil, err + } + binding, err := txHandler.GetBinding() + if err != nil { + return nil, err + } + + chaincodeInput := &pb.ChaincodeInput{ + Args: util.ToChaincodeArgs("transfer", asset, base64.StdEncoding.EncodeToString(newOwnerCert.GetCertificate())), + } + chaincodeInputRaw, err := proto.Marshal(chaincodeInput) + if err != nil { + return nil, err + } + + // Access control. Owner signs chaincodeInputRaw || binding to confirm his identity + sigma, err := ownerCert.Sign(append(chaincodeInputRaw, binding...)) + if err != nil { + return nil, err + } + + // Prepare spec and submit + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: chaincodeInput, + Metadata: sigma, // Proof of identity + ConfidentialityLevel: confidentialityLevel, + } + + chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + // Now create the Transactions message and send to Peer. + transaction, err := txHandler.NewChaincodeExecute(chaincodeInvocationSpec, util.GenerateUUID()) + if err != nil { + return nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + return processTransaction(transaction) + +} + +func whoIsTheOwner(invoker crypto.Client, asset string) (transaction *pb.Transaction, resp *pb.Response, err error) { + chaincodeInput := &pb.ChaincodeInput{Args: util.ToChaincodeArgs("query", asset)} + + // Prepare spec and submit + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: chaincodeInput, + ConfidentialityLevel: confidentialityLevel, + } + + chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + // Now create the Transactions message and send to Peer. + transaction, err = invoker.NewChaincodeQuery(chaincodeInvocationSpec, util.GenerateUUID()) + if err != nil { + return nil, nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + resp, err = processTransaction(transaction) + return +} + +func getChaincodeBytes(spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) { + mode := viper.GetString("chaincode.mode") + var codePackageBytes []byte + if mode != chaincode.DevModeUserRunsChaincode { + appLogger.Debugf("Received build request for chaincode spec: %v", spec) + var err error + if err = checkSpec(spec); err != nil { + return nil, err + } + + codePackageBytes, err = container.GetChaincodePackageBytes(spec) + if err != nil { + err = fmt.Errorf("Error getting chaincode package bytes: %s", err) + appLogger.Errorf("%s", err) + return nil, err + } + } + chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes} + return chaincodeDeploymentSpec, nil +} + +func checkSpec(spec *pb.ChaincodeSpec) error { + // Don't allow nil value + if spec == nil { + return errors.New("Expected chaincode specification, nil received") + } + + platform, err := platforms.Find(spec.Type) + if err != nil { + return fmt.Errorf("Failed to determine platform type: %s", err) + } + + return platform.ValidateSpec(spec) +} diff --git a/examples/chaincode/go/asset_management_interactive/app2/assets.txt b/examples/chaincode/go/asset_management_interactive/app2/assets.txt new file mode 100644 index 00000000000..298c9910348 --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/app2/assets.txt @@ -0,0 +1,47 @@ +Lot01;BERNARD LEACH VASE WITH 'LEAPING FISH' DESIGN +Lot02;WINIFRED NICHOLSON ST IVES HARBOUR +Lot03;WILHELMINA BARNS-GRAHAM GLACIER (BONE) +Lot04;PETER LANYON TREVALGAN +Lot05;DAMIEN HIRST WITH DAVID BOWIE BEAUTIFUL, HALLO, SPACE-BOY PAINTING +Lot06;PERCY WYNDHAM LEWIS FUTURIST FIGURE +Lot07;DAVID BOMBERG MOORISH RONDA, ANDALUCIA +Lot08;PERCY WYNDHAM LEWIS CIRCUS SCENE +Lot09;HENRY LAMB STUDY FOR PORTRAIT OF LYTTON STRACHEY +Lot10;HAROLD GILMAN INTERIOR (MRS MOUNTER) +Lot11;SIR JACOB EPSTEIN LES FLEURS DU MAL - HYMNE LA BEAUT +Lot12;HENRY MOORE, O.M., C.H. FAMILY GROUP +Lot13;WILLIAM SCOTT, R.A. GIRL SEATED AT A TABLE +Lot14;IVON HITCHENS THE BOATHOUSE NO. 3 +Lot15;KENNETH ARMITAGE, R.A. FIGURE LYING ON ITS SIDE (VERSION 3) +Lot16;JACK BUTLER YEATS, R.H.A. SLEEP SOUND +Lot17;DAVID BOMBERG SUNRISE IN THE MOUNTAINS, PICOS DE ASTURIAS +Lot18;FRANK AUERBACH HEAD OF GERDA BOEHM +Lot19;SIR EDUARDO PAOLOZZI, R.A. WEDDING OF THE ROCK AND DYNAMO +Lot20;PETER LANYON WITNESS +Lot21;JEAN-MICHEL BASQUIAT UNTITLED +Lot22;JEAN-MICHEL BASQUIAT AIR POWER +Lot23;ALAN DAVIE PETER'S JOY-PIT +Lot24;BRYAN WYNTER IN THE STREAM'S PATH +Lot25;KENNETH ARMITAGE, R.A. MODEL FOR DIARCHY (SMALL VERSION) +Lot26;LEON KOSSOFF DALSTON JUNCTION NO.3, JUNE 1973 +Lot27;WILLIAM SCOTT, R.A. WINTER STILL LIFE NO.2 +Lot28;FRANK AUERBACH E.O.W'S RECLINING HEAD +Lot29;PETER LANYON INSHORE FISHING +Lot30;GRAHAM SUTHERLAND, O.M. THORN BUSH +Lot31;SIR EDUARDO PAOLOZZI, R.A. FORMS ON A BOW NO. 2 +Lot32;EDWARD WADSWORTH AU REVOIR +Lot33;LOUIS LE BROCQUY, H.R.H.A. IMAGE OF JAMES JOYCE +Lot34;MARCEL DUCHAMP WITH HIDDEN NOISE (A BRUIT SECRET) +Lot35;MARCEL DUCHAMP NUDE DESCENDING A STAIRCASE, NO. 2 +Lot36;MERET OPPENHEIM LA CONDITION HUMAINE +Lot37;FRANCIS PICABIA MENDICA +Lot38;JACOPO ROBUSTI, CALLED JACOPO TINTORETTO AND STUDIO THE ANGEL FORETELLING SAINT CATHERINE OF ALEXANDRIA OF HER MARTYRDOM +Lot39;SIR STANLEY SPENCER, R.A. CARRYING MATTRESSES +Lot40;DAVID BOMBERG PLAZUELA DE LA PAZ, RONDA +Lot41;WILLIAM TURNBULL LARGE IDOL +Lot42;PATRICK HUGHES LIQUORICE ALLSORTS +Lot43;PATRICK CAULFIELD, R.A., C.B.E. FOYER +Lot44;VICTOR PASMORE ABSTRACT IN WHITE, BLACK, GREEN AND CRIMSON +Lot45;GILBERT & GEORGE LEAF +Lot46;DAMIEN HIRST UNTITLED FISH FOR DAVID +Lot47;DAMIEN HIRST BEAUTIFUL, SHATTERING, SLASHING, VIOLENT, PINKY, HACKING, SPHINCTER PAINTING \ No newline at end of file diff --git a/examples/chaincode/go/asset_management_interactive/app3/app3.go b/examples/chaincode/go/asset_management_interactive/app3/app3.go new file mode 100644 index 00000000000..61075515905 --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/app3/app3.go @@ -0,0 +1,195 @@ +/* +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 main + +import ( + "bufio" + "fmt" + "os" + "reflect" + + "strings" + + "github.com/hyperledger/fabric/core/crypto" + pb "github.com/hyperledger/fabric/protos" + "github.com/op/go-logging" + "google.golang.org/grpc" +) + +var ( + // Logging + appLogger = logging.MustGetLogger("app") + + // NVP related objects + peerClientConn *grpc.ClientConn + serverClient pb.PeerClient + + // Charlie, Dave, and Edwina are owners + charlie crypto.Client + charlieCert crypto.CertificateHandler + + dave crypto.Client + daveCert crypto.CertificateHandler + + edwina crypto.Client + edwinaCert crypto.CertificateHandler + + assets map[string]string + lotNums []string + + clients map[string]crypto.Client + certs map[string]crypto.CertificateHandler + + myClient crypto.Client + myCert crypto.CertificateHandler +) + +func transferOwnership(lotNum string, newOwner string) (err error) { + if !isAssetKnown(lotNum) { + appLogger.Errorf("Error -- asset '%s' does not exist.", lotNum) + return + } + + if !isUserKnown(newOwner) { + appLogger.Errorf("Error -- user '%s' is not known.", newOwner) + return + } + + assetName := assets[lotNum] + + appLogger.Debugf("------------- '%s' wants to transfer the ownership of '%s: %s' to '%s'...", user, lotNum, assetName, newOwner) + + if !isOwner(assetName, user) { + appLogger.Debugf("'%s' is not the owner of '%s: %s' -- transfer not allowed.", user, lotNum, assetName) + return + } + + resp, err := transferOwnershipInternal(myClient, myCert, assetName, certs[newOwner]) + if err != nil { + appLogger.Debugf("Failed to transfer '%s: %s' to '%s'", lotNum, assetName, newOwner) + return err + } + appLogger.Debugf("Resp [%s]", resp.String()) + + appLogger.Debugf("'%s' is the new owner of '%s: %s'!", newOwner, lotNum, assetName) + appLogger.Debug("------------- Done!") + return +} + +func listOwnedAssets() { + ownedAssets := make([]string, 0, len(assets)) + + for _, lotNum := range lotNums { + assetName := assets[lotNum] + + if isOwner(assetName, user) { + ownedAsset := "'" + lotNum + ": " + assetName + "'" + ownedAssets = append(ownedAssets, ownedAsset) + } + } + + appLogger.Debugf("'%s' owns the following %d assets:", user, len(ownedAssets)) + + for _, asset := range ownedAssets { + appLogger.Debug(asset) + } +} + +func isOwner(assetName string, user string) (isOwner bool) { + appLogger.Debug("Query....") + queryTx, theOwnerIs, err := whoIsTheOwner(myClient, assetName) + if err != nil { + return false + } + appLogger.Debugf("Resp [%s]", theOwnerIs.String()) + appLogger.Debug("Query....done") + + var res []byte + if confidentialityOn { + // Decrypt result + res, err = myClient.DecryptQueryResult(queryTx, theOwnerIs.Msg) + if err != nil { + appLogger.Errorf("Failed decrypting result [%s]", err) + return false + } + } else { + res = theOwnerIs.Msg + } + + if !reflect.DeepEqual(res, certs[user].GetCertificate()) { + appLogger.Errorf("'%s' is not the owner.", user) + + appLogger.Debugf("Query result : [% x]", res) + appLogger.Debugf("%s's cert: [% x]", certs[user].GetCertificate(), user) + + return false + } else { + return true + } +} + +func isUserKnown(userName string) (ok bool) { + _, ok = clients[userName] + return ok +} + +func isAssetKnown(assetName string) (ok bool) { + _, ok = assets[assetName] + return ok +} + +func main() { + if len(os.Args) != 3 { + appLogger.Error("Error -- A ChaincodeName and username must be specified.") + os.Exit(-1) + } + + chaincodeName = os.Args[1] + user = os.Args[2] + + // Initialize a non-validating peer whose role is to submit + // transactions to the fabric network. + // A 'core.yaml' file is assumed to be available in the working directory. + if err := initNVP(); err != nil { + appLogger.Debugf("Failed initiliazing NVP [%s]", err) + os.Exit(-1) + } + + if !isUserKnown(user) { + appLogger.Errorf("Error -- user '%s' is not known.", user) + os.Exit(-1) + } + + // Enable fabric 'confidentiality' + confidentiality(true) + + reader := bufio.NewReader(os.Stdin) + + for { + fmt.Printf("%s$ ", user) + line, _ := reader.ReadString('\n') + command := strings.Split(strings.TrimRight(line, "\n"), " ") + + if command[0] == "transfer" { + transferOwnership(command[1], command[2]) + } else if command[0] == "list" { + listOwnedAssets() + } else if command[0] == "exit" { + os.Exit(0) + } + } +} diff --git a/examples/chaincode/go/asset_management_interactive/app3/app3_internal.go b/examples/chaincode/go/asset_management_interactive/app3/app3_internal.go new file mode 100644 index 00000000000..02a4fa23bf7 --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/app3/app3_internal.go @@ -0,0 +1,385 @@ +/* +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 main + +import ( + "bufio" + "encoding/base64" + "errors" + "fmt" + "os" + + "strings" + + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/core/chaincode" + "github.com/hyperledger/fabric/core/chaincode/platforms" + "github.com/hyperledger/fabric/core/config" + "github.com/hyperledger/fabric/core/container" + "github.com/hyperledger/fabric/core/crypto" + "github.com/hyperledger/fabric/core/peer" + "github.com/hyperledger/fabric/core/util" + pb "github.com/hyperledger/fabric/protos" + "github.com/op/go-logging" + "github.com/spf13/viper" + "golang.org/x/net/context" +) + +var ( + confidentialityOn bool + + confidentialityLevel pb.ConfidentialityLevel + chaincodeName string + user string +) + +func initNVP() (err error) { + if err = initPeerClient(); err != nil { + appLogger.Debugf("Failed deploying [%s]", err) + return + + } + if err = initCryptoClients(); err != nil { + appLogger.Debugf("Failed deploying [%s]", err) + return + } + + if err = readAssets(); err != nil { + appLogger.Debugf("Failed reading assets [%s]", err) + return + } + + return +} + +func initPeerClient() (err error) { + config.SetupTestConfig(".") + viper.Set("ledger.blockchain.deploy-system-chaincode", "false") + viper.Set("peer.validator.validity-period.verification", "false") + + peerClientConn, err = peer.NewPeerClientConnection() + if err != nil { + fmt.Printf("error connection to server at host:port = %s\n", viper.GetString("peer.address")) + return + } + serverClient = pb.NewPeerClient(peerClientConn) + + // Logging + var formatter = logging.MustStringFormatter( + `%{color}[%{module}] %{shortfunc} [%{shortfile}] -> %{level:.4s} %{id:03x}%{color:reset} %{message}`, + ) + logging.SetFormatter(formatter) + + return +} + +func initCryptoClients() error { + crypto.Init() + + // Initialize the clients mapping charlie, dave, and edwina + // to identities already defined in 'membersrvc.yaml' + + // Charlie as diego + if err := crypto.RegisterClient("diego", nil, "diego", "DRJ23pEQl16a"); err != nil { + return err + } + var err error + charlie, err = crypto.InitClient("diego", nil) + if err != nil { + return err + } + + // Dave as binhn + if err := crypto.RegisterClient("binhn", nil, "binhn", "7avZQLwcUe9q"); err != nil { + return err + } + dave, err = crypto.InitClient("binhn", nil) + if err != nil { + return err + } + + // Edwina as test_user0 + if err := crypto.RegisterClient("test_user0", nil, "test_user0", "MS9qrN8hFjlE"); err != nil { + return err + } + edwina, err = crypto.InitClient("test_user0", nil) + if err != nil { + return err + } + + charlieCert, err = charlie.GetEnrollmentCertificateHandler() + if err != nil { + appLogger.Errorf("Failed getting Charlie ECert [%s]", err) + return err + } + + daveCert, err = dave.GetEnrollmentCertificateHandler() + if err != nil { + appLogger.Errorf("Failed getting Dave ECert [%s]", err) + return err + } + + edwinaCert, err = edwina.GetEnrollmentCertificateHandler() + if err != nil { + appLogger.Errorf("Failed getting Edwina ECert [%s]", err) + return err + } + + clients = map[string]crypto.Client{"charlie": charlie, "dave": dave, "edwina": edwina} + certs = map[string]crypto.CertificateHandler{"charlie": charlieCert, "dave": daveCert, "edwina": edwinaCert} + + myClient = clients[user] + myCert = certs[user] + + return nil +} + +func readAssets() error { + assets = make(map[string]string) + lotNums = make([]string, 0, 47) + + file, err := os.Open("assets.txt") + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + assetLine := scanner.Text() + assetParts := strings.Split(assetLine, ";") + + lotNum := assetParts[0] + assetName := assetParts[1] + + assets[lotNum] = assetName + lotNums = append(lotNums, lotNum) + } + + if err := scanner.Err(); err != nil { + return err + } + + return nil +} + +func processTransaction(tx *pb.Transaction) (*pb.Response, error) { + return serverClient.ProcessTransaction(context.Background(), tx) +} + +func confidentiality(enabled bool) { + confidentialityOn = enabled + + if confidentialityOn { + confidentialityLevel = pb.ConfidentialityLevel_CONFIDENTIAL + } else { + confidentialityLevel = pb.ConfidentialityLevel_PUBLIC + } +} + +func deployInternal(deployer crypto.Client, adminCert crypto.CertificateHandler) (resp *pb.Response, err error) { + // Prepare the spec. The metadata includes the identity of the administrator + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Path: "github.com/hyperledger/fabric/examples/chaincode/go/asset_management"}, + //ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: &pb.ChaincodeInput{Args: util.ToChaincodeArgs("init")}, + Metadata: adminCert.GetCertificate(), + ConfidentialityLevel: confidentialityLevel, + } + + // First build the deployment spec + cds, err := getChaincodeBytes(spec) + if err != nil { + return nil, fmt.Errorf("Error getting deployment spec: %s ", err) + } + + // Now create the Transactions message and send to Peer. + transaction, err := deployer.NewChaincodeDeployTransaction(cds, cds.ChaincodeSpec.ChaincodeID.Name) + if err != nil { + return nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + resp, err = processTransaction(transaction) + + appLogger.Debugf("resp [%s]", resp.String()) + + chaincodeName = cds.ChaincodeSpec.ChaincodeID.Name + appLogger.Debugf("ChaincodeName [%s]", chaincodeName) + + return +} + +func assignOwnershipInternal(invoker crypto.Client, invokerCert crypto.CertificateHandler, asset string, newOwnerCert crypto.CertificateHandler) (resp *pb.Response, err error) { + // Get a transaction handler to be used to submit the execute transaction + // and bind the chaincode access control logic using the binding + submittingCertHandler, err := invoker.GetTCertificateHandlerNext() + if err != nil { + return nil, err + } + txHandler, err := submittingCertHandler.GetTransactionHandler() + if err != nil { + return nil, err + } + binding, err := txHandler.GetBinding() + if err != nil { + return nil, err + } + + chaincodeInput := &pb.ChaincodeInput{ + Args: util.ToChaincodeArgs("assign", asset, base64.StdEncoding.EncodeToString(newOwnerCert.GetCertificate())), + } + chaincodeInputRaw, err := proto.Marshal(chaincodeInput) + if err != nil { + return nil, err + } + + // Access control. Administrator signs chaincodeInputRaw || binding to confirm his identity + sigma, err := invokerCert.Sign(append(chaincodeInputRaw, binding...)) + if err != nil { + return nil, err + } + + // Prepare spec and submit + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: chaincodeInput, + Metadata: sigma, // Proof of identity + ConfidentialityLevel: confidentialityLevel, + } + + chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + // Now create the Transactions message and send to Peer. + transaction, err := txHandler.NewChaincodeExecute(chaincodeInvocationSpec, util.GenerateUUID()) + if err != nil { + return nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + return processTransaction(transaction) +} + +func transferOwnershipInternal(owner crypto.Client, ownerCert crypto.CertificateHandler, asset string, newOwnerCert crypto.CertificateHandler) (resp *pb.Response, err error) { + // Get a transaction handler to be used to submit the execute transaction + // and bind the chaincode access control logic using the binding + + submittingCertHandler, err := owner.GetTCertificateHandlerNext() + if err != nil { + return nil, err + } + txHandler, err := submittingCertHandler.GetTransactionHandler() + if err != nil { + return nil, err + } + binding, err := txHandler.GetBinding() + if err != nil { + return nil, err + } + + chaincodeInput := &pb.ChaincodeInput{ + Args: util.ToChaincodeArgs("transfer", asset, base64.StdEncoding.EncodeToString(newOwnerCert.GetCertificate())), + } + chaincodeInputRaw, err := proto.Marshal(chaincodeInput) + if err != nil { + return nil, err + } + + // Access control. Owner signs chaincodeInputRaw || binding to confirm his identity + sigma, err := ownerCert.Sign(append(chaincodeInputRaw, binding...)) + if err != nil { + return nil, err + } + + // Prepare spec and submit + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: chaincodeInput, + Metadata: sigma, // Proof of identity + ConfidentialityLevel: confidentialityLevel, + } + + chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + // Now create the Transactions message and send to Peer. + transaction, err := txHandler.NewChaincodeExecute(chaincodeInvocationSpec, util.GenerateUUID()) + if err != nil { + return nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + return processTransaction(transaction) + +} + +func whoIsTheOwner(invoker crypto.Client, asset string) (transaction *pb.Transaction, resp *pb.Response, err error) { + chaincodeInput := &pb.ChaincodeInput{Args: util.ToChaincodeArgs("query", asset)} + + // Prepare spec and submit + spec := &pb.ChaincodeSpec{ + Type: 1, + ChaincodeID: &pb.ChaincodeID{Name: chaincodeName}, + CtorMsg: chaincodeInput, + ConfidentialityLevel: confidentialityLevel, + } + + chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + // Now create the Transactions message and send to Peer. + transaction, err = invoker.NewChaincodeQuery(chaincodeInvocationSpec, util.GenerateUUID()) + if err != nil { + return nil, nil, fmt.Errorf("Error deploying chaincode: %s ", err) + } + + resp, err = processTransaction(transaction) + return +} + +func getChaincodeBytes(spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) { + mode := viper.GetString("chaincode.mode") + var codePackageBytes []byte + if mode != chaincode.DevModeUserRunsChaincode { + appLogger.Debugf("Received build request for chaincode spec: %v", spec) + var err error + if err = checkSpec(spec); err != nil { + return nil, err + } + + codePackageBytes, err = container.GetChaincodePackageBytes(spec) + if err != nil { + err = fmt.Errorf("Error getting chaincode package bytes: %s", err) + appLogger.Errorf("%s", err) + return nil, err + } + } + chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes} + return chaincodeDeploymentSpec, nil +} + +func checkSpec(spec *pb.ChaincodeSpec) error { + // Don't allow nil value + if spec == nil { + return errors.New("Expected chaincode specification, nil received") + } + + platform, err := platforms.Find(spec.Type) + if err != nil { + return fmt.Errorf("Failed to determine platform type: %s", err) + } + + return platform.ValidateSpec(spec) +} diff --git a/examples/chaincode/go/asset_management_interactive/app3/assets.txt b/examples/chaincode/go/asset_management_interactive/app3/assets.txt new file mode 100644 index 00000000000..298c9910348 --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/app3/assets.txt @@ -0,0 +1,47 @@ +Lot01;BERNARD LEACH VASE WITH 'LEAPING FISH' DESIGN +Lot02;WINIFRED NICHOLSON ST IVES HARBOUR +Lot03;WILHELMINA BARNS-GRAHAM GLACIER (BONE) +Lot04;PETER LANYON TREVALGAN +Lot05;DAMIEN HIRST WITH DAVID BOWIE BEAUTIFUL, HALLO, SPACE-BOY PAINTING +Lot06;PERCY WYNDHAM LEWIS FUTURIST FIGURE +Lot07;DAVID BOMBERG MOORISH RONDA, ANDALUCIA +Lot08;PERCY WYNDHAM LEWIS CIRCUS SCENE +Lot09;HENRY LAMB STUDY FOR PORTRAIT OF LYTTON STRACHEY +Lot10;HAROLD GILMAN INTERIOR (MRS MOUNTER) +Lot11;SIR JACOB EPSTEIN LES FLEURS DU MAL - HYMNE LA BEAUT +Lot12;HENRY MOORE, O.M., C.H. FAMILY GROUP +Lot13;WILLIAM SCOTT, R.A. GIRL SEATED AT A TABLE +Lot14;IVON HITCHENS THE BOATHOUSE NO. 3 +Lot15;KENNETH ARMITAGE, R.A. FIGURE LYING ON ITS SIDE (VERSION 3) +Lot16;JACK BUTLER YEATS, R.H.A. SLEEP SOUND +Lot17;DAVID BOMBERG SUNRISE IN THE MOUNTAINS, PICOS DE ASTURIAS +Lot18;FRANK AUERBACH HEAD OF GERDA BOEHM +Lot19;SIR EDUARDO PAOLOZZI, R.A. WEDDING OF THE ROCK AND DYNAMO +Lot20;PETER LANYON WITNESS +Lot21;JEAN-MICHEL BASQUIAT UNTITLED +Lot22;JEAN-MICHEL BASQUIAT AIR POWER +Lot23;ALAN DAVIE PETER'S JOY-PIT +Lot24;BRYAN WYNTER IN THE STREAM'S PATH +Lot25;KENNETH ARMITAGE, R.A. MODEL FOR DIARCHY (SMALL VERSION) +Lot26;LEON KOSSOFF DALSTON JUNCTION NO.3, JUNE 1973 +Lot27;WILLIAM SCOTT, R.A. WINTER STILL LIFE NO.2 +Lot28;FRANK AUERBACH E.O.W'S RECLINING HEAD +Lot29;PETER LANYON INSHORE FISHING +Lot30;GRAHAM SUTHERLAND, O.M. THORN BUSH +Lot31;SIR EDUARDO PAOLOZZI, R.A. FORMS ON A BOW NO. 2 +Lot32;EDWARD WADSWORTH AU REVOIR +Lot33;LOUIS LE BROCQUY, H.R.H.A. IMAGE OF JAMES JOYCE +Lot34;MARCEL DUCHAMP WITH HIDDEN NOISE (A BRUIT SECRET) +Lot35;MARCEL DUCHAMP NUDE DESCENDING A STAIRCASE, NO. 2 +Lot36;MERET OPPENHEIM LA CONDITION HUMAINE +Lot37;FRANCIS PICABIA MENDICA +Lot38;JACOPO ROBUSTI, CALLED JACOPO TINTORETTO AND STUDIO THE ANGEL FORETELLING SAINT CATHERINE OF ALEXANDRIA OF HER MARTYRDOM +Lot39;SIR STANLEY SPENCER, R.A. CARRYING MATTRESSES +Lot40;DAVID BOMBERG PLAZUELA DE LA PAZ, RONDA +Lot41;WILLIAM TURNBULL LARGE IDOL +Lot42;PATRICK HUGHES LIQUORICE ALLSORTS +Lot43;PATRICK CAULFIELD, R.A., C.B.E. FOYER +Lot44;VICTOR PASMORE ABSTRACT IN WHITE, BLACK, GREEN AND CRIMSON +Lot45;GILBERT & GEORGE LEAF +Lot46;DAMIEN HIRST UNTITLED FISH FOR DAVID +Lot47;DAMIEN HIRST BEAUTIFUL, SHATTERING, SLASHING, VIOLENT, PINKY, HACKING, SPHINCTER PAINTING \ No newline at end of file diff --git a/examples/chaincode/go/asset_management_interactive/asset_management.go b/examples/chaincode/go/asset_management_interactive/asset_management.go new file mode 100644 index 00000000000..1734c2c672d --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/asset_management.go @@ -0,0 +1,305 @@ +/* +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 main + +import ( + "encoding/base64" + "errors" + "fmt" + + "github.com/hyperledger/fabric/core/chaincode/shim" + "github.com/hyperledger/fabric/core/crypto/primitives" + "github.com/op/go-logging" +) + +var myLogger = logging.MustGetLogger("asset_mgm") + +// AssetManagementChaincode is simple chaincode implementing a basic Asset Management system +// with access control enforcement at chaincode level. +// Look here for more information on how to implement access control at chaincode level: +// https://github.com/hyperledger/fabric/blob/master/docs/tech/application-ACL.md +// An asset is simply represented by a string. +type AssetManagementChaincode struct { +} + +// Init method will be called during deployment. +// The deploy transaction metadata is supposed to contain the administrator cert +func (t *AssetManagementChaincode) Init(stub shim.ChaincodeStubInterface) ([]byte, error) { + _, args := stub.GetFunctionAndParameters() + myLogger.Debug("Init Chaincode...") + if len(args) != 0 { + return nil, errors.New("Incorrect number of arguments. Expecting 0") + } + + // Create ownership table + err := stub.CreateTable("AssetsOwnership", []*shim.ColumnDefinition{ + &shim.ColumnDefinition{Name: "Asset", Type: shim.ColumnDefinition_STRING, Key: true}, + &shim.ColumnDefinition{Name: "Owner", Type: shim.ColumnDefinition_BYTES, Key: false}, + }) + if err != nil { + return nil, errors.New("Failed creating AssetsOnwership table.") + } + + // Set the admin + // The metadata will contain the certificate of the administrator + adminCert, err := stub.GetCallerMetadata() + if err != nil { + myLogger.Debug("Failed getting metadata") + return nil, errors.New("Failed getting metadata.") + } + if len(adminCert) == 0 { + myLogger.Debug("Invalid admin certificate. Empty.") + return nil, errors.New("Invalid admin certificate. Empty.") + } + + myLogger.Debug("The administrator is [%x]", adminCert) + + stub.PutState("admin", adminCert) + + myLogger.Debug("Init Chaincode...done") + + return nil, nil +} + +func (t *AssetManagementChaincode) assign(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) { + myLogger.Debug("Assign...") + + if len(args) != 2 { + return nil, errors.New("Incorrect number of arguments. Expecting 2") + } + + asset := args[0] + owner, err := base64.StdEncoding.DecodeString(args[1]) + if err != nil { + return nil, errors.New("Failed decodinf owner") + } + + // Verify the identity of the caller + // Only an administrator can invoker assign + adminCertificate, err := stub.GetState("admin") + if err != nil { + return nil, errors.New("Failed fetching admin identity") + } + + ok, err := t.isCaller(stub, adminCertificate) + if err != nil { + return nil, errors.New("Failed checking admin identity") + } + if !ok { + return nil, errors.New("The caller is not an administrator") + } + + // Register assignment + myLogger.Debugf("New owner of [%s] is [% x]", asset, owner) + + ok, err = stub.InsertRow("AssetsOwnership", shim.Row{ + Columns: []*shim.Column{ + &shim.Column{Value: &shim.Column_String_{String_: asset}}, + &shim.Column{Value: &shim.Column_Bytes{Bytes: owner}}}, + }) + + if !ok && err == nil { + return nil, errors.New("Asset was already assigned.") + } + + myLogger.Debug("Assign...done!") + + return nil, err +} + +func (t *AssetManagementChaincode) transfer(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) { + myLogger.Debug("Transfer...") + + if len(args) != 2 { + return nil, errors.New("Incorrect number of arguments. Expecting 2") + } + + asset := args[0] + newOwner, err := base64.StdEncoding.DecodeString(args[1]) + if err != nil { + return nil, fmt.Errorf("Failed decoding owner") + } + + // Verify the identity of the caller + // Only the owner can transfer one of his assets + var columns []shim.Column + col1 := shim.Column{Value: &shim.Column_String_{String_: asset}} + columns = append(columns, col1) + + row, err := stub.GetRow("AssetsOwnership", columns) + if err != nil { + return nil, fmt.Errorf("Failed retrieving asset [%s]: [%s]", asset, err) + } + + prvOwner := row.Columns[1].GetBytes() + myLogger.Debugf("Previous owener of [%s] is [% x]", asset, prvOwner) + if len(prvOwner) == 0 { + return nil, fmt.Errorf("Invalid previous owner. Nil") + } + + // Verify ownership + ok, err := t.isCaller(stub, prvOwner) + if err != nil { + return nil, errors.New("Failed checking asset owner identity") + } + if !ok { + return nil, errors.New("The caller is not the owner of the asset") + } + + // At this point, the proof of ownership is valid, then register transfer + err = stub.DeleteRow( + "AssetsOwnership", + []shim.Column{shim.Column{Value: &shim.Column_String_{String_: asset}}}, + ) + if err != nil { + return nil, errors.New("Failed deliting row.") + } + + _, err = stub.InsertRow( + "AssetsOwnership", + shim.Row{ + Columns: []*shim.Column{ + &shim.Column{Value: &shim.Column_String_{String_: asset}}, + &shim.Column{Value: &shim.Column_Bytes{Bytes: newOwner}}, + }, + }) + if err != nil { + return nil, errors.New("Failed inserting row.") + } + + myLogger.Debug("New owner of [%s] is [% x]", asset, newOwner) + + myLogger.Debug("Transfer...done") + + return nil, nil +} + +func (t *AssetManagementChaincode) isCaller(stub shim.ChaincodeStubInterface, certificate []byte) (bool, error) { + myLogger.Debug("Check caller...") + + // In order to enforce access control, we require that the + // metadata contains the signature under the signing key corresponding + // to the verification key inside certificate of + // the payload of the transaction (namely, function name and args) and + // the transaction binding (to avoid copying attacks) + + // Verify \sigma=Sign(certificate.sk, tx.Payload||tx.Binding) against certificate.vk + // \sigma is in the metadata + + sigma, err := stub.GetCallerMetadata() + if err != nil { + return false, errors.New("Failed getting metadata") + } + payload, err := stub.GetPayload() + if err != nil { + return false, errors.New("Failed getting payload") + } + binding, err := stub.GetBinding() + if err != nil { + return false, errors.New("Failed getting binding") + } + + myLogger.Debugf("passed certificate [% x]", certificate) + myLogger.Debugf("passed sigma [% x]", sigma) + myLogger.Debugf("passed payload [% x]", payload) + myLogger.Debugf("passed binding [% x]", binding) + + ok, err := stub.VerifySignature( + certificate, + sigma, + append(payload, binding...), + ) + if err != nil { + myLogger.Errorf("Failed checking signature [%s]", err) + return ok, err + } + if !ok { + myLogger.Error("Invalid signature") + } + + myLogger.Debug("Check caller...Verified!") + + return ok, err +} + +// Invoke will be called for every transaction. +// Supported functions are the following: +// "assign(asset, owner)": to assign ownership of assets. An asset can be owned by a single entity. +// Only an administrator can call this function. +// "transfer(asset, newOwner)": to transfer the ownership of an asset. Only the owner of the specific +// asset can call this function. +// An asset is any string to identify it. An owner is representated by one of his ECert/TCert. +func (t *AssetManagementChaincode) Invoke(stub shim.ChaincodeStubInterface) ([]byte, error) { + function, args := stub.GetFunctionAndParameters() + // Handle different functions + if function == "assign" { + // Assign ownership + return t.assign(stub, args) + } else if function == "transfer" { + // Transfer ownership + return t.transfer(stub, args) + } + + return nil, errors.New("Received unknown function invocation") +} + +// Query callback representing the query of a chaincode +// Supported functions are the following: +// "query(asset)": returns the owner of the asset. +// Anyone can invoke this function. +func (t *AssetManagementChaincode) Query(stub shim.ChaincodeStubInterface) ([]byte, error) { + function, args := stub.GetFunctionAndParameters() + myLogger.Debugf("Query [%s]", function) + + if function != "query" { + return nil, errors.New("Invalid query function name. Expecting 'query' but found '" + function + "'") + } + + var err error + + if len(args) != 1 { + myLogger.Debug("Incorrect number of arguments. Expecting name of an asset to query") + return nil, errors.New("Incorrect number of arguments. Expecting name of an asset to query") + } + + // Who is the owner of the asset? + asset := args[0] + + myLogger.Debugf("Arg [%s]", string(asset)) + + var columns []shim.Column + col1 := shim.Column{Value: &shim.Column_String_{String_: asset}} + columns = append(columns, col1) + + row, err := stub.GetRow("AssetsOwnership", columns) + if err != nil { + myLogger.Debugf("Failed retriving asset [%s]: [%s]", string(asset), err) + return nil, fmt.Errorf("Failed retriving asset [%s]: [%s]", string(asset), err) + } + + myLogger.Debugf("Query done [% x]", row.Columns[1].GetBytes()) + + return row.Columns[1].GetBytes(), nil +} + +func main() { + primitives.SetSecurityLevel("SHA3", 256) + err := shim.Start(new(AssetManagementChaincode)) + if err != nil { + fmt.Printf("Error starting AssetManagementChaincode: %s", err) + } +} diff --git a/examples/chaincode/go/asset_management_interactive/build.sh b/examples/chaincode/go/asset_management_interactive/build.sh new file mode 100755 index 00000000000..863d8bc268a --- /dev/null +++ b/examples/chaincode/go/asset_management_interactive/build.sh @@ -0,0 +1,13 @@ +cd app1 +cp ../core.yaml . +go build + +cd ../app2 +cp ../core.yaml . +go build + +cd ../app3 +cp ../core.yaml . +go build + +cd ..