From 99b68175cacf94b97cd1580bc06660f4954c971a Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Fri, 14 Apr 2017 19:33:05 -0400 Subject: [PATCH] [FAB-3170] invokechaincode from java chaincode - Added invokeChaincode methods to ChaincodeStub - Added Java chaincode test to existing TestChaincodeInvokeChaincode Change-Id: I1b0674f6cc40cf6c3c5b10fb8d24274190e115d6 Signed-off-by: Luis Sanchez --- core/chaincode/chaincodetest.yaml | 8 +- core/chaincode/exectransaction_test.go | 314 ++++++++++-------- .../fabric/shim/ChaincodeStub.java | 93 ++++-- .../org/hyperledger/fabric/shim/Handler.java | 113 +++---- .../fabric/shim/HandlerHelper.java | 5 + examples/chaincode/java/.gitignore | 1 + .../src/main/java/example/LinkExample.java | 83 +++-- .../java/chaincode_example04/build.gradle | 84 +++++ .../src/main/java/example/Example04.java | 163 +++++++++ .../java/chaincode_example05/build.gradle | 83 +++++ .../src/main/java/example/Example05.java | 142 ++++++++ 11 files changed, 820 insertions(+), 269 deletions(-) create mode 100644 examples/chaincode/java/.gitignore create mode 100644 examples/chaincode/java/chaincode_example04/build.gradle create mode 100644 examples/chaincode/java/chaincode_example04/src/main/java/example/Example04.java create mode 100644 examples/chaincode/java/chaincode_example05/build.gradle create mode 100644 examples/chaincode/java/chaincode_example05/src/main/java/example/Example05.java diff --git a/core/chaincode/chaincodetest.yaml b/core/chaincode/chaincodetest.yaml index 85967a820ac..e1904d86dc7 100644 --- a/core/chaincode/chaincodetest.yaml +++ b/core/chaincode/chaincodetest.yaml @@ -134,13 +134,13 @@ logging: # info - Set default to INFO # warning:main,db=debug:chaincode=info - Override default WARNING in main,db,chaincode # chaincode=info:main=debug:db=debug:warning - Same as above - peer: debug - crypto: info + peer: warning + crypto: warning status: warning stop: warning login: warning - vm: debug - chaincode: debug + vm: warning + chaincode: warning ############################################################################### diff --git a/core/chaincode/exectransaction_test.go b/core/chaincode/exectransaction_test.go index 04c4ed1fe87..a27feb832dd 100644 --- a/core/chaincode/exectransaction_test.go +++ b/core/chaincode/exectransaction_test.go @@ -527,7 +527,7 @@ func _(chainID string, _ string) error { //} // Check the correctness of the final state after transaction execution. -func checkFinalState(cccid *ccprovider.CCContext) error { +func checkFinalState(cccid *ccprovider.CCContext, a int, b int) error { _, txsim, err := startTxSimulation(context.Background(), cccid.ChainID) if err != nil { return fmt.Errorf("Failed to get handle to simulator: %s ", err) @@ -548,8 +548,8 @@ func checkFinalState(cccid *ccprovider.CCContext) error { if resErr != nil { return fmt.Errorf("Error retrieving state from ledger for <%s>: %s", cName, resErr) } - if Aval != 90 { - return fmt.Errorf("Incorrect result. Aval %d != 90 <%s>", Aval, cName) + if Aval != a { + return fmt.Errorf("Incorrect result. Aval %d != %d <%s>", Aval, a, cName) } resbytes, resErr = txsim.GetState(cccid.Name, "b") @@ -560,8 +560,8 @@ func checkFinalState(cccid *ccprovider.CCContext) error { if resErr != nil { return fmt.Errorf("Error retrieving state from ledger for <%s>: %s", cName, resErr) } - if Bval != 210 { - return fmt.Errorf("Incorrect result. Bval %d != 210 <%s>", Bval, cName) + if Bval != b { + return fmt.Errorf("Incorrect result. Bval %d != %d <%s>", Bval, b, cName) } // Success @@ -606,7 +606,7 @@ func invokeExample02Transaction(ctxt context.Context, cccid *ccprovider.CCContex } cccid.TxID = uuid - err = checkFinalState(cccid) + err = checkFinalState(cccid, 90, 210) if err != nil { return fmt.Errorf("Incorrect final state after transaction for <%s>: %s", ccID, err) } @@ -625,172 +625,138 @@ func invokeExample02Transaction(ctxt context.Context, cccid *ccprovider.CCContex const ( chaincodeExample02GolangPath = "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" + chaincodeExample04GolangPath = "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example04" + chaincodeExample05GolangPath = "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example05" chaincodeEventSenderGolangPath = "github.com/hyperledger/fabric/examples/chaincode/go/eventsender" chaincodeExample02JavaPath = "../../examples/chaincode/java/chaincode_example02" + chaincodeExample04JavaPath = "../../examples/chaincode/java/chaincode_example04" + chaincodeExample05JavaPath = "../../examples/chaincode/java/chaincode_example05" chaincodeExample06JavaPath = "../../examples/chaincode/java/chaincode_example06" chaincodeEventSenderJavaPath = "../../examples/chaincode/java/eventsender" ) -func runChaincodeInvokeChaincode(t *testing.T, chainID1 string, chainID2 string, _ string) (err error) { +func runChaincodeInvokeChaincode(t *testing.T, channel1 string, channel2 string, tc tcicTc, cccid1 *ccprovider.CCContext, expectedA int, expectedB int, nextBlockNumber1, nextBlockNumber2 uint64) (uint64, uint64) { var ctxt = context.Background() - // Deploy first chaincode - url1 := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" - - cID1 := &pb.ChaincodeID{Name: "example02", Path: url1, Version: "0"} - f := "init" - args := util.ToChaincodeArgs(f, "a", "100", "b", "200") - - spec1 := &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID1, Input: &pb.ChaincodeInput{Args: args}} + // chaincode2: the chaincode that will call by chaincode1 + chaincode2Name := generateChaincodeName(tc.chaincodeType) + chaincode2Version := "0" + chaincode2Type := tc.chaincodeType + chaincode2Path := tc.chaincodePath + chaincode2InitArgs := util.ToChaincodeArgs("init", "e", "0") + chaincode2Creator := []byte([]byte("Alice")) - sProp, prop := putils.MockSignedEndorserProposalOrPanic(chainID1, spec1, []byte([]byte("Alice")), nil) - cccid1 := ccprovider.NewCCContext(chainID1, "example02", "0", "", false, sProp, prop) - - var nextBlockNumber uint64 = 1 - - _, err = deploy(ctxt, cccid1, spec1, nextBlockNumber) - nextBlockNumber++ - ccID1 := spec1.ChaincodeId.Name + // deploy second chaincode on channel1 + _, cccid2, err := deployChaincode(ctxt, chaincode2Name, chaincode2Version, chaincode2Type, chaincode2Path, chaincode2InitArgs, chaincode2Creator, channel1, nextBlockNumber1) if err != nil { - t.Fail() - t.Logf("Error initializing chaincode %s(%s)", ccID1, err) - theChaincodeSupport.Stop(ctxt, cccid1, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec1}) - return - } - - t.Logf("deployed chaincode_example02 got cID1:% s,\n ccID1:% s", cID1, ccID1) - - time.Sleep(time.Second) - - // Deploy second chaincode - url2 := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example04" - - cID2 := &pb.ChaincodeID{Name: "example04", Path: url2, Version: "0"} - f = "init" - args = util.ToChaincodeArgs(f, "e", "0") - - spec2 := &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID2, Input: &pb.ChaincodeInput{Args: args}} - - sProp, prop = putils.MockSignedEndorserProposalOrPanic(chainID1, spec2, []byte([]byte("Alice")), nil) - cccid2 := ccprovider.NewCCContext(chainID1, "example04", "0", "", false, sProp, prop) - - _, err = deploy(ctxt, cccid2, spec2, nextBlockNumber) - nextBlockNumber++ - ccID2 := spec2.ChaincodeId.Name - if err != nil { - t.Fail() - t.Logf("Error initializing chaincode %s(%s)", ccID2, err) - theChaincodeSupport.Stop(ctxt, cccid1, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec1}) - theChaincodeSupport.Stop(ctxt, cccid2, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec2}) - return + stopChaincode(ctxt, cccid1) + stopChaincode(ctxt, cccid2) + t.Fatalf("Error initializing chaincode %s(%s)", chaincode2Name, err) + return nextBlockNumber1, nextBlockNumber2 } + nextBlockNumber1++ time.Sleep(time.Second) // Invoke second chaincode passing the first chaincode's name as first param, // which will inturn invoke the first chaincode - f = "invoke" - cid := spec1.ChaincodeId.Name - args = util.ToChaincodeArgs(f, cid, "e", "1") - - spec2 = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID2, Input: &pb.ChaincodeInput{Args: args}} + chaincode2InvokeSpec := &pb.ChaincodeSpec{ + Type: chaincode2Type, + ChaincodeId: &pb.ChaincodeID{ + Name: chaincode2Name, + Version: chaincode2Version, + }, + Input: &pb.ChaincodeInput{ + Args: util.ToChaincodeArgs("invoke", cccid1.Name, "e", "1"), + }, + } // Invoke chaincode - var uuid string - _, uuid, _, err = invoke(ctxt, chainID1, spec2, nextBlockNumber, []byte("Alice")) - + _, txID, _, err := invoke(ctxt, channel1, chaincode2InvokeSpec, nextBlockNumber1, []byte("Alice")) if err != nil { - t.Fail() - t.Logf("Error invoking <%s>: %s", ccID2, err) - theChaincodeSupport.Stop(ctxt, cccid1, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec1}) - theChaincodeSupport.Stop(ctxt, cccid2, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec2}) - return + stopChaincode(ctxt, cccid1) + stopChaincode(ctxt, cccid2) + t.Fatalf("Error invoking <%s>: %s", chaincode2Name, err) + return nextBlockNumber1, nextBlockNumber2 } + nextBlockNumber1++ - cccid1.TxID = uuid + // TODO this doesn't seeem to be used, remove? + cccid1.TxID = txID // Check the state in the ledger - err = checkFinalState(cccid1) + err = checkFinalState(cccid1, expectedA, expectedB) if err != nil { - t.Fail() - t.Logf("Incorrect final state after transaction for <%s>: %s", ccID1, err) - theChaincodeSupport.Stop(ctxt, cccid1, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec1}) - theChaincodeSupport.Stop(ctxt, cccid2, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec2}) - return + stopChaincode(ctxt, cccid1) + stopChaincode(ctxt, cccid2) + t.Fatalf("Incorrect final state after transaction for <%s>: %s", cccid1.Name, err) + return nextBlockNumber1, nextBlockNumber2 } // Change the policies of the two channels in such a way: // 1. Alice has reader access to both the channels. // 2. Bob has access only to chainID2. // Therefore the chaincode invocation should fail. - pm := peer.GetPolicyManager(chainID1) + pm := peer.GetPolicyManager(channel1) pm.(*mockpolicies.Manager).PolicyMap = map[string]policies.Policy{ policies.ChannelApplicationWriters: &CreatorPolicy{Creators: [][]byte{[]byte("Alice")}}, } - pm = peer.GetPolicyManager(chainID2) + pm = peer.GetPolicyManager(channel2) pm.(*mockpolicies.Manager).PolicyMap = map[string]policies.Policy{ policies.ChannelApplicationWriters: &CreatorPolicy{Creators: [][]byte{[]byte("Alice"), []byte("Bob")}}, } - url2 = "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example04" - - cID2 = &pb.ChaincodeID{Name: "example04", Path: url2, Version: "0"} - f = "init" - args = util.ToChaincodeArgs(f, "e", "0") - - spec3 := &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID2, Input: &pb.ChaincodeInput{Args: args}} - sProp, prop = putils.MockSignedEndorserProposalOrPanic(chainID2, spec3, []byte([]byte("Alice")), nil) - cccid3 := ccprovider.NewCCContext(chainID2, "example04", "0", "", false, sProp, prop) - - _, err = deploy(ctxt, cccid3, spec3, 1) - chaincodeID2 := spec2.ChaincodeId.Name + // deploy chaincode2 on channel2 + _, cccid3, err := deployChaincode(ctxt, chaincode2Name, chaincode2Version, chaincode2Type, chaincode2Path, chaincode2InitArgs, chaincode2Creator, channel2, nextBlockNumber2) if err != nil { - t.Fail() - t.Logf("Error initializing chaincode %s(%s)", chaincodeID2, err) - theChaincodeSupport.Stop(ctxt, cccid1, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec1}) - theChaincodeSupport.Stop(ctxt, cccid2, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec2}) - theChaincodeSupport.Stop(ctxt, cccid3, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec3}) - return + stopChaincode(ctxt, cccid1) + stopChaincode(ctxt, cccid2) + stopChaincode(ctxt, cccid3) + t.Fatalf("Error initializing chaincode %s/%s: %s", chaincode2Name, channel2, err) + return nextBlockNumber1, nextBlockNumber2 } - + nextBlockNumber2++ time.Sleep(time.Second) - // - // - Invoke second chaincode passing the first chaincode's name as first param, which will in turn invoke the first chaincode - f = "invoke" - cid = spec1.ChaincodeId.Name - args = util.ToChaincodeArgs(f, cid, "e", "1", chainID1) - - spec2 = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID2, Input: &pb.ChaincodeInput{Args: args}} - // Invoke chaincode - //var uuid string - // Bob should not be able to call - _, _, _, err = invoke(ctxt, chainID2, spec2, 2, []byte("Bob")) + // as Bob, invoke chaincode2 on channel2 so that it invokes chaincode1 on channel1 + chaincode2InvokeSpec = &pb.ChaincodeSpec{ + Type: chaincode2Type, + ChaincodeId: &pb.ChaincodeID{ + Name: chaincode2Name, + Version: chaincode2Version, + }, + Input: &pb.ChaincodeInput{ + Args: util.ToChaincodeArgs("invoke", cccid1.Name, "e", "1", channel1), + }, + } + _, _, _, err = invoke(ctxt, channel2, chaincode2InvokeSpec, nextBlockNumber2, []byte("Bob")) if err == nil { - t.Fail() - t.Logf("Bob invoking <%s> should fail. It did not happen instead: %s", chaincodeID2, err) - theChaincodeSupport.Stop(ctxt, cccid1, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec1}) - theChaincodeSupport.Stop(ctxt, cccid2, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec2}) - theChaincodeSupport.Stop(ctxt, cccid3, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec3}) - return + // Bob should not be able to call + stopChaincode(ctxt, cccid1) + stopChaincode(ctxt, cccid2) + stopChaincode(ctxt, cccid3) + nextBlockNumber2++ + t.Fatalf("As Bob, invoking <%s/%s> via <%s/%s> should fail, but it succeeded.", cccid1.Name, cccid1.ChainID, chaincode2Name, channel2) + return nextBlockNumber1, nextBlockNumber2 } - // Alice should be able to call - _, _, _, err = invoke(ctxt, chainID2, spec2, 2, []byte("Alice")) + // as Alice, invoke chaincode2 on channel2 so that it invokes chaincode1 on channel1 + _, _, _, err = invoke(ctxt, channel2, chaincode2InvokeSpec, nextBlockNumber2, []byte("Alice")) if err != nil { - t.Fail() - t.Logf("Alice invoking <%s> should not fail. It did happen instead: %s", chaincodeID2, err) - theChaincodeSupport.Stop(ctxt, cccid1, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec1}) - theChaincodeSupport.Stop(ctxt, cccid2, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec2}) - theChaincodeSupport.Stop(ctxt, cccid3, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec3}) - return + // Alice should be able to call + stopChaincode(ctxt, cccid1) + stopChaincode(ctxt, cccid2) + stopChaincode(ctxt, cccid3) + t.Fatalf("As Alice, invoking <%s/%s> via <%s/%s> should should of succeeded, but it failed: %s", cccid1.Name, cccid1.ChainID, chaincode2Name, channel2, err) + return nextBlockNumber1, nextBlockNumber2 } + nextBlockNumber2++ - theChaincodeSupport.Stop(ctxt, cccid1, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec1}) - theChaincodeSupport.Stop(ctxt, cccid2, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec2}) - theChaincodeSupport.Stop(ctxt, cccid3, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec3}) + stopChaincode(ctxt, cccid1) + stopChaincode(ctxt, cccid2) + stopChaincode(ctxt, cccid3) - return nil + return nextBlockNumber1, nextBlockNumber2 } // Test deploy of a transaction @@ -903,30 +869,84 @@ func TestExecuteInvokeInvalidTransaction(t *testing.T) { theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: ccID}}) } +// testcase parameters for TestChaincodeInvokeChaincode +type tcicTc struct { + chaincodeType pb.ChaincodeSpec_Type + chaincodePath string +} + // Test the execution of a chaincode that invokes another chaincode. func TestChaincodeInvokeChaincode(t *testing.T) { - chainID1 := util.GetTestChainID() - chainID2 := chainID1 + "2" - - lis, err := initPeer(chainID1, chainID2) + channel := util.GetTestChainID() + channel2 := channel + "2" + lis, err := initPeer(channel, channel2) if err != nil { t.Fail() t.Logf("Error creating peer: %s", err) } - defer finitPeer(lis, chainID1, chainID2) + defer finitPeer(lis, channel, channel2) + + testCases := []tcicTc{ + {pb.ChaincodeSpec_GOLANG, chaincodeExample04GolangPath}, + {pb.ChaincodeSpec_JAVA, chaincodeExample04JavaPath}, + } + + ctx := context.Background() + + var nextBlockNumber1 uint64 = 1 + var nextBlockNumber2 uint64 = 1 - err = runChaincodeInvokeChaincode(t, chainID1, chainID2, "") + // deploy the chaincode that will be called by the second chaincode + chaincode1Name := generateChaincodeName(pb.ChaincodeSpec_GOLANG) + chaincode1Version := "0" + chaincode1Type := pb.ChaincodeSpec_GOLANG + chaincode1Path := chaincodeExample02GolangPath + initialA := 100 + initialB := 200 + chaincode1InitArgs := util.ToChaincodeArgs("init", "a", strconv.Itoa(initialA), "b", strconv.Itoa(initialB)) + chaincode1Creator := []byte([]byte("Alice")) + + // Deploy first chaincode + _, chaincodeCtx, err := deployChaincode(ctx, chaincode1Name, chaincode1Version, chaincode1Type, chaincode1Path, chaincode1InitArgs, chaincode1Creator, channel, nextBlockNumber1) if err != nil { - t.Fail() - t.Logf("Failed chaincode invoke chaincode : %s", err) - closeListenerAndSleep(lis) - return + stopChaincode(ctx, chaincodeCtx) + t.Fatalf("Error initializing chaincode %s: %s", chaincodeCtx.Name, err) + } + nextBlockNumber1++ + time.Sleep(time.Second) + + expectedA := initialA + expectedB := initialB + + for _, tc := range testCases { + t.Run(tc.chaincodeType.String(), func(t *testing.T) { + + if tc.chaincodeType == pb.ChaincodeSpec_JAVA && runtime.GOARCH != "amd64" { + t.Skip("No Java chaincode support yet on non-x86_64.") + } + + expectedA = expectedA - 10 + expectedB = expectedB + 10 + nextBlockNumber1, nextBlockNumber2 = runChaincodeInvokeChaincode(t, channel, channel2, tc, chaincodeCtx, expectedA, expectedB, nextBlockNumber1, nextBlockNumber2) + }) } closeListenerAndSleep(lis) } +func stopChaincode(ctx context.Context, chaincodeCtx *ccprovider.CCContext) { + theChaincodeSupport.Stop(ctx, chaincodeCtx, + &pb.ChaincodeDeploymentSpec{ + ChaincodeSpec: &pb.ChaincodeSpec{ + ChaincodeId: &pb.ChaincodeID{ + Name: chaincodeCtx.Name, + Version: chaincodeCtx.Version, + }, + }, + }) +} + // Test the execution of a chaincode that invokes another chaincode with wrong parameters. Should receive error from // from the called chaincode func TestChaincodeInvokeChaincodeErrorCase(t *testing.T) { @@ -1647,7 +1667,7 @@ func TestChaincodeInitializeInitError(t *testing.T) { {"RuntimeException", pb.ChaincodeSpec_JAVA, chaincodeExample06JavaPath, []string{"runtimeException"}}, } - channelID := util.GetTestChainID() + channel := util.GetTestChainID() for _, tc := range testCases { t.Run(tc.name+"_"+tc.chaincodeType.String(), func(t *testing.T) { @@ -1657,10 +1677,10 @@ func TestChaincodeInitializeInitError(t *testing.T) { } // initialize peer - if listener, err := initPeer(channelID); err != nil { + if listener, err := initPeer(channel); err != nil { t.Errorf("Error creating peer: %s", err) } else { - defer finitPeer(listener, channelID) + defer finitPeer(listener, channel) } var nextBlockNumber uint64 @@ -1672,14 +1692,12 @@ func TestChaincodeInitializeInitError(t *testing.T) { chaincodeType := tc.chaincodeType chaincodeDeployArgs := util.ArrayToChaincodeArgs(tc.args) - // new chaincode context for passing around parameters - chaincodeCtx := ccprovider.NewCCContext(channelID, chaincodeName, chaincodeVersion, "", false, nil, nil) - // attempt to deploy chaincode - _, err := deployChaincode(context.Background(), chaincodeCtx, chaincodeType, chaincodePath, chaincodeDeployArgs, nextBlockNumber) + _, chaincodeCtx, err := deployChaincode(context.Background(), chaincodeName, chaincodeVersion, chaincodeType, chaincodePath, chaincodeDeployArgs, nil, channel, nextBlockNumber) // deploy should of failed if err == nil { + stopChaincode(context.Background(), chaincodeCtx) t.Fatal("Deployment should have failed.") } t.Log(err) @@ -1703,12 +1721,12 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func deployChaincode(ctx context.Context, chaincodeCtx *ccprovider.CCContext, chaincodeType pb.ChaincodeSpec_Type, path string, args [][]byte, nextBlockNumber uint64) ([]byte, error) { +func deployChaincode(ctx context.Context, name string, version string, chaincodeType pb.ChaincodeSpec_Type, path string, args [][]byte, creator []byte, channel string, nextBlockNumber uint64) ([]byte, *ccprovider.CCContext, error) { chaincodeSpec := &pb.ChaincodeSpec{ ChaincodeId: &pb.ChaincodeID{ - Name: chaincodeCtx.Name, - Version: chaincodeCtx.Version, + Name: name, + Version: version, Path: path, }, Type: chaincodeType, @@ -1717,11 +1735,15 @@ func deployChaincode(ctx context.Context, chaincodeCtx *ccprovider.CCContext, ch }, } + signedProposal, proposal := putils.MockSignedEndorserProposalOrPanic(channel, chaincodeSpec, creator, nil) + + chaincodeCtx := ccprovider.NewCCContext(channel, name, version, "", false, signedProposal, proposal) + result, err := deploy(ctx, chaincodeCtx, chaincodeSpec, nextBlockNumber) if err != nil { - return nil, fmt.Errorf("Error deploying <%s:%s>: %s", chaincodeSpec.ChaincodeId.Name, chaincodeSpec.ChaincodeId.Version, err) + return nil, chaincodeCtx, fmt.Errorf("Error deploying <%s:%s>: %s", name, version, err) } - return result, nil + return result, chaincodeCtx, nil } var signer msp.SigningIdentity diff --git a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java index 8c540e40475..af722506fbe 100644 --- a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java +++ b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java @@ -16,6 +16,8 @@ package org.hyperledger.fabric.shim; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -23,6 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperledger.fabric.protos.peer.ChaincodeEventPackage.ChaincodeEvent; +import org.hyperledger.fabric.protos.peer.ProposalResponsePackage.Response; import com.google.protobuf.ByteString; @@ -195,13 +198,78 @@ public String createCompositeKey(String objectType, String[] attributes) { } /** - * @param chaincodeName - * @param function - * @param args + * Invoke another chaincode using the same transaction context. + * + * @param chaincodeName Name of chaincode to be invoked. + * @param args Arguments to pass on to the called chaincode. + * @param channel If not specified, the caller's channel is assumed. + * @return + */ + public Response invokeChaincode(final String chaincodeName, final List args, final String channel) { + // internally we handle chaincode name as a composite name + final String compositeName; + if(channel != null && channel.trim().length() > 0) { + compositeName = chaincodeName + "/" + channel; + } else { + compositeName = chaincodeName; + } + return handler.handleInvokeChaincode(compositeName, args, this.uuid); + } + + /** + * Invoke another chaincode using the same transaction context. + * + * @param chaincodeName Name of chaincode to be invoked. + * @param args Arguments to pass on to the called chaincode. + * @return + */ + public Response invokeChaincode(final String chaincodeName, final List args) { + return invokeChaincode(chaincodeName, args, null); + } + + /** + * Invoke another chaincode using the same transaction context. + * + * This is a convenience version of {@link #invokeChaincode(String, List, String)}. + * The string args will be encoded into as UTF-8 bytes. + * + * @param chaincodeName Name of chaincode to be invoked. + * @param args Arguments to pass on to the called chaincode. + * @param channel If not specified, the caller's channel is assumed. + * @return + */ + public Response invokeChaincodeWithStringArgs(final String chaincodeName, final List args, final String channel) { + return invokeChaincode(chaincodeName, args.stream().map(x->x.getBytes(StandardCharsets.UTF_8)).collect(Collectors.toList()), channel); + } + + /** + * Invoke another chaincode using the same transaction context. + * + * This is a convenience version of {@link #invokeChaincode(String, List)}. + * The string args will be encoded into as UTF-8 bytes. + * + * + * @param chaincodeName Name of chaincode to be invoked. + * @param args Arguments to pass on to the called chaincode. * @return */ - public String invokeChaincode(String chaincodeName, String function, List args) { - return handler.handleInvokeChaincode(chaincodeName, function, args, uuid).toStringUtf8(); + public Response invokeChaincodeWithStringArgs(final String chaincodeName, final List args) { + return invokeChaincodeWithStringArgs(chaincodeName, args, null); + } + + /** + * Invoke another chaincode using the same transaction context. + * + * This is a convenience version of {@link #invokeChaincode(String, List)}. + * The string args will be encoded into as UTF-8 bytes. + * + * + * @param chaincodeName Name of chaincode to be invoked. + * @param args Arguments to pass on to the called chaincode. + * @return + */ + public Response invokeChaincodeWithStringArgs(final String chaincodeName, final String... args) { + return invokeChaincodeWithStringArgs(chaincodeName, Arrays.asList(args), null); } // ------RAW CALLS------ @@ -233,19 +301,4 @@ public void putRawState(String key, ByteString value) { // return handler.handleGetStateByRange(startKey, endKey, limit, uuid); // } - /** - * Invokes the provided chaincode with the given function and arguments, and - * returns the raw ByteString value that invocation generated. - * - * @param chaincodeName - * The name of the chaincode to invoke - * @param function - * the function parameter to pass to the chaincode - * @param args - * the arguments to be provided in the chaincode call - * @return the value returned by the chaincode call - */ - public ByteString invokeRawChaincode(String chaincodeName, String function, List args) { - return handler.handleInvokeChaincode(chaincodeName, function, args, uuid); - } } diff --git a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/Handler.java b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/Handler.java index b75f06592ff..d17cfa1e8cf 100644 --- a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/Handler.java +++ b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/Handler.java @@ -35,6 +35,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -635,78 +636,64 @@ public QueryStateResponse handleGetStateByRange(String startKey, String endKey, } } */ - public ByteString handleInvokeChaincode(String chaincodeName, String function, List args, String uuid) { - // Check if this is a transaction - if (!isTransaction.containsKey(uuid)) { - throw new RuntimeException("Cannot invoke chaincode in query context"); - } - - ChaincodeID id = ChaincodeID.newBuilder() - .setName(chaincodeName).build(); - ChaincodeInput input = ChaincodeInput.newBuilder() - .addArgs(ByteString.copyFromUtf8(function)) - .addAllArgs(args) - .build(); - ChaincodeSpec payload = ChaincodeSpec.newBuilder() - .setChaincodeId(id) - .setInput(input) + public Response handleInvokeChaincode(String chaincodeName, List args, String txid) { + + // create invocation specification of the chaincode to invoke + final ChaincodeSpec invocationSpec = ChaincodeSpec.newBuilder() + .setChaincodeId(ChaincodeID.newBuilder() + .setName(chaincodeName) + .build()) + .setInput(ChaincodeInput.newBuilder() + .addAllArgs(args.stream().map(ByteString::copyFrom).collect(Collectors.toList())) + .build()) .build(); - // Create the channel on which to communicate the response from validating peer - Channel responseChannel; - try { - responseChannel = createChannel(uuid); - } catch (Exception e) { - logger.error(String.format("[%s]Another state request pending for this Uuid. Cannot process.", shortID(uuid))); - throw e; - } - - //Defer try { + // create the channel on which to communicate the response from validating peer + final Channel responseChannel = createChannel(txid); + // Send INVOKE_CHAINCODE message to validator chaincode support - ChaincodeMessage message = ChaincodeMessage.newBuilder() - .setType(INVOKE_CHAINCODE) - .setPayload(payload.toByteString()) - .setTxid(uuid) - .build(); - - logger.debug(String.format("[%s]Sending %s", - shortID(message), INVOKE_CHAINCODE)); - - try { - serialSend(message); - } catch (Exception e) { - logger.error("["+ shortID(message)+"]Error sending "+INVOKE_CHAINCODE+": "+e.getMessage()); - throw e; - } - - // Wait on responseChannel for response - ChaincodeMessage response; - try { - response = receiveChannel(responseChannel); - } catch (Exception e) { - logger.error(String.format("[%s]Received unexpected message type", shortID(message))); - throw new RuntimeException("Received unexpected message type"); - } + final ChaincodeMessage message = HandlerHelper.newInvokeChaincodeMessage(txid, invocationSpec.toByteString()); + logger.debug(String.format("[%s]Sending %s", shortID(message), INVOKE_CHAINCODE)); + serialSend(message); - if (response.getType() == RESPONSE) { - // Success response - logger.debug(String.format("[%s]Received %s. Successfully invoked chaincode", shortID(response.getTxid()), RESPONSE)); - return response.getPayload(); + // wait for response chaincode message + final ChaincodeMessage outerResponseMessage = receiveChannel(responseChannel); + + if(outerResponseMessage == null) { + return ChaincodeHelper.newInternalServerErrorResponse("chaincode invoke returned null"); } - - if (response.getType() == ERROR) { + + logger.debug(String.format("[%s]Received %s.", shortID(outerResponseMessage.getTxid()), outerResponseMessage.getType())); + + switch (outerResponseMessage.getType()) { + case RESPONSE: + // response message payload should be yet another chaincode message (the actual response message) + final ChaincodeMessage responseMessage = ChaincodeMessage.parseFrom(outerResponseMessage.getPayload()); + // the actual response message must be of type COMPLETED + logger.debug(String.format("[%s]Received %s.", shortID(responseMessage.getTxid()), responseMessage.getType())); + if(responseMessage.getType() == COMPLETED) { + // success + return Response.parseFrom(responseMessage.getPayload()); + } else { + // error + return ChaincodeHelper.newInternalServerErrorResponse(responseMessage.getPayload().toByteArray()); + } + case ERROR: // Error response - logger.error(String.format("[%s]Received %s.", shortID(response.getTxid()), ERROR)); - throw new RuntimeException(response.getPayload().toStringUtf8()); + logger.error(String.format("[%s]Received %s.", shortID(outerResponseMessage.getTxid()), ERROR)); + return ChaincodeHelper.newInternalServerErrorResponse(outerResponseMessage.getPayload().toByteArray()); + default: + // Incorrect chaincode message received + logger.debug(String.format("[%s]Incorrect chaincode message %s received. Expecting %s or %s",shortID(outerResponseMessage.getTxid()), outerResponseMessage.getType(), RESPONSE, ERROR)); + return ChaincodeHelper.newInternalServerErrorResponse("Incorrect chaincode message received.", outerResponseMessage.toByteArray()); } - - // Incorrect chaincode message received - logger.debug(String.format("[%s]Incorrect chaincode message %s received. Expecting %s or %s", - shortID(response.getTxid()), response.getType(), RESPONSE, ERROR)); - throw new RuntimeException("Incorrect chaincode message received"); + } catch (InvalidProtocolBufferException e) { + return ChaincodeHelper.newInternalServerErrorResponse(e); + } catch (RuntimeException e) { + return ChaincodeHelper.newInternalServerErrorResponse(e); } finally { - deleteChannel(uuid); + deleteChannel(txid); } } diff --git a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/HandlerHelper.java b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/HandlerHelper.java index a59770e2e3b..f179fafbe09 100644 --- a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/HandlerHelper.java +++ b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/HandlerHelper.java @@ -16,6 +16,7 @@ import static org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type.COMPLETED; import static org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type.ERROR; import static org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type.GET_STATE; +import static org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type.INVOKE_CHAINCODE; import java.io.PrintWriter; import java.io.StringWriter; @@ -48,6 +49,10 @@ static ChaincodeMessage newErrorEventMessage(final String txid, final String mes static ChaincodeMessage newCompletedEventMessage(final String txid, final Response response, final ChaincodeEvent event) { return newEventMessage(COMPLETED, txid, response.toByteString(), event); } + + static ChaincodeMessage newInvokeChaincodeMessage(final String txid, final ByteString payload) { + return newEventMessage(INVOKE_CHAINCODE, txid, payload, null); + } private static ChaincodeMessage newEventMessage(final Type type, final String txid, final ByteString payload) { return newEventMessage(type, txid, payload, null); diff --git a/examples/chaincode/java/.gitignore b/examples/chaincode/java/.gitignore new file mode 100644 index 00000000000..91f3e7bd12e --- /dev/null +++ b/examples/chaincode/java/.gitignore @@ -0,0 +1 @@ +**/.classpath diff --git a/examples/chaincode/java/LinkExample/src/main/java/example/LinkExample.java b/examples/chaincode/java/LinkExample/src/main/java/example/LinkExample.java index c143509cd08..b1d27d05edc 100644 --- a/examples/chaincode/java/LinkExample/src/main/java/example/LinkExample.java +++ b/examples/chaincode/java/LinkExample/src/main/java/example/LinkExample.java @@ -1,5 +1,5 @@ /* -Copyright DTCC 2016 All Rights Reserved. +Copyright DTCC, IBM 2016, 2017 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. @@ -16,47 +16,65 @@ package example; -import com.google.protobuf.ByteString; -import org.hyperledger.fabric.shim.ChaincodeBase; -import org.hyperledger.fabric.shim.ChaincodeStub; +import static java.lang.String.format; +import static org.hyperledger.fabric.shim.ChaincodeHelper.newBadRequestResponse; +import static org.hyperledger.fabric.shim.ChaincodeHelper.newInternalServerErrorResponse; +import static org.hyperledger.fabric.shim.ChaincodeHelper.newSuccessResponse; -import java.nio.charset.StandardCharsets; -import java.util.LinkedList; import java.util.List; +import org.hyperledger.fabric.protos.common.Common.Status; +import org.hyperledger.fabric.protos.peer.ProposalResponsePackage.Response; +import org.hyperledger.fabric.shim.ChaincodeBase; +import org.hyperledger.fabric.shim.ChaincodeStub; + public class LinkExample extends ChaincodeBase { - //Default name for map chaincode in dev mode - //Can be set to a hash location via init or setMap - private String mapChaincode = "map"; + /** Default name for map chaincode in dev mode. Can be set to a hash location via init or setMap */ + private static final String DEFAULT_MAP_CHAINCODE_NAME = "map"; + + private String mapChaincodeName = DEFAULT_MAP_CHAINCODE_NAME; @Override - public String run(ChaincodeStub stub, String function, String[] args) { - switch (function) { - case "init": - case "setMap": - mapChaincode = args[0]; - break; - case "put": - stub.invokeChaincode(mapChaincode, function, toByteStringList(args), ""); - default: - break; - } - return null; + public Response init(ChaincodeStub stub) { + return invoke(stub); } - + @Override - public String query(ChaincodeStub stub, String function, String[] args) { - String tmp = stub.queryChaincode("map", function, toByteStringList(args)); - if (tmp.isEmpty()) tmp = "NULL"; - else tmp = "\"" + tmp + "\""; - tmp += " (queried from map chaincode)"; - return tmp; + public Response invoke(ChaincodeStub stub) { + try { + final List argList = stub.getArgsAsStrings(); + final String function = argList.get(0); + final List args = argList.subList(0, argList.size()); + + switch (function) { + case "init": + case "setMap": + this.mapChaincodeName = args.get(0); + return newSuccessResponse(); + case "put": + stub.invokeChaincodeWithStringArgs(this.mapChaincodeName, args); + case "query": + return doQuery(stub, args); + default: + return newBadRequestResponse(format("Unknown function: %s", function)); + } + } catch (Throwable e) { + return newInternalServerErrorResponse(e); + } + } + + private Response doQuery(ChaincodeStub stub, List args) { + final Response response = stub.invokeChaincodeWithStringArgs(this.mapChaincodeName, args); + if(response.getStatus() == Status.SUCCESS_VALUE) { + return newSuccessResponse(String.format("\"%s\" (queried from %s chaincode)", response.getPayload().toStringUtf8(), this.mapChaincodeName)); + } else { + return response; + } } public static void main(String[] args) throws Exception { new LinkExample().start(args); - //new Example().start(); } @Override @@ -64,11 +82,4 @@ public String getChaincodeID() { return "link"; } - private List toByteStringList(String[] args) { - LinkedList result = new LinkedList(); - for (int i=0; i args = stub.getArgsAsStrings(); + if(args.size() != 3) { + return newBadRequestResponse("Incorrect number of arguments. Expecting \"init\" plus 2 more."); + } + + final String key = args.get(1); + final int value = Integer.parseInt(args.get(2)); + stub.putState(key, String.valueOf(value)); + + } catch (NumberFormatException e) { + return newBadRequestResponse("Expecting integer value for sum"); + } catch (Throwable t) { + return newInternalServerErrorResponse(t); + } + + return newSuccessResponse(); + } + + @Override + public Response invoke(ChaincodeStub stub) { + // expects to be called with: { "invoke"|"query", chaincodeName, key } + try { + final List argList = stub.getArgsAsStrings(); + final String function = argList.get(0); + final String[] args = argList.stream().skip(1).toArray(String[]::new); + + switch (function) { + case "invoke": + return doInvoke(stub, args); + case "query": + return doQuery(stub, args); + default: + return newBadRequestResponse(format("Unknown function: %s", function)); + } + } catch (NumberFormatException e) { + return newBadRequestResponse(e.toString()); + } catch (AssertionError e) { + return newBadRequestResponse(e.getMessage()); + } catch (Throwable e) { + return newInternalServerErrorResponse(e); + } + } + + + private Response doQuery(ChaincodeStub stub, String[] args) { + switch (args.length) { + case 1: + case 2: + case 3: { + final String key = args[0]; + final int value = Integer.parseInt(stub.getState(key)); + return newSuccessResponse( + Json.createObjectBuilder() + .add("Name", key) + .add("Amount", value) + .build().toString().getBytes(UTF_8) + ); + } + case 4: { + final String chaincodeToCall = args[1]; + final String key = args[2]; + final String channel = args[3]; + + // invoke other chaincode + final Response response = stub.invokeChaincodeWithStringArgs(chaincodeToCall, Arrays.asList(new String[]{ "query", key}), channel); + + // check for error + if(response.getStatus() != Status.SUCCESS_VALUE) { + return response; + } + + // return payload + return newSuccessResponse(response.getPayload().toByteArray()); + } + default: + throw new AssertionError("Incorrect number of arguments. Expecting 1 or 4."); + } + } + + private Response doInvoke(ChaincodeStub stub, String[] args) { + if(args.length != 3 && args.length != 4) throw new AssertionError("Incorrect number of arguments. Expecting 3 or 4"); + + // the other chaincode's name + final String nameOfChaincodeToCall = args[0]; + + // other chaincode's channel + final String channel; + if(args.length == 4) { + channel = args[3]; + } else { + channel = null; + } + + // state key to be updated + final String key = args[1]; + + // state value to be stored upon success + final int value = Integer.parseInt(args[2]); + + // invoke other chaincode + final Response response = stub.invokeChaincodeWithStringArgs(nameOfChaincodeToCall, Arrays.asList("invoke", "a", "b", "10"), channel); + + // check for error + if(response.getStatus() != Status.SUCCESS_VALUE) { + return newInternalServerErrorResponse("Failed to query chaincode.", response.getPayload().toByteArray()); + } + + // update the ledger to indicate a successful invoke + stub.putState(key, String.valueOf(value)); + + // return the called chaincode's response + return response; + } + + @Override + public String getChaincodeID() { + return "Example04"; + } + + public static void main(String[] args) throws Exception { + new Example04().start(args); + } + +} diff --git a/examples/chaincode/java/chaincode_example05/build.gradle b/examples/chaincode/java/chaincode_example05/build.gradle new file mode 100644 index 00000000000..f932fd9286e --- /dev/null +++ b/examples/chaincode/java/chaincode_example05/build.gradle @@ -0,0 +1,83 @@ +/* +Copyright DTCC, IBM 2016, 2017 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. +*/ + + +buildscript { + repositories { + mavenLocal() + mavenCentral() + jcenter() + } +} + +plugins { + id "java" + id "eclipse" + id "application" +} + + +task printClasspath { + doLast { + configurations.testRuntime.each { println it } + } +} + +archivesBaseName = "chaincode" +mainClassName="example.Example05" + +run { + if (project.hasProperty("appArgs")) { + args = Eval.me(appArgs) + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + } +} + +repositories { + mavenLocal() + mavenCentral() +} + + +jar.doFirst { + destinationDir=file("${buildDir}") + manifest { + attributes ( + 'Main-Class': mainClassName, + 'Class-Path': configurations.runtime.collect { "libs/"+"$it.name" }.join(' ') + ) + } +} + +task copyToLib(type: Copy) { + into "$buildDir/libs" + from configurations.runtime +} +build.finalizedBy(copyToLib) + + +dependencies { + compile 'io.grpc:grpc-all:0.13.2' + compile 'commons-cli:commons-cli:1.3.1' + compile 'org.hyperledger:shim-client:1.0' +} diff --git a/examples/chaincode/java/chaincode_example05/src/main/java/example/Example05.java b/examples/chaincode/java/chaincode_example05/src/main/java/example/Example05.java new file mode 100644 index 00000000000..8ba2837f638 --- /dev/null +++ b/examples/chaincode/java/chaincode_example05/src/main/java/example/Example05.java @@ -0,0 +1,142 @@ +// Copyright IBM 2017 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 example; + +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hyperledger.fabric.shim.ChaincodeHelper.newBadRequestResponse; +import static org.hyperledger.fabric.shim.ChaincodeHelper.newInternalServerErrorResponse; +import static org.hyperledger.fabric.shim.ChaincodeHelper.newSuccessResponse; + +import java.util.Arrays; +import java.util.List; + +import org.hyperledger.fabric.protos.common.Common.Status; +import org.hyperledger.fabric.protos.peer.ProposalResponsePackage.Response; +import org.hyperledger.fabric.shim.ChaincodeBase; +import org.hyperledger.fabric.shim.ChaincodeStub; + +public class Example05 extends ChaincodeBase { + + @Override + public Response init(ChaincodeStub stub) { + // expects to be called with: { "init", key, value } + try { + + final List args = stub.getArgsAsStrings(); + if(args.size() != 3) { + return newBadRequestResponse("Incorrect number of arguments. Expecting \"init\" plus 2 more."); + } + + final String key = args.get(1); + final int value = Integer.parseInt(args.get(2)); + stub.putState(key, String.valueOf(value)); + + } catch (NumberFormatException e) { + return newBadRequestResponse("Expecting integer value for sum"); + } catch (Throwable t) { + return newInternalServerErrorResponse(t); + } + + return newSuccessResponse(); + } + + @Override + public Response invoke(ChaincodeStub stub) { + // expects to be called with: { "invoke"|"query", chaincodeName, key } + try { + final List argList = stub.getArgsAsStrings(); + final String function = argList.get(0); + final String[] args = argList.stream().skip(1).toArray(String[]::new); + + switch (function) { + case "invoke": + return doInvoke(stub, args); + case "query": + return doQuery(stub, args); + default: + return newBadRequestResponse(format("Unknown function: %s", function)); + } + } catch (NumberFormatException e) { + return newBadRequestResponse(e.toString()); + } catch (AssertionError e) { + return newBadRequestResponse(e.getMessage()); + } catch (Throwable e) { + return newInternalServerErrorResponse(e); + } + } + + + private Response doQuery(ChaincodeStub stub, String[] args) { + // query is the same as invoke, but with response payload wrapped in json + final Response result = doInvoke(stub, args); + if(result.getStatus() == Status.SUCCESS_VALUE) { + return newSuccessResponse(format("{\"Name\":\"%s\",\"Value\":%s}", args[0],result.getPayload().toStringUtf8())); + } else { + return result; + } + } + + private Response doInvoke(ChaincodeStub stub, String[] args) { + if(args.length !=2) throw new AssertionError("Incorrect number of arguments. Expecting 2"); + + // the other chaincode's id + final String chaincodeName = args[0]; + + // key containing the sum + final String key = args[1]; + + // query other chaincode for value of key "a" + final Response queryResponseA = stub.invokeChaincodeWithStringArgs(chaincodeName, Arrays.asList(new String[] {"query", "a"})); + + // check for error + if(queryResponseA.getStatus() != Status.SUCCESS_VALUE) { + return newInternalServerErrorResponse("Failed to query chaincode.", queryResponseA.getPayload().toByteArray()); + } + + // parse response + final int a = Integer.parseInt(queryResponseA.getPayload().toStringUtf8()); + + // query other chaincode for value of key "b" + final Response queryResponseB = stub.invokeChaincodeWithStringArgs(chaincodeName, "query", "b"); + + // check for error + if(queryResponseB.getStatus() != Status.SUCCESS_VALUE) { + return newInternalServerErrorResponse("Failed to query chaincode.", queryResponseB.getPayload().toByteArray()); + } + + // parse response + final int b = Integer.parseInt(queryResponseB.getPayload().toStringUtf8()); + + // calculate sum + final int sum = a + b; + + // write new sum to the ledger + stub.putState(key, String.valueOf(sum)); + + // return sum as string in payload + return newSuccessResponse(String.valueOf(sum).getBytes(UTF_8)); + } + + @Override + public String getChaincodeID() { + return "Example05"; + } + + public static void main(String[] args) throws Exception { + new Example05().start(args); + } + +}