Mo-Che Chan
Mo-Che Chan

Reputation: 51

Why the chaincode in Hyperledger Fabric 1.0 cannot be concurrently invoked?

After enrolling, installing and instantiating the chaincode fabric/example/chaincode/go/chaincode_example02, I run the following steps.

peer chaincode instantiate --orderer orderer0:7050 --tls true --path example02 --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/orderer/localMspConfig/cacerts/ordererOrg0.pem --chainID mychannel --name example02cc --version 1.0 --ctor '{"Args":["init","A","1000","B","2000"]}' 

peer chaincode query --chainID mychannel --name example02cc --ctor '{"Args":["query","A"]}'

peer chaincode query --chainID mychannel --name example02cc --ctor '{"Args":["query","B"]}'

So far, I confirm that A is equal to 1000 and B is equal to 2000. Afterwards, The result will be variable if I invoke the following step with different timings.

peer chaincode invoke --orderer orderer0:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/orderer/localMspConfig/cacerts/ordererOrg0.pem --chainID mychannel --name example02cc --ctor '{"Args":["invoke","A","B","1"]}'

Specifically, A will be equal to 998 and B will be equal to 2002 if I run the previous step twice with a 10 second pause. A will be equal to 990 and B will be equal to 2010 if I run the previous step ten times with 10 second pause between every step. However, without any pause, A will be equal to 999 and B will be equal to 2001 if I run the previous step twice. A will be equal to 999 and B will be equal to 2001 if I run the previous step ten times without pause between every step.

I have tested several times with different arguments. Furthermore, I have tested other chaincodes. It seems like that the chaincode only accept the first invoking request, and discards subsequent invoking requests. So, the questions are:

  1. Is this a mechanism to prevent double-spending? or just a weakness?
  2. How to solve this problem which limits the transaction rate.
  3. I think that chaincode should support concurrent invocations. Can chaincode support concurrent invocations actually?
  4. Can a single chaincode invoke multiple requests in a single block period?

Upvotes: 0

Views: 1707

Answers (2)

Imre Kocsis
Imre Kocsis

Reputation: 156

From the point of view of fabric this is actually normal behavior - although may seem a bit counterintuitive first.

What you see happening follows logically from the documented transaction flow (+ knowing that there is some batching performed on the orderer with batch-timeouts). Let us assume that during simulation (endorsement), variable A is read and then marked for re-setting by the chaincode simulation. What value was read and what value you want the variable set to become part of the proposed transaction (+ whether endorsers accept the transaction + crypto-stuff). Then we go through submission to orderer + distribution to channel peers + channel peers checking (among others) whether the original "read value assumptions" of the proposal still hold. (See 5.: Validation on the referenced peers.) If the value of A has changed "since simulation", then the transaction will not be valid.

The impact of timing on this is the following. If there is enough "leeway" between two invokes, the following happens:

  • Call1 - Proposal created with original A value
  • Call1 proposal gets endorsements, sent to orderer, gets batched
  • After some time, peers get the ordered-endorsed Tx from the orderer, check it, find it fine, the value of A is modified (committed to the Ledger) as requested
  • Call2 - Proposal created with new A value read etc.

The crucial thing is that what happens if you create and submit the second proposal before the effects of the first would have been committed to the Ledger. (So without "waiting enough".)

  • Call2 simulation still sees the original A value, endorsements are computed based on that
  • This is bundled into the "read values" property of the transaction
  • By the time it reaches the channel peers for committing, the value of A has been already modified by Call1
  • Thus, this transaction will be invalid and have no effect on the ledger

Such effects are not exactly new, we did get bitten by similar stuff with fabric 0.6. Fortunately, there are a few "clean" ways out of this; I suggest to read up on "tokenization" in the Blockchain context. In the context of example02, you can make all "units" have a GUID and track essentially ownership vectors; or you can go full-UTXO, Bitcoin style. Option b) can be to write the Tx requests themselves into the Ledger (Tx1:+20, Tx2: -40, Tx3: +65, ...) and base the comuptation of "current acccount state" on Ledger-stored Tx logs, although this can get a bit messy.

And do note that massive (and painless) concurrency is eminently possible, as long as the "working sets" of Tx requests do not overlap much. For many domains, this is possible; e.g. for a cryptocurrency, it is not the same currency unit that is being passed around like hot potatoes, but a large set of currency units having many transactions, but levelled out across them.

On your specific questions:

  1. see far above
  2. see the two suggestions above
  3. Depends on what you mean by concurrent invocation, but at the heart of the fabric 1.0 architecture is that a full ordering is set up on all endorsed requests and this ordering is enforced during validation and Ledger update. Massive amounts of peers requesting things concurrently - sure; "reentrant chaincode" - no-no-no.
  4. Chaincodes don't "invoke requests". If you mean whether a single chaincode can have multiple transactions in a single block: sure, e.g. selling 100 different cars using a "sell_cars" chaincode.

Upvotes: 1

zhaochy
zhaochy

Reputation: 774

The chaincode need to do endorsement and then commit it the ledger state before the next invoke.

When you call peer chaincode invoke ..., fabric response quickly after the endorsement has finished. But the commit will still take some time. If you run the second invoke directly after the first invoke, the second invoke will be endorse correctly, but no commit happened.

So, for your questions:

  1. You can try to invoke chaincode by java-sdk or node-sdk. take the javasdk for example, the progress is first send transactionProposalRequest to chaincode, which is the endorsement process in fabric. And after the endorsement is finished, and your endorment policy is correctly passed. the transaction is send to fabirc by sdk-client, this API will return an CompletableFuture<BlockEvent.TransactionEvent>, which is similar to the Promise in js. when the transaction is done, the CompletableFuture.thenApply() is triggled. You may check the src/test/java/org.hyperledger.fabric.sdkintergration/End2endIT.java for example.

  2. You may write a batch in your chaincode. This can support multiple invoke and query at once, it solves your question in some degree. The batch is designed as follow, new a m map[string]string and toDelete []string in your cc, when invoke put your key/val pairs in m, and when query get key/val from m first, if not found, query from stub, and when delete requirement, delete from m and put the key to toDelete , after all requirements are finished, commit all data in m and toDelete at once. I tried this mechanism in my project, it work good.

  3. blockchain is designed not for high concurrent. It is designed for confidentiality, scalability and security.

  4. see 2

Upvotes: 2

Related Questions