Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated states and contracts. #2

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.survey

import com.sun.crypto.provider.AESKeyGenerator
import com.google.common.hash.HashCode
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.transactions.LedgerTransaction
import net.corda.finance.contracts.asset.Cash

import net.corda.finance.utils.sumCash


// *****************
Expand All @@ -27,52 +27,85 @@ class SurveyContract : Contract {
val command = tx.commands.requireSingleCommand<Commands>()
when(command.value){
is Commands.IssueRequest -> {
requireThat {
// Input and output states.
"There should be one input state, cash." using (tx.inputStates.size == 1)
"There should be two output states, cash and survey request." using (tx.outputStates.size == 2)

val inputCash = tx.inputsOfType<Cash.State>().single()
val outputCash = tx.outputsOfType<Cash.State>().single()
val outputSurveyRequest = tx.outputsOfType<SurveyRequestState>().single()
"The survey price must positive." using (outputSurveyRequest.surveyPrice > 0)
"The survey price must be equal to the cash." using (inputCash.amount.quantity.toInt() == outputSurveyRequest.surveyPrice)
"The input cash must be equal to the output cash." using (inputCash.amount.quantity == outputCash.amount.quantity)
"The output survey status is pending." using (outputSurveyRequest.status == "Pending")

// Owners and signers.
"The input cash owner is the requester." using (inputCash.owner == outputSurveyRequest.requester)
"The output cash owner is the surveyor." using (outputCash.owner == outputSurveyRequest.surveyor)
"The surveyor and requester must be signers." using (command.signers.containsAll(outputSurveyRequest.participants.map { it.owningKey }))
"The input cash owner must be signer." using (command.signers.containsAll(inputCash.participants.map { it.owningKey }))
}
}
is Commands.Issue -> {
val outputSurvey = tx.outputsOfType<SurveyState>().single()
requireThat {
"Only one output state should be created." using (tx.outputs.size == 1)
"No inputs should be consumed when issuing a survey." using (tx.inputs.isEmpty())
val out = tx.outputsOfType<SurveyState>().single()
"The issuer and the owner must be the same entity." using (out.issuer == out.owner)
"Issuer must be the signer." using (command.signers.contains(out.issuer.owningKey))
// Survey-specific constraints.
"The survey's value must be non-negative." using (out.initialPrice > 0)
"The initial price is equal to the resale price" using (out.initialPrice != out.resalePrice)
// Input and output states.
"One input state should be consumed, the survey request." using (tx.inputStates.size == 1)
"Three output states should be created, the survey, the survey key, and the survey request." using (tx.outputs.size == 3)

val inputSurveyRequest = tx.inputsOfType<SurveyRequestState>().single()
val outputSurveyKey = tx.outputsOfType<SurveyKeyState>().singleOrNull()
val outputSurveyRequest = tx.outputsOfType<SurveyRequestState>().single()
// Sample constraints.
"The survey's initial price must be positive." using (outputSurvey.initialPrice > 0)
"The initial price must be equal to the resale price." using (outputSurvey.initialPrice == outputSurvey.resalePrice)
"The output survey request status is complete." using (outputSurveyRequest.status == "Complete")
"The key is included." using (outputSurveyKey != null)

// Owners and signers.
"The purchase requester is the final survey owner." using (inputSurveyRequest.requester == outputSurvey.owner)
"Issuer must be the signer." using (command.signers.contains(outputSurvey.issuer.owningKey))
"Purchaser must be the signer." using (command.signers.contains(outputSurvey.owner.owningKey))
}
try {
tx.getAttachment(outputSurvey.surveyHash)
} catch(e: Exception) {
throw kotlin.IllegalArgumentException("Attachment missing or does not match hash.")
}
}
is Commands.Trade -> {
requireThat {
"There should be two input states" using (tx.inputStates.size == 2)
"There should be two output states" using (tx.outputStates.size == 2)
// Input and output states.
val inputCash = tx.inputsOfType<Cash.State>().toList()
val inputSurvey = tx.inputsOfType<SurveyState>().single()
val outputSurvey = tx.outputsOfType<SurveyState>().single()
val inputCash = tx.inputsOfType<Cash.State>().single()
val outputCash = tx.outputsOfType<Cash.State>().single()
"There should be one input survey state" using (tx.inputsOfType<SurveyState>().size == 1)
"There should be one output survey state" using (tx.outputsOfType<SurveyState>().size == 1)
"There should be at least one input cash state" using (tx.inputsOfType<Cash.State>().size > 1)
"There should be at least one output cash state" using (tx.outputsOfType<Cash.State>().size > 1)
"The initial and final hashes must be the same." using (inputSurvey.surveyHash == outputSurvey.surveyHash)
"Input cash should be at least equal to the resale price" using (inputCash.sumCash().quantity.toInt() >= outputSurvey.resalePrice)
"Output cash to the owner should be equal to resale price" using (outputCash.ownedBy(outputSurvey.owner).amount.quantity.toDouble() == (inputSurvey.resalePrice * 0.8))
"Output cash to issuer is equal to 20% of the resale price" using (outputCash.ownedBy(outputSurvey.issuer).amount.quantity.toDouble() == (inputSurvey.resalePrice * 0.2))

"Resale price should be positive" using (outputSurvey.resalePrice > 0)
"Resale price should be less than initial price" using (inputSurvey.resalePrice > inputSurvey.initialPrice)

"Input cash should be more than the purchasing price" using (inputCash.amount.quantity > inputSurvey.initialPrice)
"Output cash should be equal to resell price" using (outputCash.amount.quantity.toInt() == inputSurvey.resalePrice)

"The person who owns the cash initially, now owns the survey." using (inputCash.owner == outputSurvey.owner)
"The person who owns survey initially, now owns the cash." using (inputSurvey.owner == outputCash.owner)
"Cannot sell survey to yourself" using (inputSurvey.owner == outputSurvey.owner)

// Owners and signers.
"The owner of the input cash, now owns the survey." using (inputCash.first().owner == outputSurvey.owner)
"The owner of the survey initially, now owns the cash." using (inputSurvey.owner == outputCash.owner)
"Cannot sell survey to yourself." using (inputSurvey.owner != outputSurvey.owner)
"All of the survey participants must be signers." using (command.signers.containsAll(inputSurvey.participants.map { it.owningKey }))
"The cash owner must be signer." using (command.signers.containsAll(inputCash.participants.map { it.owningKey }))
"The cash owner must be signer." using (command.signers.contains(inputCash.single().owner.owningKey))
}
}
}
}

// Used to indicate the transaction's intent.
interface Commands : CommandData {
class IssueRequest : Commands
class Issue : Commands
class Trade : Commands
class IssueRequest : Commands
class OracleCommand(val linearId: UniqueIdentifier) : Commands
}
}

Expand All @@ -81,22 +114,21 @@ class SurveyContract : Contract {
// *********
data class SurveyState(val issuer: Party,
val owner: Party,
val propertyAddress: String,
val landTitleId: String,
val surveyDate: String,
val initialPrice: Int,
val resalePrice : Int,
val resalePrice: Int,
val surveyHash: Int,
override val linearId: UniqueIdentifier) : LinearState {
override val participants: List<AbstractParty> get() = listOf(issuer, owner)
}

// *********
// * SurveyIssuanceState
// * SurveyKeyState
// * The facts shared by the surveyor to
// * the buyer once the survey is complete
// *********
data class SurveyKeyState(val encodedSurveyHash: String,
val encodedSurveyKey : String,

data class SurveyKeyState(val encodedSurveyKey: String,
override val linearId: UniqueIdentifier) : LinearState {
override val participants: List<AbstractParty> get() = listOf()
}
Expand All @@ -106,12 +138,11 @@ data class SurveyKeyState(val encodedSurveyHash: String,
// * For potential to make a request for a survey
// * at a particular address for price.
// *********
data class SurveyRequestState(val requester :Party,
val surveyor : Party,
val propertyAddress: String,
data class SurveyRequestState(val requester: Party,
val surveyor: Party,
val landTitleId: String,
val surveyPrice : Int,
override val linearId : UniqueIdentifier) : LinearState {
override val participants: List<AbstractParty>
get() = listOf(requester)
val surveyPrice: Int,
val status: String,
override val linearId: UniqueIdentifier = UniqueIdentifier()) : LinearState {
override val participants: List<AbstractParty> get() = listOf(requester, surveyor)
}
41 changes: 41 additions & 0 deletions cordapp/src/main/kotlin/com/survey/Helper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.survey

import com.google.common.collect.ImmutableList
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.flows.FlowException
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.Vault
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria
import java.security.Key
import java.util.jar.JarInputStream

fun ServiceHub.firstIdentityByName(name: CordaX500Name) = networkMapCache.getNodeByLegalName(name)?.legalIdentities?.first()
?: throw IllegalArgumentException("Requested oracle $name not found on network.")

public object Helper {

fun getSurveyByLinearId(linearId: UniqueIdentifier, serviceHub: ServiceHub): StateAndRef<SurveyState> {
val queryCriteria = QueryCriteria.LinearStateQueryCriteria(
null,
ImmutableList.of(linearId),
Vault.StateStatus.UNCONSUMED, null)
return serviceHub.vaultService.queryBy<SurveyState>(queryCriteria).states.singleOrNull()
?: throw FlowException("Survey with id $linearId not found.")
}

fun getSurveyRequestByLinearId(linearId: UniqueIdentifier, serviceHub: ServiceHub): StateAndRef<SurveyRequestState> {
val queryCriteria = QueryCriteria.LinearStateQueryCriteria(
null,
ImmutableList.of(linearId),
Vault.StateStatus.UNCONSUMED, null)
return serviceHub.vaultService.queryBy<SurveyRequestState>(queryCriteria).states.singleOrNull()
?: throw FlowException("Survey with id $linearId not found.")
}

fun encryptAttachment(attachment: JarInputStream): Pair<ByteArray?, Key?> {
return Pair(null, null)
}
}
21 changes: 6 additions & 15 deletions cordapp/src/main/kotlin/com/survey/api/SurveyAPI.kt
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
package com.survey.api

import com.survey.SurveyState
import com.survey.flows.IssueFlow.SurveyIssuanceFlow
import com.survey.flows.IssueFlow.SurveyRequestFlow
import com.template.flow.SelfIssueCashFlow
import net.corda.finance.flows.*
import com.survey.flows.IssueFlow
import net.corda.core.contracts.Amount
import net.corda.core.crypto.SecureHash
import java.util.Currency
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.messaging.vaultQueryBy
import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.finance.flows.CashIssueFlow
import org.slf4j.Logger
import java.util.*
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import net.corda.core.utilities.loggerFor
import net.corda.finance.contracts.asset.Cash
import org.slf4j.Logger
import javax.ws.rs.core.Response.Status.BAD_REQUEST
import javax.ws.rs.core.Response.Status.CREATED


// *****************
Expand Down Expand Up @@ -136,7 +127,7 @@ class SurveyAPI(val rpcOps: CordaRPCOps) {
val purchaserParty = rpcOps.wellKnownPartyFromX500Name(purchaserx500Name) ?: throw Exception("Party not recognised.")

return try {
val signedTx = rpcOps.startTrackedFlowDynamic(SurveyIssuanceFlow::class.java, purchaserParty, surveyDate, price, propertyAddress, landTitleId, encodedSurveyHash, encodedSurveyKey).returnValue.getOrThrow()
val signedTx = rpcOps.startTrackedFlowDynamic(IssueFlow.IssueSurveyFlow::class.java, purchaserParty, surveyDate, price, propertyAddress, landTitleId, encodedSurveyHash, encodedSurveyKey).returnValue.getOrThrow()
Response.status(Response.Status.CREATED).entity("Transaction id "+signedTx.hashCode()+" committed to ledger.\n").build()

} catch (ex: Throwable) {
Expand Down
Loading