Reading data from ENV variables in a subflow - corda

I am trying to make an API call from a sub-flow which is annotated with #InitiatedBy. Is there any way to read the API URL from an ENV variable in a sub-flow, instead of hard-coding it?
For example:
#InitiatedBy(Initiator::class)
class Acceptor(val otherPartyFlow: FlowSession) : FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction {
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val txID = otherPartyFlow.receive<SecureHash>().unwrap { secureHash -> secureHash }
val commitedId = waitForLedgerCommit(txID)
val op = commitedId.tx.outputStates.single() as RequestState
val txBuilder = TransactionBuilder(notary)
try {
val res = khttp.get("http://localhost:3000/getTitle", timeout = 30.0).jsonObject.getString("data")
val iouState = IOUState(res, serviceHub.myInfo.legalIdentities.first(), otherPartyFlow.counterparty)
val txCommand = Command(RequestContract.Commands.Approve(), ourIdentity.owningKey)
val txCommand1 = Command(IOUContract.Commands.Create(), ourIdentity.owningKey)
txBuilder.addInputState(commitedId.tx.outRefsOfType<RequestState>().single())
txBuilder.addOutputState(iouState, IOU_CONTRACT_ID)
txBuilder.addOutputState(op.copy(status = "Transferred"), RequestContract.REQUEST_ID)
txBuilder.addCommand(txCommand)
txBuilder.addCommand(txCommand1)
// Verify that the transaction is valid.
txBuilder.verify(serviceHub)
// Stage 3.
val partSignedTx = serviceHub.signInitialTransaction(txBuilder)
return subFlow(FinalityFlow(partSignedTx))
}catch (ex: Exception){
logger.info(ex.message)
txBuilder.addInputState(commitedId.tx.outRefsOfType<RequestState>().single())
val txCommand1 = Command(RequestContract.Commands.Expire(), ourIdentity.owningKey)
txBuilder.addCommand(txCommand1)
txBuilder.verify(serviceHub)
// Stage 3.
val partSignedTx = serviceHub.signInitialTransaction(txBuilder)
return subFlow(FinalityFlow(partSignedTx))
}
}
}
The node on which the Acceptor flow is executed needs to retrieve some data from its external system over API. Currently it is hardcoded in the sub-flow code. Is it possible for a sub-flow to read the URL (http://localhost:3000/getTitle) from ENV variable / node.conf file?

In Corda 4+
Corda 4 will introduce the concept of CorDapp configuration files that will allow you to provide CorDapp-specific configuration.
These configuration files must be placed in the <node_dir>/cordapps/config folder. These files are loaded when a CordappContext is created and so can change during runtime.
The name of the file should match the name of the CorDapp JAR (e.g. if your CorDapp is called hello-0.1.jar the config file should be called config/hello-0.1.conf). The config files must be written in the Typesafe/Lightbend config format.
CorDapp configuration can be accessed from CordappContext::config whenever a CordappContext is available.
Corda 3 alternatives
In the meantime, in Corda 3, your best options are either to create your own local configuration files (see here), or to store the configuration in your node database (see here).

Related

Corda move tokens from account to party (account hosted in different party)

Our Corda network has 3 nodes in addition to the notary.
The image shows what each node should do.
Only in this scenario we are in trouble "Need to move tokens from account holder to Party B"
Flow code:
class TransferETokenDiffNodeFlow(val actualHolder: AbstractParty,
val newHolder: AbstractParty,
val numEtokens: Double,
val observables: MutableList = mutableListOf()) :
FlowLogic() {
private fun notary() = serviceHub.networkMapCache.notaryIdentities.first()
#Suspendable
override fun call(): SignedTransaction {
progressTracker.currentStep = INITIALIZING
val txBuilder = TransactionBuilder(notary())
val actualHolderStateRef = accountService.accountInfo(actualHolder.owningKey)
?: throw AccountNotFoundException("Account not found exception.")
val actualHolderInfo = actualHolderStateRef.state.data
val actualHolderSession = initiateFlow(actualHolderInfo.host)
actualHolderSession.send(numEtokens)
actualHolderSession.send(actualHolder)
actualHolderSession.send(newHolder)
val inputs = subFlow(ReceiveStateAndRefFlow(actualHolderSession))
val tokens: List = actualHolderSession.receive>().unwrap { it -> it}
progressTracker.currentStep = BUILDING
addMoveTokens(txBuilder, inputs, tokens)
progressTracker.currentStep = SIGNING
val initialSignedTrnx = serviceHub.signInitialTransaction(txBuilder)
progressTracker.currentStep = GATHERING_SIGS
val fulySignedTx= subFlow(CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession)))
progressTracker.currentStep = FINALISING_CREATE
val stx = subFlow(FinalityFlow(fulySignedTx, listOf(actualHolderSession)))
progressTracker.currentStep = FINALISING
val statesTx = stx.tx.outRefsOfType()
statesTx.forEach { state ->
observables.forEach { observable ->
subFlow(ShareStateAndSyncAccounts(state, observable))
}
}
return stx
}
}
//ResponderFlow code:
class TransferETokenDiffNodeFlowResponder(val counterpartySession: FlowSession) : FlowLogic() {
#Suspendable
override fun call(): SignedTransaction {
val numEtokens = counterpartySession.receive().unwrap { it }
val actualHolder = counterpartySession.receive().unwrap { it }
val newHolder = counterpartySession.receive().unwrap { it }
val partyAndAmount = PartyAndAmount(newHolder, numEtokens of EnergyTokenType.getInstance("ENERGY"))
val actualHolderStateRef = accountService.accountInfo(actualHolder.owningKey)
?: throw AccountNotFoundException("Account not found exception.")
val actualHolderInfo = actualHolderStateRef.state.data
val criteria = QueryCriteria.VaultQueryCriteria(externalIds = listOf(actualHolderInfo.identifier.id),
status = Vault.StateStatus.UNCONSUMED)
val selector = DatabaseTokenSelection(serviceHub)
val (inputs, outputs) = selector.generateMove(listOf(partyAndAmount).toPairs(),
actualHolder, TokenQueryBy(queryCriteria = criteria), runId.uuid)
subFlow(SendStateAndRefFlow(counterpartySession, inputs))
counterpartySession.send(outputs)
subFlow(object : SignTransactionFlow(counterpartySession) {
#Throws(FlowException::class)
override fun checkTransaction(stx: SignedTransaction) {
}
})
return subFlow(ReceiveFinalityFlow(counterpartySession))
}
}
We need to execute the flow at Party C and the actualHolder is the account holder and the newHolder is Party B.
With this code the returns an error:
net.corda.core.CordaRuntimeException: java.lang.IllegalArgumentException: Flow sessions were not provided for the following transaction participants: [O = Party B, L = Curitiba, C = BR]
But if I change the code and add the Party B session, it returns the error:
java.lang.IllegalArgumentException: The Initiator of CollectSignaturesFlow must pass in exactly the sessions required to sign the transaction.
The question is, why doesn't addMoveTokens put newHolder as a required signer?
And how can I solve this problem?
There are many points to discuss in your code; let's start with the error:
When you move tokens, the only required signer is the current holder; so in your case you should pass only one FlowSession which is the session with PartyA (since it's the holder of the tokens); so you should only have:
CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession))
As for finalizing the transaction, sessions for all participants must be passed. The union of all participants in your case is the current holders of the tokens (i.e. PartyA) and the new holders (i.e. PartyB); so you should have:
FinalityFlow(fulySignedTx, listOf(actualHolderSession, partyB))
That's why when you passed only actualHolderSession for both sub-flows; you received that a session is missing (because finality flow wants a session for PartyB as well); and when you added PartyB session for both, the collect signatures flow complained that you're passing an extra session (PartyB isn't required to sign the move command, only the current holder is).
Since your responder flow is handling both tasks (signing and finalizing); you must send from the initiator to the responder some data (String or whatever you choose) to signify the role of the responder, so for PartyA you would send SignerAndFinalizer; while for PartyB you send Finalizer; and in the beginning of your responder, you receive the "role" and act accordingly (i.e. if it's Finalizer you don't call SignTransactionFlow, only ReceiveFinalityFlow).
Now to other topics:
I don't recommend that a party moves tokens that are held by another party. Imagine you own money in your account, and I move that money from your account. It should be the node that holds the tokens that initiates the move. Actually that's how the ready flows of the SDK operate.
If you look at the AbstractMoveTokensFlow you will see that they only sign the transaction locally by relying on ObserverAwareFinalityFlow (see here); to confirm that, you can see that inside ObserverAwareFinalityFlow there is no CollectSignaturesFlow (i.e. it doesn't request signatures from other nodes), only signs locally (see here).
What this all means is that if you used the ready flow of the SDK to move tokens that are held by a node different than the node that's calling the move flow; you'll get an error, because the signature of the holder is required, but the ready flow doesn't collect signatures from other nodes, only from the node that called the move flow, which isn't the holder of the tokens.
I recommend following the approach of the SDK, in that only the holder of the tokens can move their tokens.
Another things mentioned by the Accounts library documentation is not to mix using accounts and non-accounts (i.e. only move from account to account or from party to party); it is advisable instead to create a "default" account for your node (e.g. create an account with a name that matches the X500 name of your node), read here.

Unable to Create Corda Transaction with self node using corda 4.0

I want to create transaction with self node using corda 4.0 .
I used sample IOU example for this and added my changes in ExampleFlow as shown in below code .
https://github.com/corda/cordapp-example/tree/release-V4/java-source.
But its not allowing to create transaction with self node.
also I followed/implemented the answers from this Corda 4 - Single Party Transaction Failed to Commit to Ledger
but it didnt work out.
I made Only changes in ExampleFlow as shown below / Rest of the code from iOU example is same.
Please help.
#Suspendable
override fun call(): SignedTransaction {
// Obtain a reference to the notary we want to use.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// Stage 1.
progressTracker.currentStep = GENERATING_TRANSACTION
// Generate an unsigned transaction.
val iouState = IOUState(iouValue, serviceHub.myInfo.legalIdentities.first(), otherParty)
val txCommand = Command(IOUContract.Commands.Create(), listOf(ourIdentity.owningKey))
val txBuilder = TransactionBuilder(notary)
.addOutputState(iouState, IOU_CONTRACT_ID)
.addCommand(txCommand)
// Stage 2.
progressTracker.currentStep = VERIFYING_TRANSACTION
// Verify that the transaction is valid.
txBuilder.verify(serviceHub)
// Stage 3.
progressTracker.currentStep = SIGNING_TRANSACTION
// Sign the transaction.
val partSignedTx = serviceHub.signInitialTransaction(txBuilder)
// Stage 5.
progressTracker.currentStep = FINALISING_TRANSACTION
// Notarise and record the transaction in both parties' vaults.
return subFlow(FinalityFlow(partSignedTx,emptyList()))
}
I believe the issue is because you are using the otherparty in the state:
val iouState = IOUState(iouValue, serviceHub.myInfo.legalIdentities.first(), otherParty)
Corda 4 requires that all participants flowsession must be provided in the FinalityFlow, so that the state information could be distributed properly.
Refer here for more
Flow Sessions we're not provided for following transaction participants - Corda 4

Corda How to Transact a Partially Signed transaction corda 4?

I am trying to do the following scenario ,
There is a basic IOU transaction which is done from Party A to Party B in which Party A who is the initiator will only sign the transaction , Party B just accepts it(without signing) . To achieve the following I did the following things which worked fine in Corda 3 using the IOU flow of the corda samples project.
1.In the transaction command , I just passed only the Initiator owning key instead of sending both the participants owning keys
I removed the "All Signers" check from the contract
I removed the "Gather counter party signature" Step.
When I moved the same to corda 4 , I noticed the following
Since I am not setting the acceptors owning key as part of the
txcommand , The transaction is saved in the initiator but not part
of the acceptor because of session issue which I understood from the
following.reference
Looking at the reference I rectified it accordingly, now Corda is expecting the signing of the acceptor also which is against my use case, if I do not add , it is throwing the following error
'net.corda.core.transactions.SignedTransaction$SignaturesMissingException:Missing
signatures on transaction '
Please let me know if any workaround is present on the same.
P.S : I am using the cordapp-example Java code
Here is the sample flow which achieves the same functionality
#InitiatingFlow
#StartableByRPC
class IssuerRegistration(
val issuerData: IssuerData
) : BaseFlow<String>() {
override val progressTracker = ProgressTracker()
#Suspendable
override fun call(): String {
// We retrieve the notary identity from the network map.
val notary = firstNotary
// We create the transaction components.
val meta_Info = Meta_Info("pdf", Utils.getCurrentDateTime(), Utils.getCurrentDateTime())
val ids = Ids(issuerData.ids.dunsId)
//create list of all parties involved in this flow
val partList = ArrayList<Party>()
partList.add(ourIdentity)
partList.add(createParty("partyb", serviceHub))
//random unique id of the state
val userUniqueId = UUID.randomUUID()
//create output state actually this will
//save to related parties vault
val outputState = IssuerState(userUniqueId.toString(),
issuerData.company_id,
issuerData.company_name,
issuerData.company_symbol,
ids,
meta_Info,
partList)
//registration command will be verified by issuercontract
val command = Command(RegistrationCommand.IssuerRegistration(),
ourIdentity.owningKey)
// We create a transaction builder and add the components.
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, RegistrationContract.ID)
.addCommand(command)
// Verifying the transaction.
txBuilder.verify(serviceHub)
// Signing the transaction. from sender side
val signedTx = serviceHub.signInitialTransaction(txBuilder)
val listOfSession = ArrayList<FlowSession>()
//creating all other party session to get
//signature from to validate the transaction
val otherPartySession = initiateFlow(otherPartySession)
listOfSession.add(otherPartySession)
//end the flow and write the transation data to related parties vault
subFlow(FinalityFlow(signedTx, listOfSession))
return userUniqueId.toString()
}
}
// Replace Responder's definition with:
#InitiatedBy(Flows.IssuerRegistration::class)
class IssueResponder(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
subFlow(ReceiveFinalityFlow(otherPartySession))
}
}
Issue was on the acceptor side , since I was using the SignTransactionFlow which expects the participants to all be signers. Once I commented the code and just ran the ReceiveFinalityFlow it worked fine. Reference : SignTransactionFlow

Question about attachment upload and download

First I have read the doc from corda about using attachment. However I still have question about the process of uploading and downloading attachment.
My task was to write a simple cordapp for transferring file from NodeA to NodeB. After uploading a zip file from NodeA shell, I received a hash and then included this in the transaction. The flow was successful. However, in NodeB, I could not get the file back. Then I tried to get the file back from NodeA using that hash. However, the shell returned error message and said invalidInputSteam.
But then when I ran cordaftp (https://github.com/corda/cordaftp) and tried to upload a file and download this from the same shell, the shell correctly asked the path for storage. I had read various posts and knew that I need to include extra codes for successfully downloading. But I have no idea which file I should amend and what code I should write. Hope someone can help me solve my problem as I already spent days on reading previous posts and doc.
Here below is the flow part:
#InitiatingFlow
#StartableByRPC
class FileInitiateFlow(
val receiver: Party,
val comment: String,
val hash: SecureHash.SHA256) : FlowLogic<SignedTransaction>() {
companion object {
object GENERATING_TRANSACTION : Step("Generating transaction")
object VERIFYING_TRANSACTION : Step("Verifying contract constraints.")
object SIGNING_TRANSACTION : Step("Signing transaction with sender private key.")
object GATHERING_SIGS : Step("Gathering the receiver's signature."){
override fun childProgressTracker() = CollectSignaturesFlow.tracker()
}
object FINALISING_TRANSACTION : Step("Obtaining notary signature and recording transaction.") {
override fun childProgressTracker() = FinalityFlow.tracker()
}
fun tracker() = ProgressTracker(
GENERATING_TRANSACTION,
VERIFYING_TRANSACTION,
SIGNING_TRANSACTION,
GATHERING_SIGS,
FINALISING_TRANSACTION
)
}
override val progressTracker = tracker()
#Suspendable
override fun call(): SignedTransaction {
// Obtain a reference to the notary we want to use.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val sender = serviceHub.myInfo.legalIdentities.first()
// Stage 1.
progressTracker.currentStep = GENERATING_TRANSACTION
// Generate an unsigned transaction.
val fileState = FileState(sender, receiver,comment)
val txCommand = Command(RoamingContract.Commands.FileInitiate(), fileState.participants.map { it.owningKey })
val txBuilder = TransactionBuilder(notary)
.addOutputState(fileState, ID)
.addCommand(txCommand)
.addAttachment(hash)
// Stage 2.
progressTracker.currentStep = VERIFYING_TRANSACTION
// Verify that the transaction is valid.
txBuilder.verify(serviceHub)
// Stage 3.
progressTracker.currentStep = SIGNING_TRANSACTION
// Sign the transaction.
val partSignedTx = serviceHub.signInitialTransaction(txBuilder)
// Stage 4.
progressTracker.currentStep = GATHERING_SIGS
// Send the state to the counterparty, and receive it back with their signature.
val otherPartyFlow = initiateFlow(receiver)
val fullySignedTx = subFlow(CollectSignaturesFlow(partSignedTx, setOf(otherPartyFlow), GATHERING_SIGS.childProgressTracker()))
// Stage 5.
progressTracker.currentStep = FINALISING_TRANSACTION
// Notarise and record the transaction in both parties' vaults.
return subFlow(FinalityFlow(fullySignedTx, FINALISING_TRANSACTION.childProgressTracker()))
}
}
#InitiatedBy(FileInitiateFlow::class)
class FileInitiateRespond(val senderFlow: FlowSession) : FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction{
val signedTransactionFlow = object : SignTransactionFlow(senderFlow) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val output = stx.tx.outputs.single().data
"This must be an File State" using (output is FileState)
}
}
return subFlow(signedTransactionFlow)
}
}
So I first run uploadAttachment for uploading the zip file, I got the hash and then start the flow, with hash as input. The flow was succeeded, but in receiver's side, I could not get the hash key for the uploaded file from checking existing state.
Attachment hashes of instances of SecureHash. There is a bug in Corda 3.2/3.3 whereby the shell cannot convert strings into SecureHash objects.
The fix is here: https://github.com/corda/corda/pull/3248 and will be available in Corda 4.

Why do I get initiator of CollectSignaturesFlow must pass in exact sessions error in Corda?

I am making a CorDapp that involves two parties - a Client and an Underwriter. I have two main flows, one being IssuePolicy and another being PayoutPolicy. When I run each flow once, there are no issues. When I run IssuePolicy again, I receive the error: The Initiator of CollectSignaturesFlow must pass in exactly the sessions required to sign the transaction. I've already searched Stack Overflow and found this post: Flow Exception in CollectSignaturesFlow but I believe I have signed with one party and initiated flow with the other, so I am not sure whether this solution applies to me. Any help would be appreciated!
IssuePolicy Flow:
// Step 3. Building.
progressTracker.currentStep = BUILDING
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val utx = TransactionBuilder(notary = notary)
.addOutputState(policy, INSUREFLIGHT_CONTRACT_ID)
.addCommand(InsureFlightContract.Commands.Issue(), policy.participants.map { it.owningKey })
.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
// Stage 4. Get some cash from the vault and add a spend to our transaction builder.
// We pay cash to the underwriter's policy key.
val (_, cashSigningKeys) = Cash.generateSpend(serviceHub, utx, premium, underwriter)
check(cashSigningKeys == cashSigningKeys){
throw FlowException("")
}
// Step 5. Sign the transaction.
progressTracker.currentStep = SIGNING
val ptx = serviceHub.signInitialTransaction(utx, policy.client.owningKey)
// Step 6. Get the counter-party signature.
progressTracker.currentStep = COLLECTING
val otherpartySession = initiateFlow(underwriter)
val stx = subFlow(CollectSignaturesFlow(
ptx,
listOf(otherpartySession),
COLLECTING.childProgressTracker())
)
// Step 7. Finalize the transaction.
progressTracker.currentStep = FINALIZING
return subFlow(FinalityFlow(stx, FINALIZING.childProgressTracker()))
}
}
// Allows counterparty to respond.
#InitiatedBy(Initiator::class)
class IssuePolicyResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val signTransactionFlow = object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
}
}
subFlow(signTransactionFlow)
}
}
}
Policy State Definition:
//Policy Class, includes premium, claim, client, underwriter, flight, and policyID
data class Policy(val premium: Amount<Currency>,
val claim: Amount<Currency>,
val client: Party,
val underwriter: Party,
val flight: String,
override val linearId: UniqueIdentifier = UniqueIdentifier()) : LinearState {
//Get clients and underwriters
override val participants: List<Party> get() = listOf(client, underwriter)
//Functions to update policy parameters
fun payPremium(amountToPay: Amount<Currency>) = copy(premium = premium + amountToPay)
fun payClaim(amountToPay: Amount<Currency>) = copy(claim = claim + amountToPay)
fun withNewClient(newClient: Party) = copy(client = newClient)
fun withNewUnderwriter(newUnderwriter: Party) = copy(underwriter = newUnderwriter)
fun withNewFlight(newFlight: String) = copy(flight = newFlight)
//Provides response
override fun toString(): String {
val clientString = (client as? Party)?.name?.organisation ?: client.owningKey.toBase58String()
val underwriterString = (underwriter as? Party)?.name?.organisation ?: underwriter.owningKey.toBase58String()
return "Policy($linearId): $clientString has paid a premium of $$premium for flight $flight, underwritten by $underwriterString with" +
"a claim amount of $$claim."
}
}
Commands in the Contract:
interface Commands : CommandData {
class Issue : TypeOnlyCommandData(), Commands
class Settle : TypeOnlyCommandData(), Commands
}
override fun verify(tx: LedgerTransaction): Unit {
val command = tx.commands.requireSingleCommand<Commands>()
val setOfSigners = command.signers.toSet()
when (command.value) {
is Commands.Issue -> verifyIssue(tx, setOfSigners)
is Commands.Settle -> verifySettle(tx, setOfSigners)
else -> throw IllegalArgumentException("Unrecognized command. You can only issue or settle.")
}
}
Not all the cash in your node's vault will be owned by your node's main public key. This is because when Cash.generateSpend generates change outputs, this change is assigned to a new confidential identity instead of your node's main identity for privacy reasons.
This is the purpose of the cashSigningKeys returned by Cash.generateSpend. It's a list of the public keys that own all the cash added to the transaction builder by Cash.generateSpend.
I'm guessing that the first time you run IssuePolicy/PayoutPolicy, it generates some cash owned by a new confidential identity. You never sign the transaction with the key of the new confidential identity. Effectively, it is this confidential identity's session that is missing when you call CollectSignaturesFlow.
Of course, it doesn't make sense to create a session with this confidential identity, since it actually corresponds to the node running the flow. Instead, you need to take the cashSigningKeys returned by Cash.generateSpend, and sign the transaction with these as well before calling CollectSignaturesFlow, by calling:
val ptx = serviceHub.signInitialTransaction(
utx,
cashSigningKeys + policy.client.owningKey)

Resources