i'm having some trouble to implement a simple use case in corda. i want to implement a free of payment transfer. the lifecycle is maintained in corda.
At first i create a bond state that will be saved only on nodeA that works without problem). After that i want to create a new state that represent an FOP proposal, that the receiver(nodeB) can accept. The idea is that the bond will be consumed when the fop proposal is created. after the receiver accepts the proposal(another flow that needs to be implemented) a new bond state will be created an the the receiver is the owner.
The Flow to create the FOP proposal (a state that should be saved on nodeA and nodeB) is this:
object IssueFopFlow {
@InitiatingFlow
@StartableByRPC
class Initiator(private val sender: AbstractParty,
private val receiver: AbstractParty,
private val amount: Int,
private val bondLinearId: UniqueIdentifier) : MyBaseFlow() {
companion object {
object INITIALISING : ProgressTracker.Step("Performing initial steps.")
object BUILDING : ProgressTracker.Step("Building and verifying transaction.")
object SIGNING : ProgressTracker.Step("Signing transaction.")
object COLLECTING : ProgressTracker.Step("Collecting counterparty signature.") {
override fun childProgressTracker() = CollectSignaturesFlow.tracker()
}
object FINALISING : ProgressTracker.Step("Finalising transaction.") {
override fun childProgressTracker() = FinalityFlow.tracker()
}
fun tracker() = ProgressTracker(INITIALISING, BUILDING, SIGNING, COLLECTING, FINALISING)
}
override val progressTracker: ProgressTracker = tracker()
@Suspendable
override fun call(): SignedTransaction {
// Step 1. Initialisation.
progressTracker.currentStep = INITIALISING
val bondForFop = getBondByLinearId(bondLinearId)
val inputBond = bondForFop.state.data
// check that the bond owner is calling this flow
check(inputBond.owner == ourIdentity) {
throw FlowException("Issue fop transfer must be initiated by the bond owner.")
}
// check the amount
check(inputBond.amount >= amount) {
throw FlowException("Not enough of the bond ${inputBond.isin}. Quantity: ${inputBond.amount}. Amount to transfer: ${amount}")
}
// create fop transfer state
val fopTransfer = FopState(sender, receiver, amount, inputBond.isin)
val ourSigningKey = fopTransfer.sender.owningKey
// Step 2. Building.
progressTracker.currentStep = BUILDING
val utx = TransactionBuilder(firstNotary)
.addInputState(bondForFop)
.addOutputState(fopTransfer, FOP_CONTRACT_ID)
.addCommand(FopContract.Commands.Issue(), listOf(sender.owningKey, receiver.owningKey))
.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
// create new bond state
if (amount < inputBond.amount) {
val (command, newState) = inputBond.reduce(amount)
utx
.addOutputState(newState, BOND_CONTRACT_ID)
.addCommand(command, newState.owner.owningKey)
}
utx.verify(serviceHub)
// Step 3. Sign the transaction.
progressTracker.currentStep = SIGNING
val ptx = serviceHub.signInitialTransaction(utx, ourSigningKey)
// Step 4. Get the counter-party signature.
progressTracker.currentStep = COLLECTING
val senderFlow = initiateFlow(ourIdentity)
val stx = subFlow(
CollectSignaturesFlow(
ptx,
setOf(senderFlow),
listOf(sender.owningKey, receiver.owningKey),
COLLECTING.childProgressTracker()
)
)
// Step 5. Finalise the transaction.
progressTracker.currentStep = FINALISING
return subFlow(FinalityFlow(stx, FINALISING.childProgressTracker()))
}
}
@InitiatedBy(Initiator::class)
class Responder(private val otherFlow: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
subFlow(IdentitySyncFlow.Receive(otherFlow))
val stx = subFlow(SignTxFlowNoChecking(otherFlow))
return waitForLedgerCommit(stx.id)
}
}
}
My current problem is, that i get an exception:
net.corda.core.contracts.TransactionVerificationException$ContractConstraintRejection: Contract constraints failed for com.models.BondContract, transaction: 44A52F07B9579C5106321361A6154C1EE5EF5670FA94CEFF24AB487F0B20D733
at net.corda.core.transactions.LedgerTransaction.verifyConstraints(LedgerTransaction.kt:91) ~[corda-core-2.0.0.jar:?]
at net.corda.core.transactions.LedgerTransaction.verify(LedgerTransaction.kt:67) ~[corda-core-2.0.0.jar:?]
at net.corda.core.transactions.TransactionBuilder.verify(TransactionBuilder.kt:113) ~[corda-core-2.0.0.jar:?]
at com.flow.IssueFopFlow$Initiator.call(IssueFopFlow.kt:78) ~[cordapp-0.1.jar:?]
at com.flow.IssueFopFlow$Initiator.call(IssueFopFlow.kt:19) ~[cordapp-0.1.jar:?]
at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:96) [corda-node-2.0.0.jar:?]
at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:41) [corda-node-2.0.0.jar:?]
at co.paralleluniverse.fibers.Fiber.run1(Fiber.java:1092) [quasar-core-0.7.9-jdk8.jar:0.7.9]
at co.paralleluniverse.fibers.Fiber.exec(Fiber.java:788) [quasar-core-0.7.9-jdk8.jar:0.7.9]
at co.paralleluniverse.fibers.RunnableFiberTask.doExec(RunnableFiberTask.java:100) [quasar-core-0.7.9-jdk8.jar:0.7.9]
at co.paralleluniverse.fibers.RunnableFiberTask.run(RunnableFiberTask.java:91) [quasar-core-0.7.9-jdk8.jar:0.7.9]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_162]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_162]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [?:1.8.0_162]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [?:1.8.0_162]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_162]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_162]
I don't get why. the verify implementation of the BondContract should be fine(only from my point of view :D). Here is also the code of the bond contract.
open class BondContract : Contract {
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<Commands>()
val setOfSigners = command.signers.toSet()
when (command.value) {
is Commands.Issue -> verifyIssue(tx, setOfSigners)
is Commands.Reduce -> verifyReduce(tx, setOfSigners)
else -> throw IllegalArgumentException("Unrecognised command.")
}
}
private fun keysFromParticipants(bond: BondState): Set<PublicKey> {
return bond.participants.map {
it.owningKey
}.toSet()
}
private fun verifyIssue(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
"No inputs should be consumed when issuing an obligation." using (tx.inputStates.isEmpty())
"Only one bond state should be created when issuing an obligation." using (tx.outputStates.size == 1)
val bond = tx.outputsOfType<BondState>().single()
"A newly issued bond must have a positive amount." using (bond.amount > 0)
"Issuer may sign bond issue transaction." using
(signers == keysFromParticipants(bond))
}
private fun verifyReduce(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
val bond = tx.outputsOfType<BondState>().single()
"A bond must have a positive amount." using (bond.amount > 0)
}
interface Commands : CommandData {
class Move : TypeOnlyCommandData(), Commands
class Issue : TypeOnlyCommandData(), Commands
class Reduce : TypeOnlyCommandData(), Commands
}
}
// *********
// * State *
// *********
data class BondState(val isin: String,
val amount: Int,
override val owner: AbstractParty,
override val linearId: UniqueIdentifier = UniqueIdentifier()) : LinearState, QueryableState, OwnableState {
override val participants: List<AbstractParty> get() = listOf(owner)
override fun generateMappedObject(schema: MappedSchema): PersistentState {
return when (schema) {
is BondSchemaV1 -> BondSchemaV1.PersistentBond(
this.owner.toString(),
this.isin,
this.amount,
this.linearId.id
)
else -> throw IllegalArgumentException("Unrecognised schema $schema")
}
}
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(BondSchemaV1)
override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(BondContract.Commands.Move(), copy(owner = newOwner))
fun reduce(amountToReduce: Int) = CommandAndState(BondContract.Commands.Reduce(), copy(amount = amount - amountToReduce))
}
Can someone help me?