Merge branch 'master' into sofus-generic-contract

This commit is contained in:
sofusmortensen 2016-07-10 12:13:32 +02:00
commit 07c1f8b86a
1173 changed files with 28584 additions and 5130 deletions

1
.idea/modules.xml generated
View File

@ -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>

View File

@ -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") {

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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>
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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;
});
}
}

View File

@ -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();
});
});
}
}

View File

@ -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)
}
}
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -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'

View File

@ -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) {

View File

@ -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
}

View File

@ -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

View File

@ -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()
}

View File

@ -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?

View File

@ -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!!)

View File

@ -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." }
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -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>>
}

View File

@ -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>
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View 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)

View File

@ -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() }

View File

@ -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)
}

View File

@ -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"

View File

@ -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))
}

View File

@ -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?)

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

View File

@ -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,

View File

@ -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()

View File

@ -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>,

View File

@ -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
}
}

View File

@ -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)
}
}
}
}

View File

@ -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))
}
}

View File

@ -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")

View File

@ -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()}"
}

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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()
}
}
}

View File

@ -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()) }
}
}

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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

View 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.

View File

@ -41,6 +41,7 @@ Read on to learn:
tutorial-contract
protocol-state-machines
oracles
event-scheduling
.. toctree::
:maxdepth: 2

View File

@ -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>`_.

View File

@ -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 ...

View File

@ -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:

View File

@ -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

View File

@ -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&lt;K, V&gt; 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 &amp; 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

View 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>&nbsp;/&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<a href="index.html">Exit</a>&nbsp;/&nbsp;<a href=".">&lt;init&gt;</a><br/>
<br/>
<h1>&lt;init&gt;</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>&nbsp;<a href="../../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="../../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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>

View 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>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<a href="index.html">Exit</a>&nbsp;/&nbsp;<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">&lt;</span><a href="../../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</span></code><br/>
Overrides <a href="../../../-fungible-asset/-commands/-exit/amount.html">Exit.amount</a><br/>
<br/>
<br/>
</BODY>
</HTML>

View 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>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<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>&nbsp;<span class="symbol">:</span>&nbsp;<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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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">&lt;init&gt;</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>&nbsp;<a href="../../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="../../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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">&lt;</span><a href="../../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</span></code></td>
</tr>
</tbody>
</table>
</BODY>
</HTML>

View 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>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<a href="index.html">Issue</a>&nbsp;/&nbsp;<a href=".">&lt;init&gt;</a><br/>
<br/>
<h1>&lt;init&gt;</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>&nbsp;<span class="identifier">Long</span>&nbsp;<span class="symbol">=</span>&nbsp;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>

View 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>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<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>&nbsp;<span class="symbol">:</span>&nbsp;<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">&lt;init&gt;</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>&nbsp;<span class="identifier">Long</span>&nbsp;<span class="symbol">=</span>&nbsp;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>

View 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>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<a href="index.html">Issue</a>&nbsp;/&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<a href="index.html">Move</a>&nbsp;/&nbsp;<a href=".">&lt;init&gt;</a><br/>
<br/>
<h1>&lt;init&gt;</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>&nbsp;<a href="../../../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a><span class="symbol">?</span>&nbsp;<span class="symbol">=</span>&nbsp;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>

View 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>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<a href="index.html">Move</a>&nbsp;/&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<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>&nbsp;<span class="symbol">:</span>&nbsp;<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">&lt;init&gt;</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>&nbsp;<a href="../../../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a><span class="symbol">?</span>&nbsp;<span class="symbol">=</span>&nbsp;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>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href=".">Commands</a><br/>
<br/>
<h1>Commands</h1>
<code><span class="keyword">interface </span><span class="identifier">Commands</span>&nbsp;<span class="symbol">:</span>&nbsp;<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>&nbsp;<span class="symbol">:</span>&nbsp;<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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<span class="symbol">:</span>&nbsp;<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>&nbsp;<span class="symbol">:</span>&nbsp;<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>&nbsp;<span class="symbol">:</span>&nbsp;<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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<span class="symbol">:</span>&nbsp;<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>&nbsp;<span class="symbol">:</span>&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="index.html">Cash</a>&nbsp;/&nbsp;<a href=".">&lt;init&gt;</a><br/>
<br/>
<h1>&lt;init&gt;</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>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<a href=".">&lt;init&gt;</a><br/>
<br/>
<h1>&lt;init&gt;</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>&nbsp;<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>&nbsp;<a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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>&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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">&lt;</span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</span></code><br/>
Overrides <a href="../../-fungible-asset/-state/amount.html">State.amount</a><br/>
<br/>
<br/>
</BODY>
</HTML>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<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>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../-fungible-asset/-state/index.html"><span class="identifier">State</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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">&lt;init&gt;</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>&nbsp;<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>&nbsp;<a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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>&nbsp;<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">&lt;</span><a href="../../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">&gt;</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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<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">&lt;ERROR CLASS&gt;</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>&nbsp;<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>&nbsp;<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>&nbsp;<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">&lt;ERROR CLASS&gt;</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>&nbsp;<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>&nbsp;<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">&lt;</span><span class="identifier">State</span><span class="symbol">&gt;</span></code></td>
</tr>
</tbody>
</table>
</BODY>
</HTML>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span></code><br/>
Overrides <a href="../../-fungible-asset-state/issuance-def.html">FungibleAssetState.issuanceDef</a><br/>
<br/>
<br/>
</BODY>
</HTML>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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>&nbsp;<a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span></code><br/>
<br/>
<br/>
</BODY>
</HTML>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">&gt;</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>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span></code><br/>
Overrides <a href="../../-fungible-asset-state/product-amount.html">FungibleAssetState.productAmount</a><br/>
<br/>
<br/>
</BODY>
</HTML>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<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>&nbsp;<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">&lt;ERROR CLASS&gt;</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>

View 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>&nbsp;/&nbsp;<a href="index.html">Cash</a>&nbsp;/&nbsp;<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>&nbsp;<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>&nbsp;<a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<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>&nbsp;<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>&nbsp;<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>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="index.html">Cash</a>&nbsp;/&nbsp;<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>&nbsp;<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>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<span class="identifier">List</span><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-state-and-ref/index.html"><span class="identifier">StateAndRef</span></a><span class="symbol">&lt;</span><a href="-state/index.html"><span class="identifier">State</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<span class="identifier">List</span><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-state-and-ref/index.html"><span class="identifier">StateAndRef</span></a><span class="symbol">&lt;</span><a href="-state/index.html"><span class="identifier">State</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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>&nbsp;<span class="identifier">Set</span><span class="symbol">&lt;</span><a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">&gt;</span><span class="symbol">?</span>&nbsp;<span class="symbol">=</span>&nbsp;null<span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">&gt;</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>

View 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>&nbsp;/&nbsp;<a href=".">Cash</a><br/>
<br/>
<h1>Cash</h1>
<code><span class="keyword">class </span><span class="identifier">Cash</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../-fungible-asset/index.html"><span class="identifier">FungibleAsset</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<span class="symbol">:</span>&nbsp;<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>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../-fungible-asset/-state/index.html"><span class="identifier">State</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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">&lt;init&gt;</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>&nbsp;<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>&nbsp;<a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<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>&nbsp;<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>&nbsp;<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>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<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>&nbsp;<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>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<span class="identifier">List</span><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-state-and-ref/index.html"><span class="identifier">StateAndRef</span></a><span class="symbol">&lt;</span><a href="-state/index.html"><span class="identifier">State</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">&gt;</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>&nbsp;<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>&nbsp;<span class="identifier">List</span><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-state-and-ref/index.html"><span class="identifier">StateAndRef</span></a><span class="symbol">&lt;</span><a href="-state/index.html"><span class="identifier">State</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</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>&nbsp;<span class="identifier">Set</span><span class="symbol">&lt;</span><a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">&gt;</span><span class="symbol">?</span>&nbsp;<span class="symbol">=</span>&nbsp;null<span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">&gt;</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>&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="index.html">Cash</a>&nbsp;/&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href=".">FungibleAssetState</a><br/>
<br/>
<h1>FungibleAssetState</h1>
<code><span class="keyword">interface </span><span class="identifier">FungibleAssetState</span><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">, </span><span class="identifier">I</span><span class="symbol">&gt;</span>&nbsp;<span class="symbol">:</span>&nbsp;<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">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</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>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</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>&nbsp;<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">&lt;</span><span class="identifier">T</span><span class="symbol">,</span>&nbsp;<span class="identifier">I</span><span class="symbol">&gt;</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>&nbsp;<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">&lt;ERROR CLASS&gt;</span><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-command-data.html"><span class="identifier">CommandData</span></a><span class="symbol">,</span>&nbsp;<a href="../../com.r3corda.core.contracts/-ownable-state/index.html"><span class="identifier">OwnableState</span></a><span class="symbol">&gt;</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>&nbsp;<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">&lt;</span><a href="../../com.r3corda.core.contracts/-contract-state/index.html"><span class="identifier">ContractState</span></a><span class="symbol">&gt;</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">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span>&nbsp;<span class="symbol">:</span>&nbsp;<span class="identifier">FungibleAssetState</span><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">,</span>&nbsp;<a href="../../com.r3corda.core.contracts/-issued/index.html"><span class="identifier">Issued</span></a><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span><span class="symbol">&gt;</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">&lt;</span><span class="identifier">P</span><span class="symbol">&gt;</span>&nbsp;<span class="symbol">:</span>&nbsp;<span class="identifier">FungibleAssetState</span><span class="symbol">&lt;</span><span class="identifier">P</span><span class="symbol">,</span>&nbsp;<a href="../-obligation/-issuance-definition/index.html"><span class="identifier">IssuanceDefinition</span></a><span class="symbol">&lt;</span><span class="identifier">P</span><span class="symbol">&gt;</span><span class="symbol">&gt;</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">&lt;</span><a href="../-obligation/-state/index.html"><span class="identifier">State</span></a><span class="symbol">&lt;</span><span class="identifier">P</span><span class="symbol">&gt;</span><span class="symbol">&gt;</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>

View 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>&nbsp;/&nbsp;<a href="index.html">FungibleAssetState</a>&nbsp;/&nbsp;<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>

View 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>&nbsp;/&nbsp;<a href="index.html">FungibleAssetState</a>&nbsp;/&nbsp;<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>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</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>&nbsp;<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">&lt;</span><span class="identifier">T</span><span class="symbol">,</span>&nbsp;<span class="identifier">I</span><span class="symbol">&gt;</span></code><br/>
<br/>
<br/>
</BODY>
</HTML>

View 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>&nbsp;/&nbsp;<a href="index.html">FungibleAssetState</a>&nbsp;/&nbsp;<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">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span></code><br/>
<br/>
<br/>
</BODY>
</HTML>

Some files were not shown because too many files have changed in this diff Show More