0
votes

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?

1
you may want to reformulate the title as a question. It doesn't show the problem and it will be hard to be found for other.Pablo Palacios
thx @moplin for the tipArton Berisha
the problem happens at net.corda.core.transactions.LedgerTransaction line 90. the state (in my case the bond that is the input of the transaction) has another value in state.constraint.attechmentId than the contractAttachment.attechment.id (defined at line 87). because of that the exception is thrown.Arton Berisha
i deleted the building folder and the problem is gone. looks like just rebuilding the application is a bad idea.Arton Berisha
do a gradle clean. probably the ledger got corrupted between storing the tx in the ledger and subsequent gradle deployNodes.Kid101

1 Answers

0
votes

This problem was fixed by performing a clean build, either by:

  • Using the Gradle clean flag (e.g. gradlew clean deployNodes)
  • Deleting the build folder manually