The accounts library enables a Corda node's vault partition into several subsets. The vault refers to a set of state objects. Each subset is a representation of an account. In different words, the account's library enables the operator of a Corda node to segment the vault into several "logical" sub-vaults, which benefits in the following ways.
First, add a variable for the accounts release group and the version you wish to use, and add the Corda version that should've installed locally.
buildscript { ext { corda_release_version = '4.3-RC01' corda_gradle_plugins_version = '4.0.42' accounts_release_version = '1.0-RC03' quasar_version = '0.7.10' kotlin_version = '1.2.71' testng_version = '6.14.3' } repositories { mavenCentral() jcenter() maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version" classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" } }
Second, you must add the accounts artifactory repository, the list of repositories for your project :
allprojects { repositories { jcenter() mavenCentral() maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda' } //Corda testing node-driver requires gradle-tooling-api maven { url 'https://repo.gradle.org/gradle/libs-releases-local/' } //This is needed until the accounts sdk is official maven { url "http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-dev" } maven { url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib' } }
You can add the Corda and accounts dependencies to the dependencies block in each module of your CorDapp.
dependencies { cordaCompile "net.corda:corda-node-api:$corda_release_version" cordapp "com.r3.corda.lib.accounts:accounts-contracts:$accounts_release_version" cordapp "com.r3.corda.lib.accounts:accounts-workflows:$accounts_release_version" }
These should also be added to the deploy nodes task with the following syntax:
nodeDefaults { projectCordapp { deploy = false } cordapp("com.r3.corda.lib.accounts:accounts-contracts:$accounts_release_version") cordapp("com.r3.corda.lib.accounts:accounts-workflows:$accounts_release_version") rpcUsers = [[user: "user1", "password": "test", "permissions": ["ALL"]]] }
How to share IOUvalue from one node to another is described in Corda's official website IOU example.
In this blog, we will explain how to share value from one account to another on the same node.
The IOUState
The shared facts on the blockchain are represented as states. Our task will define a new state " TransactionState" to represent an IOU.
Corda state is an instance of a class that implements the ContractState interface.
In Corda, the ContractState interface has a single field, participants. It represents a list of the entities for which this state is relevant.
@BelongsToContract(TransactionContracts::class) class TransactionState( val IOUvalue: Int, val lender: PublicKey, val borrower: PublicKey ) : ContractState { override val participants: List<AbstractParty> get() = listOf(lender, borrower).map { AnonymousParty(it)} }
The IOUContract
In Corda, every contract must implement the Contract interface.
In Corda, the contract has a single method, verify, which takes a Transaction as input.
class TransactionContracts : Contract { companion object { @JvmStatic val Trans_CONTRACT_ID = TransactionContracts::class.java.name } interface Commands : CommandData{ class Create : TypeOnlyCommandData(),Commands } override fun verify(tx: LedgerTransaction) { } }
The IOU Flow
In Corda, recording a transaction or sending a transaction to a counterparty is very common. Corda developer, instead of forcing to reimplement their logic to handle these tasks, use Corda provided several library's flows to handle these tasks. Using these flows, we invoke in the context of a larger flow to handle a repeatable task sub-flows.
Corda Flow is instances of classes, made of FlowLogic subclass.
@InitiatingFlow means that this flow is part of a flow pair and that it triggers the other side to run the counterpart flow (which in our case is the TransactionResponderFlow defined below).
In Corda @StartableByRPC , RPC interface [startFlowDynamic] Used to initiate any flow [FlowLogic] and [startTrackedFlowDynamic]) must have this annotation. If it's missing, the flow will not be allowed to start and an exception will be thrown.
@InitiatingFlow @StartableByRPC class TransactionFlow (val IouValue : Int, val lenderId : UUID, val borrowerId: UUID ) : FlowLogic<SignedTransaction>() { override fun call() :SignedTransaction{ val notary = serviceHub.networkMapCache.notaryIdentities.first(); val lenderAccountInfo = accountService.accountInfo(lenderId) ?: throw IllegalStateException("Can't find account to move from $lenderId") val borrowerAccountInfo = accountService.accountInfo(borrowerId) ?: throw IllegalStateException("Can't find account to move from $borrowerId") val lenderKey = subFlow(RequestKeyForAccount(lenderAccountInfo.state.data)).owningKey val browerKey = subFlow(RequestKeyForAccount(borrowerAccountInfo.state.data)).owningKey val transactionState = TransactionState(IouValue,lenderKey,browerKey) val commond = Command(TransactionContracts.Commands.Create(),transactionState.participants.map {it.owningKey}) val txbuilder = TransactionBuilder(notary). addOutputState(transactionState,TransactionContracts.Trans_CONTRACT_ID).addCommand(commond) txbuilder.verify(serviceHub) var keysToSignWith = mutableListOf(ourIdentity.owningKey, lenderKey) //Only add the borrower account if it is hosted on this node (potentially it might be on a different node) if (borrowerAccountInfo.state.data.host == serviceHub.myInfo.legalIdentitiesAndCerts.first().party) { keysToSignWith.add(browerKey) } val locallySignedTx = serviceHub.signInitialTransaction(txbuilder,keysToSignWith) //We have to do 2 different flows depending on whether the other account is on our node or a different node if (borrowerAccountInfo.state.data.host == serviceHub.myInfo.legalIdentitiesAndCerts.first().party) { //Notarise and record the transaction in just our vault. return subFlow(FinalityFlow(locallySignedTx, emptyList())) } else{ val borrowerSession = initiateFlow(borrowerAccountInfo.state.data.host) val borrowerSignature = subFlow(CollectSignatureFlow(locallySignedTx, borrowerSession, browerKey)) val fullySignedTx = locallySignedTx.withAdditionalSignatures(borrowerSignature) return subFlow(FinalityFlow(fullySignedTx, listOf(borrowerSession))) } } }
The IOU Responder Flow
TransactionResponderFlow, in this flow, the borrower has to use ReceiveFinalityFlow to receive and record transactions. It responds to the flow of the lender.
@InitiatedBy(TransactionFlow::class) class TransactionResponderFlow(val counterPartySession : FlowSession) : FlowLogic<SignedTransaction>() { override fun call(): SignedTransaction { val signTransactionFlow = object : SignTransactionFlow(counterPartySession) { override fun checkTransaction(stx: SignedTransaction) = requireThat { val output = stx.tx.outputs.single().data "This must be an lender and borrower transaction." using (output is TransactionState<*>) } } val txId = subFlow(signTransactionFlow).id return subFlow(ReceiveFinalityFlow(counterPartySession, expectedTxId = txId)) } }
In this post, we implemented the process of an IOU using the account library while inspecting the components that are required to do so. In the Cordapp, States are facts that are shared among parties on the network, contracts are used to validate transactions and flows contain the logic to propose new transactions.
At Oodles, along with offering Corda blockchain development services, we keep planning to write more on the development aspects of Corda and its complex features.