Introducing Atomic Cross-Channel Updates in Oracle Blockchain

October 10, 2022 | 12 minute read
Minollo Innocenti
Blockchain Architect
Text Size 100%:

Overview

Enterprise use cases deployed in Oracle Blockchain are growing in complexity and blockchain topology is evolving to support them. Some applications use multiple chaincodes that work on separate but related ledgers, which are deployed on multiple channels. This is similar to how Microservices within a single application often deal with different databases. For example, when an application combines a service or exchange of goods and a payment for the service, these may be persisted in separate ledgers, but need to be committed atomically (either all of them successfully, or none of them). Similarly atomic updates are required when there’s a token exchange with tokens on separate channels (or even separate blockchains), so that neither party gets credited unless both parties do. With the complexity of transaction ordering and commit protocols, this hasn’t been possible in Hyperledger Fabric (HLF from here on) or other blockchains – different transactions hitting different channels are always committed independently from each other. 

In order to make blockchain more suitable for complex application scenarios, Oracle worked on addressing this challenge and has provided cross-channel atomic updates based on 2-Phase Commit (2PC) model in the latest version of Oracle Blockchain Platform. In short, 2PC model consists of two phases – Prepare phase where a coordinator process attempts to prepare all the transaction's participating processes to take the necessary steps for either committing or aborting the transaction and to vote, either "Yes": commit (if the transaction participant's local portion execution has ended properly), or "No": abort (if a problem has been detected with the local portion), and the Commit phase, in which, based on voting of the participants, the coordinator decides whether to commit (only if all have voted "Yes") or abort the transaction (otherwise), and notifies the result to all the participants, which then proceed to commit the updates or roll them back (in case of abort).

The approach described here implements these concepts in HLF:

  • Prepare a blockchain transaction - Stage the changes and possibly lock K/V pairs in world state until the transaction is actually committed or rolled back, then
  • Commit the changes from a previously prepared blockchain transaction - Apply the changes previously staged and release the associated locks (if any) in the world state, or
  • Rollback the changes from a previously prepared transaction - Abandon the previously staged world state changes and release the associated locks (if any).  Note that Prepare phase blockchain transactions remain in the ledger, but their write set becomes irrelevant.

This is achieved without changing the HLF client/server API, and it supports different levels of transaction "isolation" (even if the term is used liberally in this context).

While the Two-Phase Commit (2PC from here on) transactions can be manually managed by a client application, our long-standing objective is to provide commonly useful capabilities in the OBP as built-in features whenever possible, so developers don’t need to rely on complex client logic or an external Transaction Manager. This post highlights how you can rely on REST Proxy in Oracle Blockchain Platform (OBP) to invoke multiple blockchain transactions across different channels in an atomic manner and leverage the REST Proxy as the Transaction Manager.

Invoking Atomic Transactions in OBP

OBP REST Proxy exposes a new atomicTransactions endpoint which accepts multiple HLF transaction requests – typically spanning different channels, and it returns details about the success or failure of the atomic operation. As an example, suppose your blockchain network enables users to exchange marbles (reusing a popular HLF example) on channel goods, and tokens (leveraging another simple HLF example – balance transfer) on channel wallet. Now suppose that Garcia wants to sell Smith his marble1 in exchange for 50 tokens. That operation involves two distinct blockchain transactions hitting two different ledgers. HLF doesn’t provide any mechanism to force those two transactions to happen atomically, creating the risk that Garcia might not get his tokens after transferring ownership of marble1 to Smith; or that – vice versa – Smith might not get the marble even after transferring tokens to Garcia. In blockchain world, such an exchange across multiple chains, e.g., Bitcoin and Ethereum is often handled by a Hash Time-Locked Contract (HTLC) implementation that uses escrow accounts on each network and requires both users to orchestrate a number of steps.

Atomic transactions across channels are meant to solve exactly this kind of problems, but without the cost and complexity of HTLCs. Instead of thinking about the two blockchain transactions as two distinct operations, we think about them as a single atomic transaction, which consists of two operations. The API allows multiple operations to be specified in an array form, each involving separate chaincode and channel as shown below:

{
 "transactions": [
      {"chaincode":"obcs-marbles","args":["transferMarble", "marble1", "smith"],"timeout":0, "channel":"goods"},
   
{"chaincode":"obcs-example02","args":["pay", "smith", "garcia", "50"],"timeout":0, "channel":"wallet"}
  ],
  

}

That’s the payload that an application can send to the atomicTransactions endpoint in OBP REST Proxy. You can easily recognize the two specific operations that the application is requesting to perform – and which must happen atomically.

If all goes well, and marble1 gets transferred to Smith, as well as 50 tokens get transferred from Smith to Garcia, the requested operation returns a response of this kind:

{
  "returnCode": "Success",
  "result": {
    "transactions": [
      {
        "channel": "goods",
        "chaincode": "obcs-marbles",
        "txstatus": "Committed",
        "prepare": {
          "txid": "fe262233"
        },
        "commit": {
          "txid": "badcb3b4"
        },
        "rollback": {}
      },
      {
        "channel": "wallet",
        "chaincode": "obcs-example02",
        "txstatus": "Committed",
        "prepare": {
          "txid": "9caf809c"
        },
        "commit": {
          "txid": "1594d0ab"
        },
        "rollback": {}
      }
    ],
    "globalStatus": "Success",
    "globalTxid": "259910a26006",

The response is telling us quite a few things:

  • The atomic transaction request was successful and was assigned an ID (globalTxid: 259910a26006)
  • Each requested blockchain transaction was split into two distinct operations:
    • Prepare (fe262233 and 9caf809c)
    • Commit (badcb3b4 and 1594d0ab)
  • Both operations succeeded and the atomic transaction request has been successfully completed

What happened under the cover is that OBP REST Proxy coordinated the execution of the transaction by:

  • Endorsing and committing the prepare transactions against the two channels; such process is equivalent to a “normal” transaction execution, with the exception that an additional chaincode argument lets OBP know that such execution is meant to stage any change applied by the chaincode, and not to finalize it yet; and that locks must be created to prevent other concurrent transaction from modifying values which are being staged. Once the prepare transactions are committed to ledger and StateDB in HLF, no “dirty” data will be exposed to other transactions happening in parallel.
  • As the prepare phase was successful, the process continued by endorsing and committing the commit transactions against the two channels. Such operation does not execute any business logic exposed by chaincodes or require developers to implement any changes in the chaincode; instead, it un-stages previously staged K/V pair changes and it remove locks that may have been created as part of the prepare transaction.

Now, let’s suppose that the same flow is executed in a context where Smith doesn’t have enough tokens to pay for Garcia’s marble. Here’s the response we will see:

{ 
"returnCode": "Failure",

  "result": {
    "transactions": [
      {
        "channel": "goods",
        "chaincode": "obcs-marbles",
        "txstatus": "Rolledback",
        "prepare": {
          "txid": "fe262233"
        },
        "commit": {},
        "rollback": {
          "txid": "badcb3b4"
        },
      },
      {
        "channel": "wallet",
        "chaincode": "obcs-example02",
        "txstatus": "FailedPrepare",
        "prepare": {
          "error": "failed to invoke chaincode. 'smith' doesn’t have enough tokens to pay 'garcia'"
        },
        "commit": {},
        "rollback": {
          "error":"skipping rollback since prepare stage has failed or was skipped"
        }
      }
    ],
    "globalStatus": "Failure",
    "globalTxid": "259910a26006",
    …

This return payload tells us that:

  • The prepare phase of the requested marble transfer operation was successful (fe262233)
  • The prepare phase of the requested token transfer failed because Smith doesn’t have enough tokens to complete it
  • As a result, the prepared marble transfer operation was rolled back (badcb3b4)
  • The overall atomic transaction is considered failed

 It’s worth noticing that no compensation logic needs to be applied to the rollback operation, as the overall design of the solution simply allows us to remove the staged changes and the related locks.

The following table shows a (heavily simplified) description of the sequence of transactions corresponding to the successful/failed invocations described above:

Sequence of transactions corresponding to the successful and failed invocations

Channel

Chaincode

Operation

Arguments

TxID

Comment

Successful case
  Goods   obcs-marbles   transferMarble   marble1, smith,
  2pc.serializable,
  2pc. 259910a26006
  fe262233   prepare
Wallet obcs-example02 pay 50
2pc.serializable,
2pc. 259910a26006
9caf809c prepare
Goods obcs-marbles 2pc.commit 2pc. 259910a26006,
2pc.prep. fe262233
badcb3b4 commit
Wallet obcs-example02 2pc.commit 2pc. 259910a26006,
2pc.prep. 9caf809c
1594d0ab commit
Failed Case
Goods obcs-marbles transferMarble marble1, smith,
2pc.serializable,
2pc. 259910a26006
fe262233 prepare
Wallet obcs-example02 pay 50
2pc.serializable,
2pc. 259910a26006
prepare
failed
Goods obcs-marbles 2pc.rollback 2pc. 259910a26006,
2pc.prep. fe262233
badcb3b4 rollback

Peer chaincode handler and commit pipeline: the magic sauce

How does all this work under the cover – at the peer level?  Each phase of the 2PC Prepare/Commit protocol for each sub-transaction is handled as a normal HLF transaction with its own endorsement and commit phase:

Oracle Blockchain Peer Node Handling of 2PC Transactions

2PC Phase

HLF Phase

Peer actions for each channel/chaincode

Prepare Endorsement
  • When a chaincode operation is requested in the context of an atomic transaction, two specific arguments specifying that the transaction is a prepare phase of a 2PC overall transaction are added by the REST proxy: the desired isolation level and the global transaction ID.
  • The peer(s) selected to endorse the transaction scan the arguments and realize that they are dealing with a prepare transaction.
  • Any putState(K, V) operation performed by the chaincode is added to the transaction read-write set not as K/V (as happens in a normal transaction), but as staged_K/V.
  • A K/V pair representing the overall state of the distributed transaction that includes this prepare transaction is added to the read/write set.
  • Depending on the specified transaction isolation level (serializable or readCommitted), the peer adds locks for all K/V pairs involved in write and possibly read operations. Locks are created as additional key/value pairs added to the transaction write set.
Commit
  • Existing locks are checked in StateDB to see if any key in the transactions read/write set is affected by them. If they are, the transaction is flagged as invalid.
  • The staged K/V pairs are created in StateDB.
  • The locks K/V pairs are created in StateDB.
  • The state K/V pair describing the state of the global transaction to which this prepare transaction is associated is created in StateDB. 
Commit Endorsement
  • A transaction is recognized as a commit 2PC transaction when a specific argument is included in its invocation together with the associated global transaction ID.
  • While the transaction is executed against the same channel/chaincode as its prepared equivalent, a commit 2PC transaction doesn’t invoke any chaincode function. Instead, its read/write set is created by the peer itself by:
    • Converting the associated staged_K/V pairs into K/V ones
    • Deleting the K/V pairs identifying related locks
    • Updating the K/V pair for the status of the overall global transaction
Commit
  • This is quite simple, as the read/write set created at endorsement time is flushed to StateDB.
Rollback Endorsement
  • A transaction is recognized as a rollback  2PC transaction when a specific argument is included in its invocation, in addition to information about the associated global transaction ID.
  • While the transaction is executed against the same channel/chaincode as its prepared equivalent, a rollback 2PC transaction doesn’t invoke any chaincode function. Instead, its read/write set is created by the peer itself by:
    • Deleting the associated staged_K/V pairs
    • Deleting the K/V pairs identifying related locks
    • Updating the K/V pair for the status of the overall global transaction
  Commit
  • This is quite simple, as the read/write set created at endorsement time is flushed to StateDB 


 

 
In summary, these enhancements at the HLF peer level together with the associated REST proxy orchestration capabilities allow us to provide atomic update capabilities across multiple channel/chaincode operations without impacting the chaincode business logic or burdening the client application with additional complexity.

What about atomic updates across blockchain and database or multiple blockchains?

There are valid scenarios where this requirement comes up – for example, when using off-chain storage for large records or documents in concert with blockchain record to anchor the related metadata in a tamper-proof distributed ledger. Or the same example of token exchange mentioned earlier, but across Oracle blockchain and Ethereum.

The same 2PC mechanism in OBP can be leveraged but the orchestration has to move outside of the OBP REST proxy to an external Transaction Manager. There’s a well-known and widely used XA distributed transaction protocol, which uses Transaction Managers in concert with XA Resource Managers (RMs) for each data store. By implementing XA Resource Manager for OBP on top of 2PC primitives, we can bring the advantage of an XA protocol – using standard Transaction Managers (like WebLogic, JBoss, WebSphere, or Tuxedo) – to coordinate distributed transactions across OBP and a variety of other data sources which support the XA protocol.

Thanks to the 2PC protocol support described above, the latest release of OBP also provides an OBP XA Resource Manager as a Java library, which allows developers to include OBP transactions coordinated by external Transaction Managers (like WebLogic) in conjunction with transactions for other XA-compatible data sources (Oracle DB, MySQL, …) and ensure they are atomically committed (or rolled back) as a group. More to come about this in a future post, including how to include transactions for other blockchain in atomic updates.

Conclusion

Being able to execute multiple transactions across different blockchain channels in an atomic manner is crucial to many use cases. No blockchain implementations other than Oracle Blockchain Platform supports such use cases without the need to change existing chaincodes and/or to introduce different concepts like Hashed TimeLock Contracts (HTLC).

The underlying 2PC protocol supported by OBP allows not only to execute atomic transactions across different channels of the same blockchain network, but also across different networks and even across multiple resources (OBP and Oracle DB – for example) when exposed as an XA implementation, which can be coordinated by external transaction managers.

To try this yourself, attend the Low-Code Blockchain NFT Apps Using Blockchain App Builder and APEX [HOL4082] Hands-on Lab at Oracle CloudWorld 2022 (Las Vegas, Oct. 18-20).  Afterwards, this will also be available as a self-service Oracle LiveLab at https://apexapps.oracle.com/pls/apex/dbpm/r/livelabs/home.

 

Minollo Innocenti

Blockchain Architect


Previous Post

Get the latest updates on Enterprise Blockchain at Oracle CloudWorld 2022

Kiran Makarla | 3 min read

Next Post


Latest OCI Blockchain Platform update enables blockchain interoperability and brings Web3 capabilities to OCI

Mark Rakhmilevich | 13 min read