mirror of
https://github.com/corda/corda.git
synced 2025-04-07 11:27:01 +00:00
Replaces keys and parties in states with AbstractParty
Switch to using AbstractParty as the standard identifier for parties in states, so that full parties can be used during construction of transactions and anonymised parties when the transaction is being added to the ledger.
This commit is contained in:
parent
d3bb040355
commit
c13a99a2f9
@ -10,6 +10,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.BusinessCalendar
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
@ -66,6 +67,7 @@ object JacksonSupport {
|
||||
addDeserializer(AnonymousParty::class.java, AnonymousPartyDeserializer)
|
||||
addSerializer(Party::class.java, PartySerializer)
|
||||
addDeserializer(Party::class.java, PartyDeserializer)
|
||||
addDeserializer(AbstractParty::class.java, PartyDeserializer)
|
||||
addSerializer(BigDecimal::class.java, ToStringSerializer)
|
||||
addDeserializer(BigDecimal::class.java, NumberDeserializers.BigDecimalDeserializer())
|
||||
addSerializer(SecureHash::class.java, SecureHashSerializer)
|
||||
@ -160,8 +162,16 @@ object JacksonSupport {
|
||||
}
|
||||
|
||||
val mapper = parser.codec as PartyObjectMapper
|
||||
val principal = X500Name(parser.text)
|
||||
return mapper.partyFromPrincipal(principal) ?: throw JsonParseException(parser, "Could not find a Party with name ${principal}")
|
||||
// TODO: We should probably have a better specified way of identifying X.500 names vs keys
|
||||
// Base58 keys never include an equals character, while X.500 names always will, so we use that to determine
|
||||
// how to parse the content
|
||||
return if (parser.text.contains("=")) {
|
||||
val principal = X500Name(parser.text)
|
||||
mapper.partyFromPrincipal(principal) ?: throw JsonParseException(parser, "Could not find a Party with name ${principal}")
|
||||
} else {
|
||||
val key = parsePublicKeyBase58(parser.text)
|
||||
mapper.partyFromKey(key) ?: throw JsonParseException(parser, "Could not find a Party with key ${key.toStringShort()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState
|
||||
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
|
||||
// see a signature from each of those keys. The actual signatures have been verified against the transaction
|
||||
// data by the platform before execution.
|
||||
val owningPubKeys = inputs.map { it.owner }.toSet()
|
||||
val owningPubKeys = inputs.map { it.owner.owningKey }.toSet()
|
||||
val command = commands.requireSingleCommand<T>()
|
||||
val keysThatSigned = command.signers.toSet()
|
||||
requireThat {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package 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.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
|
||||
// The dummy contract doesn't do anything useful. It exists for testing purposes.
|
||||
|
||||
@ -14,12 +14,12 @@ data class DummyContract(override val legalContractReference: SecureHash = Secur
|
||||
val magicNumber: Int
|
||||
}
|
||||
|
||||
data class SingleOwnerState(override val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState, State {
|
||||
data class SingleOwnerState(override val magicNumber: Int = 0, override val owner: AbstractParty) : OwnableState, State {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = listOf(owner)
|
||||
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,9 +28,9 @@ data class DummyContract(override val legalContractReference: SecureHash = Secur
|
||||
* in a different field, however this is a good example of a contract with multiple states.
|
||||
*/
|
||||
data class MultiOwnerState(override val magicNumber: Int = 0,
|
||||
val owners: List<PublicKey>) : ContractState, State {
|
||||
val owners: List<AbstractParty>) : ContractState, State {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey> get() = owners
|
||||
override val participants: List<AbstractParty> get() = owners
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
@ -47,22 +47,22 @@ data class DummyContract(override val legalContractReference: SecureHash = Secur
|
||||
fun generateInitial(magicNumber: Int, notary: Party, owner: PartyAndReference, vararg otherOwners: PartyAndReference): TransactionBuilder {
|
||||
val owners = listOf(owner) + otherOwners
|
||||
return if (owners.size == 1) {
|
||||
val state = SingleOwnerState(magicNumber, owners.first().party.owningKey)
|
||||
val state = SingleOwnerState(magicNumber, owners.first().party)
|
||||
TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owners.first().party.owningKey))
|
||||
} else {
|
||||
val state = MultiOwnerState(magicNumber, owners.map { it.party.owningKey })
|
||||
val state = MultiOwnerState(magicNumber, owners.map { it.party })
|
||||
TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owners.map { it.party.owningKey }))
|
||||
}
|
||||
}
|
||||
|
||||
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: PublicKey) = move(listOf(prior), newOwner)
|
||||
fun move(priors: List<StateAndRef<DummyContract.SingleOwnerState>>, newOwner: PublicKey): TransactionBuilder {
|
||||
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: AbstractParty) = move(listOf(prior), newOwner)
|
||||
fun move(priors: List<StateAndRef<DummyContract.SingleOwnerState>>, newOwner: AbstractParty): TransactionBuilder {
|
||||
require(priors.isNotEmpty())
|
||||
val priorState = priors[0].state.data
|
||||
val (cmd, state) = priorState.withNewOwner(newOwner)
|
||||
return TransactionType.General.Builder(notary = priors[0].state.notary).withItems(
|
||||
/* INPUTS */ *priors.toTypedArray(),
|
||||
/* COMMAND */ Command(cmd, priorState.owner),
|
||||
/* COMMAND */ Command(cmd, priorState.owner.owningKey),
|
||||
/* OUTPUT */ state
|
||||
)
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.flows.ContractUpgradeFlow
|
||||
import java.security.PublicKey
|
||||
|
||||
// The dummy contract doesn't do anything useful. It exists for testing purposes.
|
||||
val DUMMY_V2_PROGRAM_ID = DummyContractV2()
|
||||
@ -15,9 +15,9 @@ val DUMMY_V2_PROGRAM_ID = DummyContractV2()
|
||||
class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.State> {
|
||||
override val legacyContract = DummyContract::class.java
|
||||
|
||||
data class State(val magicNumber: Int = 0, val owners: List<PublicKey>) : ContractState {
|
||||
data class State(val magicNumber: Int = 0, val owners: List<AbstractParty>) : ContractState {
|
||||
override val contract = DUMMY_V2_PROGRAM_ID
|
||||
override val participants: List<PublicKey> = owners
|
||||
override val participants: List<AbstractParty> = owners
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
@ -44,16 +44,16 @@ class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.St
|
||||
*
|
||||
* @return a pair of wire transaction, and a set of those who should sign the transaction for it to be valid.
|
||||
*/
|
||||
fun generateUpgradeFromV1(vararg states: StateAndRef<DummyContract.State>): Pair<WireTransaction, Set<PublicKey>> {
|
||||
fun generateUpgradeFromV1(vararg states: StateAndRef<DummyContract.State>): Pair<WireTransaction, Set<AbstractParty>> {
|
||||
val notary = states.map { it.state.notary }.single()
|
||||
require(states.isNotEmpty())
|
||||
|
||||
val signees = states.flatMap { it.state.data.participants }.toSet()
|
||||
val signees: Set<AbstractParty> = states.flatMap { it.state.data.participants }.distinct().toSet()
|
||||
return Pair(TransactionType.General.Builder(notary).apply {
|
||||
states.forEach {
|
||||
addInputState(it)
|
||||
addOutputState(upgrade(it.state.data))
|
||||
addCommand(UpgradeCommand(DUMMY_V2_PROGRAM_ID.javaClass), signees.toList())
|
||||
addCommand(UpgradeCommand(DUMMY_V2_PROGRAM_ID.javaClass), signees.map { it.owningKey }.toList())
|
||||
}
|
||||
}.toWireTransaction(), signees)
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import java.security.PublicKey
|
||||
import net.corda.core.identity.AbstractParty
|
||||
|
||||
/**
|
||||
* Dummy state for use in testing. Not part of any contract, not even the [DummyContract].
|
||||
*/
|
||||
data class DummyState(val magicNumber: Int = 0) : ContractState {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = emptyList()
|
||||
}
|
||||
|
@ -1,12 +1,8 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException("Insufficient balance, missing $amountMissing")
|
||||
|
||||
@ -32,9 +28,9 @@ interface FungibleAsset<T : Any> : OwnableState {
|
||||
*/
|
||||
val exitKeys: Collection<PublicKey>
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
override val owner: PublicKey
|
||||
override val owner: AbstractParty
|
||||
|
||||
fun move(newAmount: Amount<Issued<T>>, newOwner: PublicKey): FungibleAsset<T>
|
||||
fun move(newAmount: Amount<Issued<T>>, newOwner: AbstractParty): FungibleAsset<T>
|
||||
|
||||
// Just for grouping
|
||||
interface Commands : CommandData {
|
||||
|
@ -4,6 +4,7 @@ import net.corda.core.contracts.clauses.Clause
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogicRef
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
@ -114,7 +115,7 @@ interface ContractState {
|
||||
* The participants list should normally be derived from the contents of the state. E.g. for [Cash] the participants
|
||||
* list should just contain the owner.
|
||||
*/
|
||||
val participants: List<PublicKey>
|
||||
val participants: List<AbstractParty>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,10 +175,10 @@ fun <T : Any> Amount<Issued<T>>.withoutIssuer(): Amount<T> = Amount(quantity, to
|
||||
*/
|
||||
interface OwnableState : ContractState {
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
val owner: PublicKey
|
||||
val owner: AbstractParty
|
||||
|
||||
/** Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone */
|
||||
fun withNewOwner(newOwner: PublicKey): Pair<CommandData, OwnableState>
|
||||
fun withNewOwner(newOwner: AbstractParty): Pair<CommandData, OwnableState>
|
||||
}
|
||||
|
||||
/** Something which is scheduled to happen at a point in time */
|
||||
@ -280,7 +281,7 @@ interface DealState : LinearState {
|
||||
* separate process exchange certificates to ascertain identities. Thus decoupling identities from
|
||||
* [ContractState]s.
|
||||
* */
|
||||
val parties: List<AnonymousParty>
|
||||
val parties: List<AbstractParty>
|
||||
|
||||
/**
|
||||
* Generate a partial transaction representing an agreement (command) to this deal, allowing a general
|
||||
@ -343,9 +344,7 @@ inline fun <reified T : ContractState> Iterable<StateAndRef<ContractState>>.filt
|
||||
* ledger. The reference is intended to be encrypted so it's meaningless to anyone other than the party.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class PartyAndReference(val party: AnonymousParty, val reference: OpaqueBytes) {
|
||||
constructor(party: Party, reference: OpaqueBytes) : this(party.toAnonymous(), reference)
|
||||
|
||||
data class PartyAndReference(val party: AbstractParty, val reference: OpaqueBytes) {
|
||||
override fun toString() = "$party$reference"
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ sealed class TransactionType {
|
||||
*/
|
||||
class Builder(notary: Party) : TransactionBuilder(NotaryChange, notary) {
|
||||
override fun addInputState(stateAndRef: StateAndRef<*>) {
|
||||
signers.addAll(stateAndRef.state.data.participants)
|
||||
signers.addAll(stateAndRef.state.data.participants.map { it.owningKey })
|
||||
super.addInputState(stateAndRef)
|
||||
}
|
||||
}
|
||||
@ -171,6 +171,6 @@ sealed class TransactionType {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRequiredSigners(tx: LedgerTransaction) = tx.inputs.flatMap { it.state.data.participants }.toSet()
|
||||
override fun getRequiredSigners(tx: LedgerTransaction) = tx.inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet()
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,11 @@
|
||||
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
|
||||
@ -18,6 +19,8 @@ object NullPublicKey : PublicKey, Comparable<PublicKey> {
|
||||
override fun toString() = "NULL_KEY"
|
||||
}
|
||||
|
||||
val NULL_PARTY = AnonymousParty(NullPublicKey)
|
||||
|
||||
// TODO: Clean up this duplication between Null and Dummy public key
|
||||
@CordaSerializable
|
||||
class DummyPublicKey(val s: String) : PublicKey, Comparable<PublicKey> {
|
||||
|
@ -16,7 +16,6 @@ abstract class AbstractParty(val owningKey: PublicKey) {
|
||||
override fun equals(other: Any?): Boolean = other is AbstractParty && this.owningKey == other.owningKey
|
||||
|
||||
override fun hashCode(): Int = owningKey.hashCode()
|
||||
abstract fun toAnonymous(): AnonymousParty
|
||||
abstract fun nameOrNull(): X500Name?
|
||||
|
||||
abstract fun ref(bytes: OpaqueBytes): PartyAndReference
|
||||
|
@ -18,5 +18,4 @@ class AnonymousParty(owningKey: PublicKey) : AbstractParty(owningKey) {
|
||||
override fun nameOrNull(): X500Name? = null
|
||||
|
||||
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
|
||||
override fun toAnonymous() = this
|
||||
}
|
@ -29,9 +29,8 @@ import java.security.PublicKey
|
||||
// TODO: Remove "open" from [Party] once deprecated crypto.Party class is removed
|
||||
open class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey) {
|
||||
constructor(certAndKey: CertificateAndKey) : this(X500Name(certAndKey.certificate.subjectDN.name), certAndKey.keyPair.public)
|
||||
override fun toAnonymous(): AnonymousParty = AnonymousParty(owningKey)
|
||||
override fun toString() = "${owningKey.toBase58String()} ($name)"
|
||||
override fun nameOrNull(): X500Name? = name
|
||||
|
||||
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this.toAnonymous(), bytes)
|
||||
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
@ -54,7 +55,7 @@ interface IdentityService {
|
||||
fun partyFromName(name: String): Party?
|
||||
fun partyFromX500Name(principal: X500Name): Party?
|
||||
|
||||
fun partyFromAnonymous(party: AnonymousParty): Party?
|
||||
fun partyFromAnonymous(party: AbstractParty): Party?
|
||||
fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,9 @@ package net.corda.core.node.services
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
@ -286,7 +288,7 @@ interface VaultService {
|
||||
@Suspendable
|
||||
fun generateSpend(tx: TransactionBuilder,
|
||||
amount: Amount<Currency>,
|
||||
to: PublicKey,
|
||||
to: AbstractParty,
|
||||
onlyFromParties: Set<AbstractParty>? = null): Pair<TransactionBuilder, List<PublicKey>>
|
||||
|
||||
// DOCSTART VaultStatesQuery
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -59,13 +60,13 @@ abstract class AbstractStateReplacementFlow {
|
||||
|
||||
progressTracker.currentStep = SIGNING
|
||||
|
||||
val myKey = serviceHub.myInfo.legalIdentity.owningKey
|
||||
val myKey = serviceHub.myInfo.legalIdentity
|
||||
val me = listOf(myKey)
|
||||
|
||||
val signatures = if (participants == me) {
|
||||
getNotarySignatures(stx)
|
||||
} else {
|
||||
collectSignatures(participants - me, stx)
|
||||
collectSignatures((participants - me).map { it.owningKey }, stx)
|
||||
}
|
||||
|
||||
val finalTx = stx + signatures
|
||||
@ -73,7 +74,7 @@ abstract class AbstractStateReplacementFlow {
|
||||
return finalTx.tx.outRef(0)
|
||||
}
|
||||
|
||||
abstract protected fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>>
|
||||
abstract protected fun assembleTx(): Pair<SignedTransaction, Iterable<AbstractParty>>
|
||||
|
||||
@Suspendable
|
||||
private fun collectSignatures(participants: Iterable<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||
|
@ -3,6 +3,7 @@ package net.corda.flows
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
@ -32,12 +33,12 @@ class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState
|
||||
@JvmStatic
|
||||
fun verify(input: ContractState, output: ContractState, commandData: Command) {
|
||||
val command = commandData.value as UpgradeCommand
|
||||
val participants: Set<PublicKey> = input.participants.toSet()
|
||||
val participantKeys: Set<PublicKey> = input.participants.map { it.owningKey }.toSet()
|
||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
|
||||
requireThat {
|
||||
"The signing keys include all participant keys" using keysThatSigned.containsAll(participants)
|
||||
"The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
|
||||
"Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
|
||||
"Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
|
||||
"Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
|
||||
@ -53,11 +54,11 @@ class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState
|
||||
.withItems(
|
||||
stateRef,
|
||||
contractUpgrade.upgrade(stateRef.state.data),
|
||||
Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants))
|
||||
Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey }))
|
||||
}
|
||||
}
|
||||
|
||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
|
||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<AbstractParty>> {
|
||||
val stx = assembleBareTx(originalState, modification)
|
||||
.signWith(serviceHub.legalIdentityKey)
|
||||
.toSignedTransaction(false)
|
||||
|
@ -105,7 +105,7 @@ class FinalityFlow(val transactions: Iterable<SignedTransaction>,
|
||||
// Calculate who is meant to see the results based on the participants involved.
|
||||
val keys = ltx.outputs.flatMap { it.data.participants } + ltx.inputs.flatMap { it.state.data.participants }
|
||||
// TODO: Is it safe to drop participants we don't know how to contact? Does not knowing how to contact them count as a reason to fail?
|
||||
val parties = keys.mapNotNull { serviceHub.identityService.partyFromKey(it) }.toSet()
|
||||
val parties = keys.mapNotNull { serviceHub.identityService.partyFromAnonymous(it) }.toSet()
|
||||
Pair(stx, parties)
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package net.corda.flows
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
@ -24,11 +25,11 @@ class NotaryChangeFlow<out T : ContractState>(
|
||||
progressTracker: ProgressTracker = tracker())
|
||||
: AbstractStateReplacementFlow.Instigator<T, T, Party>(originalState, newNotary, progressTracker) {
|
||||
|
||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
|
||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<AbstractParty>> {
|
||||
val state = originalState.state
|
||||
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
|
||||
|
||||
val participants: Iterable<PublicKey>
|
||||
val participants: Iterable<AbstractParty>
|
||||
|
||||
if (state.encumbrance == null) {
|
||||
val modifiedState = TransactionState(state.data, modification)
|
||||
@ -53,14 +54,14 @@ class NotaryChangeFlow<out T : ContractState>(
|
||||
*
|
||||
* @return union of all added states' participants
|
||||
*/
|
||||
private fun resolveEncumbrances(tx: TransactionBuilder): Iterable<PublicKey> {
|
||||
private fun resolveEncumbrances(tx: TransactionBuilder): Iterable<AbstractParty> {
|
||||
val stateRef = originalState.ref
|
||||
val txId = stateRef.txhash
|
||||
val issuingTx = serviceHub.storageService.validatedTransactions.getTransaction(txId)
|
||||
?: throw StateReplacementException("Transaction $txId not found")
|
||||
val outputs = issuingTx.tx.outputs
|
||||
|
||||
val participants = mutableSetOf<PublicKey>()
|
||||
val participants = mutableSetOf<AbstractParty>()
|
||||
|
||||
var nextStateIndex = stateRef.index
|
||||
var newOutputPosition = tx.outputStates().size
|
||||
|
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.testing.ALICE_PUBKEY
|
||||
import org.junit.Test
|
||||
@ -14,7 +15,7 @@ class DummyContractV2Tests {
|
||||
@Test
|
||||
fun `upgrade from v1`() {
|
||||
val contractUpgrade = DummyContractV2()
|
||||
val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE_PUBKEY), DUMMY_NOTARY)
|
||||
val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_NOTARY)
|
||||
val v1Ref = StateRef(SecureHash.randomSHA256(), 0)
|
||||
val v1StateAndRef = StateAndRef(v1State, v1Ref)
|
||||
val (tx, _) = DummyContractV2().generateUpgradeFromV1(v1StateAndRef)
|
||||
|
@ -2,13 +2,12 @@ package net.corda.core.contracts
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.utilities.DUMMY_PUBKEY_1
|
||||
import net.corda.core.utilities.DUMMY_PUBKEY_2
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.ledger
|
||||
import net.corda.testing.transaction
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@ -19,9 +18,9 @@ class TransactionEncumbranceTests {
|
||||
|
||||
val state = Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` defaultIssuer,
|
||||
owner = DUMMY_PUBKEY_1
|
||||
owner = MEGA_CORP
|
||||
)
|
||||
val stateWithNewOwner = state.copy(owner = DUMMY_PUBKEY_2)
|
||||
val stateWithNewOwner = state.copy(owner = MINI_CORP)
|
||||
|
||||
val FOUR_PM: Instant = Instant.parse("2015-04-17T16:00:00.00Z")
|
||||
val FIVE_PM: Instant = FOUR_PM.plus(1, ChronoUnit.HOURS)
|
||||
@ -40,7 +39,7 @@ class TransactionEncumbranceTests {
|
||||
data class State(
|
||||
val validFrom: Instant
|
||||
) : ContractState {
|
||||
override val participants: List<PublicKey> = emptyList()
|
||||
override val participants: List<AbstractParty> = emptyList()
|
||||
override val contract: Contract = TEST_TIMELOCK_ID
|
||||
}
|
||||
}
|
||||
@ -52,7 +51,7 @@ class TransactionEncumbranceTests {
|
||||
input { state }
|
||||
output(encumbrance = 1) { stateWithNewOwner }
|
||||
output("5pm time-lock") { timeLock }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
verifies()
|
||||
}
|
||||
}
|
||||
@ -70,7 +69,7 @@ class TransactionEncumbranceTests {
|
||||
input("state encumbered by 5pm time-lock")
|
||||
input("5pm time-lock")
|
||||
output { stateWithNewOwner }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
timestamp(FIVE_PM)
|
||||
verifies()
|
||||
}
|
||||
@ -89,7 +88,7 @@ class TransactionEncumbranceTests {
|
||||
input("state encumbered by 5pm time-lock")
|
||||
input("5pm time-lock")
|
||||
output { state }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
timestamp(FOUR_PM)
|
||||
this `fails with` "the time specified in the time-lock has passed"
|
||||
}
|
||||
@ -106,7 +105,7 @@ class TransactionEncumbranceTests {
|
||||
transaction {
|
||||
input("state encumbered by 5pm time-lock")
|
||||
output { stateWithNewOwner }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
timestamp(FIVE_PM)
|
||||
this `fails with` "Missing required encumbrance 1 in INPUT"
|
||||
}
|
||||
@ -118,7 +117,7 @@ class TransactionEncumbranceTests {
|
||||
transaction {
|
||||
input { state }
|
||||
output(encumbrance = 0) { stateWithNewOwner }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
this `fails with` "Missing required encumbrance 0 in OUTPUT"
|
||||
}
|
||||
}
|
||||
@ -129,7 +128,7 @@ class TransactionEncumbranceTests {
|
||||
input { state }
|
||||
output(encumbrance = 2) { stateWithNewOwner }
|
||||
output { timeLock }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
this `fails with` "Missing required encumbrance 2 in OUTPUT"
|
||||
}
|
||||
}
|
||||
@ -146,7 +145,7 @@ class TransactionEncumbranceTests {
|
||||
input("state encumbered by some other state")
|
||||
input("5pm time-lock")
|
||||
output { stateWithNewOwner }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
timestamp(FIVE_PM)
|
||||
this `fails with` "Missing required encumbrance 1 in INPUT"
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.testing.ALICE_PUBKEY
|
||||
import org.junit.Test
|
||||
import java.security.KeyPair
|
||||
import kotlin.test.assertEquals
|
||||
@ -95,7 +94,7 @@ class TransactionTests {
|
||||
|
||||
@Test
|
||||
fun `transactions with no inputs can have any notary`() {
|
||||
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE_PUBKEY), DUMMY_NOTARY)
|
||||
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_NOTARY)
|
||||
val inputs = emptyList<StateAndRef<*>>()
|
||||
val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB))
|
||||
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
||||
@ -120,7 +119,7 @@ class TransactionTests {
|
||||
|
||||
@Test
|
||||
fun `transaction verification fails for duplicate inputs`() {
|
||||
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE_PUBKEY), DUMMY_NOTARY)
|
||||
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_NOTARY)
|
||||
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
|
||||
val stateAndRef = StateAndRef(baseOutState, stateRef)
|
||||
val inputs = listOf(stateAndRef, stateAndRef)
|
||||
@ -148,7 +147,7 @@ class TransactionTests {
|
||||
@Test
|
||||
fun `general transactions cannot change notary`() {
|
||||
val notary: Party = DUMMY_NOTARY
|
||||
val inState = TransactionState(DummyContract.SingleOwnerState(0, ALICE_PUBKEY), notary)
|
||||
val inState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), notary)
|
||||
val outState = inState.copy(notary = ALICE)
|
||||
val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0)))
|
||||
val outputs = listOf(outState)
|
||||
|
@ -12,9 +12,7 @@ import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.DUMMY_PUBKEY_1
|
||||
import net.corda.core.utilities.TEST_TX_TIME
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MEGA_CORP_PUBKEY
|
||||
import net.corda.testing.ledger
|
||||
import net.corda.testing.*
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import kotlin.test.*
|
||||
@ -30,20 +28,20 @@ class PartialMerkleTreeTest {
|
||||
output("MEGA_CORP cash") {
|
||||
Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||
owner = MEGA_CORP_PUBKEY
|
||||
owner = MEGA_CORP
|
||||
)
|
||||
}
|
||||
output("dummy cash 1") {
|
||||
Cash.State(
|
||||
amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||
owner = DUMMY_PUBKEY_1
|
||||
owner = MINI_CORP
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("MEGA_CORP cash")
|
||||
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
|
||||
output("MEGA_CORP cash".output<Cash.State>().copy(owner = MINI_CORP))
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
@ -98,7 +96,7 @@ class PartialMerkleTreeTest {
|
||||
fun filtering(elem: Any): Boolean {
|
||||
return when (elem) {
|
||||
is StateRef -> true
|
||||
is TransactionState<*> -> elem.data.participants[0].keys == DUMMY_PUBKEY_1.keys
|
||||
is TransactionState<*> -> elem.data.participants[0].owningKey.keys == MINI_CORP_PUBKEY.keys
|
||||
is Command -> MEGA_CORP_PUBKEY in elem.signers
|
||||
is Timestamp -> true
|
||||
is PublicKey -> elem == MEGA_CORP_PUBKEY
|
||||
|
@ -88,7 +88,7 @@ class CollectSignaturesFlowTests {
|
||||
val state = receive<DummyContract.MultiOwnerState>(otherParty).unwrap { it }
|
||||
val notary = serviceHub.networkMapCache.notaryNodes.single().notaryIdentity
|
||||
|
||||
val command = Command(DummyContract.Commands.Create(), state.participants)
|
||||
val command = Command(DummyContract.Commands.Create(), state.participants.map { it.owningKey })
|
||||
val builder = TransactionType.General.Builder(notary = notary).withItems(state, command)
|
||||
val ptx = builder.signWith(serviceHub.legalIdentityKey).toSignedTransaction(false)
|
||||
val stx = subFlow(CollectSignaturesFlow(ptx))
|
||||
@ -108,7 +108,7 @@ class CollectSignaturesFlowTests {
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
val notary = serviceHub.networkMapCache.notaryNodes.single().notaryIdentity
|
||||
val command = Command(DummyContract.Commands.Create(), state.participants)
|
||||
val command = Command(DummyContract.Commands.Create(), state.participants.map { it.owningKey })
|
||||
val builder = TransactionType.General.Builder(notary = notary).withItems(state, command)
|
||||
val ptx = builder.signWith(serviceHub.legalIdentityKey).toSignedTransaction(false)
|
||||
val stx = subFlow(CollectSignaturesFlow(ptx))
|
||||
@ -142,7 +142,7 @@ class CollectSignaturesFlowTests {
|
||||
fun `successfully collects two signatures`() {
|
||||
val magicNumber = 1337
|
||||
val parties = listOf(a.info.legalIdentity, b.info.legalIdentity, c.info.legalIdentity)
|
||||
val state = DummyContract.MultiOwnerState(magicNumber, parties.map { it.owningKey })
|
||||
val state = DummyContract.MultiOwnerState(magicNumber, parties)
|
||||
val flow = a.services.startFlow(TestFlowTwo.Initiator(state, b.info.legalIdentity))
|
||||
mockNet.runNetwork()
|
||||
val result = flow.resultFuture.getOrThrow()
|
||||
|
@ -5,6 +5,7 @@ import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.startFlow
|
||||
@ -27,7 +28,6 @@ import net.corda.testing.startRpcClient
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
@ -187,21 +187,21 @@ class ContractUpgradeFlowTest {
|
||||
val firstState = a.database.transaction { a.vault.unconsumedStates<ContractState>().single() }
|
||||
assertTrue(firstState.state.data is CashV2.State, "Contract state is upgraded to the new version.")
|
||||
assertEquals(Amount(1000000, USD).`issued by`(a.info.legalIdentity.ref(1)), (firstState.state.data as CashV2.State).amount, "Upgraded cash contain the correct amount.")
|
||||
assertEquals(listOf(a.info.legalIdentity.owningKey), (firstState.state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")
|
||||
assertEquals<Collection<AbstractParty>>(listOf(a.info.legalIdentity), (firstState.state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")
|
||||
}
|
||||
|
||||
class CashV2 : UpgradedContract<Cash.State, CashV2.State> {
|
||||
override val legacyContract = Cash::class.java
|
||||
|
||||
data class State(override val amount: Amount<Issued<Currency>>, val owners: List<PublicKey>) : FungibleAsset<Currency> {
|
||||
override val owner: PublicKey = owners.first()
|
||||
override val exitKeys = (owners + amount.token.issuer.party.owningKey).toSet()
|
||||
data class State(override val amount: Amount<Issued<Currency>>, val owners: List<AbstractParty>) : FungibleAsset<Currency> {
|
||||
override val owner: AbstractParty = owners.first()
|
||||
override val exitKeys = (owners + amount.token.issuer.party).map { it.owningKey }.toSet()
|
||||
override val contract = CashV2()
|
||||
override val participants = owners
|
||||
|
||||
override fun move(newAmount: Amount<Issued<Currency>>, newOwner: PublicKey) = copy(amount = amount.copy(newAmount.quantity), owners = listOf(newOwner))
|
||||
override fun move(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty) = copy(amount = amount.copy(newAmount.quantity), owners = listOf(newOwner))
|
||||
override fun toString() = "${Emoji.bagOfCash}New Cash($amount at ${amount.token.issuer} owned by $owner)"
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Cash.Commands.Move(), copy(owners = listOf(newOwner)))
|
||||
override fun withNewOwner(newOwner: AbstractParty) = Pair(Cash.Commands.Move(), copy(owners = listOf(newOwner)))
|
||||
}
|
||||
|
||||
override fun upgrade(state: Cash.State) = CashV2.State(state.amount.times(1000), listOf(state.owner))
|
||||
|
@ -12,7 +12,7 @@ import net.corda.flows.ResolveTransactionsFlow
|
||||
import net.corda.node.utilities.transaction
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MEGA_CORP_KEY
|
||||
import net.corda.testing.MINI_CORP_PUBKEY
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -94,7 +94,7 @@ class ResolveTransactionsFlowTest {
|
||||
val count = 50
|
||||
var cursor = stx2
|
||||
repeat(count) {
|
||||
val stx = DummyContract.move(cursor.tx.outRef(0), MINI_CORP_PUBKEY)
|
||||
val stx = DummyContract.move(cursor.tx.outRef(0), MINI_CORP)
|
||||
.addSignatureUnchecked(NullSignature)
|
||||
.toSignedTransaction(false)
|
||||
a.database.transaction {
|
||||
@ -113,13 +113,13 @@ class ResolveTransactionsFlowTest {
|
||||
fun `triangle of transactions resolves fine`() {
|
||||
val stx1 = makeTransactions().first
|
||||
|
||||
val stx2 = DummyContract.move(stx1.tx.outRef(0), MINI_CORP_PUBKEY).run {
|
||||
val stx2 = DummyContract.move(stx1.tx.outRef(0), MINI_CORP).run {
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
toSignedTransaction()
|
||||
}
|
||||
|
||||
val stx3 = DummyContract.move(listOf(stx1.tx.outRef(0), stx2.tx.outRef(0)), MINI_CORP_PUBKEY).run {
|
||||
val stx3 = DummyContract.move(listOf(stx1.tx.outRef(0), stx2.tx.outRef(0)), MINI_CORP).run {
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
toSignedTransaction()
|
||||
@ -173,7 +173,7 @@ class ResolveTransactionsFlowTest {
|
||||
it.signWith(DUMMY_NOTARY_KEY)
|
||||
it.toSignedTransaction(false)
|
||||
}
|
||||
val dummy2: SignedTransaction = DummyContract.move(dummy1.tx.outRef(0), MINI_CORP_PUBKEY).let {
|
||||
val dummy2: SignedTransaction = DummyContract.move(dummy1.tx.outRef(0), MINI_CORP).let {
|
||||
it.signWith(MEGA_CORP_KEY)
|
||||
it.signWith(DUMMY_NOTARY_KEY)
|
||||
it.toSignedTransaction()
|
||||
|
@ -5,6 +5,7 @@ import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
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.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.StorageService
|
||||
@ -53,7 +54,7 @@ class AttachmentClassLoaderTests {
|
||||
class AttachmentDummyContract : Contract {
|
||||
data class State(val magicNumber: Int = 0) : ContractState {
|
||||
override val contract = ATTACHMENT_TEST_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = listOf()
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,10 @@ package net.corda.core.node
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ class VaultUpdateTests {
|
||||
}
|
||||
|
||||
private class DummyState : ContractState {
|
||||
override val participants: List<PublicKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = emptyList()
|
||||
override val contract = VaultUpdateTests.DummyContract
|
||||
}
|
||||
|
@ -2,14 +2,19 @@ package net.corda.core.serialization
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.core.utilities.DUMMY_KEY_2
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.core.utilities.TEST_TX_TIME
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MEGA_CORP_KEY
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.generateStateRef
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.security.SignatureException
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
@ -27,12 +32,12 @@ class TransactionSerializationTests {
|
||||
data class State(
|
||||
val deposit: PartyAndReference,
|
||||
val amount: Amount<Currency>,
|
||||
override val owner: PublicKey) : OwnableState {
|
||||
override val owner: AbstractParty) : OwnableState {
|
||||
override val contract: Contract = TEST_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = listOf(owner)
|
||||
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
@ -44,9 +49,9 @@ class TransactionSerializationTests {
|
||||
// It refers to a fake TX/state that we don't bother creating here.
|
||||
val depositRef = MINI_CORP.ref(1)
|
||||
val fakeStateRef = generateStateRef()
|
||||
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
|
||||
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY)
|
||||
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, DUMMY_KEY_1.public), DUMMY_NOTARY)
|
||||
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, MEGA_CORP), DUMMY_NOTARY), fakeStateRef)
|
||||
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, MEGA_CORP), DUMMY_NOTARY)
|
||||
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, MEGA_CORP), DUMMY_NOTARY)
|
||||
|
||||
|
||||
lateinit var tx: TransactionBuilder
|
||||
@ -54,14 +59,14 @@ class TransactionSerializationTests {
|
||||
@Before
|
||||
fun setup() {
|
||||
tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(
|
||||
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(DUMMY_KEY_1.public))
|
||||
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(MEGA_CORP.owningKey))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun signWireTX() {
|
||||
tx.signWith(DUMMY_NOTARY_KEY)
|
||||
tx.signWith(DUMMY_KEY_1)
|
||||
tx.signWith(MEGA_CORP_KEY)
|
||||
val signedTX = tx.toSignedTransaction()
|
||||
|
||||
// Now check that the signature we just made verifies.
|
||||
@ -81,7 +86,7 @@ class TransactionSerializationTests {
|
||||
tx.toSignedTransaction()
|
||||
}
|
||||
|
||||
tx.signWith(DUMMY_KEY_1)
|
||||
tx.signWith(MEGA_CORP_KEY)
|
||||
tx.signWith(DUMMY_NOTARY_KEY)
|
||||
val signedTX = tx.toSignedTransaction()
|
||||
|
||||
@ -104,7 +109,7 @@ class TransactionSerializationTests {
|
||||
@Test
|
||||
fun timestamp() {
|
||||
tx.setTime(TEST_TX_TIME, 30.seconds)
|
||||
tx.signWith(DUMMY_KEY_1)
|
||||
tx.signWith(MEGA_CORP_KEY)
|
||||
tx.signWith(DUMMY_NOTARY_KEY)
|
||||
val stx = tx.toSignedTransaction()
|
||||
assertEquals(TEST_TX_TIME, stx.tx.timestamp?.midpoint)
|
||||
|
@ -38,10 +38,9 @@ UNRELEASED
|
||||
* ``Party`` has moved to the ``net.corda.core.identity`` package; there is a deprecated class in its place for
|
||||
backwards compatibility, but it will be removed in a future release and developers should move to the new class as soon
|
||||
as possible.
|
||||
* There is a new ``AbstractParty`` superclass to ``Party``, which contains just the public key. A new class
|
||||
``AnonymousParty`` has been added, which is intended to be used in place of ``Party`` or ``PublicKey`` in contract
|
||||
state objects. The exception to this is where the party in a contract state is intended to be well known, such as
|
||||
issuer of a ``Cash`` state.
|
||||
* There is a new ``AbstractParty`` superclass to ``Party``, which contains just the public key. This now replaces
|
||||
use of ``Party`` and ``PublicKey`` in state objects, and allows use of full or anonymised parties depending on
|
||||
use-case.
|
||||
* Names of parties are now stored as a ``X500Name`` rather than a ``String``, to correctly enforce basic structure of the
|
||||
name. As a result all node legal names must now be structured as X.500 distinguished names.
|
||||
|
||||
|
@ -48,12 +48,12 @@ private fun gatherOurInputs(serviceHub: ServiceHub,
|
||||
notary: Party?): Pair<List<StateAndRef<Cash.State>>, Long> {
|
||||
// Collect cash type inputs
|
||||
val cashStates = serviceHub.vaultService.unconsumedStates<Cash.State>()
|
||||
// extract our key identity for convenience
|
||||
val ourKey = serviceHub.myInfo.legalIdentity.owningKey
|
||||
// extract our identity for convenience
|
||||
val ourIdentity = serviceHub.myInfo.legalIdentity
|
||||
// Filter down to our own cash states with right currency and issuer
|
||||
val suitableCashStates = cashStates.filter {
|
||||
val state = it.state.data
|
||||
(state.owner == ourKey)
|
||||
(state.owner == ourIdentity)
|
||||
&& (state.amount.token == amountRequired.token)
|
||||
}
|
||||
require(!suitableCashStates.isEmpty()) { "Insufficient funds" }
|
||||
@ -90,12 +90,12 @@ private fun prepareOurInputsAndOutputs(serviceHub: ServiceHub, request: FxReques
|
||||
val (inputs, residual) = gatherOurInputs(serviceHub, sellAmount, request.notary)
|
||||
|
||||
// Build and an output state for the counterparty
|
||||
val transferedFundsOutput = Cash.State(sellAmount, request.counterparty.owningKey)
|
||||
val transferedFundsOutput = Cash.State(sellAmount, request.counterparty)
|
||||
|
||||
if (residual > 0L) {
|
||||
// Build an output state for the residual change back to us
|
||||
val residualAmount = Amount(residual, sellAmount.token)
|
||||
val residualOutput = Cash.State(residualAmount, serviceHub.myInfo.legalIdentity.owningKey)
|
||||
val residualOutput = Cash.State(residualAmount, serviceHub.myInfo.legalIdentity)
|
||||
return FxResponse(inputs, listOf(transferedFundsOutput, residualOutput))
|
||||
} else {
|
||||
return FxResponse(inputs, listOf(transferedFundsOutput))
|
||||
@ -140,7 +140,7 @@ class ForeignExchangeFlow(val tradeId: String,
|
||||
require(it.inputs.all { it.state.notary == notary }) {
|
||||
"notary of remote states must be same as for our states"
|
||||
}
|
||||
require(it.inputs.all { it.state.data.owner == remoteRequestWithNotary.owner.owningKey }) {
|
||||
require(it.inputs.all { it.state.data.owner == remoteRequestWithNotary.owner }) {
|
||||
"The inputs are not owned by the correct counterparty"
|
||||
}
|
||||
require(it.inputs.all { it.state.data.amount.token == remoteRequestWithNotary.amount.token }) {
|
||||
@ -153,7 +153,7 @@ class ForeignExchangeFlow(val tradeId: String,
|
||||
>= remoteRequestWithNotary.amount.quantity) {
|
||||
"the provided inputs don't provide sufficient funds"
|
||||
}
|
||||
require(it.outputs.filter { it.owner == serviceHub.myInfo.legalIdentity.owningKey }.
|
||||
require(it.outputs.filter { it.owner == serviceHub.myInfo.legalIdentity }.
|
||||
map { it.amount.quantity }.sum() == remoteRequestWithNotary.amount.quantity) {
|
||||
"the provided outputs don't provide the request quantity"
|
||||
}
|
||||
@ -195,8 +195,8 @@ class ForeignExchangeFlow(val tradeId: String,
|
||||
val builder = TransactionType.General.Builder(ourStates.inputs.first().state.notary)
|
||||
|
||||
// Add the move commands and key to indicate all the respective owners and need to sign
|
||||
val ourSigners = ourStates.inputs.map { it.state.data.owner }.toSet()
|
||||
val theirSigners = theirStates.inputs.map { it.state.data.owner }.toSet()
|
||||
val ourSigners = ourStates.inputs.map { it.state.data.owner.owningKey }.toSet()
|
||||
val theirSigners = theirStates.inputs.map { it.state.data.owner.owningKey }.toSet()
|
||||
builder.addCommand(Cash.Commands.Move(), (ourSigners + theirSigners).toList())
|
||||
|
||||
// Build and add the inputs and outputs
|
||||
|
@ -2,9 +2,13 @@ package net.corda.docs
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.containsAny
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.ServiceHub
|
||||
@ -64,10 +68,10 @@ data class TradeApprovalContract(override val legalContractReference: SecureHash
|
||||
override val contract: TradeApprovalContract = TradeApprovalContract()) : LinearState {
|
||||
|
||||
val parties: List<Party> get() = listOf(source, counterparty)
|
||||
override val participants: List<PublicKey> get() = parties.map { it.owningKey }
|
||||
override val participants: List<AbstractParty> get() = parties
|
||||
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||
return participants.any { it.containsAny(ourKeys) }
|
||||
return participants.any { it.owningKey.containsAny(ourKeys) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,8 @@ We've added the ability for flows to be versioned by their CorDapp developers. T
|
||||
version of a flow and allows it to reject flow communication with a node which isn't using the same fact. In a future
|
||||
release we allow a node to have multiple versions of the same flow running to enable backwards compatibility.
|
||||
|
||||
There are major changes to the ``Party`` class as part of confidential identities. See :doc:`changelog` for full details.
|
||||
There are major changes to the ``Party`` class as part of confidential identities, and how parties and keys are stored
|
||||
in transaction state objects. See :doc:`changelog` for full details.
|
||||
|
||||
|
||||
Milestone 11
|
||||
|
@ -2,16 +2,16 @@ package net.corda.contracts.universal
|
||||
|
||||
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.TransactionBuilder
|
||||
import java.math.BigDecimal
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
|
||||
val UNIVERSAL_PROGRAM_ID = UniversalContract()
|
||||
|
||||
class UniversalContract : Contract {
|
||||
data class State(override val participants: List<PublicKey>,
|
||||
data class State(override val participants: List<AbstractParty>,
|
||||
val details: Arrangement) : ContractState {
|
||||
override val contract = UNIVERSAL_PROGRAM_ID
|
||||
}
|
||||
@ -316,7 +316,7 @@ class UniversalContract : Contract {
|
||||
override val legalContractReference: SecureHash
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
fun generateIssue(tx: TransactionBuilder, arrangement: Arrangement, at: PartyAndReference, notary: PublicKey) {
|
||||
fun generateIssue(tx: TransactionBuilder, arrangement: Arrangement, at: PartyAndReference, notary: Party) {
|
||||
check(tx.inputStates().isEmpty())
|
||||
tx.addOutputState(State(listOf(notary), arrangement))
|
||||
tx.addCommand(Commands.Issue(), at.party.owningKey)
|
||||
|
@ -130,15 +130,15 @@ class Cap {
|
||||
val paymentFinal = arrange { highStreetBank.owes(acmeCorp, 250.K, EUR) }
|
||||
|
||||
|
||||
val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractInitial)
|
||||
val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY), contractInitial)
|
||||
|
||||
val stateAfterFixingFirst = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractAfterFixingFirst)
|
||||
val stateAfterFixingFirst = UniversalContract.State(listOf(DUMMY_NOTARY), contractAfterFixingFirst)
|
||||
|
||||
val stateAfterExecutionFirst = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractAfterExecutionFirst)
|
||||
val statePaymentFirst = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), paymentFirst)
|
||||
val stateAfterExecutionFirst = UniversalContract.State(listOf(DUMMY_NOTARY), contractAfterExecutionFirst)
|
||||
val statePaymentFirst = UniversalContract.State(listOf(DUMMY_NOTARY), paymentFirst)
|
||||
|
||||
val stateAfterFixingFinal = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractAfterFixingFinal)
|
||||
val statePaymentFinal = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), paymentFinal)
|
||||
val stateAfterFixingFinal = UniversalContract.State(listOf(DUMMY_NOTARY), contractAfterFixingFinal)
|
||||
val statePaymentFinal = UniversalContract.State(listOf(DUMMY_NOTARY), paymentFinal)
|
||||
|
||||
val contractLimitedCap = arrange {
|
||||
rollOut("2016-04-01".ld, "2017-04-01".ld, Frequency.SemiAnnual, object {
|
||||
|
@ -44,11 +44,11 @@ class Caplet {
|
||||
|
||||
val contractFinal = arrange { highStreetBank.owes(acmeCorp, 250.K, EUR) }
|
||||
|
||||
val stateStart = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract)
|
||||
val stateStart = UniversalContract.State(listOf(DUMMY_NOTARY), contract)
|
||||
|
||||
val stateFixed = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractFixed)
|
||||
val stateFixed = UniversalContract.State(listOf(DUMMY_NOTARY), contractFixed)
|
||||
|
||||
val stateFinal = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractFinal)
|
||||
val stateFinal = UniversalContract.State(listOf(DUMMY_NOTARY), contractFinal)
|
||||
|
||||
@Test
|
||||
fun issue() {
|
||||
|
@ -43,9 +43,9 @@ class FXFwdTimeOption
|
||||
val TEST_TX_TIME_BEFORE_MATURITY: Instant get() = Instant.parse("2018-05-01T12:00:00.00Z")
|
||||
val TEST_TX_TIME_AFTER_MATURITY: Instant get() = Instant.parse("2018-06-02T12:00:00.00Z")
|
||||
|
||||
val inState = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), initialContract)
|
||||
val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), outContract1)
|
||||
val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), outContract2)
|
||||
val inState = UniversalContract.State(listOf(DUMMY_NOTARY), initialContract)
|
||||
val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY), outContract1)
|
||||
val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY), outContract2)
|
||||
|
||||
@Test
|
||||
fun `issue - signature`() {
|
||||
|
@ -25,18 +25,18 @@ class FXSwap {
|
||||
val transfer1 = arrange { highStreetBank.owes(acmeCorp, 1070.K, EUR) }
|
||||
val transfer2 = arrange { acmeCorp.owes(highStreetBank, 1.M, USD) }
|
||||
|
||||
val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transfer1)
|
||||
val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transfer2)
|
||||
val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY), transfer1)
|
||||
val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY), transfer2)
|
||||
|
||||
val transferBad1 = arrange { highStreetBank.owes(acmeCorp, 1070.K, USD) } // wrong currency
|
||||
val transferBad2 = arrange { acmeCorp.owes(highStreetBank, 900.K, USD) } // wrong amount
|
||||
val transferBad3 = arrange { highStreetBank.owes(highStreetBank, 1070.K, EUR) } // wrong party
|
||||
|
||||
val outStateBad1 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferBad1)
|
||||
val outStateBad2 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferBad2)
|
||||
val outStateBad3 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferBad3)
|
||||
val outStateBad1 = UniversalContract.State(listOf(DUMMY_NOTARY), transferBad1)
|
||||
val outStateBad2 = UniversalContract.State(listOf(DUMMY_NOTARY), transferBad2)
|
||||
val outStateBad3 = UniversalContract.State(listOf(DUMMY_NOTARY), transferBad3)
|
||||
|
||||
val inState = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract)
|
||||
val inState = UniversalContract.State(listOf(DUMMY_NOTARY), contract)
|
||||
|
||||
@Test
|
||||
fun `issue - signature`() {
|
||||
|
@ -122,12 +122,12 @@ class IRS {
|
||||
|
||||
val paymentFirst = arrange { highStreetBank.owes(acmeCorp, 250.K, EUR) }
|
||||
|
||||
val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractInitial)
|
||||
val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY), contractInitial)
|
||||
|
||||
val stateAfterFixingFirst = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractAfterFixingFirst)
|
||||
val stateAfterExecutionFirst = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractAfterExecutionFirst)
|
||||
val stateAfterFixingFirst = UniversalContract.State(listOf(DUMMY_NOTARY), contractAfterFixingFirst)
|
||||
val stateAfterExecutionFirst = UniversalContract.State(listOf(DUMMY_NOTARY), contractAfterExecutionFirst)
|
||||
|
||||
val statePaymentFirst = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), paymentFirst)
|
||||
val statePaymentFirst = UniversalContract.State(listOf(DUMMY_NOTARY), paymentFirst)
|
||||
|
||||
|
||||
@Test
|
||||
|
@ -36,7 +36,7 @@ class RollOutTests {
|
||||
}
|
||||
}
|
||||
}
|
||||
val stateStart = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract)
|
||||
val stateStart = UniversalContract.State(listOf(DUMMY_NOTARY), contract)
|
||||
|
||||
val contractStep1a = arrange {
|
||||
rollOut("2016-10-03".ld, "2017-09-01".ld, Frequency.Monthly) {
|
||||
@ -55,8 +55,8 @@ class RollOutTests {
|
||||
highStreetBank.owes(acmeCorp, 10.K, USD)
|
||||
}
|
||||
|
||||
val stateStep1a = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractStep1a)
|
||||
val stateStep1b = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractStep1b)
|
||||
val stateStep1a = UniversalContract.State(listOf(DUMMY_NOTARY), contractStep1a)
|
||||
val stateStep1b = UniversalContract.State(listOf(DUMMY_NOTARY), contractStep1b)
|
||||
|
||||
val contract_transfer1 = arrange {
|
||||
highStreetBank.owes(acmeCorp, 10.K, USD)
|
||||
|
@ -54,7 +54,7 @@ class Swaption {
|
||||
|
||||
}
|
||||
|
||||
val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractInitial)
|
||||
val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY), contractInitial)
|
||||
|
||||
@Test
|
||||
fun issue() {
|
||||
|
@ -33,12 +33,12 @@ class ZeroCouponBond {
|
||||
val transfer = arrange { highStreetBank.owes(acmeCorp, 100.K, GBP) }
|
||||
val transferWrong = arrange { highStreetBank.owes(acmeCorp, 80.K, GBP) }
|
||||
|
||||
val inState = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract)
|
||||
val inState = UniversalContract.State(listOf(DUMMY_NOTARY), contract)
|
||||
|
||||
val outState = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transfer)
|
||||
val outStateWrong = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferWrong)
|
||||
val outState = UniversalContract.State(listOf(DUMMY_NOTARY), transfer)
|
||||
val outStateWrong = UniversalContract.State(listOf(DUMMY_NOTARY), transferWrong)
|
||||
|
||||
val outStateMove = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractMove)
|
||||
val outStateMove = UniversalContract.State(listOf(DUMMY_NOTARY), contractMove)
|
||||
|
||||
@Test
|
||||
fun basic() {
|
||||
|
@ -3,6 +3,7 @@ package net.corda.contracts.isolated
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
|
||||
@ -13,7 +14,7 @@ val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract()
|
||||
class AnotherDummyContract : Contract, net.corda.core.node.DummyContractBackdoor {
|
||||
data class State(val magicNumber: Int = 0) : ContractState {
|
||||
override val contract = ANOTHER_DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = emptyList()
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.contracts;
|
||||
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.time.*;
|
||||
@ -12,7 +13,7 @@ import java.util.*;
|
||||
* ultimately either language can be used against a common test framework (and therefore can be used for real).
|
||||
*/
|
||||
public interface ICommercialPaperState extends ContractState {
|
||||
ICommercialPaperState withOwner(PublicKey newOwner);
|
||||
ICommercialPaperState withOwner(AbstractParty newOwner);
|
||||
|
||||
ICommercialPaperState withFaceValue(Amount<Issued<Currency>> newFaceValue);
|
||||
|
||||
|
@ -9,6 +9,9 @@ import net.corda.core.contracts.Contract;
|
||||
import net.corda.core.contracts.TransactionForContract.*;
|
||||
import net.corda.core.contracts.clauses.*;
|
||||
import net.corda.core.crypto.*;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.identity.AnonymousParty;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.node.services.*;
|
||||
import net.corda.core.transactions.*;
|
||||
@ -34,14 +37,14 @@ public class JavaCommercialPaper implements Contract {
|
||||
@SuppressWarnings("unused")
|
||||
public static class State implements OwnableState, ICommercialPaperState {
|
||||
private PartyAndReference issuance;
|
||||
private PublicKey owner;
|
||||
private AbstractParty owner;
|
||||
private Amount<Issued<Currency>> faceValue;
|
||||
private Instant maturityDate;
|
||||
|
||||
public State() {
|
||||
} // For serialization
|
||||
|
||||
public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> faceValue,
|
||||
public State(PartyAndReference issuance, AbstractParty owner, Amount<Issued<Currency>> faceValue,
|
||||
Instant maturityDate) {
|
||||
this.issuance = issuance;
|
||||
this.owner = owner;
|
||||
@ -53,13 +56,13 @@ public class JavaCommercialPaper implements Contract {
|
||||
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
|
||||
}
|
||||
|
||||
public ICommercialPaperState withOwner(PublicKey newOwner) {
|
||||
public ICommercialPaperState withOwner(AbstractParty newOwner) {
|
||||
return new State(this.issuance, newOwner, this.faceValue, this.maturityDate);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Pair<CommandData, OwnableState> withNewOwner(@NotNull PublicKey newOwner) {
|
||||
public Pair<CommandData, OwnableState> withNewOwner(@NotNull AbstractParty newOwner) {
|
||||
return new Pair<>(new Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate));
|
||||
}
|
||||
|
||||
@ -76,7 +79,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PublicKey getOwner() {
|
||||
public AbstractParty getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
@ -117,12 +120,12 @@ public class JavaCommercialPaper implements Contract {
|
||||
}
|
||||
|
||||
public State withoutOwner() {
|
||||
return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate);
|
||||
return new State(issuance, new AnonymousParty(NullPublicKey.INSTANCE), faceValue, maturityDate);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<PublicKey> getParticipants() {
|
||||
public List<AbstractParty> getParticipants() {
|
||||
return ImmutableList.of(this.owner);
|
||||
}
|
||||
}
|
||||
@ -162,12 +165,12 @@ public class JavaCommercialPaper implements Contract {
|
||||
@NotNull List<? extends State> inputs,
|
||||
@NotNull List<? extends State> outputs,
|
||||
@NotNull List<? extends AuthenticatedObject<? extends Commands>> commands,
|
||||
@NotNull State groupingKey) {
|
||||
State groupingKey) {
|
||||
AuthenticatedObject<Commands.Move> cmd = requireSingleCommand(tx.getCommands(), Commands.Move.class);
|
||||
// There should be only a single input due to aggregation above
|
||||
State input = single(inputs);
|
||||
|
||||
if (!cmd.getSigners().contains(input.getOwner()))
|
||||
if (!cmd.getSigners().contains(input.getOwner().getOwningKey()))
|
||||
throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP");
|
||||
|
||||
// Check the output CP state is the same as the input state, ignoring the owner field.
|
||||
@ -194,13 +197,13 @@ public class JavaCommercialPaper implements Contract {
|
||||
@NotNull List<? extends State> inputs,
|
||||
@NotNull List<? extends State> outputs,
|
||||
@NotNull List<? extends AuthenticatedObject<? extends Commands>> commands,
|
||||
@NotNull State groupingKey) {
|
||||
State groupingKey) {
|
||||
AuthenticatedObject<Commands.Redeem> cmd = requireSingleCommand(tx.getCommands(), Commands.Redeem.class);
|
||||
|
||||
// There should be only a single input due to aggregation above
|
||||
State input = single(inputs);
|
||||
|
||||
if (!cmd.getSigners().contains(input.getOwner()))
|
||||
if (!cmd.getSigners().contains(input.getOwner().getOwningKey()))
|
||||
throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP");
|
||||
|
||||
Timestamp timestamp = tx.getTimestamp();
|
||||
@ -237,7 +240,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
@NotNull List<? extends State> inputs,
|
||||
@NotNull List<? extends State> outputs,
|
||||
@NotNull List<? extends AuthenticatedObject<? extends Commands>> commands,
|
||||
@NotNull State groupingKey) {
|
||||
State groupingKey) {
|
||||
AuthenticatedObject<Commands.Issue> cmd = requireSingleCommand(tx.getCommands(), Commands.Issue.class);
|
||||
State output = single(outputs);
|
||||
Timestamp timestampCommand = tx.getTimestamp();
|
||||
@ -304,7 +307,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
}
|
||||
|
||||
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary, Integer encumbrance) {
|
||||
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
|
||||
State state = new State(issuance, issuance.getParty(), faceValue, maturityDate);
|
||||
TransactionState output = new TransactionState<>(state, notary, encumbrance);
|
||||
return new TransactionType.General.Builder(notary).withItems(output, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
|
||||
}
|
||||
@ -317,12 +320,12 @@ public class JavaCommercialPaper implements Contract {
|
||||
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, VaultService vault) throws InsufficientBalanceException {
|
||||
vault.generateSpend(tx, StructuresKt.withoutIssuer(paper.getState().getData().getFaceValue()), paper.getState().getData().getOwner(), null);
|
||||
tx.addInputState(paper);
|
||||
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner()));
|
||||
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner().getOwningKey()));
|
||||
}
|
||||
|
||||
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
|
||||
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, AbstractParty newOwner) {
|
||||
tx.addInputState(paper);
|
||||
tx.addOutputState(new TransactionState<>(new State(paper.getState().getData().getIssuance(), newOwner, paper.getState().getData().getFaceValue(), paper.getState().getData().getMaturityDate()), paper.getState().getNotary(), paper.getState().getEncumbrance()));
|
||||
tx.addCommand(new Command(new Commands.Move(), paper.getState().getData().getOwner()));
|
||||
tx.addCommand(new Command(new Commands.Move(), paper.getState().getData().getOwner().getOwningKey()));
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.contracts.clauses.GroupClauseVerifier
|
||||
import net.corda.core.contracts.clauses.verifyClause
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.random63BitValue
|
||||
@ -19,7 +20,6 @@ import net.corda.core.schemas.QueryableState
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.Emoji
|
||||
import net.corda.schemas.CommercialPaperSchemaV1
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
@ -61,22 +61,22 @@ class CommercialPaper : Contract {
|
||||
|
||||
data class State(
|
||||
val issuance: PartyAndReference,
|
||||
override val owner: PublicKey,
|
||||
override val owner: AbstractParty,
|
||||
val faceValue: Amount<Issued<Currency>>,
|
||||
val maturityDate: Instant
|
||||
) : OwnableState, QueryableState, ICommercialPaperState {
|
||||
override val contract = CP_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = listOf(owner)
|
||||
|
||||
val token: Issued<Terms>
|
||||
get() = Issued(issuance, Terms(faceValue.token, maturityDate))
|
||||
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
override fun toString() = "${Emoji.newspaper}CommercialPaper(of $faceValue redeemable on $maturityDate by '$issuance', owned by $owner)"
|
||||
|
||||
// Although kotlin is smart enough not to need these, as we are using the ICommercialPaperState, we need to declare them explicitly for use later,
|
||||
override fun withOwner(newOwner: PublicKey): ICommercialPaperState = copy(owner = newOwner)
|
||||
override fun withOwner(newOwner: AbstractParty): ICommercialPaperState = copy(owner = newOwner)
|
||||
|
||||
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
|
||||
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
|
||||
@ -91,7 +91,7 @@ class CommercialPaper : Contract {
|
||||
is CommercialPaperSchemaV1 -> CommercialPaperSchemaV1.PersistentCommercialPaperState(
|
||||
issuanceParty = this.issuance.party.owningKey.toBase58String(),
|
||||
issuanceRef = this.issuance.reference.bytes,
|
||||
owner = this.owner.toBase58String(),
|
||||
owner = this.owner.owningKey.toBase58String(),
|
||||
maturity = this.maturityDate,
|
||||
faceValue = this.faceValue.quantity,
|
||||
currency = this.faceValue.token.product.currencyCode,
|
||||
@ -146,7 +146,7 @@ class CommercialPaper : Contract {
|
||||
val command = commands.requireSingleCommand<Commands.Move>()
|
||||
val input = inputs.single()
|
||||
requireThat {
|
||||
"the transaction is signed by the owner of the CP" using (input.owner in command.signers)
|
||||
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
|
||||
"the state is propagated" using (outputs.size == 1)
|
||||
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
||||
// the input ignoring the owner field due to the grouping.
|
||||
@ -175,7 +175,7 @@ class CommercialPaper : Contract {
|
||||
"the paper must have matured" using (time >= input.maturityDate)
|
||||
"the received amount equals the face value" using (received == input.faceValue)
|
||||
"the paper must be destroyed" using outputs.isEmpty()
|
||||
"the transaction is signed by the owner of the CP" using (input.owner in command.signers)
|
||||
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
|
||||
}
|
||||
|
||||
return setOf(command.value)
|
||||
@ -196,17 +196,17 @@ class CommercialPaper : Contract {
|
||||
* at the moment: this restriction is not fundamental and may be lifted later.
|
||||
*/
|
||||
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
|
||||
val state = TransactionState(State(issuance, issuance.party.owningKey, faceValue, maturityDate), notary)
|
||||
val state = TransactionState(State(issuance, issuance.party, faceValue, maturityDate), notary)
|
||||
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
|
||||
*/
|
||||
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) {
|
||||
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: AbstractParty) {
|
||||
tx.addInputState(paper)
|
||||
tx.addOutputState(TransactionState(paper.state.data.copy(owner = newOwner), paper.state.notary))
|
||||
tx.addCommand(Commands.Move(), paper.state.data.owner)
|
||||
tx.addCommand(Commands.Move(), paper.state.data.owner.owningKey)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,12 +223,12 @@ class CommercialPaper : Contract {
|
||||
val amount = paper.state.data.faceValue.let { amount -> Amount(amount.quantity, amount.token.product) }
|
||||
vault.generateSpend(tx, amount, paper.state.data.owner)
|
||||
tx.addInputState(paper)
|
||||
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner)
|
||||
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner.owningKey)
|
||||
}
|
||||
}
|
||||
|
||||
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = copy(owner = owner)
|
||||
infix fun CommercialPaper.State.`owned by`(owner: AbstractParty) = copy(owner = owner)
|
||||
infix fun CommercialPaper.State.`with notary`(notary: Party) = TransactionState(this, notary)
|
||||
infix fun ICommercialPaperState.`owned by`(newOwner: PublicKey) = withOwner(newOwner)
|
||||
infix fun ICommercialPaperState.`owned by`(newOwner: AbstractParty) = withOwner(newOwner)
|
||||
|
||||
|
||||
|
@ -3,13 +3,13 @@ package net.corda.contracts
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.sumCashBy
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.NullPublicKey
|
||||
import net.corda.core.crypto.NULL_PARTY
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.Emoji
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
@ -27,19 +27,19 @@ class CommercialPaperLegacy : Contract {
|
||||
|
||||
data class State(
|
||||
val issuance: PartyAndReference,
|
||||
override val owner: PublicKey,
|
||||
override val owner: AbstractParty,
|
||||
val faceValue: Amount<Issued<Currency>>,
|
||||
val maturityDate: Instant
|
||||
) : OwnableState, ICommercialPaperState {
|
||||
override val contract = CP_LEGACY_PROGRAM_ID
|
||||
override val participants = listOf(owner)
|
||||
|
||||
fun withoutOwner() = copy(owner = NullPublicKey)
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
fun withoutOwner() = copy(owner = NULL_PARTY)
|
||||
override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
override fun toString() = "${Emoji.newspaper}CommercialPaper(of $faceValue redeemable on $maturityDate by '$issuance', owned by $owner)"
|
||||
|
||||
// Although kotlin is smart enough not to need these, as we are using the ICommercialPaperState, we need to declare them explicitly for use later,
|
||||
override fun withOwner(newOwner: PublicKey): ICommercialPaperState = copy(owner = newOwner)
|
||||
override fun withOwner(newOwner: AbstractParty): ICommercialPaperState = copy(owner = newOwner)
|
||||
|
||||
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
|
||||
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
|
||||
@ -70,7 +70,7 @@ class CommercialPaperLegacy : Contract {
|
||||
is Commands.Move -> {
|
||||
val input = inputs.single()
|
||||
requireThat {
|
||||
"the transaction is signed by the owner of the CP" using (input.owner in command.signers)
|
||||
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
|
||||
"the state is propagated" using (outputs.size == 1)
|
||||
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
||||
// the input ignoring the owner field due to the grouping.
|
||||
@ -86,7 +86,7 @@ class CommercialPaperLegacy : Contract {
|
||||
"the paper must have matured" using (time >= input.maturityDate)
|
||||
"the received amount equals the face value" using (received == input.faceValue)
|
||||
"the paper must be destroyed" using outputs.isEmpty()
|
||||
"the transaction is signed by the owner of the CP" using (input.owner in command.signers)
|
||||
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,14 +114,14 @@ class CommercialPaperLegacy : Contract {
|
||||
|
||||
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant,
|
||||
notary: Party): TransactionBuilder {
|
||||
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate)
|
||||
val state = State(issuance, issuance.party, faceValue, maturityDate)
|
||||
return TransactionBuilder(notary = notary).withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
|
||||
}
|
||||
|
||||
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) {
|
||||
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: AbstractParty) {
|
||||
tx.addInputState(paper)
|
||||
tx.addOutputState(paper.state.data.withOwner(newOwner))
|
||||
tx.addCommand(Command(Commands.Move(), paper.state.data.owner))
|
||||
tx.addCommand(Command(Commands.Move(), paper.state.data.owner.owningKey))
|
||||
}
|
||||
|
||||
@Throws(InsufficientBalanceException::class)
|
||||
@ -130,6 +130,6 @@ class CommercialPaperLegacy : Contract {
|
||||
// Add the cash movement using the states in our vault.
|
||||
vault.generateSpend(tx, paper.state.data.faceValue.withoutIssuer(), paper.state.data.owner)
|
||||
tx.addInputState(paper)
|
||||
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner))
|
||||
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner.owningKey))
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import net.corda.core.utilities.Emoji
|
||||
import net.corda.schemas.CashSchemaV1
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import java.math.BigInteger
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -87,27 +86,27 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
|
||||
override val amount: Amount<Issued<Currency>>,
|
||||
|
||||
/** There must be a MoveCommand signed by this key to claim the amount. */
|
||||
override val owner: PublicKey
|
||||
override val owner: AbstractParty
|
||||
) : FungibleAsset<Currency>, QueryableState {
|
||||
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey)
|
||||
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: AbstractParty)
|
||||
: this(Amount(amount.quantity, Issued(deposit, amount.token)), owner)
|
||||
|
||||
override val exitKeys = setOf(owner, amount.token.issuer.party.owningKey)
|
||||
override val exitKeys = setOf(owner.owningKey, amount.token.issuer.party.owningKey)
|
||||
override val contract = CASH_PROGRAM_ID
|
||||
override val participants = listOf(owner)
|
||||
|
||||
override fun move(newAmount: Amount<Issued<Currency>>, newOwner: PublicKey): FungibleAsset<Currency>
|
||||
override fun move(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty): FungibleAsset<Currency>
|
||||
= copy(amount = amount.copy(newAmount.quantity), owner = newOwner)
|
||||
|
||||
override fun toString() = "${Emoji.bagOfCash}Cash($amount at ${amount.token.issuer} owned by $owner)"
|
||||
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
|
||||
/** Object Relational Mapping support. */
|
||||
override fun generateMappedObject(schema: MappedSchema): PersistentState {
|
||||
return when (schema) {
|
||||
is CashSchemaV1 -> CashSchemaV1.PersistentCashState(
|
||||
owner = this.owner.toBase58String(),
|
||||
owner = this.owner.owningKey.toBase58String(),
|
||||
pennies = this.amount.quantity,
|
||||
currency = this.amount.token.product.currencyCode,
|
||||
issuerParty = this.amount.token.issuer.party.owningKey.toBase58String(),
|
||||
@ -148,16 +147,16 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
|
||||
/**
|
||||
* Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.
|
||||
*/
|
||||
fun generateIssue(tx: TransactionBuilder, tokenDef: Issued<Currency>, pennies: Long, owner: PublicKey, notary: Party)
|
||||
fun generateIssue(tx: TransactionBuilder, tokenDef: Issued<Currency>, pennies: Long, owner: AbstractParty, notary: Party)
|
||||
= generateIssue(tx, Amount(pennies, tokenDef), owner, notary)
|
||||
|
||||
/**
|
||||
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
|
||||
*/
|
||||
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: PublicKey, notary: Party)
|
||||
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: AbstractParty, notary: Party)
|
||||
= generateIssue(tx, TransactionState(State(amount, owner), notary), generateIssueCommand())
|
||||
|
||||
override fun deriveState(txState: TransactionState<State>, amount: Amount<Issued<Currency>>, owner: PublicKey)
|
||||
override fun deriveState(txState: TransactionState<State>, amount: Amount<Issued<Currency>>, owner: AbstractParty)
|
||||
= txState.copy(data = txState.data.copy(amount = amount, owner = owner))
|
||||
|
||||
override fun generateExitCommand(amount: Amount<Issued<Currency>>) = Commands.Exit(amount)
|
||||
@ -175,7 +174,7 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
|
||||
* if there are none, or if any of the cash states cannot be added together (i.e. are
|
||||
* different currencies or issuers).
|
||||
*/
|
||||
fun Iterable<ContractState>.sumCashBy(owner: PublicKey): Amount<Issued<Currency>> = filterIsInstance<Cash.State>().filter { it.owner == owner }.map { it.amount }.sumOrThrow()
|
||||
fun Iterable<ContractState>.sumCashBy(owner: AbstractParty): Amount<Issued<Currency>> = filterIsInstance<Cash.State>().filter { it.owner == owner }.map { it.amount }.sumOrThrow()
|
||||
|
||||
/**
|
||||
* Sums the cash states in the list, throwing an exception if there are none, or if any of the cash
|
||||
@ -191,12 +190,12 @@ fun Iterable<ContractState>.sumCashOrZero(currency: Issued<Currency>): Amount<Is
|
||||
return filterIsInstance<Cash.State>().map { it.amount }.sumOrZero(currency)
|
||||
}
|
||||
|
||||
fun Cash.State.ownedBy(owner: PublicKey) = copy(owner = owner)
|
||||
fun Cash.State.issuedBy(party: AbstractParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party.toAnonymous()))))
|
||||
fun Cash.State.ownedBy(owner: AbstractParty) = copy(owner = owner)
|
||||
fun Cash.State.issuedBy(party: AbstractParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party))))
|
||||
fun Cash.State.issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = deposit)))
|
||||
fun Cash.State.withDeposit(deposit: PartyAndReference): Cash.State = copy(amount = amount.copy(token = amount.token.copy(issuer = deposit)))
|
||||
|
||||
infix fun Cash.State.`owned by`(owner: PublicKey) = ownedBy(owner)
|
||||
infix fun Cash.State.`owned by`(owner: AbstractParty) = ownedBy(owner)
|
||||
infix fun Cash.State.`issued by`(party: AbstractParty) = issuedBy(party)
|
||||
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = withDeposit(deposit)
|
||||
@ -208,6 +207,6 @@ val DUMMY_CASH_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) }
|
||||
/** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */
|
||||
val DUMMY_CASH_ISSUER by lazy { Party(X500Name("CN=Snake Oil Issuer,O=R3,OU=corda,L=London,C=UK"), DUMMY_CASH_ISSUER_KEY.public).ref(1) }
|
||||
/** An extension property that lets you write 100.DOLLARS.CASH */
|
||||
val Amount<Currency>.CASH: Cash.State get() = Cash.State(Amount(quantity, Issued(DUMMY_CASH_ISSUER, token)), NullPublicKey)
|
||||
/** An extension property that lets you get a cash state from an issued token, under the [NullPublicKey] */
|
||||
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey)
|
||||
val Amount<Currency>.CASH: Cash.State get() = Cash.State(Amount(quantity, Issued(DUMMY_CASH_ISSUER, token)), NULL_PARTY)
|
||||
/** An extension property that lets you get a cash state from an issued token, under the [NULL_PARTY] */
|
||||
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NULL_PARTY)
|
||||
|
@ -9,10 +9,10 @@ import net.corda.core.contracts.clauses.GroupClauseVerifier
|
||||
import net.corda.core.contracts.clauses.verifyClause
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -94,21 +94,21 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
|
||||
override val amount: Amount<Issued<Commodity>>,
|
||||
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
override val owner: PublicKey
|
||||
override val owner: AbstractParty
|
||||
) : FungibleAsset<Commodity> {
|
||||
constructor(deposit: PartyAndReference, amount: Amount<Commodity>, owner: PublicKey)
|
||||
constructor(deposit: PartyAndReference, amount: Amount<Commodity>, owner: AbstractParty)
|
||||
: this(Amount(amount.quantity, Issued(deposit, amount.token)), owner)
|
||||
|
||||
override val contract = COMMODITY_PROGRAM_ID
|
||||
override val exitKeys = Collections.singleton(owner)
|
||||
override val exitKeys = Collections.singleton(owner.owningKey)
|
||||
override val participants = listOf(owner)
|
||||
|
||||
override fun move(newAmount: Amount<Issued<Commodity>>, newOwner: PublicKey): FungibleAsset<Commodity>
|
||||
override fun move(newAmount: Amount<Issued<Commodity>>, newOwner: AbstractParty): FungibleAsset<Commodity>
|
||||
= copy(amount = amount.copy(newAmount.quantity), owner = newOwner)
|
||||
|
||||
override fun toString() = "Commodity($amount at ${amount.token.issuer} owned by $owner)"
|
||||
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
// Just for grouping
|
||||
@ -145,17 +145,17 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
|
||||
/**
|
||||
* Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.
|
||||
*/
|
||||
fun generateIssue(tx: TransactionBuilder, tokenDef: Issued<Commodity>, pennies: Long, owner: PublicKey, notary: Party)
|
||||
fun generateIssue(tx: TransactionBuilder, tokenDef: Issued<Commodity>, pennies: Long, owner: AbstractParty, notary: Party)
|
||||
= generateIssue(tx, Amount(pennies, tokenDef), owner, notary)
|
||||
|
||||
/**
|
||||
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
|
||||
*/
|
||||
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Commodity>>, owner: PublicKey, notary: Party)
|
||||
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Commodity>>, owner: AbstractParty, notary: Party)
|
||||
= generateIssue(tx, TransactionState(State(amount, owner), notary), generateIssueCommand())
|
||||
|
||||
|
||||
override fun deriveState(txState: TransactionState<State>, amount: Amount<Issued<Commodity>>, owner: PublicKey)
|
||||
override fun deriveState(txState: TransactionState<State>, amount: Amount<Issued<Commodity>>, owner: AbstractParty)
|
||||
= txState.copy(data = txState.data.copy(amount = amount, owner = owner))
|
||||
|
||||
override fun generateExitCommand(amount: Amount<Issued<Commodity>>) = Commands.Exit(amount)
|
||||
|
@ -5,7 +5,9 @@ import net.corda.contracts.asset.Obligation.Lifecycle.NORMAL
|
||||
import net.corda.contracts.clause.*
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.clauses.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.NULL_PARTY
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
@ -22,6 +24,36 @@ import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import kotlin.collections.Collection
|
||||
import kotlin.collections.Iterable
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.Map
|
||||
import kotlin.collections.Set
|
||||
import kotlin.collections.all
|
||||
import kotlin.collections.asIterable
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.contains
|
||||
import kotlin.collections.distinct
|
||||
import kotlin.collections.emptySet
|
||||
import kotlin.collections.filter
|
||||
import kotlin.collections.filterIsInstance
|
||||
import kotlin.collections.first
|
||||
import kotlin.collections.firstOrNull
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.groupBy
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.collections.iterator
|
||||
import kotlin.collections.listOf
|
||||
import kotlin.collections.map
|
||||
import kotlin.collections.none
|
||||
import kotlin.collections.reduce
|
||||
import kotlin.collections.set
|
||||
import kotlin.collections.setOf
|
||||
import kotlin.collections.single
|
||||
import kotlin.collections.toSet
|
||||
import kotlin.collections.union
|
||||
import kotlin.collections.withIndex
|
||||
|
||||
// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
|
||||
val OBLIGATION_PROGRAM_ID = Obligation<Currency>()
|
||||
@ -274,27 +306,20 @@ class Obligation<P : Any> : Contract {
|
||||
data class State<P : Any>(
|
||||
var lifecycle: Lifecycle = Lifecycle.NORMAL,
|
||||
/** Where the debt originates from (obligor) */
|
||||
val obligor: AnonymousParty,
|
||||
val obligor: AbstractParty,
|
||||
val template: Terms<P>,
|
||||
val quantity: Long,
|
||||
/** The public key of the entity the contract pays to */
|
||||
val beneficiary: PublicKey
|
||||
val beneficiary: AbstractParty
|
||||
) : FungibleAsset<Obligation.Terms<P>>, NettableState<State<P>, MultilateralNetState<P>> {
|
||||
constructor(lifecycle: Lifecycle = Lifecycle.NORMAL,
|
||||
obligor: Party,
|
||||
template: Terms<P>,
|
||||
quantity: Long,
|
||||
beneficiary: PublicKey)
|
||||
: this(lifecycle, obligor.toAnonymous(), template, quantity, beneficiary)
|
||||
|
||||
override val amount: Amount<Issued<Terms<P>>> = Amount(quantity, Issued(obligor.ref(0), template))
|
||||
override val contract = OBLIGATION_PROGRAM_ID
|
||||
override val exitKeys: Collection<PublicKey> = setOf(beneficiary)
|
||||
override val exitKeys: Collection<PublicKey> = setOf(beneficiary.owningKey)
|
||||
val dueBefore: Instant = template.dueBefore
|
||||
override val participants: List<PublicKey> = listOf(obligor.owningKey, beneficiary)
|
||||
override val owner: PublicKey = beneficiary
|
||||
override val participants: List<AbstractParty> = listOf(obligor, beneficiary)
|
||||
override val owner: AbstractParty = beneficiary
|
||||
|
||||
override fun move(newAmount: Amount<Issued<Terms<P>>>, newOwner: PublicKey): State<P>
|
||||
override fun move(newAmount: Amount<Issued<Terms<P>>>, newOwner: AbstractParty): State<P>
|
||||
= copy(quantity = newAmount.quantity, beneficiary = newOwner)
|
||||
|
||||
override fun toString() = when (lifecycle) {
|
||||
@ -305,7 +330,7 @@ class Obligation<P : Any> : Contract {
|
||||
override val bilateralNetState: BilateralNetState<P>
|
||||
get() {
|
||||
check(lifecycle == Lifecycle.NORMAL)
|
||||
return BilateralNetState(setOf(obligor.owningKey, beneficiary), template)
|
||||
return BilateralNetState(setOf(obligor, beneficiary), template)
|
||||
}
|
||||
override val multilateralNetState: MultilateralNetState<P>
|
||||
get() {
|
||||
@ -327,7 +352,7 @@ class Obligation<P : Any> : Contract {
|
||||
}
|
||||
}
|
||||
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(beneficiary = newOwner))
|
||||
override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Move(), copy(beneficiary = newOwner))
|
||||
}
|
||||
|
||||
// Just for grouping
|
||||
@ -418,7 +443,7 @@ class Obligation<P : Any> : Contract {
|
||||
}
|
||||
}
|
||||
}
|
||||
val owningPubKeys = inputs.filter { it is State<P> }.map { (it as State<P>).beneficiary }.toSet()
|
||||
val owningPubKeys = inputs.filter { it is State<P> }.map { (it as State<P>).beneficiary.owningKey }.toSet()
|
||||
val keysThatSigned = setLifecycleCommand.signers.toSet()
|
||||
requireThat {
|
||||
"the owning keys are a subset of the signing keys" using keysThatSigned.containsAll(owningPubKeys)
|
||||
@ -433,7 +458,7 @@ class Obligation<P : Any> : Contract {
|
||||
* and same parties involved).
|
||||
*/
|
||||
fun generateCloseOutNetting(tx: TransactionBuilder,
|
||||
signer: PublicKey,
|
||||
signer: AbstractParty,
|
||||
vararg states: State<P>) {
|
||||
val netState = states.firstOrNull()?.bilateralNetState
|
||||
|
||||
@ -447,7 +472,7 @@ class Obligation<P : Any> : Contract {
|
||||
val out = states.reduce(State<P>::net)
|
||||
if (out.quantity > 0L)
|
||||
tx.addOutputState(out)
|
||||
tx.addCommand(Commands.Net(NetType.PAYMENT), signer)
|
||||
tx.addCommand(Commands.Net(NetType.PAYMENT), signer.owningKey)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -475,9 +500,9 @@ class Obligation<P : Any> : Contract {
|
||||
obligor: AbstractParty,
|
||||
issuanceDef: Terms<P>,
|
||||
pennies: Long,
|
||||
beneficiary: PublicKey,
|
||||
beneficiary: AbstractParty,
|
||||
notary: Party)
|
||||
= OnLedgerAsset.generateIssue(tx, TransactionState(State(Lifecycle.NORMAL, obligor.toAnonymous(), issuanceDef, pennies, beneficiary), notary), Commands.Issue())
|
||||
= OnLedgerAsset.generateIssue(tx, TransactionState(State(Lifecycle.NORMAL, obligor, issuanceDef, pennies, beneficiary), notary), Commands.Issue())
|
||||
|
||||
fun generatePaymentNetting(tx: TransactionBuilder,
|
||||
issued: Issued<Obligation.Terms<P>>,
|
||||
@ -487,8 +512,8 @@ class Obligation<P : Any> : Contract {
|
||||
"all states are in the normal lifecycle state " using (states.all { it.lifecycle == Lifecycle.NORMAL })
|
||||
}
|
||||
val groups = states.groupBy { it.multilateralNetState }
|
||||
val partyLookup = HashMap<PublicKey, AnonymousParty>()
|
||||
val signers = states.map { it.beneficiary }.union(states.map { it.obligor.owningKey }).toSet()
|
||||
val partyLookup = HashMap<PublicKey, AbstractParty>()
|
||||
val signers = states.map { it.beneficiary }.union(states.map { it.obligor }).toSet()
|
||||
|
||||
// Create a lookup table of the party that each public key represents.
|
||||
states.map { it.obligor }.forEach { partyLookup.put(it.owningKey, it) }
|
||||
@ -502,12 +527,12 @@ class Obligation<P : Any> : Contract {
|
||||
netBalances
|
||||
// Convert the balances into obligation state objects
|
||||
.map { entry ->
|
||||
State(Lifecycle.NORMAL, partyLookup[entry.key.first]!!,
|
||||
State(Lifecycle.NORMAL, entry.key.first,
|
||||
netState.template, entry.value.quantity, entry.key.second)
|
||||
}
|
||||
// Add the new states to the TX
|
||||
.forEach { tx.addOutputState(it, notary) }
|
||||
tx.addCommand(Commands.Net(NetType.PAYMENT), signers.toList())
|
||||
tx.addCommand(Commands.Net(NetType.PAYMENT), signers.map { it.owningKey })
|
||||
}
|
||||
|
||||
}
|
||||
@ -533,14 +558,14 @@ class Obligation<P : Any> : Contract {
|
||||
// Produce a new set of states
|
||||
val groups = statesAndRefs.groupBy { it.state.data.amount.token }
|
||||
for ((_, stateAndRefs) in groups) {
|
||||
val partiesUsed = ArrayList<PublicKey>()
|
||||
val partiesUsed = ArrayList<AbstractParty>()
|
||||
stateAndRefs.forEach { stateAndRef ->
|
||||
val outState = stateAndRef.state.data.copy(lifecycle = lifecycle)
|
||||
tx.addInputState(stateAndRef)
|
||||
tx.addOutputState(outState, notary)
|
||||
partiesUsed.add(stateAndRef.state.data.beneficiary)
|
||||
}
|
||||
tx.addCommand(Commands.SetLifecycle(lifecycle), partiesUsed.distinct())
|
||||
tx.addCommand(Commands.SetLifecycle(lifecycle), partiesUsed.map { it.owningKey }.distinct())
|
||||
}
|
||||
tx.setTime(issuanceDef.dueBefore, issuanceDef.timeTolerance)
|
||||
}
|
||||
@ -578,7 +603,7 @@ class Obligation<P : Any> : Contract {
|
||||
val template: Terms<P> = issuanceDef.product
|
||||
val obligationTotal: Amount<P> = Amount(states.map { it.data }.sumObligations<P>().quantity, template.product)
|
||||
var obligationRemaining: Amount<P> = obligationTotal
|
||||
val assetSigners = HashSet<PublicKey>()
|
||||
val assetSigners = HashSet<AbstractParty>()
|
||||
|
||||
statesAndRefs.forEach { tx.addInputState(it) }
|
||||
|
||||
@ -611,7 +636,7 @@ class Obligation<P : Any> : Contract {
|
||||
}
|
||||
|
||||
// Add the asset move command and obligation settle
|
||||
tx.addCommand(moveCommand, assetSigners.toList())
|
||||
tx.addCommand(moveCommand, assetSigners.map { it.owningKey })
|
||||
tx.addCommand(Commands.Settle(Amount((obligationTotal - obligationRemaining).quantity, issuanceDef)), obligationIssuer.owningKey)
|
||||
}
|
||||
|
||||
@ -630,11 +655,11 @@ class Obligation<P : Any> : Contract {
|
||||
*
|
||||
* @return a map of obligor/beneficiary pairs to the balance due.
|
||||
*/
|
||||
fun <P : Any> extractAmountsDue(product: Obligation.Terms<P>, states: Iterable<Obligation.State<P>>): Map<Pair<PublicKey, PublicKey>, Amount<Obligation.Terms<P>>> {
|
||||
val balances = HashMap<Pair<PublicKey, PublicKey>, Amount<Obligation.Terms<P>>>()
|
||||
fun <P : Any> extractAmountsDue(product: Obligation.Terms<P>, states: Iterable<Obligation.State<P>>): Map<Pair<AbstractParty, AbstractParty>, Amount<Obligation.Terms<P>>> {
|
||||
val balances = HashMap<Pair<AbstractParty, AbstractParty>, Amount<Obligation.Terms<P>>>()
|
||||
|
||||
states.forEach { state ->
|
||||
val key = Pair(state.obligor.owningKey, state.beneficiary)
|
||||
val key = Pair(state.obligor, state.beneficiary)
|
||||
val balance = balances[key] ?: Amount(0L, product)
|
||||
balances[key] = balance + Amount(state.amount.quantity, state.amount.token.product)
|
||||
}
|
||||
@ -645,8 +670,8 @@ fun <P : Any> extractAmountsDue(product: Obligation.Terms<P>, states: Iterable<O
|
||||
/**
|
||||
* Net off the amounts due between parties.
|
||||
*/
|
||||
fun <P : Any> netAmountsDue(balances: Map<Pair<PublicKey, PublicKey>, Amount<P>>): Map<Pair<PublicKey, PublicKey>, Amount<P>> {
|
||||
val nettedBalances = HashMap<Pair<PublicKey, PublicKey>, Amount<P>>()
|
||||
fun <P: AbstractParty, T : Any> netAmountsDue(balances: Map<Pair<P, P>, Amount<T>>): Map<Pair<P, P>, Amount<T>> {
|
||||
val nettedBalances = HashMap<Pair<P, P>, Amount<T>>()
|
||||
|
||||
balances.forEach { balance ->
|
||||
val (obligor, beneficiary) = balance.key
|
||||
@ -669,9 +694,11 @@ fun <P : Any> netAmountsDue(balances: Map<Pair<PublicKey, PublicKey>, Amount<P>>
|
||||
*
|
||||
* @param balances payments due, indexed by obligor and beneficiary. Zero balances are stripped from the map before being
|
||||
* returned.
|
||||
* @param P type of party to operate on.
|
||||
* @param T token that balances represent
|
||||
*/
|
||||
fun <P : Any> sumAmountsDue(balances: Map<Pair<PublicKey, PublicKey>, Amount<P>>): Map<PublicKey, Long> {
|
||||
val sum = HashMap<PublicKey, Long>()
|
||||
fun <P: AbstractParty, T : Any> sumAmountsDue(balances: Map<Pair<P, P>, Amount<T>>): Map<P, Long> {
|
||||
val sum = HashMap<P, Long>()
|
||||
|
||||
// Fill the map with zeroes initially
|
||||
balances.keys.forEach {
|
||||
@ -712,11 +739,11 @@ fun <P : Any> Iterable<ContractState>.sumObligationsOrZero(issuanceDef: Issued<O
|
||||
= filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(issuanceDef)
|
||||
|
||||
infix fun <T : Any> Obligation.State<T>.at(dueBefore: Instant) = copy(template = template.copy(dueBefore = dueBefore))
|
||||
infix fun <T : Any> Obligation.State<T>.between(parties: Pair<AbstractParty, PublicKey>) = copy(obligor = parties.first.toAnonymous(), beneficiary = parties.second)
|
||||
infix fun <T : Any> Obligation.State<T>.`owned by`(owner: PublicKey) = copy(beneficiary = owner)
|
||||
infix fun <T : Any> Obligation.State<T>.`issued by`(party: AbstractParty) = copy(obligor = party.toAnonymous())
|
||||
infix fun <T : Any> Obligation.State<T>.between(parties: Pair<AbstractParty, AbstractParty>) = copy(obligor = parties.first, beneficiary = parties.second)
|
||||
infix fun <T : Any> Obligation.State<T>.`owned by`(owner: AbstractParty) = copy(beneficiary = owner)
|
||||
infix fun <T : Any> Obligation.State<T>.`issued by`(party: AbstractParty) = copy(obligor = party)
|
||||
// For Java users:
|
||||
@Suppress("unused") fun <T : Any> Obligation.State<T>.ownedBy(owner: PublicKey) = copy(beneficiary = owner)
|
||||
@Suppress("unused") fun <T : Any> Obligation.State<T>.ownedBy(owner: AbstractParty) = copy(beneficiary = owner)
|
||||
|
||||
@Suppress("unused") fun <T : Any> Obligation.State<T>.issuedBy(party: AnonymousParty) = copy(obligor = party)
|
||||
|
||||
@ -728,4 +755,4 @@ val DUMMY_OBLIGATION_ISSUER by lazy { Party(X500Name("CN=Snake Oil Issuer,O=R3,O
|
||||
val Issued<Currency>.OBLIGATION_DEF: Obligation.Terms<Currency>
|
||||
get() = Obligation.Terms(nonEmptySetOf(Cash().legalContractReference), nonEmptySetOf(this), TEST_TX_TIME)
|
||||
val Amount<Issued<Currency>>.OBLIGATION: Obligation.State<Currency>
|
||||
get() = Obligation.State(Obligation.Lifecycle.NORMAL, DUMMY_OBLIGATION_ISSUER.toAnonymous(), token.OBLIGATION_DEF, quantity, NullPublicKey)
|
||||
get() = Obligation.State(Obligation.Lifecycle.NORMAL, DUMMY_OBLIGATION_ISSUER, token.OBLIGATION_DEF, quantity, NULL_PARTY)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.contracts.asset
|
||||
|
||||
import net.corda.contracts.clause.AbstractConserveAmount
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
@ -51,9 +51,9 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
|
||||
@JvmStatic
|
||||
fun <S : FungibleAsset<T>, T: Any> generateSpend(tx: TransactionBuilder,
|
||||
amount: Amount<T>,
|
||||
to: PublicKey,
|
||||
to: AbstractParty,
|
||||
acceptableStates: List<StateAndRef<S>>,
|
||||
deriveState: (TransactionState<S>, Amount<Issued<T>>, PublicKey) -> TransactionState<S>,
|
||||
deriveState: (TransactionState<S>, Amount<Issued<T>>, AbstractParty) -> TransactionState<S>,
|
||||
generateMoveCommand: () -> CommandData): Pair<TransactionBuilder, List<PublicKey>> {
|
||||
// Discussion
|
||||
//
|
||||
@ -90,7 +90,7 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val keysUsed = gathered.map { it.state.data.owner }
|
||||
val keysUsed = gathered.map { it.state.data.owner.owningKey }
|
||||
|
||||
val states = gathered.groupBy { it.state.data.amount.token.issuer }.map {
|
||||
val coins = it.value
|
||||
@ -165,7 +165,7 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
|
||||
@JvmStatic
|
||||
fun <S : FungibleAsset<T>, T: Any> generateExit(tx: TransactionBuilder, amountIssued: Amount<Issued<T>>,
|
||||
assetStates: List<StateAndRef<S>>,
|
||||
deriveState: (TransactionState<S>, Amount<Issued<T>>, PublicKey) -> TransactionState<S>,
|
||||
deriveState: (TransactionState<S>, Amount<Issued<T>>, AbstractParty) -> TransactionState<S>,
|
||||
generateMoveCommand: () -> CommandData,
|
||||
generateExitCommand: (Amount<Issued<T>>) -> CommandData): PublicKey {
|
||||
val owner = assetStates.map { it.state.data.owner }.toSet().singleOrNull() ?: throw InsufficientBalanceException(amountIssued)
|
||||
@ -193,7 +193,7 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
|
||||
|
||||
for (state in gathered) tx.addInputState(state)
|
||||
for (state in outputs) tx.addOutputState(state)
|
||||
tx.addCommand(generateMoveCommand(), gathered.map { it.state.data.owner })
|
||||
tx.addCommand(generateMoveCommand(), gathered.map { it.state.data.owner.owningKey })
|
||||
tx.addCommand(generateExitCommand(amountIssued), gathered.flatMap { it.state.data.exitKeys })
|
||||
return amountIssued.token.issuer.party.owningKey
|
||||
}
|
||||
@ -250,5 +250,5 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
|
||||
* implementations to have fields in their state which we don't know about here, and we simply leave them untouched
|
||||
* when sending out "change" from spending/exiting.
|
||||
*/
|
||||
abstract fun deriveState(txState: TransactionState<S>, amount: Amount<Issued<T>>, owner: PublicKey): TransactionState<S>
|
||||
abstract fun deriveState(txState: TransactionState<S>, amount: Amount<Issued<T>>, owner: AbstractParty): TransactionState<S>
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package net.corda.contracts.clause
|
||||
import net.corda.contracts.asset.OnLedgerAsset
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.clauses.Clause
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import java.security.PublicKey
|
||||
@ -31,7 +32,7 @@ abstract class AbstractConserveAmount<S : FungibleAsset<T>, C : CommandData, T :
|
||||
@Throws(InsufficientBalanceException::class)
|
||||
fun generateExit(tx: TransactionBuilder, amountIssued: Amount<Issued<T>>,
|
||||
assetStates: List<StateAndRef<S>>,
|
||||
deriveState: (TransactionState<S>, Amount<Issued<T>>, PublicKey) -> TransactionState<S>,
|
||||
deriveState: (TransactionState<S>, Amount<Issued<T>>, AbstractParty) -> TransactionState<S>,
|
||||
generateMoveCommand: () -> CommandData,
|
||||
generateExitCommand: (Amount<Issued<T>>) -> CommandData): PublicKey
|
||||
= OnLedgerAsset.generateExit(tx, amountIssued, assetStates, deriveState, generateMoveCommand, generateExitCommand)
|
||||
|
@ -6,6 +6,7 @@ import net.corda.contracts.asset.extractAmountsDue
|
||||
import net.corda.contracts.asset.sumAmountsDue
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.clauses.Clause
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
@ -22,7 +23,7 @@ interface NetState<P : Any> {
|
||||
* Bilateral states are used in close-out netting.
|
||||
*/
|
||||
data class BilateralNetState<P : Any>(
|
||||
val partyKeys: Set<PublicKey>,
|
||||
val partyKeys: Set<AbstractParty>,
|
||||
override val template: Obligation.Terms<P>
|
||||
) : NetState<P>
|
||||
|
||||
@ -86,7 +87,7 @@ open class NetClause<C : CommandData, P : Any> : Clause<ContractState, C, Unit>(
|
||||
}
|
||||
|
||||
// TODO: Handle proxies nominated by parties, i.e. a central clearing service
|
||||
val involvedParties = inputs.map { it.beneficiary }.union(inputs.map { it.obligor.owningKey }).toSet()
|
||||
val involvedParties: Set<PublicKey> = inputs.map { it.beneficiary.owningKey }.union(inputs.map { it.obligor.owningKey }).toSet()
|
||||
when (command.value.type) {
|
||||
// For close-out netting, allow any involved party to sign
|
||||
NetType.CLOSE_OUT -> require(command.signers.intersect(involvedParties).isNotEmpty()) { "any involved party has signed" }
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.contracts.DealState
|
||||
import net.corda.core.contracts.TransactionForContract
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
@ -17,12 +18,12 @@ class DummyDealContract : Contract {
|
||||
|
||||
data class State(
|
||||
override val contract: Contract = DummyDealContract(),
|
||||
override val participants: List<PublicKey> = listOf(),
|
||||
override val participants: List<AbstractParty> = listOf(),
|
||||
override val linearId: UniqueIdentifier = UniqueIdentifier(),
|
||||
override val ref: String,
|
||||
override val parties: List<AnonymousParty> = listOf()) : DealState {
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||
return participants.any { it.containsAny(ourKeys) }
|
||||
return participants.any { it.owningKey.containsAny(ourKeys) }
|
||||
}
|
||||
|
||||
override fun generateAgreement(notary: Party): TransactionBuilder {
|
||||
|
@ -4,9 +4,9 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.clauses.Clause
|
||||
import net.corda.core.contracts.clauses.FilterOn
|
||||
import net.corda.core.contracts.clauses.verifyClause
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.containsAny
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import java.security.PublicKey
|
||||
|
||||
class DummyLinearContract : Contract {
|
||||
@ -20,11 +20,11 @@ class DummyLinearContract : Contract {
|
||||
data class State(
|
||||
override val linearId: UniqueIdentifier = UniqueIdentifier(),
|
||||
override val contract: Contract = DummyLinearContract(),
|
||||
override val participants: List<PublicKey> = listOf(),
|
||||
override val participants: List<AbstractParty> = listOf(),
|
||||
val nonce: SecureHash = SecureHash.randomSHA256()) : LinearState {
|
||||
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||
return participants.any { it.containsAny(ourKeys) }
|
||||
return participants.any { it.owningKey.containsAny(ourKeys) }
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,8 @@ import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.Vault
|
||||
@ -21,13 +22,15 @@ import java.util.*
|
||||
@JvmOverloads
|
||||
fun ServiceHub.fillWithSomeTestDeals(dealIds: List<String>,
|
||||
revisions: Int? = 0,
|
||||
participants: List<PublicKey> = emptyList()) : Vault<DealState> {
|
||||
participants: List<AbstractParty> = emptyList()) : Vault<DealState> {
|
||||
val freshKey = keyManagementService.freshKey()
|
||||
val freshPublicKey = freshKey.public
|
||||
val recipient = AnonymousParty(freshPublicKey)
|
||||
|
||||
val transactions: List<SignedTransaction> = dealIds.map {
|
||||
// Issue a deal state
|
||||
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||
addOutputState(DummyDealContract.State(ref = it, participants = participants.plus(freshKey.public)))
|
||||
addOutputState(DummyDealContract.State(ref = it, participants = participants.plus(recipient)))
|
||||
signWith(freshKey)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}
|
||||
@ -47,13 +50,15 @@ fun ServiceHub.fillWithSomeTestDeals(dealIds: List<String>,
|
||||
@JvmOverloads
|
||||
fun ServiceHub.fillWithSomeTestLinearStates(numberToCreate: Int,
|
||||
uid: UniqueIdentifier = UniqueIdentifier(),
|
||||
participants: List<PublicKey> = emptyList()) : Vault<LinearState> {
|
||||
participants: List<AbstractParty> = emptyList()) : Vault<LinearState> {
|
||||
val freshKey = keyManagementService.freshKey()
|
||||
val freshPublicKey = freshKey.public
|
||||
val recipient = AnonymousParty(freshPublicKey)
|
||||
|
||||
val transactions: List<SignedTransaction> = (1..numberToCreate).map {
|
||||
// Issue a Linear state
|
||||
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||
addOutputState(DummyLinearContract.State(linearId = uid, participants = participants.plus(freshKey.public)))
|
||||
addOutputState(DummyLinearContract.State(linearId = uid, participants = participants.plus(recipient)))
|
||||
signWith(freshKey)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}
|
||||
@ -87,18 +92,19 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>,
|
||||
atMostThisManyStates: Int = 10,
|
||||
rng: Random = Random(),
|
||||
ref: OpaqueBytes = OpaqueBytes(ByteArray(1, { 1 })),
|
||||
ownedBy: PublicKey? = null,
|
||||
ownedBy: AbstractParty? = null,
|
||||
issuedBy: PartyAndReference = DUMMY_CASH_ISSUER,
|
||||
issuerKey: KeyPair = DUMMY_CASH_ISSUER_KEY): Vault<Cash.State> {
|
||||
val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng)
|
||||
|
||||
val myKey: PublicKey = ownedBy ?: myInfo.legalIdentity.owningKey
|
||||
val myKey: PublicKey = ownedBy?.owningKey ?: myInfo.legalIdentity.owningKey
|
||||
val me = AnonymousParty(myKey)
|
||||
|
||||
// We will allocate one state to one transaction, for simplicities sake.
|
||||
val cash = Cash()
|
||||
val transactions: List<SignedTransaction> = amounts.map { pennies ->
|
||||
val issuance = TransactionType.General.Builder(null)
|
||||
cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy.copy(reference = ref), howMuch.token)), myKey, outputNotary)
|
||||
cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy.copy(reference = ref), howMuch.token)), me, outputNotary)
|
||||
issuance.signWith(issuerKey)
|
||||
|
||||
return@map issuance.toSignedTransaction(true)
|
||||
|
@ -60,7 +60,7 @@ class CashExitFlow(val amount: Amount<Currency>, val issueRef: OpaqueBytes, prog
|
||||
// count as a reason to fail?
|
||||
val participants: Set<Party> = inputStates
|
||||
.filterIsInstance<Cash.State>()
|
||||
.map { serviceHub.identityService.partyFromKey(it.owner) }
|
||||
.map { serviceHub.identityService.partyFromAnonymous(it.owner) }
|
||||
.filterNotNull()
|
||||
.toSet()
|
||||
|
||||
|
@ -37,7 +37,8 @@ class CashIssueFlow(val amount: Amount<Currency>,
|
||||
progressTracker.currentStep = GENERATING_TX
|
||||
val builder: TransactionBuilder = TransactionType.General.Builder(notary = null)
|
||||
val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef)
|
||||
Cash().generateIssue(builder, amount.issuedBy(issuer), recipient.owningKey, notary)
|
||||
// TODO: Get a transaction key, don't just re-use the owning key
|
||||
Cash().generateIssue(builder, amount.issuedBy(issuer), recipient, notary)
|
||||
progressTracker.currentStep = SIGNING_TX
|
||||
val myKey = serviceHub.legalIdentityKey
|
||||
builder.signWith(myKey)
|
||||
|
@ -39,7 +39,8 @@ open class CashPaymentFlow(
|
||||
serviceHub.vaultService.generateSpend(
|
||||
builder,
|
||||
amount,
|
||||
recipient.owningKey,
|
||||
// TODO: Get a transaction key, don't just re-use the owning key
|
||||
recipient,
|
||||
issuerConstraint)
|
||||
} catch (e: InsufficientBalanceException) {
|
||||
throw CashException("Insufficient cash for spend: ${e.message}", e)
|
||||
|
@ -8,6 +8,7 @@ import net.corda.core.crypto.expandedCompositeKeys
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.seconds
|
||||
@ -34,7 +35,7 @@ import java.util.*
|
||||
* 3. S signs it and commits it to the ledger, notarising it and distributing the final signed transaction back
|
||||
* to B.
|
||||
*
|
||||
* Assuming no malicious termination, they both end the flow being in posession of a valid, signed transaction
|
||||
* Assuming no malicious termination, they both end the flow being in possession of a valid, signed transaction
|
||||
* that represents an atomic asset swap.
|
||||
*
|
||||
* Note that it's the *seller* who initiates contact with the buyer, not vice-versa as you might imagine.
|
||||
@ -118,7 +119,7 @@ object TwoPartyTradeFlow {
|
||||
// even though it is missing signatures.
|
||||
subFlow(ResolveTransactionsFlow(wtx, otherParty))
|
||||
|
||||
if (wtx.outputs.map { it.data }.sumCashBy(myPublicKey).withoutIssuer() != price)
|
||||
if (wtx.outputs.map { it.data }.sumCashBy(AnonymousParty(myPublicKey)).withoutIssuer() != price)
|
||||
throw FlowException("Transaction is not sending us the right amount of cash")
|
||||
|
||||
it
|
||||
@ -219,7 +220,7 @@ object TwoPartyTradeFlow {
|
||||
val ptx = TransactionType.General.Builder(notary)
|
||||
|
||||
// Add input and output states for the movement of cash, by using the Cash contract to generate the states
|
||||
val (tx, cashSigningPubKeys) = serviceHub.vaultService.generateSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey)
|
||||
val (tx, cashSigningPubKeys) = serviceHub.vaultService.generateSpend(ptx, tradeRequest.price, AnonymousParty(tradeRequest.sellerOwnerKey))
|
||||
|
||||
// Add inputs/outputs/a command for the movement of the asset.
|
||||
tx.addInputState(tradeRequest.assetForSale)
|
||||
@ -228,10 +229,10 @@ object TwoPartyTradeFlow {
|
||||
// we want for privacy reasons: the key is here ONLY to manage and control ownership, it is not intended to
|
||||
// reveal who the owner actually is. The key management service is expected to derive a unique key from some
|
||||
// initial seed in order to provide privacy protection.
|
||||
val freshKey = serviceHub.keyManagementService.freshKey()
|
||||
val (command, state) = tradeRequest.assetForSale.state.data.withNewOwner(freshKey.public)
|
||||
val freshPublicKey = serviceHub.keyManagementService.freshKey().public
|
||||
val (command, state) = tradeRequest.assetForSale.state.data.withNewOwner(AnonymousParty(freshPublicKey))
|
||||
tx.addOutputState(state, tradeRequest.assetForSale.state.notary)
|
||||
tx.addCommand(command, tradeRequest.assetForSale.state.data.owner)
|
||||
tx.addCommand(command, tradeRequest.assetForSale.state.data.owner.owningKey)
|
||||
|
||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||
// to have one.
|
||||
|
@ -1,12 +1,15 @@
|
||||
package net.corda.contracts.asset;
|
||||
|
||||
import kotlin.*;
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.serialization.*;
|
||||
import org.junit.*;
|
||||
import kotlin.Unit;
|
||||
import net.corda.core.contracts.PartyAndReference;
|
||||
import net.corda.core.identity.AnonymousParty;
|
||||
import net.corda.core.serialization.OpaqueBytes;
|
||||
import org.junit.Test;
|
||||
|
||||
import static net.corda.core.contracts.ContractsDSL.*;
|
||||
import static net.corda.core.utilities.TestConstants.*;
|
||||
import static net.corda.core.contracts.ContractsDSL.DOLLARS;
|
||||
import static net.corda.core.contracts.ContractsDSL.issuedBy;
|
||||
import static net.corda.core.utilities.TestConstants.getDUMMY_PUBKEY_1;
|
||||
import static net.corda.core.utilities.TestConstants.getDUMMY_PUBKEY_2;
|
||||
import static net.corda.testing.CoreTestUtils.*;
|
||||
|
||||
/**
|
||||
@ -15,8 +18,8 @@ import static net.corda.testing.CoreTestUtils.*;
|
||||
public class CashTestsJava {
|
||||
private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});
|
||||
private final PartyAndReference defaultIssuer = getMEGA_CORP().ref(defaultRef);
|
||||
private final Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), getDUMMY_PUBKEY_1());
|
||||
private final Cash.State outState = new Cash.State(inState.getAmount(), getDUMMY_PUBKEY_2());
|
||||
private final Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), new AnonymousParty(getDUMMY_PUBKEY_1()));
|
||||
private final Cash.State outState = new Cash.State(inState.getAmount(), new AnonymousParty(getDUMMY_PUBKEY_2()));
|
||||
|
||||
@Test
|
||||
public void trivial() {
|
||||
@ -26,7 +29,7 @@ public class CashTestsJava {
|
||||
tx.failsWith("the amounts balance");
|
||||
|
||||
tx.tweak(tw -> {
|
||||
tw.output(new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), getDUMMY_PUBKEY_2()));
|
||||
tw.output(new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), new AnonymousParty(getDUMMY_PUBKEY_2())));
|
||||
return tw.failsWith("the amounts balance");
|
||||
});
|
||||
|
||||
|
@ -4,15 +4,13 @@ import net.corda.contracts.asset.*
|
||||
import net.corda.contracts.testing.fillWithSomeTestCash
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.days
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.core.utilities.DUMMY_PUBKEY_1
|
||||
import net.corda.core.utilities.TEST_TX_TIME
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.node.utilities.transaction
|
||||
import net.corda.testing.*
|
||||
@ -38,7 +36,7 @@ interface ICommercialPaperTestTemplate {
|
||||
class JavaCommercialPaperTest : ICommercialPaperTestTemplate {
|
||||
override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State(
|
||||
MEGA_CORP.ref(123),
|
||||
MEGA_CORP_PUBKEY,
|
||||
MEGA_CORP,
|
||||
1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
||||
TEST_TX_TIME + 7.days
|
||||
)
|
||||
@ -51,7 +49,7 @@ class JavaCommercialPaperTest : ICommercialPaperTestTemplate {
|
||||
class KotlinCommercialPaperTest : ICommercialPaperTestTemplate {
|
||||
override fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
||||
issuance = MEGA_CORP.ref(123),
|
||||
owner = MEGA_CORP_PUBKEY,
|
||||
owner = MEGA_CORP,
|
||||
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
||||
maturityDate = TEST_TX_TIME + 7.days
|
||||
)
|
||||
@ -64,7 +62,7 @@ class KotlinCommercialPaperTest : ICommercialPaperTestTemplate {
|
||||
class KotlinCommercialPaperLegacyTest : ICommercialPaperTestTemplate {
|
||||
override fun getPaper(): ICommercialPaperState = CommercialPaperLegacy.State(
|
||||
issuance = MEGA_CORP.ref(123),
|
||||
owner = MEGA_CORP_PUBKEY,
|
||||
owner = MEGA_CORP,
|
||||
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
||||
maturityDate = TEST_TX_TIME + 7.days
|
||||
)
|
||||
@ -91,8 +89,8 @@ class CommercialPaperTestsGeneric {
|
||||
val someProfits = 1200.DOLLARS `issued by` issuer
|
||||
ledger {
|
||||
unverifiedTransaction {
|
||||
output("alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY)
|
||||
output("some profits", someProfits.STATE `owned by` MEGA_CORP_PUBKEY)
|
||||
output("alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
|
||||
output("some profits", someProfits.STATE `owned by` MEGA_CORP)
|
||||
}
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
@ -108,8 +106,8 @@ class CommercialPaperTestsGeneric {
|
||||
transaction("Trade") {
|
||||
input("paper")
|
||||
input("alice's $900")
|
||||
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
|
||||
output("alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
|
||||
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
|
||||
output("alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
||||
this.verifies()
|
||||
@ -122,8 +120,8 @@ class CommercialPaperTestsGeneric {
|
||||
input("some profits")
|
||||
|
||||
fun TransactionDSL<TransactionDSLInterpreter>.outputs(aliceGetsBack: Amount<Issued<Currency>>) {
|
||||
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
|
||||
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
|
||||
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE }
|
||||
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP }
|
||||
}
|
||||
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
@ -270,8 +268,8 @@ class CommercialPaperTestsGeneric {
|
||||
// Alice pays $9000 to BigCorp to own some of their debt.
|
||||
moveTX = run {
|
||||
val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
aliceVaultService.generateSpend(ptx, 9000.DOLLARS, bigCorpServices.key.public)
|
||||
CommercialPaper().generateMove(ptx, issueTX.tx.outRef(0), aliceServices.key.public)
|
||||
aliceVaultService.generateSpend(ptx, 9000.DOLLARS, AnonymousParty(bigCorpServices.key.public))
|
||||
CommercialPaper().generateMove(ptx, issueTX.tx.outRef(0), AnonymousParty(aliceServices.key.public))
|
||||
ptx.signWith(bigCorpServices.key)
|
||||
ptx.signWith(aliceServices.key)
|
||||
ptx.signWith(DUMMY_NOTARY_KEY)
|
||||
|
@ -2,8 +2,10 @@ package net.corda.contracts.asset
|
||||
|
||||
import net.corda.contracts.testing.fillWithSomeTestCash
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.node.services.unconsumedStates
|
||||
@ -23,7 +25,6 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.Closeable
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
|
||||
@ -32,11 +33,11 @@ class CashTests {
|
||||
val defaultIssuer = MEGA_CORP.ref(defaultRef)
|
||||
val inState = Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` defaultIssuer,
|
||||
owner = DUMMY_PUBKEY_1
|
||||
owner = AnonymousParty(DUMMY_PUBKEY_1)
|
||||
)
|
||||
// Input state held by the issuer
|
||||
val issuerInState = inState.copy(owner = defaultIssuer.party.owningKey)
|
||||
val outState = issuerInState.copy(owner = DUMMY_PUBKEY_2)
|
||||
val issuerInState = inState.copy(owner = defaultIssuer.party)
|
||||
val outState = issuerInState.copy(owner = AnonymousParty(DUMMY_PUBKEY_2))
|
||||
|
||||
fun Cash.State.editDepositRef(ref: Byte) = copy(
|
||||
amount = Amount(amount.quantity, token = amount.token.copy(amount.token.issuer.copy(reference = OpaqueBytes.of(ref))))
|
||||
@ -70,13 +71,13 @@ class CashTests {
|
||||
}
|
||||
|
||||
services.fillWithSomeTestCash(howMuch = 100.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
|
||||
issuedBy = MEGA_CORP.ref(1), issuerKey = MEGA_CORP_KEY, ownedBy = OUR_PUBKEY_1)
|
||||
issuedBy = MEGA_CORP.ref(1), issuerKey = MEGA_CORP_KEY, ownedBy = OUR_IDENTITY_1)
|
||||
services.fillWithSomeTestCash(howMuch = 400.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
|
||||
issuedBy = MEGA_CORP.ref(1), issuerKey = MEGA_CORP_KEY, ownedBy = OUR_PUBKEY_1)
|
||||
issuedBy = MEGA_CORP.ref(1), issuerKey = MEGA_CORP_KEY, ownedBy = OUR_IDENTITY_1)
|
||||
services.fillWithSomeTestCash(howMuch = 80.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
|
||||
issuedBy = MINI_CORP.ref(1), issuerKey = MINI_CORP_KEY, ownedBy = OUR_PUBKEY_1)
|
||||
issuedBy = MINI_CORP.ref(1), issuerKey = MINI_CORP_KEY, ownedBy = OUR_IDENTITY_1)
|
||||
services.fillWithSomeTestCash(howMuch = 80.SWISS_FRANCS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
|
||||
issuedBy = MINI_CORP.ref(1), issuerKey = MINI_CORP_KEY, ownedBy = OUR_PUBKEY_1)
|
||||
issuedBy = MINI_CORP.ref(1), issuerKey = MINI_CORP_KEY, ownedBy = OUR_IDENTITY_1)
|
||||
|
||||
vaultStatesUnconsumed = services.vaultService.unconsumedStates<Cash.State>().toList()
|
||||
}
|
||||
@ -142,7 +143,7 @@ class CashTests {
|
||||
output {
|
||||
Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
|
||||
owner = DUMMY_PUBKEY_1
|
||||
owner = AnonymousParty(DUMMY_PUBKEY_1)
|
||||
)
|
||||
}
|
||||
tweak {
|
||||
@ -158,14 +159,14 @@ class CashTests {
|
||||
fun generateIssueRaw() {
|
||||
// Test generation works.
|
||||
val tx: WireTransaction = TransactionType.General.Builder(notary = null).apply {
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(DUMMY_PUBKEY_1), notary = DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
assertTrue(tx.inputs.isEmpty())
|
||||
val s = tx.outputs[0].data as Cash.State
|
||||
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
|
||||
assertEquals(MINI_CORP as AbstractParty, s.amount.token.issuer.party)
|
||||
assertEquals(DUMMY_PUBKEY_1, s.owner)
|
||||
assertEquals(AnonymousParty(DUMMY_PUBKEY_1), s.owner)
|
||||
assertTrue(tx.commands[0].value is Cash.Commands.Issue)
|
||||
assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0])
|
||||
}
|
||||
@ -175,7 +176,7 @@ class CashTests {
|
||||
// Test issuance from an issued amount
|
||||
val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34)
|
||||
val tx: WireTransaction = TransactionType.General.Builder(notary = null).apply {
|
||||
Cash().generateIssue(this, amount, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
|
||||
Cash().generateIssue(this, amount, owner = AnonymousParty(DUMMY_PUBKEY_1), notary = DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
assertTrue(tx.inputs.isEmpty())
|
||||
@ -248,14 +249,14 @@ class CashTests {
|
||||
// Issue some cash
|
||||
var ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
|
||||
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY)
|
||||
ptx.signWith(MINI_CORP_KEY)
|
||||
val tx = ptx.toSignedTransaction()
|
||||
|
||||
// Include the previously issued cash in a new issuance command
|
||||
ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
ptx.addInputState(tx.tx.outRef<Cash.State>(0))
|
||||
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -329,7 +330,7 @@ class CashTests {
|
||||
input {
|
||||
inState.copy(
|
||||
amount = 150.POUNDS `issued by` defaultIssuer,
|
||||
owner = DUMMY_PUBKEY_2
|
||||
owner = AnonymousParty(DUMMY_PUBKEY_2)
|
||||
)
|
||||
}
|
||||
output { outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer) }
|
||||
@ -382,10 +383,10 @@ class CashTests {
|
||||
// Multi-issuer case.
|
||||
transaction {
|
||||
input { issuerInState }
|
||||
input { issuerInState.copy(owner = MINI_CORP_PUBKEY) `issued by` MINI_CORP }
|
||||
input { issuerInState.copy(owner = MINI_CORP) `issued by` MINI_CORP }
|
||||
|
||||
output { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP }
|
||||
output { issuerInState.copy(owner = MINI_CORP_PUBKEY, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
|
||||
output { issuerInState.copy(owner = MINI_CORP, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
|
||||
|
||||
command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
|
||||
@ -420,19 +421,19 @@ class CashTests {
|
||||
|
||||
// Can't merge them together.
|
||||
tweak {
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS `issued by` defaultIssuer) }
|
||||
output { inState.copy(owner = AnonymousParty(DUMMY_PUBKEY_2), amount = 2000.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
// Missing MiniCorp deposit
|
||||
tweak {
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(owner = AnonymousParty(DUMMY_PUBKEY_2)) }
|
||||
output { inState.copy(owner = AnonymousParty(DUMMY_PUBKEY_2)) }
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
|
||||
// This works.
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) `issued by` MINI_CORP }
|
||||
output { inState.copy(owner = AnonymousParty(DUMMY_PUBKEY_2)) }
|
||||
output { inState.copy(owner = AnonymousParty(DUMMY_PUBKEY_2)) `issued by` MINI_CORP }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
@ -442,11 +443,11 @@ class CashTests {
|
||||
fun multiCurrency() {
|
||||
// Check we can do an atomic currency trade tx.
|
||||
transaction {
|
||||
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), DUMMY_PUBKEY_2)
|
||||
input { inState `owned by` DUMMY_PUBKEY_1 }
|
||||
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), AnonymousParty(DUMMY_PUBKEY_2))
|
||||
input { inState `owned by` AnonymousParty(DUMMY_PUBKEY_1) }
|
||||
input { pounds }
|
||||
output { inState `owned by` DUMMY_PUBKEY_2 }
|
||||
output { pounds `owned by` DUMMY_PUBKEY_1 }
|
||||
output { inState `owned by` AnonymousParty(DUMMY_PUBKEY_2) }
|
||||
output { pounds `owned by` AnonymousParty(DUMMY_PUBKEY_1) }
|
||||
command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Cash.Commands.Move() }
|
||||
|
||||
this.verifies()
|
||||
@ -458,13 +459,13 @@ class CashTests {
|
||||
// Spend tx generation
|
||||
|
||||
val OUR_KEY: KeyPair by lazy { generateKeyPair() }
|
||||
val OUR_PUBKEY_1: PublicKey get() = OUR_KEY.public
|
||||
val OUR_IDENTITY_1: AbstractParty get() = AnonymousParty(OUR_KEY.public)
|
||||
|
||||
val THEIR_PUBKEY_1 = DUMMY_PUBKEY_2
|
||||
val THEIR_IDENTITY_1 = AnonymousParty(DUMMY_PUBKEY_2)
|
||||
|
||||
fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) =
|
||||
StateAndRef(
|
||||
Cash.State(amount `issued by` corp.ref(depositRef), OUR_PUBKEY_1) `with notary` DUMMY_NOTARY,
|
||||
Cash.State(amount `issued by` corp.ref(depositRef), OUR_IDENTITY_1) `with notary` DUMMY_NOTARY,
|
||||
StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
|
||||
)
|
||||
|
||||
@ -484,7 +485,7 @@ class CashTests {
|
||||
return tx.toWireTransaction()
|
||||
}
|
||||
|
||||
fun makeSpend(amount: Amount<Currency>, dest: PublicKey): WireTransaction {
|
||||
fun makeSpend(amount: Amount<Currency>, dest: AbstractParty): WireTransaction {
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
database.transaction {
|
||||
vault.generateSpend(tx, amount, dest)
|
||||
@ -566,13 +567,13 @@ class CashTests {
|
||||
|
||||
database.transaction {
|
||||
|
||||
val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1)
|
||||
val wtx = makeSpend(100.DOLLARS, THEIR_IDENTITY_1)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val vaultState = vaultStatesUnconsumed.elementAt(0)
|
||||
assertEquals(vaultState.ref, wtx.inputs[0])
|
||||
assertEquals(vaultState.state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0].data)
|
||||
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||
assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1), wtx.outputs[0].data)
|
||||
assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||
}
|
||||
}
|
||||
|
||||
@ -582,7 +583,7 @@ class CashTests {
|
||||
database.transaction {
|
||||
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
vault.generateSpend(tx, 80.DOLLARS, ALICE_PUBKEY, setOf(MINI_CORP.toAnonymous()))
|
||||
vault.generateSpend(tx, 80.DOLLARS, ALICE, setOf(MINI_CORP))
|
||||
|
||||
assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0])
|
||||
}
|
||||
@ -593,14 +594,14 @@ class CashTests {
|
||||
|
||||
database.transaction {
|
||||
|
||||
val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1)
|
||||
val wtx = makeSpend(10.DOLLARS, THEIR_IDENTITY_1)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val vaultState = vaultStatesUnconsumed.elementAt(0)
|
||||
assertEquals(vaultState.ref, wtx.inputs[0])
|
||||
assertEquals(vaultState.state.data.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
|
||||
assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
|
||||
assertEquals(vaultState.state.data.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data)
|
||||
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||
assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||
}
|
||||
}
|
||||
|
||||
@ -608,15 +609,15 @@ class CashTests {
|
||||
fun generateSpendWithTwoInputs() {
|
||||
|
||||
database.transaction {
|
||||
val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1)
|
||||
val wtx = makeSpend(500.DOLLARS, THEIR_IDENTITY_1)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val vaultState0 = vaultStatesUnconsumed.elementAt(0)
|
||||
val vaultState1 = vaultStatesUnconsumed.elementAt(1)
|
||||
assertEquals(vaultState0.ref, wtx.inputs[0])
|
||||
assertEquals(vaultState1.ref, wtx.inputs[1])
|
||||
assertEquals(vaultState0.state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
|
||||
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||
assertEquals(vaultState0.state.data.copy(owner = THEIR_IDENTITY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
|
||||
assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||
}
|
||||
}
|
||||
|
||||
@ -624,7 +625,7 @@ class CashTests {
|
||||
fun generateSpendMixedDeposits() {
|
||||
|
||||
database.transaction {
|
||||
val wtx = makeSpend(580.DOLLARS, THEIR_PUBKEY_1)
|
||||
val wtx = makeSpend(580.DOLLARS, THEIR_IDENTITY_1)
|
||||
assertEquals(3, wtx.inputs.size)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -635,9 +636,9 @@ class CashTests {
|
||||
assertEquals(vaultState0.ref, wtx.inputs[0])
|
||||
assertEquals(vaultState1.ref, wtx.inputs[1])
|
||||
assertEquals(vaultState2.ref, wtx.inputs[2])
|
||||
assertEquals(vaultState0.state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data)
|
||||
assertEquals(vaultState2.state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0].data)
|
||||
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||
assertEquals(vaultState0.state.data.copy(owner = THEIR_IDENTITY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data)
|
||||
assertEquals(vaultState2.state.data.copy(owner = THEIR_IDENTITY_1), wtx.outputs[0].data)
|
||||
assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||
}
|
||||
}
|
||||
|
||||
@ -647,12 +648,12 @@ class CashTests {
|
||||
database.transaction {
|
||||
|
||||
val e: InsufficientBalanceException = assertFailsWith("balance") {
|
||||
makeSpend(1000.DOLLARS, THEIR_PUBKEY_1)
|
||||
makeSpend(1000.DOLLARS, THEIR_IDENTITY_1)
|
||||
}
|
||||
assertEquals((1000 - 580).DOLLARS, e.amountMissing)
|
||||
|
||||
assertFailsWith(InsufficientBalanceException::class) {
|
||||
makeSpend(81.SWISS_FRANCS, THEIR_PUBKEY_1)
|
||||
makeSpend(81.SWISS_FRANCS, THEIR_IDENTITY_1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -662,9 +663,9 @@ class CashTests {
|
||||
*/
|
||||
@Test
|
||||
fun aggregation() {
|
||||
val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP_PUBKEY)
|
||||
val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP_PUBKEY)
|
||||
val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY)
|
||||
val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP)
|
||||
val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP)
|
||||
val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP)
|
||||
|
||||
// Obviously it must be possible to aggregate states with themselves
|
||||
assertEquals(fiveThousandDollarsFromMega.amount.token, fiveThousandDollarsFromMega.amount.token)
|
||||
@ -678,7 +679,7 @@ class CashTests {
|
||||
|
||||
// States cannot be aggregated if the currency differs
|
||||
assertNotEquals(oneThousandDollarsFromMini.amount.token,
|
||||
Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY).amount.token)
|
||||
Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP).amount.token)
|
||||
|
||||
// States cannot be aggregated if the reference differs
|
||||
assertNotEquals(fiveThousandDollarsFromMega.amount.token, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).amount.token)
|
||||
@ -688,20 +689,20 @@ class CashTests {
|
||||
@Test
|
||||
fun `summing by owner`() {
|
||||
val states = listOf(
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY),
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP),
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP)
|
||||
)
|
||||
assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP_PUBKEY))
|
||||
assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP))
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class)
|
||||
fun `summing by owner throws`() {
|
||||
val states = listOf(
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP)
|
||||
)
|
||||
states.sumCashBy(MINI_CORP_PUBKEY)
|
||||
states.sumCashBy(MINI_CORP)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -720,9 +721,9 @@ class CashTests {
|
||||
@Test
|
||||
fun `summing a single currency`() {
|
||||
val states = listOf(
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP)
|
||||
)
|
||||
// Test that summing everything produces the total number of dollars
|
||||
val expected = 7000.DOLLARS `issued by` defaultIssuer
|
||||
@ -733,8 +734,8 @@ class CashTests {
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun `summing multiple currencies`() {
|
||||
val states = listOf(
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||
Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP)
|
||||
)
|
||||
// Test that summing everything fails because we're mixing units
|
||||
states.sumCash()
|
||||
@ -748,14 +749,14 @@ class CashTests {
|
||||
output("MEGA_CORP cash") {
|
||||
Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||
owner = MEGA_CORP_PUBKEY
|
||||
owner = MEGA_CORP
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("MEGA_CORP cash")
|
||||
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
|
||||
output("MEGA_CORP cash".output<Cash.State>().copy(owner = AnonymousParty(DUMMY_PUBKEY_1)))
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
@ -764,7 +765,7 @@ class CashTests {
|
||||
transaction {
|
||||
input("MEGA_CORP cash")
|
||||
// We send it to another pubkey so that the transaction is not identical to the previous one
|
||||
output("MEGA_CORP cash".output<Cash.State>().copy(owner = ALICE_PUBKEY))
|
||||
output("MEGA_CORP cash".output<Cash.State>().copy(owner = ALICE))
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ package net.corda.contracts.asset
|
||||
|
||||
import net.corda.contracts.asset.Obligation.Lifecycle
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.NullPublicKey
|
||||
import net.corda.core.crypto.NULL_PARTY
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.testing.*
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.*
|
||||
@ -33,18 +34,18 @@ class ObligationTests {
|
||||
obligor = MEGA_CORP,
|
||||
template = megaCorpDollarSettlement,
|
||||
quantity = 1000.DOLLARS.quantity,
|
||||
beneficiary = DUMMY_PUBKEY_1
|
||||
beneficiary = CHARLIE
|
||||
)
|
||||
val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2)
|
||||
val outState = inState.copy(beneficiary = AnonymousParty(DUMMY_PUBKEY_2))
|
||||
|
||||
private fun cashObligationTestRoots(
|
||||
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
|
||||
) = group.apply {
|
||||
unverifiedTransaction {
|
||||
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY))
|
||||
output("Bob's $1,000,000 obligation to Alice", oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY))
|
||||
output("MegaCorp's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB_PUBKEY))
|
||||
output("Alice's $1,000,000", 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY)
|
||||
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(ALICE, BOB))
|
||||
output("Bob's $1,000,000 obligation to Alice", oneMillionDollars.OBLIGATION between Pair(BOB, ALICE))
|
||||
output("MegaCorp's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB))
|
||||
output("Alice's $1,000,000", 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE)
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,13 +72,13 @@ class ObligationTests {
|
||||
tweak {
|
||||
output { outState }
|
||||
output { outState `issued by` MINI_CORP }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
|
||||
this `fails with` "at least one asset input"
|
||||
}
|
||||
// Simple reallocation works.
|
||||
tweak {
|
||||
output { outState }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -98,7 +99,7 @@ class ObligationTests {
|
||||
// institution is allowed to issue as much cash as they want.
|
||||
transaction {
|
||||
output { outState }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Issue() }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Issue() }
|
||||
this `fails with` "output states are issued by a command signer"
|
||||
}
|
||||
transaction {
|
||||
@ -106,7 +107,7 @@ class ObligationTests {
|
||||
Obligation.State(
|
||||
obligor = MINI_CORP,
|
||||
quantity = 1000.DOLLARS.quantity,
|
||||
beneficiary = DUMMY_PUBKEY_1,
|
||||
beneficiary = CHARLIE,
|
||||
template = megaCorpDollarSettlement
|
||||
)
|
||||
}
|
||||
@ -121,14 +122,14 @@ class ObligationTests {
|
||||
// Test generation works.
|
||||
val tx = TransactionType.General.Builder(notary = null).apply {
|
||||
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
beneficiary = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
|
||||
beneficiary = CHARLIE, notary = DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
assertTrue(tx.inputs.isEmpty())
|
||||
val expected = Obligation.State(
|
||||
obligor = MINI_CORP,
|
||||
quantity = 100.DOLLARS.quantity,
|
||||
beneficiary = DUMMY_PUBKEY_1,
|
||||
beneficiary = CHARLIE,
|
||||
template = megaCorpDollarSettlement
|
||||
)
|
||||
assertEquals(tx.outputs[0].data, expected)
|
||||
@ -142,7 +143,7 @@ class ObligationTests {
|
||||
|
||||
// Move fails: not allowed to summon money.
|
||||
tweak {
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
|
||||
@ -199,7 +200,7 @@ class ObligationTests {
|
||||
// Issue some obligation
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction()
|
||||
|
||||
@ -207,16 +208,16 @@ class ObligationTests {
|
||||
val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
ptx.addInputState(tx.tx.outRef<Obligation.State<Currency>>(0))
|
||||
Obligation<Currency>().generateIssue(ptx, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
|
||||
}
|
||||
|
||||
/** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */
|
||||
@Test
|
||||
fun `generate close-out net transaction`() {
|
||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
|
||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY)
|
||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)
|
||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE)
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, obligationAliceToBob, obligationBobToAlice)
|
||||
Obligation<Currency>().generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
|
||||
signWith(ALICE_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
@ -226,24 +227,24 @@ class ObligationTests {
|
||||
/** Test generating a transaction to net two obligations of the different sizes, and confirm the balance is correct. */
|
||||
@Test
|
||||
fun `generate close-out net transaction with remainder`() {
|
||||
val obligationAliceToBob = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB_PUBKEY)
|
||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY)
|
||||
val obligationAliceToBob = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB)
|
||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE)
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, obligationAliceToBob, obligationBobToAlice)
|
||||
Obligation<Currency>().generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
|
||||
signWith(ALICE_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
assertEquals(1, tx.outputs.size)
|
||||
|
||||
val actual = tx.outputs[0].data
|
||||
assertEquals((1000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB_PUBKEY), actual)
|
||||
assertEquals((1000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB), actual)
|
||||
}
|
||||
|
||||
/** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */
|
||||
@Test
|
||||
fun `generate payment net transaction`() {
|
||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
|
||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY)
|
||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)
|
||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE)
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generatePaymentNetting(this, obligationAliceToBob.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
||||
signWith(ALICE_KEY)
|
||||
@ -256,8 +257,8 @@ class ObligationTests {
|
||||
/** Test generating a transaction to two obligations, where one is bigger than the other and therefore there is a remainder. */
|
||||
@Test
|
||||
fun `generate payment net transaction with remainder`() {
|
||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
|
||||
val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE_PUBKEY)
|
||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)
|
||||
val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE)
|
||||
val tx = TransactionType.General.Builder(null).apply {
|
||||
Obligation<Currency>().generatePaymentNetting(this, obligationAliceToBob.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
||||
signWith(ALICE_KEY)
|
||||
@ -278,7 +279,7 @@ class ObligationTests {
|
||||
// Generate a transaction issuing the obligation
|
||||
var tx = TransactionType.General.Builder(null).apply {
|
||||
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement.copy(dueBefore = dueBefore), 100.DOLLARS.quantity,
|
||||
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction()
|
||||
var stateAndRef = tx.tx.outRef<Obligation.State<Currency>>(0)
|
||||
@ -309,14 +310,14 @@ class ObligationTests {
|
||||
@Test
|
||||
fun `generate settlement transaction`() {
|
||||
val cashTx = TransactionType.General.Builder(null).apply {
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY, DUMMY_NOTARY)
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` defaultIssuer, MINI_CORP, DUMMY_NOTARY)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
|
||||
// Generate a transaction issuing the obligation
|
||||
val obligationTx = TransactionType.General.Builder(null).apply {
|
||||
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
|
||||
@ -354,7 +355,7 @@ class ObligationTests {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
input("MegaCorp's $1,000,000 obligation to Bob")
|
||||
output("change") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB_PUBKEY) }
|
||||
output("change") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB) }
|
||||
command(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
@ -368,7 +369,7 @@ class ObligationTests {
|
||||
transaction("Issuance") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
output("change") { (oneMillionDollars.splitEvenly(2).first()).OBLIGATION between Pair(ALICE, BOB_PUBKEY) }
|
||||
output("change") { (oneMillionDollars.splitEvenly(2).first()).OBLIGATION between Pair(ALICE, BOB) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "amounts owed on input and output must match"
|
||||
@ -422,7 +423,7 @@ class ObligationTests {
|
||||
transaction("Issuance") {
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
input("MegaCorp's $1,000,000 obligation to Bob")
|
||||
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE_PUBKEY) }
|
||||
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE) }
|
||||
command(ALICE_PUBKEY, BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
@ -436,7 +437,7 @@ class ObligationTests {
|
||||
transaction("Issuance") {
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
input("MegaCorp's $1,000,000 obligation to Bob")
|
||||
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE_PUBKEY) }
|
||||
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE) }
|
||||
command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "all involved parties have signed"
|
||||
@ -452,7 +453,7 @@ class ObligationTests {
|
||||
transaction("Settlement") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Alice's $1,000,000")
|
||||
output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY }
|
||||
output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB }
|
||||
command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity, inState.amount.token)) }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
||||
this.verifies()
|
||||
@ -463,10 +464,10 @@ class ObligationTests {
|
||||
val halfAMillionDollars = 500000.DOLLARS `issued by` defaultIssuer
|
||||
ledger {
|
||||
transaction("Settlement") {
|
||||
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY))
|
||||
input(500000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY)
|
||||
output("Alice's $500,000 obligation to Bob") { halfAMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) }
|
||||
output("Bob's $500,000") { 500000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY }
|
||||
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB))
|
||||
input(500000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE)
|
||||
output("Alice's $500,000 obligation to Bob") { halfAMillionDollars.OBLIGATION between Pair(ALICE, BOB) }
|
||||
output("Bob's $500,000") { 500000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB }
|
||||
command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity / 2, inState.amount.token)) }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
||||
this.verifies()
|
||||
@ -474,12 +475,12 @@ class ObligationTests {
|
||||
}
|
||||
|
||||
// Make sure we can't settle an obligation that's defaulted
|
||||
val defaultedObligation: Obligation.State<Currency> = (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)).copy(lifecycle = Lifecycle.DEFAULTED)
|
||||
val defaultedObligation: Obligation.State<Currency> = (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)).copy(lifecycle = Lifecycle.DEFAULTED)
|
||||
ledger {
|
||||
transaction("Settlement") {
|
||||
input(defaultedObligation) // Alice's defaulted $1,000,000 obligation to Bob
|
||||
input(1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY)
|
||||
output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY }
|
||||
input(1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE)
|
||||
output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB }
|
||||
command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity, inState.amount.token)) }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
||||
this `fails with` "all inputs are in the normal state"
|
||||
@ -492,7 +493,7 @@ class ObligationTests {
|
||||
transaction("Settlement") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Alice's $1,000,000")
|
||||
output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY }
|
||||
output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB }
|
||||
command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity / 2, inState.amount.token)) }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
||||
this `fails with` "amount in settle command"
|
||||
@ -506,17 +507,17 @@ class ObligationTests {
|
||||
val oneUnitFcoj = Amount(1, defaultFcoj)
|
||||
val obligationDef = Obligation.Terms(nonEmptySetOf(CommodityContract().legalContractReference), nonEmptySetOf(defaultFcoj), TEST_TX_TIME)
|
||||
val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE,
|
||||
obligationDef, oneUnitFcoj.quantity, NullPublicKey)
|
||||
obligationDef, oneUnitFcoj.quantity, NULL_PARTY)
|
||||
// Try settling a simple commodity obligation
|
||||
ledger {
|
||||
unverifiedTransaction {
|
||||
output("Alice's 1 FCOJ obligation to Bob", oneUnitFcojObligation between Pair(ALICE, BOB_PUBKEY))
|
||||
output("Alice's 1 FCOJ", CommodityContract.State(oneUnitFcoj, ALICE_PUBKEY))
|
||||
output("Alice's 1 FCOJ obligation to Bob", oneUnitFcojObligation between Pair(ALICE, BOB))
|
||||
output("Alice's 1 FCOJ", CommodityContract.State(oneUnitFcoj, ALICE))
|
||||
}
|
||||
transaction("Settlement") {
|
||||
input("Alice's 1 FCOJ obligation to Bob")
|
||||
input("Alice's 1 FCOJ")
|
||||
output("Bob's 1 FCOJ") { CommodityContract.State(oneUnitFcoj, BOB_PUBKEY) }
|
||||
output("Bob's 1 FCOJ") { CommodityContract.State(oneUnitFcoj, BOB) }
|
||||
command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneUnitFcoj.quantity, oneUnitFcojObligation.amount.token)) }
|
||||
command(ALICE_PUBKEY) { CommodityContract.Commands.Move(Obligation<Commodity>().legalContractReference) }
|
||||
verifies()
|
||||
@ -531,7 +532,7 @@ class ObligationTests {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Settlement") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Lifecycle.DEFAULTED) }
|
||||
this `fails with` "there is a timestamp from the authority"
|
||||
}
|
||||
@ -541,8 +542,8 @@ class ObligationTests {
|
||||
val pastTestTime = TEST_TX_TIME - Duration.ofDays(7)
|
||||
val futureTestTime = TEST_TX_TIME + Duration.ofDays(7)
|
||||
transaction("Settlement") {
|
||||
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` futureTestTime)
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` futureTestTime)
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Lifecycle.DEFAULTED) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "the due date has passed"
|
||||
@ -551,8 +552,8 @@ class ObligationTests {
|
||||
// Try defaulting an obligation that is now in the past
|
||||
ledger {
|
||||
transaction("Settlement") {
|
||||
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` pastTestTime)
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` pastTestTime)
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Lifecycle.DEFAULTED) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
@ -565,7 +566,7 @@ class ObligationTests {
|
||||
fun testMergeSplit() {
|
||||
// Splitting value works.
|
||||
transaction {
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
|
||||
tweak {
|
||||
input { inState }
|
||||
repeat(4) { output { inState.copy(quantity = inState.quantity / 4) } }
|
||||
@ -624,7 +625,7 @@ class ObligationTests {
|
||||
inState.copy(
|
||||
quantity = 15000,
|
||||
template = megaCorpPoundSettlement,
|
||||
beneficiary = DUMMY_PUBKEY_2
|
||||
beneficiary = AnonymousParty(DUMMY_PUBKEY_2)
|
||||
)
|
||||
}
|
||||
output { outState.copy(quantity = 115000) }
|
||||
@ -635,7 +636,7 @@ class ObligationTests {
|
||||
input { inState }
|
||||
input { inState `issued by` MINI_CORP }
|
||||
output { outState }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
}
|
||||
@ -648,17 +649,17 @@ class ObligationTests {
|
||||
output { outState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) }
|
||||
|
||||
tweak {
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(100.DOLLARS.quantity, inState.amount.token)) }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Exit(Amount(100.DOLLARS.quantity, inState.amount.token)) }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
|
||||
tweak {
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(200.DOLLARS.quantity, inState.amount.token)) }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Exit(Amount(200.DOLLARS.quantity, inState.amount.token)) }
|
||||
this `fails with` "required net.corda.core.contracts.FungibleAsset.Commands.Move command"
|
||||
|
||||
tweak {
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -676,14 +677,14 @@ class ObligationTests {
|
||||
output { inState.copy(template = inState.template.copy(acceptableIssuedProducts = megaIssuedPounds), quantity = inState.quantity - 200.POUNDS.quantity) }
|
||||
output { inState.copy(template = inState.template.copy(acceptableIssuedProducts = megaIssuedDollars), quantity = inState.quantity - 200.DOLLARS.quantity) }
|
||||
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
|
||||
|
||||
this `fails with` "the amounts balance"
|
||||
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(200.DOLLARS.quantity, inState.amount.token.copy(product = megaCorpDollarSettlement))) }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Exit(Amount(200.DOLLARS.quantity, inState.amount.token.copy(product = megaCorpDollarSettlement))) }
|
||||
this `fails with` "the amounts balance"
|
||||
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(200.POUNDS.quantity, inState.amount.token.copy(product = megaCorpPoundSettlement))) }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Exit(Amount(200.POUNDS.quantity, inState.amount.token.copy(product = megaCorpPoundSettlement))) }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -697,20 +698,20 @@ class ObligationTests {
|
||||
|
||||
// Can't merge them together.
|
||||
tweak {
|
||||
output { inState.copy(beneficiary = DUMMY_PUBKEY_2, quantity = 200000L) }
|
||||
output { inState.copy(beneficiary = AnonymousParty(DUMMY_PUBKEY_2), quantity = 200000L) }
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
// Missing MiniCorp deposit
|
||||
tweak {
|
||||
output { inState.copy(beneficiary = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(beneficiary = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(beneficiary = AnonymousParty(DUMMY_PUBKEY_2)) }
|
||||
output { inState.copy(beneficiary = AnonymousParty(DUMMY_PUBKEY_2)) }
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
|
||||
// This works.
|
||||
output { inState.copy(beneficiary = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(beneficiary = DUMMY_PUBKEY_2) `issued by` MINI_CORP }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() }
|
||||
output { inState.copy(beneficiary = AnonymousParty(DUMMY_PUBKEY_2)) }
|
||||
output { inState.copy(beneficiary = AnonymousParty(DUMMY_PUBKEY_2)) `issued by` MINI_CORP }
|
||||
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -719,12 +720,12 @@ class ObligationTests {
|
||||
fun multiCurrency() {
|
||||
// Check we can do an atomic currency trade tx.
|
||||
transaction {
|
||||
val pounds = Obligation.State(Lifecycle.NORMAL, MINI_CORP, megaCorpPoundSettlement, 658.POUNDS.quantity, DUMMY_PUBKEY_2)
|
||||
input { inState `owned by` DUMMY_PUBKEY_1 }
|
||||
val pounds = Obligation.State(Lifecycle.NORMAL, MINI_CORP, megaCorpPoundSettlement, 658.POUNDS.quantity, AnonymousParty(DUMMY_PUBKEY_2))
|
||||
input { inState `owned by` CHARLIE }
|
||||
input { pounds }
|
||||
output { inState `owned by` DUMMY_PUBKEY_2 }
|
||||
output { pounds `owned by` DUMMY_PUBKEY_1 }
|
||||
command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Obligation.Commands.Move() }
|
||||
output { inState `owned by` AnonymousParty(DUMMY_PUBKEY_2) }
|
||||
output { pounds `owned by` CHARLIE }
|
||||
command(CHARLIE.owningKey, DUMMY_PUBKEY_2) { Obligation.Commands.Move() }
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
@ -733,11 +734,11 @@ class ObligationTests {
|
||||
@Test
|
||||
fun `nettability of settlement contracts`() {
|
||||
val fiveKDollarsFromMegaToMega = Obligation.State(Lifecycle.NORMAL, MEGA_CORP, megaCorpDollarSettlement,
|
||||
5000.DOLLARS.quantity, MEGA_CORP_PUBKEY)
|
||||
5000.DOLLARS.quantity, MEGA_CORP)
|
||||
val twoKDollarsFromMegaToMini = Obligation.State(Lifecycle.NORMAL, MEGA_CORP, megaCorpDollarSettlement,
|
||||
2000.DOLLARS.quantity, MINI_CORP_PUBKEY)
|
||||
2000.DOLLARS.quantity, MINI_CORP)
|
||||
val oneKDollarsFromMiniToMega = Obligation.State(Lifecycle.NORMAL, MINI_CORP, megaCorpDollarSettlement,
|
||||
1000.DOLLARS.quantity, MEGA_CORP_PUBKEY)
|
||||
1000.DOLLARS.quantity, MEGA_CORP)
|
||||
|
||||
// Obviously states must be nettable with themselves
|
||||
assertEquals(fiveKDollarsFromMegaToMega.bilateralNetState, fiveKDollarsFromMegaToMega.bilateralNetState)
|
||||
@ -778,9 +779,9 @@ class ObligationTests {
|
||||
@Test
|
||||
fun `extraction of issuance defintion`() {
|
||||
val fiveKDollarsFromMegaToMega = Obligation.State(Lifecycle.NORMAL, MEGA_CORP, megaCorpDollarSettlement,
|
||||
5000.DOLLARS.quantity, MEGA_CORP_PUBKEY)
|
||||
5000.DOLLARS.quantity, MEGA_CORP)
|
||||
val oneKDollarsFromMiniToMega = Obligation.State(Lifecycle.NORMAL, MINI_CORP, megaCorpDollarSettlement,
|
||||
1000.DOLLARS.quantity, MEGA_CORP_PUBKEY)
|
||||
1000.DOLLARS.quantity, MEGA_CORP)
|
||||
|
||||
// Issuance definitions must match the input
|
||||
assertEquals(fiveKDollarsFromMegaToMega.template, megaCorpDollarSettlement)
|
||||
@ -791,14 +792,14 @@ class ObligationTests {
|
||||
fun `adding two settlement contracts nets them`() {
|
||||
val megaCorpDollarSettlement = Obligation.Terms(trustedCashContract, megaIssuedDollars, fivePm)
|
||||
val fiveKDollarsFromMegaToMini = Obligation.State(Lifecycle.NORMAL, MEGA_CORP, megaCorpDollarSettlement,
|
||||
5000.DOLLARS.quantity, MINI_CORP_PUBKEY)
|
||||
5000.DOLLARS.quantity, MINI_CORP)
|
||||
val oneKDollarsFromMiniToMega = Obligation.State(Lifecycle.NORMAL, MINI_CORP, megaCorpDollarSettlement,
|
||||
1000.DOLLARS.quantity, MEGA_CORP_PUBKEY)
|
||||
1000.DOLLARS.quantity, MEGA_CORP)
|
||||
|
||||
var actual = fiveKDollarsFromMegaToMini.net(fiveKDollarsFromMegaToMini.copy(quantity = 2000.DOLLARS.quantity))
|
||||
// Both pay from mega to mini, so we add directly
|
||||
var expected = Obligation.State(Lifecycle.NORMAL, MEGA_CORP, megaCorpDollarSettlement, 7000.DOLLARS.quantity,
|
||||
MINI_CORP_PUBKEY)
|
||||
MINI_CORP)
|
||||
assertEquals(expected, actual)
|
||||
|
||||
// Reversing the direction should mean adding the second state subtracts from the first
|
||||
@ -809,7 +810,7 @@ class ObligationTests {
|
||||
// Trying to add an incompatible state must throw an error
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
fiveKDollarsFromMegaToMini.net(Obligation.State(Lifecycle.NORMAL, MINI_CORP, megaCorpDollarSettlement, 1000.DOLLARS.quantity,
|
||||
MINI_CORP_PUBKEY))
|
||||
MINI_CORP))
|
||||
}
|
||||
}
|
||||
|
||||
@ -817,9 +818,9 @@ class ObligationTests {
|
||||
fun `extracting amounts due between parties from a list of states`() {
|
||||
val megaCorpDollarSettlement = Obligation.Terms(trustedCashContract, megaIssuedDollars, fivePm)
|
||||
val fiveKDollarsFromMegaToMini = Obligation.State(Lifecycle.NORMAL, MEGA_CORP, megaCorpDollarSettlement,
|
||||
5000.DOLLARS.quantity, MINI_CORP_PUBKEY)
|
||||
5000.DOLLARS.quantity, MINI_CORP)
|
||||
val amount = fiveKDollarsFromMegaToMini.amount
|
||||
val expected: Map<Pair<PublicKey, PublicKey>, Amount<Obligation.Terms<Currency>>> = mapOf(Pair(Pair(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY), Amount(amount.quantity, amount.token.product)))
|
||||
val expected: Map<Pair<AbstractParty, AbstractParty>, Amount<Obligation.Terms<Currency>>> = mapOf(Pair(Pair(MEGA_CORP, MINI_CORP), Amount(amount.quantity, amount.token.product)))
|
||||
val actual = extractAmountsDue(megaCorpDollarSettlement, listOf(fiveKDollarsFromMegaToMini))
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -827,24 +828,24 @@ class ObligationTests {
|
||||
@Test
|
||||
fun `netting equal balances due between parties`() {
|
||||
// Now try it with two balances, which cancel each other out
|
||||
val balanced: Map<Pair<PublicKey, PublicKey>, Amount<Currency>> = mapOf(
|
||||
Pair(Pair(ALICE_PUBKEY, BOB_PUBKEY), Amount(100000000, GBP)),
|
||||
Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(100000000, GBP))
|
||||
val balanced: Map<Pair<AbstractParty, AbstractParty>, Amount<Currency>> = mapOf(
|
||||
Pair(Pair(ALICE, BOB), Amount(100000000, GBP)),
|
||||
Pair(Pair(BOB, ALICE), Amount(100000000, GBP))
|
||||
)
|
||||
val expected: Map<Pair<PublicKey, PublicKey>, Amount<Currency>> = emptyMap() // Zero balances are stripped before returning
|
||||
val actual: Map<Pair<PublicKey, PublicKey>, Amount<Currency>> = netAmountsDue(balanced)
|
||||
val expected: Map<Pair<AbstractParty, AbstractParty>, Amount<Currency>> = emptyMap() // Zero balances are stripped before returning
|
||||
val actual: Map<Pair<AbstractParty, AbstractParty>, Amount<Currency>> = netAmountsDue(balanced)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `netting difference balances due between parties`() {
|
||||
// Now try it with two balances, which cancel each other out
|
||||
val balanced: Map<Pair<PublicKey, PublicKey>, Amount<Currency>> = mapOf(
|
||||
Pair(Pair(ALICE_PUBKEY, BOB_PUBKEY), Amount(100000000, GBP)),
|
||||
Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(200000000, GBP))
|
||||
val balanced: Map<Pair<AbstractParty, AbstractParty>, Amount<Currency>> = mapOf(
|
||||
Pair(Pair(ALICE, BOB), Amount(100000000, GBP)),
|
||||
Pair(Pair(BOB, ALICE), Amount(200000000, GBP))
|
||||
)
|
||||
val expected: Map<Pair<PublicKey, PublicKey>, Amount<Currency>> = mapOf(
|
||||
Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(100000000, GBP))
|
||||
val expected: Map<Pair<AbstractParty, AbstractParty>, Amount<Currency>> = mapOf(
|
||||
Pair(Pair(BOB, ALICE), Amount(100000000, GBP))
|
||||
)
|
||||
val actual = netAmountsDue(balanced)
|
||||
assertEquals(expected, actual)
|
||||
@ -852,16 +853,16 @@ class ObligationTests {
|
||||
|
||||
@Test
|
||||
fun `summing empty balances due between parties`() {
|
||||
val empty = emptyMap<Pair<PublicKey, PublicKey>, Amount<Currency>>()
|
||||
val expected = emptyMap<PublicKey, Long>()
|
||||
val empty = emptyMap<Pair<AbstractParty, AbstractParty>, Amount<Currency>>()
|
||||
val expected = emptyMap<AbstractParty, Long>()
|
||||
val actual = sumAmountsDue(empty)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `summing balances due between parties`() {
|
||||
val simple: Map<Pair<PublicKey, PublicKey>, Amount<Currency>> = mapOf(Pair(Pair(ALICE_PUBKEY, BOB_PUBKEY), Amount(100000000, GBP)))
|
||||
val expected: Map<PublicKey, Long> = mapOf(Pair(ALICE_PUBKEY, -100000000L), Pair(BOB_PUBKEY, 100000000L))
|
||||
val simple: Map<Pair<AbstractParty, AbstractParty>, Amount<Currency>> = mapOf(Pair(Pair(ALICE, BOB), Amount(100000000, GBP)))
|
||||
val expected: Map<AbstractParty, Long> = mapOf(Pair(ALICE, -100000000L), Pair(BOB, 100000000L))
|
||||
val actual = sumAmountsDue(simple)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -869,11 +870,11 @@ class ObligationTests {
|
||||
@Test
|
||||
fun `summing balances due between parties which net to zero`() {
|
||||
// Now try it with two balances, which cancel each other out
|
||||
val balanced: Map<Pair<PublicKey, PublicKey>, Amount<Currency>> = mapOf(
|
||||
Pair(Pair(ALICE_PUBKEY, BOB_PUBKEY), Amount(100000000, GBP)),
|
||||
Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(100000000, GBP))
|
||||
val balanced: Map<Pair<AbstractParty, AbstractParty>, Amount<Currency>> = mapOf(
|
||||
Pair(Pair(ALICE, BOB), Amount(100000000, GBP)),
|
||||
Pair(Pair(BOB, ALICE), Amount(100000000, GBP))
|
||||
)
|
||||
val expected: Map<PublicKey, Long> = emptyMap() // Zero balances are stripped before returning
|
||||
val expected: Map<AbstractParty, Long> = emptyMap() // Zero balances are stripped before returning
|
||||
val actual = sumAmountsDue(balanced)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.TransactionType
|
||||
import net.corda.core.crypto.NullSignature
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.testing.*
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
@ -25,7 +26,7 @@ class ContractStateGenerator : Generator<ContractState>(ContractState::class.jav
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): ContractState {
|
||||
return Cash.State(
|
||||
amount = AmountGenerator(IssuedGenerator(CurrencyGenerator())).generate(random, status),
|
||||
owner = PublicKeyGenerator().generate(random, status)
|
||||
owner = AnonymousParty(PublicKeyGenerator().generate(random, status))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -54,8 +54,8 @@ class CashPaymentFlowTests {
|
||||
net.runNetwork()
|
||||
val paymentTx = future.getOrThrow()
|
||||
val states = paymentTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>()
|
||||
val ourState = states.single { it.owner != payTo.owningKey }
|
||||
val paymentState = states.single { it.owner == payTo.owningKey }
|
||||
val ourState = states.single { it.owner.owningKey != payTo.owningKey }
|
||||
val paymentState = states.single { it.owner.owningKey == payTo.owningKey }
|
||||
assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), paymentState.amount)
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,11 @@ import io.requery.rx.KotlinRxEntityStore
|
||||
import io.requery.sql.*
|
||||
import io.requery.sql.platform.Generic
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.schemas.requery.converters.InstantConverter
|
||||
@ -19,17 +23,16 @@ import net.corda.core.schemas.requery.converters.VaultStateStatusConverter
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.core.utilities.BOB
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.core.utilities.DUMMY_PUBKEY_1
|
||||
import net.corda.core.utilities.DUMMY_PUBKEY_2
|
||||
import org.h2.jdbcx.JdbcDataSource
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import rx.Observable
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
@ -81,12 +84,12 @@ class VaultSchemaTest {
|
||||
private class VaultNoopContract : Contract {
|
||||
override val legalContractReference = SecureHash.sha256("")
|
||||
|
||||
data class VaultNoopState(override val owner: PublicKey) : OwnableState {
|
||||
data class VaultNoopState(override val owner: AbstractParty) : OwnableState {
|
||||
override val contract = VaultNoopContract()
|
||||
override val participants: List<PublicKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = listOf(owner)
|
||||
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Create(), copy(owner = newOwner))
|
||||
override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Create(), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
@ -101,10 +104,10 @@ class VaultSchemaTest {
|
||||
private fun setupDummyData() {
|
||||
// dummy Transaction
|
||||
val notary: Party = DUMMY_NOTARY
|
||||
val inState1 = TransactionState(DummyContract.SingleOwnerState(0, DUMMY_PUBKEY_1), notary)
|
||||
val inState1 = TransactionState(DummyContract.SingleOwnerState(0, ALICE), notary)
|
||||
val inState2 = TransactionState(DummyContract.MultiOwnerState(0,
|
||||
listOf(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2)), notary)
|
||||
val inState3 = TransactionState(VaultNoopContract.VaultNoopState(DUMMY_PUBKEY_1), notary)
|
||||
listOf(ALICE, BOB)), notary)
|
||||
val inState3 = TransactionState(VaultNoopContract.VaultNoopState(ALICE), notary)
|
||||
val outState1 = inState1.copy()
|
||||
val outState2 = inState2.copy()
|
||||
val outState3 = inState3.copy()
|
||||
@ -132,9 +135,9 @@ class VaultSchemaTest {
|
||||
|
||||
private fun createTxnWithTwoStateTypes(): LedgerTransaction {
|
||||
val notary: Party = DUMMY_NOTARY
|
||||
val inState1 = TransactionState(DummyContract.SingleOwnerState(0, DUMMY_PUBKEY_1), notary)
|
||||
val inState1 = TransactionState(DummyContract.SingleOwnerState(0, ALICE), notary)
|
||||
val inState2 = TransactionState(DummyContract.MultiOwnerState(0,
|
||||
listOf(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2)), notary)
|
||||
listOf(ALICE, BOB)), notary)
|
||||
val outState1 = inState1.copy()
|
||||
val outState2 = inState2.copy()
|
||||
val state1TxHash = SecureHash.randomSHA256()
|
||||
|
@ -57,7 +57,7 @@ class BFTNotaryServiceTests : NodeBasedTest() {
|
||||
firstSpend.resultFuture.getOrThrow()
|
||||
|
||||
val secondSpendTx = TransactionType.General.Builder(notaryParty).withItems(inputState).run {
|
||||
val dummyState = DummyContract.SingleOwnerState(0, alice.info.legalIdentity.owningKey)
|
||||
val dummyState = DummyContract.SingleOwnerState(0, alice.info.legalIdentity)
|
||||
addOutputState(dummyState)
|
||||
signWith(aliceKey)
|
||||
toSignedTransaction(false)
|
||||
|
@ -46,7 +46,7 @@ class RaftNotaryServiceTests : NodeBasedTest() {
|
||||
firstSpend.resultFuture.getOrThrow()
|
||||
|
||||
val secondSpendTx = TransactionType.General.Builder(notaryParty).withItems(inputState).run {
|
||||
val dummyState = DummyContract.SingleOwnerState(0, alice.info.legalIdentity.owningKey)
|
||||
val dummyState = DummyContract.SingleOwnerState(0, alice.info.legalIdentity)
|
||||
addOutputState(dummyState)
|
||||
signWith(aliceKey)
|
||||
toSignedTransaction(false)
|
||||
|
@ -115,7 +115,7 @@ class ContractUpgradeHandler(otherSide: Party) : AbstractStateReplacementFlow.Ac
|
||||
val proposedTx = proposal.stx.tx
|
||||
val expectedTx = ContractUpgradeFlow.assembleBareTx(oldStateAndRef, proposal.modification).toWireTransaction()
|
||||
requireThat {
|
||||
"The instigator is one of the participants" using (otherSide.owningKey in oldStateAndRef.state.data.participants)
|
||||
"The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants)
|
||||
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification == authorisedUpgrade)
|
||||
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package net.corda.node.services.identity
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
@ -16,7 +17,6 @@ import java.security.cert.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
/**
|
||||
* Simple identity service which caches parties and provides functionality for efficient lookup.
|
||||
@ -44,7 +44,7 @@ class InMemoryIdentityService : SingletonSerializeAsToken(), IdentityService {
|
||||
@Deprecated("Use partyFromX500Name")
|
||||
override fun partyFromName(name: String): Party? = principalToParties[X500Name(name)]
|
||||
override fun partyFromX500Name(principal: X500Name): Party? = principalToParties[principal]
|
||||
override fun partyFromAnonymous(party: AnonymousParty): Party? = partyFromKey(party.owningKey)
|
||||
override fun partyFromAnonymous(party: AbstractParty): Party? = partyFromKey(party.owningKey)
|
||||
override fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
|
||||
|
||||
@Throws(IdentityService.UnknownAnonymousPartyException::class)
|
||||
|
@ -11,11 +11,12 @@ import io.requery.kotlin.notNull
|
||||
import io.requery.query.RowExpression
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.OnLedgerAsset
|
||||
import net.corda.contracts.clause.AbstractConserveAmount
|
||||
import net.corda.core.ThreadBox
|
||||
import net.corda.core.bufferUntilSubscribed
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.containsAny
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
@ -468,7 +469,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
|
||||
@Suspendable
|
||||
override fun generateSpend(tx: TransactionBuilder,
|
||||
amount: Amount<Currency>,
|
||||
to: PublicKey,
|
||||
to: AbstractParty,
|
||||
onlyFromParties: Set<AbstractParty>?): Pair<TransactionBuilder, List<PublicKey>> {
|
||||
// Retrieve unspent and unlocked cash states that meet our spending criteria.
|
||||
val acceptableCoins = unconsumedStatesForSpending<Cash.State>(amount, onlyFromParties, tx.notary, tx.lockId)
|
||||
@ -477,7 +478,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
|
||||
{ Cash().generateMoveCommand() })
|
||||
}
|
||||
|
||||
private fun deriveState(txState: TransactionState<Cash.State>, amount: Amount<Issued<Currency>>, owner: PublicKey)
|
||||
private fun deriveState(txState: TransactionState<Cash.State>, amount: Amount<Issued<Currency>>, owner: AbstractParty)
|
||||
= txState.copy(data = txState.data.copy(amount = amount, owner = owner))
|
||||
|
||||
private fun makeUpdate(tx: WireTransaction, ourKeys: Set<PublicKey>): Vault.Update {
|
||||
@ -528,7 +529,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
|
||||
}
|
||||
|
||||
private fun isRelevant(state: ContractState, ourKeys: Set<PublicKey>) = when (state) {
|
||||
is OwnableState -> state.owner.containsAny(ourKeys)
|
||||
is OwnableState -> state.owner.owningKey.containsAny(ourKeys)
|
||||
// It's potentially of interest to the vault
|
||||
is LinearState -> state.isRelevant(ourKeys)
|
||||
else -> false
|
||||
|
@ -91,7 +91,7 @@ class CordaRPCOpsImplTest {
|
||||
|
||||
val expectedState = Cash.State(Amount(quantity,
|
||||
Issued(aliceNode.info.legalIdentity.ref(ref), GBP)),
|
||||
recipient.owningKey)
|
||||
recipient)
|
||||
|
||||
var issueSmId: StateMachineRunId? = null
|
||||
stateMachineUpdates.expectEvents {
|
||||
|
@ -14,6 +14,7 @@ import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.map
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.NodeInfo
|
||||
@ -97,7 +98,7 @@ class TwoPartyTradeFlowTests {
|
||||
}
|
||||
|
||||
val alicesFakePaper = aliceNode.database.transaction {
|
||||
fillUpForSeller(false, aliceNode.info.legalIdentity.owningKey,
|
||||
fillUpForSeller(false, aliceNode.info.legalIdentity,
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null, notaryNode.info.notaryIdentity).second
|
||||
}
|
||||
|
||||
@ -144,7 +145,7 @@ class TwoPartyTradeFlowTests {
|
||||
}
|
||||
|
||||
val alicesFakePaper = aliceNode.database.transaction {
|
||||
fillUpForSeller(false, aliceNode.info.legalIdentity.owningKey,
|
||||
fillUpForSeller(false, aliceNode.info.legalIdentity,
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null, notaryNode.info.notaryIdentity).second
|
||||
}
|
||||
|
||||
@ -195,7 +196,7 @@ class TwoPartyTradeFlowTests {
|
||||
bobNode.services.fillWithSomeTestCash(2000.DOLLARS, outputNotary = notaryNode.info.notaryIdentity)
|
||||
}
|
||||
val alicesFakePaper = aliceNode.database.transaction {
|
||||
fillUpForSeller(false, aliceNode.info.legalIdentity.owningKey,
|
||||
fillUpForSeller(false, aliceNode.info.legalIdentity,
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null, notaryNode.info.notaryIdentity).second
|
||||
}
|
||||
insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, aliceKey, notaryKey)
|
||||
@ -301,6 +302,7 @@ class TwoPartyTradeFlowTests {
|
||||
val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name)
|
||||
val aliceNode = makeNodeWithTracking(notaryNode.info.address, ALICE.name)
|
||||
val bobNode = makeNodeWithTracking(notaryNode.info.address, BOB.name)
|
||||
val alice = aliceNode.info.legalIdentity
|
||||
val aliceKey = aliceNode.services.legalIdentityKey
|
||||
|
||||
ledger(aliceNode.services) {
|
||||
@ -317,12 +319,13 @@ class TwoPartyTradeFlowTests {
|
||||
}
|
||||
|
||||
val extraKey = bobNode.keyManagement.freshKey()
|
||||
val bobsFakeCash = fillUpForBuyer(false, extraKey.public,
|
||||
val extraPublicKey = extraKey.public
|
||||
val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(extraPublicKey),
|
||||
DUMMY_CASH_ISSUER.party,
|
||||
notaryNode.info.notaryIdentity).second
|
||||
val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bobNode.services.legalIdentityKey, extraKey)
|
||||
val alicesFakePaper = aliceNode.database.transaction {
|
||||
fillUpForSeller(false, aliceNode.info.legalIdentity.owningKey,
|
||||
fillUpForSeller(false, alice,
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, attachmentID, notaryNode.info.notaryIdentity).second
|
||||
}
|
||||
val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, aliceKey)
|
||||
@ -416,13 +419,14 @@ class TwoPartyTradeFlowTests {
|
||||
attachment(ByteArrayInputStream(stream.toByteArray()))
|
||||
}
|
||||
|
||||
val bobsFakeCash = fillUpForBuyer(false, bobNode.keyManagement.freshKey().public,
|
||||
val bobsKey = bobNode.keyManagement.freshKey().public
|
||||
val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(bobsKey),
|
||||
DUMMY_CASH_ISSUER.party,
|
||||
notaryNode.info.notaryIdentity).second
|
||||
insertFakeTransactions(bobsFakeCash, bobNode, notaryNode)
|
||||
|
||||
val alicesFakePaper = aliceNode.database.transaction {
|
||||
fillUpForSeller(false, aliceNode.info.legalIdentity.owningKey,
|
||||
fillUpForSeller(false, aliceNode.info.legalIdentity,
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, attachmentID, notaryNode.info.notaryIdentity).second
|
||||
}
|
||||
|
||||
@ -520,14 +524,16 @@ class TwoPartyTradeFlowTests {
|
||||
val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name)
|
||||
val aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name)
|
||||
val bobNode = net.createPartyNode(notaryNode.info.address, BOB.name)
|
||||
val alice = aliceNode.info.legalIdentity
|
||||
val aliceKey = aliceNode.services.legalIdentityKey
|
||||
val bob = bobNode.info.legalIdentity
|
||||
val bobKey = bobNode.services.legalIdentityKey
|
||||
val issuer = MEGA_CORP.ref(1, 2, 3)
|
||||
|
||||
val bobsBadCash = fillUpForBuyer(bobError, bobKey.public, DUMMY_CASH_ISSUER.party,
|
||||
val bobsBadCash = fillUpForBuyer(bobError, bob, DUMMY_CASH_ISSUER.party,
|
||||
notaryNode.info.notaryIdentity).second
|
||||
val alicesFakePaper = aliceNode.database.transaction {
|
||||
fillUpForSeller(aliceError, aliceNode.info.legalIdentity.owningKey,
|
||||
fillUpForSeller(aliceError, alice,
|
||||
1200.DOLLARS `issued by` issuer, null, notaryNode.info.notaryIdentity).second
|
||||
}
|
||||
|
||||
@ -571,16 +577,16 @@ class TwoPartyTradeFlowTests {
|
||||
|
||||
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
|
||||
withError: Boolean,
|
||||
owner: PublicKey,
|
||||
issuer: AnonymousParty,
|
||||
owner: AbstractParty,
|
||||
issuer: AbstractParty,
|
||||
notary: Party): Pair<Vault<ContractState>, List<WireTransaction>> {
|
||||
val interimOwnerKey = MEGA_CORP_PUBKEY
|
||||
val interimOwner = MEGA_CORP
|
||||
// Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she
|
||||
// wants to sell to Bob.
|
||||
val eb1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) {
|
||||
// Issued money to itself.
|
||||
output("elbonian money 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` interimOwnerKey }
|
||||
output("elbonian money 2", notary = notary) { 1000.DOLLARS.CASH `issued by` issuer `owned by` interimOwnerKey }
|
||||
output("elbonian money 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` interimOwner }
|
||||
output("elbonian money 2", notary = notary) { 1000.DOLLARS.CASH `issued by` issuer `owned by` interimOwner }
|
||||
if (!withError) {
|
||||
command(issuer.owningKey) { Cash.Commands.Issue() }
|
||||
} else {
|
||||
@ -599,15 +605,15 @@ class TwoPartyTradeFlowTests {
|
||||
val bc1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) {
|
||||
input("elbonian money 1")
|
||||
output("bob cash 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` owner }
|
||||
command(interimOwnerKey) { Cash.Commands.Move() }
|
||||
command(interimOwner.owningKey) { Cash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
val bc2 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) {
|
||||
input("elbonian money 2")
|
||||
output("bob cash 2", notary = notary) { 300.DOLLARS.CASH `issued by` issuer `owned by` owner }
|
||||
output(notary = notary) { 700.DOLLARS.CASH `issued by` issuer `owned by` interimOwnerKey } // Change output.
|
||||
command(interimOwnerKey) { Cash.Commands.Move() }
|
||||
output(notary = notary) { 700.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } // Change output.
|
||||
command(interimOwner.owningKey) { Cash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
@ -617,7 +623,7 @@ class TwoPartyTradeFlowTests {
|
||||
|
||||
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
|
||||
withError: Boolean,
|
||||
owner: PublicKey,
|
||||
owner: AbstractParty,
|
||||
amount: Amount<Issued<Currency>>,
|
||||
attachmentID: SecureHash?,
|
||||
notary: Party): Pair<Vault<ContractState>, List<WireTransaction>> {
|
||||
|
@ -123,9 +123,9 @@ class NotaryChangeTests {
|
||||
val owner = node.info.legalIdentity.ref(0)
|
||||
val notary = notaryNode.info.notaryIdentity
|
||||
|
||||
val stateA = DummyContract.SingleOwnerState(Random().nextInt(), owner.party.owningKey)
|
||||
val stateB = DummyContract.SingleOwnerState(Random().nextInt(), owner.party.owningKey)
|
||||
val stateC = DummyContract.SingleOwnerState(Random().nextInt(), owner.party.owningKey)
|
||||
val stateA = DummyContract.SingleOwnerState(Random().nextInt(), owner.party)
|
||||
val stateB = DummyContract.SingleOwnerState(Random().nextInt(), owner.party)
|
||||
val stateC = DummyContract.SingleOwnerState(Random().nextInt(), owner.party)
|
||||
|
||||
val tx = TransactionType.General.Builder(null).apply {
|
||||
addCommand(Command(DummyContract.Commands.Create(), owner.party.owningKey))
|
||||
@ -162,7 +162,7 @@ fun issueState(node: AbstractNode, notaryNode: AbstractNode): StateAndRef<*> {
|
||||
|
||||
fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode, notaryNode: AbstractNode): StateAndRef<DummyContract.MultiOwnerState> {
|
||||
val state = TransactionState(DummyContract.MultiOwnerState(0,
|
||||
listOf(nodeA.info.legalIdentity.owningKey, nodeB.info.legalIdentity.owningKey)), notaryNode.info.notaryIdentity)
|
||||
listOf(nodeA.info.legalIdentity, nodeB.info.legalIdentity)), notaryNode.info.notaryIdentity)
|
||||
val tx = TransactionType.NotaryChange.Builder(notaryNode.info.notaryIdentity).withItems(state)
|
||||
val nodeAKey = nodeA.services.legalIdentityKey
|
||||
val nodeBKey = nodeB.services.legalIdentityKey
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.NullPublicKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.serialization.storageKryo
|
||||
@ -129,7 +130,7 @@ class RequeryConfigurationTest {
|
||||
index = txnState.index
|
||||
stateStatus = Vault.StateStatus.UNCONSUMED
|
||||
contractStateClassName = DummyContract.SingleOwnerState::class.java.name
|
||||
contractState = DummyContract.SingleOwnerState(owner = DUMMY_PUBKEY_1).serialize(storageKryo()).bytes
|
||||
contractState = DummyContract.SingleOwnerState(owner = AnonymousParty(DUMMY_PUBKEY_1)).serialize(storageKryo()).bytes
|
||||
notaryName = txn.tx.notary!!.name.toString()
|
||||
notaryKey = txn.tx.notary!!.owningKey.toBase58String()
|
||||
recordedTime = Instant.now()
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.days
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRef
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
@ -113,7 +114,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
}
|
||||
|
||||
class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant) : LinearState, SchedulableState {
|
||||
override val participants: List<PublicKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
override val linearId = UniqueIdentifier()
|
||||
|
@ -2,12 +2,13 @@ package net.corda.node.services.events
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.crypto.containsAny
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.flows.SchedulableFlow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.linearHeadsOfType
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
@ -46,10 +47,10 @@ class ScheduledFlowTests {
|
||||
}
|
||||
}
|
||||
|
||||
override val participants: List<PublicKey> = listOf(source.owningKey, destination.owningKey)
|
||||
override val participants: List<AbstractParty> = listOf(source, destination)
|
||||
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||
return participants.any { it.containsAny(ourKeys) }
|
||||
return participants.any { it.owningKey.containsAny(ourKeys) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ class DataVendingServiceTests {
|
||||
@Test
|
||||
fun `notify of transaction`() {
|
||||
val (vaultServiceNode, registerNode) = network.createTwoNodes()
|
||||
val beneficiary = vaultServiceNode.info.legalIdentity.owningKey
|
||||
val beneficiary = vaultServiceNode.info.legalIdentity
|
||||
val deposit = registerNode.info.legalIdentity.ref(1)
|
||||
network.runNetwork()
|
||||
|
||||
@ -67,7 +67,7 @@ class DataVendingServiceTests {
|
||||
@Test
|
||||
fun `notify failure`() {
|
||||
val (vaultServiceNode, registerNode) = network.createTwoNodes()
|
||||
val beneficiary = vaultServiceNode.info.legalIdentity.owningKey
|
||||
val beneficiary = vaultServiceNode.info.legalIdentity
|
||||
val deposit = MEGA_CORP.ref(1)
|
||||
network.runNetwork()
|
||||
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.schemas.PersistentState
|
||||
@ -86,7 +87,7 @@ class HibernateObserverTests {
|
||||
override val contract: Contract
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
override val participants: List<CompositeKey>
|
||||
override val participants: List<AbstractParty>
|
||||
get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.testing.fillWithSomeTestCash
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.node.services.StatesNotAvailableException
|
||||
import net.corda.core.node.services.TxWritableStorageService
|
||||
import net.corda.core.node.services.VaultService
|
||||
@ -401,11 +402,11 @@ class NodeVaultServiceTest {
|
||||
fun addNoteToTransaction() {
|
||||
database.transaction {
|
||||
|
||||
val freshKey = services.legalIdentityKey
|
||||
val freshKey = services.legalIdentityKey.public
|
||||
|
||||
// Issue a txn to Send us some Money
|
||||
val usefulTX = TransactionType.General.Builder(null).apply {
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), AnonymousParty(freshKey), DUMMY_NOTARY)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
}.toSignedTransaction()
|
||||
|
||||
@ -418,7 +419,7 @@ class NodeVaultServiceTest {
|
||||
|
||||
// Issue more Money (GBP)
|
||||
val anotherTX = TransactionType.General.Builder(null).apply {
|
||||
Cash().generateIssue(this, 200.POUNDS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
|
||||
Cash().generateIssue(this, 200.POUNDS `issued by` MEGA_CORP.ref(1), AnonymousParty(freshKey), DUMMY_NOTARY)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
}.toSignedTransaction()
|
||||
|
||||
|
@ -206,9 +206,9 @@ class VaultQueryTests {
|
||||
fun `unconsumed states by participants`() {
|
||||
database.transaction {
|
||||
|
||||
services.fillWithSomeTestLinearStates(2, UniqueIdentifier("TEST"), participants = listOf(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY))
|
||||
services.fillWithSomeTestDeals(listOf("456"), 3, participants = listOf(MEGA_CORP_PUBKEY, BIG_CORP_PUBKEY))
|
||||
services.fillWithSomeTestDeals(listOf("123", "789"), participants = listOf(BIG_CORP_PUBKEY, MINI_CORP_PUBKEY))
|
||||
services.fillWithSomeTestLinearStates(2, UniqueIdentifier("TEST"), participants = listOf(MEGA_CORP, MINI_CORP))
|
||||
services.fillWithSomeTestDeals(listOf("456"), 3, participants = listOf(MEGA_CORP, BIG_CORP))
|
||||
services.fillWithSomeTestDeals(listOf("123", "789"), participants = listOf(BIG_CORP, MINI_CORP))
|
||||
|
||||
// DOCSTART VaultQueryExample5
|
||||
val criteria = VaultQueryCriteria(participantIdentities = listOf(MEGA_CORP.name, MINI_CORP.name))
|
||||
|
@ -4,17 +4,17 @@ import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.testing.*
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.node.services.consumedStates
|
||||
import net.corda.core.node.services.unconsumedStates
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.BOB_KEY
|
||||
import net.corda.core.utilities.BOB
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.core.utilities.LogHelper
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.node.utilities.transaction
|
||||
import net.corda.testing.BOB_PUBKEY
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MEGA_CORP_KEY
|
||||
import net.corda.testing.node.MockServices
|
||||
@ -79,7 +79,7 @@ class VaultWithCashTest {
|
||||
|
||||
val state = w[0].state.data
|
||||
assertEquals(30.45.DOLLARS `issued by` DUMMY_CASH_ISSUER, state.amount)
|
||||
assertEquals(services.key.public, state.owner)
|
||||
assertEquals(services.key.public, state.owner.owningKey)
|
||||
|
||||
assertEquals(34.70.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w[2].state.data).amount)
|
||||
assertEquals(34.85.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w[1].state.data).amount)
|
||||
@ -91,8 +91,9 @@ class VaultWithCashTest {
|
||||
database.transaction {
|
||||
// A tx that sends us money.
|
||||
val freshKey = services.keyManagementService.freshKey()
|
||||
val freshPublicKey = freshKey.public
|
||||
val usefulTX = TransactionType.General.Builder(null).apply {
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), AnonymousParty(freshPublicKey), DUMMY_NOTARY)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
}.toSignedTransaction()
|
||||
|
||||
@ -101,7 +102,7 @@ class VaultWithCashTest {
|
||||
|
||||
// A tx that spends our money.
|
||||
val spendTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
vault.generateSpend(this, 80.DOLLARS, BOB_PUBKEY)
|
||||
vault.generateSpend(this, 80.DOLLARS, BOB)
|
||||
signWith(freshKey)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
@ -110,7 +111,7 @@ class VaultWithCashTest {
|
||||
|
||||
// A tx that doesn't send us anything.
|
||||
val irrelevantTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB, DUMMY_NOTARY)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
@ -128,13 +129,14 @@ class VaultWithCashTest {
|
||||
@Test
|
||||
fun `issue and attempt double spend`() {
|
||||
val freshKey = services.keyManagementService.freshKey()
|
||||
val freshPublicKey = freshKey.public
|
||||
|
||||
database.transaction {
|
||||
// A tx that sends us money.
|
||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L),
|
||||
issuedBy = MEGA_CORP.ref(1),
|
||||
issuerKey = MEGA_CORP_KEY,
|
||||
ownedBy = freshKey.public)
|
||||
ownedBy = AnonymousParty(freshPublicKey))
|
||||
println("Cash balance: ${vault.cashBalances[USD]}")
|
||||
|
||||
assertThat(vault.unconsumedStates<Cash.State>()).hasSize(10)
|
||||
@ -149,7 +151,7 @@ class VaultWithCashTest {
|
||||
try {
|
||||
val txn1 =
|
||||
TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
vault.generateSpend(this, 60.DOLLARS, BOB_PUBKEY)
|
||||
vault.generateSpend(this, 60.DOLLARS, BOB)
|
||||
signWith(freshKey)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
@ -181,7 +183,7 @@ class VaultWithCashTest {
|
||||
try {
|
||||
val txn2 =
|
||||
TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
vault.generateSpend(this, 80.DOLLARS, BOB_PUBKEY)
|
||||
vault.generateSpend(this, 80.DOLLARS, BOB)
|
||||
signWith(freshKey)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
@ -219,12 +221,14 @@ class VaultWithCashTest {
|
||||
fun `branching LinearStates fails to verify`() {
|
||||
database.transaction {
|
||||
val freshKey = services.keyManagementService.freshKey()
|
||||
val freshPublicKey = freshKey.public
|
||||
val freshIdentity = AnonymousParty(freshPublicKey)
|
||||
val linearId = UniqueIdentifier()
|
||||
|
||||
// Issue a linear state
|
||||
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity)))
|
||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity)))
|
||||
signWith(freshKey)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
@ -239,12 +243,14 @@ class VaultWithCashTest {
|
||||
fun `sequencing LinearStates works`() {
|
||||
database.transaction {
|
||||
val freshKey = services.keyManagementService.freshKey()
|
||||
val freshPublicKey = freshKey.public
|
||||
val freshIdentity = AnonymousParty(freshPublicKey)
|
||||
|
||||
val linearId = UniqueIdentifier()
|
||||
|
||||
// Issue a linear state
|
||||
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity)))
|
||||
signWith(freshKey)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
@ -256,7 +262,7 @@ class VaultWithCashTest {
|
||||
|
||||
// Move the same state
|
||||
val dummyMove = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity)))
|
||||
addInputState(dummyIssue.tx.outRef<LinearState>(0))
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
@ -272,8 +278,9 @@ class VaultWithCashTest {
|
||||
fun `spending cash in vault of mixed state types works`() {
|
||||
|
||||
val freshKey = services.keyManagementService.freshKey()
|
||||
val freshPublicKey = freshKey.public
|
||||
database.transaction {
|
||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L), ownedBy = freshKey.public)
|
||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L), ownedBy = AnonymousParty(freshPublicKey))
|
||||
services.fillWithSomeTestCash(100.SWISS_FRANCS, DUMMY_NOTARY, 2, 2, Random(0L))
|
||||
services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 1, 1, Random(0L))
|
||||
val cash = vault.unconsumedStates<Cash.State>()
|
||||
@ -287,7 +294,7 @@ class VaultWithCashTest {
|
||||
database.transaction {
|
||||
// A tx that spends our money.
|
||||
val spendTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
vault.generateSpend(this, 80.DOLLARS, BOB_PUBKEY)
|
||||
vault.generateSpend(this, 80.DOLLARS, BOB)
|
||||
signWith(freshKey)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
@ -305,6 +312,8 @@ class VaultWithCashTest {
|
||||
fun `consuming multiple contract state types in same transaction`() {
|
||||
|
||||
val freshKey = services.keyManagementService.freshKey()
|
||||
val freshPublicKey = freshKey.public
|
||||
val freshIdentity = AnonymousParty(freshPublicKey)
|
||||
database.transaction {
|
||||
|
||||
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
|
||||
@ -317,8 +326,8 @@ class VaultWithCashTest {
|
||||
|
||||
// Create a txn consuming different contract types
|
||||
val dummyMove = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||
addOutputState(DummyLinearContract.State(participants = listOf(freshKey.public)))
|
||||
addOutputState(DummyDealContract.State(ref = "999", participants = listOf(freshKey.public)))
|
||||
addOutputState(DummyLinearContract.State(participants = listOf(freshIdentity)))
|
||||
addOutputState(DummyDealContract.State(ref = "999", participants = listOf(freshIdentity)))
|
||||
addInputState(linearStates.first())
|
||||
addInputState(deals.first())
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.startTrackedFlow
|
||||
import net.corda.core.sizedInputStreamAndHash
|
||||
@ -179,6 +180,6 @@ class AttachmentContract : Contract {
|
||||
|
||||
data class State(val hash: SecureHash.SHA256) : ContractState {
|
||||
override val contract: Contract = AttachmentContract()
|
||||
override val participants: List<PublicKey> = emptyList()
|
||||
override val participants: List<AbstractParty> = emptyList()
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class IRSDemoTest : IntegrationTestCategory {
|
||||
val vaultUpdates = proxy.vaultAndUpdates().second
|
||||
|
||||
val fixingDates = vaultUpdates.map { update ->
|
||||
val irsStates = update.produced.map { it.state.data }.filterIsInstance<InterestRateSwap.State<*>>()
|
||||
val irsStates = update.produced.map { it.state.data }.filterIsInstance<InterestRateSwap.State>()
|
||||
irsStates.mapNotNull { it.calculation.nextFixingDate() }.max()
|
||||
}.cache().toBlocking()
|
||||
|
||||
|
@ -40,22 +40,22 @@ class InterestRateSwapAPI(val rpc: CordaRPCOps) {
|
||||
|
||||
private val logger = loggerFor<InterestRateSwapAPI>()
|
||||
|
||||
private fun generateDealLink(deal: InterestRateSwap.State<*>) = "/api/irs/deals/" + deal.common.tradeID
|
||||
private fun generateDealLink(deal: InterestRateSwap.State) = "/api/irs/deals/" + deal.common.tradeID
|
||||
|
||||
private fun getDealByRef(ref: String): InterestRateSwap.State<*>? {
|
||||
private fun getDealByRef(ref: String): InterestRateSwap.State? {
|
||||
val (vault, vaultUpdates) = rpc.vaultAndUpdates()
|
||||
vaultUpdates.notUsed()
|
||||
val states = vault.filterStatesOfType<InterestRateSwap.State<*>>().filter { it.state.data.ref == ref }
|
||||
val states = vault.filterStatesOfType<InterestRateSwap.State>().filter { it.state.data.ref == ref }
|
||||
return if (states.isEmpty()) null else {
|
||||
val deals = states.map { it.state.data }
|
||||
return if (deals.isEmpty()) null else deals[0]
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAllDeals(): Array<InterestRateSwap.State<*>> {
|
||||
private fun getAllDeals(): Array<InterestRateSwap.State> {
|
||||
val (vault, vaultUpdates) = rpc.vaultAndUpdates()
|
||||
vaultUpdates.notUsed()
|
||||
val states = vault.filterStatesOfType<InterestRateSwap.State<*>>()
|
||||
val states = vault.filterStatesOfType<InterestRateSwap.State>()
|
||||
val swaps = states.map { it.state.data }.toTypedArray()
|
||||
return swaps
|
||||
}
|
||||
@ -63,14 +63,14 @@ class InterestRateSwapAPI(val rpc: CordaRPCOps) {
|
||||
@GET
|
||||
@Path("deals")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
fun fetchDeals(): Array<InterestRateSwap.State<*>> = getAllDeals()
|
||||
fun fetchDeals(): Array<InterestRateSwap.State> = getAllDeals()
|
||||
|
||||
@POST
|
||||
@Path("deals")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
fun storeDeal(newDeal: InterestRateSwap.State<Party>): Response {
|
||||
fun storeDeal(newDeal: InterestRateSwap.State): Response {
|
||||
return try {
|
||||
rpc.startFlow(AutoOfferFlow::Requester, newDeal.toAnonymous()).returnValue.getOrThrow()
|
||||
rpc.startFlow(AutoOfferFlow::Requester, newDeal).returnValue.getOrThrow()
|
||||
Response.created(URI.create(generateDealLink(newDeal))).build()
|
||||
} catch (ex: Throwable) {
|
||||
logger.info("Exception when creating deal: $ex")
|
||||
|
@ -2,7 +2,8 @@ package net.corda.irs.contract
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.clauses.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.containsAny
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
@ -307,8 +308,8 @@ class InterestRateSwap : Contract {
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
open class FixedLeg<P : AbstractParty>(
|
||||
var fixedRatePayer: P,
|
||||
open class FixedLeg(
|
||||
var fixedRatePayer: AbstractParty,
|
||||
notional: Amount<Currency>,
|
||||
paymentFrequency: Frequency,
|
||||
effectiveDate: LocalDate,
|
||||
@ -335,7 +336,7 @@ class InterestRateSwap : Contract {
|
||||
if (other?.javaClass != javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as FixedLeg<*>
|
||||
other as FixedLeg
|
||||
|
||||
if (fixedRatePayer != other.fixedRatePayer) return false
|
||||
if (fixedRate != other.fixedRate) return false
|
||||
@ -347,7 +348,7 @@ class InterestRateSwap : Contract {
|
||||
override fun hashCode() = super.hashCode() + 31 * Objects.hash(fixedRatePayer, fixedRate, rollConvention)
|
||||
|
||||
// Can't autogenerate as not a data class :-(
|
||||
fun copy(fixedRatePayer: P = this.fixedRatePayer,
|
||||
fun copy(fixedRatePayer: AbstractParty = this.fixedRatePayer,
|
||||
notional: Amount<Currency> = this.notional,
|
||||
paymentFrequency: Frequency = this.paymentFrequency,
|
||||
effectiveDate: LocalDate = this.effectiveDate,
|
||||
@ -365,17 +366,11 @@ class InterestRateSwap : Contract {
|
||||
fixedRatePayer, notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate,
|
||||
terminationDateAdjustment, dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay,
|
||||
paymentCalendar, interestPeriodAdjustment, fixedRate, rollConvention)
|
||||
|
||||
fun toAnonymous(): FixedLeg<AnonymousParty> {
|
||||
return FixedLeg(fixedRatePayer.toAnonymous(), notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate, terminationDateAdjustment,
|
||||
dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment,
|
||||
fixedRate, rollConvention)
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
open class FloatingLeg<P : AbstractParty>(
|
||||
var floatingRatePayer: P,
|
||||
open class FloatingLeg(
|
||||
var floatingRatePayer: AbstractParty,
|
||||
notional: Amount<Currency>,
|
||||
paymentFrequency: Frequency,
|
||||
effectiveDate: LocalDate,
|
||||
@ -411,7 +406,7 @@ class InterestRateSwap : Contract {
|
||||
if (other?.javaClass != javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as FloatingLeg<*>
|
||||
other as FloatingLeg
|
||||
|
||||
if (floatingRatePayer != other.floatingRatePayer) return false
|
||||
if (rollConvention != other.rollConvention) return false
|
||||
@ -433,7 +428,7 @@ class InterestRateSwap : Contract {
|
||||
index, indexSource, indexTenor)
|
||||
|
||||
|
||||
fun copy(floatingRatePayer: P = this.floatingRatePayer,
|
||||
fun copy(floatingRatePayer: AbstractParty = this.floatingRatePayer,
|
||||
notional: Amount<Currency> = this.notional,
|
||||
paymentFrequency: Frequency = this.paymentFrequency,
|
||||
effectiveDate: LocalDate = this.effectiveDate,
|
||||
@ -462,13 +457,6 @@ class InterestRateSwap : Contract {
|
||||
paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment, rollConvention,
|
||||
fixingRollConvention, resetDayInMonth, fixingPeriod, resetRule, fixingsPerPayment,
|
||||
fixingCalendar, index, indexSource, indexTenor)
|
||||
|
||||
fun toAnonymous(): FloatingLeg<AnonymousParty> {
|
||||
return FloatingLeg(floatingRatePayer.toAnonymous(), notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate, terminationDateAdjustment,
|
||||
dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment,
|
||||
rollConvention, fixingRollConvention, resetDayInMonth, fixingPeriodOffset, resetRule, fixingsPerPayment,
|
||||
fixingCalendar, index, indexSource, indexTenor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.Timestamped(), Clauses.Group()), tx.commands.select<Commands>())
|
||||
@ -478,7 +466,7 @@ class InterestRateSwap : Contract {
|
||||
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides
|
||||
* helper functions for the clauses.
|
||||
*/
|
||||
abstract class AbstractIRSClause : Clause<State<*>, Commands, UniqueIdentifier>() {
|
||||
abstract class AbstractIRSClause : Clause<State, Commands, UniqueIdentifier>() {
|
||||
// These functions may make more sense to use for basket types, but for now let's leave them here
|
||||
fun checkLegDates(legs: List<CommonLeg>) {
|
||||
requireThat {
|
||||
@ -494,7 +482,7 @@ class InterestRateSwap : Contract {
|
||||
"The notional for all legs must be the same" using legs.all { it.notional == legs[0].notional }
|
||||
}
|
||||
for (leg: CommonLeg in legs) {
|
||||
if (leg is FixedLeg<*>) {
|
||||
if (leg is FixedLeg) {
|
||||
requireThat {
|
||||
// TODO: Confirm: would someone really enter a swap with a negative fixed rate?
|
||||
"Fixed leg rate must be positive" using leg.fixedRate.isPositive()
|
||||
@ -520,9 +508,9 @@ class InterestRateSwap : Contract {
|
||||
}
|
||||
}
|
||||
|
||||
class Group : GroupClauseVerifier<State<*>, Commands, UniqueIdentifier>(AnyOf(Agree(), Fix(), Pay(), Mature())) {
|
||||
class Group : GroupClauseVerifier<State, Commands, UniqueIdentifier>(AnyOf(Agree(), Fix(), Pay(), Mature())) {
|
||||
// Group by Trade ID for in / out states
|
||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State<*>, UniqueIdentifier>> {
|
||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, UniqueIdentifier>> {
|
||||
return tx.groupStates { state -> state.linearId }
|
||||
}
|
||||
}
|
||||
@ -543,12 +531,12 @@ class InterestRateSwap : Contract {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Agree::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State<*>>,
|
||||
outputs: List<State<*>>,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||
val command = tx.commands.requireSingleCommand<Commands.Agree>()
|
||||
val irs = outputs.filterIsInstance<State<*>>().single()
|
||||
val irs = outputs.filterIsInstance<State>().single()
|
||||
requireThat {
|
||||
"There are no in states for an agreement" using inputs.isEmpty()
|
||||
"There are events in the fix schedule" using (irs.calculation.fixedLegPaymentSchedule.isNotEmpty())
|
||||
@ -579,13 +567,13 @@ class InterestRateSwap : Contract {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Refix::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State<*>>,
|
||||
outputs: List<State<*>>,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||
val command = tx.commands.requireSingleCommand<Commands.Refix>()
|
||||
val irs = outputs.filterIsInstance<State<*>>().single()
|
||||
val prevIrs = inputs.filterIsInstance<State<*>>().single()
|
||||
val irs = outputs.filterIsInstance<State>().single()
|
||||
val prevIrs = inputs.filterIsInstance<State>().single()
|
||||
val paymentDifferences = getFloatingLegPaymentsDifferences(prevIrs.calculation.floatingLegPaymentSchedule, irs.calculation.floatingLegPaymentSchedule)
|
||||
|
||||
// Having both of these tests are "redundant" as far as verify() goes, however, by performing both
|
||||
@ -624,8 +612,8 @@ class InterestRateSwap : Contract {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Pay::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State<*>>,
|
||||
outputs: List<State<*>>,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||
val command = tx.commands.requireSingleCommand<Commands.Pay>()
|
||||
@ -640,12 +628,12 @@ class InterestRateSwap : Contract {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Mature::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State<*>>,
|
||||
outputs: List<State<*>>,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||
val command = tx.commands.requireSingleCommand<Commands.Mature>()
|
||||
val irs = inputs.filterIsInstance<State<*>>().single()
|
||||
val irs = inputs.filterIsInstance<State>().single()
|
||||
requireThat {
|
||||
"No more fixings to be applied" using (irs.calculation.nextFixingDate() == null)
|
||||
"The irs is fully consumed and there is no id matched output state" using outputs.isEmpty()
|
||||
@ -667,9 +655,9 @@ class InterestRateSwap : Contract {
|
||||
/**
|
||||
* The state class contains the 4 major data classes.
|
||||
*/
|
||||
data class State<P : AbstractParty>(
|
||||
val fixedLeg: FixedLeg<P>,
|
||||
val floatingLeg: FloatingLeg<P>,
|
||||
data class State(
|
||||
val fixedLeg: FixedLeg,
|
||||
val floatingLeg: FloatingLeg,
|
||||
val calculation: Calculation,
|
||||
val common: Common,
|
||||
override val linearId: UniqueIdentifier = UniqueIdentifier(common.tradeID)
|
||||
@ -682,15 +670,15 @@ class InterestRateSwap : Contract {
|
||||
|
||||
override val ref = common.tradeID
|
||||
|
||||
override val participants: List<PublicKey>
|
||||
get() = parties.map { it.owningKey }
|
||||
override val participants: List<AbstractParty>
|
||||
get() = parties
|
||||
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||
return fixedLeg.fixedRatePayer.owningKey.containsAny(ourKeys) || floatingLeg.floatingRatePayer.owningKey.containsAny(ourKeys)
|
||||
}
|
||||
|
||||
override val parties: List<AnonymousParty>
|
||||
get() = listOf(fixedLeg.fixedRatePayer.toAnonymous(), floatingLeg.floatingRatePayer.toAnonymous())
|
||||
override val parties: List<AbstractParty>
|
||||
get() = listOf(fixedLeg.fixedRatePayer, floatingLeg.floatingRatePayer)
|
||||
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
|
||||
val nextFixingOf = nextFixingOf() ?: return null
|
||||
@ -700,10 +688,10 @@ class InterestRateSwap : Contract {
|
||||
return ScheduledActivity(flowLogicRefFactory.create(FixingFlow.FixingRoleDecider::class.java, thisStateRef), instant)
|
||||
}
|
||||
|
||||
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg.toAnonymous(), fixedLeg.toAnonymous(), calculation, common, notary)
|
||||
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
|
||||
|
||||
override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
|
||||
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this.toAnonymous(), oldState.state.notary), oldState.ref), fix)
|
||||
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, oldState.state.notary), oldState.ref), fix)
|
||||
}
|
||||
|
||||
override fun nextFixingOf(): FixOf? {
|
||||
@ -737,22 +725,13 @@ class InterestRateSwap : Contract {
|
||||
* Just makes printing it out a bit better for those who don't have 80000 column wide monitors.
|
||||
*/
|
||||
fun prettyPrint() = toString().replace(",", "\n")
|
||||
|
||||
fun toAnonymous(): State<AnonymousParty> {
|
||||
return if (this.fixedLeg.fixedRatePayer is AnonymousParty) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
this as State<AnonymousParty>
|
||||
} else {
|
||||
State(fixedLeg.toAnonymous(), floatingLeg.toAnonymous(), calculation, common, linearId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This generates the agreement state and also the schedules from the initial data.
|
||||
* Note: The day count, interest rate calculation etc are not finished yet, but they are demonstrable.
|
||||
*/
|
||||
fun generateAgreement(floatingLeg: FloatingLeg<AnonymousParty>, fixedLeg: FixedLeg<AnonymousParty>, calculation: Calculation,
|
||||
fun generateAgreement(floatingLeg: FloatingLeg, fixedLeg: FixedLeg, calculation: Calculation,
|
||||
common: Common, notary: Party): TransactionBuilder {
|
||||
|
||||
val fixedLegPaymentSchedule = LinkedHashMap<LocalDate, FixedRatePaymentEvent>()
|
||||
@ -816,7 +795,7 @@ class InterestRateSwap : Contract {
|
||||
}
|
||||
}
|
||||
|
||||
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State<AnonymousParty>>, fixing: Fix) {
|
||||
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Fix) {
|
||||
tx.addInputState(irs)
|
||||
val fixedRate = FixedRate(RatioUnit(fixing.value))
|
||||
tx.addOutputState(
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.irs.contract
|
||||
|
||||
fun InterestRateSwap.State<*>.exportIRSToCSV(): String =
|
||||
fun InterestRateSwap.State.exportIRSToCSV(): String =
|
||||
"Fixed Leg\n" + FixedRatePaymentEvent.CSVHeader + "\n" +
|
||||
this.calculation.fixedLegPaymentSchedule.toSortedMap().values.map { it.asCSV() }.joinToString("\n") + "\n" +
|
||||
"Floating Leg\n" + FloatingRatePaymentEvent.CSVHeader + "\n" +
|
||||
|
@ -79,9 +79,9 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
||||
return future
|
||||
}
|
||||
|
||||
private fun loadLinearHeads(node: SimulatedNode): Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State<AnonymousParty>>> {
|
||||
private fun loadLinearHeads(node: SimulatedNode): Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State>> {
|
||||
return node.database.transaction {
|
||||
node.services.vaultService.linearHeadsOfType<InterestRateSwap.State<AnonymousParty>>()
|
||||
node.services.vaultService.linearHeadsOfType<InterestRateSwap.State>()
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,8 +90,8 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
||||
val node1: SimulatedNode = banks[i]
|
||||
val node2: SimulatedNode = banks[j]
|
||||
|
||||
val swaps: Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State<AnonymousParty>>> = loadLinearHeads(node1)
|
||||
val theDealRef: StateAndRef<InterestRateSwap.State<AnonymousParty>> = swaps.values.single()
|
||||
val swaps: Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State>> = loadLinearHeads(node1)
|
||||
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
|
||||
|
||||
// Do we have any more days left in this deal's lifetime? If not, return.
|
||||
val nextFixingDate = theDealRef.state.data.calculation.nextFixingDate() ?: return null
|
||||
@ -121,9 +121,9 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
||||
// We load the IRS afresh each time because the leg parts of the structure aren't data classes so they don't
|
||||
// have the convenient copy() method that'd let us make small adjustments. Instead they're partly mutable.
|
||||
// TODO: We should revisit this in post-Excalibur cleanup and fix, e.g. by introducing an interface.
|
||||
val irs = om.readValue<InterestRateSwap.State<AnonymousParty>>(javaClass.classLoader.getResource("simulation/trade.json"))
|
||||
irs.fixedLeg.fixedRatePayer = node1.info.legalIdentity.toAnonymous()
|
||||
irs.floatingLeg.floatingRatePayer = node2.info.legalIdentity.toAnonymous()
|
||||
val irs = om.readValue<InterestRateSwap.State>(javaClass.classLoader.getResource("simulation/trade.json"))
|
||||
irs.fixedLeg.fixedRatePayer = node1.info.legalIdentity
|
||||
irs.floatingLeg.floatingRatePayer = node2.info.legalIdentity
|
||||
|
||||
@InitiatingFlow
|
||||
class StartDealFlow(val otherParty: Party,
|
||||
|
@ -16,12 +16,12 @@ import java.time.LocalDate
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State<AnonymousParty> {
|
||||
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
return when (irsSelect) {
|
||||
1 -> {
|
||||
|
||||
val fixedLeg = InterestRateSwap.FixedLeg(
|
||||
fixedRatePayer = MEGA_CORP.toAnonymous(),
|
||||
fixedRatePayer = MEGA_CORP,
|
||||
notional = 15900000.DOLLARS,
|
||||
paymentFrequency = Frequency.SemiAnnual,
|
||||
effectiveDate = LocalDate.of(2016, 3, 10),
|
||||
@ -40,7 +40,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State<AnonymousParty> {
|
||||
)
|
||||
|
||||
val floatingLeg = InterestRateSwap.FloatingLeg(
|
||||
floatingRatePayer = MINI_CORP.toAnonymous(),
|
||||
floatingRatePayer = MINI_CORP,
|
||||
notional = 15900000.DOLLARS,
|
||||
paymentFrequency = Frequency.Quarterly,
|
||||
effectiveDate = LocalDate.of(2016, 3, 10),
|
||||
@ -111,7 +111,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State<AnonymousParty> {
|
||||
// I did a mock up start date 10/03/2015 – 10/03/2025 so you have 5 cashflows on float side that have been preset the rest are unknown
|
||||
|
||||
val fixedLeg = InterestRateSwap.FixedLeg(
|
||||
fixedRatePayer = MEGA_CORP.toAnonymous(),
|
||||
fixedRatePayer = MEGA_CORP,
|
||||
notional = 25000000.DOLLARS,
|
||||
paymentFrequency = Frequency.SemiAnnual,
|
||||
effectiveDate = LocalDate.of(2015, 3, 10),
|
||||
@ -130,7 +130,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State<AnonymousParty> {
|
||||
)
|
||||
|
||||
val floatingLeg = InterestRateSwap.FloatingLeg(
|
||||
floatingRatePayer = MINI_CORP.toAnonymous(),
|
||||
floatingRatePayer = MINI_CORP,
|
||||
notional = 25000000.DOLLARS,
|
||||
paymentFrequency = Frequency.Quarterly,
|
||||
effectiveDate = LocalDate.of(2015, 3, 10),
|
||||
@ -246,8 +246,8 @@ class IRSTests {
|
||||
/**
|
||||
* Utility so I don't have to keep typing this.
|
||||
*/
|
||||
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State<AnonymousParty> {
|
||||
return generateIRSTxn(irsSelector).tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State<AnonymousParty>>().single()
|
||||
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
|
||||
return generateIRSTxn(irsSelector).tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -301,7 +301,7 @@ class IRSTests {
|
||||
var previousTXN = generateIRSTxn(1)
|
||||
previousTXN.toLedgerTransaction(services).verify()
|
||||
services.recordTransactions(previousTXN)
|
||||
fun currentIRS() = previousTXN.tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State<AnonymousParty>>().single()
|
||||
fun currentIRS() = previousTXN.tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
||||
|
||||
while (true) {
|
||||
val nextFix: FixOf = currentIRS().nextFixingOf() ?: break
|
||||
@ -381,7 +381,7 @@ class IRSTests {
|
||||
|
||||
transaction("Fix") {
|
||||
input("irs post agreement")
|
||||
val postAgreement = "irs post agreement".output<InterestRateSwap.State<AnonymousParty>>()
|
||||
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
|
||||
output("irs post first fixing") {
|
||||
postAgreement.copy(
|
||||
postAgreement.fixedLeg,
|
||||
@ -688,7 +688,7 @@ class IRSTests {
|
||||
transaction("Fix") {
|
||||
input("irs post agreement1")
|
||||
input("irs post agreement2")
|
||||
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State<AnonymousParty>>()
|
||||
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
|
||||
output("irs post first fixing1") {
|
||||
postAgreement1.copy(
|
||||
postAgreement1.fixedLeg,
|
||||
@ -697,7 +697,7 @@ class IRSTests {
|
||||
postAgreement1.common.copy(tradeID = "t1")
|
||||
)
|
||||
}
|
||||
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State<AnonymousParty>>()
|
||||
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
|
||||
output("irs post first fixing2") {
|
||||
postAgreement2.copy(
|
||||
postAgreement2.fixedLeg,
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.LogHelper
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
@ -241,5 +242,5 @@ class NodeInterestRatesTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeTX() = TransactionType.General.Builder(DUMMY_NOTARY).withItems(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY)
|
||||
private fun makeTX() = TransactionType.General.Builder(DUMMY_NOTARY).withItems(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE `with notary` DUMMY_NOTARY)
|
||||
}
|
||||
|
@ -20,9 +20,8 @@ class DummyIssueAndMove(private val notary: Party, private val counterpartyNode:
|
||||
}
|
||||
serviceHub.recordTransactions(issueTx.toSignedTransaction())
|
||||
// Move ownership of the asset to the counterparty
|
||||
val counterPartyKey = counterpartyNode.owningKey
|
||||
val asset = issueTx.toWireTransaction().outRef<DummyContract.SingleOwnerState>(0)
|
||||
val moveTx = DummyContract.move(asset, counterPartyKey).apply {
|
||||
val moveTx = DummyContract.move(asset, counterpartyNode).apply {
|
||||
signWith(myKeyPair)
|
||||
}
|
||||
// We don't check signatures because we know that the notary's signature is missing
|
||||
|
@ -4,9 +4,9 @@ import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.core.contracts.TransactionType
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
|
||||
@ -16,12 +16,12 @@ import java.security.PublicKey
|
||||
* TODO: Merge with the existing demo IRS code.
|
||||
*/
|
||||
data class IRSState(val swap: SwapData,
|
||||
val buyer: AnonymousParty,
|
||||
val seller: AnonymousParty,
|
||||
val buyer: AbstractParty,
|
||||
val seller: AbstractParty,
|
||||
override val contract: OGTrade,
|
||||
override val linearId: UniqueIdentifier = UniqueIdentifier(swap.id.first + swap.id.second)) : DealState {
|
||||
override val ref: String = linearId.externalId!! // Same as the constructor for UniqueIdentified
|
||||
override val parties: List<AnonymousParty> get() = listOf(buyer, seller)
|
||||
override val parties: List<AbstractParty> get() = listOf(buyer, seller)
|
||||
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||
return parties.flatMap { it.owningKey.keys }.intersect(ourKeys).isNotEmpty()
|
||||
@ -32,6 +32,6 @@ data class IRSState(val swap: SwapData,
|
||||
return TransactionType.General.Builder(notary).withItems(state, Command(OGTrade.Commands.Agree(), parties.map { it.owningKey }))
|
||||
}
|
||||
|
||||
override val participants: List<PublicKey>
|
||||
get() = parties.map { it.owningKey }
|
||||
override val participants: List<AbstractParty>
|
||||
get() = parties
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ data class OGTrade(override val legalContractReference: SecureHash = SecureHash.
|
||||
require(inputs.size == 0) { "Inputs must be empty" }
|
||||
require(outputs.size == 1) { "" }
|
||||
require(outputs[0].buyer != outputs[0].seller)
|
||||
require(outputs[0].parties.map { it.owningKey }.containsAll(outputs[0].participants))
|
||||
require(outputs[0].parties.containsAll(outputs[0].participants))
|
||||
require(outputs[0].parties.containsAll(listOf(outputs[0].buyer, outputs[0].seller)))
|
||||
require(outputs[0].swap.startDate.isBefore(outputs[0].swap.endDate))
|
||||
require(outputs[0].swap.notional > BigDecimal(0))
|
||||
|
@ -1,10 +1,10 @@
|
||||
package net.corda.vega.contracts
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.vega.flows.SimmRevaluation
|
||||
@ -19,7 +19,7 @@ import java.time.temporal.ChronoUnit
|
||||
*/
|
||||
data class PortfolioState(val portfolio: List<StateRef>,
|
||||
override val contract: PortfolioSwap,
|
||||
private val _parties: Pair<AnonymousParty, AnonymousParty>,
|
||||
private val _parties: Pair<AbstractParty, AbstractParty>,
|
||||
val valuationDate: LocalDate,
|
||||
val valuation: PortfolioValuation? = null,
|
||||
override val linearId: UniqueIdentifier = UniqueIdentifier())
|
||||
@ -27,12 +27,12 @@ data class PortfolioState(val portfolio: List<StateRef>,
|
||||
@CordaSerializable
|
||||
data class Update(val portfolio: List<StateRef>? = null, val valuation: PortfolioValuation? = null)
|
||||
|
||||
override val parties: List<AnonymousParty> get() = _parties.toList()
|
||||
override val parties: List<AbstractParty> get() = _parties.toList()
|
||||
override val ref: String = linearId.toString()
|
||||
val valuer: AnonymousParty get() = parties[0]
|
||||
val valuer: AbstractParty get() = parties[0]
|
||||
|
||||
override val participants: List<PublicKey>
|
||||
get() = parties.map { it.owningKey }
|
||||
override val participants: List<AbstractParty>
|
||||
get() = parties
|
||||
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity {
|
||||
val flow = flowLogicRefFactory.create(SimmRevaluation.Initiator::class.java, thisStateRef, LocalDate.now())
|
||||
|
@ -71,7 +71,7 @@ data class PortfolioSwap(override val legalContractReference: SecureHash = Secur
|
||||
"there are no inputs" using (inputs.size == 0)
|
||||
"there is one output" using (outputs.size == 1)
|
||||
"valuer must be a party" using (outputs[0].parties.contains(outputs[0].valuer))
|
||||
"all participants must be parties" using (outputs[0].parties.map { it.owningKey }.containsAll(outputs[0].participants))
|
||||
"all participants must be parties" using (outputs[0].parties.containsAll(outputs[0].participants))
|
||||
}
|
||||
|
||||
return setOf(command.value)
|
||||
|
@ -1,10 +1,10 @@
|
||||
package net.corda.vega.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -38,7 +38,7 @@ object IRSTradeFlow {
|
||||
} else {
|
||||
Pair(otherParty, myIdentity)
|
||||
}
|
||||
val offer = IRSState(swap, buyer.toAnonymous(), seller.toAnonymous(), OGTrade())
|
||||
val offer = IRSState(swap, buyer, seller, OGTrade())
|
||||
|
||||
logger.info("Handshake finished, sending IRS trade offer message")
|
||||
val otherPartyAgreeFlag = sendAndReceive<Boolean>(otherParty, OfferMessage(notary, offer)).unwrap { it }
|
||||
|
@ -10,11 +10,11 @@ import com.opengamma.strata.pricer.rate.ImmutableRatesProvider
|
||||
import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.services.dealsWith
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -85,7 +85,7 @@ object SimmFlow {
|
||||
@Suspendable
|
||||
private fun agreePortfolio(portfolio: Portfolio) {
|
||||
logger.info("Agreeing portfolio")
|
||||
val parties = Pair(myIdentity.toAnonymous(), otherParty.toAnonymous())
|
||||
val parties = Pair(myIdentity, otherParty)
|
||||
val portfolioState = PortfolioState(portfolio.refs, PortfolioSwap(), parties, valuationDate)
|
||||
|
||||
send(otherParty, OfferMessage(notary, portfolioState, existing?.ref, valuationDate))
|
||||
@ -219,13 +219,6 @@ object SimmFlow {
|
||||
return receive<Boolean>(replyToParty).unwrap { it }
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun agreeValuation(portfolio: Portfolio, asOf: LocalDate, valuer: AnonymousParty): PortfolioValuation {
|
||||
val valuerParty = serviceHub.identityService.partyFromAnonymous(valuer)
|
||||
require(valuerParty != null)
|
||||
return agreeValuation(portfolio, asOf, valuerParty!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* So this is the crux of the Simm Agreement flow
|
||||
* It needs to do several things - which are mainly defined by the analytics engine we are using - which in this
|
||||
@ -326,7 +319,7 @@ object SimmFlow {
|
||||
@Suspendable
|
||||
private fun updateValuation(stateRef: StateAndRef<PortfolioState>) {
|
||||
val portfolio = stateRef.state.data.portfolio.toStateAndRef<IRSState>(serviceHub).toPortfolio()
|
||||
val valuer = stateRef.state.data.valuer
|
||||
val valuer = serviceHub.identityService.partyFromAnonymous(stateRef.state.data.valuer) ?: throw IllegalStateException("Unknown valuer party ${stateRef.state.data.valuer}")
|
||||
val valuation = agreeValuation(portfolio, offer.valuationDate, valuer)
|
||||
subFlow(object : StateRevisionFlow.Receiver<PortfolioState.Update>(replyToParty) {
|
||||
override fun verifyProposal(proposal: Proposal<PortfolioState.Update>) {
|
||||
|
@ -21,7 +21,7 @@ object SimmRevaluation {
|
||||
override fun call(): Unit {
|
||||
val stateAndRef = serviceHub.vaultService.linearHeadsOfType<PortfolioState>().values.first { it.ref == curStateRef }
|
||||
val curState = stateAndRef.state.data
|
||||
val myIdentity = serviceHub.myInfo.legalIdentity.toAnonymous()
|
||||
val myIdentity = serviceHub.myInfo.legalIdentity
|
||||
if (myIdentity == curState.parties[0]) {
|
||||
val otherParty = serviceHub.identityService.partyFromAnonymous(curState.parties[1])
|
||||
require(otherParty != null) { "Other party must be known by this node" }
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.vega.flows
|
||||
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -16,7 +17,7 @@ import java.security.PublicKey
|
||||
object StateRevisionFlow {
|
||||
class Requester<T>(curStateRef: StateAndRef<RevisionedState<T>>,
|
||||
updatedData: T) : AbstractStateReplacementFlow.Instigator<RevisionedState<T>, RevisionedState<T>, T>(curStateRef, updatedData) {
|
||||
override fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
|
||||
override fun assembleTx(): Pair<SignedTransaction, List<AbstractParty>> {
|
||||
val state = originalState.state.data
|
||||
val tx = state.generateRevision(originalState.state.notary, originalState, modification)
|
||||
tx.setTime(serviceHub.clock.instant(), 30.seconds)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user