mirror of
https://github.com/corda/corda.git
synced 2025-04-07 11:27:01 +00:00
Merge branch 'master' into sofus-generic-contract
This commit is contained in:
commit
07c1f8b86a
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@ -21,6 +21,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/node/node_main.iml" filepath="$PROJECT_DIR$/.idea/modules/node/node_main.iml" group="node" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/node/node_test.iml" filepath="$PROJECT_DIR$/.idea/modules/node/node_test.iml" group="node" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping_integrationTest.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping_integrationTest.iml" group="r3prototyping" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping_main.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping_main.iml" group="r3prototyping" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping_test.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping_test.iml" group="r3prototyping" />
|
||||
</modules>
|
||||
|
54
build.gradle
54
build.gradle
@ -1,11 +1,11 @@
|
||||
group 'com.r3cev.prototyping'
|
||||
version '1.0-SNAPSHOT'
|
||||
group 'com.r3corda'
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
apply plugin: 'project-report'
|
||||
apply plugin: QuasarPlugin
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
|
||||
allprojects {
|
||||
sourceCompatibility = 1.8
|
||||
@ -17,13 +17,16 @@ allprojects {
|
||||
}
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.0.2'
|
||||
ext.kotlin_version = '1.0.3'
|
||||
ext.quasar_version = '0.7.5'
|
||||
ext.asm_version = '0.5.3'
|
||||
ext.artemis_version = '1.3.0'
|
||||
ext.jetty_version = '9.1.1.v20140108'
|
||||
ext.jersey_version = '2.22.2'
|
||||
ext.jackson_version = '2.8.0.rc2'
|
||||
ext.jetty_version = '9.3.9.v20160517'
|
||||
ext.jersey_version = '2.23.1'
|
||||
ext.jolokia_version = '2.0.0-M1'
|
||||
ext.slf4j_version = '1.7.21'
|
||||
ext.assertj_version = '3.5.1'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@ -31,9 +34,17 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// Can run 'gradle dependencyUpdates' to find new versions of things.
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
// Our version: bump this on release.
|
||||
group 'com.r3corda'
|
||||
version '0.2-SNAPSHOT'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@ -44,10 +55,23 @@ repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
integrationTest {
|
||||
kotlin {
|
||||
compileClasspath += main.output + test.output
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/integration-test/kotlin')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
configurations {
|
||||
// we don't want isolated.jar in classPath, since we want to test jar being dynamically loaded as an attachment
|
||||
runtime.exclude module: 'isolated'
|
||||
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
// This is required for quasar. I think.
|
||||
@ -64,12 +88,17 @@ dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.1"
|
||||
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2"
|
||||
compile 'com.squareup.okhttp3:okhttp:3.3.1'
|
||||
|
||||
// Unit testing helpers.
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.assertj:assertj-core:3.4.1'
|
||||
testCompile 'com.pholser:junit-quickcheck-core:0.6'
|
||||
|
||||
// Integration test helpers
|
||||
integrationTestCompile 'junit:junit:4.12'
|
||||
integrationTestCompile 'org.assertj:assertj-core:${assertj_version}'
|
||||
}
|
||||
|
||||
// Package up the demo programs.
|
||||
@ -99,8 +128,7 @@ task getTraderDemo(type: CreateStartScripts) {
|
||||
|
||||
// Force windows script classpath to wildcard path to avoid the 'Command Line Is Too Long' issues
|
||||
// with generated scripts. Include Jolokia .war explicitly as this isn't picked up by wildcard
|
||||
tasks.withType(CreateStartScripts)
|
||||
{
|
||||
tasks.withType(CreateStartScripts) {
|
||||
doLast {
|
||||
windowsScript.text = windowsScript
|
||||
.readLines()
|
||||
@ -109,6 +137,16 @@ tasks.withType(CreateStartScripts)
|
||||
}
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
testClassesDir = sourceSets.integrationTest.output.classesDir
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
}
|
||||
test.finalizedBy(integrationTest)
|
||||
|
||||
tasks.withType(Test) {
|
||||
reports.html.destination = file("${reporting.baseDir}/${name}")
|
||||
}
|
||||
|
||||
quasarScan.dependsOn('classes', 'core:classes', 'contracts:classes', 'node:classes')
|
||||
|
||||
applicationDistribution.into("bin") {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package com.r3corda.contracts;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.r3corda.contracts.cash.Cash;
|
||||
import com.r3corda.contracts.cash.CashKt;
|
||||
import com.r3corda.contracts.cash.InsufficientBalanceException;
|
||||
import com.r3corda.contracts.asset.Cash;
|
||||
import com.r3corda.contracts.asset.CashKt;
|
||||
import com.r3corda.contracts.asset.InsufficientBalanceException;
|
||||
import com.r3corda.core.contracts.*;
|
||||
import com.r3corda.core.contracts.TransactionForContract.InOutGroup;
|
||||
import com.r3corda.core.crypto.NullPublicKey;
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.r3corda.contracts
|
||||
|
||||
import com.r3corda.contracts.cash.Cash
|
||||
import com.r3corda.contracts.cash.InsufficientBalanceException
|
||||
import com.r3corda.contracts.cash.sumCashBy
|
||||
import com.r3corda.contracts.asset.Cash
|
||||
import com.r3corda.contracts.asset.InsufficientBalanceException
|
||||
import com.r3corda.contracts.asset.sumCashBy
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.NullPublicKey
|
||||
import com.r3corda.core.crypto.Party
|
||||
|
@ -3,6 +3,9 @@ package com.r3corda.contracts
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.protocols.ProtocolLogicRefFactory
|
||||
import com.r3corda.core.utilities.suggestInterestRateAnnouncementTimeWindow
|
||||
import com.r3corda.protocols.TwoPartyDealProtocol
|
||||
import org.apache.commons.jexl3.JexlBuilder
|
||||
import org.apache.commons.jexl3.MapContext
|
||||
import java.math.BigDecimal
|
||||
@ -588,7 +591,7 @@ class InterestRateSwap() : Contract {
|
||||
val floatingLeg: FloatingLeg,
|
||||
val calculation: Calculation,
|
||||
val common: Common
|
||||
) : FixableDealState {
|
||||
) : FixableDealState, SchedulableState {
|
||||
|
||||
override val contract = IRS_PROGRAM_ID
|
||||
override val thread = SecureHash.sha256(common.tradeID)
|
||||
@ -604,6 +607,14 @@ class InterestRateSwap() : Contract {
|
||||
override val parties: Array<Party>
|
||||
get() = arrayOf(fixedLeg.fixedRatePayer, floatingLeg.floatingRatePayer)
|
||||
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity? {
|
||||
val nextFixingOf = nextFixingOf() ?: return null
|
||||
|
||||
// This is perhaps not how we should determine the time point in the business day, but instead expect the schedule to detail some of these aspects
|
||||
val (instant, duration) = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name, source = floatingLeg.indexSource, date = nextFixingOf.forDay)
|
||||
return ScheduledActivity(protocolLogicRefFactory.create(TwoPartyDealProtocol.FixingRoleDecider::class.java, thisStateRef, duration), instant)
|
||||
}
|
||||
|
||||
// TODO: This changing of the public key violates the assumption that Party is a fixed identity key.
|
||||
override fun withPublicKey(before: Party, after: PublicKey): DealState {
|
||||
val newParty = Party(before.name, after)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.r3corda.contracts.cash
|
||||
package com.r3corda.contracts.asset
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
@ -54,6 +54,9 @@ class Cash : FungibleAsset<Currency>() {
|
||||
) : FungibleAsset.State<Currency> {
|
||||
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey)
|
||||
: this(Amount(amount.quantity, Issued<Currency>(deposit, amount.token)), owner)
|
||||
|
||||
override val productAmount: Amount<Currency>
|
||||
get() = Amount(amount.quantity, amount.token.product)
|
||||
override val deposit: PartyAndReference
|
||||
get() = amount.token.issuer
|
||||
override val contract = CASH_PROGRAM_ID
|
||||
@ -62,8 +65,8 @@ class Cash : FungibleAsset<Currency>() {
|
||||
override val participants: List<PublicKey>
|
||||
get() = listOf(owner)
|
||||
|
||||
override fun move(amount: Amount<Issued<Currency>>, owner: PublicKey): FungibleAsset.State<Currency>
|
||||
= copy(amount = amount, owner = owner)
|
||||
override fun move(newAmount: Amount<Currency>, newOwner: PublicKey): FungibleAsset.State<Currency>
|
||||
= copy(amount = amount.copy(newAmount.quantity, amount.token), owner = newOwner)
|
||||
|
||||
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
|
||||
|
||||
@ -75,9 +78,9 @@ class Cash : FungibleAsset<Currency>() {
|
||||
/**
|
||||
* A command stating that money has been moved, optionally to fulfil another contract.
|
||||
*
|
||||
* @param contractHash the hash of the contract this cash is settling, to ensure one cash contract cannot be
|
||||
* used to settle multiple contracts. May be null, if this is not relevant to any other contract in the
|
||||
* same transaction
|
||||
* @param contractHash the contract this move is for the attention of. Only that contract's verify function
|
||||
* should take the moved states into account when considering whether it is valid. Typically this will be
|
||||
* null.
|
||||
*/
|
||||
data class Move(override val contractHash: SecureHash? = null) : FungibleAsset.Commands.Move, Commands
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.r3corda.contracts.cash
|
||||
package com.r3corda.contracts.asset
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
@ -34,7 +34,7 @@ abstract class FungibleAsset<T> : Contract {
|
||||
interface State<T> : FungibleAssetState<T, Issued<T>> {
|
||||
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||
val deposit: PartyAndReference
|
||||
override val amount: Amount<Issued<T>>
|
||||
val amount: Amount<Issued<T>>
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
override val owner: PublicKey
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.r3corda.contracts.cash
|
||||
package com.r3corda.contracts.asset
|
||||
|
||||
import com.r3corda.core.contracts.Amount
|
||||
import com.r3corda.core.contracts.Issued
|
||||
@ -10,6 +10,6 @@ import java.security.PublicKey
|
||||
*/
|
||||
interface FungibleAssetState<T, I> : OwnableState {
|
||||
val issuanceDef: I
|
||||
val amount: Amount<Issued<T>>
|
||||
fun move(amount: Amount<Issued<T>>, owner: PublicKey): FungibleAssetState<T, I>
|
||||
val productAmount: Amount<T>
|
||||
fun move(newAmount: Amount<T>, newOwner: PublicKey): FungibleAssetState<T, I>
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package com.r3corda.contracts
|
||||
package com.r3corda.contracts.asset
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting
|
||||
import com.r3corda.contracts.cash.*
|
||||
import com.r3corda.contracts.asset.FungibleAssetState
|
||||
import com.r3corda.contracts.asset.sumFungibleOrNull
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
@ -18,12 +19,10 @@ import java.util.*
|
||||
val OBLIGATION_PROGRAM_ID = Obligation<Currency>()
|
||||
|
||||
/**
|
||||
* A cash settlement contract commits the issuer to delivering a specified amount of cash (represented as the [Cash]
|
||||
* contract) at a specified future point in time. Similarly to cash, settlement transactions may split and merge
|
||||
* contracts across multiple input and output states.
|
||||
*
|
||||
* The goal of this design is to handle money owed, and these contracts are expected to be netted/merged, with
|
||||
* settlement only for any remainder amount.
|
||||
* An obligation contract commits the obligor to delivering a specified amount of a fungible asset (for example the
|
||||
* [Cash] contract) at a specified future point in time. Settlement transactions may split and merge contracts across
|
||||
* multiple input and output states. The goal of this design is to handle amounts owed, and these contracts are expected
|
||||
* to be netted/merged, with settlement only for any remainder amount.
|
||||
*
|
||||
* @param P the product the obligation is for payment of.
|
||||
*/
|
||||
@ -51,7 +50,7 @@ class Obligation<P> : Contract {
|
||||
NORMAL,
|
||||
/**
|
||||
* Indicates the contract has not been settled by its due date. Once in the defaulted state,
|
||||
* it can only be reverted to [NORMAL] state by the owner.
|
||||
* it can only be reverted to [NORMAL] state by the beneficiary.
|
||||
*/
|
||||
DEFAULTED
|
||||
}
|
||||
@ -61,7 +60,7 @@ class Obligation<P> : Contract {
|
||||
* underlying issued thing.
|
||||
*/
|
||||
interface NetState<P> {
|
||||
val issued: Issued<P>
|
||||
val template: StateTemplate<P>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,11 +70,8 @@ class Obligation<P> : Contract {
|
||||
*/
|
||||
data class BilateralNetState<P>(
|
||||
val partyKeys: Set<PublicKey>,
|
||||
val issuanceDef: StateTemplate<P>
|
||||
) : NetState<P> {
|
||||
override val issued: Issued<P>
|
||||
get() = issuanceDef.issued
|
||||
}
|
||||
override val template: StateTemplate<P>
|
||||
) : NetState<P>
|
||||
|
||||
/**
|
||||
* Subset of state, containing the elements which must match for two or more obligation transactions to be candidates
|
||||
@ -86,11 +82,8 @@ class Obligation<P> : Contract {
|
||||
* Used in cases where all parties (or their proxies) are signing, such as central clearing.
|
||||
*/
|
||||
data class MultilateralNetState<P>(
|
||||
val issuanceDef: StateTemplate<P>
|
||||
) : NetState<P> {
|
||||
override val issued: Issued<P>
|
||||
get() = issuanceDef.issued
|
||||
}
|
||||
override val template: StateTemplate<P>
|
||||
) : NetState<P>
|
||||
|
||||
/**
|
||||
* Subset of state, containing the elements specified when issuing a new settlement contract.
|
||||
@ -98,17 +91,17 @@ class Obligation<P> : Contract {
|
||||
* @param P the product the obligation is for payment of.
|
||||
*/
|
||||
data class StateTemplate<P>(
|
||||
/** The hash of the cash contract we're willing to accept in payment for this debt. */
|
||||
/** The hash of the asset contract we're willing to accept in payment for this debt. */
|
||||
val acceptableContracts: NonEmptySet<SecureHash>,
|
||||
/** The parties whose cash we are willing to accept in payment for this debt. */
|
||||
val acceptableIssuanceDefinitions: NonEmptySet<Issued<P>>,
|
||||
/** The parties whose assets we are willing to accept in payment for this debt. */
|
||||
val acceptableIssuedProducts: NonEmptySet<Issued<P>>,
|
||||
|
||||
/** When the contract must be settled by. */
|
||||
val dueBefore: Instant,
|
||||
val timeTolerance: Duration = Duration.ofSeconds(30)
|
||||
) {
|
||||
val issued: Issued<P>
|
||||
get() = acceptableIssuanceDefinitions.toSet().single()
|
||||
val product: P
|
||||
get() = acceptableIssuedProducts.map { it.product }.toSet().single()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,57 +112,58 @@ class Obligation<P> : Contract {
|
||||
* @param P the product the obligation is for payment of.
|
||||
*/
|
||||
data class IssuanceDefinition<P>(
|
||||
val issuer: Party,
|
||||
val obligor: Party,
|
||||
val template: StateTemplate<P>
|
||||
) {
|
||||
val currency: P
|
||||
get() = template.issued.product
|
||||
val issued: Issued<P>
|
||||
get() = template.issued
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* A state representing the obligation of one party (issuer) to deliver a specified number of
|
||||
* units of an underlying asset (described as issuanceDef.acceptableCashIssuance) to the owner
|
||||
* A state representing the obligation of one party (obligor) to deliver a specified number of
|
||||
* units of an underlying asset (described as issuanceDef.acceptableIssuedProducts) to the beneficiary
|
||||
* no later than the specified time.
|
||||
*
|
||||
* @param P the product the obligation is for payment of.
|
||||
*/
|
||||
data class State<P>(
|
||||
var lifecycle: Lifecycle = Lifecycle.NORMAL,
|
||||
/** Where the debt originates from (issuer) */
|
||||
val issuer: Party,
|
||||
/** Where the debt originates from (obligor) */
|
||||
val obligor: Party,
|
||||
val template: StateTemplate<P>,
|
||||
val quantity: Long,
|
||||
/** The public key of the entity the contract pays to */
|
||||
override val owner: PublicKey
|
||||
val beneficiary: PublicKey
|
||||
) : FungibleAssetState<P, IssuanceDefinition<P>>, BilateralNettableState<State<P>> {
|
||||
override val amount: Amount<Issued<P>>
|
||||
get() = Amount(quantity, template.issued)
|
||||
val amount: Amount<P>
|
||||
get() = Amount(quantity, template.product)
|
||||
val aggregateState: IssuanceDefinition<P>
|
||||
get() = issuanceDef
|
||||
override val productAmount: Amount<P>
|
||||
get() = amount
|
||||
override val contract = OBLIGATION_PROGRAM_ID
|
||||
val acceptableContracts: NonEmptySet<SecureHash>
|
||||
get() = template.acceptableContracts
|
||||
val acceptableIssuanceDefinitions: NonEmptySet<*>
|
||||
get() = template.acceptableIssuanceDefinitions
|
||||
get() = template.acceptableIssuedProducts
|
||||
val dueBefore: Instant
|
||||
get() = template.dueBefore
|
||||
override val issuanceDef: IssuanceDefinition<P>
|
||||
get() = IssuanceDefinition(issuer, template)
|
||||
get() = IssuanceDefinition(obligor, template)
|
||||
override val participants: List<PublicKey>
|
||||
get() = listOf(issuer.owningKey, owner)
|
||||
get() = listOf(obligor.owningKey, beneficiary)
|
||||
override val owner: PublicKey
|
||||
get() = beneficiary
|
||||
|
||||
override fun move(amount: Amount<Issued<P>>, owner: PublicKey): Obligation.State<P>
|
||||
= copy(quantity = amount.quantity, owner = owner)
|
||||
override fun move(newAmount: Amount<P>, newOwner: PublicKey): State<P>
|
||||
= copy(quantity = newAmount.quantity, beneficiary = newOwner)
|
||||
|
||||
override fun toString() = when (lifecycle) {
|
||||
Lifecycle.NORMAL -> "${Emoji.bagOfCash}Debt($amount due $dueBefore to ${owner.toStringShort()})"
|
||||
Lifecycle.DEFAULTED -> "${Emoji.bagOfCash}Debt($amount unpaid by $dueBefore to ${owner.toStringShort()})"
|
||||
Lifecycle.NORMAL -> "${Emoji.bagOfCash}Debt($amount due $dueBefore to ${beneficiary.toStringShort()})"
|
||||
Lifecycle.DEFAULTED -> "${Emoji.bagOfCash}Debt($amount unpaid by $dueBefore to ${beneficiary.toStringShort()})"
|
||||
}
|
||||
|
||||
override val bilateralNetState: BilateralNetState<P>
|
||||
get() {
|
||||
check(lifecycle == Lifecycle.NORMAL)
|
||||
return BilateralNetState(setOf(issuer.owningKey, owner), template)
|
||||
return BilateralNetState(setOf(obligor.owningKey, beneficiary), template)
|
||||
}
|
||||
val multilateralNetState: MultilateralNetState<P>
|
||||
get() {
|
||||
@ -182,78 +176,83 @@ class Obligation<P> : Contract {
|
||||
val netB = other.bilateralNetState
|
||||
require(netA == netB) { "net substates of the two state objects must be identical" }
|
||||
|
||||
if (issuer.owningKey == other.issuer.owningKey) {
|
||||
// Both sides are from the same issuer to owner
|
||||
if (obligor.owningKey == other.obligor.owningKey) {
|
||||
// Both sides are from the same obligor to beneficiary
|
||||
return copy(quantity = quantity + other.quantity)
|
||||
} else {
|
||||
// Issuer and owner are backwards
|
||||
// Issuer and beneficiary are backwards
|
||||
return copy(quantity = quantity - other.quantity)
|
||||
}
|
||||
}
|
||||
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(issuanceDef), copy(owner = newOwner))
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(issuanceDef), copy(beneficiary = newOwner))
|
||||
}
|
||||
|
||||
/** Interface for commands that apply to aggregated states */
|
||||
interface AggregateCommands<P> : CommandData {
|
||||
/** Interface for commands that apply to states grouped by issuance definition */
|
||||
interface IssuanceCommands<P> : CommandData {
|
||||
val aggregateState: IssuanceDefinition<P>
|
||||
}
|
||||
|
||||
// Just for grouping
|
||||
interface Commands : CommandData {
|
||||
/**
|
||||
* Net two or more cash settlement states together in a close-out netting style. Limited to bilateral netting
|
||||
* as only the owner (not the issuer) needs to sign.
|
||||
* Net two or more obligation states together in a close-out netting style. Limited to bilateral netting
|
||||
* as only the beneficiary (not the obligor) needs to sign.
|
||||
*/
|
||||
data class Net(val type: NetType) : Commands
|
||||
|
||||
/**
|
||||
* A command stating that a debt has been moved, optionally to fulfil another contract.
|
||||
*
|
||||
* @param contractHash the hash of contract's code, which indicates to that contract that the
|
||||
* obligation states moved in this transaction are for their sole attention.
|
||||
* This is a single value to ensure the same state(s) cannot be used to settle multiple contracts.
|
||||
* May be null, if this is not relevant to any other contract in the same transaction.
|
||||
* @param contractHash the contract this move is for the attention of. Only that contract's verify function
|
||||
* should take the moved states into account when considering whether it is valid. Typically this will be
|
||||
* null.
|
||||
*/
|
||||
data class Move<P>(override val aggregateState: IssuanceDefinition<P>,
|
||||
override val contractHash: SecureHash? = null) : Commands, AggregateCommands<P>, MoveCommand
|
||||
override val contractHash: SecureHash? = null) : Commands, IssuanceCommands<P>, MoveCommand
|
||||
|
||||
/**
|
||||
* Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
* has a unique ID even when there are no inputs.
|
||||
* Allows new obligation states to be issued into existence: the nonce ("number used once") ensures the
|
||||
* transaction has a unique ID even when there are no inputs.
|
||||
*/
|
||||
data class Issue<P>(override val aggregateState: IssuanceDefinition<P>,
|
||||
val nonce: Long = random63BitValue()) : Commands, AggregateCommands<P>
|
||||
val nonce: Long = random63BitValue()) : Commands, IssuanceCommands<P>
|
||||
|
||||
/**
|
||||
* A command stating that the issuer is settling some or all of the amount owed by paying in a suitable cash
|
||||
* contract. If this reduces the balance to zero, the contract moves to the settled state.
|
||||
* @see [Cash.Commands.Move]
|
||||
* A command stating that the obligor is settling some or all of the amount owed by transferring a suitable
|
||||
* state object to the beneficiary. If this reduces the balance to zero, the state object is destroyed.
|
||||
* @see [MoveCommand]
|
||||
*/
|
||||
data class Settle<P>(override val aggregateState: IssuanceDefinition<P>,
|
||||
val amount: Amount<Issued<P>>) : Commands, AggregateCommands<P>
|
||||
val amount: Amount<P>) : Commands, IssuanceCommands<P>
|
||||
|
||||
/**
|
||||
* A command stating that the owner is moving the contract into the defaulted state as it has not been settled
|
||||
* A command stating that the beneficiary is moving the contract into the defaulted state as it has not been settled
|
||||
* by the due date, or resetting a defaulted contract back to the issued state.
|
||||
*/
|
||||
data class SetLifecycle<P>(override val aggregateState: IssuanceDefinition<P>,
|
||||
val lifecycle: Lifecycle) : Commands, AggregateCommands<P>
|
||||
val lifecycle: Lifecycle) : Commands, IssuanceCommands<P> {
|
||||
val inverse: Lifecycle
|
||||
get() = when (lifecycle) {
|
||||
Lifecycle.NORMAL -> Lifecycle.DEFAULTED
|
||||
Lifecycle.DEFAULTED -> Lifecycle.NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A command stating that the debt is being released by the owner. Normally would indicate
|
||||
* either settlement outside of the ledger, or that the issuer is unable to pay.
|
||||
* A command stating that the debt is being released by the beneficiary. Normally would indicate
|
||||
* either settlement outside of the ledger, or that the obligor is unable to pay.
|
||||
*/
|
||||
data class Exit<P>(override val aggregateState: IssuanceDefinition<P>,
|
||||
val amount: Amount<Issued<P>>) : Commands, AggregateCommands<P>
|
||||
val amount: Amount<P>) : Commands, IssuanceCommands<P>
|
||||
}
|
||||
|
||||
/** This is the function EVERYONE runs */
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
val commands = tx.commands.select<Obligation.Commands>()
|
||||
val commands = tx.commands.select<Commands>()
|
||||
|
||||
// Net commands are special, and cross issuance definitions, so handle them first
|
||||
val netCommands = commands.select<Obligation.Commands.Net>()
|
||||
val netCommands = commands.select<Commands.Net>()
|
||||
if (netCommands.isNotEmpty()) {
|
||||
val netCommand = netCommands.single()
|
||||
val groups = when (netCommand.value.type) {
|
||||
@ -264,40 +263,39 @@ class Obligation<P> : Contract {
|
||||
verifyNetCommand(inputs, outputs, netCommand, key)
|
||||
}
|
||||
} else {
|
||||
val commandGroups = tx.groupCommands<AggregateCommands<P>, IssuanceDefinition<P>> { it.value.aggregateState }
|
||||
val commandGroups = tx.groupCommands<IssuanceCommands<P>, IssuanceDefinition<P>> { it.value.aggregateState }
|
||||
// Each group is a set of input/output states with distinct issuance definitions. These types
|
||||
// of settlement are not fungible and must be kept separated for bookkeeping purposes.
|
||||
val groups = tx.groupStates() { it: State<P> -> it.issuanceDef }
|
||||
val groups = tx.groupStates() { it: State<P> -> it.aggregateState }
|
||||
|
||||
for ((inputs, outputs, key) in groups) {
|
||||
// Either inputs or outputs could be empty.
|
||||
val issuer = key.issuer
|
||||
val commands = commandGroups[key] ?: emptyList()
|
||||
val obligor = key.obligor
|
||||
|
||||
requireThat {
|
||||
"there are no zero sized outputs" by outputs.none { it.amount.quantity == 0L }
|
||||
}
|
||||
|
||||
verifyCommandGroup(tx, commands, inputs, outputs, issuer, key)
|
||||
verifyCommandGroup(tx, commandGroups[key] ?: emptyList(), inputs, outputs, obligor, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyCommandGroup(tx: TransactionForContract,
|
||||
commands: List<AuthenticatedObject<AggregateCommands<P>>>,
|
||||
commands: List<AuthenticatedObject<IssuanceCommands<P>>>,
|
||||
inputs: List<State<P>>,
|
||||
outputs: List<State<P>>,
|
||||
issuer: Party,
|
||||
obligor: Party,
|
||||
key: IssuanceDefinition<P>) {
|
||||
// We've already pre-grouped by currency amongst other fields, and verified above that every state specifies
|
||||
// at least one acceptable cash issuance definition, so we can just use the first issuance definition to
|
||||
// determine currency
|
||||
val currency = key.template.acceptableIssuanceDefinitions.first()
|
||||
// We've already pre-grouped by product amongst other fields, and verified above that every state specifies
|
||||
// at least one acceptable issuance definition, so we can just use the first issuance definition to
|
||||
// determine product
|
||||
val issued = key.template.acceptableIssuedProducts.first()
|
||||
|
||||
// Issue, default, net and settle commands are all single commands (there's only ever one of them, and
|
||||
// they exclude all other commands).
|
||||
val issueCommand = commands.select<Commands.Issue<P>>().firstOrNull()
|
||||
val defaultCommand = commands.select<Commands.SetLifecycle<P>>().firstOrNull()
|
||||
val setLifecycleCommand = commands.select<Commands.SetLifecycle<P>>().firstOrNull()
|
||||
val settleCommand = commands.select<Commands.Settle<P>>().firstOrNull()
|
||||
|
||||
if (commands.size != 1) {
|
||||
@ -308,8 +306,8 @@ class Obligation<P> : Contract {
|
||||
|
||||
// Issue, default and net commands are special, and do not follow normal input/output summing rules, so
|
||||
// deal with them first
|
||||
if (defaultCommand != null) {
|
||||
verifyDefaultCommand(inputs, outputs, tx, defaultCommand)
|
||||
if (setLifecycleCommand != null) {
|
||||
verifySetLifecycleCommand(inputs, outputs, tx, setLifecycleCommand)
|
||||
} else {
|
||||
// Only the default command processes inputs/outputs that are not in the normal state
|
||||
// TODO: Need to be able to exit defaulted amounts
|
||||
@ -318,15 +316,15 @@ class Obligation<P> : Contract {
|
||||
"all outputs are in the normal state " by outputs.all { it.lifecycle == Lifecycle.NORMAL }
|
||||
}
|
||||
if (issueCommand != null) {
|
||||
verifyIssueCommand(inputs, outputs, tx, issueCommand, currency, issuer)
|
||||
verifyIssueCommand(inputs, outputs, issueCommand, issued, obligor)
|
||||
} else if (settleCommand != null) {
|
||||
// Perhaps through an abundance of caution, settlement is enforced as its own command.
|
||||
// This could perhaps be merged into verifyBalanceChange() later, however doing so introduces a lot
|
||||
// of scope for making it more opaque what's going on in a transaction and whether it's as expected
|
||||
// by all parties.
|
||||
verifySettleCommand(inputs, outputs, tx, settleCommand, currency, issuer, key)
|
||||
verifySettleCommand(inputs, outputs, tx, settleCommand, issued, obligor, key)
|
||||
} else {
|
||||
verifyBalanceChange(inputs, outputs, commands, currency, issuer)
|
||||
verifyBalanceChange(inputs, outputs, commands, issued.product, obligor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,32 +337,32 @@ class Obligation<P> : Contract {
|
||||
*/
|
||||
private fun verifyBalanceChange(inputs: List<State<P>>,
|
||||
outputs: List<State<P>>,
|
||||
commands: List<AuthenticatedObject<AggregateCommands<P>>>,
|
||||
currency: Issued<P>,
|
||||
issuer: Party) {
|
||||
commands: List<AuthenticatedObject<IssuanceCommands<P>>>,
|
||||
product: P,
|
||||
obligor: Party) {
|
||||
// Sum up how much settlement owed there is in the inputs, and the difference in outputs. The difference should
|
||||
// be matched by exit commands representing the extracted amount.
|
||||
|
||||
val inputAmount = inputs.sumObligationsOrNull<P>() ?: throw IllegalArgumentException("there is at least one obligation input for this group")
|
||||
val outputAmount = outputs.sumObligationsOrZero(currency)
|
||||
val outputAmount = outputs.sumObligationsOrZero(product)
|
||||
|
||||
val exitCommands = commands.select<Commands.Exit<P>>()
|
||||
val requiredExitSignatures = HashSet<PublicKey>()
|
||||
val amountExitingLedger: Amount<Issued<P>> = if (exitCommands.isNotEmpty()) {
|
||||
val amountExitingLedger: Amount<P> = if (exitCommands.isNotEmpty()) {
|
||||
require(exitCommands.size == 1) { "There can only be one exit command" }
|
||||
val exitCommand = exitCommands.single()
|
||||
// If we want to remove debt from the ledger, that must be signed for by the owner. For now we require exit
|
||||
// commands to be signed by all input owners, unlocking the full input amount, rather than trying to detangle
|
||||
// If we want to remove debt from the ledger, that must be signed for by the beneficiary. For now we require exit
|
||||
// commands to be signed by all input beneficiarys, unlocking the full input amount, rather than trying to detangle
|
||||
// exactly who exited what.
|
||||
requiredExitSignatures.addAll(inputs.map { it.owner })
|
||||
requiredExitSignatures.addAll(inputs.map { it.beneficiary })
|
||||
exitCommand.value.amount
|
||||
} else {
|
||||
Amount(0, currency)
|
||||
Amount(0, product)
|
||||
}
|
||||
|
||||
requireThat {
|
||||
"there are no zero sized inputs" by inputs.none { it.amount.quantity == 0L }
|
||||
"at issuer ${issuer.name} the amounts balance" by
|
||||
"at obligor ${obligor.name} the amounts balance" by
|
||||
(inputAmount == outputAmount + amountExitingLedger)
|
||||
}
|
||||
|
||||
@ -375,39 +373,36 @@ class Obligation<P> : Contract {
|
||||
* A default command mutates inputs and produces identical outputs, except that the lifecycle changes.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected fun verifyDefaultCommand(inputs: List<State<P>>,
|
||||
outputs: List<State<P>>,
|
||||
tx: TransactionForContract,
|
||||
setLifecycleCommand: AuthenticatedObject<Commands.SetLifecycle<P>>) {
|
||||
protected fun verifySetLifecycleCommand(inputs: List<State<P>>,
|
||||
outputs: List<State<P>>,
|
||||
tx: TransactionForContract,
|
||||
setLifecycleCommand: AuthenticatedObject<Commands.SetLifecycle<P>>) {
|
||||
// Default must not change anything except lifecycle, so number of inputs and outputs must match
|
||||
// exactly.
|
||||
require(inputs.size == outputs.size) { "Number of inputs and outputs must match" }
|
||||
|
||||
// If we have an default command, perform special processing: issued contracts can only be defaulted
|
||||
// after the due date, and default/reset can only be done by the owner
|
||||
val expectedOutputState: Lifecycle = setLifecycleCommand.value.lifecycle
|
||||
val expectedInputState: Lifecycle
|
||||
|
||||
expectedInputState = when (expectedOutputState) {
|
||||
Lifecycle.DEFAULTED -> Lifecycle.NORMAL
|
||||
Lifecycle.NORMAL -> Lifecycle.DEFAULTED
|
||||
}
|
||||
// after the due date, and default/reset can only be done by the beneficiary
|
||||
val expectedInputLifecycle: Lifecycle = setLifecycleCommand.value.inverse
|
||||
val expectedOutputLifecycle: Lifecycle = setLifecycleCommand.value.lifecycle
|
||||
|
||||
// Check that we're past the deadline for ALL involved inputs, and that the output states correspond 1:1
|
||||
for ((stateIdx, input) in inputs.withIndex()) {
|
||||
val actualOutput = outputs[stateIdx]
|
||||
val deadline = input.dueBefore
|
||||
// TODO: Determining correct timestamp authority needs rework now that timestamping service is part of
|
||||
// notary.
|
||||
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
|
||||
val expectedOutput: State<P> = input.copy(lifecycle = expectedOutputState)
|
||||
val expectedOutput: State<P> = input.copy(lifecycle = expectedOutputLifecycle)
|
||||
|
||||
requireThat {
|
||||
"there is a timestamp from the authority" by (timestamp != null)
|
||||
"the due date has passed" by (timestamp?.after?.isBefore(deadline) ?: false)
|
||||
"input state lifecycle is correct" by (input.lifecycle == expectedInputState)
|
||||
"the due date has passed" by (timestamp!!.after?.isAfter(deadline) ?: false)
|
||||
"input state lifecycle is correct" by (input.lifecycle == expectedInputLifecycle)
|
||||
"output state corresponds exactly to input state, with lifecycle changed" by (expectedOutput == actualOutput)
|
||||
}
|
||||
}
|
||||
val owningPubKeys = inputs.map { it.owner }.toSet()
|
||||
val owningPubKeys = inputs.map { it.beneficiary }.toSet()
|
||||
val keysThatSigned = setLifecycleCommand.signers.toSet()
|
||||
requireThat {
|
||||
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
|
||||
@ -417,18 +412,17 @@ class Obligation<P> : Contract {
|
||||
@VisibleForTesting
|
||||
protected fun verifyIssueCommand(inputs: List<State<P>>,
|
||||
outputs: List<State<P>>,
|
||||
tx: TransactionForContract,
|
||||
issueCommand: AuthenticatedObject<Commands.Issue<P>>,
|
||||
currency: Issued<P>,
|
||||
issuer: Party) {
|
||||
issued: Issued<P>,
|
||||
obligor: Party) {
|
||||
// If we have an issue command, perform special processing: the group is must have no inputs,
|
||||
// and that signatures are present for all issuers.
|
||||
// and that signatures are present for all obligors.
|
||||
|
||||
val inputAmount = inputs.sumObligationsOrZero(currency)
|
||||
val outputAmount = outputs.sumObligations<P>()
|
||||
val inputAmount: Amount<P> = inputs.sumObligationsOrZero(issued.product)
|
||||
val outputAmount: Amount<P> = outputs.sumObligations<P>()
|
||||
requireThat {
|
||||
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
|
||||
"output deposits are owned by a command signer" by (issuer in issueCommand.signingParties)
|
||||
"output deposits are owned by a command signer" by (obligor in issueCommand.signingParties)
|
||||
"output values sum to more than the inputs" by (outputAmount > inputAmount)
|
||||
"valid settlement issuance definition is not this issuance definition" by inputs.none { it.issuanceDef in it.acceptableIssuanceDefinitions }
|
||||
}
|
||||
@ -448,24 +442,25 @@ class Obligation<P> : Contract {
|
||||
"all outputs are in the normal state " by outputs.all { it.lifecycle == Lifecycle.NORMAL }
|
||||
}
|
||||
|
||||
val token = netState.issued
|
||||
// Create two maps of balances from issuers to owners, one for input states, the other for output states.
|
||||
val inputBalances = extractAmountsDue(token, inputs)
|
||||
val outputBalances = extractAmountsDue(token, outputs)
|
||||
val template = netState.template
|
||||
val product = template.product
|
||||
// Create two maps of balances from obligors to beneficiaries, one for input states, the other for output states.
|
||||
val inputBalances = extractAmountsDue(product, inputs)
|
||||
val outputBalances = extractAmountsDue(product, outputs)
|
||||
|
||||
// Sum the columns of the matrices. This will yield the net amount payable to/from each party to/from all other participants.
|
||||
// The two summaries must match, reflecting that the amounts owed match on both input and output.
|
||||
requireThat {
|
||||
"all input states use the expected token" by (inputs.all { it.issuanceDef.issued == token })
|
||||
"all output states use the expected token" by (outputs.all { it.issuanceDef.issued == token })
|
||||
"all input states use the same template" by (inputs.all { it.template == template })
|
||||
"all output states use the same template" by (outputs.all { it.template == template })
|
||||
"amounts owed on input and output must match" by (sumAmountsDue(inputBalances) == sumAmountsDue(outputBalances))
|
||||
}
|
||||
|
||||
// TODO: Handle proxies nominated by parties, i.e. a central clearing service
|
||||
val involvedParties = inputs.map { it.owner }.union(inputs.map { it.issuer.owningKey }).toSet()
|
||||
val involvedParties = inputs.map { it.beneficiary }.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(involvedParties.intersect(command.signers).isNotEmpty()) { "any involved party has signed" }
|
||||
NetType.CLOSE_OUT -> require(command.signers.intersect(involvedParties).isNotEmpty()) { "any involved party has signed" }
|
||||
// Require signatures from all parties (this constraint can be changed for other contracts, and is used as a
|
||||
// placeholder while exact requirements are established), or fail the transaction.
|
||||
NetType.PAYMENT -> require(command.signers.containsAll(involvedParties)) { "all involved parties have signed" }
|
||||
@ -479,20 +474,20 @@ class Obligation<P> : Contract {
|
||||
outputs: List<State<P>>,
|
||||
tx: TransactionForContract,
|
||||
command: AuthenticatedObject<Commands.Settle<P>>,
|
||||
currency: Issued<P>,
|
||||
issuer: Party,
|
||||
issued: Issued<P>,
|
||||
obligor: Party,
|
||||
key: IssuanceDefinition<P>) {
|
||||
val template = key.template
|
||||
val inputAmount = inputs.sumObligationsOrNull<P>() ?: throw IllegalArgumentException("there is at least one obligation input for this group")
|
||||
val outputAmount = outputs.sumObligationsOrZero(currency)
|
||||
val inputAmount: Amount<P> = inputs.sumObligationsOrNull<P>() ?: throw IllegalArgumentException("there is at least one obligation input for this group")
|
||||
val outputAmount: Amount<P> = outputs.sumObligationsOrZero(issued.product)
|
||||
|
||||
// Sum up all cash contracts that are moving and fulfil our requirements
|
||||
// Sum up all asset state objects that are moving and fulfil our requirements
|
||||
|
||||
// The cash contract verification handles ensuring there's inputs enough to cover the output states, we only
|
||||
// care about counting how much cash is output in this transaction. We then calculate the difference in
|
||||
// The fungible asset contract verification handles ensuring there's inputs enough to cover the output states,
|
||||
// we only care about counting how much is output in this transaction. We then calculate the difference in
|
||||
// settlement amounts between the transaction inputs and outputs, and the two must match. No elimination is
|
||||
// done of amounts paid in by each owner, as it's presumed the owners have enough sense to do that themselves.
|
||||
// Therefore if someone actually signed the following transaction:
|
||||
// done of amounts paid in by each beneficiary, as it's presumed the beneficiaries have enough sense to do that
|
||||
// themselves. Therefore if someone actually signed the following transaction (using cash just for an example):
|
||||
//
|
||||
// Inputs:
|
||||
// £1m cash owned by B
|
||||
@ -500,37 +495,37 @@ class Obligation<P> : Contract {
|
||||
// Outputs:
|
||||
// £1m cash owned by B
|
||||
// Commands:
|
||||
// Settle (signed by B)
|
||||
// Settle (signed by A)
|
||||
// Move (signed by B)
|
||||
//
|
||||
// That would pass this check. Ensuring they do not is best addressed in the transaction generation stage.
|
||||
val cashStates = tx.outStates.filterIsInstance<FungibleAssetState<*, *>>()
|
||||
val acceptableCashStates = cashStates
|
||||
// TODO: This filter is nonsense, because it just checks there is a cash contract loaded, we need to
|
||||
// verify the cash contract is the cash contract we expect.
|
||||
val assetStates = tx.outputs.filterIsInstance<FungibleAssetState<*, *>>()
|
||||
val acceptableAssetStates = assetStates
|
||||
// TODO: This filter is nonsense, because it just checks there is an asset contract loaded, we need to
|
||||
// verify the asset contract is the asset contract we expect.
|
||||
// Something like:
|
||||
// attachments.mustHaveOneOf(key.acceptableCashContract)
|
||||
// attachments.mustHaveOneOf(key.acceptableAssetContract)
|
||||
.filter { it.contract.legalContractReference in template.acceptableContracts }
|
||||
// Restrict the states to those of the correct issuance definition (this normally
|
||||
// covers currency and issuer, but is opaque to us)
|
||||
.filter { it.issuanceDef in template.acceptableIssuanceDefinitions }
|
||||
// covers issued product and obligor, but is opaque to us)
|
||||
.filter { it.issuanceDef in template.acceptableIssuedProducts }
|
||||
// Catch that there's nothing useful here, so we can dump out a useful error
|
||||
requireThat {
|
||||
"there are cash state outputs" by (cashStates.size > 0)
|
||||
"there are defined acceptable cash states" by (acceptableCashStates.size > 0)
|
||||
"there are fungible asset state outputs" by (assetStates.size > 0)
|
||||
"there are defined acceptable fungible asset states" by (acceptableAssetStates.size > 0)
|
||||
}
|
||||
|
||||
val amountReceivedByOwner = acceptableCashStates.groupBy { it.owner }
|
||||
val amountReceivedByOwner = acceptableAssetStates.groupBy { it.owner }
|
||||
// Note we really do want to search all commands, because we want move commands of other contracts, not just
|
||||
// this one.
|
||||
val moveCommands = tx.commands.select<MoveCommand>()
|
||||
var totalPenniesSettled = 0L
|
||||
val requiredSigners = inputs.map { it.issuer.owningKey }.toSet()
|
||||
val requiredSigners = inputs.map { it.obligor.owningKey }.toSet()
|
||||
|
||||
for ((owner, obligations) in inputs.groupBy { it.owner }) {
|
||||
val settled = amountReceivedByOwner[owner]?.sumCashOrNull()
|
||||
for ((beneficiary, obligations) in inputs.groupBy { it.beneficiary }) {
|
||||
val settled = amountReceivedByOwner[beneficiary]?.sumFungibleOrNull<P>()
|
||||
if (settled != null) {
|
||||
val debt = obligations.sumObligationsOrZero(currency)
|
||||
val debt = obligations.sumObligationsOrZero(issued)
|
||||
require(settled.quantity <= debt.quantity) { "Payment of $settled must not exceed debt $debt" }
|
||||
totalPenniesSettled += settled.quantity
|
||||
}
|
||||
@ -542,19 +537,19 @@ class Obligation<P> : Contract {
|
||||
"all move commands relate to this contract" by (moveCommands.map { it.value.contractHash }
|
||||
.all { it == null || it == legalContractReference })
|
||||
"contract does not try to consume itself" by (moveCommands.map { it.value }.filterIsInstance<Commands.Move<P>>()
|
||||
.none { it.aggregateState.issued in template.acceptableIssuanceDefinitions })
|
||||
"amounts paid must match recipients to settle" by inputs.map { it.owner }.containsAll(amountReceivedByOwner.keys)
|
||||
"signatures are present from all issuers" by command.signers.containsAll(requiredSigners)
|
||||
.none { it.aggregateState == key })
|
||||
"amounts paid must match recipients to settle" by inputs.map { it.beneficiary }.containsAll(amountReceivedByOwner.keys)
|
||||
"signatures are present from all obligors" by command.signers.containsAll(requiredSigners)
|
||||
"there are no zero sized inputs" by inputs.none { it.amount.quantity == 0L }
|
||||
"at issuer ${issuer.name} the obligations after settlement balance" by
|
||||
(inputAmount == outputAmount + Amount(totalPenniesSettled, currency))
|
||||
"at obligor ${obligor.name} the obligations after settlement balance" by
|
||||
(inputAmount == outputAmount + Amount(totalPenniesSettled, issued.product))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a transaction performing close-out netting of two or more states.
|
||||
*
|
||||
* @param signer the party who will sign the transaction. Must be one of the issuer or owner.
|
||||
* @param signer the party who will sign the transaction. Must be one of the obligor or beneficiary.
|
||||
* @param states two or more states, which must be compatible for bilateral netting (same issuance definitions,
|
||||
* and same parties involved).
|
||||
*/
|
||||
@ -570,7 +565,9 @@ class Obligation<P> : Contract {
|
||||
"signer is in the state parties" by (signer in netState!!.partyKeys)
|
||||
}
|
||||
|
||||
tx.addOutputState(states.reduce { stateA, stateB -> stateA.net(stateB) })
|
||||
val out = states.reduce { stateA, stateB -> stateA.net(stateB) }
|
||||
if (out.quantity > 0L)
|
||||
tx.addOutputState(out)
|
||||
tx.addCommand(Commands.Net(NetType.PAYMENT), signer)
|
||||
}
|
||||
|
||||
@ -578,20 +575,20 @@ class Obligation<P> : Contract {
|
||||
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
|
||||
*/
|
||||
fun generateIssue(tx: TransactionBuilder,
|
||||
issuer: Party,
|
||||
obligor: Party,
|
||||
issuanceDef: StateTemplate<P>,
|
||||
pennies: Long,
|
||||
owner: PublicKey,
|
||||
beneficiary: PublicKey,
|
||||
notary: Party) {
|
||||
check(tx.inputStates().isEmpty())
|
||||
check(tx.outputStates().map { it.data }.sumObligationsOrNull<P>() == null)
|
||||
val aggregateState = IssuanceDefinition(issuer, issuanceDef)
|
||||
tx.addOutputState(State(Lifecycle.NORMAL, issuer, issuanceDef, pennies, owner), notary)
|
||||
tx.addCommand(Commands.Issue(aggregateState), issuer.owningKey)
|
||||
val aggregateState = IssuanceDefinition(obligor, issuanceDef)
|
||||
tx.addOutputState(State(Lifecycle.NORMAL, obligor, issuanceDef, pennies, beneficiary), notary)
|
||||
tx.addCommand(Commands.Issue(aggregateState), obligor.owningKey)
|
||||
}
|
||||
|
||||
fun generatePaymentNetting(tx: TransactionBuilder,
|
||||
currency: Issued<P>,
|
||||
issued: Issued<P>,
|
||||
notary: Party,
|
||||
vararg states: State<P>) {
|
||||
requireThat {
|
||||
@ -599,20 +596,20 @@ class Obligation<P> : Contract {
|
||||
}
|
||||
val groups = states.groupBy { it.multilateralNetState }
|
||||
val partyLookup = HashMap<PublicKey, Party>()
|
||||
val signers = states.map { it.owner }.union(states.map { it.issuer.owningKey }).toSet()
|
||||
val signers = states.map { it.beneficiary }.union(states.map { it.obligor.owningKey }).toSet()
|
||||
|
||||
// Create a lookup table of the party that each public key represents.
|
||||
states.map { it.issuer }.forEach { partyLookup.put(it.owningKey, it) }
|
||||
states.map { it.obligor }.forEach { partyLookup.put(it.owningKey, it) }
|
||||
|
||||
for ((netState, groupStates) in groups) {
|
||||
// Extract the net balances
|
||||
val netBalances = netAmountsDue(extractAmountsDue(currency, states.asIterable()))
|
||||
val netBalances = netAmountsDue(extractAmountsDue(issued.product, states.asIterable()))
|
||||
|
||||
netBalances
|
||||
// Convert the balances into obligation state objects
|
||||
.map { entry ->
|
||||
State(Lifecycle.NORMAL, partyLookup[entry.key.first]!!,
|
||||
netState.issuanceDef, entry.value.quantity, entry.key.second)
|
||||
netState.template, entry.value.quantity, entry.key.second)
|
||||
}
|
||||
// Add the new states to the TX
|
||||
.forEach { tx.addOutputState(it, notary) }
|
||||
@ -647,7 +644,7 @@ class Obligation<P> : Contract {
|
||||
val outState = stateAndRef.state.data.copy(lifecycle = lifecycle)
|
||||
tx.addInputState(stateAndRef)
|
||||
tx.addOutputState(outState, notary)
|
||||
partiesUsed.add(stateAndRef.state.data.owner)
|
||||
partiesUsed.add(stateAndRef.state.data.beneficiary)
|
||||
}
|
||||
tx.addCommand(Commands.SetLifecycle(aggregateState, lifecycle), partiesUsed.distinct())
|
||||
}
|
||||
@ -657,54 +654,55 @@ class Obligation<P> : Contract {
|
||||
/**
|
||||
* @param statesAndRefs a list of state objects, which MUST all have the same aggregate state. This is done as
|
||||
* only a single settlement command can be present in a transaction, to avoid potential problems with allocating
|
||||
* cash to different obligation issuances.
|
||||
* @param cashStatesAndRefs a list of cash state objects, which MUST all be in the same currency. It is strongly
|
||||
* encouraged that these all have the same owner.
|
||||
* assets to different obligation issuances.
|
||||
* @param assetStatesAndRefs a list of fungible asset state objects, which MUST all be of the same issued product.
|
||||
* It is strongly encouraged that these all have the same beneficiary.
|
||||
* @param moveCommand the command used to move the asset state objects to their new owner.
|
||||
*/
|
||||
fun generateSettle(tx: TransactionBuilder,
|
||||
statesAndRefs: Iterable<StateAndRef<State<P>>>,
|
||||
cashStatesAndRefs: Iterable<StateAndRef<FungibleAssetState<P, *>>>,
|
||||
assetStatesAndRefs: Iterable<StateAndRef<FungibleAssetState<P, *>>>,
|
||||
moveCommand: MoveCommand,
|
||||
notary: Party) {
|
||||
val states = statesAndRefs.map { it.state }
|
||||
val notary = states.first().notary
|
||||
val obligationIssuer = states.first().data.issuer
|
||||
val obligationOwner = states.first().data.owner
|
||||
val obligationIssuer = states.first().data.obligor
|
||||
val obligationOwner = states.first().data.beneficiary
|
||||
|
||||
requireThat {
|
||||
"all cash states use the same notary" by (cashStatesAndRefs.all { it.state.notary == notary })
|
||||
"all fungible asset states use the same notary" by (assetStatesAndRefs.all { it.state.notary == notary })
|
||||
"all obligation states are in the normal state" by (statesAndRefs.all { it.state.data.lifecycle == Lifecycle.NORMAL })
|
||||
"all obligation states use the same notary" by (statesAndRefs.all { it.state.notary == notary })
|
||||
"all obligation states have the same issuer" by (statesAndRefs.all { it.state.data.issuer == obligationIssuer })
|
||||
"all obligation states have the same owner" by (statesAndRefs.all { it.state.data.owner == obligationOwner })
|
||||
"all obligation states have the same obligor" by (statesAndRefs.all { it.state.data.obligor == obligationIssuer })
|
||||
"all obligation states have the same beneficiary" by (statesAndRefs.all { it.state.data.beneficiary == obligationOwner })
|
||||
}
|
||||
|
||||
// TODO: A much better (but more complex) solution would be to have two iterators, one for obligations,
|
||||
// one for cash, and step through each in a semi-synced manner. For now however we just bundle all the states
|
||||
// one for the assets, and step through each in a semi-synced manner. For now however we just bundle all the states
|
||||
// on each side together
|
||||
|
||||
val issuanceDef = getIssuanceDefinitionOrThrow(statesAndRefs.map { it.state.data })
|
||||
val template = issuanceDef.template
|
||||
val obligationTotal: Amount<Issued<P>> = states.map { it.data }.sumObligations<P>()
|
||||
var obligationRemaining: Amount<Issued<P>> = obligationTotal
|
||||
val cashSigners = HashSet<PublicKey>()
|
||||
val obligationTotal: Amount<P> = states.map { it.data }.sumObligations<P>()
|
||||
var obligationRemaining: Amount<P> = obligationTotal
|
||||
val assetSigners = HashSet<PublicKey>()
|
||||
|
||||
statesAndRefs.forEach { tx.addInputState(it) }
|
||||
|
||||
// Move the cash to the new owner
|
||||
cashStatesAndRefs.forEach {
|
||||
// Move the assets to the new beneficiary
|
||||
assetStatesAndRefs.forEach {
|
||||
if (obligationRemaining.quantity > 0L) {
|
||||
val cashState = it.state
|
||||
val assetState = it.state
|
||||
tx.addInputState(it)
|
||||
if (obligationRemaining >= cashState.data.amount) {
|
||||
tx.addOutputState(cashState.data.move(cashState.data.amount, obligationOwner), notary)
|
||||
obligationRemaining -= cashState.data.amount
|
||||
if (obligationRemaining >= assetState.data.productAmount) {
|
||||
tx.addOutputState(assetState.data.move(assetState.data.productAmount, obligationOwner), notary)
|
||||
obligationRemaining -= assetState.data.productAmount
|
||||
} else {
|
||||
// Split the state in two, sending the change back to the previous owner
|
||||
tx.addOutputState(cashState.data.move(obligationRemaining, obligationOwner), notary)
|
||||
tx.addOutputState(cashState.data.move(cashState.data.amount - obligationRemaining, cashState.data.owner), notary)
|
||||
// Split the state in two, sending the change back to the previous beneficiary
|
||||
tx.addOutputState(assetState.data.move(obligationRemaining, obligationOwner), notary)
|
||||
tx.addOutputState(assetState.data.move(assetState.data.productAmount - obligationRemaining, assetState.data.owner), notary)
|
||||
obligationRemaining -= Amount(0L, obligationRemaining.token)
|
||||
}
|
||||
cashSigners.add(cashState.data.owner)
|
||||
assetSigners.add(assetState.data.owner)
|
||||
}
|
||||
}
|
||||
|
||||
@ -715,8 +713,8 @@ class Obligation<P> : Contract {
|
||||
// Destroy all of the states
|
||||
}
|
||||
|
||||
// Add the cash move command and obligation settle
|
||||
tx.addCommand(Cash.Commands.Move(), cashSigners.toList())
|
||||
// Add the asset move command and obligation settle
|
||||
tx.addCommand(moveCommand, assetSigners.toList())
|
||||
tx.addCommand(Commands.Settle(issuanceDef, obligationTotal - obligationRemaining), obligationOwner)
|
||||
}
|
||||
|
||||
@ -731,17 +729,17 @@ class Obligation<P> : Contract {
|
||||
|
||||
|
||||
/**
|
||||
* Convert a list of settlement states into total from each issuer to a owner.
|
||||
* Convert a list of settlement states into total from each obligor to a beneficiary.
|
||||
*
|
||||
* @return a map of issuer/owner pairs to the balance due.
|
||||
* @return a map of obligor/beneficiary pairs to the balance due.
|
||||
*/
|
||||
fun <P> extractAmountsDue(currency: Issued<P>, states: Iterable<Obligation.State<P>>): Map<Pair<PublicKey, PublicKey>, Amount<Issued<P>>> {
|
||||
val balances = HashMap<Pair<PublicKey, PublicKey>, Amount<Issued<P>>>()
|
||||
fun <P> extractAmountsDue(product: P, states: Iterable<Obligation.State<P>>): Map<Pair<PublicKey, PublicKey>, Amount<P>> {
|
||||
val balances = HashMap<Pair<PublicKey, PublicKey>, Amount<P>>()
|
||||
|
||||
states.forEach { state ->
|
||||
val key = Pair(state.issuer.owningKey, state.owner)
|
||||
val balance = balances[key] ?: Amount(0L, currency)
|
||||
balances[key] = balance + state.amount
|
||||
val key = Pair(state.obligor.owningKey, state.beneficiary)
|
||||
val balance = balances[key] ?: Amount(0L, product)
|
||||
balances[key] = balance + state.productAmount
|
||||
}
|
||||
|
||||
return balances
|
||||
@ -750,12 +748,12 @@ fun <P> extractAmountsDue(currency: Issued<P>, states: Iterable<Obligation.State
|
||||
/**
|
||||
* Net off the amounts due between parties.
|
||||
*/
|
||||
fun <P> netAmountsDue(balances: Map<Pair<PublicKey, PublicKey>, Amount<Issued<P>>>): Map<Pair<PublicKey, PublicKey>, Amount<Issued<P>>> {
|
||||
val nettedBalances = HashMap<Pair<PublicKey, PublicKey>, Amount<Issued<P>>>()
|
||||
fun <P> netAmountsDue(balances: Map<Pair<PublicKey, PublicKey>, Amount<P>>): Map<Pair<PublicKey, PublicKey>, Amount<P>> {
|
||||
val nettedBalances = HashMap<Pair<PublicKey, PublicKey>, Amount<P>>()
|
||||
|
||||
balances.forEach { balance ->
|
||||
val (issuer, owner) = balance.key
|
||||
val oppositeKey = Pair(owner, issuer)
|
||||
val (obligor, beneficiary) = balance.key
|
||||
val oppositeKey = Pair(beneficiary, obligor)
|
||||
val opposite = (balances[oppositeKey] ?: Amount(0L, balance.value.token))
|
||||
// Drop zero balances
|
||||
if (balance.value > opposite) {
|
||||
@ -770,9 +768,9 @@ fun <P> netAmountsDue(balances: Map<Pair<PublicKey, PublicKey>, Amount<Issued<P>
|
||||
|
||||
/**
|
||||
* Calculate the total balance movement for each party in the transaction, based off a summary of balances between
|
||||
* each issuer and owner.
|
||||
* each obligor and beneficiary.
|
||||
*
|
||||
* @param balances payments due, indexed by issuer and owner. Zero balances are stripped from the map before being
|
||||
* @param balances payments due, indexed by obligor and beneficiary. Zero balances are stripped from the map before being
|
||||
* returned.
|
||||
*/
|
||||
fun <P> sumAmountsDue(balances: Map<Pair<PublicKey, PublicKey>, Amount<P>>): Map<PublicKey, Long> {
|
||||
@ -785,17 +783,17 @@ fun <P> sumAmountsDue(balances: Map<Pair<PublicKey, PublicKey>, Amount<P>>): Map
|
||||
}
|
||||
|
||||
for ((key, amount) in balances) {
|
||||
val (issuer, owner) = key
|
||||
// Subtract it from the issuer
|
||||
sum[issuer] = sum[issuer]!! - amount.quantity
|
||||
// Add it to the owner
|
||||
sum[owner] = sum[owner]!! + amount.quantity
|
||||
val (obligor, beneficiary) = key
|
||||
// Subtract it from the obligor
|
||||
sum[obligor] = sum[obligor]!! - amount.quantity
|
||||
// Add it to the beneficiary
|
||||
sum[beneficiary] = sum[beneficiary]!! + amount.quantity
|
||||
}
|
||||
|
||||
// Strip zero balances
|
||||
val iterator = sum.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val (key, amount) = iterator.next()
|
||||
val amount = iterator.next().value
|
||||
if (amount == 0L) {
|
||||
iterator.remove()
|
||||
}
|
||||
@ -804,16 +802,15 @@ fun <P> sumAmountsDue(balances: Map<Pair<PublicKey, PublicKey>, Amount<P>>): Map
|
||||
return sum
|
||||
}
|
||||
|
||||
/** Sums the cash states in the list, throwing an exception if there are none.
|
||||
* All cash states in the list are presumed to be nettable.
|
||||
*/
|
||||
fun <P> Iterable<ContractState>.sumObligations() = filterIsInstance<Obligation.State<P>>().map { it.amount }.sumOrThrow()
|
||||
/** Sums the obligation states in the list, throwing an exception if there are none. All state objects in the list are presumed to be nettable. */
|
||||
fun <P> Iterable<ContractState>.sumObligations(): Amount<P>
|
||||
= filterIsInstance<Obligation.State<P>>().map { it.amount }.sumOrThrow()
|
||||
|
||||
/** Sums the cash settlement states in the list, returning null if there are none. */
|
||||
fun <P> Iterable<ContractState>.sumObligationsOrNull()
|
||||
/** Sums the obligation states in the list, returning null if there are none. */
|
||||
fun <P> Iterable<ContractState>.sumObligationsOrNull(): Amount<P>?
|
||||
= filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrNull()
|
||||
|
||||
/** Sums the cash settlement states in the list, returning zero of the given currency if there are none. */
|
||||
fun <P> Iterable<ContractState>.sumObligationsOrZero(currency: Issued<P>)
|
||||
= filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(currency)
|
||||
/** Sums the obligation states in the list, returning zero of the given product if there are none. */
|
||||
fun <P> Iterable<ContractState>.sumObligationsOrZero(product: P): Amount<P>
|
||||
= filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(product)
|
||||
|
@ -1,20 +1,26 @@
|
||||
package com.r3corda.contracts.testing
|
||||
|
||||
import com.r3corda.contracts.*
|
||||
import com.r3corda.contracts.cash.CASH_PROGRAM_ID
|
||||
import com.r3corda.contracts.cash.Cash
|
||||
import com.r3corda.contracts.asset.CASH_PROGRAM_ID
|
||||
import com.r3corda.contracts.asset.Cash
|
||||
import com.r3corda.contracts.asset.Obligation
|
||||
import com.r3corda.core.contracts.Amount
|
||||
import com.r3corda.core.contracts.Contract
|
||||
import com.r3corda.core.contracts.ContractState
|
||||
import com.r3corda.core.contracts.DUMMY_PROGRAM_ID
|
||||
import com.r3corda.core.contracts.DummyContract
|
||||
import com.r3corda.core.contracts.DummyState
|
||||
import com.r3corda.core.contracts.PartyAndReference
|
||||
import com.r3corda.core.contracts.Issued
|
||||
import com.r3corda.core.contracts.ContractState
|
||||
import com.r3corda.core.contracts.TransactionState
|
||||
import com.r3corda.core.crypto.NullPublicKey
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.generateKeyPair
|
||||
import com.r3corda.core.testing.MINI_CORP
|
||||
import com.r3corda.core.testing.TEST_TX_TIME
|
||||
import com.r3corda.core.utilities.nonEmptySetOf
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
// In a real system this would be a persistent map of hash to bytecode and we'd instantiate the object as needed inside
|
||||
@ -27,7 +33,7 @@ val TEST_PROGRAM_MAP: Map<Contract, Class<out Contract>> = mapOf(
|
||||
IRS_PROGRAM_ID to InterestRateSwap::class.java
|
||||
)
|
||||
|
||||
fun generateState() = DummyContract.State(Random().nextInt())
|
||||
fun generateState() = DummyState(Random().nextInt())
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@ -56,6 +62,12 @@ object JavaTestHelpers {
|
||||
@JvmStatic fun withNotary(state: Cash.State, notary: Party) = TransactionState(state, notary)
|
||||
@JvmStatic fun withDeposit(state: Cash.State, deposit: PartyAndReference) = state.copy(amount = state.amount.copy(token = state.amount.token.copy(issuer = deposit)))
|
||||
|
||||
@JvmStatic fun <T> at(state: Obligation.State<T>, dueBefore: Instant) = state.copy(template = state.template.copy(dueBefore = dueBefore))
|
||||
@JvmStatic fun <T> at(issuanceDef: Obligation.IssuanceDefinition<T>, dueBefore: Instant) = issuanceDef.copy(template = issuanceDef.template.copy(dueBefore = dueBefore))
|
||||
@JvmStatic fun <T> between(state: Obligation.State<T>, parties: Pair<Party, PublicKey>) = state.copy(obligor = parties.first, beneficiary = parties.second)
|
||||
@JvmStatic fun <T> ownedBy(state: Obligation.State<T>, owner: PublicKey) = state.copy(beneficiary = owner)
|
||||
@JvmStatic fun <T> issuedBy(state: Obligation.State<T>, party: Party) = state.copy(obligor = party)
|
||||
|
||||
@JvmStatic fun ownedBy(state: CommercialPaper.State, owner: PublicKey) = state.copy(owner = owner)
|
||||
@JvmStatic fun withNotary(state: CommercialPaper.State, notary: Party) = TransactionState(state, notary)
|
||||
@JvmStatic fun ownedBy(state: ICommercialPaperState, new_owner: PublicKey) = state.withOwner(new_owner)
|
||||
@ -66,6 +78,12 @@ object JavaTestHelpers {
|
||||
Amount<Issued<Currency>>(amount.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, amount.token)),
|
||||
NullPublicKey)
|
||||
@JvmStatic fun STATE(amount: Amount<Issued<Currency>>) = Cash.State(amount, NullPublicKey)
|
||||
|
||||
// Allows you to write 100.DOLLARS.OBLIGATION
|
||||
@JvmStatic fun OBLIGATION_DEF(issued: Issued<Currency>)
|
||||
= Obligation.StateTemplate(nonEmptySetOf(Cash().legalContractReference), nonEmptySetOf(issued), TEST_TX_TIME)
|
||||
@JvmStatic fun OBLIGATION(amount: Amount<Issued<Currency>>) = Obligation.State(Obligation.Lifecycle.NORMAL, MINI_CORP,
|
||||
OBLIGATION_DEF(amount.token), amount.quantity, NullPublicKey)
|
||||
}
|
||||
|
||||
|
||||
@ -75,6 +93,12 @@ infix fun Cash.State.`issued by`(deposit: PartyAndReference) = JavaTestHelpers.i
|
||||
infix fun Cash.State.`with notary`(notary: Party) = JavaTestHelpers.withNotary(this, notary)
|
||||
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = JavaTestHelpers.withDeposit(this, deposit)
|
||||
|
||||
infix fun <T> Obligation.State<T>.`at`(dueBefore: Instant) = JavaTestHelpers.at(this, dueBefore)
|
||||
infix fun <T> Obligation.IssuanceDefinition<T>.`at`(dueBefore: Instant) = JavaTestHelpers.at(this, dueBefore)
|
||||
infix fun <T> Obligation.State<T>.`between`(parties: Pair<Party, PublicKey>) = JavaTestHelpers.between(this, parties)
|
||||
infix fun <T> Obligation.State<T>.`owned by`(owner: PublicKey) = JavaTestHelpers.ownedBy(this, owner)
|
||||
infix fun <T> Obligation.State<T>.`issued by`(party: Party) = JavaTestHelpers.issuedBy(this, party)
|
||||
|
||||
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = JavaTestHelpers.ownedBy(this, owner)
|
||||
infix fun CommercialPaper.State.`with notary`(notary: Party) = JavaTestHelpers.withNotary(this, notary)
|
||||
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = JavaTestHelpers.ownedBy(this, new_owner)
|
||||
@ -87,3 +111,6 @@ val DUMMY_CASH_ISSUER = Party("Snake Oil Issuer", DUMMY_CASH_ISSUER_KEY.public).
|
||||
val Amount<Currency>.CASH: Cash.State get() = JavaTestHelpers.CASH(this)
|
||||
val Amount<Issued<Currency>>.STATE: Cash.State get() = JavaTestHelpers.STATE(this)
|
||||
|
||||
/** Allows you to write 100.DOLLARS.CASH */
|
||||
val Issued<Currency>.OBLIGATION_DEF: Obligation.StateTemplate<Currency> get() = JavaTestHelpers.OBLIGATION_DEF(this)
|
||||
val Amount<Issued<Currency>>.OBLIGATION: Obligation.State<Currency> get() = JavaTestHelpers.OBLIGATION(this)
|
||||
|
@ -1,7 +1,7 @@
|
||||
@file:JvmName("WalletFiller")
|
||||
package com.r3corda.contracts.testing
|
||||
|
||||
import com.r3corda.contracts.cash.Cash
|
||||
import com.r3corda.contracts.asset.Cash
|
||||
import com.r3corda.core.contracts.Amount
|
||||
import com.r3corda.core.contracts.Issued
|
||||
import com.r3corda.core.contracts.SignedTransaction
|
||||
@ -66,6 +66,7 @@ private fun calculateRandomlySizedAmounts(howMuch: Amount<Currency>, min: Int, m
|
||||
val numStates = min + Math.floor(rng.nextDouble() * (max - min)).toInt()
|
||||
val amounts = LongArray(numStates)
|
||||
val baseSize = howMuch.quantity / numStates
|
||||
check(baseSize > 0) { baseSize }
|
||||
var filledSoFar = 0L
|
||||
for (i in 0..numStates - 1) {
|
||||
if (i < numStates - 1) {
|
||||
@ -76,6 +77,7 @@ private fun calculateRandomlySizedAmounts(howMuch: Amount<Currency>, min: Int, m
|
||||
// Handle inexact rounding.
|
||||
amounts[i] = howMuch.quantity - filledSoFar
|
||||
}
|
||||
check(amounts[i] >= 0) { amounts[i] }
|
||||
}
|
||||
check(amounts.sum() == howMuch.quantity)
|
||||
return amounts
|
||||
|
@ -1,13 +1,12 @@
|
||||
package com.r3corda.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.contracts.cash.Cash
|
||||
import com.r3corda.contracts.cash.sumCashBy
|
||||
import com.r3corda.contracts.asset.Cash
|
||||
import com.r3corda.contracts.asset.sumCashBy
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.signWithECDSA
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.node.NodeInfo
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import com.r3corda.core.random63BitValue
|
||||
@ -43,7 +42,8 @@ import java.util.*
|
||||
* To see an example of how to use this class, look at the unit tests.
|
||||
*/
|
||||
object TwoPartyTradeProtocol {
|
||||
val TRADE_TOPIC = "platform.trade"
|
||||
|
||||
val TOPIC = "platform.trade"
|
||||
|
||||
class UnacceptablePriceException(val givenPrice: Amount<Issued<Currency>>) : Exception()
|
||||
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
|
||||
@ -61,7 +61,7 @@ object TwoPartyTradeProtocol {
|
||||
class SignaturesFromSeller(val sellerSig: DigitalSignature.WithKey,
|
||||
val notarySig: DigitalSignature.LegallyIdentifiable)
|
||||
|
||||
open class Seller(val otherSide: SingleMessageRecipient,
|
||||
open class Seller(val otherSide: Party,
|
||||
val notaryNode: NodeInfo,
|
||||
val assetToSell: StateAndRef<OwnableState>,
|
||||
val price: Amount<Issued<Currency>>,
|
||||
@ -83,6 +83,8 @@ object TwoPartyTradeProtocol {
|
||||
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, NOTARY, SENDING_SIGS)
|
||||
}
|
||||
|
||||
override val topic: String get() = TOPIC
|
||||
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
val partialTX: SignedTransaction = receiveAndCheckProposedTransaction()
|
||||
@ -109,7 +111,7 @@ object TwoPartyTradeProtocol {
|
||||
// Make the first message we'll send to kick off the protocol.
|
||||
val hello = SellerTradeInfo(assetToSell, price, myKeyPair.public, sessionID)
|
||||
|
||||
val maybeSTX = sendAndReceive<SignedTransaction>(TRADE_TOPIC, otherSide, buyerSessionID, sessionID, hello)
|
||||
val maybeSTX = sendAndReceive<SignedTransaction>(otherSide, buyerSessionID, sessionID, hello)
|
||||
|
||||
progressTracker.currentStep = VERIFYING
|
||||
|
||||
@ -167,12 +169,12 @@ object TwoPartyTradeProtocol {
|
||||
|
||||
logger.trace { "Built finished transaction, sending back to secondary!" }
|
||||
|
||||
send(TRADE_TOPIC, otherSide, buyerSessionID, SignaturesFromSeller(ourSignature, notarySignature))
|
||||
send(otherSide, buyerSessionID, SignaturesFromSeller(ourSignature, notarySignature))
|
||||
return fullySigned
|
||||
}
|
||||
}
|
||||
|
||||
open class Buyer(val otherSide: SingleMessageRecipient,
|
||||
open class Buyer(val otherSide: Party,
|
||||
val notary: Party,
|
||||
val acceptablePrice: Amount<Issued<Currency>>,
|
||||
val typeToBuy: Class<out OwnableState>,
|
||||
@ -186,6 +188,7 @@ object TwoPartyTradeProtocol {
|
||||
|
||||
object SWAPPING_SIGNATURES : ProgressTracker.Step("Swapping signatures with the seller")
|
||||
|
||||
override val topic: String get() = TOPIC
|
||||
override val progressTracker = ProgressTracker(RECEIVING, VERIFYING, SIGNING, SWAPPING_SIGNATURES)
|
||||
|
||||
@Suspendable
|
||||
@ -196,8 +199,6 @@ object TwoPartyTradeProtocol {
|
||||
val (ptx, cashSigningPubKeys) = assembleSharedTX(tradeRequest)
|
||||
val stx = signWithOurKeys(cashSigningPubKeys, ptx)
|
||||
|
||||
// exitProcess(0)
|
||||
|
||||
val signatures = swapSignaturesWithSeller(stx, tradeRequest.sessionID)
|
||||
|
||||
logger.trace { "Got signatures from seller, verifying ... " }
|
||||
@ -216,7 +217,7 @@ object TwoPartyTradeProtocol {
|
||||
private fun receiveAndValidateTradeRequest(): SellerTradeInfo {
|
||||
progressTracker.currentStep = RECEIVING
|
||||
// Wait for a trade request to come in on our pre-provided session ID.
|
||||
val maybeTradeRequest = receive<SellerTradeInfo>(TRADE_TOPIC, sessionID)
|
||||
val maybeTradeRequest = receive<SellerTradeInfo>(sessionID)
|
||||
|
||||
progressTracker.currentStep = VERIFYING
|
||||
maybeTradeRequest.validate {
|
||||
@ -247,7 +248,7 @@ object TwoPartyTradeProtocol {
|
||||
|
||||
// TODO: Protect against the seller terminating here and leaving us in the lurch without the final tx.
|
||||
|
||||
return sendAndReceive<SignaturesFromSeller>(TRADE_TOPIC, otherSide, theirSessionID, sessionID, stx).validate { it }
|
||||
return sendAndReceive<SignaturesFromSeller>(otherSide, theirSessionID, sessionID, stx).validate { it }
|
||||
}
|
||||
|
||||
private fun signWithOurKeys(cashSigningPubKeys: List<PublicKey>, ptx: TransactionBuilder): SignedTransaction {
|
||||
|
@ -0,0 +1,61 @@
|
||||
package com.r3corda.contracts.asset;
|
||||
|
||||
import com.r3corda.core.contracts.PartyAndReference;
|
||||
import com.r3corda.core.serialization.OpaqueBytes;
|
||||
import kotlin.Unit;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.r3corda.core.testing.JavaTestHelpers.*;
|
||||
import static com.r3corda.core.contracts.JavaTestHelpers.*;
|
||||
import static com.r3corda.contracts.testing.JavaTestHelpers.*;
|
||||
|
||||
/**
|
||||
* This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL
|
||||
*/
|
||||
public class CashTestsJava {
|
||||
|
||||
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});;
|
||||
private PartyAndReference defaultIssuer = getMEGA_CORP().ref(defaultRef);
|
||||
private Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), getDUMMY_PUBKEY_1());
|
||||
private Cash.State outState = new Cash.State(inState.getAmount(), getDUMMY_PUBKEY_2());
|
||||
|
||||
@Test
|
||||
public void trivial() {
|
||||
ledger(lg -> {
|
||||
lg.transaction(tx -> {
|
||||
tx.input(inState);
|
||||
tx.failsWith("the amounts balance");
|
||||
|
||||
tx.tweak(tw -> {
|
||||
tw.output(new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), getDUMMY_PUBKEY_2()));
|
||||
return tw.failsWith("the amounts balance");
|
||||
});
|
||||
|
||||
tx.tweak(tw -> {
|
||||
tw.output(outState);
|
||||
// No command arguments
|
||||
return tw.failsWith("required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command");
|
||||
});
|
||||
tx.tweak(tw -> {
|
||||
tw.output(outState);
|
||||
tw.command(getDUMMY_PUBKEY_2(), new Cash.Commands.Move());
|
||||
return tw.failsWith("the owning keys are the same as the signing keys");
|
||||
});
|
||||
tx.tweak(tw -> {
|
||||
tw.output(outState);
|
||||
tw.output(issuedBy(outState, getMINI_CORP()));
|
||||
tw.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
|
||||
return tw.failsWith("at least one asset input");
|
||||
});
|
||||
|
||||
// Simple reallocation works.
|
||||
return tx.tweak(tw -> {
|
||||
tw.output(outState);
|
||||
tw.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
|
||||
return tw.verifies();
|
||||
});
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package com.r3corda.contracts.cash;
|
||||
|
||||
import com.r3corda.core.contracts.PartyAndReference;
|
||||
import com.r3corda.core.serialization.OpaqueBytes;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.r3corda.core.testing.JavaTestHelpers.*;
|
||||
import static com.r3corda.core.contracts.JavaTestHelpers.*;
|
||||
import static com.r3corda.contracts.testing.JavaTestHelpers.*;
|
||||
|
||||
/**
|
||||
* This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL
|
||||
*/
|
||||
public class CashTestsJava {
|
||||
|
||||
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});;
|
||||
private PartyAndReference defaultIssuer = getMEGA_CORP().ref(defaultRef);
|
||||
private Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), getDUMMY_PUBKEY_1());
|
||||
private Cash.State outState = new Cash.State(inState.getAmount(), getDUMMY_PUBKEY_2());
|
||||
|
||||
@Test
|
||||
public void trivial() {
|
||||
|
||||
transaction(tx -> {
|
||||
tx.input(inState);
|
||||
tx.failsRequirement("the amounts balance");
|
||||
|
||||
tx.tweak(tw -> {
|
||||
tw.output(new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), getDUMMY_PUBKEY_2()));
|
||||
return tw.failsRequirement("the amounts balance");
|
||||
});
|
||||
|
||||
tx.tweak(tw -> {
|
||||
tw.output(outState);
|
||||
// No command arguments
|
||||
return tw.failsRequirement("required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command");
|
||||
});
|
||||
tx.tweak(tw -> {
|
||||
tw.output(outState);
|
||||
tw.arg(getDUMMY_PUBKEY_2(), new Cash.Commands.Move());
|
||||
return tw.failsRequirement("the owning keys are the same as the signing keys");
|
||||
});
|
||||
tx.tweak(tw -> {
|
||||
tw.output(outState);
|
||||
tw.output(issuedBy(outState, getMINI_CORP()));
|
||||
tw.arg(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
|
||||
return tw.failsRequirement("at least one asset input");
|
||||
});
|
||||
|
||||
// Simple reallocation works.
|
||||
return tx.tweak(tw -> {
|
||||
tw.output(outState);
|
||||
tw.arg(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
|
||||
return tw.accepts();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.r3corda.contracts
|
||||
|
||||
import com.r3corda.contracts.cash.Cash
|
||||
import com.r3corda.contracts.asset.Cash
|
||||
import com.r3corda.contracts.testing.*
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
@ -12,15 +12,15 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.time.Instant
|
||||
import java.util.Currency
|
||||
import java.util.*
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
interface ICommercialPaperTestTemplate {
|
||||
open fun getPaper(): ICommercialPaperState
|
||||
open fun getIssueCommand(): CommandData
|
||||
open fun getRedeemCommand(): CommandData
|
||||
open fun getMoveCommand(): CommandData
|
||||
fun getPaper(): ICommercialPaperState
|
||||
fun getIssueCommand(): CommandData
|
||||
fun getRedeemCommand(): CommandData
|
||||
fun getMoveCommand(): CommandData
|
||||
}
|
||||
|
||||
class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
|
||||
@ -63,81 +63,113 @@ class CommercialPaperTestsGeneric {
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
|
||||
@Test
|
||||
fun ok() {
|
||||
trade().verify()
|
||||
}
|
||||
fun `trade lifecycle test`() {
|
||||
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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `not matured at redemption`() {
|
||||
trade(redemptionTime = TEST_TX_TIME + 2.days).expectFailureOfTx(3, "must have matured")
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
transaction("Issuance") {
|
||||
output("paper") { thisTest.getPaper() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// The CP is sold to alice for her $900, $100 less than the face value. At 10% interest after only 7 days,
|
||||
// that sounds a bit too good to be true!
|
||||
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>().data `owned by` ALICE_PUBKEY }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// Time passes, and Alice redeem's her CP for $1000, netting a $100 profit. MegaCorp has received $1200
|
||||
// as a single payment from somewhere and uses it to pay Alice off, keeping the remaining $200 as change.
|
||||
transaction("Redemption") {
|
||||
input("alice's paper")
|
||||
input("some profits")
|
||||
|
||||
fun TransactionDSL<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.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 }
|
||||
}
|
||||
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
command(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
|
||||
|
||||
tweak {
|
||||
outputs(700.DOLLARS `issued by` issuer)
|
||||
timestamp(TEST_TX_TIME + 8.days)
|
||||
this `fails with` "received amount equals the face value"
|
||||
}
|
||||
outputs(1000.DOLLARS `issued by` issuer)
|
||||
|
||||
|
||||
tweak {
|
||||
timestamp(TEST_TX_TIME + 2.days)
|
||||
this `fails with` "must have matured"
|
||||
}
|
||||
timestamp(TEST_TX_TIME + 8.days)
|
||||
|
||||
tweak {
|
||||
output { "paper".output<ICommercialPaperState>().data }
|
||||
this `fails with` "must be destroyed"
|
||||
}
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `key mismatch at issue`() {
|
||||
transactionGroup {
|
||||
transaction {
|
||||
output { thisTest.getPaper() }
|
||||
arg(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
|
||||
expectFailureOfTx(1, "signed by the claimed issuer")
|
||||
transaction {
|
||||
output { thisTest.getPaper() }
|
||||
command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "signed by the claimed issuer"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `face value is not zero`() {
|
||||
transactionGroup {
|
||||
transaction {
|
||||
output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
|
||||
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
|
||||
expectFailureOfTx(1, "face value is not zero")
|
||||
transaction {
|
||||
output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "face value is not zero"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `maturity date not in the past`() {
|
||||
transactionGroup {
|
||||
transaction {
|
||||
output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) }
|
||||
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
|
||||
expectFailureOfTx(1, "maturity date is not in the past")
|
||||
transaction {
|
||||
output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "maturity date is not in the past"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `issue cannot replace an existing state`() {
|
||||
transactionGroup {
|
||||
roots {
|
||||
transaction(thisTest.getPaper() `with notary` DUMMY_NOTARY label "paper")
|
||||
}
|
||||
transaction {
|
||||
input("paper")
|
||||
output { thisTest.getPaper() }
|
||||
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
|
||||
expectFailureOfTx(1, "there is no input state")
|
||||
transaction {
|
||||
input(thisTest.getPaper())
|
||||
output { thisTest.getPaper() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "there is no input state"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `did not receive enough money at redemption`() {
|
||||
trade(aliceGetsBack = 700.DOLLARS `issued by` issuer).expectFailureOfTx(3, "received amount equals the face value")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `paper must be destroyed by redemption`() {
|
||||
trade(destroyPaperAtRedemption = false).expectFailureOfTx(3, "must be destroyed")
|
||||
}
|
||||
|
||||
fun <T : ContractState> cashOutputsToWallet(vararg outputs: TransactionState<T>): Pair<LedgerTransaction, List<StateAndRef<T>>> {
|
||||
val ltx = LedgerTransaction(emptyList(), listOf(*outputs), emptyList(), emptyList(), SecureHash.randomSHA256(), emptyList(), TransactionType.General())
|
||||
return Pair(ltx, outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
|
||||
@ -199,52 +231,4 @@ class CommercialPaperTestsGeneric {
|
||||
|
||||
TransactionGroup(setOf(issueTX, moveTX, validRedemption), setOf(corpWalletTX, alicesWalletTX)).verify()
|
||||
}
|
||||
|
||||
// Generate a trade lifecycle with various parameters.
|
||||
fun trade(redemptionTime: Instant = TEST_TX_TIME + 8.days,
|
||||
aliceGetsBack: Amount<Issued<Currency>> = 1000.DOLLARS `issued by` issuer,
|
||||
destroyPaperAtRedemption: Boolean = true): TransactionGroupDSL<ICommercialPaperState> {
|
||||
val someProfits = 1200.DOLLARS `issued by` issuer
|
||||
return transactionGroupFor() {
|
||||
roots {
|
||||
transaction(900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY label "alice's $900")
|
||||
transaction(someProfits.STATE `owned by` MEGA_CORP_PUBKEY `with notary` DUMMY_NOTARY label "some profits")
|
||||
}
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
transaction("Issuance") {
|
||||
output("paper") { thisTest.getPaper() }
|
||||
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
}
|
||||
|
||||
// The CP is sold to alice for her $900, $100 less than the face value. At 10% interest after only 7 days,
|
||||
// that sounds a bit too good to be true!
|
||||
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.data `owned by` ALICE_PUBKEY }
|
||||
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||
arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
||||
}
|
||||
|
||||
// Time passes, and Alice redeem's her CP for $1000, netting a $100 profit. MegaCorp has received $1200
|
||||
// as a single payment from somewhere and uses it to pay Alice off, keeping the remaining $200 as change.
|
||||
transaction("Redemption") {
|
||||
input("alice's paper")
|
||||
input("some profits")
|
||||
|
||||
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
|
||||
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
|
||||
if (!destroyPaperAtRedemption)
|
||||
output { "paper".output.data }
|
||||
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
|
||||
|
||||
timestamp(redemptionTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,12 +200,12 @@ class IRSTests {
|
||||
|
||||
@Test
|
||||
fun ok() {
|
||||
trade().verify()
|
||||
trade().verifies()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ok with groups`() {
|
||||
tradegroups().verify()
|
||||
tradegroups().verifies()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -360,38 +360,40 @@ class IRSTests {
|
||||
/**
|
||||
* Generates a typical transactional history for an IRS.
|
||||
*/
|
||||
fun trade(): TransactionGroupDSL<InterestRateSwap.State> {
|
||||
fun trade(): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||
|
||||
val ld = LocalDate.of(2016, 3, 8)
|
||||
val bd = BigDecimal("0.0063518")
|
||||
|
||||
val txgroup: TransactionGroupDSL<InterestRateSwap.State> = transactionGroupFor() {
|
||||
return ledger {
|
||||
transaction("Agreement") {
|
||||
output("irs post agreement") { singleIRS() }
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction("Fix") {
|
||||
input("irs post agreement")
|
||||
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
|
||||
output("irs post first fixing") {
|
||||
"irs post agreement".output.data.copy(
|
||||
"irs post agreement".output.data.fixedLeg,
|
||||
"irs post agreement".output.data.floatingLeg,
|
||||
"irs post agreement".output.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
||||
"irs post agreement".output.data.common
|
||||
postAgreement.data.copy(
|
||||
postAgreement.data.fixedLeg,
|
||||
postAgreement.data.floatingLeg,
|
||||
postAgreement.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
||||
postAgreement.data.common
|
||||
)
|
||||
}
|
||||
arg(ORACLE_PUBKEY) {
|
||||
command(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Fix()
|
||||
}
|
||||
arg(ORACLE_PUBKEY) {
|
||||
command(ORACLE_PUBKEY) {
|
||||
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
return txgroup
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -399,9 +401,9 @@ class IRSTests {
|
||||
transaction {
|
||||
input() { singleIRS() }
|
||||
output("irs post agreement") { singleIRS() }
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "There are no in states for an agreement"
|
||||
this `fails with` "There are no in states for an agreement"
|
||||
}
|
||||
}
|
||||
|
||||
@ -413,9 +415,9 @@ class IRSTests {
|
||||
output() {
|
||||
irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "There are events in the fix schedule"
|
||||
this `fails with` "There are events in the fix schedule"
|
||||
}
|
||||
}
|
||||
|
||||
@ -427,9 +429,9 @@ class IRSTests {
|
||||
output() {
|
||||
irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "There are events in the float schedule"
|
||||
this `fails with` "There are events in the float schedule"
|
||||
}
|
||||
}
|
||||
|
||||
@ -440,18 +442,18 @@ class IRSTests {
|
||||
output() {
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(quantity = 0)))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "All notionals must be non zero"
|
||||
this `fails with` "All notionals must be non zero"
|
||||
}
|
||||
|
||||
transaction {
|
||||
output() {
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0)))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "All notionals must be non zero"
|
||||
this `fails with` "All notionals must be non zero"
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,9 +465,9 @@ class IRSTests {
|
||||
output() {
|
||||
modifiedIRS
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The fixed leg rate must be positive"
|
||||
this `fails with` "The fixed leg rate must be positive"
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,9 +482,9 @@ class IRSTests {
|
||||
output() {
|
||||
modifiedIRS
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The currency of the notionals must be the same"
|
||||
this `fails with` "The currency of the notionals must be the same"
|
||||
}
|
||||
}
|
||||
|
||||
@ -494,9 +496,9 @@ class IRSTests {
|
||||
output() {
|
||||
modifiedIRS
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "All leg notionals must be the same"
|
||||
this `fails with` "All leg notionals must be the same"
|
||||
}
|
||||
}
|
||||
|
||||
@ -508,9 +510,9 @@ class IRSTests {
|
||||
output() {
|
||||
modifiedIRS1
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The effective date is before the termination date for the fixed leg"
|
||||
this `fails with` "The effective date is before the termination date for the fixed leg"
|
||||
}
|
||||
|
||||
val modifiedIRS2 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.floatingLeg.effectiveDate.minusDays(1)))
|
||||
@ -518,9 +520,9 @@ class IRSTests {
|
||||
output() {
|
||||
modifiedIRS2
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The effective date is before the termination date for the floating leg"
|
||||
this `fails with` "The effective date is before the termination date for the floating leg"
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,9 +535,9 @@ class IRSTests {
|
||||
output() {
|
||||
modifiedIRS3
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The termination dates are aligned"
|
||||
this `fails with` "The termination dates are aligned"
|
||||
}
|
||||
|
||||
|
||||
@ -544,24 +546,23 @@ class IRSTests {
|
||||
output() {
|
||||
modifiedIRS4
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails requirement` "The effective dates are aligned"
|
||||
this `fails with` "The effective dates are aligned"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `various fixing tests`() {
|
||||
|
||||
val ld = LocalDate.of(2016, 3, 8)
|
||||
val bd = BigDecimal("0.0063518")
|
||||
|
||||
transaction {
|
||||
output("irs post agreement") { singleIRS() }
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
val oldIRS = singleIRS(1)
|
||||
@ -578,31 +579,31 @@ class IRSTests {
|
||||
|
||||
// Templated tweak for reference. A corrent fixing applied should be ok
|
||||
tweak {
|
||||
arg(ORACLE_PUBKEY) {
|
||||
command(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Fix()
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
arg(ORACLE_PUBKEY) {
|
||||
command(ORACLE_PUBKEY) {
|
||||
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
|
||||
}
|
||||
output() { newIRS }
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new
|
||||
tweak {
|
||||
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
|
||||
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
|
||||
command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
|
||||
output() { oldIRS }
|
||||
this`fails requirement` "There is at least one difference in the IRS floating leg payment schedules"
|
||||
this `fails with` "There is at least one difference in the IRS floating leg payment schedules"
|
||||
}
|
||||
|
||||
// This tests tries to sneak in a change to another fixing (which may or may not be the latest one)
|
||||
tweak {
|
||||
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
|
||||
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
arg(ORACLE_PUBKEY) {
|
||||
command(ORACLE_PUBKEY) {
|
||||
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
|
||||
}
|
||||
|
||||
@ -619,14 +620,14 @@ class IRSTests {
|
||||
newIRS.common
|
||||
)
|
||||
}
|
||||
this`fails requirement` "There is only one change in the IRS floating leg payment schedule"
|
||||
this `fails with` "There is only one change in the IRS floating leg payment schedule"
|
||||
}
|
||||
|
||||
// This tests modifies the payment currency for the fixing
|
||||
tweak {
|
||||
arg(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
|
||||
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
|
||||
command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
|
||||
|
||||
val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key }
|
||||
val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY")))
|
||||
@ -640,7 +641,7 @@ class IRSTests {
|
||||
newIRS.common
|
||||
)
|
||||
}
|
||||
this`fails requirement` "The fix payment has the same currency as the notional"
|
||||
this `fails with` "The fix payment has the same currency as the notional"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -652,13 +653,13 @@ class IRSTests {
|
||||
* result and the grouping won't work either.
|
||||
* In reality, the only fields that should be in common will be the next fixing date and the reference rate.
|
||||
*/
|
||||
fun tradegroups(): TransactionGroupDSL<InterestRateSwap.State> {
|
||||
fun tradegroups(): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||
val ld1 = LocalDate.of(2016, 3, 8)
|
||||
val bd1 = BigDecimal("0.0063518")
|
||||
|
||||
val irs = singleIRS()
|
||||
|
||||
val txgroup: TransactionGroupDSL<InterestRateSwap.State> = transactionGroupFor() {
|
||||
return ledger {
|
||||
transaction("Agreement") {
|
||||
output("irs post agreement1") {
|
||||
irs.copy(
|
||||
@ -668,8 +669,9 @@ class IRSTests {
|
||||
irs.common.copy(tradeID = "t1")
|
||||
)
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction("Agreement") {
|
||||
@ -681,40 +683,43 @@ class IRSTests {
|
||||
irs.common.copy(tradeID = "t2")
|
||||
)
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction("Fix") {
|
||||
input("irs post agreement1")
|
||||
input("irs post agreement2")
|
||||
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
|
||||
output("irs post first fixing1") {
|
||||
"irs post agreement1".output.data.copy(
|
||||
"irs post agreement1".output.data.fixedLeg,
|
||||
"irs post agreement1".output.data.floatingLeg,
|
||||
"irs post agreement1".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||
"irs post agreement1".output.data.common.copy(tradeID = "t1")
|
||||
postAgreement1.data.copy(
|
||||
postAgreement1.data.fixedLeg,
|
||||
postAgreement1.data.floatingLeg,
|
||||
postAgreement1.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||
postAgreement1.data.common.copy(tradeID = "t1")
|
||||
)
|
||||
}
|
||||
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
|
||||
output("irs post first fixing2") {
|
||||
"irs post agreement2".output.data.copy(
|
||||
"irs post agreement2".output.data.fixedLeg,
|
||||
"irs post agreement2".output.data.floatingLeg,
|
||||
"irs post agreement2".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||
"irs post agreement2".output.data.common.copy(tradeID = "t2")
|
||||
postAgreement2.data.copy(
|
||||
postAgreement2.data.fixedLeg,
|
||||
postAgreement2.data.floatingLeg,
|
||||
postAgreement2.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||
postAgreement2.data.common.copy(tradeID = "t2")
|
||||
)
|
||||
}
|
||||
|
||||
arg(ORACLE_PUBKEY) {
|
||||
command(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Fix()
|
||||
}
|
||||
arg(ORACLE_PUBKEY) {
|
||||
command(ORACLE_PUBKEY) {
|
||||
Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1)
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
return txgroup
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.r3corda.contracts.cash
|
||||
package com.r3corda.contracts.asset
|
||||
|
||||
import com.r3corda.contracts.testing.`issued by`
|
||||
import com.r3corda.contracts.testing.`owned by`
|
||||
@ -31,33 +31,33 @@ class CashTests {
|
||||
fun trivial() {
|
||||
transaction {
|
||||
input { inState }
|
||||
this `fails requirement` "the amounts balance"
|
||||
this `fails with` "the amounts balance"
|
||||
|
||||
tweak {
|
||||
output { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails requirement` "the amounts balance"
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
tweak {
|
||||
output { outState }
|
||||
// No command arguments
|
||||
this `fails requirement` "required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command"
|
||||
this `fails with` "required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command"
|
||||
}
|
||||
tweak {
|
||||
output { outState }
|
||||
arg(DUMMY_PUBKEY_2) { Cash.Commands.Move() }
|
||||
this `fails requirement` "the owning keys are the same as the signing keys"
|
||||
command(DUMMY_PUBKEY_2) { Cash.Commands.Move() }
|
||||
this `fails with` "the owning keys are the same as the signing keys"
|
||||
}
|
||||
tweak {
|
||||
output { outState }
|
||||
output { outState `issued by` MINI_CORP }
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this `fails requirement` "at least one asset input"
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this `fails with` "at least one asset input"
|
||||
}
|
||||
// Simple reallocation works.
|
||||
tweak {
|
||||
output { outState }
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this.accepts()
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,19 +66,19 @@ class CashTests {
|
||||
fun issueMoney() {
|
||||
// Check we can't "move" money into existence.
|
||||
transaction {
|
||||
input { DummyContract.State() }
|
||||
input { DummyState() }
|
||||
output { outState }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
command(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
|
||||
this `fails requirement` "there is at least one asset input"
|
||||
this `fails with` "there is at least one asset input"
|
||||
}
|
||||
|
||||
// Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised
|
||||
// institution is allowed to issue as much cash as they want.
|
||||
transaction {
|
||||
output { outState }
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Issue() }
|
||||
this `fails requirement` "output deposits are owned by a command signer"
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Issue() }
|
||||
this `fails with` "output deposits are owned by a command signer"
|
||||
}
|
||||
transaction {
|
||||
output {
|
||||
@ -88,11 +88,11 @@ class CashTests {
|
||||
)
|
||||
}
|
||||
tweak {
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue(0) }
|
||||
this `fails requirement` "has a nonce"
|
||||
command(MINI_CORP_PUBKEY) { Cash.Commands.Issue(0) }
|
||||
this `fails with` "has a nonce"
|
||||
}
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this.accepts()
|
||||
command(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// Test generation works.
|
||||
@ -120,14 +120,14 @@ class CashTests {
|
||||
|
||||
// Move fails: not allowed to summon money.
|
||||
tweak {
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this `fails with` "at issuer MegaCorp the amounts balance"
|
||||
}
|
||||
|
||||
// Issue works.
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this.accepts()
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,36 +135,36 @@ class CashTests {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { inState.copy(amount = inState.amount / 2) }
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this `fails requirement` "output values sum to more than the inputs"
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this `fails with` "output values sum to more than the inputs"
|
||||
}
|
||||
|
||||
// Can't have an issue command that doesn't actually issue money.
|
||||
transaction {
|
||||
input { inState }
|
||||
output { inState }
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this `fails requirement` "output values sum to more than the inputs"
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this `fails with` "output values sum to more than the inputs"
|
||||
}
|
||||
|
||||
// Can't have any other commands if we have an issue command (because the issue command overrules them)
|
||||
transaction {
|
||||
input { inState }
|
||||
output { inState.copy(amount = inState.amount * 2) }
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this `fails requirement` "there is only a single issue command"
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this `fails with` "there is only a single issue command"
|
||||
}
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
this `fails requirement` "there is only a single issue command"
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
this `fails with` "there is only a single issue command"
|
||||
}
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(inState.amount / 2) }
|
||||
this `fails requirement` "there is only a single issue command"
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(inState.amount / 2) }
|
||||
this `fails with` "there is only a single issue command"
|
||||
}
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,25 +191,25 @@ class CashTests {
|
||||
fun testMergeSplit() {
|
||||
// Splitting value works.
|
||||
transaction {
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
tweak {
|
||||
input { inState }
|
||||
for (i in 1..4) output { inState.copy(amount = inState.amount / 4) }
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
// Merging 4 inputs into 2 outputs works.
|
||||
tweak {
|
||||
for (i in 1..4) input { inState.copy(amount = inState.amount / 4) }
|
||||
output { inState.copy(amount = inState.amount / 2) }
|
||||
output { inState.copy(amount = inState.amount / 2) }
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
// Merging 2 inputs into 1 works.
|
||||
tweak {
|
||||
input { inState.copy(amount = inState.amount / 2) }
|
||||
input { inState.copy(amount = inState.amount / 2) }
|
||||
output { inState }
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -219,13 +219,13 @@ class CashTests {
|
||||
transaction {
|
||||
input { inState }
|
||||
input { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails requirement` "zero sized inputs"
|
||||
this `fails with` "zero sized inputs"
|
||||
}
|
||||
transaction {
|
||||
input { inState }
|
||||
output { inState }
|
||||
output { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails requirement` "zero sized outputs"
|
||||
this `fails with` "zero sized outputs"
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,21 +235,21 @@ class CashTests {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState `issued by` MINI_CORP }
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
this `fails with` "at issuer MegaCorp the amounts balance"
|
||||
}
|
||||
// Can't change deposit reference when splitting.
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState.copy(amount = inState.amount / 2).editDepositRef(0) }
|
||||
output { outState.copy(amount = inState.amount / 2).editDepositRef(1) }
|
||||
this `fails requirement` "for deposit [01] at issuer MegaCorp the amounts balance"
|
||||
this `fails with` "for deposit [01] at issuer MegaCorp the amounts balance"
|
||||
}
|
||||
// Can't mix currencies.
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) }
|
||||
output { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) }
|
||||
this `fails requirement` "the amounts balance"
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
transaction {
|
||||
input { inState }
|
||||
@ -260,22 +260,22 @@ class CashTests {
|
||||
)
|
||||
}
|
||||
output { outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails requirement` "the amounts balance"
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
// Can't have superfluous input states from different issuers.
|
||||
transaction {
|
||||
input { inState }
|
||||
input { inState `issued by` MINI_CORP }
|
||||
output { outState }
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this `fails requirement` "at issuer MiniCorp the amounts balance"
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this `fails with` "at issuer MiniCorp the amounts balance"
|
||||
}
|
||||
// Can't combine two different deposits at the same issuer.
|
||||
transaction {
|
||||
input { inState }
|
||||
input { inState.editDepositRef(3) }
|
||||
output { outState.copy(amount = inState.amount * 2).editDepositRef(3) }
|
||||
this `fails requirement` "for deposit [01]"
|
||||
this `fails with` "for deposit [01]"
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,18 +287,18 @@ class CashTests {
|
||||
output { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
|
||||
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer) }
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this `fails requirement` "the amounts balance"
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer) }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails requirement` "required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command"
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails with` "required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command"
|
||||
|
||||
tweak {
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this.accepts()
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -310,15 +310,15 @@ class CashTests {
|
||||
output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP }
|
||||
output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
|
||||
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
this `fails with` "at issuer MegaCorp the amounts balance"
|
||||
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails requirement` "at issuer MiniCorp the amounts balance"
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails with` "at issuer MiniCorp the amounts balance"
|
||||
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) }
|
||||
this.accepts()
|
||||
command(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,20 +332,20 @@ class CashTests {
|
||||
// Can't merge them together.
|
||||
tweak {
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
this `fails with` "at issuer MegaCorp the amounts balance"
|
||||
}
|
||||
// Missing MiniCorp deposit
|
||||
tweak {
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
this `fails with` "at issuer MegaCorp the amounts balance"
|
||||
}
|
||||
|
||||
// This works.
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) `issued by` MINI_CORP }
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this.accepts()
|
||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,9 +358,9 @@ class CashTests {
|
||||
input { pounds }
|
||||
output { inState `owned by` DUMMY_PUBKEY_2 }
|
||||
output { pounds `owned by` DUMMY_PUBKEY_1 }
|
||||
arg(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Cash.Commands.Move() }
|
||||
command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Cash.Commands.Move() }
|
||||
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
package com.r3corda.contracts
|
||||
package com.r3corda.contracts.asset
|
||||
|
||||
import com.r3corda.contracts.cash.Cash
|
||||
import com.r3corda.contracts.Obligation.Lifecycle
|
||||
import com.r3corda.contracts.asset.Obligation.Lifecycle
|
||||
import com.r3corda.contracts.testing.*
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.seconds
|
||||
import com.r3corda.core.testing.*
|
||||
import com.r3corda.core.testing.JavaTestHelpers
|
||||
import com.r3corda.core.utilities.nonEmptySetOf
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
@ -18,60 +18,64 @@ class ObligationTests {
|
||||
val defaultIssuer = MEGA_CORP.ref(1)
|
||||
val defaultUsd = USD `issued by` defaultIssuer
|
||||
val oneMillionDollars = 1000000.DOLLARS `issued by` defaultIssuer
|
||||
val trustedCashContract = nonEmptySetOf(SecureHash.randomSHA256() as SecureHash)
|
||||
val megaIssuedDollars = nonEmptySetOf(Issued<Currency>(defaultIssuer, USD))
|
||||
val megaIssuedPounds = nonEmptySetOf(Issued<Currency>(defaultIssuer, GBP))
|
||||
val trustedCashContract = nonEmptySetOf(SecureHash.Companion.randomSHA256() as SecureHash)
|
||||
val megaIssuedDollars = nonEmptySetOf(Issued(defaultIssuer, USD))
|
||||
val megaIssuedPounds = nonEmptySetOf(Issued(defaultIssuer, GBP))
|
||||
val fivePm = Instant.parse("2016-01-01T17:00:00.00Z")
|
||||
val sixPm = Instant.parse("2016-01-01T18:00:00.00Z")
|
||||
val notary = MEGA_CORP
|
||||
val megaCorpDollarSettlement = Obligation.StateTemplate(trustedCashContract, megaIssuedDollars, fivePm)
|
||||
val megaCorpPoundSettlement = megaCorpDollarSettlement.copy(acceptableIssuanceDefinitions = megaIssuedPounds)
|
||||
val megaCorpPoundSettlement = megaCorpDollarSettlement.copy(acceptableIssuedProducts = megaIssuedPounds)
|
||||
val inState = Obligation.State(
|
||||
lifecycle = Lifecycle.NORMAL,
|
||||
issuer = MEGA_CORP,
|
||||
obligor = MEGA_CORP,
|
||||
template = megaCorpDollarSettlement,
|
||||
quantity = 1000.DOLLARS.quantity,
|
||||
owner = DUMMY_PUBKEY_1
|
||||
beneficiary = DUMMY_PUBKEY_1
|
||||
)
|
||||
val outState = inState.copy(owner = DUMMY_PUBKEY_2)
|
||||
val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2)
|
||||
|
||||
private fun obligationTestRoots(group: TransactionGroupDSL<Obligation.State<Currency>>) = group.Roots()
|
||||
.transaction(oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `with notary` DUMMY_NOTARY label "Alice's $1,000,000 obligation to Bob")
|
||||
.transaction(oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY) `with notary` DUMMY_NOTARY label "Bob's $1,000,000 obligation to Alice")
|
||||
.transaction(oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, BOB_PUBKEY) `with notary` DUMMY_NOTARY label "MegaCorp's $1,000,000 obligation to Bob")
|
||||
.transaction(1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY label "Alice's $1,000,000")
|
||||
private fun obligationTestRoots(
|
||||
group: LedgerDSL<EnforceVerifyOrFail, 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)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun trivial() {
|
||||
transaction {
|
||||
input { inState }
|
||||
this `fails requirement` "the amounts balance"
|
||||
this `fails with` "the amounts balance"
|
||||
|
||||
tweak {
|
||||
output { outState.copy(quantity = 2000.DOLLARS.quantity) }
|
||||
this `fails requirement` "the amounts balance"
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
tweak {
|
||||
output { outState }
|
||||
// No command arguments
|
||||
this `fails requirement` "required com.r3corda.contracts.Obligation.Commands.Move command"
|
||||
// No command commanduments
|
||||
this `fails with` "required com.r3corda.contracts.asset.Obligation.Commands.Move command"
|
||||
}
|
||||
tweak {
|
||||
output { outState }
|
||||
arg(DUMMY_PUBKEY_2) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails requirement` "the owning keys are the same as the signing keys"
|
||||
command(DUMMY_PUBKEY_2) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails with` "the owning keys are the same as the signing keys"
|
||||
}
|
||||
tweak {
|
||||
output { outState }
|
||||
output { outState `issued by` MINI_CORP }
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails requirement` "at least one obligation input"
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails with` "at least one obligation input"
|
||||
}
|
||||
// Simple reallocation works.
|
||||
tweak {
|
||||
output { outState }
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this.accepts()
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,46 +84,49 @@ class ObligationTests {
|
||||
fun `issue debt`() {
|
||||
// Check we can't "move" debt into existence.
|
||||
transaction {
|
||||
input { DummyContract.State() }
|
||||
input { DummyState() }
|
||||
output { outState }
|
||||
arg(MINI_CORP_PUBKEY) { Obligation.Commands.Move(outState.issuanceDef) }
|
||||
command(MINI_CORP_PUBKEY) { Obligation.Commands.Move(outState.issuanceDef) }
|
||||
|
||||
this `fails requirement` "there is at least one obligation input"
|
||||
this `fails with` "there is at least one obligation input"
|
||||
}
|
||||
|
||||
// Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised
|
||||
// institution is allowed to issue as much cash as they want.
|
||||
transaction {
|
||||
output { outState }
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Issue(outState.issuanceDef) }
|
||||
this `fails requirement` "output deposits are owned by a command signer"
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Issue(outState.issuanceDef) }
|
||||
this `fails with` "output deposits are owned by a command signer"
|
||||
}
|
||||
transaction {
|
||||
output {
|
||||
Obligation.State(
|
||||
issuer = MINI_CORP,
|
||||
obligor = MINI_CORP,
|
||||
quantity = 1000.DOLLARS.quantity,
|
||||
owner = DUMMY_PUBKEY_1,
|
||||
beneficiary = DUMMY_PUBKEY_1,
|
||||
template = megaCorpDollarSettlement
|
||||
)
|
||||
}
|
||||
tweak {
|
||||
arg(MINI_CORP_PUBKEY) { Obligation.Commands.Issue(Obligation.IssuanceDefinition(MINI_CORP, megaCorpDollarSettlement), 0) }
|
||||
this `fails requirement` "has a nonce"
|
||||
command(MINI_CORP_PUBKEY) { Obligation.Commands.Issue(Obligation.IssuanceDefinition(MINI_CORP, megaCorpDollarSettlement), 0) }
|
||||
this `fails with` "has a nonce"
|
||||
}
|
||||
arg(MINI_CORP_PUBKEY) { Obligation.Commands.Issue(Obligation.IssuanceDefinition(MINI_CORP, megaCorpDollarSettlement)) }
|
||||
this.accepts()
|
||||
command(MINI_CORP_PUBKEY) { Obligation.Commands.Issue(Obligation.IssuanceDefinition(MINI_CORP, megaCorpDollarSettlement)) }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// Test generation works.
|
||||
val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
Obligation<Currency>().generateIssue(ptx, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
|
||||
beneficiary = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
|
||||
assertTrue(ptx.inputStates().isEmpty())
|
||||
val s = ptx.outputStates()[0].data as Obligation.State<Currency>
|
||||
assertEquals(100.DOLLARS `issued by` MEGA_CORP.ref(1), s.amount)
|
||||
assertEquals(MINI_CORP, s.issuer)
|
||||
assertEquals(DUMMY_PUBKEY_1, s.owner)
|
||||
val expected = Obligation.State(
|
||||
obligor = MINI_CORP,
|
||||
quantity = 100.DOLLARS.quantity,
|
||||
beneficiary = DUMMY_PUBKEY_1,
|
||||
template = megaCorpDollarSettlement
|
||||
)
|
||||
assertEquals(ptx.outputStates()[0].data, expected)
|
||||
assertTrue(ptx.commands()[0].value is Obligation.Commands.Issue<*>)
|
||||
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0])
|
||||
|
||||
@ -130,14 +137,14 @@ class ObligationTests {
|
||||
|
||||
// Move fails: not allowed to summon money.
|
||||
tweak {
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails with` "at obligor MegaCorp the amounts balance"
|
||||
}
|
||||
|
||||
// Issue works.
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
this.accepts()
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,40 +152,40 @@ class ObligationTests {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { inState.copy(quantity = inState.amount.quantity / 2) }
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
this `fails requirement` "output values sum to more than the inputs"
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
this `fails with` "output values sum to more than the inputs"
|
||||
}
|
||||
|
||||
// Can't have an issue command that doesn't actually issue money.
|
||||
transaction {
|
||||
input { inState }
|
||||
output { inState }
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
this `fails requirement` "output values sum to more than the inputs"
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
this `fails with` "output values sum to more than the inputs"
|
||||
}
|
||||
|
||||
// Can't have any other commands if we have an issue command (because the issue command overrules them)
|
||||
transaction {
|
||||
input { inState }
|
||||
output { inState.copy(quantity = inState.amount.quantity * 2) }
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
this `fails requirement` "only move/exit commands can be present along with other obligation commands"
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) }
|
||||
this `fails with` "only move/exit commands can be present along with other obligation commands"
|
||||
}
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails requirement` "only move/exit commands can be present along with other obligation commands"
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails with` "only move/exit commands can be present along with other obligation commands"
|
||||
}
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.SetLifecycle(inState.issuanceDef, Lifecycle.DEFAULTED) }
|
||||
this `fails requirement` "only move/exit commands can be present along with other obligation commands"
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.SetLifecycle(inState.issuanceDef, Lifecycle.DEFAULTED) }
|
||||
this `fails with` "only move/exit commands can be present along with other obligation commands"
|
||||
}
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit<Currency>(inState.issuanceDef, inState.amount / 2) }
|
||||
this `fails requirement` "only move/exit commands can be present along with other obligation commands"
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, inState.amount / 2) }
|
||||
this `fails with` "only move/exit commands can be present along with other obligation commands"
|
||||
}
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,18 +196,46 @@ class ObligationTests {
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun `reject issuance with inputs`() {
|
||||
// Issue some obligation
|
||||
var ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
|
||||
Obligation<Currency>().generateIssue(ptx, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
ptx.signWith(MINI_CORP_KEY)
|
||||
val tx = ptx.toSignedTransaction()
|
||||
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)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction()
|
||||
|
||||
// Include the previously issued obligation in a new issuance command
|
||||
ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
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,
|
||||
owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
beneficiary = MINI_CORP_PUBKEY, 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 tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, obligationAliceToBob, obligationBobToAlice)
|
||||
signWith(ALICE_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
assertEquals(0, tx.outputs.size)
|
||||
}
|
||||
|
||||
/** 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 tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, 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)
|
||||
}
|
||||
|
||||
/** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */
|
||||
@ -208,9 +243,13 @@ class ObligationTests {
|
||||
fun `generate payment net transaction`() {
|
||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)
|
||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)
|
||||
val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
Obligation<Currency>().generatePaymentNetting(ptx, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
||||
assertEquals(0, ptx.outputStates().size)
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generatePaymentNetting(this, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
||||
signWith(ALICE_KEY)
|
||||
signWith(BOB_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
assertEquals(0, tx.outputs.size)
|
||||
}
|
||||
|
||||
/** Test generating a transaction to two obligations, where one is bigger than the other and therefore there is a remainder. */
|
||||
@ -218,238 +257,269 @@ class ObligationTests {
|
||||
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 ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
Obligation<Currency>().generatePaymentNetting(ptx, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
||||
assertEquals(1, ptx.outputStates().size)
|
||||
val out = ptx.outputStates().single().data as Obligation.State<Currency>
|
||||
assertEquals(1000000.DOLLARS.quantity, out.quantity)
|
||||
assertEquals(BOB, out.issuer)
|
||||
assertEquals(ALICE_PUBKEY, out.owner)
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generatePaymentNetting(this, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
||||
signWith(ALICE_KEY)
|
||||
signWith(BOB_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
assertEquals(1, tx.outputs.size)
|
||||
val expected = obligationBobToAlice.copy(quantity = obligationBobToAlice.quantity - obligationAliceToBob.quantity)
|
||||
val actual = tx.outputs[0].data
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
/** Test generating a transaction to mark outputs as having defaulted. */
|
||||
@Test
|
||||
fun `generate set lifecycle`() {
|
||||
// Issue some obligation
|
||||
val dueBefore = Instant.parse("2010-01-01T17:00:00Z")
|
||||
// We don't actually verify the states, this is just here to make things look sensible
|
||||
val dueBefore = TEST_TX_TIME - Duration.ofDays(7)
|
||||
|
||||
// Generate a transaction issuing the obligation
|
||||
var tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement.copy(dueBefore = dueBefore), 100.DOLLARS.quantity,
|
||||
owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction()
|
||||
var stateAndRef = tx.tx.outRef<Obligation.State<Currency>>(0)
|
||||
|
||||
// Now generate a transaction marking the obligation as having defaulted
|
||||
tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateSetLifecycle(this, listOf(stateAndRef), Obligation.Lifecycle.DEFAULTED, DUMMY_NOTARY)
|
||||
Obligation<Currency>().generateSetLifecycle(this, listOf(stateAndRef), Lifecycle.DEFAULTED, DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction(false)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
assertEquals(1, tx.tx.outputs.size)
|
||||
assertEquals(stateAndRef.state.data.copy(lifecycle = Obligation.Lifecycle.DEFAULTED), tx.tx.outputs[0].data)
|
||||
assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.DEFAULTED), tx.tx.outputs[0].data)
|
||||
assertTrue(tx.verify().isEmpty())
|
||||
|
||||
// And set it back
|
||||
stateAndRef = tx.tx.outRef<Obligation.State<Currency>>(0)
|
||||
tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateSetLifecycle(this, listOf(stateAndRef), Obligation.Lifecycle.NORMAL, DUMMY_NOTARY)
|
||||
Obligation<Currency>().generateSetLifecycle(this, listOf(stateAndRef), Lifecycle.NORMAL, DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction(false)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
assertEquals(1, tx.tx.outputs.size)
|
||||
assertEquals(stateAndRef.state.data.copy(lifecycle = Obligation.Lifecycle.NORMAL), tx.tx.outputs[0].data)
|
||||
assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.NORMAL), tx.tx.outputs[0].data)
|
||||
assertTrue(tx.verify().isEmpty())
|
||||
}
|
||||
|
||||
/** Test generating a transaction to settle an obligation. */
|
||||
@Test
|
||||
fun `generate settlement transaction`() {
|
||||
var ptx: TransactionBuilder
|
||||
|
||||
// Generate a transaction to issue the cash we'll need
|
||||
ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
Cash().generateIssue(ptx, 100.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
|
||||
ptx.signWith(MEGA_CORP_KEY)
|
||||
val cashTx = ptx.toSignedTransaction().tx
|
||||
val cashTx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Cash().generateIssue(this, 100.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY, DUMMY_NOTARY)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
|
||||
// Generate a transaction issuing the obligation
|
||||
ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
Obligation<Currency>().generateIssue(ptx, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
ptx.signWith(MINI_CORP_KEY)
|
||||
val obligationTx = ptx.toSignedTransaction().tx
|
||||
val obligationTx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
|
||||
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
|
||||
// Now generate a transaction settling the obligation
|
||||
ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
val stateAndRef = obligationTx.outRef<Obligation.State<Currency>>(0)
|
||||
Obligation<Currency>().generateSettle(ptx, listOf(obligationTx.outRef(0)), listOf(cashTx.outRef(0)), DUMMY_NOTARY)
|
||||
assertEquals(2, ptx.inputStates().size)
|
||||
assertEquals(1, ptx.outputStates().size)
|
||||
val settleTx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
Obligation<Currency>().generateSettle(this, listOf(obligationTx.outRef(0)), listOf(cashTx.outRef(0)), Cash.Commands.Move(), DUMMY_NOTARY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
}.toSignedTransaction().tx
|
||||
assertEquals(2, settleTx.inputs.size)
|
||||
assertEquals(1, settleTx.outputs.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `close-out netting`() {
|
||||
// Try netting out two obligations
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
// Note we can sign with either key here
|
||||
arg(ALICE_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
command(ALICE_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}.verify()
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// Try netting out two obligations, with the third uninvolved obligation left
|
||||
// as-is
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
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) }
|
||||
arg(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
command(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}.verify()
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// Try having outputs mis-match the inputs
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
output("change") { (oneMillionDollars / 2).OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) }
|
||||
arg(BOB_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "amounts owed on input and output must match"
|
||||
}
|
||||
}.expectFailureOfTx(1, "amounts owed on input and output must match")
|
||||
}
|
||||
|
||||
// Have the wrong signature on the transaction
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "any involved party has signed"
|
||||
}
|
||||
}.expectFailureOfTx(1, "any involved party has signed")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `payment netting`() {
|
||||
// Try netting out two obligations
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
arg(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}.verify()
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// Try netting out two obligations, but only provide one signature. Unlike close-out netting, we need both
|
||||
// signatures for payment netting
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
arg(BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "all involved parties have signed"
|
||||
}
|
||||
}.expectFailureOfTx(1, "all involved parties have signed")
|
||||
}
|
||||
|
||||
// Multilateral netting, A -> B -> C which can net down to A -> C
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(this)
|
||||
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) }
|
||||
arg(ALICE_PUBKEY, BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
command(ALICE_PUBKEY, BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}.verify()
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
// Multilateral netting without the key of the receiving party
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(this)
|
||||
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) }
|
||||
arg(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "all involved parties have signed"
|
||||
}
|
||||
}.expectFailureOfTx(1, "all involved parties have signed")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `settlement`() {
|
||||
// Try netting out two obligations
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(this)
|
||||
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 }
|
||||
arg(ALICE_PUBKEY) { Obligation.Commands.Settle<Currency>(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), oneMillionDollars) }
|
||||
arg(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
||||
command(ALICE_PUBKEY) { Obligation.Commands.Settle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Amount(oneMillionDollars.quantity, USD)) }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
||||
this.verifies()
|
||||
}
|
||||
}.verify()
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `payment default`() {
|
||||
// Try defaulting an obligation without a timestamp
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
ledger {
|
||||
obligationTestRoots(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 = Obligation.Lifecycle.DEFAULTED) }
|
||||
arg(BOB_PUBKEY) { Obligation.Commands.SetLifecycle<Currency>(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Obligation.Lifecycle.DEFAULTED) }
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Lifecycle.DEFAULTED) }
|
||||
this `fails with` "there is a timestamp from the authority"
|
||||
}
|
||||
}.expectFailureOfTx(1, "there is a timestamp from the authority")
|
||||
}
|
||||
|
||||
// Try defaulting an obligation
|
||||
transactionGroupFor<Obligation.State<Currency>>() {
|
||||
obligationTestRoots(this)
|
||||
// Try defaulting an obligation due in the future
|
||||
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) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` futureTestTime, Lifecycle.DEFAULTED) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this `fails with` "the due date has passed"
|
||||
}
|
||||
|
||||
// Try defaulting an obligation that is now in the past
|
||||
ledger {
|
||||
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 = Obligation.Lifecycle.DEFAULTED) }
|
||||
arg(BOB_PUBKEY) { Obligation.Commands.SetLifecycle<Currency>(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Obligation.Lifecycle.DEFAULTED) }
|
||||
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) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` pastTestTime, Lifecycle.DEFAULTED) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}.verify()
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMergeSplit() {
|
||||
// Splitting value works.
|
||||
transaction {
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
tweak {
|
||||
input { inState }
|
||||
for (i in 1..4) output { inState.copy(quantity = inState.quantity / 4) }
|
||||
this.accepts()
|
||||
repeat(4) { output { inState.copy(quantity = inState.quantity / 4) } }
|
||||
this.verifies()
|
||||
}
|
||||
// Merging 4 inputs into 2 outputs works.
|
||||
tweak {
|
||||
for (i in 1..4) input { inState.copy(quantity = inState.quantity / 4) }
|
||||
repeat(4) { input { inState.copy(quantity = inState.quantity / 4) } }
|
||||
output { inState.copy(quantity = inState.quantity / 2) }
|
||||
output { inState.copy(quantity = inState.quantity / 2) }
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
// Merging 2 inputs into 1 works.
|
||||
tweak {
|
||||
input { inState.copy(quantity = inState.quantity / 2) }
|
||||
input { inState.copy(quantity = inState.quantity / 2) }
|
||||
output { inState }
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -459,29 +529,30 @@ class ObligationTests {
|
||||
transaction {
|
||||
input { inState }
|
||||
input { inState.copy(quantity = 0L) }
|
||||
this `fails requirement` "zero sized inputs"
|
||||
this `fails with` "zero sized inputs"
|
||||
}
|
||||
transaction {
|
||||
input { inState }
|
||||
output { inState }
|
||||
output { inState.copy(quantity = 0L) }
|
||||
this `fails requirement` "zero sized outputs"
|
||||
this `fails with` "zero sized outputs"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun trivialMismatches() {
|
||||
// Can't change issuer.
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState `issued by` MINI_CORP }
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
this `fails with` "at obligor MegaCorp the amounts balance"
|
||||
}
|
||||
// Can't mix currencies.
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState.copy(quantity = 80000, template = megaCorpDollarSettlement) }
|
||||
output { outState.copy(quantity = 20000, template = megaCorpPoundSettlement) }
|
||||
this `fails requirement` "the amounts balance"
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
transaction {
|
||||
input { inState }
|
||||
@ -489,20 +560,20 @@ class ObligationTests {
|
||||
inState.copy(
|
||||
quantity = 15000,
|
||||
template = megaCorpPoundSettlement,
|
||||
owner = DUMMY_PUBKEY_2
|
||||
beneficiary = DUMMY_PUBKEY_2
|
||||
)
|
||||
}
|
||||
output { outState.copy(quantity = 115000) }
|
||||
this `fails requirement` "the amounts balance"
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
// Can't have superfluous input states from different issuers.
|
||||
transaction {
|
||||
input { inState }
|
||||
input { inState `issued by` MINI_CORP }
|
||||
output { outState }
|
||||
arg(DUMMY_PUBKEY_1) {Obligation.Commands.Move(inState.issuanceDef) }
|
||||
arg(DUMMY_PUBKEY_1) {Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) }
|
||||
this `fails requirement` "at issuer MiniCorp the amounts balance"
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) }
|
||||
this `fails with` "at obligor MiniCorp the amounts balance"
|
||||
}
|
||||
}
|
||||
|
||||
@ -514,18 +585,18 @@ class ObligationTests {
|
||||
output { outState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) }
|
||||
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit<Currency>(inState.issuanceDef, 100.DOLLARS `issued by` defaultIssuer) }
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails requirement` "the amounts balance"
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 100.DOLLARS) }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit<Currency>(inState.issuanceDef, 200.DOLLARS `issued by` defaultIssuer) }
|
||||
this `fails requirement` "required com.r3corda.contracts.Obligation.Commands.Move command"
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 200.DOLLARS) }
|
||||
this `fails with` "required com.r3corda.contracts.asset.Obligation.Commands.Move command"
|
||||
|
||||
tweak {
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this.accepts()
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -537,19 +608,19 @@ class ObligationTests {
|
||||
output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) `issued by` MINI_CORP }
|
||||
output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) }
|
||||
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
this `fails with` "at obligor MegaCorp the amounts balance"
|
||||
|
||||
arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit<Currency>(inState.issuanceDef, 200.DOLLARS `issued by` defaultIssuer) }
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 200.DOLLARS) }
|
||||
tweak {
|
||||
arg(MINI_CORP_PUBKEY) { Obligation.Commands.Exit<Currency>((inState `issued by` MINI_CORP).issuanceDef, 0.DOLLARS `issued by` defaultIssuer) }
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) }
|
||||
this `fails requirement` "at issuer MiniCorp the amounts balance"
|
||||
command(MINI_CORP_PUBKEY) { Obligation.Commands.Exit((inState `issued by` MINI_CORP).issuanceDef, 0.DOLLARS) }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) }
|
||||
this `fails with` "at obligor MiniCorp the amounts balance"
|
||||
}
|
||||
arg(MINI_CORP_PUBKEY) { Obligation.Commands.Exit<Currency>((inState `issued by` MINI_CORP).issuanceDef, 200.DOLLARS `issued by` defaultIssuer) }
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) }
|
||||
this.accepts()
|
||||
command(MINI_CORP_PUBKEY) { Obligation.Commands.Exit((inState `issued by` MINI_CORP).issuanceDef, 200.DOLLARS) }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@ -562,22 +633,22 @@ class ObligationTests {
|
||||
|
||||
// Can't merge them together.
|
||||
tweak {
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2, quantity = 200000L) }
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
output { inState.copy(beneficiary = DUMMY_PUBKEY_2, quantity = 200000L) }
|
||||
this `fails with` "at obligor MegaCorp the amounts balance"
|
||||
}
|
||||
// Missing MiniCorp deposit
|
||||
tweak {
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
output { inState.copy(beneficiary = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(beneficiary = DUMMY_PUBKEY_2) }
|
||||
this `fails with` "at obligor MegaCorp the amounts balance"
|
||||
}
|
||||
|
||||
// This works.
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||
output { inState.copy(owner = DUMMY_PUBKEY_2) `issued by` MINI_CORP }
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) }
|
||||
this.accepts()
|
||||
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(inState.issuanceDef) }
|
||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@ -590,10 +661,10 @@ class ObligationTests {
|
||||
input { pounds }
|
||||
output { inState `owned by` DUMMY_PUBKEY_2 }
|
||||
output { pounds `owned by` DUMMY_PUBKEY_1 }
|
||||
arg(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
arg(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Obligation.Commands.Move(pounds.issuanceDef) }
|
||||
command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Obligation.Commands.Move(inState.issuanceDef) }
|
||||
command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Obligation.Commands.Move(pounds.issuanceDef) }
|
||||
|
||||
this.accepts()
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@ -626,12 +697,12 @@ class ObligationTests {
|
||||
|
||||
// States must not be nettable if the cash contract differs
|
||||
assertNotEquals(fiveKDollarsFromMegaToMega.bilateralNetState,
|
||||
fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableContracts = nonEmptySetOf(SecureHash.randomSHA256()))).bilateralNetState)
|
||||
fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableContracts = nonEmptySetOf(SecureHash.Companion.randomSHA256()))).bilateralNetState)
|
||||
|
||||
// States must not be nettable if the trusted issuers differ
|
||||
val miniCorpIssuer = nonEmptySetOf(Issued<Currency>(MINI_CORP.ref(1), USD))
|
||||
val miniCorpIssuer = nonEmptySetOf(Issued(MINI_CORP.ref(1), USD))
|
||||
assertNotEquals(fiveKDollarsFromMegaToMega.bilateralNetState,
|
||||
fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableIssuanceDefinitions = miniCorpIssuer)).bilateralNetState)
|
||||
fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableIssuedProducts = miniCorpIssuer)).bilateralNetState)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
@ -686,7 +757,7 @@ class ObligationTests {
|
||||
val fiveKDollarsFromMegaToMini = Obligation.State(Lifecycle.NORMAL, MEGA_CORP, megaCorpDollarSettlement,
|
||||
5000.DOLLARS.quantity, MINI_CORP_PUBKEY)
|
||||
val expected = mapOf(Pair(Pair(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY), fiveKDollarsFromMegaToMini.amount))
|
||||
val actual = extractAmountsDue<Currency>(defaultUsd, listOf(fiveKDollarsFromMegaToMini))
|
||||
val actual = extractAmountsDue(USD, listOf(fiveKDollarsFromMegaToMini))
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@ -697,8 +768,8 @@ class ObligationTests {
|
||||
Pair(Pair(ALICE_PUBKEY, BOB_PUBKEY), Amount(100000000, GBP)),
|
||||
Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(100000000, GBP))
|
||||
)
|
||||
val expected: Map<PublicKey, Long> = emptyMap() // Zero balances are stripped before returning
|
||||
val actual = sumAmountsDue(balanced)
|
||||
val expected: Map<Pair<PublicKey, PublicKey>, Amount<Currency>> = emptyMap() // Zero balances are stripped before returning
|
||||
val actual = netAmountsDue(balanced)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@ -706,13 +777,13 @@ class ObligationTests {
|
||||
fun `netting difference balances due between parties`() {
|
||||
// Now try it with two balances, which cancel each other out
|
||||
val balanced = mapOf(
|
||||
Pair(Pair(ALICE_PUBKEY, BOB_PUBKEY), Amount(100000000, GBP) `issued by` defaultIssuer),
|
||||
Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(200000000, GBP) `issued by` defaultIssuer)
|
||||
Pair(Pair(ALICE_PUBKEY, BOB_PUBKEY), Amount(100000000, GBP)),
|
||||
Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(200000000, GBP))
|
||||
)
|
||||
val expected = mapOf(
|
||||
Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(100000000, GBP) `issued by` defaultIssuer)
|
||||
Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(100000000, GBP))
|
||||
)
|
||||
var actual = netAmountsDue<Currency>(balanced)
|
||||
val actual = netAmountsDue(balanced)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
group 'com.r3cev.prototyping'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: QuasarPlugin
|
||||
// Applying the maven plugin means this will get installed locally when running "gradle install"
|
||||
apply plugin: 'maven'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
@ -24,8 +23,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.assertj:assertj-core:3.4.1'
|
||||
testCompile "commons-fileupload:commons-fileupload:1.3.1"
|
||||
testCompile "commons-fileupload:commons-fileupload:1.3.2"
|
||||
|
||||
// Guava: Google test library (collections test suite)
|
||||
testCompile "com.google.guava:guava-testlib:19.0"
|
||||
@ -38,27 +36,27 @@ dependencies {
|
||||
compile "com.google.code.findbugs:jsr305:3.0.1"
|
||||
|
||||
// AssertJ: for fluent assertions for testing
|
||||
testCompile "org.assertj:assertj-core:3.4.1"
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
|
||||
// SLF4J: Logging framework.
|
||||
compile "org.slf4j:slf4j-jdk14:1.7.13"
|
||||
compile "org.slf4j:slf4j-jdk14:${slf4j_version}"
|
||||
|
||||
// Guava: Google utilities library.
|
||||
compile "com.google.guava:guava:19.0"
|
||||
|
||||
// RxJava: observable streams of events.
|
||||
compile "io.reactivex:rxjava:1.0.17"
|
||||
compile "io.reactivex:rxjava:1.1.6"
|
||||
|
||||
// Kryo: object graph serialization.
|
||||
compile "com.esotericsoftware:kryo:3.0.3"
|
||||
compile "de.javakaffee:kryo-serializers:0.37"
|
||||
compile "com.esotericsoftware:kryo:4.0.0"
|
||||
compile "de.javakaffee:kryo-serializers:0.38"
|
||||
|
||||
// Apache JEXL: An embeddable expression evaluation library.
|
||||
// This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API.
|
||||
compile "org.apache.commons:commons-jexl3:3.0"
|
||||
|
||||
// For JSON
|
||||
compile "com.fasterxml.jackson.core:jackson-databind:2.5.5"
|
||||
compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}"
|
||||
|
||||
// Java ed25519 implementation. See https://github.com/str4d/ed25519-java/
|
||||
compile 'net.i2p.crypto:eddsa:0.1.0'
|
||||
|
@ -11,11 +11,9 @@ import java.io.InputStream
|
||||
import java.math.BigDecimal
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.SecureRandom
|
||||
import java.time.Duration
|
||||
import java.time.temporal.Temporal
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.locks.Lock
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.concurrent.withLock
|
||||
@ -144,9 +142,13 @@ inline fun <T> logElapsedTime(label: String, logger: Logger? = null, body: () ->
|
||||
*
|
||||
* val ii = state.locked { i }
|
||||
*/
|
||||
class ThreadBox<T>(content: T, val lock: Lock = ReentrantLock()) {
|
||||
class ThreadBox<T>(content: T, val lock: ReentrantLock = ReentrantLock()) {
|
||||
val content = content
|
||||
inline fun <R> locked(body: T.() -> R): R = lock.withLock { body(content) }
|
||||
inline fun <R> alreadyLocked(body: T.() -> R): R {
|
||||
check(lock.isHeldByCurrentThread, { "Expected $lock to already be locked." })
|
||||
return body(content)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,16 +179,17 @@ class TransientProperty<T>(private val initializer: () -> T) {
|
||||
* Given a path to a zip file, extracts it to the given directory.
|
||||
*/
|
||||
fun extractZipFile(zipPath: Path, toPath: Path) {
|
||||
if (!Files.exists(toPath))
|
||||
Files.createDirectories(toPath)
|
||||
val normalisedToPath = toPath.normalize()
|
||||
if (!Files.exists(normalisedToPath))
|
||||
Files.createDirectories(normalisedToPath)
|
||||
|
||||
ZipInputStream(BufferedInputStream(Files.newInputStream(zipPath))).use { zip ->
|
||||
while (true) {
|
||||
val e = zip.nextEntry ?: break
|
||||
val outPath = toPath.resolve(e.name)
|
||||
val outPath = normalisedToPath.resolve(e.name)
|
||||
|
||||
// Security checks: we should reject a zip that contains tricksy paths that try to escape toPath.
|
||||
if (!outPath.normalize().startsWith(toPath))
|
||||
if (!outPath.normalize().startsWith(normalisedToPath))
|
||||
throw IllegalStateException("ZIP contained a path that resolved incorrectly: ${e.name}")
|
||||
|
||||
if (e.isDirectory) {
|
||||
|
@ -60,14 +60,22 @@ inline fun <R> requireThat(body: Requirements.() -> R) = R.body()
|
||||
//// Authenticated commands ///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** Filters the command list by type, party and public key all at once. */
|
||||
inline fun <reified T : CommandData> List<AuthenticatedObject<CommandData>>.select(signer: PublicKey? = null,
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signer: PublicKey? = null,
|
||||
party: Party? = null) =
|
||||
filter { it.value is T }.
|
||||
filter { if (signer == null) true else it.signers.contains(signer) }.
|
||||
filter { if (party == null) true else it.signingParties.contains(party) }.
|
||||
filter { if (signer == null) true else signer in it.signers }.
|
||||
filter { if (party == null) true else party in it.signingParties }.
|
||||
map { AuthenticatedObject<T>(it.signers, it.signingParties, it.value as T) }
|
||||
|
||||
inline fun <reified T : CommandData> List<AuthenticatedObject<CommandData>>.requireSingleCommand() = try {
|
||||
/** Filters the command list by type, parties and public keys all at once. */
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signers: Collection<PublicKey>?,
|
||||
parties: Collection<Party>?) =
|
||||
filter { it.value is T }.
|
||||
filter { if (signers == null) true else it.signers.containsAll(signers)}.
|
||||
filter { if (parties == null) true else it.signingParties.containsAll(parties) }.
|
||||
map { AuthenticatedObject<T>(it.signers, it.signingParties, it.value as T) }
|
||||
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingleCommand() = try {
|
||||
select<T>().single()
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw IllegalStateException("Required ${T::class.qualifiedName} command") // Better error message.
|
||||
@ -106,9 +114,10 @@ fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: Stri
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
// TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here?
|
||||
inline fun <reified T : CommandData> verifyMoveCommand(inputs: List<OwnableState>, tx: TransactionForContract) {
|
||||
return verifyMoveCommand<T>(inputs, tx.commands)
|
||||
}
|
||||
inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState>,
|
||||
tx: TransactionForContract)
|
||||
: MoveCommand
|
||||
= verifyMoveCommand<T>(inputs, tx.commands)
|
||||
|
||||
/**
|
||||
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
|
||||
@ -116,13 +125,17 @@ inline fun <reified T : CommandData> verifyMoveCommand(inputs: List<OwnableState
|
||||
* @param T the type of the move command
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
inline fun <reified T : CommandData> verifyMoveCommand(inputs: List<OwnableState>, commands: List<AuthenticatedObject<CommandData>>) {
|
||||
inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState>,
|
||||
commands: List<AuthenticatedObject<CommandData>>)
|
||||
: MoveCommand {
|
||||
// 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 keysThatSigned = commands.requireSingleCommand<T>().signers.toSet()
|
||||
val command = commands.requireSingleCommand<T>()
|
||||
val keysThatSigned = command.signers.toSet()
|
||||
requireThat {
|
||||
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
|
||||
}
|
||||
return command.value
|
||||
}
|
||||
|
@ -9,13 +9,12 @@ import java.security.PublicKey
|
||||
val DUMMY_PROGRAM_ID = DummyContract()
|
||||
|
||||
class DummyContract : Contract {
|
||||
data class State(val magicNumber: Int = 0) : ContractState {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
get() = emptyList()
|
||||
|
||||
interface State : ContractState {
|
||||
val magicNumber: Int
|
||||
}
|
||||
|
||||
data class SingleOwnerState(val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState {
|
||||
data class SingleOwnerState(override val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState, State {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
get() = listOf(owner)
|
||||
@ -23,8 +22,13 @@ class DummyContract : Contract {
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
data class MultiOwnerState(val magicNumber: Int = 0,
|
||||
val owners: List<PublicKey>) : ContractState {
|
||||
/**
|
||||
* Alternative state with multiple owners. This exists primarily to provide a dummy state with multiple
|
||||
* participants, and could in theory be merged with [SingleOwnerState] by putting the additional participants
|
||||
* 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 {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
get() = owners
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.r3corda.core.contracts
|
||||
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* Dummy state for use in testing. Not part of any real contract.
|
||||
*/
|
||||
data class DummyState(val magicNumber: Int = 0) : ContractState {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
get() = emptyList()
|
||||
}
|
@ -3,6 +3,8 @@ package com.r3corda.core.contracts
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.crypto.toStringShort
|
||||
import com.r3corda.core.protocols.ProtocolLogicRef
|
||||
import com.r3corda.core.protocols.ProtocolLogicRefFactory
|
||||
import com.r3corda.core.serialization.OpaqueBytes
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import java.io.FileNotFoundException
|
||||
@ -57,7 +59,7 @@ interface ContractState {
|
||||
* is a miniature file system in which each file can be precisely mapped to the defining attachment.
|
||||
*
|
||||
* Attachments may contain many things (data files, legal documents, etc) but mostly they contain JVM bytecode.
|
||||
* The classfiles inside define not only [Contract] implementations but also the classes that define the states.
|
||||
* The class files inside define not only [Contract] implementations but also the classes that define the states.
|
||||
* Within the rest of a transaction, user-providable components are referenced by name only.
|
||||
*
|
||||
* This means that a smart contract in Corda does two things:
|
||||
@ -141,6 +143,33 @@ interface OwnableState : ContractState {
|
||||
fun withNewOwner(newOwner: PublicKey): Pair<CommandData, OwnableState>
|
||||
}
|
||||
|
||||
/** Something which is scheduled to happen at a point in time */
|
||||
interface Scheduled {
|
||||
val scheduledAt: Instant
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a contract state (unconsumed output) of type [LinearState] and a point in time that a lifecycle event is expected to take place
|
||||
* for that contract state.
|
||||
*
|
||||
* This is effectively the input to a scheduler, which wakes up at that point in time and asks the contract state what
|
||||
* lifecycle processing needs to take place. e.g. a fixing or a late payment etc.
|
||||
*/
|
||||
data class ScheduledStateRef(val ref: StateRef, override val scheduledAt: Instant) : Scheduled
|
||||
|
||||
/**
|
||||
* This class represents the lifecycle activity that a contract state of type [LinearState] would like to perform at a given point in time.
|
||||
* e.g. run a fixing protocol
|
||||
*
|
||||
* Note the use of [ProtocolLogicRef] to represent a safe way to transport a [ProtocolLogic] out of the contract sandbox.
|
||||
*
|
||||
* Currently we support only protocol based activities as we expect there to be a transaction generated off the back of
|
||||
* the activity, otherwise we have to start tracking secondary state on the platform of which scheduled activities
|
||||
* for a particular [ContractState] have been processed/fired etc. If the activity is not "on ledger" then the
|
||||
* scheduled activity shouldn't be either.
|
||||
*/
|
||||
data class ScheduledActivity(val logicRef: ProtocolLogicRef, override val scheduledAt: Instant) : Scheduled
|
||||
|
||||
/**
|
||||
* A state that evolves by superseding itself, all of which share the common "thread"
|
||||
*
|
||||
@ -154,12 +183,24 @@ interface LinearState : ContractState {
|
||||
fun isRelevant(ourKeys: Set<PublicKey>): Boolean
|
||||
}
|
||||
|
||||
interface SchedulableState : ContractState {
|
||||
/**
|
||||
* Indicate whether there is some activity to be performed at some future point in time with respect to this
|
||||
* [ContractState], what that activity is and at what point in time it should be initiated.
|
||||
* This can be used to implement deadlines for payment or processing of financial instruments according to a schedule.
|
||||
*
|
||||
* The state has no reference to it's own StateRef, so supply that for use as input to any ProtocolLogic constructed.
|
||||
*
|
||||
* @return null if there is no activity to schedule
|
||||
*/
|
||||
fun nextScheduledActivity(thisStateRef: StateRef, protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity?
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface representing an agreement that exposes various attributes that are common. Implementing it simplifies
|
||||
* implementation of general protocols that manipulate many agreement types.
|
||||
*/
|
||||
interface DealState : LinearState {
|
||||
|
||||
/** Human readable well known reference (e.g. trade reference) */
|
||||
val ref: String
|
||||
|
||||
@ -187,8 +228,6 @@ interface DealState : LinearState {
|
||||
interface FixableDealState : DealState {
|
||||
/**
|
||||
* When is the next fixing and what is the fixing for?
|
||||
*
|
||||
* TODO: In future we would use this to register for an event to trigger a/the fixing protocol
|
||||
*/
|
||||
fun nextFixingOf(): FixOf?
|
||||
|
||||
|
@ -15,6 +15,10 @@ import java.util.*
|
||||
*
|
||||
* The builder can be customised for specific transaction types, e.g. where additional processing is needed
|
||||
* before adding a state/command.
|
||||
*
|
||||
* @param notary The default notary that will be used for outputs that don't have a notary specified. When this is set,
|
||||
* an output state can be added by just passing in a [ContractState] – a [TransactionState] with the
|
||||
* default notary will be generated automatically.
|
||||
*/
|
||||
abstract class TransactionBuilder(protected val type: TransactionType = TransactionType.General(),
|
||||
protected val notary: Party? = null) {
|
||||
@ -129,6 +133,7 @@ abstract class TransactionBuilder(protected val type: TransactionType = Transact
|
||||
|
||||
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
|
||||
|
||||
/** A default notary must be specified during builder construction to use this method */
|
||||
fun addOutputState(state: ContractState) {
|
||||
checkNotNull(notary) { "Need to specify a Notary for the state, or set a default one on TransactionBuilder initialisation" }
|
||||
addOutputState(state, notary!!)
|
||||
|
@ -0,0 +1,92 @@
|
||||
package com.r3corda.core.contracts.clauses
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import java.util.*
|
||||
|
||||
interface Clause {
|
||||
/** Classes for commands which must ALL be present in transaction for this clause to be triggered */
|
||||
val requiredCommands: Set<Class<out CommandData>>
|
||||
/** Behaviour if this clause is matched */
|
||||
val ifNotMatched: MatchBehaviour
|
||||
/** Behaviour if this clause is not matches */
|
||||
val ifMatched: MatchBehaviour
|
||||
}
|
||||
|
||||
enum class MatchBehaviour {
|
||||
CONTINUE,
|
||||
END,
|
||||
ERROR
|
||||
}
|
||||
|
||||
interface SingleVerify {
|
||||
/**
|
||||
* Verify the transaction matches the conditions from this clause. For example, a "no zero amount output" clause
|
||||
* would check each of the output states that it applies to, looking for a zero amount, and throw IllegalStateException
|
||||
* if any matched.
|
||||
*
|
||||
* @return the set of commands that are consumed IF this clause is matched, and cannot be used to match a
|
||||
* later clause. This would normally be all commands matching "requiredCommands" for this clause, but some
|
||||
* verify() functions may do further filtering on possible matches, and return a subset. This may also include
|
||||
* commands that were not required (for example the Exit command for fungible assets is optional).
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
fun verify(tx: TransactionForContract,
|
||||
commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData>
|
||||
|
||||
}
|
||||
|
||||
|
||||
interface SingleClause : Clause, SingleVerify
|
||||
|
||||
/**
|
||||
* Abstract superclass for clause-based contracts to extend, which provides a verify() function
|
||||
* that delegates to the supplied list of clauses.
|
||||
*/
|
||||
abstract class ClauseVerifier : Contract {
|
||||
abstract val clauses: List<SingleClause>
|
||||
abstract fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
|
||||
override fun verify(tx: TransactionForContract) = verifyClauses(tx, clauses, extractCommands(tx))
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a transaction against the given list of clauses.
|
||||
*
|
||||
* @param tx transaction to be verified.
|
||||
* @param clauses the clauses to verify.
|
||||
* @param T common supertype of commands to extract from the transaction, which are of relevance to these clauses.
|
||||
*/
|
||||
inline fun <reified T : CommandData> verifyClauses(tx: TransactionForContract,
|
||||
clauses: List<SingleClause>)
|
||||
= verifyClauses(tx, clauses, tx.commands.select<T>())
|
||||
|
||||
/**
|
||||
* Verify a transaction against the given list of clauses.
|
||||
*
|
||||
* @param tx transaction to be verified.
|
||||
* @param clauses the clauses to verify.
|
||||
* @param commands commands extracted from the transaction, which are relevant to the
|
||||
* clauses.
|
||||
*/
|
||||
fun verifyClauses(tx: TransactionForContract,
|
||||
clauses: List<SingleClause>,
|
||||
commands: Collection<AuthenticatedObject<CommandData>>) {
|
||||
val unmatchedCommands = ArrayList(commands.map { it.value })
|
||||
|
||||
verify@ for (clause in clauses) {
|
||||
val matchBehaviour = if (unmatchedCommands.map { command -> command.javaClass }.containsAll(clause.requiredCommands)) {
|
||||
unmatchedCommands.removeAll(clause.verify(tx, commands))
|
||||
clause.ifMatched
|
||||
} else {
|
||||
clause.ifNotMatched
|
||||
}
|
||||
|
||||
when (matchBehaviour) {
|
||||
MatchBehaviour.ERROR -> throw IllegalStateException()
|
||||
MatchBehaviour.CONTINUE -> {
|
||||
}
|
||||
MatchBehaviour.END -> break@verify
|
||||
}
|
||||
}
|
||||
|
||||
require(unmatchedCommands.isEmpty()) { "All commands must be matched at end of execution." }
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package com.r3corda.core.contracts.clauses
|
||||
|
||||
import com.r3corda.core.contracts.AuthenticatedObject
|
||||
import com.r3corda.core.contracts.CommandData
|
||||
import com.r3corda.core.contracts.ContractState
|
||||
import com.r3corda.core.contracts.TransactionForContract
|
||||
import java.util.*
|
||||
|
||||
interface GroupVerify<S, T : Any> {
|
||||
/**
|
||||
*
|
||||
* @return the set of commands that are consumed IF this clause is matched, and cannot be used to match a
|
||||
* later clause.
|
||||
*/
|
||||
fun verify(tx: TransactionForContract,
|
||||
inputs: List<S>,
|
||||
outputs: List<S>,
|
||||
commands: Collection<AuthenticatedObject<CommandData>>,
|
||||
token: T): Set<CommandData>
|
||||
}
|
||||
|
||||
interface GroupClause<S : ContractState, T : Any> : Clause, GroupVerify<S, T>
|
||||
|
||||
abstract class GroupClauseVerifier<S : ContractState, T : Any> : SingleClause {
|
||||
abstract val clauses: List<GroupClause<S, T>>
|
||||
override val requiredCommands: Set<Class<CommandData>>
|
||||
get() = emptySet()
|
||||
|
||||
abstract fun extractGroups(tx: TransactionForContract): List<TransactionForContract.InOutGroup<out S, T>>
|
||||
|
||||
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> {
|
||||
val groups = extractGroups(tx)
|
||||
val matchedCommands = HashSet<CommandData>()
|
||||
val unmatchedCommands = ArrayList(commands.map { it.value })
|
||||
|
||||
for ((inputs, outputs, token) in groups) {
|
||||
val temp = verifyGroup(commands, inputs, outputs, token, tx, unmatchedCommands)
|
||||
matchedCommands.addAll(temp)
|
||||
unmatchedCommands.removeAll(temp)
|
||||
}
|
||||
|
||||
return matchedCommands
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a subset of a transaction's inputs and outputs matches the conditions from this clause. For example, a
|
||||
* "no zero amount output" clause would check each of the output states within the group, looking for a zero amount,
|
||||
* and throw IllegalStateException if any matched.
|
||||
*
|
||||
* @param commands the full set of commands which apply to this contract.
|
||||
* @param inputs input states within this group.
|
||||
* @param outputs output states within this group.
|
||||
* @param token the object used as a key when grouping states.
|
||||
* @param unmatchedCommands commands which have not yet been matched within this group.
|
||||
* @return matchedCommands commands which are matched during the verification process.
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
private fun verifyGroup(commands: Collection<AuthenticatedObject<CommandData>>,
|
||||
inputs: List<S>,
|
||||
outputs: List<S>,
|
||||
token: T,
|
||||
tx: TransactionForContract,
|
||||
unmatchedCommands: List<CommandData>): Set<CommandData> {
|
||||
val matchedCommands = HashSet<CommandData>()
|
||||
verify@ for (clause in clauses) {
|
||||
val matchBehaviour = if (unmatchedCommands.map { command -> command.javaClass }.containsAll(clause.requiredCommands)) {
|
||||
matchedCommands.addAll(clause.verify(tx, inputs, outputs, commands, token))
|
||||
clause.ifMatched
|
||||
} else {
|
||||
clause.ifNotMatched
|
||||
}
|
||||
|
||||
when (matchBehaviour) {
|
||||
MatchBehaviour.ERROR -> throw IllegalStateException()
|
||||
MatchBehaviour.CONTINUE -> {
|
||||
}
|
||||
MatchBehaviour.END -> break@verify
|
||||
}
|
||||
}
|
||||
return matchedCommands
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.r3corda.core.contracts.clauses
|
||||
|
||||
import com.r3corda.core.contracts.AuthenticatedObject
|
||||
import com.r3corda.core.contracts.CommandData
|
||||
import com.r3corda.core.contracts.TransactionForContract
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A clause which intercepts calls to a wrapped clause, and passes them through verification
|
||||
* only from a pre-clause. This is similar to an inceptor in aspect orientated programming.
|
||||
*/
|
||||
data class InterceptorClause(
|
||||
val preclause: SingleVerify,
|
||||
val clause: SingleClause
|
||||
) : SingleClause {
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = clause.ifNotMatched
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = clause.ifMatched
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = clause.requiredCommands
|
||||
|
||||
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> {
|
||||
val consumed = HashSet(preclause.verify(tx, commands))
|
||||
consumed.addAll(clause.verify(tx, commands))
|
||||
return consumed
|
||||
}
|
||||
}
|
@ -5,9 +5,9 @@ import com.r3corda.core.serialization.OpaqueBytes
|
||||
import com.r3corda.core.serialization.SerializedBytes
|
||||
import com.r3corda.core.serialization.deserialize
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.security.interfaces.ECPublicKey
|
||||
import net.i2p.crypto.eddsa.KeyPairGenerator as EddsaKeyPairGenerator
|
||||
|
||||
fun newSecureRandom(): SecureRandom {
|
||||
@ -158,8 +158,8 @@ fun PublicKey.verifyWithECDSA(content: ByteArray, signature: DigitalSignature) {
|
||||
|
||||
/** Render a public key to a string, using a short form if it's an elliptic curve public key */
|
||||
fun PublicKey.toStringShort(): String {
|
||||
return (this as? ECPublicKey)?.let { key ->
|
||||
"DL" + Base58.encode(key.w.affineX.toByteArray()) // DL -> Distributed Ledger
|
||||
return (this as? EdDSAPublicKey)?.let { key ->
|
||||
"DL" + Base58.encode(key.abyte) // DL -> Distributed Ledger
|
||||
} ?: toString()
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
package com.r3corda.core.node
|
||||
|
||||
/**
|
||||
* Implement this interface on a class advertised in a META-INF/services/com.r3corda.core.node.CordaPluginRegistry file
|
||||
* to extend a Corda node with additional application services.
|
||||
*/
|
||||
interface CordaPluginRegistry {
|
||||
/**
|
||||
* List of JAX-RS classes inside the contract jar. They are expected to have a single parameter constructor that takes a ServiceHub as input.
|
||||
* These are listed as Class<*>, because they will be instantiated inside an AttachmentClassLoader so that subsequent protocols, contracts, etc
|
||||
* will be running in the appropriate isolated context.
|
||||
*/
|
||||
val webApis: List<Class<*>>
|
||||
|
||||
/**
|
||||
* A Map with an entry for each consumed protocol used by the webAPIs.
|
||||
* The key of each map entry should contain the ProtocolLogic<T> class name.
|
||||
* The associated map values are the union of all concrete class names passed to the protocol constructor.
|
||||
* Standard java.lang.* and kotlin.* types do not need to be included explicitly
|
||||
* This is used to extend the white listed protocols that can be initiated from the ServiceHub invokeProtocolAsync method
|
||||
*/
|
||||
val requiredProtocols: Map<String, Set<String>>
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package com.r3corda.core.node
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.messaging.MessagingService
|
||||
import com.r3corda.core.node.services.*
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import java.time.Clock
|
||||
|
||||
/**
|
||||
@ -20,6 +22,7 @@ interface ServiceHub {
|
||||
val storageService: StorageService
|
||||
val networkService: MessagingService
|
||||
val networkMapCache: NetworkMapCache
|
||||
val schedulerService: SchedulerService
|
||||
val clock: Clock
|
||||
|
||||
/**
|
||||
@ -60,4 +63,11 @@ interface ServiceHub {
|
||||
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||
return definingTx.tx.outputs[stateRef.index]
|
||||
}
|
||||
|
||||
/**
|
||||
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the protocol.
|
||||
*
|
||||
* @throws IllegalProtocolLogicException or IllegalArgumentException if there are problems with the [logicType] or [args]
|
||||
*/
|
||||
fun <T : Any> invokeProtocolAsync(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ListenableFuture<T>
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package com.r3corda.core.node.services
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
@ -113,6 +115,17 @@ interface WalletService {
|
||||
* the update.
|
||||
*/
|
||||
val updates: rx.Observable<Wallet.Update>
|
||||
|
||||
/**
|
||||
* Provide a [Future] for when a [StateRef] is consumed, which can be very useful in building tests.
|
||||
*/
|
||||
fun whenConsumed(ref: StateRef): ListenableFuture<Wallet.Update> {
|
||||
val future = SettableFuture.create<Wallet.Update>()
|
||||
updates.filter { ref in it.consumed }.first().subscribe {
|
||||
future.set(it)
|
||||
}
|
||||
return future
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : LinearState> WalletService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)
|
||||
@ -156,6 +169,7 @@ interface StorageService {
|
||||
* Returns the legal identity that this node is configured with. Assumed to be initialised when the node is
|
||||
* first installed.
|
||||
*/
|
||||
//TODO this should be in the IdentityService, or somewhere not here
|
||||
val myLegalIdentity: Party
|
||||
val myLegalIdentityKey: KeyPair
|
||||
}
|
||||
@ -172,4 +186,24 @@ interface TxWritableStorageService : StorageService {
|
||||
override val validatedTransactions: TransactionStorage
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to schedule activity at some point in time. This interface might well be expanded to
|
||||
* increase the feature set in the future.
|
||||
*
|
||||
* If the point in time is in the past, the expectation is that the activity will happen shortly after it is scheduled.
|
||||
*
|
||||
* The main consumer initially is an observer of the wallet to schedule activities based on transactions as they are
|
||||
* recorded.
|
||||
*/
|
||||
interface SchedulerService {
|
||||
/**
|
||||
* Schedule a new activity for a TX output, probably because it was just produced.
|
||||
*
|
||||
* Only one activity can be scheduled for a particular [StateRef] at any one time. Scheduling a [ScheduledStateRef]
|
||||
* replaces any previously scheduled [ScheduledStateRef] for any one [StateRef].
|
||||
*/
|
||||
fun scheduleStateActivity(action: ScheduledStateRef)
|
||||
|
||||
/** Unschedule all activity for a TX output, probably because it was consumed. */
|
||||
fun unscheduleStateActivity(ref: StateRef)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package com.r3corda.core.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.messaging.MessageRecipients
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.node.ServiceHub
|
||||
import com.r3corda.core.utilities.ProgressTracker
|
||||
import com.r3corda.core.utilities.UntrustworthyData
|
||||
@ -25,6 +25,7 @@ import org.slf4j.Logger
|
||||
* it to the [subProtocol] method. It will return the result of that protocol when it completes.
|
||||
*/
|
||||
abstract class ProtocolLogic<T> {
|
||||
|
||||
/** Reference to the [Fiber] instance that is the top level controller for the entire flow. */
|
||||
lateinit var psm: ProtocolStateMachine<*>
|
||||
|
||||
@ -38,22 +39,30 @@ abstract class ProtocolLogic<T> {
|
||||
*/
|
||||
val serviceHub: ServiceHub get() = psm.serviceHub
|
||||
|
||||
/**
|
||||
* The topic to use when communicating with other parties. If more than one topic is required then use sub-protocols.
|
||||
* Note that this is temporary until protocol sessions are properly implemented.
|
||||
*/
|
||||
protected abstract val topic: String
|
||||
|
||||
// Kotlin helpers that allow the use of generic types.
|
||||
inline fun <reified T : Any> sendAndReceive(topic: String, destination: MessageRecipients, sessionIDForSend: Long,
|
||||
sessionIDForReceive: Long, obj: Any): UntrustworthyData<T> {
|
||||
return psm.sendAndReceive(topic, destination, sessionIDForSend, sessionIDForReceive, obj, T::class.java)
|
||||
inline fun <reified T : Any> sendAndReceive(destination: Party,
|
||||
sessionIDForSend: Long,
|
||||
sessionIDForReceive: Long,
|
||||
payload: Any): UntrustworthyData<T> {
|
||||
return psm.sendAndReceive(topic, destination, sessionIDForSend, sessionIDForReceive, payload, T::class.java)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> receive(topic: String, sessionIDForReceive: Long): UntrustworthyData<T> {
|
||||
return receive(topic, sessionIDForReceive, T::class.java)
|
||||
inline fun <reified T : Any> receive(sessionIDForReceive: Long): UntrustworthyData<T> {
|
||||
return receive(sessionIDForReceive, T::class.java)
|
||||
}
|
||||
|
||||
@Suspendable fun <T : Any> receive(topic: String, sessionIDForReceive: Long, clazz: Class<T>): UntrustworthyData<T> {
|
||||
return psm.receive(topic, sessionIDForReceive, clazz)
|
||||
@Suspendable fun <T : Any> receive(sessionIDForReceive: Long, receiveType: Class<T>): UntrustworthyData<T> {
|
||||
return psm.receive(topic, sessionIDForReceive, receiveType)
|
||||
}
|
||||
|
||||
@Suspendable fun send(topic: String, destination: MessageRecipients, sessionID: Long, obj: Any) {
|
||||
psm.send(topic, destination, sessionID, obj)
|
||||
@Suspendable fun send(destination: Party, sessionID: Long, payload: Any) {
|
||||
psm.send(topic, destination, sessionID, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,9 +80,15 @@ abstract class ProtocolLogic<T> {
|
||||
|
||||
private fun maybeWireUpProgressTracking(subLogic: ProtocolLogic<*>) {
|
||||
val ours = progressTracker
|
||||
|
||||
val theirs = subLogic.progressTracker
|
||||
if (ours != null && theirs != null)
|
||||
if (ours != null && theirs != null) {
|
||||
if (ours.currentStep == ProgressTracker.UNSTARTED) {
|
||||
logger.warn("ProgressTracker has not been started for $this")
|
||||
ours.nextStep()
|
||||
}
|
||||
ours.setChildProgressTracker(ours.currentStep, theirs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,4 +105,5 @@ abstract class ProtocolLogic<T> {
|
||||
/** This is where you fill out your business logic. */
|
||||
@Suspendable
|
||||
abstract fun call(): T
|
||||
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
package com.r3corda.core.protocols
|
||||
|
||||
import com.google.common.primitives.Primitives
|
||||
import com.r3corda.core.contracts.StateRef
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
||||
import com.r3corda.protocols.TwoPartyDealProtocol
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.jvm.javaType
|
||||
import kotlin.reflect.primaryConstructor
|
||||
|
||||
/**
|
||||
* A class for conversion to and from [ProtocolLogic] and [ProtocolLogicRef] instances
|
||||
*
|
||||
* Validation of types is performed on the way in and way out in case this object is passed between JVMs which might have differing
|
||||
* whitelists.
|
||||
*
|
||||
* TODO: Ways to populate whitelist of "blessed" protocols per node/party
|
||||
* TODO: Ways to populate argument types whitelist. Per node/party or global?
|
||||
* TODO: Align with API related logic for passing in ProtocolLogic references (ProtocolRef)
|
||||
* TODO: Actual support for AppContext / AttachmentsClassLoader
|
||||
*/
|
||||
class ProtocolLogicRefFactory(private val protocolWhitelist: Map<String, Set<String>>) : SingletonSerializeAsToken() {
|
||||
|
||||
constructor() : this(mapOf(Pair(TwoPartyDealProtocol.FixingRoleDecider::class.java.name, setOf(StateRef::class.java.name, Duration::class.java.name))))
|
||||
|
||||
// Pending real dependence on AppContext for class loading etc
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun validateProtocolClassName(className: String, appContext: AppContext) {
|
||||
// TODO: make this specific to the attachments in the [AppContext] by including [SecureHash] in whitelist check
|
||||
require(protocolWhitelist.containsKey(className)) { "${ProtocolLogic::class.java.simpleName} of ${ProtocolLogicRef::class.java.simpleName} must have type on the whitelist: $className" }
|
||||
}
|
||||
|
||||
// Pending real dependence on AppContext for class loading etc
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun validateArgClassName(className: String, argClassName: String, appContext: AppContext) {
|
||||
// TODO: consider more carefully what to whitelist and how to secure protocols
|
||||
// For now automatically accept standard java.lang.* and kotlin.* types.
|
||||
// All other types require manual specification at ProtocolLogicRefFactory construction time.
|
||||
if (argClassName.startsWith("java.lang.") || argClassName.startsWith("kotlin.")) {
|
||||
return
|
||||
}
|
||||
// TODO: make this specific to the attachments in the [AppContext] by including [SecureHash] in whitelist check
|
||||
require(protocolWhitelist[className]!!.contains(argClassName)) { "Args to ${className} must have types on the args whitelist: $argClassName" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [ProtocolLogicRef] for the Kotlin primary constructor or Java constructor and the given args.
|
||||
*/
|
||||
fun create(type: Class<out ProtocolLogic<*>>, vararg args: Any?): ProtocolLogicRef {
|
||||
val constructor = type.kotlin.primaryConstructor ?: return createJava(type, *args)
|
||||
if (constructor.parameters.size < args.size) {
|
||||
throw IllegalProtocolLogicException(type, "due to too many arguments supplied to kotlin primary constructor")
|
||||
}
|
||||
// Build map of args from array
|
||||
val argsMap = args.zip(constructor.parameters).map { Pair(it.second.name!!, it.first) }.toMap()
|
||||
return createKotlin(type, argsMap)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [ProtocolLogicRef] by trying to find a Kotlin constructor that matches the given args.
|
||||
*
|
||||
* TODO: Rethink language specific naming.
|
||||
*/
|
||||
fun createKotlin(type: Class<out ProtocolLogic<*>>, args: Map<String, Any?>): ProtocolLogicRef {
|
||||
// TODO: we need to capture something about the class loader or "application context" into the ref,
|
||||
// perhaps as some sort of ThreadLocal style object. For now, just create an empty one.
|
||||
val appContext = AppContext(emptyList())
|
||||
validateProtocolClassName(type.name, appContext)
|
||||
// Check we can find a constructor and populate the args to it, but don't call it
|
||||
createConstructor(appContext, type, args)
|
||||
return ProtocolLogicRef(type.name, appContext, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [ProtocolLogicRef] by trying to find a Java constructor that matches the given args.
|
||||
*/
|
||||
private fun createJava(type: Class<out ProtocolLogic<*>>, vararg args: Any?): ProtocolLogicRef {
|
||||
// Build map for each
|
||||
val argsMap = HashMap<String, Any?>(args.size)
|
||||
var index = 0
|
||||
args.forEach { argsMap["arg${index++}"] = it }
|
||||
return createKotlin(type, argsMap)
|
||||
}
|
||||
|
||||
fun toProtocolLogic(ref: ProtocolLogicRef): ProtocolLogic<*> {
|
||||
validateProtocolClassName(ref.protocolLogicClassName, ref.appContext)
|
||||
val klass = Class.forName(ref.protocolLogicClassName, true, ref.appContext.classLoader).asSubclass(ProtocolLogic::class.java)
|
||||
return createConstructor(ref.appContext, klass, ref.args)()
|
||||
}
|
||||
|
||||
private fun createConstructor(appContext: AppContext, clazz: Class<out ProtocolLogic<*>>, args: Map<String, Any?>): () -> ProtocolLogic<*> {
|
||||
for (constructor in clazz.kotlin.constructors) {
|
||||
val params = buildParams(appContext, clazz, constructor, args) ?: continue
|
||||
// If we get here then we matched every parameter
|
||||
return { constructor.callBy(params) }
|
||||
}
|
||||
throw IllegalProtocolLogicException(clazz, "as could not find matching constructor for: $args")
|
||||
}
|
||||
|
||||
private fun buildParams(appContext: AppContext, clazz: Class<out ProtocolLogic<*>>, constructor: KFunction<ProtocolLogic<*>>, args: Map<String, Any?>): HashMap<KParameter, Any?>? {
|
||||
val params = hashMapOf<KParameter, Any?>()
|
||||
val usedKeys = hashSetOf<String>()
|
||||
for (parameter in constructor.parameters) {
|
||||
if (!tryBuildParam(args, parameter, params)) {
|
||||
return null
|
||||
} else {
|
||||
usedKeys += parameter.name!!
|
||||
}
|
||||
}
|
||||
if ((args.keys - usedKeys).isNotEmpty()) {
|
||||
// Not all args were used
|
||||
return null
|
||||
}
|
||||
params.values.forEach { if (it is Any) validateArgClassName(clazz.name, it.javaClass.name, appContext) }
|
||||
return params
|
||||
}
|
||||
|
||||
private fun tryBuildParam(args: Map<String, Any?>, parameter: KParameter, params: HashMap<KParameter, Any?>): Boolean {
|
||||
val containsKey = parameter.name in args
|
||||
// OK to be missing if optional
|
||||
return (parameter.isOptional && !containsKey) || (containsKey && paramCanBeBuilt(args, parameter, params))
|
||||
}
|
||||
|
||||
private fun paramCanBeBuilt(args: Map<String, Any?>, parameter: KParameter, params: HashMap<KParameter, Any?>): Boolean {
|
||||
val value = args[parameter.name]
|
||||
params[parameter] = value
|
||||
return (value is Any && parameterAssignableFrom(parameter.type.javaType, value)) || parameter.type.isMarkedNullable
|
||||
}
|
||||
|
||||
private fun parameterAssignableFrom(type: Type, value: Any): Boolean {
|
||||
if (type is Class<*>) {
|
||||
if (type.isPrimitive) {
|
||||
return Primitives.unwrap(value.javaClass) == type
|
||||
} else {
|
||||
return type.isAssignableFrom(value.javaClass)
|
||||
}
|
||||
} else if (type is ParameterizedType) {
|
||||
return parameterAssignableFrom(type.rawType, value)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IllegalProtocolLogicException(type: Class<*>, msg: String) : IllegalArgumentException("${ProtocolLogicRef::class.java.simpleName} cannot be constructed for ${ProtocolLogic::class.java.simpleName} of type ${type.name} $msg")
|
||||
|
||||
/**
|
||||
* A class representing a [ProtocolLogic] instance which would be possible to safely pass out of the contract sandbox
|
||||
*
|
||||
* Only allows a String reference to the ProtocolLogic class, and only allows restricted argument types as per [ProtocolLogicRefFactory]
|
||||
*/
|
||||
// TODO: align this with the existing [ProtocolRef] in the bank-side API (probably replace some of the API classes)
|
||||
data class ProtocolLogicRef internal constructor(val protocolLogicClassName: String, val appContext: AppContext, val args: Map<String, Any?>)
|
||||
|
||||
/**
|
||||
* This is just some way to track what attachments need to be in the class loader, but may later include some app
|
||||
* properties loaded from the attachments. And perhaps the authenticated user for an API call?
|
||||
*/
|
||||
data class AppContext(val attachments: List<SecureHash>) {
|
||||
// TODO: build a real [AttachmentsClassLoader] etc
|
||||
val classLoader: ClassLoader
|
||||
get() = this.javaClass.classLoader
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package com.r3corda.core.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.messaging.MessageRecipients
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.node.ServiceHub
|
||||
import com.r3corda.core.utilities.UntrustworthyData
|
||||
import org.slf4j.Logger
|
||||
@ -12,14 +12,14 @@ import org.slf4j.Logger
|
||||
*/
|
||||
interface ProtocolStateMachine<R> {
|
||||
@Suspendable
|
||||
fun <T : Any> sendAndReceive(topic: String, destination: MessageRecipients, sessionIDForSend: Long, sessionIDForReceive: Long,
|
||||
obj: Any, recvType: Class<T>): UntrustworthyData<T>
|
||||
fun <T : Any> sendAndReceive(topic: String, destination: Party, sessionIDForSend: Long, sessionIDForReceive: Long,
|
||||
payload: Any, recvType: Class<T>): UntrustworthyData<T>
|
||||
|
||||
@Suspendable
|
||||
fun <T : Any> receive(topic: String, sessionIDForReceive: Long, recvType: Class<T>): UntrustworthyData<T>
|
||||
|
||||
@Suspendable
|
||||
fun send(topic: String, destination: MessageRecipients, sessionID: Long, obj: Any)
|
||||
fun send(topic: String, destination: Party, sessionID: Long, payload: Any)
|
||||
|
||||
val serviceHub: ServiceHub
|
||||
val logger: Logger
|
||||
|
@ -360,10 +360,6 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
|
||||
// This is required to make all the unit tests pass
|
||||
register(Party::class.java)
|
||||
|
||||
// Work around a bug in Kryo handling nested generics
|
||||
register(Issued::class.java, ImmutableClassSerializer(Issued::class))
|
||||
register(TransactionState::class.java, ImmutableClassSerializer(TransactionState::class))
|
||||
|
||||
// This ensures a NonEmptySetSerializer is constructed with an initial value.
|
||||
register(NonEmptySet::class.java, NonEmptySetSerializer)
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
package com.r3corda.core.testing
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import java.io.InputStream
|
||||
|
||||
interface OutputStateLookup {
|
||||
fun <S : ContractState> retrieveOutputStateAndRef(clazz: Class<S>, label: String): StateAndRef<S>
|
||||
}
|
||||
|
||||
interface LedgerDSLInterpreter<R, out T : TransactionDSLInterpreter<R>> : OutputStateLookup {
|
||||
fun transaction(transactionLabel: String?, dsl: TransactionDSL<R, T>.() -> R): WireTransaction
|
||||
fun unverifiedTransaction(transactionLabel: String?, dsl: TransactionDSL<R, T>.() -> Unit): WireTransaction
|
||||
fun tweak(dsl: LedgerDSL<R, T, LedgerDSLInterpreter<R, T>>.() -> Unit)
|
||||
fun attachment(attachment: InputStream): SecureHash
|
||||
fun verifies()
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the class the top-level primitives deal with. It delegates all other primitives to the contained interpreter.
|
||||
* This way we have a decoupling of the DSL "AST" and the interpretation(s) of it. Note how the delegation forces
|
||||
* covariance of the TransactionInterpreter parameter.
|
||||
*
|
||||
* TODO (Kotlin 1.1): Use type synonyms to make the type params less unwieldy
|
||||
*/
|
||||
class LedgerDSL<R, out T : TransactionDSLInterpreter<R>, out L : LedgerDSLInterpreter<R, T>> (val interpreter: L) :
|
||||
LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>> by interpreter {
|
||||
|
||||
fun transaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R) =
|
||||
transaction(null, dsl)
|
||||
fun unverifiedTransaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> Unit) =
|
||||
unverifiedTransaction(null, dsl)
|
||||
|
||||
inline fun <reified S : ContractState> String.outputStateAndRef(): StateAndRef<S> =
|
||||
retrieveOutputStateAndRef(S::class.java, this)
|
||||
inline fun <reified S : ContractState> String.output(): TransactionState<S> =
|
||||
outputStateAndRef<S>().state
|
||||
fun String.outputRef(): StateRef = outputStateAndRef<ContractState>().ref
|
||||
}
|
354
core/src/main/kotlin/com/r3corda/core/testing/TestDSL.kt
Normal file
354
core/src/main/kotlin/com/r3corda/core/testing/TestDSL.kt
Normal file
@ -0,0 +1,354 @@
|
||||
package com.r3corda.core.testing
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.crypto.signWithECDSA
|
||||
import com.r3corda.core.node.services.IdentityService
|
||||
import com.r3corda.core.node.services.StorageService
|
||||
import com.r3corda.core.node.services.testing.MockStorageService
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import java.io.InputStream
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
fun transaction(
|
||||
transactionLabel: String? = null,
|
||||
dsl: TransactionDSL<
|
||||
EnforceVerifyOrFail,
|
||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
||||
>.() -> EnforceVerifyOrFail
|
||||
) = JavaTestHelpers.transaction(transactionLabel, dsl)
|
||||
|
||||
fun ledger(
|
||||
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
||||
storageService: StorageService = MockStorageService(),
|
||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
||||
) = JavaTestHelpers.ledger(identityService, storageService, dsl)
|
||||
|
||||
@Deprecated(
|
||||
message = "ledger doesn't nest, use tweak",
|
||||
replaceWith = ReplaceWith("tweak"),
|
||||
level = DeprecationLevel.ERROR)
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
|
||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "transaction doesn't nest, use tweak",
|
||||
replaceWith = ReplaceWith("tweak"),
|
||||
level = DeprecationLevel.ERROR)
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
|
||||
dsl: TransactionDSL<
|
||||
EnforceVerifyOrFail,
|
||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
||||
>.() -> EnforceVerifyOrFail) {
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "ledger doesn't nest, use tweak",
|
||||
replaceWith = ReplaceWith("tweak"),
|
||||
level = DeprecationLevel.ERROR)
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun LedgerDSLInterpreter<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.ledger(
|
||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
||||
}
|
||||
|
||||
/**
|
||||
* If you jumped here from a compiler error make sure the last line of your test tests for a transaction verify or fail
|
||||
* This is a dummy type that can only be instantiated by functions in this module. This way we can ensure that all tests
|
||||
* will have as the last line either an accept or a failure test. The name is deliberately long to help make sense of
|
||||
* the triggered diagnostic.
|
||||
*/
|
||||
sealed class EnforceVerifyOrFail {
|
||||
internal object Token: EnforceVerifyOrFail()
|
||||
}
|
||||
|
||||
/**
|
||||
* This interpreter builds a transaction, and [TransactionDSL.verifies] that the resolved transaction is correct. Note
|
||||
* that transactions corresponding to input states are not verified. Use [LedgerDSL.verifies] for that.
|
||||
*/
|
||||
data class TestTransactionDSLInterpreter(
|
||||
override val ledgerInterpreter: TestLedgerDSLInterpreter,
|
||||
private val inputStateRefs: ArrayList<StateRef> = arrayListOf(),
|
||||
internal val outputStates: ArrayList<LabeledOutput> = arrayListOf(),
|
||||
private val attachments: ArrayList<SecureHash> = arrayListOf(),
|
||||
private val commands: ArrayList<Command> = arrayListOf(),
|
||||
private val signers: LinkedHashSet<PublicKey> = LinkedHashSet(),
|
||||
private val transactionType: TransactionType = TransactionType.General()
|
||||
) : TransactionDSLInterpreter<EnforceVerifyOrFail>, OutputStateLookup by ledgerInterpreter {
|
||||
private fun copy(): TestTransactionDSLInterpreter =
|
||||
TestTransactionDSLInterpreter(
|
||||
ledgerInterpreter = ledgerInterpreter,
|
||||
inputStateRefs = ArrayList(inputStateRefs),
|
||||
outputStates = ArrayList(outputStates),
|
||||
attachments = ArrayList(attachments),
|
||||
commands = ArrayList(commands),
|
||||
signers = LinkedHashSet(signers),
|
||||
transactionType = transactionType
|
||||
)
|
||||
|
||||
internal fun toWireTransaction(): WireTransaction =
|
||||
WireTransaction(
|
||||
inputs = inputStateRefs,
|
||||
outputs = outputStates.map { it.state },
|
||||
attachments = attachments,
|
||||
commands = commands,
|
||||
signers = signers.toList(),
|
||||
type = transactionType
|
||||
)
|
||||
|
||||
override fun input(stateRef: StateRef) {
|
||||
val notary = ledgerInterpreter.resolveStateRef<ContractState>(stateRef).notary
|
||||
signers.add(notary.owningKey)
|
||||
inputStateRefs.add(stateRef)
|
||||
}
|
||||
|
||||
override fun _output(label: String?, notary: Party, contractState: ContractState) {
|
||||
outputStates.add(LabeledOutput(label, TransactionState(contractState, notary)))
|
||||
}
|
||||
|
||||
override fun attachment(attachmentId: SecureHash) {
|
||||
attachments.add(attachmentId)
|
||||
}
|
||||
|
||||
override fun _command(signers: List<PublicKey>, commandData: CommandData) {
|
||||
this.signers.addAll(signers)
|
||||
commands.add(Command(commandData, signers))
|
||||
}
|
||||
|
||||
override fun verifies(): EnforceVerifyOrFail {
|
||||
val resolvedTransaction = ledgerInterpreter.resolveWireTransaction(toWireTransaction())
|
||||
resolvedTransaction.verify()
|
||||
return EnforceVerifyOrFail.Token
|
||||
}
|
||||
|
||||
override fun failsWith(expectedMessage: String?): EnforceVerifyOrFail {
|
||||
val exceptionThrown = try {
|
||||
this.verifies()
|
||||
false
|
||||
} catch (exception: Exception) {
|
||||
if (expectedMessage != null) {
|
||||
val exceptionMessage = exception.message
|
||||
if (exceptionMessage == null) {
|
||||
throw AssertionError(
|
||||
"Expected exception containing '$expectedMessage' but raised exception had no message"
|
||||
)
|
||||
} else if (!exceptionMessage.toLowerCase().contains(expectedMessage.toLowerCase())) {
|
||||
throw AssertionError(
|
||||
"Expected exception containing '$expectedMessage' but raised exception was '$exception'"
|
||||
)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
if (!exceptionThrown) {
|
||||
throw AssertionError("Expected exception but didn't get one")
|
||||
}
|
||||
|
||||
return EnforceVerifyOrFail.Token
|
||||
}
|
||||
|
||||
override fun tweak(
|
||||
dsl: TransactionDSL<
|
||||
EnforceVerifyOrFail,
|
||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
||||
>.() -> EnforceVerifyOrFail
|
||||
) = dsl(TransactionDSL(copy()))
|
||||
}
|
||||
|
||||
class AttachmentResolutionException(attachmentId: SecureHash) :
|
||||
Exception("Attachment with id $attachmentId not found")
|
||||
|
||||
data class TestLedgerDSLInterpreter private constructor (
|
||||
private val identityService: IdentityService,
|
||||
private val storageService: StorageService,
|
||||
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
|
||||
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(),
|
||||
private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
||||
) : LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter> {
|
||||
|
||||
val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
|
||||
|
||||
// We specify [labelToOutputStateAndRefs] just so that Kotlin picks the primary constructor instead of cycling
|
||||
constructor(identityService: IdentityService, storageService: StorageService) : this(
|
||||
identityService, storageService, labelToOutputStateAndRefs = HashMap()
|
||||
)
|
||||
|
||||
companion object {
|
||||
private fun getCallerLocation(offset: Int): String {
|
||||
val stackTraceElement = Thread.currentThread().stackTrace[3 + offset]
|
||||
return stackTraceElement.toString()
|
||||
}
|
||||
}
|
||||
|
||||
internal data class WireTransactionWithLocation(
|
||||
val label: String?,
|
||||
val transaction: WireTransaction,
|
||||
val location: String
|
||||
)
|
||||
class VerifiesFailed(transactionLocation: String, cause: Throwable) :
|
||||
Exception("Transaction defined at ($transactionLocation) didn't verify: $cause", cause)
|
||||
class TypeMismatch(requested: Class<*>, actual: Class<*>) :
|
||||
Exception("Actual type $actual is not a subtype of requested type $requested")
|
||||
|
||||
internal fun copy(): TestLedgerDSLInterpreter =
|
||||
TestLedgerDSLInterpreter(
|
||||
identityService,
|
||||
storageService,
|
||||
labelToOutputStateAndRefs = HashMap(labelToOutputStateAndRefs),
|
||||
transactionWithLocations = HashMap(transactionWithLocations),
|
||||
nonVerifiedTransactionWithLocations = HashMap(nonVerifiedTransactionWithLocations)
|
||||
)
|
||||
|
||||
internal fun resolveWireTransaction(wireTransaction: WireTransaction): TransactionForVerification {
|
||||
return wireTransaction.run {
|
||||
val authenticatedCommands = commands.map {
|
||||
AuthenticatedObject(it.signers, it.signers.mapNotNull { identityService.partyFromKey(it) }, it.value)
|
||||
}
|
||||
val resolvedInputStates = inputs.map { resolveStateRef<ContractState>(it) }
|
||||
val resolvedAttachments = attachments.map { resolveAttachment(it) }
|
||||
TransactionForVerification(
|
||||
inputs = resolvedInputStates,
|
||||
outputs = outputs,
|
||||
commands = authenticatedCommands,
|
||||
origHash = wireTransaction.serialized.hash,
|
||||
attachments = resolvedAttachments,
|
||||
signers = signers.toList(),
|
||||
type = type
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified S : ContractState> resolveStateRef(stateRef: StateRef): TransactionState<S> {
|
||||
val transactionWithLocation =
|
||||
transactionWithLocations[stateRef.txhash] ?:
|
||||
nonVerifiedTransactionWithLocations[stateRef.txhash] ?:
|
||||
throw TransactionResolutionException(stateRef.txhash)
|
||||
val output = transactionWithLocation.transaction.outputs[stateRef.index]
|
||||
return if (S::class.java.isAssignableFrom(output.data.javaClass)) @Suppress("UNCHECKED_CAST") {
|
||||
output as TransactionState<S>
|
||||
} else {
|
||||
throw TypeMismatch(requested = S::class.java, actual = output.data.javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun resolveAttachment(attachmentId: SecureHash): Attachment =
|
||||
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
|
||||
|
||||
private fun <Return> interpretTransactionDsl(
|
||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Return
|
||||
): TestTransactionDSLInterpreter {
|
||||
val transactionInterpreter = TestTransactionDSLInterpreter(this)
|
||||
dsl(TransactionDSL(transactionInterpreter))
|
||||
return transactionInterpreter
|
||||
}
|
||||
|
||||
fun toTransactionGroup(): TransactionGroup {
|
||||
val ledgerTransactions = transactionWithLocations.map {
|
||||
it.value.transaction.toLedgerTransaction(identityService, storageService.attachments)
|
||||
}
|
||||
val nonVerifiedLedgerTransactions = nonVerifiedTransactionWithLocations.map {
|
||||
it.value.transaction.toLedgerTransaction(identityService, storageService.attachments)
|
||||
}
|
||||
return TransactionGroup(ledgerTransactions.toSet(), nonVerifiedLedgerTransactions.toSet())
|
||||
}
|
||||
|
||||
fun transactionName(transactionHash: SecureHash): String? {
|
||||
val transactionWithLocation = transactionWithLocations[transactionHash]
|
||||
return if (transactionWithLocation != null) {
|
||||
transactionWithLocation.label ?: "TX[${transactionWithLocation.location}]"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun outputToLabel(state: ContractState): String? =
|
||||
labelToOutputStateAndRefs.filter { it.value.state.data == state }.keys.firstOrNull()
|
||||
|
||||
private fun <R> recordTransactionWithTransactionMap(
|
||||
transactionLabel: String?,
|
||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> R,
|
||||
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
||||
): WireTransaction {
|
||||
val transactionLocation = getCallerLocation(3)
|
||||
val transactionInterpreter = interpretTransactionDsl(dsl)
|
||||
// Create the WireTransaction
|
||||
val wireTransaction = transactionInterpreter.toWireTransaction()
|
||||
// Record the output states
|
||||
transactionInterpreter.outputStates.forEachIndexed { index, labeledOutput ->
|
||||
if (labeledOutput.label != null) {
|
||||
labelToOutputStateAndRefs[labeledOutput.label] = wireTransaction.outRef(index)
|
||||
}
|
||||
}
|
||||
|
||||
transactionMap[wireTransaction.serialized.hash] =
|
||||
WireTransactionWithLocation(transactionLabel, wireTransaction, transactionLocation)
|
||||
|
||||
return wireTransaction
|
||||
}
|
||||
|
||||
override fun transaction(
|
||||
transactionLabel: String?,
|
||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||
) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations)
|
||||
|
||||
override fun unverifiedTransaction(
|
||||
transactionLabel: String?,
|
||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Unit
|
||||
) = recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations)
|
||||
|
||||
override fun tweak(
|
||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter,
|
||||
LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter>>.() -> Unit) =
|
||||
dsl(LedgerDSL(copy()))
|
||||
|
||||
override fun attachment(attachment: InputStream): SecureHash {
|
||||
return storageService.attachments.importAttachment(attachment)
|
||||
}
|
||||
|
||||
override fun verifies() {
|
||||
val transactionGroup = toTransactionGroup()
|
||||
try {
|
||||
transactionGroup.verify()
|
||||
} catch (exception: TransactionVerificationException) {
|
||||
throw VerifiesFailed(transactionWithLocations[exception.tx.origHash]?.location ?: "<unknown>", exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun <S : ContractState> retrieveOutputStateAndRef(clazz: Class<S>, label: String): StateAndRef<S> {
|
||||
val stateAndRef = labelToOutputStateAndRefs[label]
|
||||
if (stateAndRef == null) {
|
||||
throw IllegalArgumentException("State with label '$label' was not found")
|
||||
} else if (!clazz.isAssignableFrom(stateAndRef.state.data.javaClass)) {
|
||||
throw TypeMismatch(requested = clazz, actual = stateAndRef.state.data.javaClass)
|
||||
} else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return stateAndRef as StateAndRef<S>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyPair>) = transactionsToSign.map { wtx ->
|
||||
val allPubKeys = wtx.signers.toMutableSet()
|
||||
val bits = wtx.serialize()
|
||||
require(bits == wtx.serialized)
|
||||
val signatures = ArrayList<DigitalSignature.WithKey>()
|
||||
for (key in ALL_TEST_KEYS + extraKeys) {
|
||||
if (allPubKeys.contains(key.public)) {
|
||||
signatures += key.signWithECDSA(bits)
|
||||
allPubKeys -= key.public
|
||||
}
|
||||
}
|
||||
SignedTransaction(bits, signatures)
|
||||
}
|
||||
|
||||
fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll(
|
||||
transactionsToSign: List<WireTransaction> = this.interpreter.wireTransactions, vararg extraKeys: KeyPair) =
|
||||
signAll(transactionsToSign, extraKeys)
|
@ -7,18 +7,13 @@ import com.google.common.net.HostAndPort
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.*
|
||||
import com.r3corda.core.node.services.IdentityService
|
||||
import com.r3corda.core.node.services.StorageService
|
||||
import com.r3corda.core.node.services.testing.MockIdentityService
|
||||
import com.r3corda.core.node.services.testing.MockStorageService
|
||||
import com.r3corda.core.seconds
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import java.net.ServerSocket
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.fail
|
||||
|
||||
/** If an exception is thrown by the body, rethrows the root cause exception. */
|
||||
inline fun <R> rootCauseExceptions(body: () -> R): R {
|
||||
@ -95,9 +90,23 @@ object JavaTestHelpers {
|
||||
|
||||
@JvmStatic fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
|
||||
|
||||
@JvmStatic fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): LastLineShouldTestForAcceptOrFailure {
|
||||
return body(TransactionForTest())
|
||||
@JvmStatic @JvmOverloads fun ledger(
|
||||
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
||||
storageService: StorageService = MockStorageService(),
|
||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
||||
): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||
val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService))
|
||||
dsl(ledgerDsl)
|
||||
return ledgerDsl
|
||||
}
|
||||
|
||||
@JvmStatic @JvmOverloads fun transaction(
|
||||
transactionLabel: String? = null,
|
||||
dsl: TransactionDSL<
|
||||
EnforceVerifyOrFail,
|
||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
||||
>.() -> EnforceVerifyOrFail
|
||||
) = ledger { transaction(transactionLabel, dsl) }
|
||||
}
|
||||
|
||||
val TEST_TX_TIME = JavaTestHelpers.TEST_TX_TIME
|
||||
@ -124,27 +133,6 @@ val MOCK_IDENTITY_SERVICE = JavaTestHelpers.MOCK_IDENTITY_SERVICE
|
||||
|
||||
fun generateStateRef() = JavaTestHelpers.generateStateRef()
|
||||
|
||||
fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure) = JavaTestHelpers.transaction(body)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
|
||||
//
|
||||
// Define a transaction like this:
|
||||
//
|
||||
// transaction {
|
||||
// input { someExpression }
|
||||
// output { someExpression }
|
||||
// arg { someExpression }
|
||||
//
|
||||
// tweak {
|
||||
// ... same thing but works with a copy of the parent, can add inputs/outputs/args just within this scope.
|
||||
// }
|
||||
//
|
||||
// contract.accepts() -> should pass
|
||||
// contract `fails requirement` "some substring of the error message"
|
||||
// }
|
||||
//
|
||||
class LabeledOutput(val label: String?, val state: TransactionState<*>) {
|
||||
override fun toString() = state.toString() + (if (label != null) " ($label)" else "")
|
||||
override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state)
|
||||
@ -153,298 +141,3 @@ class LabeledOutput(val label: String?, val state: TransactionState<*>) {
|
||||
|
||||
infix fun TransactionState<*>.label(label: String) = LabeledOutput(label, this)
|
||||
|
||||
abstract class AbstractTransactionForTest {
|
||||
protected val attachments = ArrayList<SecureHash>()
|
||||
protected val outStates = ArrayList<LabeledOutput>()
|
||||
protected val commands = ArrayList<Command>()
|
||||
protected val signers = LinkedHashSet<PublicKey>()
|
||||
protected val type = TransactionType.General()
|
||||
|
||||
@JvmOverloads
|
||||
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, TransactionState(s(), DUMMY_NOTARY)).apply { outStates.add(this) }
|
||||
@JvmOverloads
|
||||
open fun output(label: String? = null, s: ContractState) = output(label) { s }
|
||||
|
||||
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
|
||||
return commands.map { AuthenticatedObject(it.signers, it.signers.mapNotNull { MOCK_IDENTITY_SERVICE.partyFromKey(it) }, it.value) }
|
||||
}
|
||||
|
||||
fun attachment(attachmentID: SecureHash) {
|
||||
attachments.add(attachmentID)
|
||||
}
|
||||
|
||||
fun arg(vararg keys: PublicKey, c: () -> CommandData) {
|
||||
val keysList = listOf(*keys)
|
||||
addCommand(Command(c(), keysList))
|
||||
}
|
||||
fun arg(key: PublicKey, c: CommandData) = arg(key) { c }
|
||||
|
||||
fun timestamp(time: Instant) {
|
||||
val data = TimestampCommand(time, 30.seconds)
|
||||
timestamp(data)
|
||||
}
|
||||
|
||||
fun timestamp(data: TimestampCommand) {
|
||||
addCommand(Command(data, DUMMY_NOTARY.owningKey))
|
||||
}
|
||||
|
||||
fun addCommand(cmd: Command) {
|
||||
signers.addAll(cmd.signers)
|
||||
commands.add(cmd)
|
||||
}
|
||||
|
||||
// Forbid patterns like: transaction { ... transaction { ... } }
|
||||
@Deprecated("Cannot nest transactions, use tweak", level = DeprecationLevel.ERROR)
|
||||
fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure) {
|
||||
}
|
||||
}
|
||||
|
||||
/** If you jumped here from a compiler error make sure the last line of your test tests for a transaction accept or fail
|
||||
* This is a dummy type that can only be instantiated by functions in this module. This way we can ensure that all tests
|
||||
* will have as the last line either an accept or a failure test. The name is deliberately long to help make sense of
|
||||
* the triggered diagnostic
|
||||
*/
|
||||
sealed class LastLineShouldTestForAcceptOrFailure {
|
||||
internal object Token: LastLineShouldTestForAcceptOrFailure()
|
||||
}
|
||||
|
||||
// Corresponds to the args to Contract.verify
|
||||
// Note on defaults: try to avoid Kotlin defaults as they don't work from Java. Instead define overloads
|
||||
open class TransactionForTest : AbstractTransactionForTest() {
|
||||
private val inStates = arrayListOf<TransactionState<ContractState>>()
|
||||
|
||||
fun input(s: () -> ContractState) {
|
||||
signers.add(DUMMY_NOTARY.owningKey)
|
||||
inStates.add(TransactionState(s(), DUMMY_NOTARY))
|
||||
}
|
||||
fun input(s: ContractState) = input { s }
|
||||
|
||||
protected fun runCommandsAndVerify(time: Instant) {
|
||||
val cmds = commandsToAuthenticatedObjects()
|
||||
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256(), signers.toList(), type)
|
||||
tx.verify()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun accepts(time: Instant = TEST_TX_TIME): LastLineShouldTestForAcceptOrFailure {
|
||||
runCommandsAndVerify(time)
|
||||
return LastLineShouldTestForAcceptOrFailure.Token
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun rejects(withMessage: String? = null, time: Instant = TEST_TX_TIME): LastLineShouldTestForAcceptOrFailure {
|
||||
val r = try {
|
||||
runCommandsAndVerify(time)
|
||||
false
|
||||
} catch (e: Exception) {
|
||||
val m = e.message
|
||||
if (m == null)
|
||||
fail("Threw exception without a message")
|
||||
else
|
||||
if (withMessage != null && !m.toLowerCase().contains(withMessage.toLowerCase())) throw AssertionError("Error was actually: $m", e)
|
||||
true
|
||||
}
|
||||
if (!r) throw AssertionError("Expected exception but didn't get one")
|
||||
return LastLineShouldTestForAcceptOrFailure.Token
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to confirm that the test, when (implicitly) run against the .verify() method, fails with the text of the message
|
||||
*/
|
||||
infix fun `fails requirement`(msg: String): LastLineShouldTestForAcceptOrFailure = rejects(msg)
|
||||
fun failsRequirement(msg: String) = this.`fails requirement`(msg)
|
||||
|
||||
// Use this to create transactions where the output of this transaction is automatically used as an input of
|
||||
// the next.
|
||||
fun chain(vararg outputLabels: String, body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): TransactionForTest {
|
||||
val states = outStates.mapNotNull {
|
||||
val l = it.label
|
||||
if (l != null && outputLabels.contains(l))
|
||||
it.state
|
||||
else
|
||||
null
|
||||
}
|
||||
val tx = TransactionForTest()
|
||||
tx.inStates.addAll(states)
|
||||
tx.body()
|
||||
return tx
|
||||
}
|
||||
|
||||
// Allow customisation of partial transactions.
|
||||
fun tweak(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): LastLineShouldTestForAcceptOrFailure {
|
||||
val tx = TransactionForTest()
|
||||
tx.inStates.addAll(inStates)
|
||||
tx.outStates.addAll(outStates)
|
||||
tx.commands.addAll(commands)
|
||||
|
||||
tx.signers.addAll(tx.inStates.map { it.notary.owningKey })
|
||||
tx.signers.addAll(commands.flatMap { it.signers })
|
||||
return tx.body()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return """transaction {
|
||||
inputs: $inStates
|
||||
outputs: $outStates
|
||||
commands $commands
|
||||
}"""
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = this === other || (other is TransactionForTest && inStates == other.inStates && outStates == other.outStates && commands == other.commands)
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = inStates.hashCode()
|
||||
result += 31 * result + outStates.hashCode()
|
||||
result += 31 * result + commands.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
|
||||
open inner class WireTransactionDSL : AbstractTransactionForTest() {
|
||||
private val inStates = ArrayList<StateRef>()
|
||||
|
||||
fun input(label: String) {
|
||||
val notaryKey = label.output.notary.owningKey
|
||||
signers.add(notaryKey)
|
||||
inStates.add(label.outputRef)
|
||||
}
|
||||
|
||||
fun toWireTransaction() = WireTransaction(inStates, attachments, outStates.map { it.state }, commands, signers.toList(), type)
|
||||
}
|
||||
|
||||
val String.output: TransactionState<T>
|
||||
get() = labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found")
|
||||
val String.outputRef: StateRef get() = labelToRefs[this] ?: throw IllegalArgumentException("Unknown label \"$this\"")
|
||||
|
||||
fun <C : ContractState> lookup(label: String): StateAndRef<C> {
|
||||
val output = label.output
|
||||
val newOutput = TransactionState(output.data as C, output.notary)
|
||||
return StateAndRef(newOutput, label.outputRef)
|
||||
}
|
||||
|
||||
private inner class InternalWireTransactionDSL : WireTransactionDSL() {
|
||||
fun finaliseAndInsertLabels(): WireTransaction {
|
||||
val wtx = toWireTransaction()
|
||||
for ((index, labelledState) in outStates.withIndex()) {
|
||||
if (labelledState.label != null) {
|
||||
labelToRefs[labelledState.label] = StateRef(wtx.id, index)
|
||||
if (stateType.isInstance(labelledState.state.data)) {
|
||||
labelToOutputs[labelledState.label] = labelledState.state as TransactionState<T>
|
||||
}
|
||||
outputsToLabels[labelledState.state] = labelledState.label
|
||||
}
|
||||
}
|
||||
return wtx
|
||||
}
|
||||
}
|
||||
|
||||
private val rootTxns = ArrayList<WireTransaction>()
|
||||
private val labelToRefs = HashMap<String, StateRef>()
|
||||
private val labelToOutputs = HashMap<String, TransactionState<T>>()
|
||||
private val outputsToLabels = HashMap<TransactionState<*>, String>()
|
||||
|
||||
fun labelForState(output: TransactionState<*>): String? = outputsToLabels[output]
|
||||
|
||||
inner class Roots {
|
||||
fun transaction(vararg outputStates: LabeledOutput): Roots {
|
||||
val outs = outputStates.map { it.state }
|
||||
val wtx = WireTransaction(emptyList(), emptyList(), outs, emptyList(), emptyList(), TransactionType.General())
|
||||
for ((index, state) in outputStates.withIndex()) {
|
||||
val label = state.label!!
|
||||
labelToRefs[label] = StateRef(wtx.id, index)
|
||||
outputsToLabels[state.state] = label
|
||||
labelToOutputs[label] = state.state as TransactionState<T>
|
||||
}
|
||||
rootTxns.add(wtx)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Don't delete, this is intended to trigger compiler diagnostic when the DSL primitive is used in the wrong place
|
||||
*/
|
||||
@Deprecated("Does not nest ", level = DeprecationLevel.ERROR)
|
||||
fun roots(body: Roots.() -> Unit) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Don't delete, this is intended to trigger compiler diagnostic when the DSL primitive is used in the wrong place
|
||||
*/
|
||||
@Deprecated("Use the vararg form of transaction inside roots", level = DeprecationLevel.ERROR)
|
||||
fun transaction(body: WireTransactionDSL.() -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
fun roots(body: Roots.() -> Unit) = Roots().apply { body() }
|
||||
|
||||
val txns = ArrayList<WireTransaction>()
|
||||
private val txnToLabelMap = HashMap<SecureHash, String>()
|
||||
|
||||
@JvmOverloads
|
||||
fun transaction(label: String? = null, body: WireTransactionDSL.() -> Unit): WireTransaction {
|
||||
val forTest = InternalWireTransactionDSL()
|
||||
forTest.body()
|
||||
val wtx = forTest.finaliseAndInsertLabels()
|
||||
txns.add(wtx)
|
||||
if (label != null)
|
||||
txnToLabelMap[wtx.id] = label
|
||||
return wtx
|
||||
}
|
||||
|
||||
fun labelForTransaction(tx: WireTransaction): String? = txnToLabelMap[tx.id]
|
||||
fun labelForTransaction(tx: LedgerTransaction): String? = txnToLabelMap[tx.id]
|
||||
|
||||
/**
|
||||
* Note: Don't delete, this is intended to trigger compiler diagnostic when the DSL primitive is used in the wrong place
|
||||
*/
|
||||
@Deprecated("Does not nest ", level = DeprecationLevel.ERROR)
|
||||
fun transactionGroup(body: TransactionGroupDSL<T>.() -> Unit) {
|
||||
}
|
||||
|
||||
fun toTransactionGroup() = TransactionGroup(
|
||||
txns.map { it.toLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments) }.toSet(),
|
||||
rootTxns.map { it.toLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments) }.toSet()
|
||||
)
|
||||
|
||||
class Failed(val index: Int, cause: Throwable) : Exception("Transaction $index didn't verify", cause)
|
||||
|
||||
fun verify() {
|
||||
val group = toTransactionGroup()
|
||||
try {
|
||||
group.verify()
|
||||
} catch (e: TransactionVerificationException) {
|
||||
// Let the developer know the index of the transaction that failed.
|
||||
val wtx: WireTransaction = txns.find { it.id == e.tx.origHash }!!
|
||||
throw Failed(txns.indexOf(wtx) + 1, e)
|
||||
}
|
||||
}
|
||||
|
||||
fun expectFailureOfTx(index: Int, message: String): Exception {
|
||||
val e = assertFailsWith(Failed::class) {
|
||||
verify()
|
||||
}
|
||||
assertEquals(index, e.index)
|
||||
if (!(e.cause?.message ?: "") .contains(message))
|
||||
throw AssertionError("Exception should have said '$message' but was actually: ${e.cause?.message}", e.cause)
|
||||
return e
|
||||
}
|
||||
|
||||
fun signAll(txnsToSign: List<WireTransaction> = txns, vararg extraKeys: KeyPair): List<SignedTransaction> {
|
||||
return txnsToSign.map { wtx ->
|
||||
val allPubKeys = wtx.signers.toMutableSet()
|
||||
val bits = wtx.serialize()
|
||||
require(bits == wtx.serialized)
|
||||
val sigs = ArrayList<DigitalSignature.WithKey>()
|
||||
for (key in ALL_TEST_KEYS + extraKeys) {
|
||||
if (allPubKeys.contains(key.public)) {
|
||||
sigs += key.signWithECDSA(bits)
|
||||
allPubKeys -= key.public
|
||||
}
|
||||
}
|
||||
SignedTransaction(bits, sigs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : ContractState> transactionGroupFor(body: TransactionGroupDSL<T>.() -> Unit) = TransactionGroupDSL<T>(T::class.java).apply { this.body() }
|
||||
fun transactionGroup(body: TransactionGroupDSL<ContractState>.() -> Unit) = TransactionGroupDSL(ContractState::class.java).apply { this.body() }
|
||||
|
@ -0,0 +1,88 @@
|
||||
package com.r3corda.core.testing
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.seconds
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
|
||||
//
|
||||
// Define a transaction like this:
|
||||
//
|
||||
// ledger {
|
||||
// transaction {
|
||||
// input { someExpression }
|
||||
// output { someExpression }
|
||||
// command { someExpression }
|
||||
//
|
||||
// tweak {
|
||||
// ... same thing but works with a copy of the parent, can add inputs/outputs/commands just within this scope.
|
||||
// }
|
||||
//
|
||||
// contract.verifies() -> verify() should pass
|
||||
// contract `fails with` "some substring of the error message"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
/**
|
||||
* The [TransactionDSLInterpreter] defines the interface DSL interpreters should satisfy. No
|
||||
* overloading/default valuing should be done here, only the basic functions that are required to implement everything.
|
||||
* Same goes for functions requiring reflection e.g. [OutputStateLookup.retrieveOutputStateAndRef]
|
||||
* Put convenience functions in [TransactionDSL] instead. There are some cases where the overloads would clash with the
|
||||
* Interpreter interface, in these cases define a "backing" function in the interface instead (e.g. [_command]).
|
||||
*
|
||||
* This way the responsibility of providing a nice frontend DSL and the implementation(s) are separated.
|
||||
*/
|
||||
interface TransactionDSLInterpreter<R> : OutputStateLookup {
|
||||
val ledgerInterpreter: LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>>
|
||||
fun input(stateRef: StateRef)
|
||||
fun _output(label: String?, notary: Party, contractState: ContractState)
|
||||
fun attachment(attachmentId: SecureHash)
|
||||
fun _command(signers: List<PublicKey>, commandData: CommandData)
|
||||
fun verifies(): R
|
||||
fun failsWith(expectedMessage: String?): R
|
||||
fun tweak(
|
||||
dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R
|
||||
): R
|
||||
}
|
||||
|
||||
class TransactionDSL<R, out T : TransactionDSLInterpreter<R>> (val interpreter: T) :
|
||||
TransactionDSLInterpreter<R> by interpreter {
|
||||
|
||||
fun input(stateLabel: String) = input(retrieveOutputStateAndRef(ContractState::class.java, stateLabel).ref)
|
||||
/**
|
||||
* Adds the passed in state as a non-verified transaction output to the ledger and adds that as an input.
|
||||
*/
|
||||
fun input(state: ContractState) {
|
||||
val transaction = ledgerInterpreter.unverifiedTransaction(null) {
|
||||
output { state }
|
||||
}
|
||||
input(transaction.outRef<ContractState>(0).ref)
|
||||
}
|
||||
fun input(stateClosure: () -> ContractState) = input(stateClosure())
|
||||
|
||||
@JvmOverloads
|
||||
fun output(label: String? = null, notary: Party = DUMMY_NOTARY, contractStateClosure: () -> ContractState) =
|
||||
_output(label, notary, contractStateClosure())
|
||||
@JvmOverloads
|
||||
fun output(label: String? = null, contractState: ContractState) =
|
||||
_output(label, DUMMY_NOTARY, contractState)
|
||||
|
||||
fun command(vararg signers: PublicKey, commandDataClosure: () -> CommandData) =
|
||||
_command(listOf(*signers), commandDataClosure())
|
||||
fun command(signer: PublicKey, commandData: CommandData) = _command(listOf(signer), commandData)
|
||||
|
||||
@JvmOverloads
|
||||
fun timestamp(time: Instant, notary: PublicKey = DUMMY_NOTARY.owningKey) =
|
||||
timestamp(TimestampCommand(time, 30.seconds), notary)
|
||||
@JvmOverloads
|
||||
fun timestamp(data: TimestampCommand, notary: PublicKey = DUMMY_NOTARY.owningKey) = command(notary, data)
|
||||
|
||||
fun fails() = failsWith(null)
|
||||
infix fun `fails with`(msg: String) = failsWith(msg)
|
||||
}
|
@ -4,7 +4,7 @@ package com.r3corda.core.utilities
|
||||
* A simple wrapper class that contains icons and support for printing them only when we're connected to a terminal.
|
||||
*/
|
||||
object Emoji {
|
||||
val hasEmojiTerminal by lazy { System.getenv("TERM") != null && System.getenv("LANG").contains("UTF-8") }
|
||||
val hasEmojiTerminal by lazy { System.getenv("TERM") != null && (System.getenv("LANG")?.contains("UTF-8") == true) }
|
||||
|
||||
const val CODE_DIAMOND = "\ud83d\udd37"
|
||||
const val CODE_BAG_OF_CASH = "\ud83d\udcb0"
|
||||
|
@ -0,0 +1,19 @@
|
||||
package com.r3corda.core.utilities
|
||||
|
||||
import java.time.*
|
||||
|
||||
/**
|
||||
* This whole file exists as short cuts to get demos working. In reality we'd have static data and/or rules engine
|
||||
* defining things like this. It currently resides in the core module because it needs to be visible to the IRS
|
||||
* contract.
|
||||
*/
|
||||
// We at some future point may implement more than just this constant announcement window and thus use the params.
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun suggestInterestRateAnnouncementTimeWindow(index: String, source: String, date: LocalDate): TimeWindow {
|
||||
// TODO: we would ordinarily convert clock to same time zone as the index/source would announce in
|
||||
// and suggest an announcement time for the interest rate
|
||||
// Here we apply a blanket announcement time of 11:45 London irrespective of source or index
|
||||
val time = LocalTime.of(11, 45)
|
||||
val zoneId = ZoneId.of("Europe/London")
|
||||
return TimeWindow(ZonedDateTime.of(date, time, zoneId).toInstant(), Duration.ofHours(24))
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package com.r3corda.protocols
|
||||
|
||||
import com.r3corda.core.messaging.MessageRecipients
|
||||
|
||||
/**
|
||||
* Abstract superclass for request messages sent to services, which includes common
|
||||
* fields such as replyTo and replyToTopic.
|
||||
*/
|
||||
abstract class AbstractRequestMessage(val replyTo: MessageRecipients, val sessionID: Long?)
|
@ -1,9 +1,9 @@
|
||||
package com.r3corda.protocols
|
||||
|
||||
import com.r3corda.core.contracts.Attachment
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.crypto.sha256
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
@ -12,14 +12,15 @@ import java.io.InputStream
|
||||
* attachments are saved to local storage automatically.
|
||||
*/
|
||||
class FetchAttachmentsProtocol(requests: Set<SecureHash>,
|
||||
otherSide: SingleMessageRecipient) : FetchDataProtocol<Attachment, ByteArray>(requests, otherSide) {
|
||||
otherSide: Party) : FetchDataProtocol<Attachment, ByteArray>(requests, otherSide) {
|
||||
|
||||
companion object {
|
||||
const val TOPIC = "platform.fetch.attachment"
|
||||
}
|
||||
|
||||
override fun load(txid: SecureHash): Attachment? = serviceHub.storageService.attachments.openAttachment(txid)
|
||||
override val topic: String get() = TOPIC
|
||||
|
||||
override val queryTopic: String = TOPIC
|
||||
override fun load(txid: SecureHash): Attachment? = serviceHub.storageService.attachments.openAttachment(txid)
|
||||
|
||||
override fun convert(wire: ByteArray): Attachment {
|
||||
return object : Attachment {
|
||||
|
@ -2,8 +2,8 @@ package com.r3corda.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.contracts.NamedByHash
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import com.r3corda.core.random63BitValue
|
||||
import com.r3corda.core.utilities.UntrustworthyData
|
||||
@ -27,17 +27,15 @@ import java.util.*
|
||||
*/
|
||||
abstract class FetchDataProtocol<T : NamedByHash, W : Any>(
|
||||
protected val requests: Set<SecureHash>,
|
||||
protected val otherSide: SingleMessageRecipient) : ProtocolLogic<FetchDataProtocol.Result<T>>() {
|
||||
protected val otherSide: Party) : ProtocolLogic<FetchDataProtocol.Result<T>>() {
|
||||
|
||||
open class BadAnswer : Exception()
|
||||
class HashNotFound(val requested: SecureHash) : BadAnswer()
|
||||
class DownloadedVsRequestedDataMismatch(val requested: SecureHash, val got: SecureHash) : BadAnswer()
|
||||
|
||||
class Request(val hashes: List<SecureHash>, replyTo: SingleMessageRecipient, sessionID: Long) : AbstractRequestMessage(replyTo, sessionID)
|
||||
data class Request(val hashes: List<SecureHash>, override val replyToParty: Party, override val sessionID: Long) : PartyRequestMessage
|
||||
data class Result<T : NamedByHash>(val fromDisk: List<T>, val downloaded: List<T>)
|
||||
|
||||
protected abstract val queryTopic: String
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Result<T> {
|
||||
// Load the items we have from disk and figure out which we're missing.
|
||||
@ -49,9 +47,9 @@ abstract class FetchDataProtocol<T : NamedByHash, W : Any>(
|
||||
logger.trace("Requesting ${toFetch.size} dependency(s) for verification")
|
||||
|
||||
val sid = random63BitValue()
|
||||
val fetchReq = Request(toFetch, serviceHub.networkService.myAddress, sid)
|
||||
val fetchReq = Request(toFetch, serviceHub.storageService.myLegalIdentity, sid)
|
||||
// TODO: Support "large message" response streaming so response sizes are not limited by RAM.
|
||||
val maybeItems = sendAndReceive<ArrayList<W?>>(queryTopic, otherSide, 0, sid, fetchReq)
|
||||
val maybeItems = sendAndReceive<ArrayList<W?>>(otherSide, 0, sid, fetchReq)
|
||||
// Check for a buggy/malicious peer answering with something that we didn't ask for.
|
||||
val downloaded = validateFetchResponse(maybeItems, toFetch)
|
||||
maybeWriteToDisk(downloaded)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.r3corda.protocols
|
||||
|
||||
import com.r3corda.core.contracts.SignedTransaction
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
|
||||
/**
|
||||
* Given a set of tx hashes (IDs), either loads them from local disk or asks the remote peer to provide them.
|
||||
@ -12,12 +12,14 @@ import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
* results in a [FetchDataProtocol.HashNotFound] exception. Note that returned transactions are not inserted into
|
||||
* the database, because it's up to the caller to actually verify the transactions are valid.
|
||||
*/
|
||||
class FetchTransactionsProtocol(requests: Set<SecureHash>, otherSide: SingleMessageRecipient) :
|
||||
class FetchTransactionsProtocol(requests: Set<SecureHash>, otherSide: Party) :
|
||||
FetchDataProtocol<SignedTransaction, SignedTransaction>(requests, otherSide) {
|
||||
|
||||
companion object {
|
||||
const val TOPIC = "platform.fetch.tx"
|
||||
}
|
||||
|
||||
override val topic: String get() = TOPIC
|
||||
|
||||
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.storageService.validatedTransactions.getTransaction(txid)
|
||||
override val queryTopic: String = TOPIC
|
||||
}
|
@ -9,8 +9,6 @@ import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SignedData
|
||||
import com.r3corda.core.crypto.signWithECDSA
|
||||
import com.r3corda.core.messaging.Ack
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.node.NodeInfo
|
||||
import com.r3corda.core.node.services.TimestampChecker
|
||||
import com.r3corda.core.node.services.UniquenessException
|
||||
import com.r3corda.core.node.services.UniquenessProvider
|
||||
@ -24,8 +22,8 @@ import com.r3corda.core.utilities.UntrustworthyData
|
||||
import java.security.PublicKey
|
||||
|
||||
object NotaryProtocol {
|
||||
val TOPIC = "platform.notary.request"
|
||||
val TOPIC_INITIATE = "platform.notary.initiate"
|
||||
|
||||
val TOPIC = "platform.notary"
|
||||
|
||||
/**
|
||||
* A protocol to be used for obtaining a signature from a [NotaryService] ascertaining the transaction
|
||||
@ -36,6 +34,7 @@ object NotaryProtocol {
|
||||
*/
|
||||
class Client(private val stx: SignedTransaction,
|
||||
override val progressTracker: ProgressTracker = Client.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
|
||||
|
||||
companion object {
|
||||
|
||||
object REQUESTING : ProgressTracker.Step("Requesting signature by Notary service")
|
||||
@ -45,21 +44,23 @@ object NotaryProtocol {
|
||||
fun tracker() = ProgressTracker(REQUESTING, VALIDATING)
|
||||
}
|
||||
|
||||
lateinit var notaryNode: NodeInfo
|
||||
override val topic: String get() = TOPIC
|
||||
|
||||
lateinit var notaryParty: Party
|
||||
|
||||
@Suspendable
|
||||
override fun call(): DigitalSignature.LegallyIdentifiable {
|
||||
progressTracker.currentStep = REQUESTING
|
||||
notaryNode = findNotaryNode()
|
||||
notaryParty = findNotaryParty()
|
||||
|
||||
val sendSessionID = random63BitValue()
|
||||
val receiveSessionID = random63BitValue()
|
||||
|
||||
val handshake = Handshake(serviceHub.networkService.myAddress, sendSessionID, receiveSessionID)
|
||||
sendAndReceive<Ack>(TOPIC_INITIATE, notaryNode.address, 0, receiveSessionID, handshake)
|
||||
val handshake = Handshake(serviceHub.storageService.myLegalIdentity, sendSessionID, receiveSessionID)
|
||||
sendAndReceive<Ack>(notaryParty, 0, receiveSessionID, handshake)
|
||||
|
||||
val request = SignRequest(stx, serviceHub.storageService.myLegalIdentity)
|
||||
val response = sendAndReceive<Result>(TOPIC, notaryNode.address, sendSessionID, receiveSessionID, request)
|
||||
val response = sendAndReceive<Result>(notaryParty, sendSessionID, receiveSessionID, request)
|
||||
|
||||
val notaryResult = validateResponse(response)
|
||||
return notaryResult.sig ?: throw NotaryException(notaryResult.error!!)
|
||||
@ -72,17 +73,17 @@ object NotaryProtocol {
|
||||
if (it.sig != null) validateSignature(it.sig, stx.txBits)
|
||||
else if (it.error is NotaryError.Conflict) it.error.conflict.verified()
|
||||
else if (it.error == null || it.error !is NotaryError)
|
||||
throw IllegalStateException("Received invalid result from Notary service '${notaryNode.identity}'")
|
||||
throw IllegalStateException("Received invalid result from Notary service '$notaryParty'")
|
||||
return it
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateSignature(sig: DigitalSignature.LegallyIdentifiable, data: SerializedBytes<WireTransaction>) {
|
||||
check(sig.signer == notaryNode.identity) { "Notary result not signed by the correct service" }
|
||||
check(sig.signer == notaryParty) { "Notary result not signed by the correct service" }
|
||||
sig.verifyWithECDSA(data)
|
||||
}
|
||||
|
||||
private fun findNotaryNode(): NodeInfo {
|
||||
private fun findNotaryParty(): Party {
|
||||
var maybeNotaryKey: PublicKey? = null
|
||||
val wtx = stx.tx
|
||||
|
||||
@ -97,8 +98,8 @@ object NotaryProtocol {
|
||||
}
|
||||
|
||||
val notaryKey = maybeNotaryKey ?: throw IllegalStateException("Transaction does not specify a Notary")
|
||||
val notaryNode = serviceHub.networkMapCache.getNodeByPublicKey(notaryKey)
|
||||
return notaryNode ?: throw IllegalStateException("No Notary node can be found with the specified public key")
|
||||
val notaryParty = serviceHub.networkMapCache.getNodeByPublicKey(notaryKey)?.identity
|
||||
return notaryParty ?: throw IllegalStateException("No Notary node can be found with the specified public key")
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,32 +111,31 @@ object NotaryProtocol {
|
||||
*
|
||||
* TODO: the notary service should only be able to see timestamp commands and inputs
|
||||
*/
|
||||
open class Service(val otherSide: SingleMessageRecipient,
|
||||
open class Service(val otherSide: Party,
|
||||
val sendSessionID: Long,
|
||||
val receiveSessionID: Long,
|
||||
val timestampChecker: TimestampChecker,
|
||||
val uniquenessProvider: UniquenessProvider) : ProtocolLogic<Unit>() {
|
||||
|
||||
override val topic: String get() = TOPIC
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val request = receive<SignRequest>(TOPIC, receiveSessionID).validate { it }
|
||||
val stx = request.tx
|
||||
val (stx, reqIdentity) = receive<SignRequest>(receiveSessionID).validate { it }
|
||||
val wtx = stx.tx
|
||||
val reqIdentity = request.callerIdentity
|
||||
|
||||
val result: Result
|
||||
try {
|
||||
val result = try {
|
||||
validateTimestamp(wtx)
|
||||
beforeCommit(stx, reqIdentity)
|
||||
commitInputStates(wtx, reqIdentity)
|
||||
|
||||
val sig = sign(stx.txBits)
|
||||
result = Result.noError(sig)
|
||||
|
||||
Result.noError(sig)
|
||||
} catch(e: NotaryException) {
|
||||
result = Result.withError(e.error)
|
||||
Result.withError(e.error)
|
||||
}
|
||||
|
||||
send(TOPIC, otherSide, sendSessionID, result)
|
||||
send(otherSide, sendSessionID, result)
|
||||
}
|
||||
|
||||
private fun validateTimestamp(tx: WireTransaction) {
|
||||
@ -180,14 +180,13 @@ object NotaryProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
class Handshake(
|
||||
replyTo: SingleMessageRecipient,
|
||||
data class Handshake(
|
||||
override val replyToParty: Party,
|
||||
val sendSessionID: Long,
|
||||
sessionID: Long) : AbstractRequestMessage(replyTo, sessionID)
|
||||
override val sessionID: Long) : PartyRequestMessage
|
||||
|
||||
/** TODO: The caller must authenticate instead of just specifying its identity */
|
||||
class SignRequest(val tx: SignedTransaction,
|
||||
val callerIdentity: Party)
|
||||
data class SignRequest(val tx: SignedTransaction, val callerIdentity: Party)
|
||||
|
||||
data class Result private constructor(val sig: DigitalSignature.LegallyIdentifiable?, val error: NotaryError?) {
|
||||
companion object {
|
||||
@ -197,7 +196,7 @@ object NotaryProtocol {
|
||||
}
|
||||
|
||||
interface Factory {
|
||||
fun create(otherSide: SingleMessageRecipient,
|
||||
fun create(otherSide: Party,
|
||||
sendSessionID: Long,
|
||||
receiveSessionID: Long,
|
||||
timestampChecker: TimestampChecker,
|
||||
@ -205,7 +204,7 @@ object NotaryProtocol {
|
||||
}
|
||||
|
||||
object DefaultFactory : Factory {
|
||||
override fun create(otherSide: SingleMessageRecipient,
|
||||
override fun create(otherSide: Party,
|
||||
sendSessionID: Long,
|
||||
receiveSessionID: Long,
|
||||
timestampChecker: TimestampChecker,
|
||||
|
@ -1,17 +1,19 @@
|
||||
package com.r3corda.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.*
|
||||
import com.r3corda.core.contracts.Fix
|
||||
import com.r3corda.core.contracts.FixOf
|
||||
import com.r3corda.core.contracts.TransactionBuilder
|
||||
import com.r3corda.core.contracts.WireTransaction
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.node.NodeInfo
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import com.r3corda.core.random63BitValue
|
||||
import com.r3corda.core.utilities.ProgressTracker
|
||||
import com.r3corda.core.utilities.suggestInterestRateAnnouncementTimeWindow
|
||||
import java.math.BigDecimal
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
// This code is unit tested in NodeInterestRates.kt
|
||||
@ -25,15 +27,15 @@ import java.util.*
|
||||
* @throws FixOutOfRange if the returned fix was further away from the expected rate by the given amount.
|
||||
*/
|
||||
open class RatesFixProtocol(protected val tx: TransactionBuilder,
|
||||
private val oracle: NodeInfo,
|
||||
private val oracle: Party,
|
||||
private val fixOf: FixOf,
|
||||
private val expectedRate: BigDecimal,
|
||||
private val rateTolerance: BigDecimal,
|
||||
private val timeOut: Duration,
|
||||
override val progressTracker: ProgressTracker = RatesFixProtocol.tracker(fixOf.name)) : ProtocolLogic<Unit>() {
|
||||
|
||||
companion object {
|
||||
val TOPIC = "platform.rates.interest.fix"
|
||||
val TOPIC_SIGN = TOPIC + ".sign"
|
||||
val TOPIC_QUERY = TOPIC + ".query"
|
||||
|
||||
class QUERYING(val name: String) : ProgressTracker.Step("Querying oracle for $name interest rate")
|
||||
object WORKING : ProgressTracker.Step("Working with data returned by oracle")
|
||||
@ -42,10 +44,12 @@ open class RatesFixProtocol(protected val tx: TransactionBuilder,
|
||||
fun tracker(fixName: String) = ProgressTracker(QUERYING(fixName), WORKING, SIGNING)
|
||||
}
|
||||
|
||||
override val topic: String get() = TOPIC
|
||||
|
||||
class FixOutOfRange(val byAmount: BigDecimal) : Exception()
|
||||
|
||||
class QueryRequest(val queries: List<FixOf>, replyTo: SingleMessageRecipient, sessionID: Long) : AbstractRequestMessage(replyTo, sessionID)
|
||||
class SignRequest(val tx: WireTransaction, replyTo: SingleMessageRecipient, sessionID: Long) : AbstractRequestMessage(replyTo, sessionID)
|
||||
data class QueryRequest(val queries: List<FixOf>, override val replyToParty: Party, override val sessionID: Long, val deadline: Instant) : PartyRequestMessage
|
||||
data class SignRequest(val tx: WireTransaction, override val replyToParty: Party, override val sessionID: Long) : PartyRequestMessage
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
@ -53,7 +57,7 @@ open class RatesFixProtocol(protected val tx: TransactionBuilder,
|
||||
val fix = query()
|
||||
progressTracker.currentStep = WORKING
|
||||
checkFixIsNearExpected(fix)
|
||||
tx.addCommand(fix, oracle.identity.owningKey)
|
||||
tx.addCommand(fix, oracle.owningKey)
|
||||
beforeSigning(fix)
|
||||
progressTracker.currentStep = SIGNING
|
||||
tx.addSignatureUnchecked(sign())
|
||||
@ -76,24 +80,26 @@ open class RatesFixProtocol(protected val tx: TransactionBuilder,
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
fun sign(): DigitalSignature.LegallyIdentifiable {
|
||||
private fun sign(): DigitalSignature.LegallyIdentifiable {
|
||||
val sessionID = random63BitValue()
|
||||
val wtx = tx.toWireTransaction()
|
||||
val req = SignRequest(wtx, serviceHub.networkService.myAddress, sessionID)
|
||||
val resp = sendAndReceive<DigitalSignature.LegallyIdentifiable>(TOPIC_SIGN, oracle.address, 0, sessionID, req)
|
||||
val req = SignRequest(wtx, serviceHub.storageService.myLegalIdentity, sessionID)
|
||||
val resp = sendAndReceive<DigitalSignature.LegallyIdentifiable>(oracle, 0, sessionID, req)
|
||||
|
||||
return resp.validate { sig ->
|
||||
check(sig.signer == oracle.identity)
|
||||
check(sig.signer == oracle)
|
||||
tx.checkSignature(sig)
|
||||
sig
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
fun query(): Fix {
|
||||
private fun query(): Fix {
|
||||
val sessionID = random63BitValue()
|
||||
val req = QueryRequest(listOf(fixOf), serviceHub.networkService.myAddress, sessionID)
|
||||
val resp = sendAndReceive<ArrayList<Fix>>(TOPIC_QUERY, oracle.address, 0, sessionID, req)
|
||||
val deadline = suggestInterestRateAnnouncementTimeWindow(fixOf.name, oracle.name, fixOf.forDay).end
|
||||
val req = QueryRequest(listOf(fixOf), serviceHub.storageService.myLegalIdentity, sessionID, deadline)
|
||||
// TODO: add deadline to receive
|
||||
val resp = sendAndReceive<ArrayList<Fix>>(oracle, 0, sessionID, req)
|
||||
|
||||
return resp.validate {
|
||||
val fix = it.first()
|
||||
|
@ -2,8 +2,8 @@ package com.r3corda.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import java.util.*
|
||||
|
||||
@ -21,7 +21,7 @@ import java.util.*
|
||||
* protocol is helpful when resolving and verifying a finished but partially signed transaction.
|
||||
*/
|
||||
class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
private val otherSide: SingleMessageRecipient) : ProtocolLogic<Unit>() {
|
||||
private val otherSide: Party) : ProtocolLogic<Unit>() {
|
||||
|
||||
companion object {
|
||||
private fun dependencyIDs(wtx: WireTransaction) = wtx.inputs.map { it.txhash }.toSet()
|
||||
@ -33,11 +33,11 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
private var stx: SignedTransaction? = null
|
||||
private var wtx: WireTransaction? = null
|
||||
|
||||
constructor(stx: SignedTransaction, otherSide: SingleMessageRecipient) : this(stx.tx, otherSide) {
|
||||
constructor(stx: SignedTransaction, otherSide: Party) : this(stx.tx, otherSide) {
|
||||
this.stx = stx
|
||||
}
|
||||
|
||||
constructor(wtx: WireTransaction, otherSide: SingleMessageRecipient) : this(dependencyIDs(wtx), otherSide) {
|
||||
constructor(wtx: WireTransaction, otherSide: Party) : this(dependencyIDs(wtx), otherSide) {
|
||||
this.wtx = wtx
|
||||
}
|
||||
|
||||
@ -70,6 +70,8 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
serviceHub.recordTransactions(downloadedSignedTxns)
|
||||
}
|
||||
|
||||
override val topic: String get() = throw UnsupportedOperationException()
|
||||
|
||||
@Suspendable
|
||||
private fun fetchDependenciesAndCheckSignatures(depsToCheck: Set<SecureHash>,
|
||||
toVerify: HashSet<LedgerTransaction>,
|
||||
|
@ -0,0 +1,23 @@
|
||||
package com.r3corda.protocols
|
||||
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.messaging.MessageRecipients
|
||||
import com.r3corda.core.node.services.NetworkMapCache
|
||||
|
||||
/**
|
||||
* Abstract superclass for request messages sent to services, which includes common
|
||||
* fields such as replyTo and sessionID.
|
||||
*/
|
||||
interface ServiceRequestMessage {
|
||||
val sessionID: Long
|
||||
fun getReplyTo(networkMapCache: NetworkMapCache): MessageRecipients
|
||||
}
|
||||
|
||||
interface PartyRequestMessage : ServiceRequestMessage {
|
||||
|
||||
val replyToParty: Party
|
||||
|
||||
override fun getReplyTo(networkMapCache: NetworkMapCache): MessageRecipients {
|
||||
return networkMapCache.partyNodes.single { it.identity == replyToParty }.address
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package com.r3corda.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.TransientProperty
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.signWithECDSA
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.node.NodeInfo
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import com.r3corda.core.random63BitValue
|
||||
@ -17,6 +17,7 @@ import java.math.BigDecimal
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.security.SignatureException
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* Classes for manipulating a two party deal or agreement.
|
||||
@ -25,9 +26,14 @@ import java.security.SignatureException
|
||||
*
|
||||
* TODO: Also, the term Deal is used here where we might prefer Agreement.
|
||||
*
|
||||
* TODO: Consider whether we can merge this with [TwoPartyTradeProtocol]
|
||||
*
|
||||
*/
|
||||
object TwoPartyDealProtocol {
|
||||
|
||||
val DEAL_TOPIC = "platform.deal"
|
||||
/** This topic exists purely for [FixingSessionInitiation] to be sent from [FixingRoleDecider] to [FixingSessionInitiationHandler] */
|
||||
val FIX_INITIATE_TOPIC = "platform.fix.initiate"
|
||||
|
||||
class DealMismatchException(val expectedDeal: ContractState, val actualDeal: ContractState) : Exception() {
|
||||
override fun toString() = "The submitted deal didn't match the expected: $expectedDeal vs $actualDeal"
|
||||
@ -52,12 +58,7 @@ object TwoPartyDealProtocol {
|
||||
* There's a good chance we can push at least some of this logic down into core protocol logic
|
||||
* and helper methods etc.
|
||||
*/
|
||||
abstract class Primary<U>(val payload: U,
|
||||
val otherSide: SingleMessageRecipient,
|
||||
val otherSessionID: Long,
|
||||
val myKeyPair: KeyPair,
|
||||
val notaryNode: NodeInfo,
|
||||
override val progressTracker: ProgressTracker = Primary.tracker()) : ProtocolLogic<SignedTransaction>() {
|
||||
abstract class Primary<U>(override val progressTracker: ProgressTracker = Primary.tracker()) : ProtocolLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
object AWAITING_PROPOSAL : ProgressTracker.Step("Handshaking and awaiting transaction proposal")
|
||||
@ -71,6 +72,14 @@ object TwoPartyDealProtocol {
|
||||
fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING, SIGNING, NOTARY, SENDING_SIGS, RECORDING, COPYING_TO_REGULATOR)
|
||||
}
|
||||
|
||||
override val topic: String get() = DEAL_TOPIC
|
||||
|
||||
abstract val payload: U
|
||||
abstract val notaryNode: NodeInfo
|
||||
abstract val otherSide: Party
|
||||
abstract val otherSessionID: Long
|
||||
abstract val myKeyPair: KeyPair
|
||||
|
||||
@Suspendable
|
||||
fun getPartialTransaction(): UntrustworthyData<SignedTransaction> {
|
||||
progressTracker.currentStep = AWAITING_PROPOSAL
|
||||
@ -79,8 +88,7 @@ object TwoPartyDealProtocol {
|
||||
|
||||
// Make the first message we'll send to kick off the protocol.
|
||||
val hello = Handshake(payload, myKeyPair.public, sessionID)
|
||||
|
||||
val maybeSTX = sendAndReceive<SignedTransaction>(DEAL_TOPIC, otherSide, otherSessionID, sessionID, hello)
|
||||
val maybeSTX = sendAndReceive<SignedTransaction>(otherSide, otherSessionID, sessionID, hello)
|
||||
|
||||
return maybeSTX
|
||||
}
|
||||
@ -143,12 +151,13 @@ object TwoPartyDealProtocol {
|
||||
|
||||
logger.trace { "Deal stored" }
|
||||
|
||||
progressTracker.currentStep = COPYING_TO_REGULATOR
|
||||
val regulators = serviceHub.networkMapCache.regulators
|
||||
if (regulators.isNotEmpty()) {
|
||||
// Copy the transaction to every regulator in the network. This is obviously completely bogus, it's
|
||||
// just for demo purposes.
|
||||
for (regulator in regulators) {
|
||||
send("regulator.all.seeing.eye", regulator.address, 0, fullySigned)
|
||||
send(regulator.identity, 0, fullySigned)
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +183,7 @@ object TwoPartyDealProtocol {
|
||||
|
||||
logger.trace { "Built finished transaction, sending back to other party!" }
|
||||
|
||||
send(DEAL_TOPIC, otherSide, otherSessionID, SignaturesFromPrimary(ourSignature, notarySignature))
|
||||
send(otherSide, otherSessionID, SignaturesFromPrimary(ourSignature, notarySignature))
|
||||
return fullySigned
|
||||
}
|
||||
}
|
||||
@ -186,10 +195,7 @@ object TwoPartyDealProtocol {
|
||||
* There's a good chance we can push at least some of this logic down into core protocol logic
|
||||
* and helper methods etc.
|
||||
*/
|
||||
abstract class Secondary<U>(val otherSide: SingleMessageRecipient,
|
||||
val notary: Party,
|
||||
val sessionID: Long,
|
||||
override val progressTracker: ProgressTracker = Secondary.tracker()) : ProtocolLogic<SignedTransaction>() {
|
||||
abstract class Secondary<U>(override val progressTracker: ProgressTracker = Secondary.tracker()) : ProtocolLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
object RECEIVING : ProgressTracker.Step("Waiting for deal info")
|
||||
@ -201,6 +207,11 @@ object TwoPartyDealProtocol {
|
||||
fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING, SWAPPING_SIGNATURES, RECORDING)
|
||||
}
|
||||
|
||||
override val topic: String get() = DEAL_TOPIC
|
||||
|
||||
abstract val otherSide: Party
|
||||
abstract val sessionID: Long
|
||||
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
val handshake = receiveAndValidateHandshake()
|
||||
@ -230,7 +241,7 @@ object TwoPartyDealProtocol {
|
||||
private fun receiveAndValidateHandshake(): Handshake<U> {
|
||||
progressTracker.currentStep = RECEIVING
|
||||
// Wait for a trade request to come in on our pre-provided session ID.
|
||||
val handshake = receive<Handshake<U>>(DEAL_TOPIC, sessionID)
|
||||
val handshake = receive<Handshake<U>>(sessionID)
|
||||
|
||||
progressTracker.currentStep = VERIFYING
|
||||
handshake.validate {
|
||||
@ -241,11 +252,11 @@ object TwoPartyDealProtocol {
|
||||
@Suspendable
|
||||
private fun swapSignaturesWithPrimary(stx: SignedTransaction, theirSessionID: Long): SignaturesFromPrimary {
|
||||
progressTracker.currentStep = SWAPPING_SIGNATURES
|
||||
logger.trace { "Sending partially signed transaction to seller" }
|
||||
logger.trace { "Sending partially signed transaction to other party" }
|
||||
|
||||
// TODO: Protect against the seller terminating here and leaving us in the lurch without the final tx.
|
||||
|
||||
return sendAndReceive<SignaturesFromPrimary>(DEAL_TOPIC, otherSide, theirSessionID, sessionID, stx).validate { it }
|
||||
return sendAndReceive<SignaturesFromPrimary>(otherSide, theirSessionID, sessionID, stx).validate { it }
|
||||
}
|
||||
|
||||
private fun signWithOurKeys(signingPubKeys: List<PublicKey>, ptx: TransactionBuilder): SignedTransaction {
|
||||
@ -271,45 +282,47 @@ object TwoPartyDealProtocol {
|
||||
/**
|
||||
* One side of the protocol for inserting a pre-agreed deal.
|
||||
*/
|
||||
open class Instigator<T : DealState>(otherSide: SingleMessageRecipient,
|
||||
notaryNode: NodeInfo,
|
||||
dealBeingOffered: T,
|
||||
myKeyPair: KeyPair,
|
||||
buyerSessionID: Long,
|
||||
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<T>(dealBeingOffered, otherSide, buyerSessionID, myKeyPair, notaryNode)
|
||||
open class Instigator<T : DealState>(override val otherSide: Party,
|
||||
val notary: Party,
|
||||
override val payload: T,
|
||||
override val myKeyPair: KeyPair,
|
||||
override val otherSessionID: Long,
|
||||
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<T>() {
|
||||
|
||||
override val notaryNode: NodeInfo get() =
|
||||
serviceHub.networkMapCache.notaryNodes.filter { it.identity == notary }.single()
|
||||
}
|
||||
|
||||
/**
|
||||
* One side of the protocol for inserting a pre-agreed deal.
|
||||
*/
|
||||
open class Acceptor<T : DealState>(otherSide: SingleMessageRecipient,
|
||||
notary: Party,
|
||||
open class Acceptor<T : DealState>(override val otherSide: Party,
|
||||
val notary: Party,
|
||||
val dealToBuy: T,
|
||||
sessionID: Long,
|
||||
override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<T>(otherSide, notary, sessionID) {
|
||||
override val sessionID: Long,
|
||||
override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<T>() {
|
||||
|
||||
override fun validateHandshake(handshake: Handshake<T>): Handshake<T> {
|
||||
with(handshake) {
|
||||
// What is the seller trying to sell us?
|
||||
val deal: T = handshake.payload
|
||||
val otherKey = handshake.publicKey
|
||||
logger.trace { "Got deal request for: ${handshake.payload}" }
|
||||
// What is the seller trying to sell us?
|
||||
val deal: T = handshake.payload
|
||||
val otherKey = handshake.publicKey
|
||||
logger.trace { "Got deal request for: ${handshake.payload.ref}" }
|
||||
|
||||
// Check the start message for acceptability.
|
||||
check(handshake.sessionID > 0)
|
||||
if (dealToBuy != deal)
|
||||
throw DealMismatchException(dealToBuy, deal)
|
||||
// Check the start message for acceptability.
|
||||
check(handshake.sessionID > 0)
|
||||
check(dealToBuy == deal)
|
||||
|
||||
// We need to substitute in the new public keys for the Parties
|
||||
val myName = serviceHub.storageService.myLegalIdentity.name
|
||||
val myOldParty = deal.parties.single { it.name == myName }
|
||||
val theirOldParty = deal.parties.single { it.name != myName }
|
||||
// We need to substitute in the new public keys for the Parties
|
||||
val myName = serviceHub.storageService.myLegalIdentity.name
|
||||
val myOldParty = deal.parties.single { it.name == myName }
|
||||
val theirOldParty = deal.parties.single { it.name != myName }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val newDeal = deal.
|
||||
withPublicKey(myOldParty, serviceHub.keyManagementService.freshKey().public).
|
||||
withPublicKey(theirOldParty, otherKey) as T
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val newDeal = deal.
|
||||
withPublicKey(myOldParty, serviceHub.keyManagementService.freshKey().public).
|
||||
withPublicKey(theirOldParty, otherKey) as T
|
||||
|
||||
return handshake.copy(payload = newDeal)
|
||||
}
|
||||
return handshake.copy(payload = newDeal)
|
||||
|
||||
}
|
||||
|
||||
@ -328,59 +341,60 @@ object TwoPartyDealProtocol {
|
||||
* One side of the fixing protocol for an interest rate swap, but could easily be generalised further.
|
||||
*
|
||||
* Do not infer too much from the name of the class. This is just to indicate that it is the "side"
|
||||
* of the protocol that is run by the party with the fixed leg of swap deal, which is the basis for decided
|
||||
* of the protocol that is run by the party with the fixed leg of swap deal, which is the basis for deciding
|
||||
* who does what in the protocol.
|
||||
*/
|
||||
open class Fixer<T : FixableDealState>(otherSide: SingleMessageRecipient,
|
||||
notary: Party,
|
||||
val dealToFix: StateAndRef<T>,
|
||||
sessionID: Long,
|
||||
val replacementProgressTracker: ProgressTracker? = null) : Secondary<StateRef>(otherSide, notary, sessionID) {
|
||||
private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.data.nextFixingOf()!!.name)
|
||||
class Fixer(val initiation: FixingSessionInitiation, override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<StateRef>() {
|
||||
|
||||
override val progressTracker: ProgressTracker = replacementProgressTracker ?: createTracker()
|
||||
override val sessionID: Long get() = initiation.sessionID
|
||||
|
||||
fun createTracker(): ProgressTracker = Secondary.tracker().apply {
|
||||
setChildProgressTracker(SIGNING, ratesFixTracker)
|
||||
}
|
||||
override val otherSide: Party get() = initiation.sender
|
||||
|
||||
private lateinit var txState: TransactionState<*>
|
||||
private lateinit var deal: FixableDealState
|
||||
|
||||
override fun validateHandshake(handshake: Handshake<StateRef>): Handshake<StateRef> {
|
||||
with(handshake) {
|
||||
logger.trace { "Got fixing request for: ${dealToFix.state}" }
|
||||
logger.trace { "Got fixing request for: ${handshake.payload}" }
|
||||
|
||||
// Check the start message for acceptability.
|
||||
if (dealToFix.ref != handshake.payload)
|
||||
throw DealRefMismatchException(dealToFix.ref, handshake.payload)
|
||||
// Check the handshake and initiation for acceptability.
|
||||
check(handshake.sessionID > 0)
|
||||
txState = serviceHub.loadState(handshake.payload)
|
||||
deal = txState.data as FixableDealState
|
||||
|
||||
return handshake
|
||||
}
|
||||
// validate the party that initiated is the one on the deal and that the recipient corresponds with it.
|
||||
// TODO: this is in no way secure and will be replaced by general session initiation logic in the future
|
||||
val myName = serviceHub.storageService.myLegalIdentity.name
|
||||
val otherParty = deal.parties.filter { it.name != myName }.single()
|
||||
check(otherParty == initiation.party)
|
||||
// Also check we are one of the parties
|
||||
deal.parties.filter { it.name == myName }.single()
|
||||
|
||||
return handshake
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun assembleSharedTX(handshake: Handshake<StateRef>): Pair<TransactionBuilder, List<PublicKey>> {
|
||||
val fixOf = dealToFix.state.data.nextFixingOf()!!
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val fixOf = deal.nextFixingOf()!!
|
||||
|
||||
// TODO Do we need/want to substitute in new public keys for the Parties?
|
||||
val myName = serviceHub.storageService.myLegalIdentity.name
|
||||
val deal: T = dealToFix.state.data
|
||||
val myOldParty = deal.parties.single { it.name == myName }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val newDeal = deal
|
||||
|
||||
val ptx = TransactionType.General.Builder()
|
||||
val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0], fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
|
||||
val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0].identity, fixOf, BigDecimal.ZERO, BigDecimal.ONE, initiation.timeout) {
|
||||
@Suspendable
|
||||
override fun beforeSigning(fix: Fix) {
|
||||
newDeal.generateFix(ptx, dealToFix, fix)
|
||||
newDeal.generateFix(ptx, StateAndRef(txState, handshake.payload), fix)
|
||||
|
||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||
// to have one.
|
||||
ptx.setTime(serviceHub.clock.instant(), notary, 30.seconds)
|
||||
ptx.setTime(serviceHub.clock.instant(), txState.notary, 30.seconds)
|
||||
}
|
||||
}
|
||||
subProtocol(addFixing)
|
||||
|
||||
return Pair(ptx, arrayListOf(myOldParty.owningKey))
|
||||
}
|
||||
}
|
||||
@ -392,11 +406,75 @@ object TwoPartyDealProtocol {
|
||||
* is just the "side" of the protocol run by the party with the floating leg as a way of deciding who
|
||||
* does what in the protocol.
|
||||
*/
|
||||
open class Floater<T : FixableDealState>(otherSide: SingleMessageRecipient,
|
||||
otherSessionID: Long,
|
||||
notary: NodeInfo,
|
||||
dealToFix: StateAndRef<T>,
|
||||
myKeyPair: KeyPair,
|
||||
val sessionID: Long,
|
||||
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<StateRef>(dealToFix.ref, otherSide, otherSessionID, myKeyPair, notary)
|
||||
class Floater(override val payload: StateRef,
|
||||
override val otherSessionID: Long,
|
||||
override val progressTracker: ProgressTracker = Primary.tracker()) : Primary<StateRef>() {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal val dealToFix: StateAndRef<FixableDealState> by TransientProperty {
|
||||
val state = serviceHub.loadState(payload) as TransactionState<FixableDealState>
|
||||
StateAndRef(state, payload)
|
||||
}
|
||||
|
||||
override val myKeyPair: KeyPair get() {
|
||||
val myName = serviceHub.storageService.myLegalIdentity.name
|
||||
val publicKey = dealToFix.state.data.parties.filter { it.name == myName }.single().owningKey
|
||||
return serviceHub.keyManagementService.toKeyPair(publicKey)
|
||||
}
|
||||
|
||||
override val otherSide: Party get() {
|
||||
// TODO: what happens if there's no node? Move to messaging taking Party and then handled in messaging layer
|
||||
val myName = serviceHub.storageService.myLegalIdentity.name
|
||||
return dealToFix.state.data.parties.filter { it.name != myName }.single()
|
||||
}
|
||||
|
||||
override val notaryNode: NodeInfo get() =
|
||||
serviceHub.networkMapCache.notaryNodes.filter { it.identity == dealToFix.state.notary }.single()
|
||||
}
|
||||
|
||||
|
||||
/** Used to set up the session between [Floater] and [Fixer] */
|
||||
data class FixingSessionInitiation(val sessionID: Long, val party: Party, val sender: Party, val timeout: Duration)
|
||||
|
||||
/**
|
||||
* This protocol looks at the deal and decides whether to be the Fixer or Floater role in agreeing a fixing.
|
||||
*
|
||||
* It is kicked off as an activity on both participant nodes by the scheduler when it's time for a fixing. If the
|
||||
* Fixer role is chosen, then that will be initiated by the [FixingSessionInitiation] message sent from the other party and
|
||||
* handled by the [FixingSessionInitiationHandler].
|
||||
*
|
||||
* TODO: Replace [FixingSessionInitiation] and [FixingSessionInitiationHandler] with generic session initiation logic once it exists.
|
||||
*/
|
||||
class FixingRoleDecider(val ref: StateRef,
|
||||
val timeout: Duration,
|
||||
override val progressTracker: ProgressTracker = tracker(ref.toString())) : ProtocolLogic<Unit>() {
|
||||
|
||||
companion object {
|
||||
class LOADING(ref: String) : ProgressTracker.Step("Loading state $ref to decide fixing role")
|
||||
|
||||
fun tracker(ref: String) = ProgressTracker(LOADING(ref))
|
||||
}
|
||||
|
||||
override val topic: String get() = FIX_INITIATE_TOPIC
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Unit {
|
||||
progressTracker.nextStep()
|
||||
val dealToFix = serviceHub.loadState(ref)
|
||||
// TODO: this is not the eventual mechanism for identifying the parties
|
||||
val sortedParties = (dealToFix.data as FixableDealState).parties.sortedBy { it.name }
|
||||
if (sortedParties[0].name == serviceHub.storageService.myLegalIdentity.name) {
|
||||
// Generate sessionID
|
||||
val sessionID = random63BitValue()
|
||||
val initation = FixingSessionInitiation(sessionID, sortedParties[0], serviceHub.storageService.myLegalIdentity, timeout)
|
||||
|
||||
// Send initiation to other side to launch one side of the fixing protocol (the Fixer).
|
||||
send(sortedParties[1], 0, initation)
|
||||
|
||||
// Then start the other side of the fixing protocol.
|
||||
val protocol = Floater(ref, sessionID)
|
||||
subProtocol(protocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -6,7 +6,6 @@ import com.r3corda.core.contracts.TransactionVerificationException
|
||||
import com.r3corda.core.contracts.WireTransaction
|
||||
import com.r3corda.core.contracts.toLedgerTransaction
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.node.services.TimestampChecker
|
||||
import com.r3corda.core.node.services.UniquenessProvider
|
||||
import java.security.SignatureException
|
||||
@ -17,7 +16,7 @@ import java.security.SignatureException
|
||||
* has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
|
||||
* indeed valid
|
||||
*/
|
||||
class ValidatingNotaryProtocol(otherSide: SingleMessageRecipient,
|
||||
class ValidatingNotaryProtocol(otherSide: Party,
|
||||
sessionIdForSend: Long,
|
||||
sessionIdForReceive: Long,
|
||||
timestampChecker: TimestampChecker,
|
||||
@ -52,7 +51,6 @@ class ValidatingNotaryProtocol(otherSide: SingleMessageRecipient,
|
||||
|
||||
@Suspendable
|
||||
private fun validateDependencies(reqIdentity: Party, wtx: WireTransaction) {
|
||||
val otherSide = serviceHub.networkMapCache.getNodeByPublicKey(reqIdentity.owningKey)!!.address
|
||||
subProtocol(ResolveTransactionsProtocol(wtx, otherSide))
|
||||
subProtocol(ResolveTransactionsProtocol(wtx, reqIdentity))
|
||||
}
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
package protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.signWithECDSA
|
||||
import com.r3corda.core.messaging.Ack
|
||||
import com.r3corda.core.node.NodeInfo
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import com.r3corda.core.random63BitValue
|
||||
import com.r3corda.core.utilities.ProgressTracker
|
||||
import com.r3corda.protocols.NotaryProtocol
|
||||
import com.r3corda.protocols.PartyRequestMessage
|
||||
import com.r3corda.protocols.ResolveTransactionsProtocol
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* Abstract protocol to be used for replacing one state with another, for example when changing the notary of a state.
|
||||
* Notably this requires a one to one replacement of states, states cannot be split, merged or issued as part of these
|
||||
* protocols.
|
||||
*
|
||||
* The [Instigator] assembles the transaction for state replacement and sends out change proposals to all participants
|
||||
* ([Acceptor]) of that state. If participants agree to the proposed change, they each sign the transaction.
|
||||
* Finally, [Instigator] sends the transaction containing all signatures back to each participant so they can record it and
|
||||
* use the new updated state for future transactions.
|
||||
*/
|
||||
abstract class AbstractStateReplacementProtocol<T> {
|
||||
interface Proposal<T> {
|
||||
val stateRef: StateRef
|
||||
val modification: T
|
||||
val stx: SignedTransaction
|
||||
}
|
||||
|
||||
data class Handshake(val sessionIdForSend: Long,
|
||||
override val replyToParty: Party,
|
||||
override val sessionID: Long) : PartyRequestMessage
|
||||
|
||||
abstract class Instigator<S : ContractState, T>(val originalState: StateAndRef<S>,
|
||||
val modification: T,
|
||||
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<StateAndRef<S>>() {
|
||||
companion object {
|
||||
|
||||
object SIGNING : ProgressTracker.Step("Requesting signatures from other parties")
|
||||
|
||||
object NOTARY : ProgressTracker.Step("Requesting notary signature")
|
||||
|
||||
fun tracker() = ProgressTracker(SIGNING, NOTARY)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call(): StateAndRef<S> {
|
||||
val (stx, participants) = assembleTx()
|
||||
|
||||
progressTracker.currentStep = SIGNING
|
||||
|
||||
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
|
||||
val me = listOf(myKey)
|
||||
|
||||
val signatures = if (participants == me) {
|
||||
listOf(getNotarySignature(stx))
|
||||
} else {
|
||||
collectSignatures(participants - me, stx)
|
||||
}
|
||||
|
||||
val finalTx = stx + signatures
|
||||
serviceHub.recordTransactions(listOf(finalTx))
|
||||
return finalTx.tx.outRef(0)
|
||||
}
|
||||
|
||||
abstract internal fun assembleProposal(stateRef: StateRef, modification: T, stx: SignedTransaction): Proposal<T>
|
||||
abstract internal fun assembleTx(): Pair<SignedTransaction, List<PublicKey>>
|
||||
|
||||
@Suspendable
|
||||
private fun collectSignatures(participants: List<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||
val sessions = mutableMapOf<NodeInfo, Long>()
|
||||
|
||||
val participantSignatures = participants.map {
|
||||
val participantNode = serviceHub.networkMapCache.getNodeByPublicKey(it) ?:
|
||||
throw IllegalStateException("Participant $it to state $originalState not found on the network")
|
||||
val sessionIdForSend = random63BitValue()
|
||||
sessions[participantNode] = sessionIdForSend
|
||||
|
||||
getParticipantSignature(participantNode, stx, sessionIdForSend)
|
||||
}
|
||||
|
||||
val allSignatures = participantSignatures + getNotarySignature(stx)
|
||||
sessions.forEach { send(it.key.identity, it.value, allSignatures) }
|
||||
|
||||
return allSignatures
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun getParticipantSignature(node: NodeInfo, stx: SignedTransaction, sessionIdForSend: Long): DigitalSignature.WithKey {
|
||||
val sessionIdForReceive = random63BitValue()
|
||||
val proposal = assembleProposal(originalState.ref, modification, stx)
|
||||
|
||||
val handshake = Handshake(sessionIdForSend, serviceHub.storageService.myLegalIdentity, sessionIdForReceive)
|
||||
sendAndReceive<Ack>(node.identity, 0, sessionIdForReceive, handshake)
|
||||
|
||||
val response = sendAndReceive<Result>(node.identity, sessionIdForSend, sessionIdForReceive, proposal)
|
||||
val participantSignature = response.validate {
|
||||
if (it.sig == null) throw StateReplacementException(it.error!!)
|
||||
else {
|
||||
check(it.sig.by == node.identity.owningKey) { "Not signed by the required participant" }
|
||||
it.sig.verifyWithECDSA(stx.txBits)
|
||||
it.sig
|
||||
}
|
||||
}
|
||||
|
||||
return participantSignature
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
|
||||
progressTracker.currentStep = NOTARY
|
||||
return subProtocol(NotaryProtocol.Client(stx))
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Acceptor<T>(val otherSide: Party,
|
||||
val sessionIdForSend: Long,
|
||||
val sessionIdForReceive: Long,
|
||||
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<Unit>() {
|
||||
|
||||
companion object {
|
||||
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
|
||||
|
||||
object APPROVING : ProgressTracker.Step("State replacement approved")
|
||||
|
||||
object REJECTING : ProgressTracker.Step("State replacement rejected")
|
||||
|
||||
fun tracker() = ProgressTracker(VERIFYING, APPROVING, REJECTING)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
progressTracker.currentStep = VERIFYING
|
||||
val proposal = receive<Proposal<T>>(sessionIdForReceive).validate { it }
|
||||
|
||||
try {
|
||||
verifyProposal(proposal)
|
||||
verifyTx(proposal.stx)
|
||||
} catch(e: Exception) {
|
||||
// TODO: catch only specific exceptions. However, there are numerous validation exceptions
|
||||
// that might occur (tx validation/resolution, invalid proposal). Need to rethink how
|
||||
// we manage exceptions and maybe introduce some platform exception hierarchy
|
||||
val myIdentity = serviceHub.storageService.myLegalIdentity
|
||||
val state = proposal.stateRef
|
||||
val reason = StateReplacementRefused(myIdentity, state, e.message)
|
||||
|
||||
reject(reason)
|
||||
return
|
||||
}
|
||||
|
||||
approve(proposal.stx)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun approve(stx: SignedTransaction) {
|
||||
progressTracker.currentStep = APPROVING
|
||||
|
||||
val mySignature = sign(stx)
|
||||
val response = Result.noError(mySignature)
|
||||
val swapSignatures = sendAndReceive<List<DigitalSignature.WithKey>>(otherSide, sessionIdForSend, sessionIdForReceive, response)
|
||||
|
||||
val allSignatures = swapSignatures.validate { signatures ->
|
||||
signatures.forEach { it.verifyWithECDSA(stx.txBits) }
|
||||
signatures
|
||||
}
|
||||
|
||||
val finalTx = stx + allSignatures
|
||||
finalTx.verify()
|
||||
serviceHub.recordTransactions(listOf(finalTx))
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun reject(e: StateReplacementRefused) {
|
||||
progressTracker.currentStep = REJECTING
|
||||
val response = Result.withError(e)
|
||||
send(otherSide, sessionIdForSend, response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the state change proposal to confirm that it's acceptable to this node. Rules for verification depend
|
||||
* on the change proposed, and may further depend on the node itself (for example configuration).
|
||||
*/
|
||||
abstract internal fun verifyProposal(proposal: Proposal<T>)
|
||||
|
||||
@Suspendable
|
||||
private fun verifyTx(stx: SignedTransaction) {
|
||||
checkMySignatureRequired(stx.tx)
|
||||
checkDependenciesValid(stx)
|
||||
checkValid(stx)
|
||||
}
|
||||
|
||||
private fun checkMySignatureRequired(tx: WireTransaction) {
|
||||
// TODO: use keys from the keyManagementService instead
|
||||
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
|
||||
require(tx.signers.contains(myKey)) { "Party is not a participant for any of the input states of transaction ${tx.id}" }
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun checkDependenciesValid(stx: SignedTransaction) {
|
||||
val dependencyTxIDs = stx.tx.inputs.map { it.txhash }.toSet()
|
||||
subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide))
|
||||
}
|
||||
|
||||
private fun checkValid(stx: SignedTransaction) {
|
||||
val ltx = stx.tx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
|
||||
serviceHub.verifyTransaction(ltx)
|
||||
}
|
||||
|
||||
private fun sign(stx: SignedTransaction): DigitalSignature.WithKey {
|
||||
val myKeyPair = serviceHub.storageService.myLegalIdentityKey
|
||||
return myKeyPair.signWithECDSA(stx.txBits)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: similar classes occur in other places (NotaryProtocol), need to consolidate
|
||||
data class Result private constructor(val sig: DigitalSignature.WithKey?, val error: StateReplacementRefused?) {
|
||||
companion object {
|
||||
fun withError(error: StateReplacementRefused) = Result(null, error)
|
||||
fun noError(sig: DigitalSignature.WithKey) = Result(sig, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Thrown when a participant refuses proposed the state replacement */
|
||||
class StateReplacementRefused(val identity: Party, val state: StateRef, val detail: String?) {
|
||||
override fun toString(): String
|
||||
= "A participant $identity refused to change state $state"
|
||||
}
|
||||
|
||||
class StateReplacementException(val error: StateReplacementRefused)
|
||||
: Exception("State change failed - $error")
|
@ -2,18 +2,8 @@ package protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.signWithECDSA
|
||||
import com.r3corda.core.messaging.Ack
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.node.NodeInfo
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import com.r3corda.core.random63BitValue
|
||||
import com.r3corda.core.utilities.ProgressTracker
|
||||
import com.r3corda.protocols.AbstractRequestMessage
|
||||
import com.r3corda.protocols.NotaryProtocol
|
||||
import com.r3corda.protocols.ResolveTransactionsProtocol
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
@ -25,53 +15,27 @@ import java.security.PublicKey
|
||||
* Finally, [Instigator] sends the transaction containing all signatures back to each participant so they can record it and
|
||||
* use the new updated state for future transactions.
|
||||
*/
|
||||
object NotaryChangeProtocol {
|
||||
val TOPIC_INITIATE = "platform.notary.change.initiate"
|
||||
val TOPIC_CHANGE = "platform.notary.change.execute"
|
||||
object NotaryChangeProtocol: AbstractStateReplacementProtocol<Party>() {
|
||||
|
||||
data class Proposal(val stateRef: StateRef,
|
||||
val newNotary: Party,
|
||||
val stx: SignedTransaction)
|
||||
val TOPIC = "platform.notary.change"
|
||||
|
||||
class Handshake(val sessionIdForSend: Long,
|
||||
replyTo: SingleMessageRecipient,
|
||||
replySessionId: Long) : AbstractRequestMessage(replyTo, replySessionId)
|
||||
data class Proposal(override val stateRef: StateRef,
|
||||
override val modification: Party,
|
||||
override val stx: SignedTransaction) : AbstractStateReplacementProtocol.Proposal<Party>
|
||||
|
||||
class Instigator<T : ContractState>(val originalState: StateAndRef<T>,
|
||||
val newNotary: Party,
|
||||
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<StateAndRef<T>>() {
|
||||
companion object {
|
||||
class Instigator<T : ContractState>(originalState: StateAndRef<T>,
|
||||
newNotary: Party,
|
||||
progressTracker: ProgressTracker = tracker())
|
||||
: AbstractStateReplacementProtocol.Instigator<T, Party>(originalState, newNotary, progressTracker) {
|
||||
|
||||
object SIGNING : ProgressTracker.Step("Requesting signatures from other parties")
|
||||
override val topic: String get() = TOPIC
|
||||
|
||||
object NOTARY : ProgressTracker.Step("Requesting current Notary signature")
|
||||
override fun assembleProposal(stateRef: StateRef, modification: Party, stx: SignedTransaction): AbstractStateReplacementProtocol.Proposal<Party>
|
||||
= NotaryChangeProtocol.Proposal(stateRef, modification, stx)
|
||||
|
||||
fun tracker() = ProgressTracker(SIGNING, NOTARY)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call(): StateAndRef<T> {
|
||||
val (stx, participants) = assembleTx()
|
||||
|
||||
progressTracker.currentStep = SIGNING
|
||||
|
||||
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
|
||||
val me = listOf(myKey)
|
||||
|
||||
val signatures = if (participants == me) {
|
||||
listOf(getNotarySignature(stx))
|
||||
} else {
|
||||
collectSignatures(participants - me, stx)
|
||||
}
|
||||
|
||||
val finalTx = stx + signatures
|
||||
serviceHub.recordTransactions(listOf(finalTx))
|
||||
return finalTx.tx.outRef(0)
|
||||
}
|
||||
|
||||
private fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
|
||||
override fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
|
||||
val state = originalState.state
|
||||
val newState = state.withNewNotary(newNotary)
|
||||
val newState = state.withNewNotary(modification)
|
||||
val participants = state.data.participants
|
||||
val tx = TransactionType.NotaryChange.Builder().withItems(originalState, newState)
|
||||
tx.signWith(serviceHub.storageService.myLegalIdentityKey)
|
||||
@ -79,116 +43,15 @@ object NotaryChangeProtocol {
|
||||
val stx = tx.toSignedTransaction(false)
|
||||
return Pair(stx, participants)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun collectSignatures(participants: List<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||
val sessions = mutableMapOf<NodeInfo, Long>()
|
||||
|
||||
val participantSignatures = participants.map {
|
||||
val participantNode = serviceHub.networkMapCache.getNodeByPublicKey(it) ?:
|
||||
throw IllegalStateException("Participant $it to state $originalState not found on the network")
|
||||
val sessionIdForSend = random63BitValue()
|
||||
sessions[participantNode] = sessionIdForSend
|
||||
|
||||
getParticipantSignature(participantNode, stx, sessionIdForSend)
|
||||
}
|
||||
|
||||
val allSignatures = participantSignatures + getNotarySignature(stx)
|
||||
sessions.forEach { send(TOPIC_CHANGE, it.key.address, it.value, allSignatures) }
|
||||
|
||||
return allSignatures
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun getParticipantSignature(node: NodeInfo, stx: SignedTransaction, sessionIdForSend: Long): DigitalSignature.WithKey {
|
||||
val sessionIdForReceive = random63BitValue()
|
||||
val proposal = Proposal(originalState.ref, newNotary, stx)
|
||||
|
||||
val handshake = Handshake(sessionIdForSend, serviceHub.networkService.myAddress, sessionIdForReceive)
|
||||
sendAndReceive<Ack>(TOPIC_INITIATE, node.address, 0, sessionIdForReceive, handshake)
|
||||
|
||||
val response = sendAndReceive<Result>(TOPIC_CHANGE, node.address, sessionIdForSend, sessionIdForReceive, proposal)
|
||||
val participantSignature = response.validate {
|
||||
if (it.sig == null) throw NotaryChangeException(it.error!!)
|
||||
else {
|
||||
check(it.sig.by == node.identity.owningKey) { "Not signed by the required participant" }
|
||||
it.sig.verifyWithECDSA(stx.txBits)
|
||||
it.sig
|
||||
}
|
||||
}
|
||||
|
||||
return participantSignature
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
|
||||
progressTracker.currentStep = NOTARY
|
||||
return subProtocol(NotaryProtocol.Client(stx))
|
||||
}
|
||||
}
|
||||
|
||||
class Acceptor(val otherSide: SingleMessageRecipient,
|
||||
val sessionIdForSend: Long,
|
||||
val sessionIdForReceive: Long,
|
||||
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<Unit>() {
|
||||
class Acceptor(otherSide: Party,
|
||||
sessionIdForSend: Long,
|
||||
sessionIdForReceive: Long,
|
||||
override val progressTracker: ProgressTracker = tracker())
|
||||
: AbstractStateReplacementProtocol.Acceptor<Party>(otherSide, sessionIdForSend, sessionIdForReceive) {
|
||||
|
||||
companion object {
|
||||
object VERIFYING : ProgressTracker.Step("Verifying Notary change proposal")
|
||||
|
||||
object APPROVING : ProgressTracker.Step("Notary change approved")
|
||||
|
||||
object REJECTING : ProgressTracker.Step("Notary change rejected")
|
||||
|
||||
fun tracker() = ProgressTracker(VERIFYING, APPROVING, REJECTING)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
progressTracker.currentStep = VERIFYING
|
||||
val proposal = receive<Proposal>(TOPIC_CHANGE, sessionIdForReceive).validate { it }
|
||||
|
||||
try {
|
||||
verifyProposal(proposal)
|
||||
verifyTx(proposal.stx)
|
||||
} catch(e: Exception) {
|
||||
// TODO: catch only specific exceptions. However, there are numerous validation exceptions
|
||||
// that might occur (tx validation/resolution, invalid proposal). Need to rethink how
|
||||
// we manage exceptions and maybe introduce some platform exception hierarchy
|
||||
val myIdentity = serviceHub.storageService.myLegalIdentity
|
||||
val state = proposal.stateRef
|
||||
val reason = NotaryChangeRefused(myIdentity, state, e.message)
|
||||
|
||||
reject(reason)
|
||||
return
|
||||
}
|
||||
|
||||
approve(proposal.stx)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun approve(stx: SignedTransaction) {
|
||||
progressTracker.currentStep = APPROVING
|
||||
|
||||
val mySignature = sign(stx)
|
||||
val response = Result.noError(mySignature)
|
||||
val swapSignatures = sendAndReceive<List<DigitalSignature.WithKey>>(TOPIC_CHANGE, otherSide, sessionIdForSend, sessionIdForReceive, response)
|
||||
|
||||
val allSignatures = swapSignatures.validate { signatures ->
|
||||
signatures.forEach { it.verifyWithECDSA(stx.txBits) }
|
||||
signatures
|
||||
}
|
||||
|
||||
val finalTx = stx + allSignatures
|
||||
finalTx.verify()
|
||||
serviceHub.recordTransactions(listOf(finalTx))
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun reject(e: NotaryChangeRefused) {
|
||||
progressTracker.currentStep = REJECTING
|
||||
val response = Result.withError(e)
|
||||
send(TOPIC_CHANGE, otherSide, sessionIdForSend, response)
|
||||
}
|
||||
override val topic: String get() = TOPIC
|
||||
|
||||
/**
|
||||
* Check the notary change proposal.
|
||||
@ -198,8 +61,8 @@ object NotaryChangeProtocol {
|
||||
* TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal
|
||||
*/
|
||||
@Suspendable
|
||||
private fun verifyProposal(proposal: NotaryChangeProtocol.Proposal) {
|
||||
val newNotary = proposal.newNotary
|
||||
override fun verifyProposal(proposal: AbstractStateReplacementProtocol.Proposal<Party>) {
|
||||
val newNotary = proposal.modification
|
||||
val isNotary = serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary }
|
||||
require(isNotary) { "The proposed node $newNotary does not run a Notary service " }
|
||||
|
||||
@ -211,51 +74,5 @@ object NotaryChangeProtocol {
|
||||
val blacklist = listOf("Evil Notary")
|
||||
require(!blacklist.contains(newNotary.name)) { "The proposed new notary $newNotary is not trusted by the party" }
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun verifyTx(stx: SignedTransaction) {
|
||||
checkMySignatureRequired(stx.tx)
|
||||
checkDependenciesValid(stx)
|
||||
checkValid(stx)
|
||||
}
|
||||
|
||||
private fun checkMySignatureRequired(tx: WireTransaction) {
|
||||
// TODO: use keys from the keyManagementService instead
|
||||
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
|
||||
require(tx.signers.contains(myKey)) { "Party is not a participant for any of the input states of transaction ${tx.id}" }
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun checkDependenciesValid(stx: SignedTransaction) {
|
||||
val dependencyTxIDs = stx.tx.inputs.map { it.txhash }.toSet()
|
||||
subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide))
|
||||
}
|
||||
|
||||
private fun checkValid(stx: SignedTransaction) {
|
||||
val ltx = stx.tx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
|
||||
serviceHub.verifyTransaction(ltx)
|
||||
}
|
||||
|
||||
private fun sign(stx: SignedTransaction): DigitalSignature.WithKey {
|
||||
val myKeyPair = serviceHub.storageService.myLegalIdentityKey
|
||||
return myKeyPair.signWithECDSA(stx.txBits)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: similar classes occur in other places (NotaryProtocol), need to consolidate
|
||||
data class Result private constructor(val sig: DigitalSignature.WithKey?, val error: NotaryChangeRefused?) {
|
||||
companion object {
|
||||
fun withError(error: NotaryChangeRefused) = Result(null, error)
|
||||
fun noError(sig: DigitalSignature.WithKey) = Result(sig, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Thrown when a participant refuses to change the notary of the state */
|
||||
class NotaryChangeRefused(val identity: Party, val state: StateRef, val cause: String?) {
|
||||
override fun toString() = "A participant $identity refused to change the notary of state $state"
|
||||
}
|
||||
|
||||
class NotaryChangeException(val error: NotaryChangeRefused) : Exception() {
|
||||
override fun toString() = "${super.toString()}: Notary change failed - ${error.toString()}"
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.r3corda.core.protocols;
|
||||
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ProtocolLogicRefFromJavaTest {
|
||||
|
||||
public static class ParamType1 {
|
||||
public final int value;
|
||||
|
||||
ParamType1(int v) {
|
||||
value = v;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ParamType2 {
|
||||
public final String value;
|
||||
|
||||
ParamType2(String v) {
|
||||
value = v;
|
||||
}
|
||||
}
|
||||
|
||||
public static class JavaProtocolLogic extends ProtocolLogic<Void> {
|
||||
|
||||
public JavaProtocolLogic(ParamType1 A, ParamType2 b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void call() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getTopic() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static class JavaNoArgProtocolLogic extends ProtocolLogic<Void> {
|
||||
|
||||
public JavaNoArgProtocolLogic() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void call() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getTopic() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Map<String, Set<String>> whiteList = new HashMap<>();
|
||||
Set<String> argsList = new HashSet<>();
|
||||
argsList.add(ParamType1.class.getName());
|
||||
argsList.add(ParamType2.class.getName());
|
||||
whiteList.put(JavaProtocolLogic.class.getName(), argsList);
|
||||
ProtocolLogicRefFactory factory = new ProtocolLogicRefFactory(whiteList);
|
||||
factory.create(JavaProtocolLogic.class, new ParamType1(1), new ParamType2("Hello Jack"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoArg() {
|
||||
Map<String, Set<String>> whiteList = new HashMap<>();
|
||||
Set<String> argsList = new HashSet<>();
|
||||
whiteList.put(JavaNoArgProtocolLogic.class.getName(), argsList);
|
||||
ProtocolLogicRefFactory factory = new ProtocolLogicRefFactory(whiteList);
|
||||
factory.create(JavaNoArgProtocolLogic.class);
|
||||
}
|
||||
}
|
@ -28,12 +28,12 @@ class TransactionGraphSearchTests {
|
||||
*/
|
||||
fun buildTransactions(command: CommandData, signer: KeyPair): GraphTransactionStorage {
|
||||
val originTx = TransactionType.General.Builder().apply {
|
||||
addOutputState(DummyContract.State(random31BitValue()), DUMMY_NOTARY)
|
||||
addOutputState(DummyState(random31BitValue()), DUMMY_NOTARY)
|
||||
addCommand(command, signer.public)
|
||||
signWith(signer)
|
||||
}.toSignedTransaction(false)
|
||||
val inputTx = TransactionType.General.Builder().apply {
|
||||
addInputState(originTx.tx.outRef<DummyContract.State>(0))
|
||||
addInputState(originTx.tx.outRef<DummyState>(0))
|
||||
signWith(signer)
|
||||
}.toSignedTransaction(false)
|
||||
return GraphTransactionStorage(originTx, inputTx)
|
||||
|
@ -47,33 +47,36 @@ class TransactionGroupTests {
|
||||
|
||||
@Test
|
||||
fun success() {
|
||||
transactionGroup {
|
||||
roots {
|
||||
transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000")
|
||||
ledger {
|
||||
unverifiedTransaction {
|
||||
output("£1000") { A_THOUSAND_POUNDS }
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("£1000")
|
||||
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("alice's £1000")
|
||||
arg(ALICE_PUBKEY) { TestCash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) }
|
||||
command(ALICE_PUBKEY) { TestCash.Commands.Move() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
verify()
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun conflict() {
|
||||
transactionGroup {
|
||||
ledger {
|
||||
val t = transaction {
|
||||
output("cash") { A_THOUSAND_POUNDS }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
val conflict1 = transaction {
|
||||
@ -81,10 +84,11 @@ class TransactionGroupTests {
|
||||
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` BOB_PUBKEY
|
||||
output { HALF }
|
||||
output { HALF }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
verify()
|
||||
verifies()
|
||||
|
||||
// Alice tries to double spend back to herself.
|
||||
val conflict2 = transaction {
|
||||
@ -92,13 +96,14 @@ class TransactionGroupTests {
|
||||
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` ALICE_PUBKEY
|
||||
output { HALF }
|
||||
output { HALF }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
assertNotEquals(conflict1, conflict2)
|
||||
|
||||
val e = assertFailsWith(TransactionConflictException::class) {
|
||||
verify()
|
||||
verifies()
|
||||
}
|
||||
assertEquals(StateRef(t.id, 0), e.conflictRef)
|
||||
assertEquals(setOf(conflict1.id, conflict2.id), setOf(e.tx1.id, e.tx2.id))
|
||||
@ -108,79 +113,83 @@ class TransactionGroupTests {
|
||||
@Test
|
||||
fun disconnected() {
|
||||
// Check that if we have a transaction in the group that doesn't connect to anything else, it's rejected.
|
||||
val tg = transactionGroup {
|
||||
val tg = ledger {
|
||||
transaction {
|
||||
output("cash") { A_THOUSAND_POUNDS }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("cash")
|
||||
output { A_THOUSAND_POUNDS `owned by` BOB_PUBKEY }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
// We have to do this manually without the DSL because transactionGroup { } won't let us create a tx that
|
||||
// points nowhere.
|
||||
val input = StateAndRef(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY, generateStateRef())
|
||||
tg.txns += TransactionType.General.Builder().apply {
|
||||
addInputState(input)
|
||||
addOutputState(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY)
|
||||
addCommand(TestCash.Commands.Move(), BOB_PUBKEY)
|
||||
}.toWireTransaction()
|
||||
|
||||
val e = assertFailsWith(TransactionResolutionException::class) {
|
||||
tg.verify()
|
||||
tg.apply {
|
||||
transaction {
|
||||
assertFailsWith(TransactionResolutionException::class) {
|
||||
input(input.ref)
|
||||
}
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
assertEquals(e.hash, input.ref.txhash)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun duplicatedInputs() {
|
||||
// Check that a transaction cannot refer to the same input more than once.
|
||||
transactionGroup {
|
||||
roots {
|
||||
transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000")
|
||||
ledger {
|
||||
unverifiedTransaction {
|
||||
output("£1000") { A_THOUSAND_POUNDS }
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("£1000")
|
||||
input("£1000")
|
||||
output { A_THOUSAND_POUNDS.copy(amount = A_THOUSAND_POUNDS.amount * 2) }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
assertFailsWith(TransactionConflictException::class) {
|
||||
verify()
|
||||
verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun signGroup() {
|
||||
val signedTxns: List<SignedTransaction> = transactionGroup {
|
||||
ledger {
|
||||
transaction {
|
||||
output("£1000") { A_THOUSAND_POUNDS }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("£1000")
|
||||
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("alice's £1000")
|
||||
arg(ALICE_PUBKEY) { TestCash.Commands.Move() }
|
||||
arg(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) }
|
||||
command(ALICE_PUBKEY) { TestCash.Commands.Move() }
|
||||
command(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) }
|
||||
this.verifies()
|
||||
}
|
||||
}.signAll()
|
||||
|
||||
// Now go through the conversion -> verification path with them.
|
||||
val ltxns = signedTxns.map {
|
||||
it.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments)
|
||||
}.toSet()
|
||||
TransactionGroup(ltxns, emptySet()).verify()
|
||||
val signedTxns: List<SignedTransaction> = signAll()
|
||||
|
||||
// Now go through the conversion -> verification path with them.
|
||||
val ltxns = signedTxns.map {
|
||||
it.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments)
|
||||
}.toSet()
|
||||
TransactionGroup(ltxns, emptySet()).verify()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,114 @@
|
||||
package com.r3corda.core.contracts.clauses
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
/**
|
||||
* Tests for the clause verifier.
|
||||
*/
|
||||
class VerifyClausesTests {
|
||||
/** Check that if there's no clauses, verification passes. */
|
||||
@Test
|
||||
fun `passes empty clauses`() {
|
||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||
verifyClauses(tx, emptyList<SingleClause>(), emptyList<AuthenticatedObject<CommandData>>())
|
||||
}
|
||||
|
||||
/** Very simple check that the function doesn't error when given any clause */
|
||||
@Test
|
||||
fun minimal() {
|
||||
val clause = object : SingleClause {
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = emptySet()
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.CONTINUE
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.CONTINUE
|
||||
|
||||
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> = emptySet()
|
||||
}
|
||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||
verifyClauses(tx, listOf(clause), emptyList<AuthenticatedObject<CommandData>>())
|
||||
}
|
||||
|
||||
/** Check that when there are no required commands, a clause always matches */
|
||||
@Test
|
||||
fun emptyAlwaysMatches() {
|
||||
val clause = object : SingleClause {
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = emptySet()
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.CONTINUE
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.ERROR
|
||||
|
||||
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> = emptySet()
|
||||
}
|
||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||
// This would error if it wasn't matched
|
||||
verifyClauses(tx, listOf(clause), emptyList<AuthenticatedObject<CommandData>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun errorSuperfluousCommands() {
|
||||
val clause = object : SingleClause {
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = emptySet()
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.ERROR
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.CONTINUE
|
||||
|
||||
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData>
|
||||
= emptySet()
|
||||
}
|
||||
val command = AuthenticatedObject(emptyList(), emptyList(), DummyContract.Commands.Create())
|
||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), listOf(command), SecureHash.randomSHA256())
|
||||
// The clause is matched, but doesn't mark the command as consumed, so this should error
|
||||
assertFailsWith<IllegalStateException> { verifyClauses(tx, listOf(clause), listOf(command)) }
|
||||
}
|
||||
|
||||
/** Check triggering of error if matched */
|
||||
@Test
|
||||
fun errorMatched() {
|
||||
val clause = object : SingleClause {
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = setOf(DummyContract.Commands.Create::class.java)
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.ERROR
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.CONTINUE
|
||||
|
||||
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData>
|
||||
= commands.select<DummyContract.Commands.Create>().map { it.value }.toSet()
|
||||
}
|
||||
var tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||
|
||||
// This should pass as it doesn't match
|
||||
verifyClauses(tx, listOf(clause), emptyList())
|
||||
|
||||
// This matches and should throw an error
|
||||
val command = AuthenticatedObject(emptyList(), emptyList(), DummyContract.Commands.Create())
|
||||
tx = TransactionForContract(emptyList(), emptyList(), emptyList(), listOf(command), SecureHash.randomSHA256())
|
||||
assertFailsWith<IllegalStateException> { verifyClauses(tx, listOf(clause), listOf(command)) }
|
||||
}
|
||||
|
||||
/** Check triggering of error if unmatched */
|
||||
@Test
|
||||
fun errorUnmatched() {
|
||||
val clause = object : SingleClause {
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = setOf(DummyContract.Commands.Create::class.java)
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.CONTINUE
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.ERROR
|
||||
|
||||
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> = emptySet()
|
||||
}
|
||||
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
|
||||
assertFailsWith<IllegalStateException> { verifyClauses(tx, listOf(clause), emptyList()) }
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package com.r3corda.core.protocols
|
||||
|
||||
import com.r3corda.core.days
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.time.Duration
|
||||
|
||||
class ProtocolLogicRefTest {
|
||||
|
||||
data class ParamType1(val value: Int)
|
||||
data class ParamType2(val value: String)
|
||||
|
||||
@Suppress("UNUSED_PARAMETER") // We will never use A or b
|
||||
class KotlinProtocolLogic(A: ParamType1, b: ParamType2) : ProtocolLogic<Unit>() {
|
||||
constructor() : this(ParamType1(1), ParamType2("2"))
|
||||
|
||||
constructor(C: ParamType2) : this(ParamType1(1), C)
|
||||
|
||||
constructor(illegal: Duration) : this(ParamType1(1), ParamType2(illegal.toString()))
|
||||
|
||||
constructor(primitive: String) : this(ParamType1(1), ParamType2(primitive))
|
||||
|
||||
constructor(kotlinType: Int) : this(ParamType1(kotlinType), ParamType2("b"))
|
||||
|
||||
override fun call(): Unit {
|
||||
}
|
||||
|
||||
override val topic: String get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
class KotlinNoArgProtocolLogic : ProtocolLogic<Unit>() {
|
||||
override fun call(): Unit {
|
||||
}
|
||||
override val topic: String get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER") // We will never use A or b
|
||||
class NotWhiteListedKotlinProtocolLogic(A: Int, b: String) : ProtocolLogic<Unit>() {
|
||||
override fun call(): Unit {
|
||||
}
|
||||
override val topic: String get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
lateinit var factory: ProtocolLogicRefFactory
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// We have to allow Java boxed primitives but Kotlin warns we shouldn't be using them
|
||||
factory = ProtocolLogicRefFactory(mapOf(Pair(KotlinProtocolLogic::class.java.name, setOf(ParamType1::class.java.name, ParamType2::class.java.name)),
|
||||
Pair(KotlinNoArgProtocolLogic::class.java.name, setOf())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlinNoArg() {
|
||||
factory.create(KotlinNoArgProtocolLogic::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlin() {
|
||||
val args = mapOf(Pair("A", ParamType1(1)), Pair("b", ParamType2("Hello Jack")))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreatePrimary() {
|
||||
factory.create(KotlinProtocolLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testCreateNotWhiteListed() {
|
||||
factory.create(NotWhiteListedKotlinProtocolLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlinVoid() {
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, emptyMap())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlinNonPrimary() {
|
||||
val args = mapOf(Pair("C", ParamType2("Hello Jack")))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testCreateArgNotWhiteListed() {
|
||||
val args = mapOf(Pair("illegal", 1.days))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateJavaPrimitiveNoRegistrationRequired() {
|
||||
val args = mapOf(Pair("primitive", "A string"))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlinPrimitiveNoRegistrationRequired() {
|
||||
val args = mapOf(Pair("kotlinType", 3))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
}
|
||||
|
||||
}
|
35
docs/build/html/_sources/codestyle.txt
vendored
35
docs/build/html/_sources/codestyle.txt
vendored
@ -185,3 +185,38 @@ instead do this
|
||||
The latter is easier to catch and handle if later necessary, and the type name should explain what went wrong.
|
||||
|
||||
Note that Kotlin does not require exception types to be declared in method prototypes like Java does.
|
||||
|
||||
5. Properties
|
||||
#############
|
||||
|
||||
Where we want a public property to have one super-type in public and another sub-type in private (or internal), perhaps
|
||||
to expose additional methods with a greater level of access to the code within the enclosing class, the style should be:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class PrivateFoo : PublicFoo
|
||||
|
||||
private val _foo = PrivateFoo()
|
||||
val foo: PublicFoo get() = _foo
|
||||
|
||||
Notably:
|
||||
|
||||
* The public property should have an explicit and more restrictive type, most likely a super class or interface.
|
||||
* The private, backed property should begin with underscore but otherwise have the same name as the public property.
|
||||
The underscore resolves a potential property name clash, and avoids naming such as "privateFoo". If the type or use
|
||||
of the private property is different enough that there is no naming collision, prefer the distinct names without
|
||||
an underscore.
|
||||
* The underscore prefix is not a general pattern for private properties.
|
||||
* The public property should not have an additional backing field but use "get()" to return an appropriate copy of the
|
||||
private field.
|
||||
* The public property should optionally wrap the returned value in an immutable wrapper, such as Guava's immutable
|
||||
collection wrappers, if that is appropriate.
|
||||
* If the code following "get()" is succinct, prefer a one-liner formatting of the public property as above, otherwise
|
||||
put the "get()" on the line below, indented.
|
||||
|
||||
6. Compiler warnings
|
||||
####################
|
||||
|
||||
We do not allow compiler warnings, except in the experimental module where the usual standards do not apply and warnings
|
||||
are suppressed. If a warning exists it should be either fixed or suppressed using @SuppressWarnings and if suppressed
|
||||
there must be an accompanying explanation in the code for why the warning is a false positive.
|
147
docs/build/html/_sources/consensus.txt
vendored
147
docs/build/html/_sources/consensus.txt
vendored
@ -1,11 +1,11 @@
|
||||
Consensus Model
|
||||
Consensus model
|
||||
===============
|
||||
|
||||
The fundamental unit of consensus in Corda is the **state**. The concept of consensus can be divided into two parts:
|
||||
|
||||
1. Consensus over state **validity** -- parties can reach certainty that a transaction defining output states is accepted by the contracts pointed to by the states and has all the required signatures. This is achieved by parties independently running the same contract code and validation logic (as described in :doc:`data model <data-model>`)
|
||||
|
||||
2. Consensus over state **uniqueness** -- parties can reach certainty the the output states created in a transaction are the unique successors to the input states consumed by that transaction (in other words -- a state has not been used as an input by more than one transaction)
|
||||
2. Consensus over state **uniqueness** -- parties can reach certainty the output states created in a transaction are the unique successors to the input states consumed by that transaction (in other words -- a state has not been used as an input by more than one transaction)
|
||||
|
||||
This article presents an initial model for addressing the **uniqueness** problem.
|
||||
|
||||
@ -15,104 +15,133 @@ Notary
|
||||
------
|
||||
|
||||
We introduce the concept of a **Notary**, which is an authority responsible for attesting that for a given transaction, it had not signed another transaction consuming any of its input states.
|
||||
The data model is extended so that every **state** has an appointed Notary:
|
||||
The data model is extended so that every **state** has an appointed notary:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
interface ContractState {
|
||||
/** Contract by which the state belongs */
|
||||
val contract: Contract
|
||||
|
||||
/** Identity of the notary that ensures this state is not used as an input to a transaction more than once */
|
||||
val notary: Party
|
||||
/**
|
||||
* A wrapper for [ContractState] containing additional platform-level state information.
|
||||
* This is the definitive state that is stored on the ledger and used in transaction outputs
|
||||
*/
|
||||
data class TransactionState<out T : ContractState>(
|
||||
/** The custom contract state */
|
||||
val data: T,
|
||||
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
|
||||
val notary: Party) {
|
||||
...
|
||||
}
|
||||
|
||||
All transactions have to be signed by their input state Notary for the output states to be **valid** (apart from *issue* transactions, containing no input states).
|
||||
All transactions have to be signed by their input state notary for the output states to be **valid** (apart from *issue* transactions, containing no input states).
|
||||
|
||||
.. note:: The Notary is a logical concept and can itself be a distributed entity, potentially a cluster maintained by mutually distrusting parties
|
||||
.. note:: The notary is a logical concept and can itself be a distributed entity, potentially a cluster maintained by mutually distrusting parties
|
||||
|
||||
When the Notary is requested to sign a transaction, it either signs over it, attesting that the outputs are the **unique** successors of the inputs,
|
||||
When the notary is requested to sign a transaction, it either signs over it, attesting that the outputs are the **unique** successors of the inputs,
|
||||
or provides conflict information for any input state that had been consumed by another transaction it had signed before.
|
||||
In doing so, the Notary provides the point of finality in the system. Until the Notary signature is obtained, parties cannot be sure that an equally valid, but conflicting transaction,
|
||||
In doing so, the notary provides the point of finality in the system. Until the notary signature is obtained, parties cannot be sure that an equally valid, but conflicting transaction,
|
||||
will not be regarded as confirmed. After the signature is obtained, the parties know that the inputs to this transaction have been uniquely consumed by this transaction.
|
||||
Hence it is the point at which we can say finality has occurred.
|
||||
|
||||
Multiple notaries
|
||||
-----------------
|
||||
|
||||
More than one notary can exist in the network. This gives the following benefits:
|
||||
|
||||
* **Custom behaviour**. We can have both validating and privacy preserving Notaries -- parties can make a choice based on their specific requirements
|
||||
* **Load balancing**. Spreading the transaction load over multiple Notaries will allow higher transaction throughput in the platform overall
|
||||
* **Low latency**. Latency could be minimised by choosing a notary physically closer the transacting parties
|
||||
|
||||
A transaction should only be signed by a notary if all of its input states point to it.
|
||||
In cases where a transaction involves states controlled by multiple notaries, the states first have to be repointed to the same notary.
|
||||
This is achieved by using a special type of transaction that doesn't modify anything but the notary pointer of the state.
|
||||
Ensuring that all input states point to the same notary is the responsibility of each involved party
|
||||
(it is another condition for an output state of the transaction to be **valid**)
|
||||
|
||||
Validation
|
||||
----------
|
||||
|
||||
The Notary *does not validate* transaction integrity (i.e. does not run contracts or check signatures) to minimise the exposed data.
|
||||
Validation would require the caller to reveal the whole transaction history chain, resulting in a privacy leak.
|
||||
One of the design decisions for a notary is whether or not to **validate** a transaction before committing its input states.
|
||||
|
||||
However, this makes it open to "denial of state" attacks, where a party could submit any invalid transaction to the Notary and thus "block" someone else's states.
|
||||
That is partially alleviated by requiring the calling party to authenticate and storing its identity for the request.
|
||||
If a transaction is not checked for validity, it opens the platform to "denial of state" attacks, where anyone can build an invalid transaction consuming someone else's states and submit it to the notary to get the states "blocked".
|
||||
However, validation of a transaction requires the notary to be able to see the full contents of the transaction in question and its dependencies.
|
||||
This is an obvious privacy leak.
|
||||
|
||||
Our platform is flexible and we currently support both validating and non-validating notary implementations -- a party can select which one to use based on its own privacy requirements.
|
||||
|
||||
.. note:: In the non-validating model the "denial of state" attack is partially alleviated by requiring the calling party to authenticate and storing its identity for the request.
|
||||
The conflict information returned by the Notary specifies the consuming transaction id along with the identity of the party that had requested the commit.
|
||||
If the conflicting transaction is valid, the current one gets aborted; if not – a dispute can be raised and the input states of the conflicting invalid transaction are "un-committed" (to be covered by legal process).
|
||||
If the conflicting transaction is valid, the current one gets aborted; if not – a dispute can be raised and the input states of the conflicting invalid transaction are "un-committed" (to be covered by legal process).
|
||||
|
||||
.. note:: At present the Notary can see the entire transaction, but we have a separate piece of work to replace the parts of the transaction it does not require knowing about with hashes (only input references, timestamp information, overall transaction ID and the necessary digests of the rest of the transaction to prove that the referenced inputs/timestamps really do form part of the stated transaction ID should be visible).
|
||||
|
||||
Multiple Notaries
|
||||
-----------------
|
||||
|
||||
More than one Notary can exist in the network. This gives the following benefits:
|
||||
|
||||
* **Custom behaviour**. We can have both validating and privacy preserving Notaries -- parties can make a choice based on their specific requirements
|
||||
* **Load balancing**. Spreading the transaction load over multiple Notaries will allow higher transaction throughput in the platform overall
|
||||
* **Low latency**. Latency could be minimised by choosing a Notary physically closer the transacting parties
|
||||
|
||||
A transaction should only be signed by a Notary if all of its input states point to it.
|
||||
In cases where a transaction involves states controlled by multiple Notaries, the states first have to be repointed to the same notary.
|
||||
This is achieved by using a special type of transaction that doesn't modify anything but the Notary pointer of the state.
|
||||
Ensuring that all input states point to the same Notary is the responsibility of each involved party
|
||||
(it is another condition for an output state of the transaction to be **valid**)
|
||||
.. note:: At present all notaries can see the entire contents of a transaction, but we have a separate piece of work to replace the parts of the transaction it does not require knowing about with hashes (only input references, timestamp information, overall transaction ID and the necessary digests of the rest of the transaction to prove that the referenced inputs/timestamps really do form part of the stated transaction ID should be visible).
|
||||
|
||||
Timestamping
|
||||
------------
|
||||
|
||||
In this model the Notary also acts as a **Timestamping Authority**, verifying the transaction timestamp command.
|
||||
In this model the notary also acts as a **Timestamping Authority**, verifying the transaction timestamp command.
|
||||
|
||||
For a timestamp to be meaningful, its implications must be binding on the party requesting it.
|
||||
A party can obtain a timestamp signature in order to prove that some event happened before/on/or after a particular point in time.
|
||||
However, if the party is not also compelled to commit to the associated transaction, it has a choice of whether or not to reveal this fact until some point in the future.
|
||||
As a result, we need to ensure that the Notary either has to also sign the transaction within some time tolerance,
|
||||
As a result, we need to ensure that the notary either has to also sign the transaction within some time tolerance,
|
||||
or perform timestamping *and* notarisation at the same time, which is the chosen behaviour for this model.
|
||||
|
||||
Implementation & Usage
|
||||
----------------------
|
||||
Running a Notary Service
|
||||
------------------------
|
||||
|
||||
At present we have single basic implementation of a Notary that uses a :code:`UniquenessProvider` storing committed input states in memory:
|
||||
At present we have two basic implementations that store committed input states in memory:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
- ``SimpleNotaryService`` -- commits the provided transaction without any validation
|
||||
|
||||
class InMemoryUniquenessProvider() : UniquenessProvider {
|
||||
/** For each input state store the consuming transaction information */
|
||||
private val committedStates = HashMap<StateRef, ConsumingTx>()
|
||||
- ``ValidatingNotaryService`` -- retrieves and validates the whole transaction history (including the given transaction) before committing
|
||||
|
||||
override fun commit(tx: WireTransaction, callerIdentity: Party) {
|
||||
...
|
||||
}
|
||||
}
|
||||
...
|
||||
/**
|
||||
* Specifies the transaction id, the position of the consumed state in the inputs, and
|
||||
* the caller identity requesting the commit
|
||||
*/
|
||||
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
|
||||
To run one of these services the node has to simply specify either ``SimpleNotaryService.Type`` or ``ValidatingNotaryService.Type`` in its ``advertisedServices`` set, and the correct type will be initialised.
|
||||
|
||||
To obtain a signature from a Notary use :code:`NotaryProtocol`, passing in a :code:`WireTransaction`.
|
||||
The protocol will work out which Notary needs to be called based on the input states and the timestamp command.
|
||||
Obtaining a signature
|
||||
---------------------
|
||||
|
||||
To obtain a signature from a notary use ``NotaryProtocol.Client``, passing in a ``WireTransaction``.
|
||||
The protocol will work out which notary needs to be called based on the input states and the timestamp command.
|
||||
For example, the following snippet can be used when writing a custom protocol:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
private fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
|
||||
return subProtocol(NotaryProtocol(wtx))
|
||||
fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
|
||||
return subProtocol(NotaryProtocol.Client(wtx))
|
||||
}
|
||||
|
||||
On conflict the :code:`NotaryProtocol` with throw a :code:`NotaryException` containing the conflict details:
|
||||
On conflict the ``NotaryProtocol`` with throw a ``NotaryException`` containing the conflict details:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
/** Specifies the consuming transaction for the conflicting input state */
|
||||
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)
|
||||
|
||||
Conflict handling and resolution is currently the responsibility of the protocol author.
|
||||
/**
|
||||
* Specifies the transaction id, the position of the consumed state in the inputs, and
|
||||
* the caller identity requesting the commit
|
||||
*/
|
||||
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
|
||||
|
||||
Conflict handling and resolution is currently the responsibility of the protocol author.
|
||||
|
||||
Changing notaries
|
||||
-----------------
|
||||
|
||||
To change the notary for an input state, use the ``NotaryChangeProtocol``. For example:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
fun changeNotary(originalState: StateAndRef<ContractState>,
|
||||
newNotary: Party): StateAndRef<ContractState> {
|
||||
val protocol = NotaryChangeProtocol.Instigator(originalState, newNotary)
|
||||
return subProtocol(protocol)
|
||||
}
|
||||
|
||||
The protocol will:
|
||||
|
||||
1. Construct a transaction with the old state as the input and the new state as the output
|
||||
|
||||
2. Obtain signatures from all *participants* (a participant is any party that is able to consume this state in a valid transaction, as defined by the state itself)
|
||||
|
||||
3. Obtain the *old* notary signature
|
||||
|
||||
4. Record and distribute the final transaction to the participants so that everyone possesses the new state
|
102
docs/build/html/_sources/event-scheduling.txt
vendored
Normal file
102
docs/build/html/_sources/event-scheduling.txt
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
.. highlight:: kotlin
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||
|
||||
Event scheduling
|
||||
================
|
||||
|
||||
This article explains our experimental approach to modelling time based events in code. It explains how a contract
|
||||
state can expose an upcoming event and what action to take if the scheduled time for that event is reached.
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Many financial instruments have time sensitive components to them. For example, an Interest Rate Swap has a schedule
|
||||
for when:
|
||||
|
||||
* Interest rate fixings should take place for floating legs, so that the interest rate used as the basis for payments
|
||||
can be agreed.
|
||||
* Any payments between the parties are expected to take place.
|
||||
* Any payments between the parties become overdue.
|
||||
|
||||
Each of these is dependent on the current state of the financial instrument. What payments and interest rate fixings
|
||||
have already happened should already be recorded in the state, for example. This means that the *next* time sensitive
|
||||
event is thus a property of the current contract state. By next, we mean earliest in chronological terms, that is still
|
||||
due. If a contract state is consumed in the UTXO model, then what *was* the next event becomes irrelevant and obsolete
|
||||
and the next time sensitive event is determined by any successor contract state.
|
||||
|
||||
Knowing when the next time sensitive event is due to occur is useful, but typically some *activity* is expected to take
|
||||
place when this event occurs. We already have a model for business processes in the form of the protocol state machines,
|
||||
so in the platform we have introduced the concept of *scheduled activities* that can invoke protocol state machines
|
||||
at a scheduled time. A contract state can optionally described the next scheduled activity for itself. If it omits
|
||||
to do so, then nothing will be scheduled.
|
||||
|
||||
How to implement scheduled events
|
||||
---------------------------------
|
||||
|
||||
There are two main steps to implementing scheduled events:
|
||||
|
||||
* Have your ``ContractState`` implementation also implement ``SchedulableState``. This requires a method named
|
||||
``nextScheduledActivity`` to be implemented which returns an optional ``ScheduledActivity`` instance.
|
||||
``ScheduledActivity`` captures what ``ProtocolLogic`` instance each node will run, to perform the activity, and when it
|
||||
will run is described by a ``java.time.Instant``. Once your state implements this interface and is tracked by the
|
||||
wallet, it can expect to be queried for the next activity when recorded via the ``ServiceHub.recordTransactions``
|
||||
method during protocols execution.
|
||||
* If nothing suitable exists, implement a ``ProtocolLogic`` to be executed by each node as the activity itself.
|
||||
The important thing to remember is that each node that is party to the transaction, in the current implementation,
|
||||
will execute the same ``ProtocolLogic`` so that needs to establish roles in the business process based on the contract
|
||||
state and the node it is running on, and follow different but complementary paths through the business logic.
|
||||
|
||||
.. note:: The scheduler's clock always operates in the UTC time zone for uniformity, so any time zone logic must be
|
||||
performed by the contract, using ``ZonedDateTime``.
|
||||
|
||||
In the short term, until we have automatic protocol session set up, you will also likely need to install a network
|
||||
handler to help with obtaining a unqiue and secure random session. An example is described below.
|
||||
|
||||
The production and consumption of ``ContractStates`` is observed by the scheduler and the activities associated with
|
||||
any consumed states are unscheduled. Any newly produced states are then queried via the ``nextScheduledActivity``
|
||||
method and if they do not return ``null`` then that activity is scheduled based on the content of the
|
||||
``ScheduledActivity`` object returned.
|
||||
|
||||
An example
|
||||
----------
|
||||
|
||||
Let's take an example of the Interest Rate Swap fixings for our scheduled events. The first task is to implement the
|
||||
``nextScheduledActivity`` method on the ``State``.
|
||||
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef,
|
||||
protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity? {
|
||||
val nextFixingOf = nextFixingOf() ?: return null
|
||||
|
||||
// This is perhaps not how we should determine the time point in the business day, but instead expect the
|
||||
// schedule to detail some of these aspects.
|
||||
val (instant, duration) = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name,
|
||||
source = floatingLeg.indexSource,
|
||||
date = nextFixingOf.forDay)
|
||||
return ScheduledActivity(protocolLogicRefFactory.create(TwoPartyDealProtocol.FixingRoleDecider::class.java,
|
||||
thisStateRef, duration), instant)
|
||||
}
|
||||
|
||||
The first thing this does is establish if there are any remaining fixings. If there are none, then it returns ``null``
|
||||
to indicate that there is no activity to schedule. Otherwise it calculates the ``Instant`` at which the interest rate
|
||||
should become available and schedules an activity at that time to work out what roles each node will take in the fixing
|
||||
business process and to take on those roles. That ``ProtocolLogic`` will be handed the ``StateRef`` for the interest
|
||||
rate swap ``State`` in question, as well as a tolerance ``Duration`` of how long to wait after the activity is triggered
|
||||
for the interest rate before indicating an error.
|
||||
|
||||
.. note:: The use of the factory to create a ``ProtocolLogicRef`` instance to embed in the ``ScheduledActivity``. This is a
|
||||
way to create a reference to the ``ProtocolLogic`` class and it's constructor parameters to instantiate that can be
|
||||
checked against a per node whitelist of approved and allowable types as part of our overall security sandboxing.
|
||||
|
||||
As previously mentioned, we currently need a small network handler to assist with session setup until the work to
|
||||
automate that is complete. See the interest rate swap specific implementation ``FixingSessionInitiationHandler`` which
|
||||
is responsible for starting a ``ProtocolLogic`` to perform one role in the fixing protocol with the ``sessionID`` sent
|
||||
by the ``FixingRoleDecider`` on the other node which then launches the other role in the fixing protocol. Currently
|
||||
the handler needs to be manually installed in the node.
|
1
docs/build/html/_sources/index.txt
vendored
1
docs/build/html/_sources/index.txt
vendored
@ -41,6 +41,7 @@ Read on to learn:
|
||||
tutorial-contract
|
||||
protocol-state-machines
|
||||
oracles
|
||||
event-scheduling
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
45
docs/build/html/_sources/inthebox.txt
vendored
45
docs/build/html/_sources/inthebox.txt
vendored
@ -1,46 +1,46 @@
|
||||
What's included?
|
||||
================
|
||||
|
||||
The current prototype consists of a small amount of code that defines:
|
||||
The Corda prototype currently includes:
|
||||
|
||||
* Key data structures.
|
||||
* A peer to peer network with message persistence and delivery retries.
|
||||
* Key data structures for defining contracts and states.
|
||||
* Smart contracts:
|
||||
* Cash
|
||||
* Cash obligations
|
||||
* Interest rate swaps
|
||||
* Commercial paper (implemented in both Java and Kotlin for comparison)
|
||||
* Algorithms that work with them, such as serialising, hashing, signing, and verification of the signatures.
|
||||
* Two smart contracts that implement a notion of a cash claim and basic commercial paper (implemented twice, in two
|
||||
different programming languages). These are simplified versions of the real things.
|
||||
* Unit tests that check the algorithms do what is expected, and which verify the behaviour of the smart contracts.
|
||||
* API documentation and tutorials (what you're reading)
|
||||
* A simple standalone node that uses an embedded message queue broker as its P2P messaging layer.
|
||||
* A trading demo that runs the node in either a listening/buying mode, or a connecting/selling mode, and swaps some
|
||||
fake commercial paper assets for some self-issued IOU cash, using a generic *protocol framework*.
|
||||
* It also includes two oracles: one for precise timestamping and another for interest rate swaps.
|
||||
* API documentation and tutorials (what you're reading).
|
||||
* A business process orchestration framework.
|
||||
* Notary infrastructure for precise timestamping, and elimination of double spending without a blockchain.
|
||||
* A simple REST API.
|
||||
|
||||
Some things it does not currently include but should gain later are:
|
||||
|
||||
* Sandboxing, distribution or publication of smart contract code
|
||||
* A peer to peer network
|
||||
* Database persistence
|
||||
* An API for integrating external software
|
||||
* A user interface for administration
|
||||
* Many other things
|
||||
|
||||
You can browse `the JIRA bug tracker <https://r3-cev.atlassian.net/>`_.
|
||||
|
||||
The prototype's goal is rapid exploration of ideas. Therefore in places it takes shortcuts that a production system
|
||||
would not in order to boost productivity:
|
||||
|
||||
* It uses an object graph serialization framework instead of a well specified, vendor neutral protocol.
|
||||
* It uses secp256r1, an obsolete elliptic curve.
|
||||
* It uses the default, out of the box Apache Artemis MQ protocol instead of AMQP/1.0 (although switching should be easy)
|
||||
* There is no inter-node SSL or other encryption yet.
|
||||
|
||||
Contracts
|
||||
---------
|
||||
|
||||
The primary goal of this prototype is to implement various kinds of contracts and verify that useful business logic
|
||||
can be expressed with the data model, developing and refining an API along the way. To that end there are currently
|
||||
two contracts in the repository:
|
||||
four contracts in the repository:
|
||||
|
||||
1. Cash
|
||||
2. Commercial paper
|
||||
3. Nettable obligations
|
||||
4. Interest rate swaps
|
||||
|
||||
``Cash`` implements the idea of a claim on some quantity of deposits at some institutional party, denominated in some currency,
|
||||
identified by some *deposit reference*. A deposit reference is an opaque byte array which is usable by
|
||||
@ -57,12 +57,14 @@ contract is implemented twice, once in Java and once in a language called Kotlin
|
||||
``InterestRateSwap`` implements a vanilla OTC same currency bilateral fixed / floating leg swap. For further details,
|
||||
see :doc:`irs`
|
||||
|
||||
``Obligation`` implements a bilaterally or multi-laterally nettable, fungible obligation that can default.
|
||||
|
||||
Each contract comes with unit tests.
|
||||
|
||||
Kotlin
|
||||
------
|
||||
|
||||
The prototype is written in a language called `Kotlin <https://kotlinlang.org/>`_. Kotlin is a language that targets the JVM
|
||||
Corda is written in a language called `Kotlin <https://kotlinlang.org/>`_. Kotlin is a language that targets the JVM
|
||||
and can be thought of as a simpler Scala, with much better Java interop. It is developed by and has commercial support
|
||||
from JetBrains, the makers of the IntelliJ IDE and other popular developer tools.
|
||||
|
||||
@ -71,11 +73,4 @@ Java for industrial use and as such, the syntax was carefully designed to be rea
|
||||
the language, after only a few minutes of introduction.
|
||||
|
||||
Due to the seamless Java interop the use of Kotlin to extend the platform is *not* required and the tutorial shows how
|
||||
to write contracts in both Kotlin and Java. You can `read more about why Kotlin is a potentially strong successor to Java here <https://medium.com/@octskyward/why-kotlin-is-my-next-programming-language-c25c001e26e3>`_.
|
||||
|
||||
Kotlin programs use the regular Java standard library and ordinary Java frameworks. Frameworks used at this time are:
|
||||
|
||||
* JUnit for unit testing
|
||||
* Kryo for serialisation (this is not intended to be permanent)
|
||||
* Gradle for the build
|
||||
* Guava for a few utility functions
|
||||
to write contracts in both Kotlin and Java. You can `read more about why Kotlin is a potentially strong successor to Java here <https://medium.com/@octskyward/why-kotlin-is-my-next-programming-language-c25c001e26e3>`_.
|
67
docs/build/html/_sources/release-notes.txt
vendored
67
docs/build/html/_sources/release-notes.txt
vendored
@ -6,13 +6,72 @@ Here are brief summaries of what's changed between each snapshot release.
|
||||
Unreleased
|
||||
----------
|
||||
|
||||
Here are changes in git master that haven't yet made it to a snapshot release:
|
||||
There are currently no unreleased changes.
|
||||
|
||||
* The cash contract has moved from com.r3corda.contracts to com.r3corda.contracts.cash.
|
||||
* Amount class is now generic, to support non-currency types (such as assets, or currency with additional information).
|
||||
Milestone 1
|
||||
-----------
|
||||
|
||||
Highlights of this release:
|
||||
|
||||
* Event scheduling. States in the ledger can now request protocols to be invoked at particular times, for states
|
||||
considered relevant by the wallet.
|
||||
* Upgrades to the notary/consensus service support:
|
||||
|
||||
* There is now a way to change the notary controlling a state.
|
||||
* You can pick between validating and non-validating notaries, these let you select your privacy/robustness tradeoff.
|
||||
|
||||
* A new obligation contract that supports bilateral and multilateral netting of obligations, default tracking and
|
||||
more.
|
||||
* Improvements to the financial type system, with core classes and contracts made more generic.
|
||||
* Switch to a better digital signature algorithm: ed25519 instead of the previous JDK default of secp256r1.
|
||||
* A new integration test suite.
|
||||
* A new Java unit testing DSL for contracts, similar in spirit to the one already developed for Kotlin users (which
|
||||
depended on Kotlin specific features).
|
||||
* An experimental module, where developers who want to work with the latest Corda code can check in contracts/cordapp
|
||||
code before it's been fully reviewed. Code in this module has compiler warnings suppressed but we will still make
|
||||
sure it compiles across refactorings.
|
||||
* Persistence improvements: transaction data is now stored to disk and automatic protocol resume is now implemented.
|
||||
* Many smaller bug fixes, cleanups and improvements.
|
||||
|
||||
We have new documentation on:
|
||||
|
||||
* :doc:`event-scheduling`
|
||||
* :doc:`transaction-data-types`
|
||||
* :doc:`consensus`
|
||||
|
||||
Summary of API changes (not exhaustive):
|
||||
|
||||
* Notary/consensus service:
|
||||
|
||||
* ``NotaryService`` is now extensible.
|
||||
* Every ``ContractState`` now has to specify a *participants* field, which is a list of parties that are able to
|
||||
consume this state in a valid transaction. This is used for e.g. making sure all relevant parties obtain the updated
|
||||
state when changing a notary.
|
||||
* Introduced ``TransactionState``, which wraps ``ContractState``, and is used when defining a transaction output.
|
||||
The notary field is moved from ``ContractState`` into ``TransactionState``.
|
||||
* Every transaction now has a *type* field, which specifies custom build & validation rules for that transaction type.
|
||||
Currently two types are supported: General (runs the default build and validation logic) and NotaryChange (
|
||||
contract code is not run during validation, checks that the notary field is the only difference between the
|
||||
inputs and outputs).
|
||||
``TransactionBuilder()`` is now abstract, you should use ``TransactionType.General.Builder()`` for building transactions.
|
||||
|
||||
* The cash contract has moved from ``com.r3corda.contracts`` to ``com.r3corda.contracts.cash``
|
||||
* ``Amount`` class is now generic, to support non-currency types such as physical assets. Where you previously had just
|
||||
``Amount``, you should now use ``Amount<Currency>``.
|
||||
* Refactored the Cash contract to have a new FungibleAsset superclass, to model all countable assets that can be merged
|
||||
and split (currency, barrels of oil, etc.)
|
||||
* Messaging:
|
||||
|
||||
* ``addMessageHandler`` now has a different signature as part of error handling changes.
|
||||
* If you want to return nothing to a protocol, use ``Ack`` instead of ``Unit`` from now on.
|
||||
|
||||
* In the IRS contract, dateOffset is now an integer instead of an enum.
|
||||
* In contracts, you now use ``tx.getInputs`` and ``tx.getOutputs`` instead of ``getInStates`` and ``getOutStates``. This is
|
||||
just a renaming.
|
||||
* A new ``NonEmptySet`` type has been added for cases where you wish to express that you have a collection of unique
|
||||
objects which cannot be empty.
|
||||
* Please use the global ``newSecureRandom()`` function rather than instantiating your own SecureRandom's from now on, as
|
||||
the custom function forces the use of non-blocking random drivers on Linux.
|
||||
|
||||
Milestone 0
|
||||
-----------
|
||||
@ -24,4 +83,4 @@ This is the first release, which includes:
|
||||
* The first version of the protocol/orchestration framework
|
||||
* Some initial support for pluggable consensus mechanisms
|
||||
* Tutorials and documentation explaining how it works
|
||||
* Much more ...
|
||||
* Much more ...
|
||||
|
@ -73,9 +73,7 @@ And in the second run:
|
||||
|
||||
./build/install/r3prototyping/bin/irsdemo --role=NodeB
|
||||
|
||||
The node in the first terminal will complain that it didn't know about nodeB, so restart it. It'll then find the
|
||||
location and identity keys of nodeA and be happy. NodeB also doubles up as the interest rates oracle and you should
|
||||
see some rates data get loaded.
|
||||
NodeB also doubles up as the interest rates oracle and you should see some rates data get loaded.
|
||||
|
||||
Now in the third terminal run:
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
Transaction Data Types
|
||||
======================
|
||||
Data types
|
||||
==========
|
||||
|
||||
There is a large library of data types used in Corda transactions and contract state objects.
|
||||
|
||||
@ -27,15 +27,18 @@ delivered (themselves referring to a currency), an ``Amount`` such as the follow
|
||||
|
||||
Amount<Obligation.State<Currency>>
|
||||
|
||||
Contract State
|
||||
--------------
|
||||
State
|
||||
-----
|
||||
|
||||
A Corda contract is composed of three parts; the executable code, the legal prose, and the state objects that represent
|
||||
the details of the contract (see :doc:`data-model` for further detail). States essentially convert the generic template
|
||||
(code and legal prose) into a specific instance. In a ``WireTransaction``, outputs are provided as ``ContractState``
|
||||
(code and legal prose) into a specific instance. In a ``WireTransaction``, outputs are provided as ``TransactionState``
|
||||
implementations, while the inputs are references to the outputs of a previous transaction. These references are then
|
||||
stored as ``StateRef`` objects, which are converted to ``StateAndRef`` on demand.
|
||||
|
||||
The ``TransactionState`` is a container for a ``ContractState`` (the custom data used by a contract program) and additional
|
||||
platform-level state information, such as the *notary* pointer (see :doc:`consensus`).
|
||||
|
||||
A number of interfaces then extend ``ContractState``, representing standardised functionality for states:
|
||||
|
||||
``OwnableState``
|
||||
@ -64,8 +67,8 @@ interface for its subclasses' state objects to implement. The clear use-case is
|
||||
intended to be readily extensible to cover other assets, for example commodities could be modelled by using a subclass
|
||||
whose state objects include further details (location of the commodity, origin, grade, etc.) as needed.
|
||||
|
||||
Transaction Types
|
||||
-----------------
|
||||
Transaction lifecycle types
|
||||
---------------------------
|
||||
|
||||
The ``WireTransaction`` class contains the core of a transaction without signatures, and with references to attachments
|
||||
in place of the attachments themselves (see also :doc:`data-model`). Once signed these are encapsulated in the
|
||||
@ -84,7 +87,7 @@ for signatures present on the transaction, as well as list of parties for those
|
||||
.. note:: These types are provisional and are likely to change in future, for example to add additional information to
|
||||
``Party``.
|
||||
|
||||
Date Support
|
||||
Date support
|
||||
------------
|
||||
|
||||
There are a number of supporting interfaces and classes for use by contract which deal with dates (especially in the
|
||||
|
438
docs/build/html/api/alltypes/index.html
vendored
438
docs/build/html/api/alltypes/index.html
vendored
@ -56,8 +56,15 @@ I/O), or a mock implementation suitable for unit test environments.</p>
|
||||
<td>
|
||||
<a href="../com.r3corda.protocols/-abstract-request-message/index.html">com.r3corda.protocols.AbstractRequestMessage</a></td>
|
||||
<td>
|
||||
<p>Abstract superclass for request messages sent to services, which includes common
|
||||
fields such as replyTo and replyToTopic.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../protocols/-abstract-state-replacement-protocol/index.html">protocols.AbstractStateReplacementProtocol</a></td>
|
||||
<td>
|
||||
<p>Abstract protocol to be used for replacing one state with another, for example when changing the notary of a state.
|
||||
Notably this requires a one to one replacement of states, states cannot be split, merged or issued as part of these
|
||||
protocols.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -83,6 +90,14 @@ We dont actually do anything with this yet though, so its ignored for now.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.messaging/-ack.html">com.r3corda.core.messaging.Ack</a></td>
|
||||
<td>
|
||||
<p>A general Ack message that conveys no content other than its presence for use when you want an acknowledgement
|
||||
from a recipient. Using <a href="#">Unit</a> can be ambiguous as it is similar to <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/Void.html">Void</a> and so could mean no response.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.utilities/-add-or-remove/index.html">com.r3corda.node.utilities.AddOrRemove</a></td>
|
||||
<td>
|
||||
<p>Enum for when adding/removing something, for example adding or removing an entry in a directory.</p>
|
||||
@ -105,6 +120,12 @@ for ensuring code runs on the right thread, and also for unit testing.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.testing/-always-succeed-contract/index.html">com.r3corda.core.testing.AlwaysSucceedContract</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-amount/index.html">com.r3corda.core.contracts.Amount</a></td>
|
||||
<td>
|
||||
<p>Amount represents a positive quantity of some token (currency, asset, etc.), measured in quantity of the smallest
|
||||
@ -114,6 +135,14 @@ amount used in whatever underlying thing the amount represents.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.protocols/-app-context/index.html">com.r3corda.core.protocols.AppContext</a></td>
|
||||
<td>
|
||||
<p>This is just some way to track what attachments need to be in the class loader, but may later include some app
|
||||
properties loaded from the attachments. And perhaps the authenticated user for an API call?</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.services.messaging/-artemis-messaging-service/index.html">com.r3corda.node.services.messaging.ArtemisMessagingService</a></td>
|
||||
<td>
|
||||
<p>This class implements the <a href="../com.r3corda.core.messaging/-messaging-service/index.html">MessagingService</a> API using Apache Artemis, the successor to their ActiveMQ product.
|
||||
@ -124,14 +153,6 @@ as well.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts.cash/-asset-issuance-definition/index.html">com.r3corda.contracts.cash.AssetIssuanceDefinition</a></td>
|
||||
<td>
|
||||
<p>Subset of cash-like contract state, containing the issuance definition. If these definitions match for two
|
||||
contracts states, those states can be aggregated.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-attachment/index.html">com.r3corda.core.contracts.Attachment</a></td>
|
||||
<td>
|
||||
<p>An attachment is a ZIP (or an optionally signed JAR) that contains one or more files. Attachments are meant to
|
||||
@ -150,8 +171,7 @@ of how attachments are meant to be used include:</p>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.node.services/-attachment-storage/index.html">com.r3corda.core.node.services.AttachmentStorage</a></td>
|
||||
<td>
|
||||
<p>An attachment store records potentially large binary objects, identified by their hash. Note that attachments are
|
||||
immutable and can never be erased once inserted</p>
|
||||
<p>An attachment store records potentially large binary objects, identified by their hash.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -181,6 +201,13 @@ API call from a single party without bi-directional access to the database of of
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-bilateral-nettable-state/index.html">com.r3corda.core.contracts.BilateralNettableState</a></td>
|
||||
<td>
|
||||
<p>Interface for state objects that support being netted with other state objects.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.utilities/-brief-log-formatter/index.html">com.r3corda.core.utilities.BriefLogFormatter</a></td>
|
||||
<td>
|
||||
<p>A Java logging formatter that writes more compact output than the default.</p>
|
||||
@ -209,7 +236,7 @@ no staff are around to handle problems.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts.cash/-cash/index.html">com.r3corda.contracts.cash.Cash</a></td>
|
||||
<a href="../com.r3corda.contracts.asset/-cash/index.html">com.r3corda.contracts.asset.Cash</a></td>
|
||||
<td>
|
||||
<p>A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple
|
||||
input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour
|
||||
@ -246,6 +273,19 @@ the same transaction.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.demos/-cli-params/index.html">com.r3corda.demos.CliParams</a></td>
|
||||
<td>
|
||||
<p>Parsed command line parameters.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.demos/-cli-params-spec/index.html">com.r3corda.demos.CliParamsSpec</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.utilities/java.time.-clock/index.html">java.time.Clock</a> (extensions in package com.r3corda.node.utilities)</td>
|
||||
<td>
|
||||
</td>
|
||||
@ -319,7 +359,9 @@ timestamp attached to the transaction itself i.e. it is NOT necessarily the curr
|
||||
<td>
|
||||
<p>A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk
|
||||
file that the program can use to persist data across transactions. States are immutable: once created they are never
|
||||
updated, instead, any changes must generate a new successor state.</p>
|
||||
updated, instead, any changes must generate a new successor state. States can be updated (consumed) only once: the
|
||||
notary is responsible for ensuring there is no "double spending" by only signing a transaction if the input states
|
||||
are all free.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -331,6 +373,12 @@ updated, instead, any changes must generate a new successor state.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/java.util.-currency/index.html">java.util.Currency</a> (extensions in package com.r3corda.core.contracts)</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.servlets/-data-upload-servlet/index.html">com.r3corda.node.servlets.DataUploadServlet</a></td>
|
||||
<td>
|
||||
<p>Accepts binary streams, finds the right <a href="../com.r3corda.node.services.api/-accepts-file-upload/index.html">AcceptsFileUpload</a> implementor and hands the stream off to it.</p>
|
||||
@ -346,14 +394,6 @@ glue that sits between the network layer and the database layer.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-date-offset/index.html">com.r3corda.core.contracts.DateOffset</a></td>
|
||||
<td>
|
||||
<p>Date offset that the fixing is done prior to the accrual start date.
|
||||
Currently not used in the calculation.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-date-roll-convention/index.html">com.r3corda.core.contracts.DateRollConvention</a></td>
|
||||
<td>
|
||||
<p>This reflects what happens if a date on which a business event is supposed to happen actually falls upon a non-working day
|
||||
@ -402,6 +442,13 @@ implementation of general protocols that manipulate many agreement types.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.serialization/-deserialize-as-kotlin-object-def.html">com.r3corda.core.serialization.DeserializeAsKotlinObjectDef</a></td>
|
||||
<td>
|
||||
<p>Marker interface for kotlin object definitions so that they are deserialized as the singleton instance.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.crypto/-digital-signature/index.html">com.r3corda.core.crypto.DigitalSignature</a></td>
|
||||
<td>
|
||||
<p>A wrapper around a digital signature. The covering field is a generic tag usable by whatever is interpreting the
|
||||
@ -429,12 +476,25 @@ building partially signed transactions.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.testing/-dummy-linear-state/index.html">com.r3corda.core.testing.DummyLinearState</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.crypto/-dummy-public-key/index.html">com.r3corda.core.crypto.DummyPublicKey</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-dummy-state/index.html">com.r3corda.core.contracts.DummyState</a></td>
|
||||
<td>
|
||||
<p>Dummy state for use in testing. Not part of any real contract.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.services.keys/-e2-e-test-key-management-service/index.html">com.r3corda.node.services.keys.E2ETestKeyManagementService</a></td>
|
||||
<td>
|
||||
<p>A simple in-memory KMS that doesnt bother saving keys to disk. A real implementation would:</p>
|
||||
@ -442,6 +502,20 @@ building partially signed transactions.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.serialization/-ed25519-private-key-serializer/index.html">com.r3corda.core.serialization.Ed25519PrivateKeySerializer</a></td>
|
||||
<td>
|
||||
<p>For serialising an ed25519 private key</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.serialization/-ed25519-public-key-serializer/index.html">com.r3corda.core.serialization.Ed25519PublicKeySerializer</a></td>
|
||||
<td>
|
||||
<p>For serialising an ed25519 public key</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.utilities/-emoji/index.html">com.r3corda.core.utilities.Emoji</a></td>
|
||||
<td>
|
||||
<p>A simple wrapper class that contains icons and support for printing them only when were connected to a terminal.</p>
|
||||
@ -546,6 +620,14 @@ Assumes that the rate is valid.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.services/-fixing-session-initiation-handler/index.html">com.r3corda.node.services.FixingSessionInitiationHandler</a></td>
|
||||
<td>
|
||||
<p>This is a temporary handler required for establishing random sessionIDs for the <a href="#">Fixer</a> and <a href="#">Floater</a> as part of
|
||||
running scheduled fixings for the <a href="#">InterestRateSwap</a> contract.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts/-floating-rate/index.html">com.r3corda.contracts.FloatingRate</a></td>
|
||||
<td>
|
||||
<p>The parent class of the Floating rate classes</p>
|
||||
@ -569,7 +651,7 @@ that would divide into (eg annually = 1, semiannual = 2, monthly = 12 etc).</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts.cash/-fungible-asset/index.html">com.r3corda.contracts.cash.FungibleAsset</a></td>
|
||||
<a href="../com.r3corda.contracts.asset/-fungible-asset/index.html">com.r3corda.contracts.asset.FungibleAsset</a></td>
|
||||
<td>
|
||||
<p>Superclass for contracts representing assets which are fungible, countable and issued by a specific party. States
|
||||
contain assets which are equivalent (such as cash of the same currency), so records of their existence can
|
||||
@ -581,15 +663,22 @@ countable, and so on.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts.cash/-fungible-asset-state/index.html">com.r3corda.contracts.cash.FungibleAssetState</a></td>
|
||||
<a href="../com.r3corda.contracts.asset/-fungible-asset-state/index.html">com.r3corda.contracts.asset.FungibleAssetState</a></td>
|
||||
<td>
|
||||
<p>Common elements of cash contract states.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.demos/-i-r-s-demo-node/index.html">com.r3corda.demos.IRSDemoNode</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.demos/-i-r-s-demo-role/index.html">com.r3corda.demos.IRSDemoRole</a></td>
|
||||
<td>
|
||||
<p>Roles. There are 4 modes this demo can be run:</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -610,6 +699,12 @@ service would provide.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.protocols/-illegal-protocol-logic-exception/index.html">com.r3corda.core.protocols.IllegalProtocolLogicException</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.serialization/-immutable-class-serializer/index.html">com.r3corda.core.serialization.ImmutableClassSerializer</a></td>
|
||||
<td>
|
||||
<p>Serializes properties and deserializes by using the constructor. This assumes that all backed properties are
|
||||
@ -656,7 +751,16 @@ testing).</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts.cash/-insufficient-balance-exception/index.html">com.r3corda.contracts.cash.InsufficientBalanceException</a></td>
|
||||
<a href="../com.r3corda.core.testing/-in-memory-wallet-service/index.html">com.r3corda.core.testing.InMemoryWalletService</a></td>
|
||||
<td>
|
||||
<p>This class implements a simple, in memory wallet that tracks states that are owned by us, and also has a convenience
|
||||
method to auto-generate some self-issued cash states that can be used for test trading. A real wallet would persist
|
||||
states relevant to us into a database and once such a wallet is implemented, this scaffolding can be removed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts.asset/-insufficient-balance-exception/index.html">com.r3corda.contracts.asset.InsufficientBalanceException</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
@ -718,6 +822,21 @@ from which the state object is initialised.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-issue-command/index.html">com.r3corda.core.contracts.IssueCommand</a></td>
|
||||
<td>
|
||||
<p>A common issue command, to enforce that issue commands have a nonce value.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-issued/index.html">com.r3corda.core.contracts.Issued</a></td>
|
||||
<td>
|
||||
<p>Definition for an issued product, which can be cash, a cash-like thing, assets, or generally anything else thats
|
||||
quantifiable with integer quantities.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/kotlin.collections.-iterable/index.html">kotlin.collections.Iterable</a> (extensions in package com.r3corda.core.contracts)</td>
|
||||
<td>
|
||||
</td>
|
||||
@ -730,7 +849,32 @@ from which the state object is initialised.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts.cash/kotlin.collections.-iterable/index.html">kotlin.collections.Iterable</a> (extensions in package com.r3corda.contracts.cash)</td>
|
||||
<a href="../com.r3corda.contracts.asset/kotlin.collections.-iterable/index.html">kotlin.collections.Iterable</a> (extensions in package com.r3corda.contracts.asset)</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.internal.testing/kotlin.collections.-iterable/index.html">kotlin.collections.Iterable</a> (extensions in package com.r3corda.node.internal.testing)</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-java-test-helpers/index.html">com.r3corda.core.contracts.JavaTestHelpers</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.testing/-java-test-helpers/index.html">com.r3corda.core.testing.JavaTestHelpers</a></td>
|
||||
<td>
|
||||
<p>JAVA INTEROP. Please keep the following points in mind when extending the Kotlin DSL</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts.testing/-java-test-helpers/index.html">com.r3corda.contracts.testing.JavaTestHelpers</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
@ -758,12 +902,29 @@ call out to a hardware security module that enforces various auditing and freque
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.serialization/-kotlin-object-serializer/index.html">com.r3corda.core.serialization.KotlinObjectSerializer</a></td>
|
||||
<td>
|
||||
<p>Serializer to deserialize kotlin object definitions marked with <a href="../com.r3corda.core.serialization/-deserialize-as-kotlin-object-def.html">DeserializeAsKotlinObjectDef</a>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.testing/-labeled-output/index.html">com.r3corda.core.testing.LabeledOutput</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.testing/-last-line-should-test-for-accept-or-failure.html">com.r3corda.core.testing.LastLineShouldTestForAcceptOrFailure</a></td>
|
||||
<td>
|
||||
<p>If you jumped here from a compiler error make sure the last line of your test tests for a transaction accept or fail
|
||||
This is a dummy type that can only be instantiated by functions in this module. This way we can ensure that all tests
|
||||
will have as the last line either an accept or a failure test. The name is deliberately long to help make sense of
|
||||
the triggered diagnostic</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-ledger-transaction/index.html">com.r3corda.core.contracts.LedgerTransaction</a></td>
|
||||
<td>
|
||||
<p>A LedgerTransaction wraps the data needed to calculate one or more successor states from a set of input states.
|
||||
@ -917,6 +1078,13 @@ This is not an interface because it is too lightweight to bother mocking out.</p
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-move-command/index.html">com.r3corda.core.contracts.MoveCommand</a></td>
|
||||
<td>
|
||||
<p>A common move command for contracts which can change owner.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.utilities/-mutable-clock/index.html">com.r3corda.node.utilities.MutableClock</a></td>
|
||||
<td>
|
||||
<p>An abstract class with helper methods for a type of Clock that might have its concept of "now"
|
||||
@ -932,6 +1100,14 @@ adjusted externally.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-net-type/index.html">com.r3corda.core.contracts.NetType</a></td>
|
||||
<td>
|
||||
<p>Enum for the types of netting that can be applied to state objects. Exact behaviour
|
||||
for each type of netting is left to the contract to determine.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.node.services/-network-cache-error/index.html">com.r3corda.core.node.services.NetworkCacheError</a></td>
|
||||
<td>
|
||||
</td>
|
||||
@ -1026,11 +1202,18 @@ rate fix (e.g. LIBOR, EURIBOR ...).</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.services.events/-node-scheduler-service/index.html">com.r3corda.node.services.events.NodeSchedulerService</a></td>
|
||||
<td>
|
||||
<p>A first pass of a simple <a href="../com.r3corda.core.node.services/-scheduler-service/index.html">SchedulerService</a> that works with <a href="#">MutableClock</a>s for testing, demonstrations and simulations
|
||||
that also encompasses the <a href="#">Wallet</a> observer for processing transactions.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.services.wallet/-node-wallet-service/index.html">com.r3corda.node.services.wallet.NodeWalletService</a></td>
|
||||
<td>
|
||||
<p>This class implements a simple, in memory wallet that tracks states that are owned by us, and also has a convenience
|
||||
method to auto-generate some self-issued cash states that can be used for test trading. A real wallet would persist
|
||||
states relevant to us into a database and once such a wallet is implemented, this scaffolding can be removed.</p>
|
||||
<p>Currently, the node wallet service is just the in-memory wallet service until we have finished evaluating and
|
||||
selecting a persistence layer (probably an ORM over a SQL DB).</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -1038,7 +1221,32 @@ states relevant to us into a database and once such a wallet is implemented, thi
|
||||
<a href="../com.r3corda.core.utilities/-non-empty-set/index.html">com.r3corda.core.utilities.NonEmptySet</a></td>
|
||||
<td>
|
||||
<p>A set which is constrained to ensure it can never be empty. An initial value must be provided at
|
||||
construction, and attempting to remove the last element will cause an IllegalStateException.</p>
|
||||
construction, and attempting to remove the last element will cause an IllegalStateException.
|
||||
The underlying set is exposed for Kryo to access, but should not be accessed directly.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.utilities/-non-empty-set-serializer/index.html">com.r3corda.core.utilities.NonEmptySetSerializer</a></td>
|
||||
<td>
|
||||
<p>Custom serializer which understands it has to read in an item before
|
||||
trying to construct the set.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../protocols/-notary-change-protocol/index.html">protocols.NotaryChangeProtocol</a></td>
|
||||
<td>
|
||||
<p>A protocol to be used for changing a states Notary. This is required since all input states to a transaction
|
||||
must point to the same notary.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.services/-notary-change-service/index.html">com.r3corda.node.services.NotaryChangeService</a></td>
|
||||
<td>
|
||||
<p>A service that monitors the network for requests for changing the notary of a state,
|
||||
and immediately runs the <a href="../protocols/-notary-change-protocol/index.html">NotaryChangeProtocol</a> if the auto-accept criteria are met.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -1074,6 +1282,16 @@ construction, and attempting to remove the last element will cause an IllegalSta
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.contracts.asset/-obligation/index.html">com.r3corda.contracts.asset.Obligation</a></td>
|
||||
<td>
|
||||
<p>An obligation contract commits the obligor to delivering a specified amount of a fungible asset (for example the
|
||||
<a href="../com.r3corda.contracts.asset/-cash/index.html">Cash</a> contract) at a specified future point in time. Settlement transactions may split and merge contracts across
|
||||
multiple input and output states. The goal of this design is to handle amounts owed, and these contracts are expected
|
||||
to be netted/merged, with settlement only for any remainder amount.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.serialization/-opaque-bytes/index.html">com.r3corda.core.serialization.OpaqueBytes</a></td>
|
||||
<td>
|
||||
<p>A simple class that wraps a byte array and makes the equals/hashCode/toString methods work as you actually expect.
|
||||
@ -1197,6 +1415,20 @@ a node crash, how many instances of your protocol there are running and so on.</
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.protocols/-protocol-logic-ref/index.html">com.r3corda.core.protocols.ProtocolLogicRef</a></td>
|
||||
<td>
|
||||
<p>A class representing a <a href="../com.r3corda.core.protocols/-protocol-logic/index.html">ProtocolLogic</a> instance which would be possible to safely pass out of the contract sandbox</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.protocols/-protocol-logic-ref-factory/index.html">com.r3corda.core.protocols.ProtocolLogicRefFactory</a></td>
|
||||
<td>
|
||||
<p>A class for conversion to and from <a href="../com.r3corda.core.protocols/-protocol-logic/index.html">ProtocolLogic</a> and <a href="../com.r3corda.core.protocols/-protocol-logic-ref/index.html">ProtocolLogicRef</a> instances</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.api/-protocol-ref.html">com.r3corda.node.api.ProtocolRef</a></td>
|
||||
<td>
|
||||
<p>Encapsulates the protocol to be instantiated. e.g. TwoPartyTradeProtocol.Buyer.</p>
|
||||
@ -1267,6 +1499,13 @@ for each step.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.node.services/-read-only-transaction-storage/index.html">com.r3corda.core.node.services.ReadOnlyTransactionStorage</a></td>
|
||||
<td>
|
||||
<p>Thread-safe storage of transactions.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.utilities/-recording-map/index.html">com.r3corda.core.utilities.RecordingMap</a></td>
|
||||
<td>
|
||||
<p>A RecordingMap wraps a regular Map<K, V> and records the sequence of gets and puts to it. This is useful in
|
||||
@ -1333,6 +1572,51 @@ again.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-schedulable-state/index.html">com.r3corda.core.contracts.SchedulableState</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-scheduled/index.html">com.r3corda.core.contracts.Scheduled</a></td>
|
||||
<td>
|
||||
<p>Something which is scheduled to happen at a point in time</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-scheduled-activity/index.html">com.r3corda.core.contracts.ScheduledActivity</a></td>
|
||||
<td>
|
||||
<p>This class represents the lifecycle activity that a contract state of type <a href="../com.r3corda.core.contracts/-linear-state/index.html">LinearState</a> would like to perform at a given point in time.
|
||||
e.g. run a fixing protocol</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.services.events/-scheduled-activity-observer/index.html">com.r3corda.node.services.events.ScheduledActivityObserver</a></td>
|
||||
<td>
|
||||
<p>This observes the wallet and schedules and unschedules activities appropriately based on state production and
|
||||
consumption.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-scheduled-state-ref/index.html">com.r3corda.core.contracts.ScheduledStateRef</a></td>
|
||||
<td>
|
||||
<p>Represents a contract state (unconsumed output) of type <a href="../com.r3corda.core.contracts/-linear-state/index.html">LinearState</a> and a point in time that a lifecycle event is expected to take place
|
||||
for that contract state.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.node.services/-scheduler-service/index.html">com.r3corda.core.node.services.SchedulerService</a></td>
|
||||
<td>
|
||||
<p>Provides access to schedule activity at some point in time. This interface might well be expanded to
|
||||
increase the feature set in the future.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.crypto/-secure-hash/index.html">com.r3corda.core.crypto.SecureHash</a></td>
|
||||
<td>
|
||||
</td>
|
||||
@ -1399,6 +1683,14 @@ functionality and you dont want to hard-code which types in the interface.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.protocols/-service-request-message/index.html">com.r3corda.protocols.ServiceRequestMessage</a></td>
|
||||
<td>
|
||||
<p>Abstract superclass for request messages sent to services, which includes common
|
||||
fields such as replyTo and replyToTopic.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.node.services/-service-type/index.html">com.r3corda.core.node.services.ServiceType</a></td>
|
||||
<td>
|
||||
<p>Identifier for service types a node can expose over the network to other peers. These types are placed into network
|
||||
@ -1497,6 +1789,19 @@ transaction defined the state and where in that transaction it was.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../protocols/-state-replacement-exception/index.html">protocols.StateReplacementException</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../protocols/-state-replacement-refused/index.html">protocols.StateReplacementRefused</a></td>
|
||||
<td>
|
||||
<p>Thrown when a participant refuses proposed the state replacement</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.api/-states-query/index.html">com.r3corda.node.api.StatesQuery</a></td>
|
||||
<td>
|
||||
<p>Extremely rudimentary query language which should most likely be replaced with a product</p>
|
||||
@ -1567,6 +1872,13 @@ way that ensures itll be released if theres an exception.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.utilities/-time-window/index.html">com.r3corda.core.utilities.TimeWindow</a></td>
|
||||
<td>
|
||||
<p>A class representing a window in time from a particular instant, lasting a specified duration.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.node.services/-timestamp-checker/index.html">com.r3corda.core.node.services.TimestampChecker</a></td>
|
||||
<td>
|
||||
<p>Checks if the given timestamp falls within the allowed tolerance interval</p>
|
||||
@ -1597,18 +1909,6 @@ then B and C trade with each other, then C and A etc).</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.demos/-trader-demo-protocol-buyer/index.html">com.r3corda.demos.TraderDemoProtocolBuyer</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.demos/-trader-demo-protocol-seller/index.html">com.r3corda.demos.TraderDemoProtocolSeller</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.api/-transaction-build-step/index.html">com.r3corda.node.api.TransactionBuildStep</a></td>
|
||||
<td>
|
||||
<p>Encapsulate a generateXXX method call on a contract.</p>
|
||||
@ -1619,9 +1919,8 @@ then B and C trade with each other, then C and A etc).</p>
|
||||
<a href="../com.r3corda.core.contracts/-transaction-builder/index.html">com.r3corda.core.contracts.TransactionBuilder</a></td>
|
||||
<td>
|
||||
<p>A TransactionBuilder is a transaction class thats mutable (unlike the others which are all immutable). It is
|
||||
intended to be passed around contracts that may edit it by adding new states/commands or modifying the existing set.
|
||||
Then once the states and commands are right, this class can be used as a holding bucket to gather signatures from
|
||||
multiple parties.</p>
|
||||
intended to be passed around contracts that may edit it by adding new states/commands. Then once the states
|
||||
and commands are right, this class can be used as a holding bucket to gather signatures from multiple parties.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -1632,6 +1931,14 @@ multiple parties.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-transaction-for-contract/index.html">com.r3corda.core.contracts.TransactionForContract</a></td>
|
||||
<td>
|
||||
<p>A transaction to be passed as input to a contract verification function. Defines helper methods to
|
||||
simplify verification logic in contracts.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.testing/-transaction-for-test/index.html">com.r3corda.core.testing.TransactionForTest</a></td>
|
||||
<td>
|
||||
</td>
|
||||
@ -1674,6 +1981,14 @@ this subgraph does not contain conflicts and is accepted by the involved contrac
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-transaction-state/index.html">com.r3corda.core.contracts.TransactionState</a></td>
|
||||
<td>
|
||||
<p>A wrapper for <a href="../com.r3corda.core.contracts/-contract-state/index.html">ContractState</a> containing additional platform-level state information.
|
||||
This is the definitive state that is stored on the ledger and used in transaction outputs.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.node.services/-transaction-storage/index.html">com.r3corda.core.node.services.TransactionStorage</a></td>
|
||||
<td>
|
||||
<p>Thread-safe storage of transactions.</p>
|
||||
@ -1681,6 +1996,13 @@ this subgraph does not contain conflicts and is accepted by the involved contrac
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-transaction-type/index.html">com.r3corda.core.contracts.TransactionType</a></td>
|
||||
<td>
|
||||
<p>Defines transaction build & validation logic for a specific transaction type</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-transaction-verification-exception/index.html">com.r3corda.core.contracts.TransactionVerificationException</a></td>
|
||||
<td>
|
||||
</td>
|
||||
@ -1711,6 +2033,13 @@ and seller) and the following steps:</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.node.services/-tx-writable-storage-service/index.html">com.r3corda.core.node.services.TxWritableStorageService</a></td>
|
||||
<td>
|
||||
<p>Storage service, with extensions to allow validated transactions to be added to. For use only within <a href="#">ServiceHub</a>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.contracts/-type-only-command-data/index.html">com.r3corda.core.contracts.TypeOnlyCommandData</a></td>
|
||||
<td>
|
||||
<p>Commands that inherit from this are intended to have no data items: its only their presence that matters.</p>
|
||||
@ -1749,8 +2078,7 @@ first. The wrapper helps you to avoid forgetting this vital step. Things you mig
|
||||
<td>
|
||||
<a href="../com.r3corda.demos.protocols/-update-business-day-protocol/index.html">com.r3corda.demos.protocols.UpdateBusinessDayProtocol</a></td>
|
||||
<td>
|
||||
<p>This is a very temporary, demo-oriented way of initiating processing of temporal events and is not
|
||||
intended as the way things will necessarily be done longer term</p>
|
||||
<p>This is a less temporary, demo-oriented way of initiating processing of temporal events</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -1782,22 +2110,6 @@ about new transactions from our peers and generate new transactions that consume
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.internal.testing/-wallet-filler/index.html">com.r3corda.node.internal.testing.WalletFiller</a></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.node.services.wallet/-wallet-impl/index.html">com.r3corda.node.services.wallet.WalletImpl</a></td>
|
||||
<td>
|
||||
<p>A wallet (name may be temporary) wraps a set of states that are useful for us to keep track of, for instance,
|
||||
because we own them. This class represents an immutable, stable state of a wallet: it is guaranteed not to
|
||||
change out from underneath you, even though the canonical currently-best-known wallet may change as we learn
|
||||
about new transactions from our peers and generate new transactions that consume states ourselves.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../com.r3corda.core.node.services/-wallet-service/index.html">com.r3corda.core.node.services.WalletService</a></td>
|
||||
<td>
|
||||
<p>A <a href="../com.r3corda.core.node.services/-wallet-service/index.html">WalletService</a> is responsible for securely and safely persisting the current state of a wallet to storage. The
|
||||
|
15
docs/build/html/api/com.r3corda.contracts.asset/-c-a-s-h_-p-r-o-g-r-a-m_-i-d.html
vendored
Normal file
15
docs/build/html/api/com.r3corda.contracts.asset/-c-a-s-h_-p-r-o-g-r-a-m_-i-d.html
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>CASH_PROGRAM_ID - </title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="index.html">com.r3corda.contracts.asset</a> / <a href=".">CASH_PROGRAM_ID</a><br/>
|
||||
<br/>
|
||||
<h1>CASH_PROGRAM_ID</h1>
|
||||
<a name="com.r3corda.contracts.asset$CASH_PROGRAM_ID"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">CASH_PROGRAM_ID</span><span class="symbol">: </span><a href="-cash/index.html"><span class="identifier">Cash</span></a></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-exit/-init-.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-exit/-init-.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Exit.<init> - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.asset</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Exit</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">Exit</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.Commands.Exit$<init>(com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))))/amount">amount</span><span class="symbol">:</span> <a href="../../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">)</span></code><br/>
|
||||
<p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-exit/amount.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-exit/amount.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Exit.amount - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.asset</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Exit</a> / <a href=".">amount</a><br/>
|
||||
<br/>
|
||||
<h1>amount</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.Commands.Exit$amount"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">amount</span><span class="symbol">: </span><a href="../../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span></code><br/>
|
||||
Overrides <a href="../../../-fungible-asset/-commands/-exit/amount.html">Exit.amount</a><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
40
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-exit/index.html
vendored
Normal file
40
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-exit/index.html
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Exit - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.asset</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href=".">Exit</a><br/>
|
||||
<br/>
|
||||
<h1>Exit</h1>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Exit</span> <span class="symbol">:</span> <a href="../index.html"><span class="identifier">Commands</span></a><span class="symbol">, </span><a href="../../../-fungible-asset/-commands/-exit/index.html"><span class="identifier">Exit</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
<p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Constructors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-init-.html"><init></a></td>
|
||||
<td>
|
||||
<code><span class="identifier">Exit</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.Commands.Exit$<init>(com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))))/amount">amount</span><span class="symbol">:</span> <a href="../../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">)</span></code><p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Properties</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="amount.html">amount</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">amount</span><span class="symbol">: </span><a href="../../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-issue/-init-.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-issue/-init-.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Issue.<init> - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.asset</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Issue</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">Issue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.Commands.Issue$<init>(kotlin.Long)/nonce">nonce</span><span class="symbol">:</span> <span class="identifier">Long</span> <span class="symbol">=</span> newSecureRandom().nextLong()<span class="symbol">)</span></code><br/>
|
||||
<p>Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
has a unique ID even when there are no inputs.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
40
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-issue/index.html
vendored
Normal file
40
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-issue/index.html
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Issue - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.asset</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href=".">Issue</a><br/>
|
||||
<br/>
|
||||
<h1>Issue</h1>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Issue</span> <span class="symbol">:</span> <a href="../../../-fungible-asset/-commands/-issue.html"><span class="identifier">Issue</span></a><span class="symbol">, </span><a href="../index.html"><span class="identifier">Commands</span></a></code><br/>
|
||||
<p>Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
has a unique ID even when there are no inputs.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Constructors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-init-.html"><init></a></td>
|
||||
<td>
|
||||
<code><span class="identifier">Issue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.Commands.Issue$<init>(kotlin.Long)/nonce">nonce</span><span class="symbol">:</span> <span class="identifier">Long</span> <span class="symbol">=</span> newSecureRandom().nextLong()<span class="symbol">)</span></code><p>Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
has a unique ID even when there are no inputs.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Properties</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="nonce.html">nonce</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">nonce</span><span class="symbol">: </span><span class="identifier">Long</span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-issue/nonce.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-issue/nonce.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Issue.nonce - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.asset</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Issue</a> / <a href=".">nonce</a><br/>
|
||||
<br/>
|
||||
<h1>nonce</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.Commands.Issue$nonce"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">nonce</span><span class="symbol">: </span><span class="identifier">Long</span></code><br/>
|
||||
Overrides <a href="../../../../com.r3corda.core.contracts/-issue-command/nonce.html">IssueCommand.nonce</a><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
20
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-move/-init-.html
vendored
Normal file
20
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-move/-init-.html
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Move.<init> - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.asset</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Move</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">Move</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.Commands.Move$<init>(com.r3corda.core.crypto.SecureHash)/contractHash">contractHash</span><span class="symbol">:</span> <a href="../../../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a><span class="symbol">?</span> <span class="symbol">=</span> null<span class="symbol">)</span></code><br/>
|
||||
<p>A command stating that money has been moved, optionally to fulfil another contract.</p>
|
||||
<h3>Parameters</h3>
|
||||
<a name="contractHash"></a>
|
||||
<code>contractHash</code> - the contract this move is for the attention of. Only that contracts verify function
|
||||
should take the moved states into account when considering whether it is valid. Typically this will be
|
||||
null.<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
18
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-move/contract-hash.html
vendored
Normal file
18
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-move/contract-hash.html
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Move.contractHash - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.asset</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Move</a> / <a href=".">contractHash</a><br/>
|
||||
<br/>
|
||||
<h1>contractHash</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.Commands.Move$contractHash"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">contractHash</span><span class="symbol">: </span><a href="../../../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a><span class="symbol">?</span></code><br/>
|
||||
Overrides <a href="../../../../com.r3corda.core.contracts/-move-command/contract-hash.html">MoveCommand.contractHash</a><br/>
|
||||
<p>Contract code the moved state(s) are for the attention of, for example to indicate that the states are moved in
|
||||
order to settle an obligation contracts state object(s).</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
45
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-move/index.html
vendored
Normal file
45
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/-move/index.html
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Move - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.asset</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href=".">Move</a><br/>
|
||||
<br/>
|
||||
<h1>Move</h1>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Move</span> <span class="symbol">:</span> <a href="../../../-fungible-asset/-commands/-move.html"><span class="identifier">Move</span></a><span class="symbol">, </span><a href="../index.html"><span class="identifier">Commands</span></a></code><br/>
|
||||
<p>A command stating that money has been moved, optionally to fulfil another contract.</p>
|
||||
<h3>Parameters</h3>
|
||||
<a name="contractHash"></a>
|
||||
<code>contractHash</code> - the contract this move is for the attention of. Only that contracts verify function
|
||||
should take the moved states into account when considering whether it is valid. Typically this will be
|
||||
null.<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Constructors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-init-.html"><init></a></td>
|
||||
<td>
|
||||
<code><span class="identifier">Move</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.Commands.Move$<init>(com.r3corda.core.crypto.SecureHash)/contractHash">contractHash</span><span class="symbol">:</span> <a href="../../../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a><span class="symbol">?</span> <span class="symbol">=</span> null<span class="symbol">)</span></code><p>A command stating that money has been moved, optionally to fulfil another contract.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Properties</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="contract-hash.html">contractHash</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">contractHash</span><span class="symbol">: </span><a href="../../../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a><span class="symbol">?</span></code><p>Contract code the moved state(s) are for the attention of, for example to indicate that the states are moved in
|
||||
order to settle an obligation contracts state object(s).</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
70
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/index.html
vendored
Normal file
70
docs/build/html/api/com.r3corda.contracts.asset/-cash/-commands/index.html
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href=".">Commands</a><br/>
|
||||
<br/>
|
||||
<h1>Commands</h1>
|
||||
<code><span class="keyword">interface </span><span class="identifier">Commands</span> <span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-command-data.html"><span class="identifier">CommandData</span></a></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Types</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-exit/index.html">Exit</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Exit</span> <span class="symbol">:</span> <span class="identifier">Commands</span><span class="symbol">, </span><a href="../../-fungible-asset/-commands/-exit/index.html"><span class="identifier">Exit</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-issue/index.html">Issue</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Issue</span> <span class="symbol">:</span> <a href="../../-fungible-asset/-commands/-issue.html"><span class="identifier">Issue</span></a><span class="symbol">, </span><span class="identifier">Commands</span></code><p>Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
has a unique ID even when there are no inputs.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-move/index.html">Move</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Move</span> <span class="symbol">:</span> <a href="../../-fungible-asset/-commands/-move.html"><span class="identifier">Move</span></a><span class="symbol">, </span><span class="identifier">Commands</span></code><p>A command stating that money has been moved, optionally to fulfil another contract.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Inheritors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-exit/index.html">Exit</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Exit</span> <span class="symbol">:</span> <span class="identifier">Commands</span><span class="symbol">, </span><a href="../../-fungible-asset/-commands/-exit/index.html"><span class="identifier">Exit</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-issue/index.html">Issue</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Issue</span> <span class="symbol">:</span> <a href="../../-fungible-asset/-commands/-issue.html"><span class="identifier">Issue</span></a><span class="symbol">, </span><span class="identifier">Commands</span></code><p>Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
has a unique ID even when there are no inputs.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-move/index.html">Move</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Move</span> <span class="symbol">:</span> <a href="../../-fungible-asset/-commands/-move.html"><span class="identifier">Move</span></a><span class="symbol">, </span><span class="identifier">Commands</span></code><p>A command stating that money has been moved, optionally to fulfil another contract.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
25
docs/build/html/api/com.r3corda.contracts.asset/-cash/-init-.html
vendored
Normal file
25
docs/build/html/api/com.r3corda.contracts.asset/-cash/-init-.html
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.<init> - </title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts.asset</a> / <a href="index.html">Cash</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">Cash</span><span class="symbol">(</span><span class="symbol">)</span></code><br/>
|
||||
<p>A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple
|
||||
input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour
|
||||
(a blend of issuer+depositRef) and you couldnt merge outputs of two colours together, but you COULD put them in
|
||||
the same transaction.</p>
|
||||
<p>The goal of this design is to ensure that money can be withdrawn from the ledger easily: if you receive some money
|
||||
via this contract, you always know where to go in order to extract it from the R3 ledger, no matter how many hands
|
||||
it has passed through in the intervening time.</p>
|
||||
<p>At the same time, other contracts that just want money and dont care much who is currently holding it in their
|
||||
vaults can ignore the issuer/depositRefs and just examine the amount fields.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
18
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/-init-.html
vendored
Normal file
18
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/-init-.html
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.<init> - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">State</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/deposit">deposit</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-party-and-reference/index.html"><span class="identifier">PartyAndReference</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/amount">amount</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/owner">owner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<code><span class="identifier">State</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey)/amount">amount</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey)/owner">owner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span></code><br/>
|
||||
<p>A state representing a cash claim against some party</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/amount.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/amount.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.amount - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">amount</a><br/>
|
||||
<br/>
|
||||
<h1>amount</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$amount"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">amount</span><span class="symbol">: </span><a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span></code><br/>
|
||||
Overrides <a href="../../-fungible-asset/-state/amount.html">State.amount</a><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
43
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/contract.html
vendored
Normal file
43
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/contract.html
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.contract - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">contract</a><br/>
|
||||
<br/>
|
||||
<h1>contract</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$contract"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">contract</span><span class="symbol">: </span><a href="../index.html"><span class="identifier">Cash</span></a></code><br/>
|
||||
Overrides <a href="../../../com.r3corda.core.contracts/-contract-state/contract.html">ContractState.contract</a><br/>
|
||||
<p>An instance of the contract class that will verify this state.</p>
|
||||
<h1>Discussion</h1>
|
||||
<p>This field is not the final design, its just a piece of temporary scaffolding. Once the contract sandbox is
|
||||
further along, this field will become a description of which attachments are acceptable for defining the
|
||||
contract.</p>
|
||||
<p>Recall that an attachment is a zip file that can be referenced from any transaction. The contents of the
|
||||
attachments are merged together and cannot define any overlapping files, thus for any given transaction there
|
||||
is a miniature file system in which each file can be precisely mapped to the defining attachment.</p>
|
||||
<p>Attachments may contain many things (data files, legal documents, etc) but mostly they contain JVM bytecode.
|
||||
The class files inside define not only <a href="../../../com.r3corda.core.contracts/-contract/index.html">Contract</a> implementations but also the classes that define the states.
|
||||
Within the rest of a transaction, user-providable components are referenced by name only.</p>
|
||||
<p>This means that a smart contract in Corda does two things:</p>
|
||||
<ol><li><p>Define the data structures that compose the ledger (the states)</p>
|
||||
</li><li><p>Define the rules for updating those structures</p>
|
||||
</li></ol><p>The first is merely a utility role ... in theory contract code could manually parse byte streams by hand.
|
||||
The second is vital to the integrity of the ledger. So this field needs to be able to express constraints like:</p>
|
||||
<ul><li><p>Only attachment 733c350f396a727655be1363c06635ba355036bd54a5ed6e594fd0b5d05f42f6 may be used with this state.</p>
|
||||
</li><li><p>Any attachment signed by public key 2d1ce0e330c52b8055258d776c40 may be used with this state.</p>
|
||||
</li><li><p>Attachments (1, 2, 3) may all be used with this state.</p>
|
||||
</li></ul><p>and so on. In this way it becomes possible for the business logic governing a state to be evolved, if the
|
||||
constraints are flexible enough.</p>
|
||||
<p>Because contract classes often also define utilities that generate relevant transactions, and because attachments
|
||||
cannot know their own hashes, we will have to provide various utilities to assist with obtaining the right
|
||||
code constraints from within the contract code itself.</p>
|
||||
<p>TODO: Implement the above description. See COR-226</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
20
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/deposit.html
vendored
Normal file
20
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/deposit.html
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.deposit - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">deposit</a><br/>
|
||||
<br/>
|
||||
<h1>deposit</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$deposit"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">deposit</span><span class="symbol">: </span><a href="../../../com.r3corda.core.contracts/-party-and-reference/index.html"><span class="identifier">PartyAndReference</span></a></code><br/>
|
||||
Overrides <a href="../../-fungible-asset/-state/deposit.html">State.deposit</a><br/>
|
||||
<p>Where the underlying currency backing this ledger entry can be found (propagated)</p>
|
||||
<p><strong>Getter</strong><br/>
|
||||
<p>Where the underlying currency backing this ledger entry can be found (propagated)</p>
|
||||
</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
132
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/index.html
vendored
Normal file
132
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/index.html
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href=".">State</a><br/>
|
||||
<br/>
|
||||
<h1>State</h1>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">State</span> <span class="symbol">:</span> <a href="../../-fungible-asset/-state/index.html"><span class="identifier">State</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
<p>A state representing a cash claim against some party</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Constructors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-init-.html"><init></a></td>
|
||||
<td>
|
||||
<code><span class="identifier">State</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/deposit">deposit</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-party-and-reference/index.html"><span class="identifier">PartyAndReference</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/amount">amount</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/owner">owner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span></code><code><span class="identifier">State</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey)/amount">amount</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$<init>(com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey)/owner">owner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span></code><p>A state representing a cash claim against some party</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Properties</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="amount.html">amount</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">amount</span><span class="symbol">: </span><a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="contract.html">contract</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">contract</span><span class="symbol">: </span><a href="../index.html"><span class="identifier">Cash</span></a></code><p>An instance of the contract class that will verify this state.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="deposit.html">deposit</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">deposit</span><span class="symbol">: </span><a href="../../../com.r3corda.core.contracts/-party-and-reference/index.html"><span class="identifier">PartyAndReference</span></a></code><p>Where the underlying currency backing this ledger entry can be found (propagated)</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="issuance-def.html">issuanceDef</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">issuanceDef</span><span class="symbol">: </span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="owner.html">owner</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">owner</span><span class="symbol">: </span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a></code><p>There must be a MoveCommand signed by this key to claim the amount</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="participants.html">participants</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">participants</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">></span></code><p>A <emph>participant</emph> is any party that is able to consume this state in a valid transaction.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="product-amount.html">productAmount</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">productAmount</span><span class="symbol">: </span><a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Functions</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="move.html">move</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">fun </span><span class="identifier">move</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$move(com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/newAmount">newAmount</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$move(com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/newOwner">newOwner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span><span class="symbol">: </span><a href="../../-fungible-asset/-state/index.html"><span class="identifier">State</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="to-string.html">toString</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">fun </span><span class="identifier">toString</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">String</span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="with-new-owner.html">withNewOwner</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">fun </span><span class="identifier">withNewOwner</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$withNewOwner(java.security.PublicKey)/newOwner">newOwner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier"><ERROR CLASS></span></code><p>Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Extension Functions</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../../../com.r3corda.contracts.testing/issued by.html">issued by</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">infix</span> <span class="keyword">fun </span><span class="identifier">State</span><span class="symbol">.</span><span class="identifier">issued by</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.testing$issued by(com.r3corda.contracts.asset.Cash.State, com.r3corda.core.crypto.Party)/party">party</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">State</span></code><br/>
|
||||
<code><span class="keyword">infix</span> <span class="keyword">fun </span><span class="identifier">State</span><span class="symbol">.</span><span class="identifier">issued by</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.testing$issued by(com.r3corda.contracts.asset.Cash.State, com.r3corda.core.contracts.PartyAndReference)/deposit">deposit</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-party-and-reference/index.html"><span class="identifier">PartyAndReference</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">State</span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../../../com.r3corda.contracts.testing/owned by.html">owned by</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">infix</span> <span class="keyword">fun </span><span class="identifier">State</span><span class="symbol">.</span><span class="identifier">owned by</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.testing$owned by(com.r3corda.contracts.asset.Cash.State, java.security.PublicKey)/owner">owner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier"><ERROR CLASS></span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../../../com.r3corda.contracts.testing/with deposit.html">with deposit</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">infix</span> <span class="keyword">fun </span><span class="identifier">State</span><span class="symbol">.</span><span class="identifier">with deposit</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.testing$with deposit(com.r3corda.contracts.asset.Cash.State, com.r3corda.core.contracts.PartyAndReference)/deposit">deposit</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-party-and-reference/index.html"><span class="identifier">PartyAndReference</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">State</span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../../../com.r3corda.contracts.testing/with notary.html">with notary</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">infix</span> <span class="keyword">fun </span><span class="identifier">State</span><span class="symbol">.</span><span class="identifier">with notary</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.testing$with notary(com.r3corda.contracts.asset.Cash.State, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><a href="../../../com.r3corda.core.contracts/-transaction-state/index.html"><span class="identifier">TransactionState</span></a><span class="symbol"><</span><span class="identifier">State</span><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/issuance-def.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/issuance-def.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.issuanceDef - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">issuanceDef</a><br/>
|
||||
<br/>
|
||||
<h1>issuanceDef</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$issuanceDef"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">issuanceDef</span><span class="symbol">: </span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
Overrides <a href="../../-fungible-asset-state/issuance-def.html">FungibleAssetState.issuanceDef</a><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
15
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/move.html
vendored
Normal file
15
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/move.html
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.move - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">move</a><br/>
|
||||
<br/>
|
||||
<h1>move</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$move(com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)"></a>
|
||||
<code><span class="keyword">fun </span><span class="identifier">move</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$move(com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/newAmount">newAmount</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$move(com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey)/newOwner">newOwner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span><span class="symbol">: </span><a href="../../-fungible-asset/-state/index.html"><span class="identifier">State</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
17
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/owner.html
vendored
Normal file
17
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/owner.html
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.owner - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">owner</a><br/>
|
||||
<br/>
|
||||
<h1>owner</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$owner"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">owner</span><span class="symbol">: </span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a></code><br/>
|
||||
Overrides <a href="../../-fungible-asset/-state/owner.html">State.owner</a><br/>
|
||||
<p>There must be a MoveCommand signed by this key to claim the amount</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
34
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/participants.html
vendored
Normal file
34
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/participants.html
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.participants - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">participants</a><br/>
|
||||
<br/>
|
||||
<h1>participants</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$participants"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">participants</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">></span></code><br/>
|
||||
Overrides <a href="../../../com.r3corda.core.contracts/-contract-state/participants.html">ContractState.participants</a><br/>
|
||||
<p>A <emph>participant</emph> is any party that is able to consume this state in a valid transaction.</p>
|
||||
<p>The list of participants is required for certain types of transactions. For example, when changing the notary
|
||||
for this state (<a href="../../../com.r3corda.core.contracts/-transaction-type/-notary-change/index.html">TransactionType.NotaryChange</a>), every participants has to be involved and approve the transaction
|
||||
so that they receive the updated state, and dont end up in a situation where they can no longer use a state
|
||||
they possess, since someone consumed that state during the notary change process.</p>
|
||||
<p>The participants list should normally be derived from the contents of the state. E.g. for <a href="../index.html">Cash</a> the participants
|
||||
list should just contain the owner.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<p><strong>Getter</strong><br/>
|
||||
<p>A <emph>participant</emph> is any party that is able to consume this state in a valid transaction.</p>
|
||||
<p>The list of participants is required for certain types of transactions. For example, when changing the notary
|
||||
for this state (<a href="../../../com.r3corda.core.contracts/-transaction-type/-notary-change/index.html">TransactionType.NotaryChange</a>), every participants has to be involved and approve the transaction
|
||||
so that they receive the updated state, and dont end up in a situation where they can no longer use a state
|
||||
they possess, since someone consumed that state during the notary change process.</p>
|
||||
<p>The participants list should normally be derived from the contents of the state. E.g. for <a href="../index.html">Cash</a> the participants
|
||||
list should just contain the owner.</p>
|
||||
</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/product-amount.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/product-amount.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.productAmount - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">productAmount</a><br/>
|
||||
<br/>
|
||||
<h1>productAmount</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$productAmount"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">productAmount</span><span class="symbol">: </span><a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
Overrides <a href="../../-fungible-asset-state/product-amount.html">FungibleAssetState.productAmount</a><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
15
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/to-string.html
vendored
Normal file
15
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/to-string.html
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.toString - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">toString</a><br/>
|
||||
<br/>
|
||||
<h1>toString</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$toString()"></a>
|
||||
<code><span class="keyword">fun </span><span class="identifier">toString</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">String</span></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
17
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/with-new-owner.html
vendored
Normal file
17
docs/build/html/api/com.r3corda.contracts.asset/-cash/-state/with-new-owner.html
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.withNewOwner - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.asset</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">withNewOwner</a><br/>
|
||||
<br/>
|
||||
<h1>withNewOwner</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash.State$withNewOwner(java.security.PublicKey)"></a>
|
||||
<code><span class="keyword">fun </span><span class="identifier">withNewOwner</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash.State$withNewOwner(java.security.PublicKey)/newOwner">newOwner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier"><ERROR CLASS></span></code><br/>
|
||||
Overrides <a href="../../../com.r3corda.core.contracts/-ownable-state/with-new-owner.html">OwnableState.withNewOwner</a><br/>
|
||||
<p>Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
21
docs/build/html/api/com.r3corda.contracts.asset/-cash/generate-issue.html
vendored
Normal file
21
docs/build/html/api/com.r3corda.contracts.asset/-cash/generate-issue.html
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.generateIssue - </title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts.asset</a> / <a href="index.html">Cash</a> / <a href=".">generateIssue</a><br/>
|
||||
<br/>
|
||||
<h1>generateIssue</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)"></a>
|
||||
<code><span class="keyword">fun </span><span class="identifier">generateIssue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/tx">tx</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/tokenDef">tokenDef</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/pennies">pennies</span><span class="symbol">:</span> <span class="identifier">Long</span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/owner">owner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span> <a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
|
||||
<p>Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<a name="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, com.r3corda.core.crypto.Party)"></a>
|
||||
<code><span class="keyword">fun </span><span class="identifier">generateIssue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, com.r3corda.core.crypto.Party)/tx">tx</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, com.r3corda.core.crypto.Party)/amount">amount</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, com.r3corda.core.crypto.Party)/owner">owner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span> <a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
|
||||
<p>Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
28
docs/build/html/api/com.r3corda.contracts.asset/-cash/generate-spend.html
vendored
Normal file
28
docs/build/html/api/com.r3corda.contracts.asset/-cash/generate-spend.html
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.generateSpend - </title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts.asset</a> / <a href="index.html">Cash</a> / <a href=".">generateSpend</a><br/>
|
||||
<br/>
|
||||
<h1>generateSpend</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))))"></a>
|
||||
<code><span class="keyword">fun </span><span class="identifier">generateSpend</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))))/tx">tx</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))))/amount">amount</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))))/to">to</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))))/cashStates">cashStates</span><span class="symbol">:</span> <span class="identifier">List</span><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-state-and-ref/index.html"><span class="identifier">StateAndRef</span></a><span class="symbol"><</span><a href="-state/index.html"><span class="identifier">State</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">></span></code><br/>
|
||||
<p>Generate a transaction that consumes one or more of the given input states to move money to the given pubkey.
|
||||
Note that the wallet list is not updated: its up to you to do that.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<a name="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))"></a>
|
||||
<code><span class="keyword">fun </span><span class="identifier">generateSpend</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/tx">tx</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/amount">amount</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/to">to</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/cashStates">cashStates</span><span class="symbol">:</span> <span class="identifier">List</span><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-state-and-ref/index.html"><span class="identifier">StateAndRef</span></a><span class="symbol"><</span><a href="-state/index.html"><span class="identifier">State</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/onlyFromParties">onlyFromParties</span><span class="symbol">:</span> <span class="identifier">Set</span><span class="symbol"><</span><a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">></span><span class="symbol">?</span> <span class="symbol">=</span> null<span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">></span></code><br/>
|
||||
<p>Generate a transaction that consumes one or more of the given input states to move money to the given pubkey.
|
||||
Note that the wallet list is not updated: its up to you to do that.</p>
|
||||
<h3>Parameters</h3>
|
||||
<a name="onlyFromParties"></a>
|
||||
<code>onlyFromParties</code> - if non-null, the wallet will be filtered to only include cash states issued by the set
|
||||
of given parties. This can be useful if the party youre trying to pay has expectations
|
||||
about which type of cash claims they are willing to accept.<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
104
docs/build/html/api/com.r3corda.contracts.asset/-cash/index.html
vendored
Normal file
104
docs/build/html/api/com.r3corda.contracts.asset/-cash/index.html
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash - </title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts.asset</a> / <a href=".">Cash</a><br/>
|
||||
<br/>
|
||||
<h1>Cash</h1>
|
||||
<code><span class="keyword">class </span><span class="identifier">Cash</span> <span class="symbol">:</span> <a href="../-fungible-asset/index.html"><span class="identifier">FungibleAsset</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
<p>A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple
|
||||
input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour
|
||||
(a blend of issuer+depositRef) and you couldnt merge outputs of two colours together, but you COULD put them in
|
||||
the same transaction.</p>
|
||||
<p>The goal of this design is to ensure that money can be withdrawn from the ledger easily: if you receive some money
|
||||
via this contract, you always know where to go in order to extract it from the R3 ledger, no matter how many hands
|
||||
it has passed through in the intervening time.</p>
|
||||
<p>At the same time, other contracts that just want money and dont care much who is currently holding it in their
|
||||
vaults can ignore the issuer/depositRefs and just examine the amount fields.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Types</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-commands/index.html">Commands</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">interface </span><span class="identifier">Commands</span> <span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-command-data.html"><span class="identifier">CommandData</span></a></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-state/index.html">State</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">State</span> <span class="symbol">:</span> <a href="../-fungible-asset/-state/index.html"><span class="identifier">State</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><p>A state representing a cash claim against some party</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Constructors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-init-.html"><init></a></td>
|
||||
<td>
|
||||
<code><span class="identifier">Cash</span><span class="symbol">(</span><span class="symbol">)</span></code><p>A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple
|
||||
input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour
|
||||
(a blend of issuer+depositRef) and you couldnt merge outputs of two colours together, but you COULD put them in
|
||||
the same transaction.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Properties</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="legal-contract-reference.html">legalContractReference</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">legalContractReference</span><span class="symbol">: </span><a href="../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a></code><p>TODO:</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Functions</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="generate-issue.html">generateIssue</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">fun </span><span class="identifier">generateIssue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/tx">tx</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/tokenDef">tokenDef</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/pennies">pennies</span><span class="symbol">:</span> <span class="identifier">Long</span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/owner">owner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Issued((java.util.Currency)), kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span> <a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><p>Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.</p>
|
||||
<code><span class="keyword">fun </span><span class="identifier">generateIssue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, com.r3corda.core.crypto.Party)/tx">tx</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, com.r3corda.core.crypto.Party)/amount">amount</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, com.r3corda.core.crypto.Party)/owner">owner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span> <a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><p>Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="generate-spend.html">generateSpend</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">fun </span><span class="identifier">generateSpend</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))))/tx">tx</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))))/amount">amount</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))))/to">to</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((com.r3corda.core.contracts.Issued((java.util.Currency)))), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))))/cashStates">cashStates</span><span class="symbol">:</span> <span class="identifier">List</span><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-state-and-ref/index.html"><span class="identifier">StateAndRef</span></a><span class="symbol"><</span><a href="-state/index.html"><span class="identifier">State</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">></span></code><br/>
|
||||
<code><span class="keyword">fun </span><span class="identifier">generateSpend</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/tx">tx</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/amount">amount</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/to">to</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/cashStates">cashStates</span><span class="symbol">:</span> <span class="identifier">List</span><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-state-and-ref/index.html"><span class="identifier">StateAndRef</span></a><span class="symbol"><</span><a href="-state/index.html"><span class="identifier">State</span></a><span class="symbol">></span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.asset.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/onlyFromParties">onlyFromParties</span><span class="symbol">:</span> <span class="identifier">Set</span><span class="symbol"><</span><a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">></span><span class="symbol">?</span> <span class="symbol">=</span> null<span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">></span></code><p>Generate a transaction that consumes one or more of the given input states to move money to the given pubkey.
|
||||
Note that the wallet list is not updated: its up to you to do that.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Inherited Functions</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../-fungible-asset/verify.html">verify</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">open</span> <span class="keyword">fun </span><span class="identifier">verify</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.FungibleAsset$verify(com.r3corda.core.contracts.TransactionForContract)/tx">tx</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-transaction-for-contract/index.html"><span class="identifier">TransactionForContract</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><p>This is the function EVERYONE runs</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
25
docs/build/html/api/com.r3corda.contracts.asset/-cash/legal-contract-reference.html
vendored
Normal file
25
docs/build/html/api/com.r3corda.contracts.asset/-cash/legal-contract-reference.html
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.legalContractReference - </title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts.asset</a> / <a href="index.html">Cash</a> / <a href=".">legalContractReference</a><br/>
|
||||
<br/>
|
||||
<h1>legalContractReference</h1>
|
||||
<a name="com.r3corda.contracts.asset.Cash$legalContractReference"></a>
|
||||
<code><span class="keyword">val </span><span class="identifier">legalContractReference</span><span class="symbol">: </span><a href="../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a></code><br/>
|
||||
Overrides <a href="../../com.r3corda.core.contracts/-contract/legal-contract-reference.html">Contract.legalContractReference</a><br/>
|
||||
<p>TODO:</p>
|
||||
<ol><li><p>hash should be of the contents, not the URI</p>
|
||||
</li><li><p>allow the content to be specified at time of instance creation?</p>
|
||||
</li></ol><p>Motivation: its the difference between a state object referencing a programRef, which references a
|
||||
legalContractReference and a state object which directly references both. The latter allows the legal wording
|
||||
to evolve without requiring code changes. But creates a risk that users create objects governed by a program
|
||||
that is inconsistent with the legal contract</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
106
docs/build/html/api/com.r3corda.contracts.asset/-fungible-asset-state/index.html
vendored
Normal file
106
docs/build/html/api/com.r3corda.contracts.asset/-fungible-asset-state/index.html
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>FungibleAssetState - </title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts.asset</a> / <a href=".">FungibleAssetState</a><br/>
|
||||
<br/>
|
||||
<h1>FungibleAssetState</h1>
|
||||
<code><span class="keyword">interface </span><span class="identifier">FungibleAssetState</span><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">, </span><span class="identifier">I</span><span class="symbol">></span> <span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-ownable-state/index.html"><span class="identifier">OwnableState</span></a></code><br/>
|
||||
<p>Common elements of cash contract states.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Properties</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="issuance-def.html">issuanceDef</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">issuanceDef</span><span class="symbol">: </span><span class="identifier">I</span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="product-amount.html">productAmount</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">productAmount</span><span class="symbol">: </span><a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Inherited Properties</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../../com.r3corda.core.contracts/-ownable-state/owner.html">owner</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">owner</span><span class="symbol">: </span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a></code><p>There must be a MoveCommand signed by this key to claim the amount</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Functions</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="move.html">move</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">abstract</span> <span class="keyword">fun </span><span class="identifier">move</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.FungibleAssetState$move(com.r3corda.core.contracts.Amount((com.r3corda.contracts.asset.FungibleAssetState.T)), java.security.PublicKey)/newAmount">newAmount</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.FungibleAssetState$move(com.r3corda.core.contracts.Amount((com.r3corda.contracts.asset.FungibleAssetState.T)), java.security.PublicKey)/newOwner">newOwner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">FungibleAssetState</span><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">,</span> <span class="identifier">I</span><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Inherited Functions</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../../com.r3corda.core.contracts/-ownable-state/with-new-owner.html">withNewOwner</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">abstract</span> <span class="keyword">fun </span><span class="identifier">withNewOwner</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.core.contracts.OwnableState$withNewOwner(java.security.PublicKey)/newOwner">newOwner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier"><ERROR CLASS></span><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-command-data.html"><span class="identifier">CommandData</span></a><span class="symbol">,</span> <a href="../../com.r3corda.core.contracts/-ownable-state/index.html"><span class="identifier">OwnableState</span></a><span class="symbol">></span></code><p>Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Extension Functions</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../../com.r3corda.core.contracts/hash.html">hash</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">fun </span><a href="../../com.r3corda.core.contracts/-contract-state/index.html"><span class="identifier">ContractState</span></a><span class="symbol">.</span><span class="identifier">hash</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><a href="../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a></code><p>Returns the SHA-256 hash of the serialised contents of this state (not cached)</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../../com.r3corda.contracts.testing/with notary.html">with notary</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">infix</span> <span class="keyword">fun </span><a href="../../com.r3corda.core.contracts/-contract-state/index.html"><span class="identifier">ContractState</span></a><span class="symbol">.</span><span class="identifier">with notary</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.testing$with notary(com.r3corda.core.contracts.ContractState, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span> <a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><a href="../../com.r3corda.core.contracts/-transaction-state/index.html"><span class="identifier">TransactionState</span></a><span class="symbol"><</span><a href="../../com.r3corda.core.contracts/-contract-state/index.html"><span class="identifier">ContractState</span></a><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Inheritors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../-fungible-asset/-state/index.html">State</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">interface </span><span class="identifier">State</span><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">></span> <span class="symbol">:</span> <span class="identifier">FungibleAssetState</span><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">,</span> <a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">></span><span class="symbol">></span></code><p>A state representing a cash claim against some party</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../-obligation/-state/index.html">State</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">State</span><span class="symbol"><</span><span class="identifier">P</span><span class="symbol">></span> <span class="symbol">:</span> <span class="identifier">FungibleAssetState</span><span class="symbol"><</span><span class="identifier">P</span><span class="symbol">,</span> <a href="../-obligation/-issuance-definition/index.html"><span class="identifier">IssuanceDefinition</span></a><span class="symbol"><</span><span class="identifier">P</span><span class="symbol">></span><span class="symbol">></span><span class="symbol">, </span><a href="../../com.r3corda.core.contracts/-bilateral-nettable-state/index.html"><span class="identifier">BilateralNettableState</span></a><span class="symbol"><</span><a href="../-obligation/-state/index.html"><span class="identifier">State</span></a><span class="symbol"><</span><span class="identifier">P</span><span class="symbol">></span><span class="symbol">></span></code><p>A state representing the obligation of one party (obligor) to deliver a specified number of
|
||||
units of an underlying asset (described as issuanceDef.acceptableIssuedProducts) to the beneficiary
|
||||
no later than the specified time.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
15
docs/build/html/api/com.r3corda.contracts.asset/-fungible-asset-state/issuance-def.html
vendored
Normal file
15
docs/build/html/api/com.r3corda.contracts.asset/-fungible-asset-state/issuance-def.html
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>FungibleAssetState.issuanceDef - </title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts.asset</a> / <a href="index.html">FungibleAssetState</a> / <a href=".">issuanceDef</a><br/>
|
||||
<br/>
|
||||
<h1>issuanceDef</h1>
|
||||
<a name="com.r3corda.contracts.asset.FungibleAssetState$issuanceDef"></a>
|
||||
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">issuanceDef</span><span class="symbol">: </span><span class="identifier">I</span></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
15
docs/build/html/api/com.r3corda.contracts.asset/-fungible-asset-state/move.html
vendored
Normal file
15
docs/build/html/api/com.r3corda.contracts.asset/-fungible-asset-state/move.html
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>FungibleAssetState.move - </title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts.asset</a> / <a href="index.html">FungibleAssetState</a> / <a href=".">move</a><br/>
|
||||
<br/>
|
||||
<h1>move</h1>
|
||||
<a name="com.r3corda.contracts.asset.FungibleAssetState$move(com.r3corda.core.contracts.Amount((com.r3corda.contracts.asset.FungibleAssetState.T)), java.security.PublicKey)"></a>
|
||||
<code><span class="keyword">abstract</span> <span class="keyword">fun </span><span class="identifier">move</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.asset.FungibleAssetState$move(com.r3corda.core.contracts.Amount((com.r3corda.contracts.asset.FungibleAssetState.T)), java.security.PublicKey)/newAmount">newAmount</span><span class="symbol">:</span> <a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.asset.FungibleAssetState$move(com.r3corda.core.contracts.Amount((com.r3corda.contracts.asset.FungibleAssetState.T)), java.security.PublicKey)/newOwner">newOwner</span><span class="symbol">:</span> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span><span class="symbol">: </span><a href="index.html"><span class="identifier">FungibleAssetState</span></a><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">,</span> <span class="identifier">I</span><span class="symbol">></span></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
15
docs/build/html/api/com.r3corda.contracts.asset/-fungible-asset-state/product-amount.html
vendored
Normal file
15
docs/build/html/api/com.r3corda.contracts.asset/-fungible-asset-state/product-amount.html
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>FungibleAssetState.productAmount - </title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts.asset</a> / <a href="index.html">FungibleAssetState</a> / <a href=".">productAmount</a><br/>
|
||||
<br/>
|
||||
<h1>productAmount</h1>
|
||||
<a name="com.r3corda.contracts.asset.FungibleAssetState$productAmount"></a>
|
||||
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">productAmount</span><span class="symbol">: </span><a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">></span></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
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