Merge branch 'master' into sofus-generic-contract

This commit is contained in:
sofusmortensen 2016-06-27 00:05:37 +02:00
commit 0bdabc3a0b
801 changed files with 16182 additions and 6315 deletions

3
.gitignore vendored
View File

@ -18,6 +18,9 @@ tags
/core/build
/docs/build/doctrees
# gradle's buildSrc build/
/buildSrc/build/
# This exists only in the internal repo
/network-explorer/build

6
.idea/modules.xml generated
View File

@ -2,12 +2,18 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/buildSrc.iml" filepath="$PROJECT_DIR$/.idea/modules/buildSrc.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/buildSrc_main.iml" filepath="$PROJECT_DIR$/.idea/modules/buildSrc_main.iml" group="buildSrc" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/buildSrc_test.iml" filepath="$PROJECT_DIR$/.idea/modules/buildSrc_test.iml" group="buildSrc" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/contracts.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/contracts.iml" group="contracts" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/contracts_main.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/contracts_main.iml" group="contracts" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/contracts_test.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/contracts_test.iml" group="contracts" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/core/core.iml" filepath="$PROJECT_DIR$/.idea/modules/core/core.iml" group="core" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/core/core_main.iml" filepath="$PROJECT_DIR$/.idea/modules/core/core_main.iml" group="core" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/core/core_test.iml" filepath="$PROJECT_DIR$/.idea/modules/core/core_test.iml" group="core" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/experimental/experimental.iml" filepath="$PROJECT_DIR$/.idea/modules/experimental/experimental.iml" group="experimental" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/experimental/experimental_main.iml" filepath="$PROJECT_DIR$/.idea/modules/experimental/experimental_main.iml" group="experimental" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/experimental/experimental_test.iml" filepath="$PROJECT_DIR$/.idea/modules/experimental/experimental_test.iml" group="experimental" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated.iml" group="contracts/isolated" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated_main.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated_main.iml" group="contracts/isolated" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated_test.iml" filepath="$PROJECT_DIR$/.idea/modules/contracts/isolated/isolated_test.iml" group="contracts/isolated" />

View File

@ -5,17 +5,22 @@ apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'project-report'
apply plugin: QuasarPlugin
allprojects {
sourceCompatibility = 1.8
targetCompatibility = 1.8
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
}
buildscript {
ext.kotlin_version = '1.0.2'
ext.quasar_version = '0.7.5'
ext.asm_version = '0.5.3'
ext.artemis_version = '1.2.0'
ext.artemis_version = '1.3.0'
ext.jetty_version = '9.1.1.v20140108'
ext.jersey_version = '2.22.2'
ext.jolokia_version = '2.0.0-M1'
@ -41,12 +46,15 @@ repositories {
//noinspection GroovyAssignabilityCheck
configurations {
quasar
// we don't want isolated.jar in classPath, since we want to test jar being dynamically loaded as an attachment
runtime.exclude module: 'isolated'
}
// This is required for quasar. I think.
applicationDefaultJvmArgs = ["-javaagent:${configurations.quasar.singleFile}"]
// Needed by the :startScripts task
mainClassName = 'com.r3corda.demos.TraderDemoKt'
// To find potential version conflicts, run "gradle htmlDependencyReport" and then look in
// build/reports/project/dependencies/index.html for green highlighted parts of the tree.
@ -58,30 +66,10 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.1"
// Quasar: for the bytecode rewriting for state machines.
quasar "co.paralleluniverse:quasar-core:${quasar_version}:jdk8@jar"
// Unit testing helpers.
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:3.4.1'
}
// These lines tell Gradle to add a couple of JVM command line arguments to unit test and program runs, which set up
// the Quasar bytecode rewriting system so fibers can be suspended. The verifyInstrumentation line makes things run
// slower but you get a much better error message if you forget to annotate a method with @Suspendable that needs it.
//
// In Java 9 (hopefully) the requirement to annotate methods as @Suspendable will go away.
applicationDefaultJvmArgs = ["-javaagent:${configurations.quasar.singleFile}"]
mainClassName = 'com.r3corda.demos.TraderDemoKt'
tasks.withType(Test) {
jvmArgs "-javaagent:${configurations.quasar.singleFile}"
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
}
tasks.withType(JavaExec) {
jvmArgs "-javaagent:${configurations.quasar.singleFile}"
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
testCompile 'com.pholser:junit-quickcheck-core:0.6'
}
// Package up the demo programs.
@ -121,27 +109,7 @@ tasks.withType(CreateStartScripts)
}
}
// These lines tell gradle to run the Quasar suspendables scanner to look for unannotated super methods
// that have @Suspendable sub implementations. These tend to cause NPEs and are not caught by the verifier
// NOTE: need to make sure the output isn't on the classpath or every other run it generates empty results, so
// we explicitly delete to avoid that happening. We also need to turn off what seems to be a spurious warning in the IDE
//
// TODO: Make this task incremental, as it can be quite slow.
//noinspection GroovyAssignabilityCheck
task quasarScan(dependsOn: ['classes', 'core:classes', 'contracts:classes', 'node:classes']) << {
ant.taskdef(name:'scanSuspendables', classname:'co.paralleluniverse.fibers.instrument.SuspendablesScanner',
classpath: "${sourceSets.main.output.classesDir}:${sourceSets.main.output.resourcesDir}:${configurations.runtime.asPath}")
delete "$sourceSets.main.output.resourcesDir/META-INF/suspendables", "$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers"
ant.scanSuspendables(
auto:false,
suspendablesFile: "$sourceSets.main.output.resourcesDir/META-INF/suspendables",
supersFile: "$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers") {
fileset(dir: sourceSets.main.output.classesDir)
}
}
jar.dependsOn quasarScan
quasarScan.dependsOn('classes', 'core:classes', 'contracts:classes', 'node:classes')
applicationDistribution.into("bin") {
from(getRateFixDemo)

View File

@ -0,0 +1,57 @@
import org.gradle.api.*
import org.gradle.api.tasks.testing.Test
import org.gradle.api.tasks.JavaExec
/**
* QuasarPlugin creates a "quasar" configuration, adds quasar as a dependency and creates a "quasarScan" task that scans
* for `@Suspendable`s in the code
*/
class QuasarPlugin implements Plugin<Project> {
void apply(Project project) {
project.repositories {
mavenCentral()
}
project.configurations.create("quasar")
// To add a local .jar dependency:
// project.dependencies.add("quasar", project.files("${project.rootProject.projectDir}/lib/quasar.jar"))
project.dependencies.add("quasar", "co.paralleluniverse:quasar-core:${project.rootProject.ext.quasar_version}:jdk8@jar")
project.dependencies.add("compile", project.configurations.getByName("quasar"))
project.tasks.withType(Test) {
jvmArgs "-javaagent:${project.configurations.quasar.singleFile}"
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
}
project.tasks.withType(JavaExec) {
jvmArgs "-javaagent:${project.configurations.quasar.singleFile}"
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
}
project.task("quasarScan") {
inputs.files(project.sourceSets.main.output)
outputs.files(
"$project.sourceSets.main.output.resourcesDir/META-INF/suspendables",
"$project.sourceSets.main.output.resourcesDir/META-INF/suspendable-supers"
)
} << {
// These lines tell gradle to run the Quasar suspendables scanner to look for unannotated super methods
// that have @Suspendable sub implementations. These tend to cause NPEs and are not caught by the verifier
// NOTE: need to make sure the output isn't on the classpath or every other run it generates empty results, so
// we explicitly delete to avoid that happening. We also need to turn off what seems to be a spurious warning in the IDE
ant.taskdef(name:'scanSuspendables', classname:'co.paralleluniverse.fibers.instrument.SuspendablesScanner',
classpath: "${project.sourceSets.main.output.classesDir}:${project.sourceSets.main.output.resourcesDir}:${project.configurations.runtime.asPath}")
project.delete "$project.sourceSets.main.output.resourcesDir/META-INF/suspendables", "$project.sourceSets.main.output.resourcesDir/META-INF/suspendable-supers"
ant.scanSuspendables(
auto:false,
suspendablesFile: "$project.sourceSets.main.output.resourcesDir/META-INF/suspendables",
supersFile: "$project.sourceSets.main.output.resourcesDir/META-INF/suspendable-supers") {
fileset(dir: project.sourceSets.main.output.classesDir)
}
}
project.jar.dependsOn project.quasarScan
}
}

View File

@ -8,25 +8,27 @@
package com.r3corda.contracts.isolated
import com.r3corda.core.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
// The dummy contract doesn't do anything useful. It exists for testing purposes.
val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract()
class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdoor {
class State(val magicNumber: Int = 0, override val notary: Party) : ContractState {
data class State(val magicNumber: Int = 0) : ContractState {
override val contract = ANOTHER_DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = emptyList()
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Always accepts.
}
@ -34,8 +36,8 @@ class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdo
override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org")
override fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
val state = State(magicNumber)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
override fun inspectState(state: ContractState): Int = (state as State).magicNumber

View File

@ -3,6 +3,7 @@ package com.r3corda.contracts;
import com.r3corda.core.contracts.Amount;
import com.r3corda.core.contracts.ContractState;
import com.r3corda.core.contracts.PartyAndReference;
import com.r3corda.core.contracts.Issued;
import java.security.*;
import java.time.*;
@ -18,7 +19,7 @@ public interface ICommercialPaperState extends ContractState {
ICommercialPaperState withIssuance(PartyAndReference newIssuance);
ICommercialPaperState withFaceValue(Amount<Currency> newFaceValue);
ICommercialPaperState withFaceValue(Amount<Issued<Currency>> newFaceValue);
ICommercialPaperState withMaturityDate(Instant newMaturityDate);
}

View File

@ -1,10 +1,11 @@
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.core.contracts.TransactionForVerification.InOutGroup;
import com.r3corda.core.contracts.*;
import com.r3corda.core.contracts.TransactionForContract.InOutGroup;
import com.r3corda.core.crypto.NullPublicKey;
import com.r3corda.core.crypto.Party;
import com.r3corda.core.crypto.SecureHash;
@ -13,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
import java.security.PublicKey;
import java.time.Instant;
import java.util.Currency;
import java.util.List;
import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand;
@ -30,39 +32,38 @@ public class JavaCommercialPaper implements Contract {
public static class State implements ContractState, ICommercialPaperState {
private PartyAndReference issuance;
private PublicKey owner;
private Amount faceValue;
private Amount<Issued<Currency>> faceValue;
private Instant maturityDate;
private Party notary;
public State() {
} // For serialization
public State(PartyAndReference issuance, PublicKey owner, Amount faceValue, Instant maturityDate, Party notary) {
public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> faceValue,
Instant maturityDate) {
this.issuance = issuance;
this.owner = owner;
this.faceValue = faceValue;
this.maturityDate = maturityDate;
this.notary = notary;
}
public State copy() {
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate, this.notary);
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
}
public ICommercialPaperState withOwner(PublicKey newOwner) {
return new State(this.issuance, newOwner, this.faceValue, this.maturityDate, this.notary);
return new State(this.issuance, newOwner, this.faceValue, this.maturityDate);
}
public ICommercialPaperState withIssuance(PartyAndReference newIssuance) {
return new State(newIssuance, this.owner, this.faceValue, this.maturityDate, this.notary);
return new State(newIssuance, this.owner, this.faceValue, this.maturityDate);
}
public ICommercialPaperState withFaceValue(Amount newFaceValue) {
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate, this.notary);
public ICommercialPaperState withFaceValue(Amount<Issued<Currency>> newFaceValue) {
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate);
}
public ICommercialPaperState withMaturityDate(Instant newMaturityDate) {
return new State(this.issuance, this.owner, this.faceValue, newMaturityDate, this.notary);
return new State(this.issuance, this.owner, this.faceValue, newMaturityDate);
}
public PartyAndReference getIssuance() {
@ -73,7 +74,7 @@ public class JavaCommercialPaper implements Contract {
return owner;
}
public Amount getFaceValue() {
public Amount<Issued<Currency>> getFaceValue() {
return faceValue;
}
@ -81,12 +82,6 @@ public class JavaCommercialPaper implements Contract {
return maturityDate;
}
@NotNull
@Override
public Party getNotary() {
return notary;
}
@NotNull
@Override
public Contract getContract() {
@ -104,7 +99,6 @@ public class JavaCommercialPaper implements Contract {
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
if (notary != null ? !notary.equals(state.notary) : state.notary != null) return false;
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
}
@ -114,12 +108,17 @@ public class JavaCommercialPaper implements Contract {
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
result = 31 * result + (notary != null ? notary.hashCode() : 0);
return result;
}
public State withoutOwner() {
return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate, notary);
return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate);
}
@NotNull
@Override
public List<PublicKey> getParticipants() {
return ImmutableList.of(this.owner);
}
}
@ -147,7 +146,7 @@ public class JavaCommercialPaper implements Contract {
}
@Override
public void verify(@NotNull TransactionForVerification tx) {
public void verify(@NotNull TransactionForContract tx) {
// There are three possible things that can be done with CP.
// Issuance, trading (aka moving in this prototype) and redeeming.
// Each command has it's own set of restrictions which the verify function ... verifies.
@ -207,10 +206,11 @@ public class JavaCommercialPaper implements Contract {
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
Instant time = timestampCommand.getBefore();
Amount received = CashKt.sumCashBy(tx.getOutStates(), input.getOwner());
Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutputs(), input.getOwner());
if (!received.equals(input.getFaceValue()))
throw new IllegalStateException("Failed Requirement: received amount equals the face value");
throw new IllegalStateException("Failed Requirement: received amount equals the face value: "
+ received + " vs " + input.getFaceValue());
if (time == null || time.isBefore(input.getMaturityDate()))
throw new IllegalStateException("Failed requirement: the paper must have matured");
if (!input.getFaceValue().equals(received))
@ -229,20 +229,21 @@ public class JavaCommercialPaper implements Contract {
return SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper");
}
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate, notary);
return new TransactionBuilder().withItems(state, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
TransactionState output = new TransactionState<>(state, notary);
return new TransactionType.General.Builder().withItems(output, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
}
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
new Cash().generateSpend(tx, paper.getState().getFaceValue(), paper.getState().getOwner(), wallet, null);
tx.addInputState(paper.getRef());
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getOwner()));
new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet);
tx.addInputState(paper);
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner()));
}
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
tx.addInputState(paper.getRef());
tx.addOutputState(new State(paper.getState().getIssuance(), newOwner, paper.getState().getFaceValue(), paper.getState().getMaturityDate(), paper.getState().getNotary()));
tx.addCommand(new Command(new Commands.Move(), paper.getState().getOwner()));
tx.addInputState(paper);
tx.addOutputState(new TransactionState<>(new State(paper.getState().getData().getIssuance(), newOwner, paper.getState().getData().getFaceValue(), paper.getState().getData().getMaturityDate()), paper.getState().getNotary()));
tx.addCommand(new Command(new Commands.Move(), paper.getState().getData().getOwner()));
}
}

View File

@ -3,7 +3,6 @@ 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.core.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.NullPublicKey
import com.r3corda.core.crypto.Party
@ -12,7 +11,7 @@ import com.r3corda.core.crypto.toStringShort
import com.r3corda.core.utilities.Emoji
import java.security.PublicKey
import java.time.Instant
import java.util.Currency
import java.util.*
/**
* This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate
@ -46,11 +45,12 @@ class CommercialPaper : Contract {
data class State(
val issuance: PartyAndReference,
override val owner: PublicKey,
val faceValue: Amount<Currency>,
val maturityDate: Instant,
override val notary: Party
val faceValue: Amount<Issued<Currency>>,
val maturityDate: Instant
) : OwnableState, ICommercialPaperState {
override val contract = CP_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf(owner)
fun withoutOwner() = copy(owner = NullPublicKey)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
@ -60,7 +60,7 @@ class CommercialPaper : Contract {
override fun withOwner(newOwner: PublicKey): ICommercialPaperState = copy(owner = newOwner)
override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance)
override fun withFaceValue(newFaceValue: Amount<Currency>): ICommercialPaperState = copy(faceValue = newFaceValue)
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
}
@ -72,7 +72,7 @@ class CommercialPaper : Contract {
class Issue : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
val groups = tx.groupStates() { it: State -> it.withoutOwner() }
@ -100,7 +100,7 @@ class CommercialPaper : Contract {
// Redemption of the paper requires movement of on-ledger cash.
is Commands.Redeem -> {
val input = inputs.single()
val received = tx.outStates.sumCashBy(input.owner)
val received = tx.outputs.sumCashBy(input.owner)
val time = timestamp?.after ?: throw IllegalArgumentException("Redemptions must be timestamped")
requireThat {
"the paper must have matured" by (time >= input.maturityDate)
@ -136,18 +136,19 @@ class CommercialPaper : Contract {
* an existing transaction because you aren't able to issue multiple pieces of CP in a single transaction
* at the moment: this restriction is not fundamental and may be lifted later.
*/
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Currency>, maturityDate: Instant, notary: Party): TransactionBuilder {
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate, notary)
return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
fun generateIssue(faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
val issuance = faceValue.token.issuer
val state = TransactionState(State(issuance, issuance.party.owningKey, faceValue, maturityDate), notary)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
}
/**
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
*/
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) {
tx.addInputState(paper.ref)
tx.addOutputState(paper.state.copy(owner = newOwner))
tx.addCommand(Commands.Move(), paper.state.owner)
tx.addInputState(paper)
tx.addOutputState(TransactionState(paper.state.data.copy(owner = newOwner), paper.state.notary))
tx.addCommand(Commands.Move(), paper.state.data.owner)
}
/**
@ -160,9 +161,10 @@ class CommercialPaper : Contract {
@Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
// Add the cash movement using the states in our wallet.
Cash().generateSpend(tx, paper.state.faceValue, paper.state.owner, wallet)
tx.addInputState(paper.ref)
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.owner)
val amount = paper.state.data.faceValue.let { amount -> Amount<Currency>(amount.quantity, amount.token.product) }
Cash().generateSpend(tx, amount, paper.state.data.owner, wallet)
tx.addInputState(paper)
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner)
}
}

View File

@ -1,174 +0,0 @@
package com.r3corda.contracts
import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.cash.sumCash
import com.r3corda.contracts.cash.sumCashBy
import com.r3corda.core.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
import java.time.Instant
import java.util.*
val CROWDFUND_PROGRAM_ID = CrowdFund()
/**
* This is a basic crowd funding contract. It allows a party to create a funding opportunity, then for others to
* pledge during the funding period , and then for the party to either accept the funding (if the target has been reached)
* return the funds to the pledge-makers (if the target has not been reached).
*
* Discussion
* ----------
*
* This method of modelling a crowdfund is similar to how it'd be done in Ethereum. The state is essentially a database
* in which transactions evolve it over time. The state transition model we are using here though means it's possible
* to do it in a different approach, with some additional (not yet implemented) extensions to the model. In the UTXO
* model you can do something more like the Lighthouse application (https://www.vinumeris.com/lighthouse) in which
* the campaign data and people's pledges are transmitted out of band, with a pledge being a partially signed
* transaction which is valid only when merged with other transactions. The pledges can then be combined by the project
* owner at the point at which sufficient amounts of money have been gathered, and this creates a valid transaction
* that claims the money.
*
* TODO: Prototype this second variant of crowdfunding once the core model has been sufficiently extended.
* TODO: Experiment with the use of the javax.validation API to simplify the validation logic by annotating state members.
*
* See JIRA bug PD-21 for further discussion and followup.
*
* @author James Carlyle
*/
class CrowdFund : Contract {
data class Campaign(
val owner: PublicKey,
val name: String,
val target: Amount<Currency>,
val closingTime: Instant
) {
override fun toString() = "Crowdsourcing($target sought by $owner by $closingTime)"
}
data class State(
val campaign: Campaign,
override val notary: Party,
val closed: Boolean = false,
val pledges: List<Pledge> = ArrayList()
) : ContractState {
override val contract = CROWDFUND_PROGRAM_ID
val pledgedAmount: Amount<Currency> get() = pledges.map { it.amount }.sumOrZero(campaign.target.token)
}
data class Pledge(
val owner: PublicKey,
val amount: Amount<Currency>
)
interface Commands : CommandData {
class Register : TypeOnlyCommandData(), Commands
class Pledge : TypeOnlyCommandData(), Commands
class Close : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
// There are three possible things that can be done with Crowdsourcing.
// The first is creating it. The second is funding it with cash. The third is closing it on or after the closing
// date, and returning funds to pledge-makers if the target is unmet, or passing to the recipient.
val command = tx.commands.requireSingleCommand<CrowdFund.Commands>()
val outputCrowdFund: CrowdFund.State = tx.outStates.filterIsInstance<CrowdFund.State>().single()
val outputCash: List<Cash.State> = tx.outStates.filterIsInstance<Cash.State>()
val time = tx.commands.getTimestampByName("Notary Service")?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped")
when (command.value) {
is Commands.Register -> {
requireThat {
"there is no input state" by tx.inStates.filterIsInstance<State>().isEmpty()
"the transaction is signed by the owner of the crowdsourcing" by (command.signers.contains(outputCrowdFund.campaign.owner))
"the output registration is empty of pledges" by (outputCrowdFund.pledges.isEmpty())
"the output registration has a non-zero target" by (outputCrowdFund.campaign.target.quantity > 0)
"the output registration has a name" by (outputCrowdFund.campaign.name.isNotBlank())
"the output registration has a closing time in the future" by (time < outputCrowdFund.campaign.closingTime)
"the output registration has an open state" by (!outputCrowdFund.closed)
}
}
is Commands.Pledge -> {
val inputCrowdFund: CrowdFund.State = tx.inStates.filterIsInstance<CrowdFund.State>().single()
val pledgedCash = outputCash.sumCashBy(inputCrowdFund.campaign.owner)
requireThat {
"campaign details have not changed" by (inputCrowdFund.campaign == outputCrowdFund.campaign)
"the campaign is still open" by (inputCrowdFund.campaign.closingTime >= time)
"the pledge must be in the same currency as the goal" by (pledgedCash.token == outputCrowdFund.campaign.target.token)
"the pledged total has increased by the value of the pledge" by (outputCrowdFund.pledgedAmount == inputCrowdFund.pledgedAmount + pledgedCash)
"the output registration has an open state" by (!outputCrowdFund.closed)
}
}
is Commands.Close -> {
val inputCrowdFund: CrowdFund.State = tx.inStates.filterIsInstance<CrowdFund.State>().single()
fun checkReturns(inputCrowdFund: CrowdFund.State, outputCash: List<Cash.State>): Boolean {
for (pledge in inputCrowdFund.pledges) {
if (outputCash.none { it.amount == pledge.amount && it.owner == pledge.owner }) return false
}
return true
}
requireThat {
"campaign details have not changed" by (inputCrowdFund.campaign == outputCrowdFund.campaign)
"the closing date has past" by (time >= outputCrowdFund.campaign.closingTime)
"the input has an open state" by (!inputCrowdFund.closed)
"the output registration has a closed state" by (outputCrowdFund.closed)
// Now check whether the target was met, and if so, return cash
if (inputCrowdFund.pledgedAmount < inputCrowdFund.campaign.target) {
"the output cash returns equal the pledge total, if the target is not reached" by (outputCash.sumCash() == inputCrowdFund.pledgedAmount)
"the output cash is distributed to the pledge-makers, if the target is not reached" by (checkReturns(inputCrowdFund, outputCash))
"the output cash is distributed to the pledge-makers, if the target is not reached" by (outputCash.map { it.amount }.containsAll(inputCrowdFund.pledges.map { it.amount }))
}
"the pledged total is unchanged" by (outputCrowdFund.pledgedAmount == inputCrowdFund.pledgedAmount)
"the pledges are unchanged" by (outputCrowdFund.pledges == inputCrowdFund.pledges)
}
}
else -> throw IllegalArgumentException("Unrecognised command")
}
}
/**
* Returns a transaction that registers a crowd-funding campaing, owned by the issuing institution's key. Does not update
* an existing transaction because it's not possible to register multiple campaigns in a single transaction
*/
fun generateRegister(owner: PartyAndReference, fundingTarget: Amount<Currency>, fundingName: String, closingTime: Instant, notary: Party): TransactionBuilder {
val campaign = Campaign(owner = owner.party.owningKey, name = fundingName, target = fundingTarget, closingTime = closingTime)
val state = State(campaign, notary)
return TransactionBuilder().withItems(state, Command(Commands.Register(), owner.party.owningKey))
}
/**
* Updates the given partial transaction with an input/output/command to fund the opportunity.
*/
fun generatePledge(tx: TransactionBuilder, campaign: StateAndRef<State>, subscriber: PublicKey) {
tx.addInputState(campaign.ref)
tx.addOutputState(campaign.state.copy(
pledges = campaign.state.pledges + CrowdFund.Pledge(subscriber, 1000.DOLLARS)
))
tx.addCommand(Commands.Pledge(), subscriber)
}
fun generateClose(tx: TransactionBuilder, campaign: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
tx.addInputState(campaign.ref)
tx.addOutputState(campaign.state.copy(closed = true))
tx.addCommand(Commands.Close(), campaign.state.campaign.owner)
// If campaign target has not been met, compose cash returns
if (campaign.state.pledgedAmount < campaign.state.campaign.target) {
for (pledge in campaign.state.pledges) {
Cash().generateSpend(tx, pledge.amount, pledge.owner, wallet)
}
}
}
override val legalContractReference: SecureHash = SecureHash.sha256("Crowdsourcing")
}

View File

@ -1,33 +0,0 @@
package com.r3corda.contracts
import com.r3corda.core.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
// The dummy contract doesn't do anything useful. It exists for testing purposes.
val DUMMY_PROGRAM_ID = DummyContract()
class DummyContract : Contract {
class State(val magicNumber: Int = 0,
override val notary: Party) : ContractState {
override val contract = DUMMY_PROGRAM_ID
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
// Always accepts.
}
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
}

View File

@ -1,6 +1,5 @@
package com.r3corda.contracts
import com.r3corda.core.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
@ -373,7 +372,7 @@ class InterestRateSwap() : Contract {
var rollConvention: DateRollConvention,
var fixingRollConvention: DateRollConvention,
var resetDayInMonth: Int,
var fixingPeriod: DateOffset,
var fixingPeriodOffset: Int,
var resetRule: PaymentRule,
var fixingsPerPayment: Frequency,
var fixingCalendar: BusinessCalendar,
@ -384,7 +383,7 @@ class InterestRateSwap() : Contract {
dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment) {
override fun toString(): String = "FloatingLeg(Payer=$floatingRatePayer," + super.toString() +
"rollConvention=$rollConvention,FixingRollConvention=$fixingRollConvention,ResetDayInMonth=$resetDayInMonth" +
"FixingPeriond=$fixingPeriod,ResetRule=$resetRule,FixingsPerPayment=$fixingsPerPayment,FixingCalendar=$fixingCalendar," +
"FixingPeriondOffset=$fixingPeriodOffset,ResetRule=$resetRule,FixingsPerPayment=$fixingsPerPayment,FixingCalendar=$fixingCalendar," +
"Index=$index,IndexSource=$indexSource,IndexTenor=$indexTenor"
override fun equals(other: Any?): Boolean {
@ -398,7 +397,7 @@ class InterestRateSwap() : Contract {
if (rollConvention != other.rollConvention) return false
if (fixingRollConvention != other.fixingRollConvention) return false
if (resetDayInMonth != other.resetDayInMonth) return false
if (fixingPeriod != other.fixingPeriod) return false
if (fixingPeriodOffset != other.fixingPeriodOffset) return false
if (resetRule != other.resetRule) return false
if (fixingsPerPayment != other.fixingsPerPayment) return false
if (fixingCalendar != other.fixingCalendar) return false
@ -410,7 +409,7 @@ class InterestRateSwap() : Contract {
}
override fun hashCode() = super.hashCode() + 31 * Objects.hash(floatingRatePayer, rollConvention,
fixingRollConvention, resetDayInMonth, fixingPeriod, resetRule, fixingsPerPayment, fixingCalendar,
fixingRollConvention, resetDayInMonth, fixingPeriodOffset, resetRule, fixingsPerPayment, fixingCalendar,
index, indexSource, indexTenor)
@ -431,7 +430,7 @@ class InterestRateSwap() : Contract {
rollConvention: DateRollConvention = this.rollConvention,
fixingRollConvention: DateRollConvention = this.fixingRollConvention,
resetDayInMonth: Int = this.resetDayInMonth,
fixingPeriod: DateOffset = this.fixingPeriod,
fixingPeriod: Int = this.fixingPeriodOffset,
resetRule: PaymentRule = this.resetRule,
fixingsPerPayment: Frequency = this.fixingsPerPayment,
fixingCalendar: BusinessCalendar = this.fixingCalendar,
@ -488,7 +487,7 @@ class InterestRateSwap() : Contract {
/**
* verify() with some examples of what needs to be checked.
*/
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Group by Trade ID for in / out states
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
@ -516,7 +515,7 @@ class InterestRateSwap() : Contract {
"The termination dates are aligned" by (irs.floatingLeg.terminationDate == irs.fixedLeg.terminationDate)
"The rates are valid" by checkRates(arrayOf(irs.fixedLeg, irs.floatingLeg))
"The schedules are valid" by checkSchedules(arrayOf(irs.fixedLeg, irs.floatingLeg))
"The fixing period date offset cannot be negative" by (irs.floatingLeg.fixingPeriodOffset >= 0)
// TODO: further tests
}
@ -588,14 +587,16 @@ class InterestRateSwap() : Contract {
val fixedLeg: FixedLeg,
val floatingLeg: FloatingLeg,
val calculation: Calculation,
val common: Common,
override val notary: Party
val common: Common
) : FixableDealState {
override val contract = IRS_PROGRAM_ID
override val thread = SecureHash.sha256(common.tradeID)
override val ref = common.tradeID
override val participants: List<PublicKey>
get() = parties.map { it.owningKey }
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
return (fixedLeg.fixedRatePayer.owningKey in ourKeys) || (floatingLeg.floatingRatePayer.owningKey in ourKeys)
}
@ -619,10 +620,10 @@ class InterestRateSwap() : Contract {
}
}
override fun generateAgreement(): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
override fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix) {
InterestRateSwap().generateFix(ptx, StateAndRef(this, oldStateRef), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, oldState.state.notary), oldState.ref), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
}
override fun nextFixingOf(): FixOf? {
@ -672,15 +673,17 @@ class InterestRateSwap() : Contract {
// Create a schedule for the fixed payments
for (periodEndDate in dates) {
val paymentDate = BusinessCalendar.getOffsetDate(periodEndDate, Frequency.Daily, fixedLeg.paymentDelay)
val paymentEvent = FixedRatePaymentEvent(
// TODO: We are assuming the payment date is the end date of the accrual period.
periodEndDate, periodStartDate, periodEndDate,
paymentDate,
periodStartDate,
periodEndDate,
fixedLeg.dayCountBasisDay,
fixedLeg.dayCountBasisYear,
fixedLeg.notional,
fixedLeg.fixedRate
)
fixedLegPaymentSchedule[periodEndDate] = paymentEvent
fixedLegPaymentSchedule[paymentDate] = paymentEvent
periodStartDate = periodEndDate
}
@ -695,40 +698,43 @@ class InterestRateSwap() : Contract {
// Now create a schedule for the floating and fixes.
for (periodEndDate in dates) {
val paymentDate = BusinessCalendar.getOffsetDate(periodEndDate, Frequency.Daily, floatingLeg.paymentDelay)
val paymentEvent = FloatingRatePaymentEvent(
periodEndDate,
paymentDate,
periodStartDate,
periodEndDate,
floatingLeg.dayCountBasisDay,
floatingLeg.dayCountBasisYear,
calcFixingDate(periodStartDate, floatingLeg.fixingPeriod, floatingLeg.fixingCalendar),
calcFixingDate(periodStartDate, floatingLeg.fixingPeriodOffset, floatingLeg.fixingCalendar),
floatingLeg.notional,
ReferenceRate(floatingLeg.indexSource, floatingLeg.indexTenor, floatingLeg.index)
)
floatingLegPaymentSchedule.put(periodEndDate, paymentEvent)
floatingLegPaymentSchedule[paymentDate] = paymentEvent
periodStartDate = periodEndDate
}
val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule)
// Put all the above into a new State object.
val state = State(fixedLeg, floatingLeg, newCalculation, common, notary)
return TransactionBuilder().withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey)))
val state = State(fixedLeg, floatingLeg, newCalculation, common)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey)))
}
private fun calcFixingDate(date: LocalDate, fixingPeriod: DateOffset, calendar: BusinessCalendar): LocalDate {
return when (fixingPeriod) {
DateOffset.ZERO -> date
DateOffset.TWODAYS -> calendar.moveBusinessDays(date, DateRollDirection.BACKWARD, 2)
else -> TODO("Improved fixing date calculation logic")
private fun calcFixingDate(date: LocalDate, fixingPeriodOffset: Int, calendar: BusinessCalendar): LocalDate {
return when (fixingPeriodOffset) {
0 -> date
else -> calendar.moveBusinessDays(date, DateRollDirection.BACKWARD, fixingPeriodOffset)
}
}
// TODO: Replace with rates oracle
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Pair<LocalDate, Rate>) {
tx.addInputState(irs.ref)
tx.addOutputState(irs.state.copy(calculation = irs.state.calculation.applyFixing(fixing.first, FixedRate(fixing.second))))
tx.addCommand(Commands.Fix(), listOf(irs.state.floatingLeg.floatingRatePayer.owningKey, irs.state.fixedLeg.fixedRatePayer.owningKey))
tx.addInputState(irs)
tx.addOutputState(
irs.state.data.copy(calculation = irs.state.data.calculation.applyFixing(fixing.first, FixedRate(fixing.second))),
irs.state.notary
)
tx.addCommand(Commands.Fix(), listOf(irs.state.data.floatingLeg.floatingRatePayer.owningKey, irs.state.data.fixedLeg.fixedRatePayer.owningKey))
}
}

View File

@ -3,7 +3,7 @@ package com.r3corda.contracts
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Tenor
import java.math.BigDecimal
import java.util.Currency
import java.util.*
// Things in here will move to the general utils class when we've hammered out various discussions regarding amounts, dates, oracle etc.
@ -37,8 +37,8 @@ open class PercentageRatioUnit(percentageAsString: String) : RatioUnit(BigDecima
/**
* For the convenience of writing "5".percent
* Note that we do not currently allow 10.percent (ie no quotes) as this might get a little confusing if
* 0.1.percent was written TODO: Discuss
* Note that we do not currently allow 10.percent (ie no quotes) as this might get a little confusing if 0.1.percent was
* written. Additionally, there is a possibility of creating a precision error in the implicit conversion.
*/
val String.percent: PercentageRatioUnit get() = PercentageRatioUnit(this)

View File

@ -1,15 +0,0 @@
package com.r3corda.contracts.cash
import com.r3corda.core.contracts.IssuanceDefinition
import com.r3corda.core.contracts.PartyAndReference
import java.util.*
/**
* Subset of cash-like contract state, containing the issuance definition. If these definitions match for two
* contracts' states, those states can be aggregated.
*/
interface AssetIssuanceDefinition<T> : IssuanceDefinition {
/** Where the underlying asset backing this ledger entry can be found (propagated) */
val deposit: PartyAndReference
val token: T
}

View File

@ -3,10 +3,11 @@ package com.r3corda.contracts.cash
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.newSecureRandom
import com.r3corda.core.crypto.toStringShort
import com.r3corda.core.node.services.Wallet
import com.r3corda.core.utilities.Emoji
import java.security.PublicKey
import java.security.SecureRandom
import java.util.*
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -44,28 +45,25 @@ class Cash : FungibleAsset<Currency>() {
*/
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.gov/cash-claims.html")
data class IssuanceDefinition<T>(
/** Where the underlying currency backing this ledger entry can be found (propagated) */
override val deposit: PartyAndReference,
override val token: T
) : AssetIssuanceDefinition<T>
/** A state representing a cash claim against some party */
data class State(
/** Where the underlying currency backing this ledger entry can be found (propagated) */
override val deposit: PartyAndReference,
override val amount: Amount<Currency>,
override val amount: Amount<Issued<Currency>>,
/** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey,
override val notary: Party
override val owner: PublicKey
) : FungibleAsset.State<Currency> {
override val issuanceDef: IssuanceDefinition<Currency>
get() = IssuanceDefinition(deposit, amount.token)
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey)
: this(Amount(amount.quantity, Issued<Currency>(deposit, amount.token)), owner)
override val deposit: PartyAndReference
get() = amount.token.issuer
override val contract = CASH_PROGRAM_ID
override val issuanceDef: Issued<Currency>
get() = amount.token
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 toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
@ -74,37 +72,55 @@ class Cash : FungibleAsset<Currency>() {
// Just for grouping
interface Commands : CommandData {
class Move() : TypeOnlyCommandData(), FungibleAsset.Commands.Move
/**
* 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
*/
data class Move(override val contractHash: SecureHash? = null) : FungibleAsset.Commands.Move, Commands
/**
* 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.
*/
data class Issue(override val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : FungibleAsset.Commands.Issue
data class Issue(override val nonce: Long = newSecureRandom().nextLong()) : FungibleAsset.Commands.Issue, Commands
/**
* A command stating that money has been withdrawn from the shared ledger and is now accounted for
* in some other way.
*/
data class Exit(override val amount: Amount<Currency>) : Commands, FungibleAsset.Commands.Exit<Currency>
data class Exit(override val amount: Amount<Issued<Currency>>) : Commands, FungibleAsset.Commands.Exit<Currency>
}
/**
* Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.
*/
fun generateIssue(tx: TransactionBuilder, issuanceDef: AssetIssuanceDefinition<Currency>, pennies: Long, owner: PublicKey, notary: Party)
= generateIssue(tx, Amount(pennies, issuanceDef.token), issuanceDef.deposit, owner, notary)
fun generateIssue(tx: TransactionBuilder, tokenDef: Issued<Currency>, pennies: Long, owner: PublicKey, notary: Party)
= generateIssue(tx, Amount(pennies, tokenDef), owner, notary)
/**
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
*/
fun generateIssue(tx: TransactionBuilder, amount: Amount<Currency>, at: PartyAndReference, owner: PublicKey, notary: Party) {
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: PublicKey, notary: Party) {
check(tx.inputStates().isEmpty())
check(tx.outputStates().sumCashOrNull() == null)
tx.addOutputState(Cash.State(at, amount, owner, notary))
check(tx.outputStates().map { it.data }.sumCashOrNull() == null)
val at = amount.token.issuer
tx.addOutputState(TransactionState(Cash.State(amount, owner), notary))
tx.addCommand(Cash.Commands.Issue(), at.party.owningKey)
}
/**
* 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: it's up to you to do that.
*/
@Throws(InsufficientBalanceException::class)
fun generateSpend(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, to: PublicKey,
cashStates: List<StateAndRef<State>>): List<PublicKey> =
generateSpend(tx, Amount(amount.quantity, amount.token.product), to, cashStates,
setOf(amount.token.issuer.party))
/**
* 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: it's up to you to do that.
@ -138,45 +154,51 @@ class Cash : FungibleAsset<Currency>() {
val currency = amount.token
val acceptableCoins = run {
val ofCurrency = cashStates.filter { it.state.amount.token == currency }
val ofCurrency = cashStates.filter { it.state.data.amount.token.product == currency }
if (onlyFromParties != null)
ofCurrency.filter { it.state.deposit.party in onlyFromParties }
ofCurrency.filter { it.state.data.deposit.party in onlyFromParties }
else
ofCurrency
}
val gathered = arrayListOf<StateAndRef<State>>()
var gatheredAmount = Amount(0, currency)
var takeChangeFrom: StateAndRef<State>? = null
for (c in acceptableCoins) {
if (gatheredAmount >= amount) break
gathered.add(c)
gatheredAmount += c.state.amount
gatheredAmount += Amount(c.state.data.amount.quantity, currency)
takeChangeFrom = c
}
if (gatheredAmount < amount)
throw InsufficientBalanceException(amount - gatheredAmount)
val change = gatheredAmount - amount
val keysUsed = gathered.map { it.state.owner }.toSet()
val change = if (takeChangeFrom != null && gatheredAmount > amount) {
Amount<Issued<Currency>>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.data.issuanceDef)
} else {
null
}
val keysUsed = gathered.map { it.state.data.owner }.toSet()
val states = gathered.groupBy { it.state.deposit }.map {
val (deposit, coins) = it
val totalAmount = coins.map { it.state.amount }.sumOrThrow()
State(deposit, totalAmount, to, coins.first().state.notary)
val states = gathered.groupBy { it.state.data.deposit }.map {
val coins = it.value
val totalAmount = coins.map { it.state.data.amount }.sumOrThrow()
TransactionState(State(totalAmount, to), coins.first().state.notary)
}
val outputs = if (change.quantity > 0) {
val outputs = if (change != null) {
// Just copy a key across as the change key. In real life of course, this works but leaks private data.
// In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow
// value flows through the transaction graph.
val changeKey = gathered.first().state.owner
val changeKey = gathered.first().state.data.owner
// Add a change output and adjust the last output downwards.
states.subList(0, states.lastIndex) +
states.last().let { it.copy(amount = it.amount - change) } +
State(gathered.last().state.deposit, change, changeKey, gathered.last().state.notary)
states.last().let { TransactionState(it.data.copy(amount = it.data.amount - change), it.notary) } +
TransactionState(State(change, changeKey), gathered.last().state.notary)
} else states
for (state in gathered) tx.addInputState(state.ref)
for (state in gathered) tx.addInputState(state)
for (state in outputs) tx.addOutputState(state)
// What if we already have a move command with the right keys? Filter it out here or in platform code?
val keysList = keysUsed.toList()
@ -204,4 +226,16 @@ fun Iterable<ContractState>.sumCash() = filterIsInstance<Cash.State>().map { it.
fun Iterable<ContractState>.sumCashOrNull() = filterIsInstance<Cash.State>().map { it.amount }.sumOrNull()
/** Sums the cash states in the list, returning zero of the given currency if there are none. */
fun Iterable<ContractState>.sumCashOrZero(currency: Currency) = filterIsInstance<Cash.State>().map { it.amount }.sumOrZero(currency)
fun Iterable<ContractState>.sumCashOrZero(currency: Issued<Currency>) = filterIsInstance<Cash.State>().map { it.amount }.sumOrZero<Issued<Currency>>(currency)
/**
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
* which we have no cash evaluate to null (not present in map), not 0.
*/
val Wallet.cashBalances: Map<Currency, Amount<Currency>> get() = states.
// Select the states we own which are cash, ignore the rest, take the amounts.
mapNotNull { (it.state.data as? Cash.State)?.amount }.
// Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
groupBy { it.token.product }.
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
mapValues { it.value.map { Amount(it.quantity, it.token.product) }.sumOrThrow() }

View File

@ -14,7 +14,7 @@ import java.util.*
// Cash-like
//
class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception()
class InsufficientBalanceException(val amountMissing: Amount<Currency>) : Exception()
/**
* Superclass for contracts representing assets which are fungible, countable and issued by a specific party. States
@ -30,43 +30,41 @@ class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception()
* (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.)
*/
abstract class FungibleAsset<T> : Contract {
/** A state representing a claim against some party */
interface State<T> : FungibleAssetState<T, AssetIssuanceDefinition<T>> {
/** Where the underlying asset backing this ledger entry can be found (propagated) */
override val deposit: PartyAndReference
override val amount: Amount<T>
/** A state representing a cash claim against some party */
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>>
/** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey
override val notary: Party
}
// Just for grouping
interface Commands : CommandData {
interface Move : Commands
interface Move : MoveCommand, Commands
/**
* Allows new asset states to be issued into existence: the nonce ("number used once") ensures the transaction
* has a unique ID even when there are no inputs.
*/
interface Issue : Commands { val nonce: Long }
interface Issue : IssueCommand, Commands
/**
* A command stating that money has been withdrawn from the shared ledger and is now accounted for
* in some other way.
*/
interface Exit<T> : Commands { val amount: Amount<T> }
interface Exit<T> : Commands { val amount: Amount<Issued<T>> }
}
/** This is the function EVERYONE runs */
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Each group is a set of input/output states with distinct issuance definitions. These assets are not fungible
// and must be kept separated for bookkeeping purposes.
val groups = tx.groupStates() { it: FungibleAsset.State<T> -> it.issuanceDef }
for ((inputs, outputs, key) in groups) {
for ((inputs, outputs, token) in groups) {
// Either inputs or outputs could be empty.
val deposit = key.deposit
val token = key.token
val deposit = token.issuer
val issuer = deposit.party
requireThat {
@ -91,16 +89,16 @@ abstract class FungibleAsset<T> : Contract {
(inputAmount == outputAmount + amountExitingLedger)
}
verifyMoveCommands<Commands.Move>(inputs, tx)
verifyMoveCommand<Commands.Move>(inputs, tx)
}
}
}
private fun verifyIssueCommand(inputs: List<State<T>>,
outputs: List<State<T>>,
tx: TransactionForVerification,
tx: TransactionForContract,
issueCommand: AuthenticatedObject<Commands.Issue>,
token: T,
token: Issued<T>,
issuer: Party) {
// If we have an issue command, perform special processing: the group is allowed to have no inputs,
// and the output states must have a deposit reference owned by the signer.
@ -145,4 +143,4 @@ fun <T> Iterable<ContractState>.sumFungible() = filterIsInstance<FungibleAsset.S
fun <T> Iterable<ContractState>.sumFungibleOrNull() = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrNull()
/** Sums the asset states in the list, returning zero of the given token if there are none. */
fun <T> Iterable<ContractState>.sumFungibleOrZero(token: T) = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrZero(token)
fun <T> Iterable<ContractState>.sumFungibleOrZero(token: Issued<T>) = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrZero(token)

View File

@ -1,16 +1,15 @@
package com.r3corda.contracts.cash
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.OwnableState
import com.r3corda.core.contracts.PartyAndReference
import java.util.Currency
import java.security.PublicKey
/**
* Common elements of cash contract states.
*/
interface FungibleAssetState<T, I : AssetIssuanceDefinition<T>> : OwnableState {
interface FungibleAssetState<T, I> : OwnableState {
val issuanceDef: I
/** Where the underlying currency backing this ledger entry can be found (propagated) */
val deposit: PartyAndReference
val amount: Amount<T>
val amount: Amount<Issued<T>>
fun move(amount: Amount<Issued<T>>, owner: PublicKey): FungibleAssetState<T, I>
}

View File

@ -1,14 +1,19 @@
package com.r3corda.contracts.testing
import com.r3corda.contracts.*
import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.cash.CASH_PROGRAM_ID
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Contract
import com.r3corda.core.contracts.DUMMY_PROGRAM_ID
import com.r3corda.core.contracts.DummyContract
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.testing.DUMMY_NOTARY
import com.r3corda.core.testing.MINI_CORP
import com.r3corda.core.crypto.generateKeyPair
import java.security.PublicKey
import java.util.*
@ -18,12 +23,11 @@ val TEST_PROGRAM_MAP: Map<Contract, Class<out Contract>> = mapOf(
CASH_PROGRAM_ID to Cash::class.java,
CP_PROGRAM_ID to CommercialPaper::class.java,
JavaCommercialPaper.JCP_PROGRAM_ID to JavaCommercialPaper::class.java,
CROWDFUND_PROGRAM_ID to CrowdFund::class.java,
DUMMY_PROGRAM_ID to DummyContract::class.java,
IRS_PROGRAM_ID to InterestRateSwap::class.java
)
fun generateState(notary: Party = DUMMY_NOTARY) = DummyContract.State(Random().nextInt(), notary)
fun generateState() = DummyContract.State(Random().nextInt())
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
@ -43,13 +47,43 @@ fun generateState(notary: Party = DUMMY_NOTARY) = DummyContract.State(Random().n
// contract.accepts() -> should pass
// contract `fails requirement` "some substring of the error message"
// }
//
// TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block
infix fun Cash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
infix fun Cash.State.`issued by`(party: Party) = copy(deposit = deposit.copy(party = party))
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner = owner)
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
// For Java compatibility please define helper methods here and then define the infix notation
object JavaTestHelpers {
@JvmStatic fun ownedBy(state: Cash.State, owner: PublicKey) = state.copy(owner = owner)
@JvmStatic fun issuedBy(state: Cash.State, party: Party) = state.copy(amount = Amount<Issued<Currency>>(state.amount.quantity, state.issuanceDef.copy(issuer = state.deposit.copy(party = party))))
@JvmStatic fun issuedBy(state: Cash.State, deposit: PartyAndReference) = state.copy(amount = Amount<Issued<Currency>>(state.amount.quantity, state.issuanceDef.copy(issuer = deposit)))
@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 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)
@JvmStatic fun withNotary(state: ContractState, notary: Party) = TransactionState(state, notary)
@JvmStatic fun CASH(amount: Amount<Currency>) = Cash.State(
Amount<Issued<Currency>>(amount.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, amount.token)),
NullPublicKey)
@JvmStatic fun STATE(amount: Amount<Issued<Currency>>) = Cash.State(amount, NullPublicKey)
}
infix fun Cash.State.`owned by`(owner: PublicKey) = JavaTestHelpers.ownedBy(this, owner)
infix fun Cash.State.`issued by`(party: Party) = JavaTestHelpers.issuedBy(this, party)
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = JavaTestHelpers.issuedBy(this, deposit)
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 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)
infix fun ContractState.`with notary`(notary: Party) = JavaTestHelpers.withNotary(this, notary)
val DUMMY_CASH_ISSUER_KEY = generateKeyPair()
val DUMMY_CASH_ISSUER = Party("Snake Oil Issuer", DUMMY_CASH_ISSUER_KEY.public).ref(1)
/** Allows you to write 100.DOLLARS.CASH */
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 Amount<Currency>.CASH: Cash.State get() = Cash.State(MINI_CORP.ref(1, 2, 3), this, NullPublicKey, DUMMY_NOTARY)

View File

@ -0,0 +1,82 @@
@file:JvmName("WalletFiller")
package com.r3corda.contracts.testing
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.crypto.Party
import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.Wallet
import com.r3corda.core.serialization.OpaqueBytes
import com.r3corda.core.testing.DUMMY_NOTARY
import java.util.*
/**
* Creates a random set of between (by default) 3 and 10 cash states that add up to the given amount and adds them
* to the wallet. This is intended for unit tests.
*
* The cash is self issued with the current nodes identity, as fetched from the storage service. Thus it
* would not be trusted by any sensible market participant and is effectively an IOU. If it had been issued by
* the central bank, well ... that'd be a different story altogether.
*
* The service hub needs to provide at least a key management service and a storage service.
*
* @return a wallet object that represents the generated states (it will NOT be the full wallet from the service hub!)
*/
fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>,
notary: Party = DUMMY_NOTARY,
atLeastThisManyStates: Int = 3,
atMostThisManyStates: Int = 10,
rng: Random = Random(),
ref: OpaqueBytes = OpaqueBytes(ByteArray(1, { 0 }))): Wallet {
val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng)
val myIdentity = storageService.myLegalIdentity
val myKey = storageService.myLegalIdentityKey
// We will allocate one state to one transaction, for simplicities sake.
val cash = Cash()
val transactions: List<SignedTransaction> = amounts.map { pennies ->
// This line is what makes the cash self issued. We just use zero as our deposit reference: we don't need
// this field as there's no other database or source of truth we need to sync with.
val depositRef = myIdentity.ref(ref)
val issuance = TransactionType.General.Builder()
val freshKey = keyManagementService.freshKey()
cash.generateIssue(issuance, Amount(pennies, Issued(depositRef, howMuch.token)), freshKey.public, notary)
issuance.signWith(myKey)
return@map issuance.toSignedTransaction(true)
}
recordTransactions(transactions)
// Get all the StateRefs of all the generated transactions.
val states = transactions.flatMap { stx ->
stx.tx.outputs.indices.map { i -> stx.tx.outRef<Cash.State>(i) }
}
return Wallet(states)
}
private fun calculateRandomlySizedAmounts(howMuch: Amount<Currency>, min: Int, max: Int, rng: Random): LongArray {
val numStates = min + Math.floor(rng.nextDouble() * (max - min)).toInt()
val amounts = LongArray(numStates)
val baseSize = howMuch.quantity / numStates
var filledSoFar = 0L
for (i in 0..numStates - 1) {
if (i < numStates - 1) {
// Adjust the amount a bit up or down, to give more realistic amounts (not all identical).
amounts[i] = baseSize + (baseSize / 2 * (rng.nextDouble() - 0.5)).toLong()
filledSoFar += amounts[i]
} else {
// Handle inexact rounding.
amounts[i] = howMuch.quantity - filledSoFar
}
}
check(amounts.sum() == howMuch.quantity)
return amounts
}

View File

@ -17,7 +17,7 @@ import com.r3corda.core.utilities.trace
import java.security.KeyPair
import java.security.PublicKey
import java.security.SignatureException
import java.util.Currency
import java.util.*
/**
* This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
@ -45,7 +45,7 @@ import java.util.Currency
object TwoPartyTradeProtocol {
val TRADE_TOPIC = "platform.trade"
class UnacceptablePriceException(val givenPrice: Amount<Currency>) : Exception()
class UnacceptablePriceException(val givenPrice: Amount<Issued<Currency>>) : Exception()
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
}
@ -53,7 +53,7 @@ object TwoPartyTradeProtocol {
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>,
val price: Amount<Currency>,
val price: Amount<Issued<Currency>>,
val sellerOwnerKey: PublicKey,
val sessionID: Long
)
@ -64,7 +64,7 @@ object TwoPartyTradeProtocol {
open class Seller(val otherSide: SingleMessageRecipient,
val notaryNode: NodeInfo,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount<Currency>,
val price: Amount<Issued<Currency>>,
val myKeyPair: KeyPair,
val buyerSessionID: Long,
override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() {
@ -97,7 +97,7 @@ object TwoPartyTradeProtocol {
@Suspendable
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol.Client(stx.tx))
return subProtocol(NotaryProtocol.Client(stx))
}
@Suspendable
@ -129,7 +129,7 @@ object TwoPartyTradeProtocol {
// This verifies that the transaction is contract-valid, even though it is missing signatures.
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments))
if (wtx.outputs.sumCashBy(myKeyPair.public) != price)
if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public) != price)
throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
// There are all sorts of funny games a malicious secondary might play here, we should fix them:
@ -174,7 +174,7 @@ object TwoPartyTradeProtocol {
open class Buyer(val otherSide: SingleMessageRecipient,
val notary: Party,
val acceptablePrice: Amount<Currency>,
val acceptablePrice: Amount<Issued<Currency>>,
val typeToBuy: Class<out OwnableState>,
val sessionID: Long) : ProtocolLogic<SignedTransaction>() {
@ -221,7 +221,7 @@ object TwoPartyTradeProtocol {
progressTracker.currentStep = VERIFYING
maybeTradeRequest.validate {
// What is the seller trying to sell us?
val asset = it.assetForSale.state
val asset = it.assetForSale.state.data
val assetTypeName = asset.javaClass.name
logger.trace { "Got trade request for a $assetTypeName: ${it.assetForSale}" }
@ -266,21 +266,21 @@ object TwoPartyTradeProtocol {
}
private fun assembleSharedTX(tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> {
val ptx = TransactionBuilder()
val ptx = TransactionType.General.Builder()
// Add input and output states for the movement of cash, by using the Cash contract to generate the states.
val wallet = serviceHub.walletService.currentWallet
val cashStates = wallet.statesOfType<Cash.State>()
val cashSigningPubKeys = Cash().generateSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey, cashStates)
// Add inputs/outputs/a command for the movement of the asset.
ptx.addInputState(tradeRequest.assetForSale.ref)
ptx.addInputState(tradeRequest.assetForSale)
// Just pick some new public key for now. This won't be linked with our identity in any way, which is what
// we want for privacy reasons: the key is here ONLY to manage and control ownership, it is not intended to
// reveal who the owner actually is. The key management service is expected to derive a unique key from some
// initial seed in order to provide privacy protection.
val freshKey = serviceHub.keyManagementService.freshKey()
val (command, state) = tradeRequest.assetForSale.state.withNewOwner(freshKey.public)
ptx.addOutputState(state)
ptx.addCommand(command, tradeRequest.assetForSale.state.owner)
val (command, state) = tradeRequest.assetForSale.state.data.withNewOwner(freshKey.public)
ptx.addOutputState(state, tradeRequest.assetForSale.state.notary)
ptx.addCommand(command, tradeRequest.assetForSale.state.data.owner)
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.

View File

@ -0,0 +1,58 @@
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,15 +1,13 @@
package com.r3corda.contracts
import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.testing.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.days
import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.seconds
import com.r3corda.core.testing.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@ -29,9 +27,8 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State(
MEGA_CORP.ref(123),
MEGA_CORP_PUBKEY,
1000.DOLLARS,
TEST_TX_TIME + 7.days,
DUMMY_NOTARY
1000.DOLLARS `issued by` MEGA_CORP.ref(123),
TEST_TX_TIME + 7.days
)
override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue()
@ -43,9 +40,8 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123),
owner = MEGA_CORP_PUBKEY,
faceValue = 1000.DOLLARS,
maturityDate = TEST_TX_TIME + 7.days,
notary = DUMMY_NOTARY
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
maturityDate = TEST_TX_TIME + 7.days
)
override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue()
@ -64,6 +60,7 @@ class CommercialPaperTestsGeneric {
lateinit var thisTest: ICommercialPaperTestTemplate
val attachments = MockStorageService().attachments
val issuer = MEGA_CORP.ref(123)
@Test
fun ok() {
@ -92,7 +89,7 @@ class CommercialPaperTestsGeneric {
fun `face value is not zero`() {
transactionGroup {
transaction {
output { thisTest.getPaper().withFaceValue(0.DOLLARS) }
output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME)
}
@ -118,7 +115,7 @@ class CommercialPaperTestsGeneric {
fun `issue cannot replace an existing state`() {
transactionGroup {
roots {
transaction(thisTest.getPaper() label "paper")
transaction(thisTest.getPaper() `with notary` DUMMY_NOTARY label "paper")
}
transaction {
input("paper")
@ -133,7 +130,7 @@ class CommercialPaperTestsGeneric {
@Test
fun `did not receive enough money at redemption`() {
trade(aliceGetsBack = 700.DOLLARS).expectFailureOfTx(3, "received amount equals the face value")
trade(aliceGetsBack = 700.DOLLARS `issued by` issuer).expectFailureOfTx(3, "received amount equals the face value")
}
@Test
@ -141,16 +138,16 @@ class CommercialPaperTestsGeneric {
trade(destroyPaperAtRedemption = false).expectFailureOfTx(3, "must be destroyed")
}
fun cashOutputsToWallet(vararg states: Cash.State): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> {
val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*states), emptyList(), SecureHash.randomSHA256())
return Pair(ltx, states.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
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)) })
}
@Test
fun `issue move and then redeem`() {
// MiniCorp issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself.
val issueTX: LedgerTransaction = run {
val ptx = CommercialPaper().generateIssue(MINI_CORP.ref(123), 10000.DOLLARS, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
val ptx = CommercialPaper().generateIssue(10000.DOLLARS `issued by` MINI_CORP.ref(123), TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
@ -160,14 +157,14 @@ class CommercialPaperTestsGeneric {
}
val (alicesWalletTX, alicesWallet) = cashOutputsToWallet(
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY,
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY,
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY,
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY,
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY
)
// Alice pays $9000 to MiniCorp to own some of their debt.
val moveTX: LedgerTransaction = run {
val ptx = TransactionBuilder()
val ptx = TransactionType.General.Builder()
Cash().generateSpend(ptx, 9000.DOLLARS, MINI_CORP_PUBKEY, alicesWallet)
CommercialPaper().generateMove(ptx, issueTX.outRef(0), ALICE_PUBKEY)
ptx.signWith(MINI_CORP_KEY)
@ -178,12 +175,12 @@ class CommercialPaperTestsGeneric {
// Won't be validated.
val (corpWalletTX, corpWallet) = cashOutputsToWallet(
9000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY,
4000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY
9000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY `with notary` DUMMY_NOTARY,
4000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY `with notary` DUMMY_NOTARY
)
fun makeRedeemTX(time: Instant): LedgerTransaction {
val ptx = TransactionBuilder()
val ptx = TransactionType.General.Builder()
ptx.setTime(time, DUMMY_NOTARY, 30.seconds)
CommercialPaper().generateRedeem(ptx, moveTX.outRef(1), corpWallet)
ptx.signWith(ALICE_KEY)
@ -205,13 +202,13 @@ class CommercialPaperTestsGeneric {
// Generate a trade lifecycle with various parameters.
fun trade(redemptionTime: Instant = TEST_TX_TIME + 8.days,
aliceGetsBack: Amount<Currency> = 1000.DOLLARS,
aliceGetsBack: Amount<Issued<Currency>> = 1000.DOLLARS `issued by` issuer,
destroyPaperAtRedemption: Boolean = true): TransactionGroupDSL<ICommercialPaperState> {
val someProfits = 1200.DOLLARS
val someProfits = 1200.DOLLARS `issued by` issuer
return transactionGroupFor() {
roots {
transaction(900.DOLLARS.CASH `owned by` ALICE_PUBKEY label "alice's $900")
transaction(someProfits.CASH `owned by` MEGA_CORP_PUBKEY label "some profits")
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.
@ -226,8 +223,8 @@ class CommercialPaperTestsGeneric {
transaction("Trade") {
input("paper")
input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `owned by` MEGA_CORP_PUBKEY }
output("alice's paper") { "paper".output `owned by` ALICE_PUBKEY }
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() }
}
@ -238,10 +235,10 @@ class CommercialPaperTestsGeneric {
input("alice's paper")
input("some profits")
output("Alice's profit") { aliceGetsBack.CASH `owned by` ALICE_PUBKEY }
output("Change") { (someProfits - aliceGetsBack).CASH `owned by` MEGA_CORP_PUBKEY }
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 }
output { "paper".output.data }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() }

View File

@ -1,165 +0,0 @@
package com.r3corda.contracts
import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`owned by`
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.days
import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.seconds
import com.r3corda.core.testing.*
import org.junit.Test
import java.time.Instant
import java.util.*
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class CrowdFundTests {
val CF_1 = CrowdFund.State(
campaign = CrowdFund.Campaign(
owner = MINI_CORP_PUBKEY,
name = "kickstart me",
target = 1000.DOLLARS,
closingTime = TEST_TX_TIME + 7.days
),
closed = false,
pledges = ArrayList<CrowdFund.Pledge>(),
notary = DUMMY_NOTARY
)
val attachments = MockStorageService().attachments
@Test
fun `key mismatch at issue`() {
transactionGroup {
transaction {
output { CF_1 }
arg(DUMMY_PUBKEY_1) { CrowdFund.Commands.Register() }
timestamp(TEST_TX_TIME)
}
expectFailureOfTx(1, "the transaction is signed by the owner of the crowdsourcing")
}
}
@Test
fun `closing time not in the future`() {
transactionGroup {
transaction {
output { CF_1.copy(campaign = CF_1.campaign.copy(closingTime = TEST_TX_TIME - 1.days)) }
arg(MINI_CORP_PUBKEY) { CrowdFund.Commands.Register() }
timestamp(TEST_TX_TIME)
}
expectFailureOfTx(1, "the output registration has a closing time in the future")
}
}
@Test
fun ok() {
raiseFunds().verify()
}
private fun raiseFunds(): TransactionGroupDSL<CrowdFund.State> {
return transactionGroupFor {
roots {
transaction(1000.DOLLARS.CASH `owned by` ALICE_PUBKEY label "alice's $1000")
}
// 1. Create the funding opportunity
transaction {
output("funding opportunity") { CF_1 }
arg(MINI_CORP_PUBKEY) { CrowdFund.Commands.Register() }
timestamp(TEST_TX_TIME)
}
// 2. Place a pledge
transaction {
input ("funding opportunity")
input("alice's $1000")
output ("pledged opportunity") {
CF_1.copy(
pledges = CF_1.pledges + CrowdFund.Pledge(ALICE_PUBKEY, 1000.DOLLARS)
)
}
output { 1000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY }
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(ALICE_PUBKEY) { CrowdFund.Commands.Pledge() }
timestamp(TEST_TX_TIME)
}
// 3. Close the opportunity, assuming the target has been met
transaction {
input ("pledged opportunity")
output ("funded and closed") { "pledged opportunity".output.copy(closed = true) }
arg(MINI_CORP_PUBKEY) { CrowdFund.Commands.Close() }
timestamp(time = TEST_TX_TIME + 8.days)
}
}
}
fun cashOutputsToWallet(vararg states: Cash.State): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> {
val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*states), emptyList(), SecureHash.randomSHA256())
return Pair(ltx, states.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
}
@Test
fun `raise more funds using output-state generation functions`() {
// MiniCorp registers a crowdfunding of $1,000, to close in 7 days.
val registerTX: LedgerTransaction = run {
// craftRegister returns a partial transaction
val ptx = CrowdFund().generateRegister(MINI_CORP.ref(123), 1000.DOLLARS, "crowd funding", TEST_TX_TIME + 7.days, DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}
ptx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments)
}
// let's give Alice some funds that she can invest
val (aliceWalletTX, aliceWallet) = cashOutputsToWallet(
200.DOLLARS.CASH `owned by` ALICE_PUBKEY,
500.DOLLARS.CASH `owned by` ALICE_PUBKEY,
300.DOLLARS.CASH `owned by` ALICE_PUBKEY
)
// Alice pays $1000 to MiniCorp to fund their campaign.
val pledgeTX: LedgerTransaction = run {
val ptx = TransactionBuilder()
CrowdFund().generatePledge(ptx, registerTX.outRef(0), ALICE_PUBKEY)
Cash().generateSpend(ptx, 1000.DOLLARS, MINI_CORP_PUBKEY, aliceWallet)
ptx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
ptx.signWith(ALICE_KEY)
ptx.signWith(DUMMY_NOTARY_KEY)
// this verify passes - the transaction contains an output cash, necessary to verify the fund command
ptx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments)
}
// Won't be validated.
val (miniCorpWalletTx, miniCorpWallet) = cashOutputsToWallet(
900.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY,
400.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY
)
// MiniCorp closes their campaign.
fun makeFundedTX(time: Instant): LedgerTransaction {
val ptx = TransactionBuilder()
ptx.setTime(time, DUMMY_NOTARY, 30.seconds)
CrowdFund().generateClose(ptx, pledgeTX.outRef(0), miniCorpWallet)
ptx.signWith(MINI_CORP_KEY)
ptx.signWith(DUMMY_NOTARY_KEY)
return ptx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments)
}
val tooEarlyClose = makeFundedTX(TEST_TX_TIME + 6.days)
val validClose = makeFundedTX(TEST_TX_TIME + 8.days)
val e = assertFailsWith(TransactionVerificationException::class) {
TransactionGroup(setOf(registerTX, pledgeTX, tooEarlyClose), setOf(miniCorpWalletTx, aliceWalletTX)).verify()
}
assertTrue(e.cause!!.message!!.contains("the closing date has past"))
// This verification passes
TransactionGroup(setOf(registerTX, pledgeTX, validClose), setOf(aliceWalletTX)).verify()
}
}

View File

@ -27,7 +27,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
rollConvention = DateRollConvention.ModifiedFollowing,
dayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 0,
paymentDelay = 3,
paymentCalendar = BusinessCalendar.getInstance("London", "NewYork"),
interestPeriodAdjustment = AccrualAdjustment.Adjusted
)
@ -47,10 +47,10 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
dayInMonth = 10,
resetDayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 0,
paymentDelay = 3,
paymentCalendar = BusinessCalendar.getInstance("London", "NewYork"),
interestPeriodAdjustment = AccrualAdjustment.Adjusted,
fixingPeriod = DateOffset.TWODAYS,
fixingPeriodOffset = 2,
resetRule = PaymentRule.InAdvance,
fixingsPerPayment = Frequency.Quarterly,
fixingCalendar = BusinessCalendar.getInstance("London"),
@ -97,7 +97,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
)
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY)
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
}
2 -> {
// 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides)
@ -140,7 +140,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
paymentDelay = 0,
paymentCalendar = BusinessCalendar.getInstance(),
interestPeriodAdjustment = AccrualAdjustment.Adjusted,
fixingPeriod = DateOffset.TWODAYS,
fixingPeriodOffset = 2,
resetRule = PaymentRule.InAdvance,
fixingsPerPayment = Frequency.Quarterly,
fixingCalendar = BusinessCalendar.getInstance(),
@ -187,7 +187,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
)
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY)
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
}
else -> TODO("IRS number $irsSelect not defined")
@ -198,18 +198,6 @@ class IRSTests {
val attachments = MockStorageService().attachments
val exampleIRS = createDummyIRS(1)
val inState = InterestRateSwap.State(
exampleIRS.fixedLeg,
exampleIRS.floatingLeg,
exampleIRS.calculation,
exampleIRS.common,
DUMMY_NOTARY
)
val outState = inState.copy()
@Test
fun ok() {
trade().verify()
@ -255,7 +243,7 @@ class IRSTests {
* Utility so I don't have to keep typing this
*/
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
return generateIRSTxn(irsSelector).outputs.filterIsInstance<InterestRateSwap.State>().single()
return generateIRSTxn(irsSelector).outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
}
/**
@ -287,7 +275,7 @@ class IRSTests {
newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.value)))
}
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common, DUMMY_NOTARY)
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common)
println(newIRS.exportIRSToCSV())
}
@ -306,13 +294,13 @@ class IRSTests {
@Test
fun generateIRSandFixSome() {
var previousTXN = generateIRSTxn(1)
var currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single()
var currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint())
while (true) {
val nextFixingDate = currentIRS.calculation.nextFixingDate() ?: break
println("\n\n\n ***** Applying a fixing to $nextFixingDate \n\n\n")
var fixTX: LedgerTransaction = run {
val tx = TransactionBuilder()
val fixTX: LedgerTransaction = run {
val tx = TransactionType.General.Builder()
val fixing = Pair(nextFixingDate, FixedRate("0.052".percent))
InterestRateSwap().generateFix(tx, previousTXN.outRef(0), fixing)
with(tx) {
@ -323,7 +311,7 @@ class IRSTests {
}
tx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments)
}
currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single()
currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint())
previousTXN = fixTX
}
@ -360,7 +348,7 @@ class IRSTests {
for (i in stuffToPrint) {
println(i)
var z = dummyIRS.evaluateCalculation(LocalDate.of(2016, 9, 12), Expression(i))
val z = dummyIRS.evaluateCalculation(LocalDate.of(2016, 9, 15), Expression(i))
println(z.javaClass)
println(z)
println("-----------")
@ -387,11 +375,11 @@ class IRSTests {
transaction("Fix") {
input("irs post agreement")
output("irs post first fixing") {
"irs post agreement".output.copy(
"irs post agreement".output.fixedLeg,
"irs post agreement".output.floatingLeg,
"irs post agreement".output.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
"irs post agreement".output.common
"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
)
}
arg(ORACLE_PUBKEY) {
@ -573,6 +561,7 @@ class IRSTests {
output("irs post agreement") { singleIRS() }
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this.accepts()
}
val oldIRS = singleIRS(1)
@ -619,7 +608,7 @@ class IRSTests {
val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first()
val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey]
var modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.quantity, Currency.getInstance("JPY")))
val modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.quantity, Currency.getInstance("JPY")))
output() {
newIRS.copy(
@ -640,7 +629,7 @@ class IRSTests {
arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key }
var modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY")))
val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY")))
output() {
newIRS.copy(
@ -677,7 +666,6 @@ class IRSTests {
irs.floatingLeg,
irs.calculation,
irs.common.copy(tradeID = "t1")
)
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
@ -691,7 +679,6 @@ class IRSTests {
irs.floatingLeg,
irs.calculation,
irs.common.copy(tradeID = "t2")
)
}
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
@ -702,19 +689,19 @@ class IRSTests {
input("irs post agreement1")
input("irs post agreement2")
output("irs post first fixing1") {
"irs post agreement1".output.copy(
"irs post agreement1".output.fixedLeg,
"irs post agreement1".output.floatingLeg,
"irs post agreement1".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
"irs post agreement1".output.common.copy(tradeID = "t1")
"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")
)
}
output("irs post first fixing2") {
"irs post agreement2".output.copy(
"irs post agreement2".output.fixedLeg,
"irs post agreement2".output.floatingLeg,
"irs post agreement2".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
"irs post agreement2".output.common.copy(tradeID = "t2")
"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")
)
}

View File

@ -1,8 +1,9 @@
package com.r3corda.contracts.cash
import com.r3corda.contracts.DummyContract
import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.testing.`with deposit`
import com.r3corda.contracts.testing.`with notary`
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
@ -11,22 +12,20 @@ import com.r3corda.core.testing.*
import org.junit.Test
import java.security.PublicKey
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.*
class CashTests {
val defaultRef = OpaqueBytes(ByteArray(1, {1}))
val defaultIssuer = MEGA_CORP.ref(defaultRef)
val inState = Cash.State(
deposit = MEGA_CORP.ref(1),
amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1,
notary = DUMMY_NOTARY
amount = 1000.DOLLARS `issued by` defaultIssuer,
owner = DUMMY_PUBKEY_1
)
val outState = inState.copy(owner = DUMMY_PUBKEY_2)
fun Cash.State.editDepositRef(ref: Byte) = copy(deposit = deposit.copy(reference = OpaqueBytes.of(ref)))
fun Cash.State.editDepositRef(ref: Byte) = copy(
amount = Amount(amount.quantity, token = amount.token.copy(deposit.copy(reference = OpaqueBytes.of(ref))))
)
@Test
fun trivial() {
@ -35,7 +34,7 @@ class CashTests {
this `fails requirement` "the amounts balance"
tweak {
output { outState.copy(amount = 2000.DOLLARS) }
output { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "the amounts balance"
}
tweak {
@ -67,7 +66,7 @@ class CashTests {
fun issueMoney() {
// Check we can't "move" money into existence.
transaction {
input { DummyContract.State(notary = DUMMY_NOTARY) }
input { DummyContract.State() }
output { outState }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
@ -84,10 +83,8 @@ class CashTests {
transaction {
output {
Cash.State(
amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1,
deposit = MINI_CORP.ref(12, 34),
notary = DUMMY_NOTARY
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
owner = DUMMY_PUBKEY_1
)
}
tweak {
@ -99,20 +96,20 @@ class CashTests {
}
// Test generation works.
val ptx = TransactionBuilder()
Cash().generateIssue(ptx, 100.DOLLARS, MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
val ptx = TransactionType.General.Builder()
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(ptx.inputStates().isEmpty())
val s = ptx.outputStates()[0] as Cash.State
assertEquals(100.DOLLARS, s.amount)
val s = ptx.outputStates()[0].data as Cash.State
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
assertEquals(MINI_CORP, s.deposit.party)
assertEquals(DUMMY_PUBKEY_1, s.owner)
assertTrue(ptx.commands()[0].value is Cash.Commands.Issue)
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0])
// Test issuance from the issuance definition
val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD)
val templatePtx = TransactionBuilder()
Cash().generateIssue(templatePtx, issuanceDef, 100.DOLLARS.quantity, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34)
val templatePtx = TransactionType.General.Builder()
Cash().generateIssue(templatePtx, amount, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(templatePtx.inputStates().isEmpty())
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
@ -178,16 +175,16 @@ class CashTests {
@Test(expected = IllegalStateException::class)
fun `reject issuance with inputs`() {
// Issue some cash
var ptx = TransactionBuilder()
var ptx = TransactionType.General.Builder()
Cash().generateIssue(ptx, 100.DOLLARS, MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
ptx.signWith(MINI_CORP_KEY)
val tx = ptx.toSignedTransaction()
// Include the previously issued cash in a new issuance command
ptx = TransactionBuilder()
ptx.addInputState(tx.tx.outRef<Cash.State>(0).ref)
Cash().generateIssue(ptx, 100.DOLLARS, MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
ptx = TransactionType.General.Builder()
ptx.addInputState(tx.tx.outRef<Cash.State>(0))
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
}
@Test
@ -221,13 +218,13 @@ class CashTests {
fun zeroSizedValues() {
transaction {
input { inState }
input { inState.copy(amount = 0.DOLLARS) }
input { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "zero sized inputs"
}
transaction {
input { inState }
output { inState }
output { inState.copy(amount = 0.DOLLARS) }
output { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "zero sized outputs"
}
}
@ -243,26 +240,26 @@ class CashTests {
// Can't change deposit reference when splitting.
transaction {
input { inState }
output { outState.editDepositRef(0).copy(amount = inState.amount / 2) }
output { outState.editDepositRef(1).copy(amount = inState.amount / 2) }
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"
}
// Can't mix currencies.
transaction {
input { inState }
output { outState.copy(amount = 800.DOLLARS) }
output { outState.copy(amount = 200.POUNDS) }
output { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) }
output { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) }
this `fails requirement` "the amounts balance"
}
transaction {
input { inState }
input {
inState.copy(
amount = 150.POUNDS,
amount = 150.POUNDS `issued by` defaultIssuer,
owner = DUMMY_PUBKEY_2
)
}
output { outState.copy(amount = 1150.DOLLARS) }
output { outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "the amounts balance"
}
// Can't have superfluous input states from different issuers.
@ -287,16 +284,16 @@ class CashTests {
// Single input/output straightforward case.
transaction {
input { inState }
output { outState.copy(amount = inState.amount - 200.DOLLARS) }
output { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS) }
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"
}
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command"
tweak {
@ -310,17 +307,17 @@ class CashTests {
input { inState }
input { inState `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - 200.DOLLARS) `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - 200.DOLLARS) }
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() }
this `fails requirement` "at issuer MegaCorp the amounts balance"
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "at issuer MiniCorp the amounts balance"
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) }
this.accepts()
}
}
@ -334,7 +331,7 @@ class CashTests {
// Can't merge them together.
tweak {
output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS) }
output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "at issuer MegaCorp the amounts balance"
}
// Missing MiniCorp deposit
@ -356,7 +353,7 @@ class CashTests {
fun multiCurrency() {
// Check we can do an atomic currency trade tx.
transaction {
val pounds = Cash.State(MINI_CORP.ref(3, 4, 5), 658.POUNDS, DUMMY_PUBKEY_2, DUMMY_NOTARY)
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), DUMMY_PUBKEY_2)
input { inState `owned by` DUMMY_PUBKEY_1 }
input { pounds }
output { inState `owned by` DUMMY_PUBKEY_2 }
@ -376,7 +373,7 @@ class CashTests {
fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) =
StateAndRef(
Cash.State(corp.ref(depositRef), amount, OUR_PUBKEY_1, DUMMY_NOTARY),
Cash.State(amount `issued by` corp.ref(depositRef), OUR_PUBKEY_1) `with notary` DUMMY_NOTARY,
StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
)
@ -388,7 +385,7 @@ class CashTests {
)
fun makeSpend(amount: Amount<Currency>, dest: PublicKey): WireTransaction {
val tx = TransactionBuilder()
val tx = TransactionType.General.Builder()
Cash().generateSpend(tx, amount, dest, WALLET)
return tx.toWireTransaction()
}
@ -397,13 +394,13 @@ class CashTests {
fun generateSimpleDirectSpend() {
val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1)
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0])
assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0].data)
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
fun generateSimpleSpendWithParties() {
val tx = TransactionBuilder()
val tx = TransactionType.General.Builder()
Cash().generateSpend(tx, 80.DOLLARS, ALICE_PUBKEY, WALLET, setOf(MINI_CORP))
assertEquals(WALLET[2].ref, tx.inputStates()[0])
}
@ -412,8 +409,8 @@ class CashTests {
fun generateSimpleSpendWithChange() {
val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1)
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS), wtx.outputs[0])
assertEquals(WALLET[0].state.copy(amount = 90.DOLLARS), wtx.outputs[1])
assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
assertEquals(WALLET[0].state.data.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data)
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@ -422,18 +419,19 @@ class CashTests {
val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1)
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[1].ref, wtx.inputs[1])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS), wtx.outputs[0])
assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
fun generateSpendMixedDeposits() {
val wtx = makeSpend(580.DOLLARS, THEIR_PUBKEY_1)
assertEquals(3, wtx.inputs.size)
assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[1].ref, wtx.inputs[1])
assertEquals(WALLET[2].ref, wtx.inputs[2])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS), wtx.outputs[0])
assertEquals(WALLET[2].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1])
assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
assertEquals(WALLET[2].state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1].data)
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@ -454,9 +452,9 @@ class CashTests {
*/
@Test
fun aggregation() {
val fiveThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 5000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
val twoThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 2000.DOLLARS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
val oneThousandDollarsFromMini = Cash.State(MINI_CORP.ref(3), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP_PUBKEY)
val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP_PUBKEY)
val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY)
// Obviously it must be possible to aggregate states with themselves
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
@ -470,28 +468,28 @@ class CashTests {
// States cannot be aggregated if the currency differs
assertNotEquals(oneThousandDollarsFromMini.issuanceDef,
Cash.State(MINI_CORP.ref(3), 1000.POUNDS, MEGA_CORP_PUBKEY, DUMMY_NOTARY).issuanceDef)
Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY).issuanceDef)
// States cannot be aggregated if the reference differs
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef)
assertNotEquals(fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef)
assertNotEquals((fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
}
@Test
fun `summing by owner`() {
val states = listOf(
Cash.State(MEGA_CORP.ref(1), 1000.DOLLARS, MINI_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 2000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY),
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
)
assertEquals(6000.DOLLARS, states.sumCashBy(MEGA_CORP_PUBKEY))
assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP_PUBKEY))
}
@Test(expected = UnsupportedOperationException::class)
fun `summing by owner throws`() {
val states = listOf(
Cash.State(MEGA_CORP.ref(1), 2000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
)
states.sumCashBy(MINI_CORP_PUBKEY)
}
@ -499,7 +497,7 @@ class CashTests {
@Test
fun `summing no currencies`() {
val states = emptyList<Cash.State>()
assertEquals(0.POUNDS, states.sumCashOrZero(GBP))
assertEquals(0.POUNDS `issued by` defaultIssuer, states.sumCashOrZero(GBP `issued by` defaultIssuer))
assertNull(states.sumCashOrNull())
}
@ -512,21 +510,21 @@ class CashTests {
@Test
fun `summing a single currency`() {
val states = listOf(
Cash.State(MEGA_CORP.ref(1), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 2000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
)
// Test that summing everything produces the total number of dollars
var expected = 7000.DOLLARS
var actual = states.sumCash()
val expected = 7000.DOLLARS `issued by` defaultIssuer
val actual = states.sumCash()
assertEquals(expected, actual)
}
@Test(expected = IllegalArgumentException::class)
fun `summing multiple currencies`() {
val states = listOf(
Cash.State(MEGA_CORP.ref(1), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.POUNDS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
)
// Test that summing everything fails because we're mixing units
states.sumCash()

View File

@ -3,6 +3,7 @@ version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: QuasarPlugin
buildscript {
repositories {
@ -21,11 +22,6 @@ repositories {
}
}
//noinspection GroovyAssignabilityCheck
configurations {
quasar
}
dependencies {
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:3.4.1'
@ -57,9 +53,6 @@ dependencies {
compile "com.esotericsoftware:kryo:3.0.3"
compile "de.javakaffee:kryo-serializers:0.37"
// Quasar: for the bytecode rewriting for state machines.
compile "co.paralleluniverse:quasar-core:${quasar_version}:jdk8"
// 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"
@ -67,39 +60,8 @@ dependencies {
// For JSON
compile "com.fasterxml.jackson.core:jackson-databind:2.5.5"
// Quasar: for the bytecode rewriting for state machines.
quasar "co.paralleluniverse:quasar-core:${quasar_version}:jdk8@jar"
// Java ed25519 implementation. See https://github.com/str4d/ed25519-java/
compile 'net.i2p.crypto:eddsa:0.1.0'
}
tasks.withType(Test) {
jvmArgs "-javaagent:${configurations.quasar.singleFile}"
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
}
tasks.withType(JavaExec) {
jvmArgs "-javaagent:${configurations.quasar.singleFile}"
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
}
// These lines tell gradle to run the Quasar suspendables scanner to look for unannotated super methods
// that have @Suspendable sub implementations. These tend to cause NPEs and are not caught by the verifier
// NOTE: need to make sure the output isn't on the classpath or every other run it generates empty results, so
// we explicitly delete to avoid that happening. We also need to turn off what seems to be a spurious warning in the IDE
//
// TODO: Make this task incremental, as it can be quite slow.
//noinspection GroovyAssignabilityCheck
task quasarScan(dependsOn: ['classes']) << {
ant.taskdef(name:'scanSuspendables', classname:'co.paralleluniverse.fibers.instrument.SuspendablesScanner',
classpath: "${sourceSets.main.output.classesDir}:${sourceSets.main.output.resourcesDir}:${configurations.runtime.asPath}")
delete "$sourceSets.main.output.resourcesDir/META-INF/suspendables", "$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers"
ant.scanSuspendables(
auto:false,
suspendablesFile: "$sourceSets.main.output.resourcesDir/META-INF/suspendables",
supersFile: "$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers") {
fileset(dir: sourceSets.main.output.classesDir)
}
}
jar.dependsOn quasarScan
quasarScan.dependsOn('classes')

View File

@ -4,6 +4,7 @@ import com.google.common.io.ByteStreams
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
import com.google.common.util.concurrent.SettableFuture
import com.r3corda.core.crypto.newSecureRandom
import org.slf4j.Logger
import java.io.BufferedInputStream
import java.io.InputStream
@ -36,7 +37,7 @@ fun String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else
* Returns a random positive long generated using a secure RNG. This function sacrifies a bit of entropy in order to
* avoid potential bugs where the value is used in a context where negative numbers are not expected.
*/
fun random63BitValue(): Long = Math.abs(SecureRandom.getInstanceStrong().nextLong())
fun random63BitValue(): Long = Math.abs(newSecureRandom().nextLong())
// Some utilities for working with Guava listenable futures.
fun <T> ListenableFuture<T>.then(executor: Executor, body: () -> Unit) = addListener(Runnable(body), executor)
@ -200,4 +201,4 @@ fun extractZipFile(zipPath: Path, toPath: Path) {
}
}
// TODO: Generic csv printing utility for clases.
// TODO: Generic csv printing utility for clases.

View File

@ -1,12 +1,11 @@
package com.r3corda.core.contracts
import com.r3corda.core.*
import com.r3corda.core.crypto.Party
import java.security.PublicKey
import java.util.*
/**
* Defines a simple domain specific language for the specificiation of financial contracts. Currently covers:
* Defines a simple domain specific language for the specification of financial contracts. Currently covers:
*
* - Some utilities for working with commands.
* - Code for working with currencies.
@ -20,15 +19,32 @@ import java.util.*
fun currency(code: String) = Currency.getInstance(code)
val USD = currency("USD")
val GBP = currency("GBP")
val CHF = currency("CHF")
// Java interop
object JavaTestHelpers {
@JvmStatic val USD: Currency get() = currency("USD")
@JvmStatic val GBP: Currency get() = currency("GBP")
@JvmStatic val CHF: Currency get() = currency("CHF")
val Int.DOLLARS: Amount<Currency> get() = Amount(this.toLong() * 100, USD)
val Int.POUNDS: Amount<Currency> get() = Amount(this.toLong() * 100, GBP)
val Int.SWISS_FRANCS: Amount<Currency> get() = Amount(this.toLong() * 100, CHF)
@JvmStatic fun DOLLARS(amount: Int) = Amount(amount.toLong() * 100, USD)
@JvmStatic fun DOLLARS(amount: Double) = Amount((amount * 100).toLong(), USD)
@JvmStatic fun POUNDS(amount: Int) = Amount(amount.toLong() * 100, GBP)
@JvmStatic fun SWISS_FRANCS(amount: Int) = Amount(amount.toLong() * 100, CHF)
val Double.DOLLARS: Amount<Currency> get() = Amount((this * 100).toLong(), USD)
@JvmStatic fun issuedBy(currency: Currency, deposit: PartyAndReference) = Issued<Currency>(deposit, currency)
@JvmStatic fun issuedBy(amount: Amount<Currency>, deposit: PartyAndReference) = Amount(amount.quantity, issuedBy(amount.token, deposit))
}
val USD = JavaTestHelpers.USD
val GBP = JavaTestHelpers.GBP
val CHF = JavaTestHelpers.CHF
val Int.DOLLARS: Amount<Currency> get() = JavaTestHelpers.DOLLARS(this)
val Double.DOLLARS: Amount<Currency> get() = JavaTestHelpers.DOLLARS(this)
val Int.POUNDS: Amount<Currency> get() = JavaTestHelpers.POUNDS(this)
val Int.SWISS_FRANCS: Amount<Currency> get() = JavaTestHelpers.SWISS_FRANCS(this)
infix fun Currency.`issued by`(deposit: PartyAndReference) = JavaTestHelpers.issuedBy(this, deposit)
infix fun Amount<Currency>.`issued by`(deposit: PartyAndReference) = JavaTestHelpers.issuedBy(this, deposit)
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////
@ -90,13 +106,23 @@ 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> verifyMoveCommands(inputs: List<OwnableState>, tx: TransactionForVerification) {
inline fun <reified T : CommandData> verifyMoveCommand(inputs: List<OwnableState>, tx: TransactionForContract) {
return verifyMoveCommand<T>(inputs, tx.commands)
}
/**
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
*
* @param T the type of the move command
*/
@Throws(IllegalArgumentException::class)
inline fun <reified T : CommandData> verifyMoveCommand(inputs: List<OwnableState>, commands: List<AuthenticatedObject<CommandData>>) {
// 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 = tx.commands.requireSingleCommand<T>().signers.toSet()
val keysThatSigned = commands.requireSingleCommand<T>().signers.toSet()
requireThat {
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
}
}
}

View File

@ -0,0 +1,49 @@
package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
// The dummy contract doesn't do anything useful. It exists for testing purposes.
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()
}
data class SingleOwnerState(val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState {
override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
}
data class MultiOwnerState(val magicNumber: Int = 0,
val owners: List<PublicKey>) : ContractState {
override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = owners
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
class Move : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForContract) {
// Always accepts.
}
// The "empty contract"
override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = SingleOwnerState(magicNumber, owner.party.owningKey)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
}

View File

@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.r3corda.core.contracts.CommandData
import java.math.BigDecimal
import java.time.DayOfWeek
import java.time.LocalDate
@ -234,42 +233,34 @@ enum class PaymentRule {
InAdvance, InArrears,
}
/**
* Date offset that the fixing is done prior to the accrual start date.
* Currently not used in the calculation.
*/
enum class DateOffset {
// TODO: Definitely shouldn't be an enum, but let's leave it for now at T-2 is a convention.
ZERO,
TWODAYS,
}
/**
* Frequency at which an event occurs - the enumerator also casts to an integer specifying the number of times per year
* that would divide into (eg annually = 1, semiannual = 2, monthly = 12 etc).
*/
enum class Frequency(val annualCompoundCount: Int) {
Annual(1) {
override fun offset(d: LocalDate) = d.plusYears(1)
override fun offset(d: LocalDate, n: Long) = d.plusYears(1 * n)
},
SemiAnnual(2) {
override fun offset(d: LocalDate) = d.plusMonths(6)
override fun offset(d: LocalDate, n: Long) = d.plusMonths(6 * n)
},
Quarterly(4) {
override fun offset(d: LocalDate) = d.plusMonths(3)
override fun offset(d: LocalDate, n: Long) = d.plusMonths(3 * n)
},
Monthly(12) {
override fun offset(d: LocalDate) = d.plusMonths(1)
override fun offset(d: LocalDate, n: Long) = d.plusMonths(1 * n)
},
Weekly(52) {
override fun offset(d: LocalDate) = d.plusWeeks(1)
override fun offset(d: LocalDate, n: Long) = d.plusWeeks(1 * n)
},
BiWeekly(26) {
override fun offset(d: LocalDate) = d.plusWeeks(2)
override fun offset(d: LocalDate, n: Long) = d.plusWeeks(2 * n)
},
Daily(365) {
override fun offset(d: LocalDate, n: Long) = d.plusDays(1 * n)
};
abstract fun offset(d: LocalDate): LocalDate
abstract fun offset(d: LocalDate, n: Long = 1): LocalDate
// Daily() // Let's not worry about this for now.
}
@ -317,18 +308,24 @@ open class BusinessCalendar private constructor(val calendars: Array<out String>
var currentDate = startDate
while (true) {
currentDate = period.offset(currentDate)
val scheduleDate = calendar.applyRollConvention(currentDate, dateRollConvention)
currentDate = getOffsetDate(currentDate, period)
if (periodOffset == null || periodOffset <= ctr)
ret.add(scheduleDate)
ret.add(calendar.applyRollConvention(currentDate, dateRollConvention))
ctr += 1
// TODO: Fix addl period logic
if ((ctr > noOfAdditionalPeriods ) || (currentDate >= endDate ?: currentDate ))
if ((ctr > noOfAdditionalPeriods) || (currentDate >= endDate ?: currentDate))
break
}
return ret
}
/** Calculates the date from @startDate moving forward @steps of time size @period. Does not apply calendar
* logic / roll conventions.
*/
fun getOffsetDate(startDate: LocalDate, period: Frequency, steps: Int = 1): LocalDate {
if (steps == 0) return startDate
return period.offset(startDate, steps.toLong())
}
}
override fun equals(other: Any?): Boolean = if (other is BusinessCalendar) {
@ -402,3 +399,24 @@ fun calculateDaysBetween(startDate: LocalDate,
else -> TODO("Can't calculate days using convention $dcbDay / $dcbYear")
}
}
/**
* 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.
*/
enum class NetType {
/**
* Close-out netting applies where one party is bankrupt or otherwise defaults (exact terms are contract specific),
* and allows their counterparty to net obligations without requiring approval from all parties. For example, if
* Bank A owes Bank B £1m, and Bank B owes Bank A £1m, in the case of Bank B defaulting this would enable Bank A
* to net out the two obligations to zero, rather than being legally obliged to pay £1m without any realistic
* expectation of the debt to them being paid. Realistically this is limited to bilateral netting, to simplify
* determining which party must sign the netting transaction.
*/
CLOSE_OUT,
/**
* "Payment" is used to refer to conventional netting, where all parties must confirm the netting transaction. This
* can be a multilateral netting transaction, and may be created by a central clearing service.
*/
PAYMENT
}

View File

@ -1,9 +1,5 @@
package com.r3corda.core.contracts
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.contracts.TransactionForVerification
import com.r3corda.core.contracts.Fix
import com.r3corda.core.contracts.FixOf
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.toStringShort
@ -22,17 +18,99 @@ interface NamedByHash {
val id: SecureHash
}
/**
* Interface for state objects that support being netted with other state objects.
*/
interface BilateralNettableState<T: BilateralNettableState<T>> {
/**
* Returns an object used to determine if two states can be subject to close-out netting. If two states return
* equal objects, they can be close out netted together.
*/
val bilateralNetState: Any
/**
* Perform bilateral netting of this state with another state. The two states must be compatible (as in
* bilateralNetState objects are equal).
*/
fun net(other: T): T
}
/**
* 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.
* 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.
*/
interface ContractState {
/** Contract by which the state belongs */
/**
* An instance of the contract class that will verify this state.
*
* # Discussion
*
* This field is not the final design, it's 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.
*
* 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.
*
* 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.
* 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:
*
* 1. Define the data structures that compose the ledger (the states)
* 2. Define the rules for updating those structures
*
* 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:
*
* - Only attachment 733c350f396a727655be1363c06635ba355036bd54a5ed6e594fd0b5d05f42f6 may be used with this state.
* - Any attachment signed by public key 2d1ce0e330c52b8055258d776c40 may be used with this state.
* - Attachments (1, 2, 3) may all be used with this state.
*
* 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.
*
* 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.
*
* TODO: Implement the above description. See COR-226
*/
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 _participant_ is any party that is able to consume this state in a valid transaction.
*
* The list of participants is required for certain types of transactions. For example, when changing the notary
* for this state ([TransactionType.NotaryChange]), every participants has to be involved and approve the transaction
* so that they receive the updated state, and don't 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.
*
* The participants list should normally be derived from the contents of the state. E.g. for [Cash] the participants
* list should just contain the owner.
*/
val participants: List<PublicKey>
}
/**
* 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) {
/**
* Copies the underlying state, replacing the notary field with the new value.
* To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState]
*/
fun withNewNotary(newNotary: Party) = TransactionState(this.data, newNotary)
}
/**
@ -41,6 +119,17 @@ interface ContractState {
*/
interface IssuanceDefinition
/**
* Definition for an issued product, which can be cash, a cash-like thing, assets, or generally anything else that's
* quantifiable with integer quantities.
*
* @param P the type of product underlying the definition, for example [Currency].
*/
data class Issued<P>(
val issuer: PartyAndReference,
val product: P
)
/**
* A contract state that can have a single owner.
*/
@ -89,7 +178,7 @@ interface DealState : LinearState {
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a
* Contract instance from a ContractState are imminent, at which point we can move this out of here
*/
fun generateAgreement(): TransactionBuilder
fun generateAgreement(notary: Party): TransactionBuilder
}
/**
@ -109,7 +198,7 @@ interface FixableDealState : DealState {
* TODO: This would also likely move to methods on the Contract once the changes to reference
* the Contract from the ContractState are in
*/
fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix)
fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix)
}
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
@ -124,11 +213,11 @@ data class StateRef(val txhash: SecureHash, val index: Int) {
}
/** A StateAndRef is simply a (state, ref) pair. For instance, a wallet (which holds available assets) contains these. */
data class StateAndRef<out T : ContractState>(val state: T, val ref: StateRef)
data class StateAndRef<out T : ContractState>(val state: TransactionState<T>, val ref: StateRef)
/** Filters a list of [StateAndRef] objects according to the type of the states */
inline fun <reified T : ContractState> List<StateAndRef<ContractState>>.filterStatesOfType(): List<StateAndRef<T>> {
return mapNotNull { if (it.state is T) StateAndRef(it.state, it.ref) else null }
return mapNotNull { if (it.state.data is T) StateAndRef(TransactionState(it.state.data, it.state.notary), it.ref) else null }
}
/**
@ -160,6 +249,22 @@ data class Command(val value: CommandData, val signers: List<PublicKey>) {
override fun toString() = "${commandDataToString()} with pubkeys ${signers.map { it.toStringShort() }}"
}
/** A common issue command, to enforce that issue commands have a nonce value. */
// TODO: Revisit use of nonce values - should this be part of the TX rather than the command perhaps?
interface IssueCommand : CommandData {
val nonce: Long
}
/** A common move command for contracts which can change owner. */
interface MoveCommand : CommandData {
/**
* 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 contract's state object(s).
*/
// TODO: Replace SecureHash here with a general contract constraints object
val contractHash: SecureHash?
}
/** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */
data class AuthenticatedObject<out T : Any>(
val signers: List<PublicKey>,
@ -199,7 +304,7 @@ interface Contract {
* existing contract code.
*/
@Throws(IllegalArgumentException::class)
fun verify(tx: TransactionForVerification)
fun verify(tx: TransactionForContract)
/**
* Unparsed reference to the natural language contract that this code is supposed to express (usually a hash of

View File

@ -1,12 +1,6 @@
package com.r3corda.core.contracts
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.WireTransaction
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.crypto.*
import com.r3corda.core.serialization.serialize
import java.security.KeyPair
import java.security.PublicKey
@ -16,14 +10,19 @@ import java.util.*
/**
* A TransactionBuilder is a transaction class that's 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.
* 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.
*
* The builder can be customised for specific transaction types, e.g. where additional processing is needed
* before adding a state/command.
*/
class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf(),
private val attachments: MutableList<SecureHash> = arrayListOf(),
private val outputs: MutableList<ContractState> = arrayListOf(),
private val commands: MutableList<Command> = arrayListOf()) {
abstract class TransactionBuilder(protected val type: TransactionType = TransactionType.General(),
protected val notary: Party? = null) {
protected val inputs: MutableList<StateRef> = arrayListOf()
protected val attachments: MutableList<SecureHash> = arrayListOf()
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf()
protected val commands: MutableList<Command> = arrayListOf()
protected val signers: MutableSet<PublicKey> = mutableSetOf()
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
@ -49,7 +48,8 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
fun withItems(vararg items: Any): TransactionBuilder {
for (t in items) {
when (t) {
is StateRef -> addInputState(t)
is StateAndRef<*> -> addInputState(t)
is TransactionState<*> -> addOutputState(t)
is ContractState -> addOutputState(t)
is Command -> addCommand(t)
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
@ -59,7 +59,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
}
/** The signatures that have been collected so far - might be incomplete! */
private val currentSigs = arrayListOf<DigitalSignature.WithKey>()
protected val currentSigs = arrayListOf<DigitalSignature.WithKey>()
fun signWith(key: KeyPair) {
check(currentSigs.none { it.by == key.public }) { "This partial transaction was already signed by ${key.public}" }
@ -96,22 +96,25 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
}
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
ArrayList(outputs), ArrayList(commands))
ArrayList(outputs), ArrayList(commands), signers.toList(), type)
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
if (checkSufficientSignatures) {
val gotKeys = currentSigs.map { it.by }.toSet()
for (command in commands) {
if (!gotKeys.containsAll(command.signers))
throw IllegalStateException("Missing signatures on the transaction for a ${command.value.javaClass.canonicalName} command")
}
val missing = signers - gotKeys
if (missing.isNotEmpty())
throw IllegalStateException("Missing signatures on the transaction for the public keys: ${missing.map { it.toStringShort() }}")
}
return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
}
fun addInputState(ref: StateRef) {
open fun addInputState(stateAndRef: StateAndRef<*>) {
check(currentSigs.isEmpty())
inputs.add(ref)
val notaryKey = stateAndRef.state.notary.owningKey
signers.add(notaryKey)
inputs.add(stateAndRef.ref)
}
fun addAttachment(attachment: Attachment) {
@ -119,14 +122,22 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
attachments.add(attachment.id)
}
fun addOutputState(state: ContractState) {
fun addOutputState(state: TransactionState<*>) {
check(currentSigs.isEmpty())
outputs.add(state)
}
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
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!!)
}
fun addCommand(arg: Command) {
check(currentSigs.isEmpty())
// We should probably merge the lists of pubkeys for identical commands here.
// TODO: replace pubkeys in commands with 'pointers' to keys in signers
signers.addAll(arg.signers)
commands.add(arg)
}
@ -136,7 +147,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
// Accessors that yield immutable snapshots.
fun inputStates(): List<StateRef> = ArrayList(inputs)
fun outputStates(): List<ContractState> = ArrayList(outputs)
fun outputStates(): List<TransactionState<*>> = ArrayList(outputs)
fun commands(): List<Command> = ArrayList(commands)
fun attachments(): List<SecureHash> = ArrayList(attachments)
}

View File

@ -1,9 +1,7 @@
package com.r3corda.core.contracts
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.contracts.CommandData
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.ReadOnlyTransactionStorage
import java.util.*
import java.util.concurrent.Callable
@ -15,9 +13,10 @@ import java.util.concurrent.Callable
*
* In future, this should support restricting the search by time, and other types of useful query.
*
* TODO: Write unit tests for this.
* @param transactions map of transaction id to [SignedTransaction]
* @param startPoints transactions to use as starting points for the search
*/
class TransactionGraphSearch(val transactions: Map<SecureHash, SignedTransaction>,
class TransactionGraphSearch(val transactions: ReadOnlyTransactionStorage,
val startPoints: List<WireTransaction>) : Callable<List<WireTransaction>> {
class Query(
val withCommandOfType: Class<out CommandData>? = null
@ -35,7 +34,7 @@ class TransactionGraphSearch(val transactions: Map<SecureHash, SignedTransaction
while (next.isNotEmpty()) {
val hash = next.removeAt(next.lastIndex)
val tx = transactions[hash]?.tx ?: continue
val tx = transactions.getTransaction(hash)?.tx ?: continue
if (q.matches(tx))
results += tx

View File

@ -1,9 +1,5 @@
package com.r3corda.core.contracts
import com.r3corda.core.contracts.AuthenticatedObject
import com.r3corda.core.contracts.LedgerTransaction
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.node.services.AttachmentStorage
import com.r3corda.core.node.services.IdentityService
import java.io.FileNotFoundException
@ -22,7 +18,7 @@ fun WireTransaction.toLedgerTransaction(identityService: IdentityService,
val attachments = attachments.map {
attachmentStorage.openAttachment(it) ?: throw FileNotFoundException(it.toString())
}
return LedgerTransaction(inputs, attachments, outputs, authenticatedArgs, id)
return LedgerTransaction(inputs, outputs, authenticatedArgs, attachments, id, signers, type)
}
/**

View File

@ -0,0 +1,115 @@
package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.noneOrSingle
import java.security.PublicKey
/** Defines transaction build & validation logic for a specific transaction type */
sealed class TransactionType {
override fun equals(other: Any?) = other?.javaClass == javaClass
override fun hashCode() = javaClass.name.hashCode()
/**
* Check that the transaction is valid based on:
* - General platform rules
* - Rules for the specific transaction type
*
* Note: Presence of _signatures_ is not checked, only the public keys to be signed for.
*/
fun verify(tx: TransactionForVerification) {
val missing = verifySigners(tx)
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
verifyTransaction(tx)
}
/** Check that the list of signers includes all the necessary keys */
fun verifySigners(tx: TransactionForVerification): Set<PublicKey> {
val timestamp = tx.commands.noneOrSingle { it.value is TimestampCommand }
val timestampKey = timestamp?.signers.orEmpty()
val notaryKey = (tx.inputs.map { it.notary.owningKey } + timestampKey).toSet()
if (notaryKey.size > 1) throw TransactionVerificationException.MoreThanOneNotary(tx)
val requiredKeys = getRequiredSigners(tx) + notaryKey
val missing = requiredKeys - tx.signers
return missing
}
/**
* Return the list of public keys that that require signatures for the transaction type.
* Note: the notary key is checked separately for all transactions and need not be included
*/
abstract fun getRequiredSigners(tx: TransactionForVerification): Set<PublicKey>
/** Implement type specific transaction validation logic */
abstract fun verifyTransaction(tx: TransactionForVerification)
/** A general transaction type where transaction validity is determined by custom contract code */
class General : TransactionType() {
/** Just uses the default [TransactionBuilder] with no special logic */
class Builder(notary: Party? = null) : TransactionBuilder(General(), notary) {}
/**
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
* If any contract fails to verify, the whole transaction is considered to be invalid
*/
override fun verifyTransaction(tx: TransactionForVerification) {
// TODO: Check that notary is unchanged
val ctx = tx.toTransactionForContract()
val contracts = (ctx.inputs.map { it.contract } + ctx.outputs.map { it.contract }).toSet()
for (contract in contracts) {
try {
contract.verify(ctx)
} catch(e: Throwable) {
throw TransactionVerificationException.ContractRejection(tx, contract, e)
}
}
}
override fun getRequiredSigners(tx: TransactionForVerification): Set<PublicKey> {
val commandKeys = tx.commands.flatMap { it.signers }.toSet()
return commandKeys
}
}
/**
* A special transaction type for reassigning a notary for a state. Validation does not involve running
* any contract code, it just checks that the states are unmodified apart from the notary field.
*/
class NotaryChange : TransactionType() {
/**
* A transaction builder that automatically sets the transaction type to [NotaryChange]
* and adds the list of participants to the signers set for every input state.
*/
class Builder(notary: Party? = null) : TransactionBuilder(NotaryChange(), notary) {
override fun addInputState(stateAndRef: StateAndRef<*>) {
signers.addAll(stateAndRef.state.data.participants)
super.addInputState(stateAndRef)
}
}
/**
* Check that the difference between inputs and outputs is only the notary field,
* and that all required signing public keys are present
*/
override fun verifyTransaction(tx: TransactionForVerification) {
try {
tx.inputs.zip(tx.outputs).forEach {
check(it.first.data == it.second.data)
check(it.first.notary != it.second.notary)
}
check(tx.commands.isEmpty())
} catch (e: IllegalStateException) {
throw TransactionVerificationException.InvalidNotaryChange(tx)
}
}
override fun getRequiredSigners(tx: TransactionForVerification): Set<PublicKey> {
val participantKeys = tx.inputs.flatMap { it.data.participants }.toSet()
return participantKeys
}
}
}

View File

@ -1,8 +1,8 @@
package com.r3corda.core.contracts
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
import java.util.*
// TODO: Consider moving this out of the core module and providing a different way for unit tests to test contracts.
@ -30,7 +30,7 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
val resolved = HashSet<TransactionForVerification>(transactions.size)
for (tx in transactions) {
val inputs = ArrayList<ContractState>(tx.inputs.size)
val inputs = ArrayList<TransactionState<ContractState>>(tx.inputs.size)
for (ref in tx.inputs) {
val conflict = refToConsumingTXMap[ref]
if (conflict != null)
@ -42,74 +42,59 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
// Look up the output in that transaction by index.
inputs.add(ltx.outputs[ref.index])
}
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id))
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id, tx.signers, tx.type))
}
for (tx in resolved)
tx.verify()
return resolved
}
}
/** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */
data class TransactionForVerification(val inStates: List<ContractState>,
val outStates: List<ContractState>,
data class TransactionForVerification(val inputs: List<TransactionState<ContractState>>,
val outputs: List<TransactionState<ContractState>>,
val attachments: List<Attachment>,
val commands: List<AuthenticatedObject<CommandData>>,
val origHash: SecureHash) {
val origHash: SecureHash,
val signers: List<PublicKey>,
val type: TransactionType) {
override fun hashCode() = origHash.hashCode()
override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash
/**
* Verifies that the transaction is valid:
* - Checks that the input states and the timestamp point to the same Notary
* - Runs the contracts for this transaction. If any contract fails to verify, the whole transaction
* is considered to be invalid
* Verifies that the transaction is valid by running type-specific validation logic.
*
* TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated.
*
* @throws TransactionVerificationException if a contract throws an exception (the original is in the cause field)
* or the transaction has references to more than one Notary
* @throws TransactionVerificationException if validation logic fails or if a contract throws an exception
* (the original is in the cause field)
*/
@Throws(TransactionVerificationException::class)
fun verify() {
verifySingleNotary()
val contracts = (inStates.map { it.contract } + outStates.map { it.contract }).toSet()
for (contract in contracts) {
try {
contract.verify(this)
} catch(e: Throwable) {
throw TransactionVerificationException.ContractRejection(this, contract, e)
}
}
}
fun verify() = type.verify(this)
private fun verifySingleNotary() {
if (inStates.isEmpty()) return
val notary = inStates.first().notary
if (inStates.any { it.notary != notary }) throw TransactionVerificationException.MoreThanOneNotary(this)
val timestampCmd = commands.singleOrNull { it.value is TimestampCommand } ?: return
if (!timestampCmd.signers.contains(notary.owningKey)) throw TransactionVerificationException.MoreThanOneNotary(this)
}
fun toTransactionForContract() = TransactionForContract(inputs.map { it.data }, outputs.map { it.data }, attachments, commands, origHash)
}
/**
* Utilities for contract writers to incorporate into their logic.
*/
/**
* A transaction to be passed as input to a contract verification function. Defines helper methods to
* simplify verification logic in contracts.
*/
data class TransactionForContract(val inputs: List<ContractState>,
val outputs: List<ContractState>,
val attachments: List<Attachment>,
val commands: List<AuthenticatedObject<CommandData>>,
val origHash: SecureHash) {
override fun hashCode() = origHash.hashCode()
override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
/**
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
* be used to simplify this logic.
*/
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
@Deprecated("This property was renamed to inputs", ReplaceWith("inputs"))
val inStates: List<ContractState> get() = inputs
@Deprecated("This property was renamed to outputs", ReplaceWith("outputs"))
val outStates: List<ContractState> get() = outputs
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
inline fun <reified T: CommandData, K> groupCommands(keySelector: (AuthenticatedObject<T>) -> K): Map<K, List<AuthenticatedObject<T>>>
= commands.select<T>().groupBy(keySelector)
/**
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
@ -126,8 +111,8 @@ data class TransactionForVerification(val inStates: List<ContractState>,
* currency field: the resulting list can then be iterated over to perform the per-currency calculation.
*/
fun <T : ContractState, K : Any> groupStates(ofType: Class<T>, selector: (T) -> K): List<InOutGroup<T, K>> {
val inputs = inStates.filterIsInstance(ofType)
val outputs = outStates.filterIsInstance(ofType)
val inputs = inputs.filterIsInstance(ofType)
val outputs = outputs.filterIsInstance(ofType)
val inGroups: Map<K, List<T>> = inputs.groupBy(selector)
val outGroups: Map<K, List<T>> = outputs.groupBy(selector)
@ -138,8 +123,8 @@ data class TransactionForVerification(val inStates: List<ContractState>,
/** See the documentation for the reflection-based version of [groupStates] */
inline fun <reified T : ContractState, K : Any> groupStates(selector: (T) -> K): List<InOutGroup<T, K>> {
val inputs = inStates.filterIsInstance<T>()
val outputs = outStates.filterIsInstance<T>()
val inputs = inputs.filterIsInstance<T>()
val outputs = outputs.filterIsInstance<T>()
val inGroups: Map<K, List<T>> = inputs.groupBy(selector)
val outGroups: Map<K, List<T>> = outputs.groupBy(selector)
@ -161,6 +146,24 @@ data class TransactionForVerification(val inStates: List<ContractState>,
return result
}
/** Utilities for contract writers to incorporate into their logic. */
/**
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
* be used to simplify this logic.
*/
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
}
class TransactionResolutionException(val hash: SecureHash) : Exception()
@ -169,4 +172,6 @@ class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTra
sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) {
class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
class SignersMissing(tx: TransactionForVerification, missing: List<PublicKey>) : TransactionVerificationException(tx, null)
class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
}

View File

@ -1,7 +1,6 @@
package com.r3corda.core.contracts
import com.esotericsoftware.kryo.Kryo
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.toStringShort
@ -44,8 +43,10 @@ import java.security.SignatureException
/** Transaction ready for serialisation, without any signatures attached. */
data class WireTransaction(val inputs: List<StateRef>,
val attachments: List<SecureHash>,
val outputs: List<ContractState>,
val commands: List<Command>) : NamedByHash {
val outputs: List<TransactionState<ContractState>>,
val commands: List<Command>,
val signers: List<PublicKey>,
val type: TransactionType) : NamedByHash {
// Cache the serialised form of the transaction and its hash to give us fast access to it.
@Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null
@ -64,11 +65,11 @@ data class WireTransaction(val inputs: List<StateRef>,
@Suppress("UNCHECKED_CAST")
fun <T : ContractState> outRef(index: Int): StateAndRef<T> {
require(index >= 0 && index < outputs.size)
return StateAndRef(outputs[index] as T, StateRef(id, index))
return StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
}
/** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.indexOfOrThrow(state))
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.map { it.data }.indexOfOrThrow(state))
override fun toString(): String {
val buf = StringBuilder()
@ -110,7 +111,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
/**
* Verify the signatures, deserialise the wire transaction and then check that the set of signatures found contains
* the set of pubkeys in the commands. If any signatures are missing, either throws an exception (by default) or
* the set of pubkeys in the signers list. If any signatures are missing, either throws an exception (by default) or
* returns the list of keys that have missing signatures, depending on the parameter.
*
* @throws SignatureException if a signature is invalid, does not match or if any signature is missing.
@ -131,15 +132,20 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
return copy(sigs = sigs + sig)
}
fun withAdditionalSignatures(sigList: Iterable<DigitalSignature.WithKey>): SignedTransaction {
return copy(sigs = sigs + sigList)
}
/** Alias for [withAdditionalSignature] to let you use Kotlin operator overloading. */
operator fun plus(sig: DigitalSignature.WithKey) = withAdditionalSignature(sig)
operator fun plus(sigList: Collection<DigitalSignature.WithKey>) = withAdditionalSignatures(sigList)
/**
* Returns the set of missing signatures - a signature must be present for every command pub key
* and the Notary (if it is specified)
* Returns the set of missing signatures - a signature must be present for each signer public key
*/
fun getMissingSignatures(): Set<PublicKey> {
val requiredKeys = tx.commands.flatMap { it.signers }.toSet()
val requiredKeys = tx.signers.toSet()
val sigKeys = sigs.map { it.by }.toSet()
if (sigKeys.containsAll(requiredKeys)) return emptySet()
@ -157,15 +163,17 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
data class LedgerTransaction(
/** The input states which will be consumed/invalidated by the execution of this transaction. */
val inputs: List<StateRef>,
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
val attachments: List<Attachment>,
/** The states that will be generated by the execution of this transaction. */
val outputs: List<ContractState>,
val outputs: List<TransactionState<*>>,
/** Arbitrary data passed to the program of each input state. */
val commands: List<AuthenticatedObject<CommandData>>,
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
val attachments: List<Attachment>,
/** The hash of the original serialised WireTransaction */
override val id: SecureHash
override val id: SecureHash,
val signers: List<PublicKey>,
val type: TransactionType
) : NamedByHash {
@Suppress("UNCHECKED_CAST")
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as T, StateRef(id, index))
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
}

View File

@ -1,13 +1,22 @@
package com.r3corda.core.crypto
import com.google.common.io.BaseEncoding
import com.r3corda.core.crypto.Party
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 java.math.BigInteger
import java.security.*
import java.security.interfaces.ECPublicKey
import net.i2p.crypto.eddsa.KeyPairGenerator as EddsaKeyPairGenerator
fun newSecureRandom(): SecureRandom {
if (System.getProperty("os.name") == "Linux") {
return SecureRandom.getInstance("NativePRNGNonBlocking")
} else {
return SecureRandom.getInstanceStrong()
}
}
// "sealed" here means there can't be any subclasses other than the ones defined here.
sealed class SecureHash private constructor(bits: ByteArray) : OpaqueBytes(bits) {
@ -37,7 +46,7 @@ sealed class SecureHash private constructor(bits: ByteArray) : OpaqueBytes(bits)
@JvmStatic fun sha256Twice(bits: ByteArray) = sha256(sha256(bits).bits)
@JvmStatic fun sha256(str: String) = sha256(str.toByteArray())
@JvmStatic fun randomSHA256() = sha256(SecureRandom.getInstanceStrong().generateSeed(32))
@JvmStatic fun randomSHA256() = sha256(newSecureRandom().generateSeed(32))
}
abstract val signatureAlgorithmName: String
@ -118,7 +127,7 @@ class DummyPublicKey(val s: String) : PublicKey, Comparable<PublicKey> {
/** Utility to simplify the act of signing a byte array */
fun PrivateKey.signWithECDSA(bits: ByteArray): DigitalSignature {
val signer = Signature.getInstance("SHA256withECDSA")
val signer = EdDSAEngine()
signer.initSign(this)
signer.update(bits)
val sig = signer.sign()
@ -140,7 +149,7 @@ fun KeyPair.signWithECDSA(bitsToSign: ByteArray, party: Party): DigitalSignature
/** Utility to simplify the act of verifying a signature */
fun PublicKey.verifyWithECDSA(content: ByteArray, signature: DigitalSignature) {
val verifier = Signature.getInstance("SHA256withECDSA")
val verifier = EdDSAEngine()
verifier.initVerify(this)
verifier.update(content)
if (verifier.verify(signature.bits) == false)
@ -160,4 +169,4 @@ operator fun KeyPair.component1() = this.private
operator fun KeyPair.component2() = this.public
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */
fun generateKeyPair() = KeyPairGenerator.getInstance("EC").genKeyPair()
fun generateKeyPair() = EddsaKeyPairGenerator().generateKeyPair()

View File

@ -4,9 +4,6 @@ import com.r3corda.core.contracts.PartyAndReference
import com.r3corda.core.serialization.OpaqueBytes
import java.security.PublicKey
/**
* Created by matth on 14/05/2016.
*/
/** A [Party] is well known (name, pubkey) pair. In a real system this would probably be an X.509 certificate. */
data class Party(val name: String, val owningKey: PublicKey) {
override fun toString() = name

View File

@ -1,6 +1,7 @@
package com.r3corda.core.messaging
import com.google.common.util.concurrent.ListenableFuture
import com.r3corda.core.serialization.DeserializeAsKotlinObjectDef
import com.r3corda.core.serialization.serialize
import java.time.Instant
import java.util.concurrent.Executor
@ -133,3 +134,9 @@ interface MessageRecipientGroup : MessageRecipients
/** A special base class for the set of all possible recipients, without having to identify who they all are. */
interface AllPossibleRecipients : MessageRecipients
/**
* A general Ack message that conveys no content other than it's presence for use when you want an acknowledgement
* from a recipient. Using [Unit] can be ambiguous as it is similar to [Void] and so could mean no response.
*/
object Ack : DeserializeAsKotlinObjectDef

View File

@ -1,10 +1,8 @@
package com.r3corda.core.node
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.messaging.MessagingService
import com.r3corda.core.node.services.*
import com.r3corda.core.utilities.RecordingMap
import java.time.Clock
/**
@ -31,7 +29,7 @@ interface ServiceHub {
*/
fun verifyTransaction(ltx: LedgerTransaction) {
val dependencies = ltx.inputs.map {
storageService.validatedTransactions[it.txhash] ?: throw TransactionResolutionException(it.txhash)
storageService.validatedTransactions.getTransaction(it.txhash) ?: throw TransactionResolutionException(it.txhash)
}
val ltxns = dependencies.map { it.verifyToLedgerTransaction(identityService, storageService.attachments) }
TransactionGroup(setOf(ltx), ltxns.toSet()).verify()
@ -41,29 +39,25 @@ interface ServiceHub {
* Given a list of [SignedTransaction]s, writes them to the local storage for validated transactions and then
* sends them to the wallet for further processing.
*
* TODO: Need to come up with a way for preventing transactions being written other than by this method.
* TODO: RecordingMap is test infrastructure. Refactor it away or find a way to ensure it's only used in tests.
* @param txs The transactions to record
*/
fun recordTransactions(txs: Iterable<SignedTransaction>)
/**
* Given some [SignedTransaction]s, writes them to the local storage for validated transactions and then
* sends them to the wallet for further processing.
*
* @param txs The transactions to record
* @param skipRecordingMap This is used in unit testing and can be ignored most of the time.
*/
fun recordTransactions(txs: List<SignedTransaction>, skipRecordingMap: Boolean = false) {
val txns: Map<SecureHash, SignedTransaction> = txs.groupBy { it.id }.mapValues { it.value.first() }
val txStorage = storageService.validatedTransactions
if (txStorage is RecordingMap && skipRecordingMap)
txStorage.putAllUnrecorded(txns)
else
txStorage.putAll(txns)
walletService.notifyAll(txs.map { it.tx })
}
fun recordTransactions(vararg txs: SignedTransaction) = recordTransactions(txs.toList())
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]
*
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction
*/
fun loadState(stateRef: StateRef): ContractState {
val definingTx = storageService.validatedTransactions[stateRef.txhash] ?: throw TransactionResolutionException(stateRef.txhash)
fun loadState(stateRef: StateRef): TransactionState<*> {
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return definingTx.tx.outputs[stateRef.index]
}
}

View File

@ -5,15 +5,12 @@ import com.r3corda.core.crypto.SecureHash
import java.io.InputStream
/**
* An attachment store records potentially large binary objects, identified by their hash. Note that attachments are
* immutable and can never be erased once inserted!
* An attachment store records potentially large binary objects, identified by their hash.
*/
interface AttachmentStorage {
/**
* Returns a newly opened stream for the given locally stored attachment, or null if no such attachment is known.
* The returned stream must be closed when you are done with it to avoid resource leaks. You should probably wrap
* the result in a [JarInputStream] unless you're sending it somewhere, there is a convenience helper for this
* on [Attachment].
* Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open
* a stream for the data, which will be a zip/jar file.
*/
fun openAttachment(id: SecureHash): Attachment?

View File

@ -3,11 +3,9 @@ package com.r3corda.core.node.services
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.AttachmentStorage
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.util.*
/**
* Postfix for base topics when sending a request to a service.
@ -25,19 +23,45 @@ val TOPIC_DEFAULT_POSTFIX = ".0"
* 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.
*
* This absract class has no references to Cash contracts.
* This abstract class has no references to Cash contracts.
*
* [states] Holds the list of states that are *active* and *relevant*.
* Active means they haven't been consumed yet (or we don't know about it).
* Relevant means they contain at least one of our pubkeys
*/
abstract class Wallet {
abstract val states: List<StateAndRef<ContractState>>
class Wallet(val states: List<StateAndRef<ContractState>>) {
@Suppress("UNCHECKED_CAST")
inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state is T } as List<StateAndRef<T>>
inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
/**
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
* which we have no cash evaluate to null (not present in map), not 0.
* Represents an update observed by the Wallet that will be notified to observers. Include the [StateRef]s of
* transaction outputs that were consumed (inputs) and the [ContractState]s produced (outputs) to/by the transaction
* or transactions observed and the Wallet.
*
* If the Wallet observes multiple transactions simultaneously, where some transactions consume the outputs of some of the
* other transactions observed, then the changes are observed "net" of those.
*/
abstract val cashBalances: Map<Currency, Amount<Currency>>
data class Update(val consumed: Set<StateRef>, val produced: Set<StateAndRef<ContractState>>) {
/**
* Combine two updates into a single update with the combined inputs and outputs of the two updates but net
* any outputs of the left-hand-side (this) that are consumed by the inputs of the right-hand-side (rhs).
*
* i.e. the net effect in terms of state live-ness of receiving the combined update is the same as receiving this followed by rhs.
*/
operator fun plus(rhs: Update): Update {
val previouslyProduced = produced.map { it.ref }
val previouslyConsumed = consumed
val combined = Wallet.Update(
previouslyConsumed + (rhs.consumed - previouslyProduced),
rhs.produced + produced.filter { it.ref !in rhs.consumed })
return combined
}
}
companion object {
val NoUpdate = Update(emptySet(), emptySet())
}
}
/**
@ -53,12 +77,6 @@ interface WalletService {
*/
val currentWallet: Wallet
/**
* Returns a snapshot of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
* which we have no cash evaluate to null, not 0.
*/
val cashBalances: Map<Currency, Amount<Currency>>
/**
* Returns a snapshot of the heads of LinearStates
*/
@ -69,10 +87,10 @@ interface WalletService {
/** Returns the [linearHeads] only when the type of the state would be considered an 'instanceof' the given type. */
@Suppress("UNCHECKED_CAST")
fun <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<SecureHash, StateAndRef<T>> {
return linearHeads.filterValues { stateType.isInstance(it.state) }.mapValues { StateAndRef(it.value.state as T, it.value.ref) }
return linearHeads.filterValues { stateType.isInstance(it.state.data) }.mapValues { StateAndRef(it.value.state as TransactionState<T>, it.value.ref) }
}
fun statesForRefs(refs: List<StateRef>): Map<StateRef, ContractState?> {
fun statesForRefs(refs: List<StateRef>): Map<StateRef, TransactionState<*>?> {
val refsToStates = currentWallet.states.associateBy { it.ref }
return refs.associateBy({ it }, { refsToStates[it]?.state })
}
@ -89,6 +107,12 @@ interface WalletService {
/** Same as notifyAll but with a single transaction. */
fun notify(tx: WireTransaction): Wallet = notifyAll(listOf(tx))
/**
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the Wallet will already incorporate
* the update.
*/
val updates: rx.Observable<Wallet.Update>
}
inline fun <reified T : LinearState> WalletService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)
@ -123,7 +147,7 @@ interface StorageService {
* The signatures aren't technically needed after that point, but we keep them around so that we can relay
* the transaction data to other nodes that need it.
*/
val validatedTransactions: MutableMap<SecureHash, SignedTransaction>
val validatedTransactions: ReadOnlyTransactionStorage
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage
@ -136,4 +160,16 @@ interface StorageService {
val myLegalIdentityKey: KeyPair
}
/**
* Storage service, with extensions to allow validated transactions to be added to. For use only within [ServiceHub].
*/
interface TxWritableStorageService : StorageService {
/**
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
* The signatures aren't technically needed after that point, but we keep them around so that we can relay
* the transaction data to other nodes that need it.
*/
override val validatedTransactions: TransactionStorage
}

View File

@ -0,0 +1,26 @@
package com.r3corda.core.node.services
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.crypto.SecureHash
/**
* Thread-safe storage of transactions.
*/
interface ReadOnlyTransactionStorage {
/**
* Return the transaction with the given [id], or null if no such transaction exists.
*/
fun getTransaction(id: SecureHash): SignedTransaction?
}
/**
* Thread-safe storage of transactions.
*/
interface TransactionStorage : ReadOnlyTransactionStorage {
/**
* Add a new transaction to the store. If the store already has a transaction with the same id it will be
* overwritten.
*/
// TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry.
fun addTransaction(transaction: SignedTransaction)
}

View File

@ -1,8 +1,7 @@
package com.r3corda.core.node.services
import com.r3corda.core.crypto.Party
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
/**
@ -11,7 +10,7 @@ import com.r3corda.core.crypto.SecureHash
*/
interface UniquenessProvider {
/** Commits all input states of the given transaction */
fun commit(tx: WireTransaction, callerIdentity: Party)
fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party)
/** Specifies the consuming transaction for every conflicting state */
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)

View File

@ -6,13 +6,8 @@ import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.crypto.sha256
import com.r3corda.core.node.services.AttachmentStorage
import com.r3corda.core.node.services.IdentityService
import com.r3corda.core.node.services.KeyManagementService
import com.r3corda.core.node.services.StorageService
import com.r3corda.core.node.services.*
import com.r3corda.core.serialization.SingletonSerializeAsToken
import com.r3corda.core.utilities.RecordingMap
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
@ -82,33 +77,17 @@ class MockAttachmentStorage : AttachmentStorage {
}
}
open class MockTransactionStorage : TransactionStorage {
private val txns = HashMap<SecureHash, SignedTransaction>()
override fun addTransaction(transaction: SignedTransaction) {
txns[transaction.id] = transaction
}
override fun getTransaction(id: SecureHash): SignedTransaction? = txns[id]
}
@ThreadSafe
class MockStorageService(override val attachments: AttachmentStorage = MockAttachmentStorage(),
override val validatedTransactions: TransactionStorage = MockTransactionStorage(),
override val myLegalIdentityKey: KeyPair = generateKeyPair(),
override val myLegalIdentity: Party = Party("Unit test party", myLegalIdentityKey.public),
// This parameter is for unit tests that want to observe operation details.
val recordingAs: (String) -> String = { tableName -> "" })
: SingletonSerializeAsToken(), StorageService {
protected val tables = HashMap<String, MutableMap<*, *>>()
private fun <K, V> getMapOriginal(tableName: String): MutableMap<K, V> {
synchronized(tables) {
@Suppress("UNCHECKED_CAST")
return tables.getOrPut(tableName) {
recorderWrap(Collections.synchronizedMap(HashMap<K, V>()), tableName)
} as MutableMap<K, V>
}
}
private fun <K, V> recorderWrap(map: MutableMap<K, V>, tableName: String): MutableMap<K, V> {
if (recordingAs(tableName) != "")
return RecordingMap(map, LoggerFactory.getLogger("recordingmap.${recordingAs(tableName)}"))
else
return map
}
override val validatedTransactions: MutableMap<SecureHash, SignedTransaction>
get() = getMapOriginal("validated-transactions")
}
override val myLegalIdentity: Party = Party("Unit test party", myLegalIdentityKey.public))
: SingletonSerializeAsToken(), TxWritableStorageService

View File

@ -31,7 +31,11 @@ abstract class ProtocolLogic<T> {
/** This is where you should log things to. */
val logger: Logger get() = psm.logger
/** Provides access to big, heavy classes that may be reconstructed from time to time, e.g. across restarts */
/**
* Provides access to big, heavy classes that may be reconstructed from time to time, e.g. across restarts. It is
* only available once the protocol has started, which means it cannnot be accessed in the constructor. Either
* access this lazily or from inside [call].
*/
val serviceHub: ServiceHub get() = psm.serviceHub
// Kotlin helpers that allow the use of generic types.
@ -69,7 +73,7 @@ abstract class ProtocolLogic<T> {
val ours = progressTracker
val theirs = subLogic.progressTracker
if (ours != null && theirs != null)
ours.childrenFor[ours.currentStep] = theirs
ours.setChildProgressTracker(ours.currentStep, theirs)
}
/**

View File

@ -16,7 +16,14 @@ import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.crypto.sha256
import com.r3corda.core.node.AttachmentsClassLoader
import com.r3corda.core.node.services.AttachmentStorage
import com.r3corda.core.utilities.NonEmptySet
import com.r3corda.core.utilities.NonEmptySetSerializer
import de.javakaffee.kryoserializers.ArraysAsListSerializer
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.objenesis.strategy.StdInstantiatorStrategy
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
@ -24,6 +31,7 @@ import java.io.ObjectOutputStream
import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
import java.nio.file.Path
import java.security.PublicKey
import java.time.Instant
import java.util.*
import javax.annotation.concurrent.ThreadSafe
@ -205,6 +213,16 @@ inline fun <T> Kryo.useClassLoader(cl: ClassLoader, body: () -> T) : T {
}
}
fun Output.writeBytesWithLength(byteArray: ByteArray) {
this.writeInt(byteArray.size, true)
this.writeBytes(byteArray)
}
fun Input.readBytesWithLength(): ByteArray {
val size = this.readInt(true)
return this.readBytes(size)
}
/** Thrown during deserialisation to indicate that an attachment needed to construct the [WireTransaction] is not found */
class MissingAttachmentsException(val ids: List<SecureHash>) : Exception()
@ -216,6 +234,8 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
kryo.writeClassAndObject(output, obj.attachments)
kryo.writeClassAndObject(output, obj.outputs)
kryo.writeClassAndObject(output, obj.commands)
kryo.writeClassAndObject(output, obj.signers)
kryo.writeClassAndObject(output, obj.type)
}
@Suppress("UNCHECKED_CAST")
@ -242,14 +262,62 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
} else javaClass.classLoader
kryo.useClassLoader(classLoader) {
val outputs = kryo.readClassAndObject(input) as List<ContractState>
val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>>
val commands = kryo.readClassAndObject(input) as List<Command>
val signers = kryo.readClassAndObject(input) as List<PublicKey>
val transactionType = kryo.readClassAndObject(input) as TransactionType
return WireTransaction(inputs, attachmentHashes, outputs, commands)
return WireTransaction(inputs, attachmentHashes, outputs, commands, signers, transactionType)
}
}
}
/** For serialising an ed25519 private key */
@ThreadSafe
object Ed25519PrivateKeySerializer : Serializer<EdDSAPrivateKey>() {
val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)
override fun write(kryo: Kryo, output: Output, obj: EdDSAPrivateKey) {
check(obj.params == ed25519Curve)
output.writeBytesWithLength(obj.seed)
}
override fun read(kryo: Kryo, input: Input, type: Class<EdDSAPrivateKey>): EdDSAPrivateKey {
val seed = input.readBytesWithLength()
return EdDSAPrivateKey(EdDSAPrivateKeySpec(seed, ed25519Curve))
}
}
/** For serialising an ed25519 public key */
@ThreadSafe
object Ed25519PublicKeySerializer : Serializer<EdDSAPublicKey>() {
val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)
override fun write(kryo: Kryo, output: Output, obj: EdDSAPublicKey) {
check(obj.params == ed25519Curve)
output.writeBytesWithLength(obj.abyte)
}
override fun read(kryo: Kryo, input: Input, type: Class<EdDSAPublicKey>): EdDSAPublicKey {
val A = input.readBytesWithLength()
return EdDSAPublicKey(EdDSAPublicKeySpec(A, ed25519Curve))
}
}
/** Marker interface for kotlin object definitions so that they are deserialized as the singleton instance. */
interface DeserializeAsKotlinObjectDef
/** Serializer to deserialize kotlin object definitions marked with [DeserializeAsKotlinObjectDef]. */
object KotlinObjectSerializer : Serializer<DeserializeAsKotlinObjectDef>() {
override fun read(kryo: Kryo, input: Input, type: Class<DeserializeAsKotlinObjectDef>): DeserializeAsKotlinObjectDef {
// read the public static INSTANCE field that kotlin compiler generates.
return type.getField("INSTANCE").get(null) as DeserializeAsKotlinObjectDef
}
override fun write(kryo: Kryo, output: Output, obj: DeserializeAsKotlinObjectDef) {
}
}
fun createKryo(k: Kryo = Kryo()): Kryo {
return k.apply {
// Allow any class to be deserialized (this is insecure but for prototyping we don't care)
@ -273,8 +341,8 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
// Some things where the JRE provides an efficient custom serialisation.
val keyPair = generateKeyPair()
register(keyPair.public.javaClass, ReferencesAwareJavaSerializer)
register(keyPair.private.javaClass, ReferencesAwareJavaSerializer)
register(keyPair.public.javaClass, Ed25519PublicKeySerializer)
register(keyPair.private.javaClass, Ed25519PrivateKeySerializer)
register(Instant::class.java, ReferencesAwareJavaSerializer)
// Some classes have to be handled with the ImmutableClassSerializer because they need to have their
@ -292,6 +360,16 @@ 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)
/** This ensures any kotlin objects that implement [DeserializeAsKotlinObjectDef] are read back in as singletons. */
addDefaultSerializer(DeserializeAsKotlinObjectDef::class.java, KotlinObjectSerializer)
noReferencesWithin<WireTransaction>()
}
}

View File

@ -0,0 +1,10 @@
package com.r3corda.core.testing
import com.r3corda.core.contracts.Contract
import com.r3corda.core.contracts.TransactionForContract
import com.r3corda.core.crypto.SecureHash
class AlwaysSucceedContract(override val legalContractReference: SecureHash = SecureHash.sha256("Always succeed contract")) : Contract {
override fun verify(tx: TransactionForContract) {
}
}

View File

@ -0,0 +1,18 @@
package com.r3corda.core.testing
import com.r3corda.core.contracts.Contract
import com.r3corda.core.contracts.LinearState
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
import java.util.*
class DummyLinearState(
override val thread: SecureHash = SecureHash.randomSHA256(),
override val contract: Contract = AlwaysSucceedContract(),
override val participants: List<PublicKey> = listOf(),
val nonce: SecureHash = SecureHash.randomSHA256()) : LinearState {
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
return participants.any { ourKeys.contains(it) }
}
}

View File

@ -0,0 +1,151 @@
package com.r3corda.core.testing
import com.r3corda.core.ThreadBox
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.Wallet
import com.r3corda.core.node.services.WalletService
import com.r3corda.core.serialization.SingletonSerializeAsToken
import com.r3corda.core.utilities.loggerFor
import com.r3corda.core.utilities.trace
import rx.Observable
import rx.subjects.PublishSubject
import java.security.PublicKey
import java.util.*
import javax.annotation.concurrent.ThreadSafe
/**
* 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.
*/
@ThreadSafe
open class InMemoryWalletService(private val services: ServiceHub) : SingletonSerializeAsToken(), WalletService {
class ClashingThreads(threads: Set<SecureHash>, transactions: Iterable<WireTransaction>) :
Exception("There are multiple linear head states after processing transactions $transactions. The clashing thread(s): $threads")
private val log = loggerFor<InMemoryWalletService>()
// Variables inside InnerState are protected with a lock by the ThreadBox and aren't in scope unless you're
// inside mutex.locked {} code block. So we can't forget to take the lock unless we accidentally leak a reference
// to wallet somewhere.
private class InnerState {
var wallet = Wallet(emptyList<StateAndRef<OwnableState>>())
}
private val mutex = ThreadBox(InnerState())
override val currentWallet: Wallet get() = mutex.locked { wallet }
private val _updatesPublisher = PublishSubject.create<Wallet.Update>()
override val updates: Observable<Wallet.Update>
get() = _updatesPublisher
/**
* Returns a snapshot of the heads of LinearStates
*/
override val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
get() = currentWallet.let { wallet ->
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.data.thread }.mapValues { it.value }
}
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
val ourKeys = services.keyManagementService.keys.keys
// Note how terribly incomplete this all is!
//
// - We don't notify anyone of anything, there are no event listeners.
// - We don't handle or even notice invalidations due to double spends of things in our wallet.
// - We have no concept of confidence (for txns where there is no definite finality).
// - No notification that keys are used, for the case where we observe a spend of our own states.
// - No ability to create complex spends.
// - No logging or tracking of how the wallet got into this state.
// - No persistence.
// - Does tx relevancy calculation and key management need to be interlocked? Probably yes.
//
// ... and many other things .... (Wallet.java in bitcoinj is several thousand lines long)
var netDelta = Wallet.NoUpdate
val changedWallet = mutex.locked {
// Starting from the current wallet, keep applying the transaction updates, calculating a new Wallet each
// time, until we get to the result (this is perhaps a bit inefficient, but it's functional and easily
// unit tested).
val walletAndNetDelta = txns.fold(Pair(currentWallet, Wallet.NoUpdate)) { walletAndDelta, tx ->
val (wallet, delta) = walletAndDelta.first.update(tx, ourKeys)
val combinedDelta = delta + walletAndDelta.second
Pair(wallet, combinedDelta)
}
val clashingThreads = walletAndNetDelta.first.clashingThreads
if (!clashingThreads.isEmpty()) {
throw ClashingThreads(clashingThreads, txns)
}
wallet = walletAndNetDelta.first
netDelta = walletAndNetDelta.second
return@locked wallet
}
if (netDelta != Wallet.NoUpdate) {
_updatesPublisher.onNext(netDelta)
}
return changedWallet
}
private fun isRelevant(state: ContractState, ourKeys: Set<PublicKey>): Boolean {
return if (state is OwnableState) {
state.owner in ourKeys
} else if (state is LinearState) {
// It's potentially of interest to the wallet
state.isRelevant(ourKeys)
} else {
false
}
}
private fun Wallet.update(tx: WireTransaction, ourKeys: Set<PublicKey>): Pair<Wallet, Wallet.Update> {
val ourNewStates = tx.outputs.
filter { isRelevant(it.data, ourKeys) }.
map { tx.outRef<ContractState>(it.data) }
// Now calculate the states that are being spent by this transaction.
val consumed: Set<StateRef> = states.map { it.ref }.intersect(tx.inputs)
// Is transaction irrelevant?
if (consumed.isEmpty() && ourNewStates.isEmpty()) {
log.trace { "tx ${tx.id} was irrelevant to this wallet, ignoring" }
return Pair(this, Wallet.NoUpdate)
}
val change = Wallet.Update(consumed, HashSet(ourNewStates))
// And calculate the new wallet.
val newStates = states.filter { it.ref !in consumed } + ourNewStates
log.trace {
"Applied tx ${tx.id.prefixChars()} to the wallet: consumed ${consumed.size} states and added ${newStates.size}"
}
return Pair(Wallet(newStates), change)
}
companion object {
// Returns the set of LinearState threads that clash in the wallet
val Wallet.clashingThreads: Set<SecureHash> get() {
val clashingThreads = HashSet<SecureHash>()
val threadsSeen = HashSet<SecureHash>()
for (linearState in states.filterStatesOfType<LinearState>()) {
val thread = linearState.state.data.thread
if (threadsSeen.contains(thread)) {
clashingThreads.add(thread)
} else {
threadsSeen.add(thread)
}
}
return clashingThreads
}
}
}

View File

@ -4,12 +4,13 @@ package com.r3corda.core.testing
import com.google.common.base.Throwables
import com.google.common.net.HostAndPort
import com.r3corda.core.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.*
import com.r3corda.core.serialization.serialize
import com.r3corda.core.node.services.IdentityService
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
@ -39,41 +40,91 @@ object TestUtils {
val keypair3 = generateKeyPair()
}
// A dummy time at which we will be pretending test transactions are created.
val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z")
/**
* JAVA INTEROP. Please keep the following points in mind when extending the Kotlin DSL
*
* - Annotate functions with Kotlin defaults with @JvmOverloads. This produces the relevant overloads for Java.
* - Void closures in arguments are inconvenient in Java, use overloading to define non-closure variants as well.
* - Top-level funs should be defined in a [JavaTestHelpers] object and annotated with @JvmStatic first and should be
* referred to from the global fun. This allows static importing of [JavaTestHelpers] in Java tests, which mimicks
* top-level funs.
* - Top-level vals are trickier. *DO NOT USE @JvmField INSIDE [JavaTestHelpers]*. It's surprisingly easy to
* introduce a static init cycle because of the way Kotlin compiles top-level things, which can cause
* non-deterministic behaviour, including your field not being initialized at all! Instead opt for a proper Kotlin
* val either with a custom @JvmStatic get() or a lazy delegate if the initialiser has side-effects See examples below.
* - Infix functions work as regular ones from Java, but symbols with spaces in them don't! Define a camelCase variant
* as well.
* - varargs are exposed as array types in Java. Define overloads for common cases.
* - The Int.DOLLARS syntax doesn't work from Java. To remedy add a @JvmStatic DOLLARS(Int) function to
* [JavaTestHelpers]
*/
object JavaTestHelpers {
// A dummy time at which we will be pretending test transactions are created.
@JvmStatic val TEST_TX_TIME: Instant get() = Instant.parse("2015-04-17T12:00:00.00Z")
// A few dummy values for testing.
val MEGA_CORP_KEY = TestUtils.keypair
val MEGA_CORP_PUBKEY = MEGA_CORP_KEY.public
// A few dummy values for testing.
@JvmStatic val MEGA_CORP_KEY: KeyPair get() = TestUtils.keypair
@JvmStatic val MEGA_CORP_PUBKEY: PublicKey get() = MEGA_CORP_KEY.public
val MINI_CORP_KEY = TestUtils.keypair2
val MINI_CORP_PUBKEY = MINI_CORP_KEY.public
@JvmStatic val MINI_CORP_KEY: KeyPair get() = TestUtils.keypair2
@JvmStatic val MINI_CORP_PUBKEY: PublicKey get() = MINI_CORP_KEY.public
val ORACLE_KEY = TestUtils.keypair3
val ORACLE_PUBKEY = ORACLE_KEY.public
@JvmStatic val ORACLE_KEY: KeyPair get() = TestUtils.keypair3
@JvmStatic val ORACLE_PUBKEY: PublicKey get() = ORACLE_KEY.public
val DUMMY_PUBKEY_1 = DummyPublicKey("x1")
val DUMMY_PUBKEY_2 = DummyPublicKey("x2")
@JvmStatic val DUMMY_PUBKEY_1: PublicKey get() = DummyPublicKey("x1")
@JvmStatic val DUMMY_PUBKEY_2: PublicKey get() = DummyPublicKey("x2")
val ALICE_KEY = generateKeyPair()
val ALICE_PUBKEY = ALICE_KEY.public
val ALICE = Party("Alice", ALICE_PUBKEY)
@JvmStatic val ALICE_KEY: KeyPair by lazy { generateKeyPair() }
@JvmStatic val ALICE_PUBKEY: PublicKey get() = ALICE_KEY.public
@JvmStatic val ALICE: Party get() = Party("Alice", ALICE_PUBKEY)
val BOB_KEY = generateKeyPair()
val BOB_PUBKEY = BOB_KEY.public
val BOB = Party("Bob", BOB_PUBKEY)
@JvmStatic val BOB_KEY: KeyPair by lazy { generateKeyPair() }
@JvmStatic val BOB_PUBKEY: PublicKey get() = BOB_KEY.public
@JvmStatic val BOB: Party get() = Party("Bob", BOB_PUBKEY)
val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY)
val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY)
@JvmStatic val MEGA_CORP: Party get() = Party("MegaCorp", MEGA_CORP_PUBKEY)
@JvmStatic val MINI_CORP: Party get() = Party("MiniCorp", MINI_CORP_PUBKEY)
val DUMMY_NOTARY_KEY = generateKeyPair()
val DUMMY_NOTARY = Party("Notary Service", DUMMY_NOTARY_KEY.public)
@JvmStatic val DUMMY_NOTARY_KEY: KeyPair by lazy { generateKeyPair() }
@JvmStatic val DUMMY_NOTARY: Party get() = Party("Notary Service", DUMMY_NOTARY_KEY.public)
val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY)
@JvmStatic val ALL_TEST_KEYS: List<KeyPair> get() = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY)
val MOCK_IDENTITY_SERVICE = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
@JvmStatic val MOCK_IDENTITY_SERVICE: MockIdentityService get() = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
@JvmStatic fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
@JvmStatic fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): LastLineShouldTestForAcceptOrFailure {
return body(TransactionForTest())
}
}
val TEST_TX_TIME = JavaTestHelpers.TEST_TX_TIME
val MEGA_CORP_KEY = JavaTestHelpers.MEGA_CORP_KEY
val MEGA_CORP_PUBKEY = JavaTestHelpers.MEGA_CORP_PUBKEY
val MINI_CORP_KEY = JavaTestHelpers.MINI_CORP_KEY
val MINI_CORP_PUBKEY = JavaTestHelpers.MINI_CORP_PUBKEY
val ORACLE_KEY = JavaTestHelpers.ORACLE_KEY
val ORACLE_PUBKEY = JavaTestHelpers.ORACLE_PUBKEY
val DUMMY_PUBKEY_1 = JavaTestHelpers.DUMMY_PUBKEY_1
val DUMMY_PUBKEY_2 = JavaTestHelpers.DUMMY_PUBKEY_2
val ALICE_KEY = JavaTestHelpers.ALICE_KEY
val ALICE_PUBKEY = JavaTestHelpers.ALICE_PUBKEY
val ALICE = JavaTestHelpers.ALICE
val BOB_KEY = JavaTestHelpers.BOB_KEY
val BOB_PUBKEY = JavaTestHelpers.BOB_PUBKEY
val BOB = JavaTestHelpers.BOB
val MEGA_CORP = JavaTestHelpers.MEGA_CORP
val MINI_CORP = JavaTestHelpers.MINI_CORP
val DUMMY_NOTARY_KEY = JavaTestHelpers.DUMMY_NOTARY_KEY
val DUMMY_NOTARY = JavaTestHelpers.DUMMY_NOTARY
val ALL_TEST_KEYS = JavaTestHelpers.ALL_TEST_KEYS
val MOCK_IDENTITY_SERVICE = JavaTestHelpers.MOCK_IDENTITY_SERVICE
fun generateStateRef() = JavaTestHelpers.generateStateRef()
fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure) = JavaTestHelpers.transaction(body)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
@ -94,22 +145,25 @@ fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
// contract `fails requirement` "some substring of the error message"
// }
//
// TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block
class LabeledOutput(val label: String?, val state: ContractState) {
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)
override fun hashCode(): Int = state.hashCode()
}
infix fun ContractState.label(label: String) = LabeledOutput(label, this)
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()
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, s()).apply { outStates.add(this) }
@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) }
@ -119,10 +173,11 @@ abstract class AbstractTransactionForTest {
attachments.add(attachmentID)
}
fun arg(vararg key: PublicKey, c: () -> CommandData) {
val keys = listOf(*key)
commands.add(Command(c(), keys))
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)
@ -130,28 +185,54 @@ abstract class AbstractTransactionForTest {
}
fun timestamp(data: TimestampCommand) {
commands.add(Command(data, DUMMY_NOTARY.owningKey))
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.() -> Unit) {
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<ContractState>()
fun input(s: () -> ContractState) = inStates.add(s())
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())
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256(), signers.toList(), type)
tx.verify()
}
fun accepts(time: Instant = TEST_TX_TIME) = runCommandsAndVerify(time)
fun rejects(withMessage: String? = null, time: Instant = TEST_TX_TIME) {
@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
@ -164,18 +245,18 @@ open class TransactionForTest : AbstractTransactionForTest() {
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) = rejects(msg)
fun fails_requirement(msg: String) = this.`fails requirement`(msg)
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.() -> Unit): TransactionForTest {
fun chain(vararg outputLabels: String, body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): TransactionForTest {
val states = outStates.mapNotNull {
val l = it.label
if (l != null && outputLabels.contains(l))
@ -190,13 +271,15 @@ open class TransactionForTest : AbstractTransactionForTest() {
}
// Allow customisation of partial transactions.
fun tweak(body: TransactionForTest.() -> Unit): TransactionForTest {
fun tweak(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): LastLineShouldTestForAcceptOrFailure {
val tx = TransactionForTest()
tx.inStates.addAll(inStates)
tx.outStates.addAll(outStates)
tx.commands.addAll(commands)
tx.body()
return tx
tx.signers.addAll(tx.inStates.map { it.notary.owningKey })
tx.signers.addAll(commands.flatMap { it.signers })
return tx.body()
}
override fun toString(): String {
@ -217,23 +300,28 @@ open class TransactionForTest : AbstractTransactionForTest() {
}
}
fun transaction(body: TransactionForTest.() -> Unit) = TransactionForTest().apply { body() }
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)
fun toWireTransaction() = WireTransaction(inStates, attachments, outStates.map { it.state }, commands, signers.toList(), type)
}
val String.output: T get() = labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found")
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(label.output as C, label.outputRef)
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 {
@ -241,8 +329,8 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
for ((index, labelledState) in outStates.withIndex()) {
if (labelledState.label != null) {
labelToRefs[labelledState.label] = StateRef(wtx.id, index)
if (stateType.isInstance(labelledState.state)) {
labelToOutputs[labelledState.label] = labelledState.state as T
if (stateType.isInstance(labelledState.state.data)) {
labelToOutputs[labelledState.label] = labelledState.state as TransactionState<T>
}
outputsToLabels[labelledState.state] = labelledState.label
}
@ -253,28 +341,35 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
private val rootTxns = ArrayList<WireTransaction>()
private val labelToRefs = HashMap<String, StateRef>()
private val labelToOutputs = HashMap<String, T>()
private val outputsToLabels = HashMap<ContractState, String>()
private val labelToOutputs = HashMap<String, TransactionState<T>>()
private val outputsToLabels = HashMap<TransactionState<*>, String>()
fun labelForState(state: T): String? = outputsToLabels[state]
fun labelForState(output: TransactionState<*>): String? = outputsToLabels[output]
inner class Roots {
fun transaction(vararg outputStates: LabeledOutput) {
fun transaction(vararg outputStates: LabeledOutput): Roots {
val outs = outputStates.map { it.state }
val wtx = WireTransaction(emptyList(), emptyList(), outs, emptyList())
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 T
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) {
}
@ -285,6 +380,7 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
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()
@ -298,6 +394,9 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
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) {
}
@ -325,14 +424,14 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
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)
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.commands.flatMap { it.signers }.toMutableSet()
val allPubKeys = wtx.signers.toMutableSet()
val bits = wtx.serialize()
require(bits == wtx.serialized)
val sigs = ArrayList<DigitalSignature.WithKey>()

View File

@ -1,12 +1,20 @@
package com.r3corda.core.utilities
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import java.util.*
/**
* 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.
* The underlying set is exposed for Kryo to access, but should not be accessed directly.
*/
class NonEmptySet<T>(initial: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> {
class NonEmptySet<T>(initial: T) : MutableSet<T> {
private val set: MutableSet<T> = HashSet<T>()
init {
require (set.isEmpty()) { "Provided set must be empty." }
set.add(initial)
}
@ -80,4 +88,28 @@ fun <T> nonEmptySetOf(initial: T, vararg elements: T): NonEmptySet<T> {
// We add the first element twice, but it's a set, so who cares
set.addAll(elements)
return set
}
/**
* Custom serializer which understands it has to read in an item before
* trying to construct the set.
*/
object NonEmptySetSerializer : Serializer<NonEmptySet<Any>>() {
override fun write(kryo: Kryo, output: Output, obj: NonEmptySet<Any>) {
// Write out the contents as normal
output.writeInt(obj.size)
obj.forEach { kryo.writeClassAndObject(output, it) }
}
override fun read(kryo: Kryo, input: Input, type: Class<NonEmptySet<Any>>): NonEmptySet<Any> {
val size = input.readInt()
require(size >= 1) { "Size is positive" }
// TODO: Is there an upper limit we can apply to how big one of these could be?
val first = kryo.readClassAndObject(input)
// Read the first item and use it to construct the NonEmptySet
val set = NonEmptySet(first)
// Read in the rest of the set
for (i in 2..size) { set.add(kryo.readClassAndObject(input)) }
return set
}
}

View File

@ -49,7 +49,8 @@ class ProgressTracker(vararg steps: Step) {
/** The superclass of all step objects. */
open class Step(open val label: String) {
open val changes: Observable<Change> = Observable.empty()
open val changes: Observable<Change> get() = Observable.empty()
open fun childProgressTracker(): ProgressTracker? = null
}
/** This class makes it easier to relabel a step on the fly, to provide transient information. */
@ -77,6 +78,20 @@ class ProgressTracker(vararg steps: Step) {
/** The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. */
val steps = arrayOf(UNSTARTED, *steps, DONE)
// This field won't be serialized.
private val _changes by TransientProperty { PublishSubject.create<Change>() }
private val childProgressTrackers = HashMap<Step, Pair<ProgressTracker, Subscription>>()
init {
steps.forEach {
val childTracker = it.childProgressTracker()
if (childTracker != null) {
setChildProgressTracker(it, childTracker)
}
}
}
/** The zero-based index of the current step in the [steps] array (i.e. with UNSTARTED and DONE) */
var stepIndex: Int = 0
private set
@ -98,7 +113,7 @@ class ProgressTracker(vararg steps: Step) {
// We are going backwards: unlink and unsubscribe from any child nodes that we're rolling back
// through, in preparation for moving through them again.
for (i in stepIndex downTo index) {
childrenFor.remove(steps[i])
removeChildProgressTracker(steps[i])
}
}
@ -113,16 +128,28 @@ class ProgressTracker(vararg steps: Step) {
/** Returns the current step, descending into children to find the deepest step we are up to. */
val currentStepRecursive: Step
get() = childrenFor[currentStep]?.currentStepRecursive ?: currentStep
get() = getChildProgressTracker(currentStep)?.currentStepRecursive ?: currentStep
/**
* Writable map that lets you insert child [ProgressTracker]s for particular steps. It's OK to edit this even
* after a progress tracker has been started.
*/
val childrenFor: ChildrenProgressTrackers = ChildrenProgressTrackersImpl()
fun getChildProgressTracker(step: Step): ProgressTracker? = childProgressTrackers[step]?.first
fun setChildProgressTracker(step: ProgressTracker.Step, childProgressTracker: ProgressTracker) {
val subscription = childProgressTracker.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) })
childProgressTrackers[step] = Pair(childProgressTracker, subscription)
childProgressTracker.parent = this
_changes.onNext(Change.Structural(this, step))
}
private fun removeChildProgressTracker(step: ProgressTracker.Step) {
childProgressTrackers.remove(step)?.let {
it.first.parent = null
it.second.unsubscribe()
}
_changes.onNext(Change.Structural(this, step))
}
/** The parent of this tracker: set automatically by the parent when a tracker is added as a child */
var parent: ProgressTracker? = null
private set
/** Walks up the tree to find the top level tracker. If this is the top level tracker, returns 'this' */
val topLevelTracker: ProgressTracker
@ -138,7 +165,7 @@ class ProgressTracker(vararg steps: Step) {
if (step == UNSTARTED) continue
if (level > 0 && step == DONE) continue
result += Pair(level, step)
childrenFor[step]?.let { result += it._allSteps(level + 1) }
getChildProgressTracker(step)?.let { result += it._allSteps(level + 1) }
}
return result
}
@ -160,45 +187,12 @@ class ProgressTracker(vararg steps: Step) {
return currentStep
}
// This field won't be serialized.
private val _changes by TransientProperty { PublishSubject.create<Change>() }
/**
* An observable stream of changes: includes child steps, resets and any changes emitted by individual steps (e.g.
* if a step changed its label or rendering).
*/
val changes: Observable<Change> get() = _changes
// TODO remove this interface and add its three methods directly into ProgressTracker
interface ChildrenProgressTrackers {
operator fun get(step: ProgressTracker.Step): ProgressTracker?
operator fun set(step: ProgressTracker.Step, childProgressTracker: ProgressTracker)
fun remove(step: ProgressTracker.Step)
}
private inner class ChildrenProgressTrackersImpl : ChildrenProgressTrackers {
private val map = HashMap<Step, Pair<ProgressTracker, Subscription>>()
override fun get(step: Step): ProgressTracker? = map[step]?.first
override fun set(step: Step, childProgressTracker: ProgressTracker) {
val subscription = childProgressTracker.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) })
map[step] = Pair(childProgressTracker, subscription)
childProgressTracker.parent = this@ProgressTracker
_changes.onNext(Change.Structural(this@ProgressTracker, step))
}
override fun remove(step: Step) {
map.remove(step)?.let {
it.first.parent = null
it.second.unsubscribe()
}
_changes.onNext(Change.Structural(this@ProgressTracker, step))
}
}
}

View File

@ -0,0 +1,12 @@
package com.r3corda.core.utilities
import java.time.Duration
import java.time.Instant
/**
* A class representing a window in time from a particular instant, lasting a specified duration.
*/
data class TimeWindow(val start: Instant, val duration: Duration) {
val end: Instant
get() = start + duration
}

View File

@ -18,6 +18,6 @@ class FetchTransactionsProtocol(requests: Set<SecureHash>, otherSide: SingleMess
const val TOPIC = "platform.fetch.tx"
}
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.storageService.validatedTransactions[txid]
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.storageService.validatedTransactions.getTransaction(txid)
override val queryTopic: String = TOPIC
}

View File

@ -1,12 +1,14 @@
package com.r3corda.protocols
import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.TimestampCommand
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.crypto.DigitalSignature
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
@ -16,7 +18,6 @@ import com.r3corda.core.noneOrSingle
import com.r3corda.core.protocols.ProtocolLogic
import com.r3corda.core.random63BitValue
import com.r3corda.core.serialization.SerializedBytes
import com.r3corda.core.serialization.deserialize
import com.r3corda.core.serialization.serialize
import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.core.utilities.UntrustworthyData
@ -33,7 +34,7 @@ object NotaryProtocol {
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
* by another transaction or the timestamp is invalid
*/
class Client(private val wtx: WireTransaction,
class Client(private val stx: SignedTransaction,
override val progressTracker: ProgressTracker = Client.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
companion object {
@ -55,9 +56,9 @@ object NotaryProtocol {
val receiveSessionID = random63BitValue()
val handshake = Handshake(serviceHub.networkService.myAddress, sendSessionID, receiveSessionID)
sendAndReceive<Unit>(TOPIC_INITIATE, notaryNode.address, 0, receiveSessionID, handshake)
sendAndReceive<Ack>(TOPIC_INITIATE, notaryNode.address, 0, receiveSessionID, handshake)
val request = SignRequest(wtx.serialized, serviceHub.storageService.myLegalIdentity)
val request = SignRequest(stx, serviceHub.storageService.myLegalIdentity)
val response = sendAndReceive<Result>(TOPIC, notaryNode.address, sendSessionID, receiveSessionID, request)
val notaryResult = validateResponse(response)
@ -68,7 +69,7 @@ object NotaryProtocol {
progressTracker.currentStep = VALIDATING
response.validate {
if (it.sig != null) validateSignature(it.sig, wtx.serialized)
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}'")
@ -83,6 +84,7 @@ object NotaryProtocol {
private fun findNotaryNode(): NodeInfo {
var maybeNotaryKey: PublicKey? = null
val wtx = stx.tx
val timestampCommand = wtx.commands.singleOrNull { it.value is TimestampCommand }
if (timestampCommand != null) maybeNotaryKey = timestampCommand.signers.first()
@ -116,17 +118,17 @@ object NotaryProtocol {
@Suspendable
override fun call() {
val request = receive<SignRequest>(TOPIC, receiveSessionID).validate { it }
val txBits = request.txBits
val stx = request.tx
val wtx = stx.tx
val reqIdentity = request.callerIdentity
val wtx = txBits.deserialize()
val result: Result
try {
validateTimestamp(wtx)
beforeCommit(wtx, reqIdentity)
beforeCommit(stx, reqIdentity)
commitInputStates(wtx, reqIdentity)
val sig = sign(txBits)
val sig = sign(stx.txBits)
result = Result.noError(sig)
} catch(e: NotaryException) {
@ -158,12 +160,12 @@ object NotaryProtocol {
* undo the commit of the input states (the exact mechanism still needs to be worked out)
*/
@Suspendable
open fun beforeCommit(wtx: WireTransaction, reqIdentity: Party) {
open fun beforeCommit(stx: SignedTransaction, reqIdentity: Party) {
}
private fun commitInputStates(tx: WireTransaction, reqIdentity: Party) {
try {
uniquenessProvider.commit(tx, reqIdentity)
uniquenessProvider.commit(tx.inputs, tx.id, reqIdentity)
} catch (e: UniquenessException) {
val conflictData = e.error.serialize()
val signedConflict = SignedData(conflictData, sign(conflictData))
@ -184,7 +186,7 @@ object NotaryProtocol {
sessionID: Long) : AbstractRequestMessage(replyTo, sessionID)
/** TODO: The caller must authenticate instead of just specifying its identity */
class SignRequest(val txBits: SerializedBytes<WireTransaction>,
class SignRequest(val tx: SignedTransaction,
val callerIdentity: Party)
data class Result private constructor(val sig: DigitalSignature.LegallyIdentifiable?, val error: NotaryError?) {
@ -231,4 +233,6 @@ sealed class NotaryError {
class TimestampInvalid : NotaryError()
class TransactionInvalid : NotaryError()
class SignaturesMissing(val missingSigners: List<PublicKey>) : NotaryError()
}

View File

@ -158,7 +158,7 @@ object TwoPartyDealProtocol {
@Suspendable
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol.Client(stx.tx))
return subProtocol(NotaryProtocol.Client(stx))
}
open fun signWithOurKey(partialTX: SignedTransaction): DigitalSignature.WithKey {
@ -314,7 +314,7 @@ object TwoPartyDealProtocol {
}
override fun assembleSharedTX(handshake: Handshake<T>): Pair<TransactionBuilder, List<PublicKey>> {
val ptx = handshake.payload.generateAgreement()
val ptx = handshake.payload.generateAgreement(notary)
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.
@ -336,12 +336,12 @@ object TwoPartyDealProtocol {
val dealToFix: StateAndRef<T>,
sessionID: Long,
val replacementProgressTracker: ProgressTracker? = null) : Secondary<StateRef>(otherSide, notary, sessionID) {
private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.nextFixingOf()!!.name)
private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.data.nextFixingOf()!!.name)
override val progressTracker: ProgressTracker = replacementProgressTracker ?: createTracker()
fun createTracker(): ProgressTracker = Secondary.tracker().apply {
childrenFor[SIGNING] = ratesFixTracker
setChildProgressTracker(SIGNING, ratesFixTracker)
}
override fun validateHandshake(handshake: Handshake<StateRef>): Handshake<StateRef> {
@ -352,32 +352,27 @@ object TwoPartyDealProtocol {
if (dealToFix.ref != handshake.payload)
throw DealRefMismatchException(dealToFix.ref, handshake.payload)
// Check the transaction that contains the state which is being resolved.
// We only have a hash here, so if we don't know it already, we have to ask for it.
//subProtocol(ResolveTransactionsProtocol(setOf(handshake.payload.txhash), otherSide))
return handshake
}
}
@Suspendable
override fun assembleSharedTX(handshake: Handshake<StateRef>): Pair<TransactionBuilder, List<PublicKey>> {
val fixOf = dealToFix.state.nextFixingOf()!!
val fixOf = dealToFix.state.data.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
val deal: T = dealToFix.state.data
val myOldParty = deal.parties.single { it.name == myName }
@Suppress("UNCHECKED_CAST")
val newDeal = deal
val oldRef = dealToFix.ref
val ptx = TransactionBuilder()
val ptx = TransactionType.General.Builder()
val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0], fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
@Suspendable
override fun beforeSigning(fix: Fix) {
newDeal.generateFix(ptx, oldRef, fix)
newDeal.generateFix(ptx, dealToFix, 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.

View File

@ -1,6 +1,7 @@
package com.r3corda.protocols
import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.TransactionVerificationException
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.contracts.toLedgerTransaction
@ -22,8 +23,10 @@ class ValidatingNotaryProtocol(otherSide: SingleMessageRecipient,
timestampChecker: TimestampChecker,
uniquenessProvider: UniquenessProvider) : NotaryProtocol.Service(otherSide, sessionIdForSend, sessionIdForReceive, timestampChecker, uniquenessProvider) {
@Suspendable
override fun beforeCommit(wtx: WireTransaction, reqIdentity: Party) {
override fun beforeCommit(stx: SignedTransaction, reqIdentity: Party) {
val wtx = stx.tx
try {
checkSignatures(stx)
validateDependencies(reqIdentity, wtx)
checkContractValid(wtx)
} catch (e: Exception) {
@ -35,6 +38,13 @@ class ValidatingNotaryProtocol(otherSide: SingleMessageRecipient,
}
}
private fun checkSignatures(stx: SignedTransaction) {
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
val missing = stx.verify(false) - myKey
if (missing.isNotEmpty()) throw NotaryException(NotaryError.SignaturesMissing(missing.toList()))
}
private fun checkContractValid(wtx: WireTransaction) {
val ltx = wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
serviceHub.verifyTransaction(ltx)

View File

@ -0,0 +1,261 @@
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
/**
* A protocol to be used for changing a state's Notary. This is required since all input states to a transaction
* must point to the same notary.
*
* The [Instigator] assembles the transaction for notary 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.
*/
object NotaryChangeProtocol {
val TOPIC_INITIATE = "platform.notary.change.initiate"
val TOPIC_CHANGE = "platform.notary.change.execute"
data class Proposal(val stateRef: StateRef,
val newNotary: Party,
val stx: SignedTransaction)
class Handshake(val sessionIdForSend: Long,
replyTo: SingleMessageRecipient,
replySessionId: Long) : AbstractRequestMessage(replyTo, replySessionId)
class Instigator<T : ContractState>(val originalState: StateAndRef<T>,
val newNotary: Party,
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<StateAndRef<T>>() {
companion object {
object SIGNING : ProgressTracker.Step("Requesting signatures from other parties")
object NOTARY : ProgressTracker.Step("Requesting current Notary signature")
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>> {
val state = originalState.state
val newState = state.withNewNotary(newNotary)
val participants = state.data.participants
val tx = TransactionType.NotaryChange.Builder().withItems(originalState, newState)
tx.signWith(serviceHub.storageService.myLegalIdentityKey)
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>() {
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)
}
/**
* Check the notary change proposal.
*
* For example, if the proposed new notary has the same behaviour (e.g. both are non-validating)
* and is also in a geographically convenient location we can just automatically approve the change.
* 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
val isNotary = serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary }
require(isNotary) { "The proposed node $newNotary does not run a Notary service " }
val state = proposal.stateRef
val proposedTx = proposal.stx.tx
require(proposedTx.inputs.contains(state)) { "The proposed state $state is not in the proposed transaction inputs" }
// An example requirement
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,74 @@
package com.r3corda.core.contracts
import com.r3corda.core.crypto.newSecureRandom
import com.r3corda.core.node.services.testing.MockTransactionStorage
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.MEGA_CORP_KEY
import org.junit.Test
import java.security.KeyPair
import java.security.SecureRandom
import kotlin.test.assertEquals
class TransactionGraphSearchTests {
class GraphTransactionStorage(val originTx: SignedTransaction, val inputTx: SignedTransaction): MockTransactionStorage() {
init {
addTransaction(originTx)
addTransaction(inputTx)
}
}
fun random31BitValue(): Int = Math.abs(newSecureRandom().nextInt())
/**
* Build a pair of transactions. The first issues a dummy output state, and has a command applied, the second then
* references that state.
*
* @param command the command to add to the origin transaction.
* @param signer signer for the two transactions and their commands.
*/
fun buildTransactions(command: CommandData, signer: KeyPair): GraphTransactionStorage {
val originTx = TransactionType.General.Builder().apply {
addOutputState(DummyContract.State(random31BitValue()), DUMMY_NOTARY)
addCommand(command, signer.public)
signWith(signer)
}.toSignedTransaction(false)
val inputTx = TransactionType.General.Builder().apply {
addInputState(originTx.tx.outRef<DummyContract.State>(0))
signWith(signer)
}.toSignedTransaction(false)
return GraphTransactionStorage(originTx, inputTx)
}
@Test
fun `return empty from empty`() {
val storage = buildTransactions(DummyContract.Commands.Create(), MEGA_CORP_KEY)
val search = TransactionGraphSearch(storage, emptyList())
search.query = TransactionGraphSearch.Query()
val expected = emptyList<WireTransaction>()
val actual = search.call()
assertEquals(expected, actual)
}
@Test
fun `return empty from no match`() {
val storage = buildTransactions(DummyContract.Commands.Create(), MEGA_CORP_KEY)
val search = TransactionGraphSearch(storage, listOf(storage.inputTx.tx))
search.query = TransactionGraphSearch.Query()
val expected = emptyList<WireTransaction>()
val actual = search.call()
assertEquals(expected, actual)
}
@Test
fun `return origin on match`() {
val storage = buildTransactions(DummyContract.Commands.Create(), MEGA_CORP_KEY)
val search = TransactionGraphSearch(storage, listOf(storage.inputTx.tx))
search.query = TransactionGraphSearch.Query(DummyContract.Commands.Create::class.java)
val expected = listOf(storage.originTx.tx)
val actual = search.call()
assertEquals(expected, actual)
}
}

View File

@ -2,12 +2,13 @@ package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.newSecureRandom
import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.testing.*
import org.junit.Test
import java.security.PublicKey
import java.security.SecureRandom
import java.util.Currency
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
@ -15,36 +16,40 @@ import kotlin.test.assertNotEquals
val TEST_PROGRAM_ID = TransactionGroupTests.TestCash()
class TransactionGroupTests {
val A_THOUSAND_POUNDS = TestCash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
val A_THOUSAND_POUNDS = TestCash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY)
class TestCash : Contract {
override val legalContractReference = SecureHash.sha256("TestCash")
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
}
data class State(
val deposit: PartyAndReference,
val amount: Amount<Currency>,
override val owner: PublicKey,
override val notary: Party) : OwnableState {
override val owner: PublicKey) : OwnableState {
override val contract: Contract = TEST_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
}
interface Commands : CommandData {
class Move() : TypeOnlyCommandData(), Commands
data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Commands
data class Issue(val nonce: Long = newSecureRandom().nextLong()) : Commands
data class Exit(val amount: Amount<Currency>) : Commands
}
}
infix fun TestCash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
infix fun TestCash.State.`with notary`(notary: Party) = TransactionState(this, notary)
@Test
fun success() {
transactionGroup {
roots {
transaction(A_THOUSAND_POUNDS label "£1000")
transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000")
}
transaction {
@ -117,17 +122,17 @@ class TransactionGroupTests {
// We have to do this manually without the DSL because transactionGroup { } won't let us create a tx that
// points nowhere.
val input = generateStateRef()
tg.txns += TransactionBuilder().apply {
val input = StateAndRef(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY, generateStateRef())
tg.txns += TransactionType.General.Builder().apply {
addInputState(input)
addOutputState(A_THOUSAND_POUNDS)
addOutputState(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY)
addCommand(TestCash.Commands.Move(), BOB_PUBKEY)
}.toWireTransaction()
val e = assertFailsWith(TransactionResolutionException::class) {
tg.verify()
}
assertEquals(e.hash, input.txhash)
assertEquals(e.hash, input.ref.txhash)
}
@Test
@ -135,7 +140,7 @@ class TransactionGroupTests {
// Check that a transaction cannot refer to the same input more than once.
transactionGroup {
roots {
transaction(A_THOUSAND_POUNDS label "£1000")
transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000")
}
transaction {
@ -178,4 +183,4 @@ class TransactionGroupTests {
}.toSet()
TransactionGroup(ltxns, emptySet()).verify()
}
}
}

View File

@ -13,6 +13,7 @@ import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.net.URLClassLoader
import java.security.PublicKey
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertEquals
@ -32,16 +33,17 @@ class AttachmentClassLoaderTests {
}
class AttachmentDummyContract : Contract {
class State(val magicNumber: Int = 0,
override val notary: Party) : ContractState {
data class State(val magicNumber: Int = 0) : ContractState {
override val contract = ATTACHMENT_TEST_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf()
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Always accepts.
}
@ -49,8 +51,8 @@ class AttachmentClassLoaderTests {
override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
val state = State(magicNumber)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
}
@ -215,7 +217,7 @@ class AttachmentClassLoaderTests {
val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0] as AttachmentDummyContract.State).magicNumber)
assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
}
@Test
@ -245,8 +247,8 @@ class AttachmentClassLoaderTests {
val copiedWireTransaction = bytes.deserialize(kryo2)
assertEquals(1, copiedWireTransaction.outputs.size)
val contract2 = copiedWireTransaction.outputs[0].contract as DummyContractBackdoor
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0]))
val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
}
@Test

View File

@ -0,0 +1,85 @@
package com.r3corda.core.node
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.Wallet
import com.r3corda.core.testing.DUMMY_NOTARY
import org.junit.Test
import java.security.PublicKey
import kotlin.test.assertEquals
class WalletUpdateTests {
object DummyContract : Contract {
override fun verify(tx: TransactionForContract) {
}
override val legalContractReference: SecureHash = SecureHash.sha256("")
}
private class DummyState : ContractState {
override val participants: List<PublicKey>
get() = emptyList()
override val contract = WalletUpdateTests.DummyContract
}
private val stateRef0 = StateRef(SecureHash.randomSHA256(), 0)
private val stateRef1 = StateRef(SecureHash.randomSHA256(), 1)
private val stateRef2 = StateRef(SecureHash.randomSHA256(), 2)
private val stateRef3 = StateRef(SecureHash.randomSHA256(), 3)
private val stateRef4 = StateRef(SecureHash.randomSHA256(), 4)
private val stateAndRef0 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef0)
private val stateAndRef1 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef1)
private val stateAndRef2 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef2)
private val stateAndRef3 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef3)
private val stateAndRef4 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef4)
@Test
fun `nothing plus nothing is nothing`() {
val before = Wallet.NoUpdate
val after = before + Wallet.NoUpdate
assertEquals(before, after)
}
@Test
fun `something plus nothing is something`() {
val before = Wallet.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val after = before + Wallet.NoUpdate
assertEquals(before, after)
}
@Test
fun `nothing plus something is something`() {
val before = Wallet.NoUpdate
val after = before + Wallet.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val expected = Wallet.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
assertEquals(expected, after)
}
@Test
fun `something plus consume state 0 is something without state 0 output`() {
val before = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Wallet.Update(setOf(stateRef0), setOf())
val expected = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef1))
assertEquals(expected, after)
}
@Test
fun `something plus produce state 4 is something with additional state 4 output`() {
val before = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Wallet.Update(setOf(), setOf(stateAndRef4))
val expected = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1, stateAndRef4))
assertEquals(expected, after)
}
@Test
fun `something plus consume states 0 and 1, and produce state 4, is something without state 0 and 1 outputs and only state 4 output`() {
val before = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Wallet.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef4))
val expected = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef4))
assertEquals(expected, after)
}
}

View File

@ -1,7 +1,11 @@
package com.r3corda.core.serialization
import com.google.common.primitives.Ints
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.crypto.signWithECDSA
import com.r3corda.core.messaging.Ack
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.time.Instant
import java.util.*
@ -54,6 +58,29 @@ class KryoTests {
assertThat(bits.deserialize(kryo)).isEqualTo(cyclic)
}
@Test
fun `deserialised keypair functions the same as serialised one`() {
val keyPair = generateKeyPair()
val bitsToSign: ByteArray = Ints.toByteArray(0x01234567)
val wrongBits: ByteArray = Ints.toByteArray(0x76543210)
val signature = keyPair.signWithECDSA(bitsToSign)
signature.verifyWithECDSA(bitsToSign)
assertThatThrownBy { signature.verifyWithECDSA(wrongBits) }
val deserialisedKeyPair = keyPair.serialize(kryo).deserialize(kryo)
val deserialisedSignature = deserialisedKeyPair.signWithECDSA(bitsToSign)
assertThat(deserialisedSignature).isEqualTo(signature)
deserialisedSignature.verifyWithECDSA(bitsToSign)
assertThatThrownBy { deserialisedSignature.verifyWithECDSA(wrongBits) }
}
@Test
fun `write and read Ack`() {
val tokenizableBefore = Ack
val serializedBytes = tokenizableBefore.serialize(kryo)
val tokenizableAfter = serializedBytes.deserialize(kryo)
assertThat(tokenizableAfter).isSameAs(tokenizableBefore)
}
private data class Person(val name: String, val birthday: Instant?)
@ -65,4 +92,4 @@ class KryoTests {
override fun toString(): String = "Cyclic($value)"
}
}
}

View File

@ -1,8 +1,8 @@
package com.r3corda.core.serialization
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.newSecureRandom
import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.seconds
import com.r3corda.core.testing.*
@ -11,7 +11,7 @@ import org.junit.Test
import java.security.PublicKey
import java.security.SecureRandom
import java.security.SignatureException
import java.util.Currency
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@ -21,42 +21,47 @@ class TransactionSerializationTests {
class TestCash : Contract {
override val legalContractReference = SecureHash.sha256("TestCash")
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
}
data class State(
val deposit: PartyAndReference,
val amount: Amount<Currency>,
override val owner: PublicKey,
override val notary: Party) : OwnableState {
override val owner: PublicKey) : OwnableState {
override val contract: Contract = TEST_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
}
interface Commands : CommandData {
class Move() : TypeOnlyCommandData(), Commands
data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Commands
data class Issue(val nonce: Long = newSecureRandom().nextLong()) : Commands
data class Exit(val amount: Amount<Currency>) : Commands
}
}
// Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change).
// Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change).
// It refers to a fake TX/state that we don't bother creating here.
val depositRef = MINI_CORP.ref(1)
val outputState = TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY)
val changeState = TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY)
val fakeStateRef = generateStateRef()
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public), DUMMY_NOTARY)
lateinit var tx: TransactionBuilder
@Before
fun setup() {
tx = TransactionBuilder().withItems(
fakeStateRef, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
tx = TransactionType.General.Builder().withItems(
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
)
}
@Test
fun signWireTX() {
tx.signWith(DUMMY_NOTARY_KEY)
tx.signWith(TestUtils.keypair)
val signedTX = tx.toSignedTransaction()
@ -78,6 +83,7 @@ class TransactionSerializationTests {
}
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_NOTARY_KEY)
val signedTX = tx.toSignedTransaction()
// Cannot construct with an empty sigs list.
@ -87,8 +93,9 @@ class TransactionSerializationTests {
// If the signature was replaced in transit, we don't like it.
assertFailsWith(SignatureException::class) {
val tx2 = TransactionBuilder().withItems(fakeStateRef, outputState, changeState,
val tx2 = TransactionType.General.Builder().withItems(inputState, outputState, changeState,
Command(TestCash.Commands.Move(), TestUtils.keypair2.public))
tx2.signWith(DUMMY_NOTARY_KEY)
tx2.signWith(TestUtils.keypair2)
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
@ -107,4 +114,4 @@ class TransactionSerializationTests {
assertEquals(tx.outputStates(), ltx.outputs)
assertEquals(TEST_TX_TIME, ltx.commands.getTimestampBy(DUMMY_NOTARY)!!.midpoint)
}
}
}

View File

@ -8,6 +8,8 @@ import com.google.common.collect.testing.testers.CollectionAddAllTester
import com.google.common.collect.testing.testers.CollectionClearTester
import com.google.common.collect.testing.testers.CollectionRemoveAllTester
import com.google.common.collect.testing.testers.CollectionRetainAllTester
import com.r3corda.core.serialization.deserialize
import com.r3corda.core.serialization.serialize
import junit.framework.TestSuite
import org.junit.Test
import org.junit.runner.RunWith
@ -17,7 +19,8 @@ import kotlin.test.assertEquals
@RunWith(Suite::class)
@Suite.SuiteClasses(
NonEmptySetTest.Guava::class,
NonEmptySetTest.Remove::class
NonEmptySetTest.Remove::class,
NonEmptySetTest.Serializer::class
)
class NonEmptySetTest {
/**
@ -93,6 +96,20 @@ class NonEmptySetTest {
}
}
}
/**
* Test serialization/deserialization.
*/
class Serializer {
@Test
fun `serialize deserialize`() {
val expected: NonEmptySet<Int> = nonEmptySetOf(-17, 22, 17)
val serialized = expected.serialize().bits
val actual = serialized.deserialize<NonEmptySet<Int>>()
assertEquals(expected, actual)
}
}
}
/**

View File

@ -70,7 +70,7 @@ class ProgressTrackerTest {
pt.currentStep = SimpleSteps.ONE
assertNextStep(SimpleSteps.ONE)
pt.childrenFor[SimpleSteps.TWO] = pt2
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
pt.nextStep()
assertEquals(SimpleSteps.TWO, (stepNotification.pollFirst() as ProgressTracker.Change.Structural).parent)
assertNextStep(SimpleSteps.TWO)
@ -83,7 +83,7 @@ class ProgressTrackerTest {
@Test
fun `can be rewound`() {
val pt2 = ChildSteps.tracker()
pt.childrenFor[SimpleSteps.TWO] = pt2
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
repeat(4) { pt.nextStep() }
pt.currentStep = SimpleSteps.ONE
assertEquals(SimpleSteps.TWO, pt.nextStep())

View File

@ -26,6 +26,7 @@ Read on to learn:
inthebox
getting-set-up
data-model
transaction-data-types
consensus
messaging
running-the-demos

View File

@ -8,7 +8,10 @@ Unreleased
Here are changes in git master that haven't yet made it to a snapshot release:
* Nothing yet
* 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).
* 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.)
Milestone 0

View File

@ -20,4 +20,23 @@ forever and the software will be considered production ready. Until then, expect
hard hat.
Our goal is to cut a new milestone roughly once a month. There are no fixed dates. If need be, a milestone may slip by
a few days to ensure the code is sufficiently usable.
a few days to ensure the code is sufficiently usable. Usually the release will happen around the end of the month.
Steps to cut a release
======================
1. Pick a commit that is stable and do basic QA: run all the tests, run the demos.
2. Review the commits between this release and the last looking for new features, API changes, etc. Make sure the
summary in the current section of the :doc:`release-notes` is correct and update if not. Then move it into the right
section for this release. This is the right place to put any advice on how to port app code from the last release.
3. Additionally, if there are any new features or APIs that deserve a new section in the docsite and the author didn't
create one, bug them to do so a day or two before the release.
4. Regenerate the docsite if necessary and commit.
5. Create a branch with a name like `release-M0` where 0 is replaced by the number of the milestone.
6. Tag that branch with a tag like `release-M0.0`
7. Push the branch and the tag to git.
8. Write up a short announcement containing the summary of new features, changes, and API breaks. Send it to the
r3dlg-awg mailing list.
If there are serious bugs found in the release, backport the fix to the branch and then tag it with e.g. `release-M0.1`
Minor changes to the branch don't have to be announced unless it'd be critical to get all developers updated.

View File

@ -12,23 +12,35 @@ so far. We have:
The demos create node data directories in the root of the project. If something goes wrong with them, blow away the
directories and try again.
.. warning:: Corda is developed on MacOS and works best on UNIX systems. The trader demo is easily run on Windows but
you won't get the nice coloured output. The IRS demo relies on a shell script wrapper and isn't so easily run on
Windows currently: we will fix this soon.
.. note:: Corda is developed on MacOS and works best on UNIX systems. Both demos are easily run on Windows but
you won't get the nice coloured output.
Trader demo
-----------
.. note:: On Windows, use the same commands, but run the batch file instead of the shell file (add .bat to the command)
Open two terminals, and in the first run:
Open two terminals, and in the first run:::
.. note:: If you are planning to use non-default configuration you will need to run with --role=SetupA and --role=SetupB
beforehand with the same parameters you plan to supply to the respective nodes.
gradle installDist && ./build/install/r3prototyping/bin/trader-demo --role=BUYER
**Windows**::
gradlew.bat & .\build\install\r3prototyping\bin\trader-demo --role=BUYER
**Other**::
Other: ./gradlew installDist && ./build/install/r3prototyping/bin/trader-demo --role=BUYER
It will compile things, if necessary, then create a directory named trader-demo/buyer with a bunch of files inside and
start the node. You should see it waiting for a trade to begin.
In the second terminal, run::
In the second terminal, run:
**Windows**::
.\build\install\r3prototyping\bin\trader-demo --role=SELLER
**Other**::
./build/install/r3prototyping/bin/trader-demo --role=SELLER
@ -41,26 +53,48 @@ If it doesn't work, jump on the mailing list and let us know.
IRS demo
--------
.. warning:: This demo currently works best on MacOS or Linux
Open three terminals. In the first run:
Open three terminals. In the first run:::
**Windows**::
./scripts/irs-demo.sh nodeA
gradlew.bat installDist & .\build\install\r3prototyping\bin\irsdemo.bat --role=NodeA
And in the second run:::
**Other**::
./scripts/irs-demo.sh nodeB
./gradlew installDist && ./build/install/r3prototyping/bin/irsdemo --role=NodeA
And in the second run:
**Windows**::
.\build\install\r3prototyping\bin\irsdemo.bat --role=NodeB
**Other**::
./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.
Now in the third terminal run:::
Now in the third terminal run:
./scripts/irs-demo.sh trade trade1
**Windows**::
.\build\install\r3prototyping\bin\irsdemo.bat --role=Trade trade1
**Other**::
./build/install/r3prototyping/bin/irsdemo --role=Trade trade1
You should see some activity in the other two terminals as they set up the deal. You can now run this command in
a separate window to roll the fake clock forward and trigger lots of fixing events. Things go fast so make sure you
can see the other terminals whilst you run this command!::
can see the other terminals whilst you run this command!:
./scripts/irs-demo.sh date 2017-01-30
**Windows**::
.\build\install\r3prototyping\bin\irsdemo.bat --role=Date 2017-01-30
**Other**::
./build/install/r3prototyping/bin/irsdemo --role=Date 2017-01-30

View File

@ -0,0 +1,100 @@
Transaction Data Types
======================
There is a large library of data types used in Corda transactions and contract state objects.
Amount
------
The ``Amount`` class is used to represent an amount of some fungible asset. It is a generic class which wraps around
a type used to define the underlying product, generally represented by an ``Issued`` instance, or this can be a more
complex type such as an obligation contract issuance definition (which in turn contains a token definition for whatever
the obligation is to be settled in).
.. note:: Fungible is used here to mean that instances of an asset is interchangeable for any other identical instance,
and that they can be split/merged. For example a £5 note can reasonably be exchanged for any other £5 note, and a
£10 note can be exchanged for two £5 notes, or vice-versa.
Where a contract refers directly to an amount of something, ``Amount`` should wrap ``Issued``, which in
turn can refer to a ``Currency`` (GBP, USD, CHF, etc.), or any other class. Future work in this area will include
introducing classes to represent non-currency things (such as commodities) that Issued can wrap. For more
complex amounts, ``Amount`` can wrap other types, for example to represent a number of Obligation contracts to be
delivered (themselves referring to a currency), an ``Amount`` such as the following would used:
.. container:: codeset
.. sourcecode:: kotlin
Amount<Obligation.State<Currency>>
Contract 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``
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.
A number of interfaces then extend ``ContractState``, representing standardised functionality for states:
``OwnableState``
A state which has an owner (represented as a ``PublicKey``, discussed later). Exposes the owner and a function for
replacing the owner.
``LinearState``
A state which links back to its previous state, creating a thread of states over time. Intended to simplify tracking
state versions.
``DealState``
A state representing an agreement between two or more parties. Intended to simplify implementing generic protocols
that manipulate many agreement types.
``FixableDealState``
A deal state, with further functions exposed to support fixing of interest rates.
Things (such as attachments) which are identified by their hash should implement the ``NamedByHash`` interface,
which standardises how the ID is extracted.
FungibleAssets and Cash
-----------------------
There is a common ``FungibleAsset`` superclass for contracts which model fungible assets, which also provides a standard
interface for its subclasses' state objects to implement. The clear use-case is ``Cash``, however ``FungibleAsset`` 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
-----------------
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
``SignedTransaction`` class. For processing a transaction (i.e. to verify it) it is first converted to a
``LedgerTransaction``, which involves verifying the signatures and associating them to the relevant command(s), and
resolving the attachment references to the attachments. Commands with valid signatures are encapsulated in the
``AuthenticatedObject`` type.
Party and PublicKey
-------------------
Identities of parties involved in signing a transaction can be represented simply by their ``PublicKey``, or by further
information (such as name) using the ``Party`` class. An ``AuthenticatedObject`` contains a list of the public keys
for signatures present on the transaction, as well as list of parties for those public keys (where known).
.. note:: These types are provisional and are likely to change in future, for example to add additional information to
``Party``.
Date Support
------------
There are a number of supporting interfaces and classes for use by contract which deal with dates (especially in the
context of deadlines). As contract negotiation typically deals with deadlines in terms such as "overnight", "T+3",
etc., it's desirable to allow conversion of these terms to their equivalent deadline. ``Tenor`` models the interval
before a deadline, such as 3 days, etc., while ``DateRollConvention`` describes how deadlines are modified to take
into account bank holidays or other events that modify normal working days.
Calculating the rollover of a deadline based on working days requires information on the bank holidays involved
(and where a contract's parties are in different countries, for example, this can involve multiple separate sets of
bank holidays). The ``BusinessCalendar`` class models these calendars of business holidays; currently it loads these
from files on disk, but in future this is likely to involve reference data oracles in order to ensure consensus on the
dates used.

View File

@ -9,6 +9,14 @@
<tbody>
<tr>
<td>
<a href="../com.r3corda.node.utilities/-a-n-s-i-progress-observer/index.html">com.r3corda.node.utilities.ANSIProgressObserver</a></td>
<td>
<p>This observes the <a href="../com.r3corda.node.services.statemachine/-state-machine-manager/index.html">StateMachineManager</a> and follows the progress of <a href="../com.r3corda.core.protocols/-protocol-logic/index.html">ProtocolLogic</a>s until they complete in the order
they are added to the <a href="../com.r3corda.node.services.statemachine/-state-machine-manager/index.html">StateMachineManager</a>.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.utilities/-a-n-s-i-progress-renderer/index.html">com.r3corda.node.utilities.ANSIProgressRenderer</a></td>
<td>
<p>Knows how to render a <a href="../com.r3corda.core.utilities/-progress-tracker/index.html">ProgressTracker</a> to the terminal using coloured, emoji-fied output. Useful when writing small
@ -99,7 +107,9 @@ for ensuring code runs on the right thread, and also for unit testing.</p>
<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 currency, measured in pennies, which are the smallest representable units.</p>
<p>Amount represents a positive quantity of some token (currency, asset, etc.), measured in quantity of the smallest
representable units. Note that quantity is not necessarily 1/100ths of a currency unit, but are the actual smallest
amount used in whatever underlying thing the amount represents.</p>
</td>
</tr>
<tr>
@ -114,6 +124,14 @@ 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
@ -191,7 +209,7 @@ no staff are around to handle problems.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.contracts/-cash/index.html">com.r3corda.contracts.Cash</a></td>
<a href="../com.r3corda.contracts.cash/-cash/index.html">com.r3corda.contracts.cash.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
@ -201,10 +219,9 @@ the same transaction.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.contracts.cash/-cash-issuance-definition/index.html">com.r3corda.contracts.cash.CashIssuanceDefinition</a></td>
<a href="../com.r3corda.node.services.wallet/-cash-balance-as-metrics-observer/index.html">com.r3corda.node.services.wallet.CashBalanceAsMetricsObserver</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>
<p>This class observes the wallet and reflect current cash balances as exposed metrics in the monitoring service.</p>
</td>
</tr>
<tr>
@ -229,6 +246,12 @@ contracts states, those states can be aggregated.</p>
</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>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.contracts/-command/index.html">com.r3corda.core.contracts.Command</a></td>
<td>
<p>Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes</p>
@ -249,13 +272,6 @@ contracts states, those states can be aggregated.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.contracts.cash/-common-cash-state/index.html">com.r3corda.contracts.cash.CommonCashState</a></td>
<td>
<p>Common elements of cash contract states.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.servlets/-config/index.html">com.r3corda.node.servlets.Config</a></td>
<td>
<p>Primary purpose is to install Kotlin extensions for Jackson ObjectMapper so data classes work
@ -308,15 +324,6 @@ updated, instead, any changes must generate a new successor state.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.contracts/-crowd-fund/index.html">com.r3corda.contracts.CrowdFund</a></td>
<td>
<p>This is a basic crowd funding contract. It allows a party to create a funding opportunity, then for others to
pledge during the funding period , and then for the party to either accept the funding (if the target has been reached)
return the funds to the pledge-makers (if the target has not been reached).</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.math/-cubic-spline-interpolator/index.html">com.r3corda.core.math.CubicSplineInterpolator</a></td>
<td>
<p>Interpolates values between the given data points using a <a href="../com.r3corda.core.math/-spline-function/index.html">SplineFunction</a>.</p>
@ -416,7 +423,7 @@ building partially signed transactions.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.contracts/-dummy-contract/index.html">com.r3corda.contracts.DummyContract</a></td>
<a href="../com.r3corda.core.contracts/-dummy-contract/index.html">com.r3corda.core.contracts.DummyContract</a></td>
<td>
</td>
</tr>
@ -496,6 +503,13 @@ attachments are saved to local storage automatically.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.utilities/-fiber-box/index.html">com.r3corda.node.utilities.FiberBox</a></td>
<td>
<p>Modelled on <a href="#">ThreadBox</a>, but with support for waiting that is compatible with Quasar <a href="#">Fiber</a>s and <a href="../com.r3corda.node.utilities/-mutable-clock/index.html">MutableClock</a>s</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.contracts/-fix/index.html">com.r3corda.core.contracts.Fix</a></td>
<td>
<p>A <a href="../com.r3corda.core.contracts/-fix/index.html">Fix</a> represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx.</p>
@ -555,6 +569,31 @@ 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>
<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
be merged or split as needed where the issuer is the same. For instance, dollars issued by the Fed are fungible and
countable (in cents), barrels of West Texas crude are fungible and countable (oil from two small containers
can be poured into one large container), shares of the same class in a specific company are fungible and
countable, and so on.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.contracts.cash/-fungible-asset-state/index.html">com.r3corda.contracts.cash.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-role/index.html">com.r3corda.demos.IRSDemoRole</a></td>
<td>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.internal.testing/-i-r-s-simulation/index.html">com.r3corda.node.internal.testing.IRSSimulation</a></td>
<td>
<p>A simulation in which banks execute interest rate swaps with each other, including the fixing events.</p>
@ -617,7 +656,7 @@ testing).</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.contracts/-insufficient-balance-exception/index.html">com.r3corda.contracts.InsufficientBalanceException</a></td>
<a href="../com.r3corda.contracts.cash/-insufficient-balance-exception/index.html">com.r3corda.contracts.cash.InsufficientBalanceException</a></td>
<td>
</td>
</tr>
@ -691,7 +730,7 @@ from which the state object is initialised.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.contracts/kotlin.collections.-iterable/index.html">kotlin.collections.Iterable</a> (extensions in package com.r3corda.contracts)</td>
<a href="../com.r3corda.contracts.cash/kotlin.collections.-iterable/index.html">kotlin.collections.Iterable</a> (extensions in package com.r3corda.contracts.cash)</td>
<td>
</td>
</tr>
@ -864,6 +903,12 @@ Components that do IO are either swapped out for mocks, or pointed to a <a href=
</tr>
<tr>
<td>
<a href="../com.r3corda.core.node.services.testing/-mock-transaction-storage/index.html">com.r3corda.core.node.services.testing.MockTransactionStorage</a></td>
<td>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.services.api/-monitoring-service/index.html">com.r3corda.node.services.api.MonitoringService</a></td>
<td>
<p>Provides access to various metrics and ways to notify monitoring services of things, for sysadmin purposes.
@ -872,6 +917,14 @@ This is not an interface because it is too lightweight to bother mocking out.</p
</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"
adjusted externally.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.contracts/-named-by-hash/index.html">com.r3corda.core.contracts.NamedByHash</a></td>
<td>
<p>Implemented by anything that can be named by a secure hash value (e.g. transactions, attachments).</p>
@ -905,6 +958,12 @@ replace each other based on a serial number present in the change.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.serialization/-no-references-serializer/index.html">com.r3corda.core.serialization.NoReferencesSerializer</a></td>
<td>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.internal/-node/index.html">com.r3corda.node.internal.Node</a></td>
<td>
<p>A Node manages a standalone server that takes part in the P2P network. It creates the services found in <a href="#">ServiceHub</a>,
@ -920,6 +979,13 @@ loads important data off disk and starts listening for connections.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.serialization/-node-clock/index.html">com.r3corda.node.serialization.NodeClock</a></td>
<td>
<p>A <a href="http://docs.oracle.com/javase/6/docs/api/java/time/Clock.html">Clock</a> that tokenizes itself when serialized, and delegates to an underlying <a href="http://docs.oracle.com/javase/6/docs/api/java/time/Clock.html">Clock</a> implementation.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.services.config/-node-configuration/index.html">com.r3corda.node.services.config.NodeConfiguration</a></td>
<td>
</td>
@ -969,6 +1035,14 @@ states relevant to us into a database and once such a wallet is implemented, thi
</tr>
<tr>
<td>
<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>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.protocols/-notary-error/index.html">com.r3corda.protocols.NotaryError</a></td>
<td>
</td>
@ -983,8 +1057,6 @@ states relevant to us into a database and once such a wallet is implemented, thi
<td>
<a href="../com.r3corda.protocols/-notary-protocol/index.html">com.r3corda.protocols.NotaryProtocol</a></td>
<td>
<p>A protocol to be used for obtaining a signature from a <a href="#">NotaryService</a> ascertaining the transaction
timestamp is correct and none of its inputs have been used in another completed transaction</p>
</td>
</tr>
<tr>
@ -1060,6 +1132,13 @@ ledger. The reference is intended to be encrypted so its meaningless to anyone o
</tr>
<tr>
<td>
<a href="../com.r3corda.node.services.persistence/-per-file-transaction-storage/index.html">com.r3corda.node.services.persistence.PerFileTransactionStorage</a></td>
<td>
<p>File-based transaction storage, storing transactions per file.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.contracts/-percentage-ratio-unit/index.html">com.r3corda.contracts.PercentageRatioUnit</a></td>
<td>
<p>A class to reprecent a percentage in an unambiguous way.</p>
@ -1204,6 +1283,13 @@ e.g. LIBOR 6M as of 17 March 2016. Hence it requires a source (name) and a value
</tr>
<tr>
<td>
<a href="../com.r3corda.core.serialization/-references-aware-java-serializer/index.html">com.r3corda.core.serialization.ReferencesAwareJavaSerializer</a></td>
<td>
<p>Improvement to the builtin JavaSerializer by honouring the <a href="#">Kryo.getReferences</a> setting.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.services.api/-regulator-service/index.html">com.r3corda.node.services.api.RegulatorService</a></td>
<td>
<p>Placeholder interface for regulator services.</p>
@ -1233,6 +1319,14 @@ all the transactions have been successfully verified and inserted into the local
</tr>
<tr>
<td>
<a href="../com.r3corda.core/-retryable-exception/index.html">com.r3corda.core.RetryableException</a></td>
<td>
<p>This represents a transient exception or condition that might no longer be thrown if the operation is re-run or called
again.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.demos/-role/index.html">com.r3corda.demos.Role</a></td>
<td>
</td>
@ -1252,14 +1346,6 @@ all the transactions have been successfully verified and inserted into the local
</tr>
<tr>
<td>
<a href="../com.r3corda.core.serialization/-serialize-as-string-token/index.html">com.r3corda.core.serialization.SerializeAsStringToken</a></td>
<td>
<p>A base class for implementing large objects / components / services that need to serialize themselves to a string token
to indicate which instance the token is a serialized form of.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.serialization/-serialize-as-token/index.html">com.r3corda.core.serialization.SerializeAsToken</a></td>
<td>
<p>This interface should be implemented by classes that want to substitute a token representation of themselves if
@ -1268,6 +1354,13 @@ they are serialized because they have a lot of internal state that does not seri
</tr>
<tr>
<td>
<a href="../com.r3corda.core.serialization/-serialize-as-token-context/index.html">com.r3corda.core.serialization.SerializeAsTokenContext</a></td>
<td>
<p>A context for mapping SerializationTokens to/from SerializeAsTokens.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.serialization/-serialize-as-token-serializer/index.html">com.r3corda.core.serialization.SerializeAsTokenSerializer</a></td>
<td>
<p>A Kryo serializer for <a href="../com.r3corda.core.serialization/-serialize-as-token/index.html">SerializeAsToken</a> implementations.</p>
@ -1330,6 +1423,13 @@ contained within.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.services.transactions/-simple-notary-service/index.html">com.r3corda.node.services.transactions.SimpleNotaryService</a></td>
<td>
<p>A simple Notary service that does not perform transaction validation</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.internal.testing/-simulation/index.html">com.r3corda.node.internal.testing.Simulation</a></td>
<td>
<p>Base class for network simulations that are based on the unit test / mock environment.</p>
@ -1344,6 +1444,22 @@ contained within.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.serialization/-singleton-serialization-token/index.html">com.r3corda.core.serialization.SingletonSerializationToken</a></td>
<td>
<p>A class representing a <a href="../com.r3corda.core.serialization/-serialization-token/index.html">SerializationToken</a> for some object that is not serializable but can be looked up
(when deserialized) via just the class name.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.serialization/-singleton-serialize-as-token/index.html">com.r3corda.core.serialization.SingletonSerializeAsToken</a></td>
<td>
<p>A base class for implementing large objects / components / services that need to serialize themselves to a string token
to indicate which instance the token is a serialized form of.</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.math/-spline-function/index.html">com.r3corda.core.math.SplineFunction</a></td>
<td>
<p>A <emph>spline</emph> is function piecewise-defined by polynomial functions.
@ -1367,7 +1483,7 @@ Points at which polynomial pieces connect are known as <emph>knots</emph>.</p>
<td>
<a href="../com.r3corda.node.services.statemachine/-state-machine-manager/index.html">com.r3corda.node.services.statemachine.StateMachineManager</a></td>
<td>
<p>A StateMachineManager is responsible for coordination and persistence of multiple <a href="../com.r3corda.core.protocols/-protocol-state-machine/index.html">ProtocolStateMachine</a> objects.
<p>A StateMachineManager is responsible for coordination and persistence of multiple <a href="#">ProtocolStateMachine</a> objects.
Each such object represents an instantiation of a (two-party) protocol that has reached a particular point.</p>
</td>
</tr>
@ -1428,6 +1544,13 @@ anything like that, this interface is only big enough to support the prototyping
</tr>
<tr>
<td>
<a href="../com.r3corda.node.internal.testing/-test-clock/index.html">com.r3corda.node.internal.testing.TestClock</a></td>
<td>
<p>A <a href="http://docs.oracle.com/javase/6/docs/api/java/time/Clock.html">Clock</a> that can have the time advanced for use in testing</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.testing/-test-utils/index.html">com.r3corda.core.testing.TestUtils</a></td>
<td>
</td>
@ -1444,7 +1567,7 @@ way that ensures itll be released if theres an exception.</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.services.transactions/-timestamp-checker/index.html">com.r3corda.node.services.transactions.TimestampChecker</a></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>
</td>
@ -1551,6 +1674,13 @@ this subgraph does not contain conflicts and is accepted by the involved contrac
</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>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.contracts/-transaction-verification-exception/index.html">com.r3corda.core.contracts.TransactionVerificationException</a></td>
<td>
</td>
@ -1625,6 +1755,23 @@ intended as the way things will necessarily be done longer term</p>
</tr>
<tr>
<td>
<a href="../com.r3corda.protocols/-validating-notary-protocol/index.html">com.r3corda.protocols.ValidatingNotaryProtocol</a></td>
<td>
<p>A notary commit protocol that makes sure a given transaction is valid before committing it. This does mean that the calling
party has to reveal the whole transaction history; however, we avoid complex conflict resolution logic where a party
has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
indeed valid</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.node.services.transactions/-validating-notary-service/index.html">com.r3corda.node.services.transactions.ValidatingNotaryService</a></td>
<td>
<p>A Notary service that validates the transaction chain of he submitted transaction before committing it</p>
</td>
</tr>
<tr>
<td>
<a href="../com.r3corda.core.node.services/-wallet/index.html">com.r3corda.core.node.services.Wallet</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,
@ -1635,6 +1782,12 @@ 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,

View File

@ -1,15 +1,15 @@
<HTML>
<HEAD>
<title>CashIssuanceDefinition.deposit - </title>
<title>AssetIssuanceDefinition.deposit - </title>
<link rel="stylesheet" href="../../style.css">
</HEAD>
<BODY>
<a href="../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href="index.html">CashIssuanceDefinition</a>&nbsp;/&nbsp;<a href=".">deposit</a><br/>
<a href="../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href="index.html">AssetIssuanceDefinition</a>&nbsp;/&nbsp;<a href=".">deposit</a><br/>
<br/>
<h1>deposit</h1>
<a name="com.r3corda.contracts.cash.CashIssuanceDefinition$deposit"></a>
<a name="com.r3corda.contracts.cash.AssetIssuanceDefinition$deposit"></a>
<code><span class="keyword">abstract</span> <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/>
<p>Where the underlying currency backing this ledger entry can be found (propagated)</p>
<p>Where the underlying asset backing this ledger entry can be found (propagated)</p>
<br/>
<br/>
</BODY>

View File

@ -0,0 +1,45 @@
<HTML>
<HEAD>
<title>AssetIssuanceDefinition - </title>
<link rel="stylesheet" href="../../style.css">
</HEAD>
<BODY>
<a href="../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href=".">AssetIssuanceDefinition</a><br/>
<br/>
<h1>AssetIssuanceDefinition</h1>
<code><span class="keyword">interface </span><span class="identifier">AssetIssuanceDefinition</span><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-issuance-definition.html"><span class="identifier">IssuanceDefinition</span></a></code><br/>
<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>
<br/>
<br/>
<h3>Properties</h3>
<table>
<tbody>
<tr>
<td>
<a href="deposit.html">deposit</a></td>
<td>
<code><span class="keyword">abstract</span> <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 asset backing this ledger entry can be found (propagated)</p>
</td>
</tr>
<tr>
<td>
<a href="token.html">token</a></td>
<td>
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">token</span><span class="symbol">: </span><span class="identifier">T</span></code></td>
</tr>
</tbody>
</table>
<h3>Inheritors</h3>
<table>
<tbody>
<tr>
<td>
<a href="../-cash/-issuance-definition/index.html">IssuanceDefinition</a></td>
<td>
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">IssuanceDefinition</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">AssetIssuanceDefinition</span><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span></code></td>
</tr>
</tbody>
</table>
</BODY>
</HTML>

View File

@ -0,0 +1,15 @@
<HTML>
<HEAD>
<title>AssetIssuanceDefinition.token - </title>
<link rel="stylesheet" href="../../style.css">
</HEAD>
<BODY>
<a href="../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href="index.html">AssetIssuanceDefinition</a>&nbsp;/&nbsp;<a href=".">token</a><br/>
<br/>
<h1>token</h1>
<a name="com.r3corda.contracts.cash.AssetIssuanceDefinition$token"></a>
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">token</span><span class="symbol">: </span><span class="identifier">T</span></code><br/>
<br/>
<br/>
</BODY>
</HTML>

View File

@ -4,10 +4,10 @@
<link rel="stylesheet" href="../style.css">
</HEAD>
<BODY>
<a href="index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href=".">CASH_PROGRAM_ID</a><br/>
<a href="index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href=".">CASH_PROGRAM_ID</a><br/>
<br/>
<h1>CASH_PROGRAM_ID</h1>
<a name="com.r3corda.contracts$CASH_PROGRAM_ID"></a>
<a name="com.r3corda.contracts.cash$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/>

View File

@ -7,7 +7,7 @@
<a href="../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href="index.html">CashIssuanceDefinition</a>&nbsp;/&nbsp;<a href=".">currency</a><br/>
<br/>
<h1>currency</h1>
<a name="com.r3corda.contracts.cash.CashIssuanceDefinition$currency"></a>
<a name="com.r3corda.core.contracts.CashIssuanceDefinition$currency"></a>
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">currency</span><span class="symbol">: </span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a></code><br/>
<br/>
<br/>

View File

@ -1,45 +0,0 @@
<HTML>
<HEAD>
<title>CashIssuanceDefinition - </title>
<link rel="stylesheet" href="../../style.css">
</HEAD>
<BODY>
<a href="../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href=".">CashIssuanceDefinition</a><br/>
<br/>
<h1>CashIssuanceDefinition</h1>
<code><span class="keyword">interface </span><span class="identifier">CashIssuanceDefinition</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-issuance-definition.html"><span class="identifier">IssuanceDefinition</span></a></code><br/>
<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>
<br/>
<br/>
<h3>Properties</h3>
<table>
<tbody>
<tr>
<td>
<a href="currency.html">currency</a></td>
<td>
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">currency</span><span class="symbol">: </span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a></code></td>
</tr>
<tr>
<td>
<a href="deposit.html">deposit</a></td>
<td>
<code><span class="keyword">abstract</span> <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>
</tbody>
</table>
<h3>Inheritors</h3>
<table>
<tbody>
<tr>
<td>
<a href="../../com.r3corda.contracts/-cash/-issuance-definition/index.html">IssuanceDefinition</a></td>
<td>
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">IssuanceDefinition</span>&nbsp;<span class="symbol">:</span>&nbsp;<span class="identifier">CashIssuanceDefinition</span></code></td>
</tr>
</tbody>
</table>
</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.cash</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.cash.Cash.Commands.Exit$<init>(com.r3corda.core.contracts.Amount((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="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></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.cash</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.cash.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="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/-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.cash</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.cash.Cash.Commands.Exit$<init>(com.r3corda.core.contracts.Amount((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="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></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="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>
</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.cash</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.cash.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;SecureRandom.getInstanceStrong().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

@ -4,10 +4,10 @@
<link rel="stylesheet" href="../../../../style.css">
</HEAD>
<BODY>
<a href="../../../index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<a href=".">Issue</a><br/>
<a href="../../../index.html">com.r3corda.contracts.cash</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="../index.html"><span class="identifier">Commands</span></a></code><br/>
<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/index.html"><span class="identifier">Issue</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/>
@ -19,7 +19,7 @@ has a unique ID even when there are no inputs.</p>
<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.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;SecureRandom.getInstanceStrong().nextLong()<span class="symbol">)</span></code><p>Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
<code><span class="identifier">Issue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.cash.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;SecureRandom.getInstanceStrong().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>

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.cash</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.cash.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="../../../-fungible-asset/-commands/-issue/nonce.html">Issue.nonce</a><br/>
<br/>
<br/>
</BODY>
</HTML>

View File

@ -4,7 +4,7 @@
<link rel="stylesheet" href="../../../../style.css">
</HEAD>
<BODY>
<a href="../../../index.html">com.r3corda.contracts</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/>
<a href="../../../index.html">com.r3corda.contracts.cash</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="symbol">)</span></code><br/>

View File

@ -4,10 +4,10 @@
<link rel="stylesheet" href="../../../../style.css">
</HEAD>
<BODY>
<a href="../../../index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href="../../index.html">Cash</a>&nbsp;/&nbsp;<a href="../index.html">Commands</a>&nbsp;/&nbsp;<a href=".">Move</a><br/>
<a href="../../../index.html">com.r3corda.contracts.cash</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">class </span><span class="identifier">Move</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../../../com.r3corda.core.contracts/-type-only-command-data/index.html"><span class="identifier">TypeOnlyCommandData</span></a><span class="symbol">, </span><a href="../index.html"><span class="identifier">Commands</span></a></code><br/>
<code><span class="keyword">class </span><span class="identifier">Move</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../../../com.r3corda.core.contracts/-type-only-command-data/index.html"><span class="identifier">TypeOnlyCommandData</span></a><span class="symbol">, </span><a href="../../../-fungible-asset/-commands/-move.html"><span class="identifier">Move</span></a></code><br/>
<br/>
<br/>
<h3>Constructors</h3>

View File

@ -4,7 +4,7 @@
<link rel="stylesheet" href="../../../style.css">
</HEAD>
<BODY>
<a href="../../index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href=".">Commands</a><br/>
<a href="../../index.html">com.r3corda.contracts.cash</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/>
@ -17,7 +17,7 @@
<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></code><p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
<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>
@ -25,7 +25,7 @@ in some other way.</p>
<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;<span class="identifier">Commands</span></code><p>Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
<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/index.html"><span class="identifier">Issue</span></a></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>
@ -33,7 +33,7 @@ has a unique ID even when there are no inputs.</p>
<td>
<a href="-move/index.html">Move</a></td>
<td>
<code><span class="keyword">class </span><span class="identifier">Move</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../../com.r3corda.core.contracts/-type-only-command-data/index.html"><span class="identifier">TypeOnlyCommandData</span></a><span class="symbol">, </span><span class="identifier">Commands</span></code></td>
<code><span class="keyword">class </span><span class="identifier">Move</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../../com.r3corda.core.contracts/-type-only-command-data/index.html"><span class="identifier">TypeOnlyCommandData</span></a><span class="symbol">, </span><a href="../../-fungible-asset/-commands/-move.html"><span class="identifier">Move</span></a></code></td>
</tr>
</tbody>
</table>
@ -44,24 +44,10 @@ has a unique ID even when there are no inputs.</p>
<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></code><p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
<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;<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">class </span><span class="identifier">Move</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../../com.r3corda.core.contracts/-type-only-command-data/index.html"><span class="identifier">TypeOnlyCommandData</span></a><span class="symbol">, </span><span class="identifier">Commands</span></code></td>
</tr>
</tbody>
</table>
</BODY>

View File

@ -4,7 +4,7 @@
<link rel="stylesheet" href="../../style.css">
</HEAD>
<BODY>
<a href="../index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href="index.html">Cash</a>&nbsp;/&nbsp;<a href=".">&lt;init&gt;</a><br/>
<a href="../index.html">com.r3corda.contracts.cash</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/>

View File

@ -0,0 +1,14 @@
<HTML>
<HEAD>
<title>Cash.IssuanceDefinition.<init> - </title>
<link rel="stylesheet" href="../../../style.css">
</HEAD>
<BODY>
<a href="../../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">IssuanceDefinition</a>&nbsp;/&nbsp;<a href=".">&lt;init&gt;</a><br/>
<br/>
<h1>&lt;init&gt;</h1>
<code><span class="identifier">IssuanceDefinition</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.cash.Cash.IssuanceDefinition$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.contracts.cash.Cash.IssuanceDefinition.T)/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.cash.Cash.IssuanceDefinition$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.contracts.cash.Cash.IssuanceDefinition.T)/token">token</span><span class="symbol">:</span>&nbsp;<span class="identifier">T</span><span class="symbol">)</span></code><br/>
<br/>
<br/>
</BODY>
</HTML>

View File

@ -4,12 +4,12 @@
<link rel="stylesheet" href="../../../style.css">
</HEAD>
<BODY>
<a href="../../index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">IssuanceDefinition</a>&nbsp;/&nbsp;<a href=".">deposit</a><br/>
<a href="../../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">IssuanceDefinition</a>&nbsp;/&nbsp;<a href=".">deposit</a><br/>
<br/>
<h1>deposit</h1>
<a name="com.r3corda.contracts.Cash.IssuanceDefinition$deposit"></a>
<a name="com.r3corda.contracts.cash.Cash.IssuanceDefinition$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="../../../com.r3corda.contracts.cash/-cash-issuance-definition/deposit.html">CashIssuanceDefinition.deposit</a><br/>
Overrides <a href="../../-asset-issuance-definition/deposit.html">AssetIssuanceDefinition.deposit</a><br/>
<p>Where the underlying currency backing this ledger entry can be found (propagated)</p>
<br/>
<br/>

View File

@ -0,0 +1,43 @@
<HTML>
<HEAD>
<title>Cash.IssuanceDefinition - </title>
<link rel="stylesheet" href="../../../style.css">
</HEAD>
<BODY>
<a href="../../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href=".">IssuanceDefinition</a><br/>
<br/>
<h1>IssuanceDefinition</h1>
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">IssuanceDefinition</span><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../-asset-issuance-definition/index.html"><span class="identifier">AssetIssuanceDefinition</span></a><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span></code><br/>
<br/>
<br/>
<h3>Constructors</h3>
<table>
<tbody>
<tr>
<td>
<a href="-init-.html">&lt;init&gt;</a></td>
<td>
<code><span class="identifier">IssuanceDefinition</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.cash.Cash.IssuanceDefinition$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.contracts.cash.Cash.IssuanceDefinition.T)/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.cash.Cash.IssuanceDefinition$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.contracts.cash.Cash.IssuanceDefinition.T)/token">token</span><span class="symbol">:</span>&nbsp;<span class="identifier">T</span><span class="symbol">)</span></code></td>
</tr>
</tbody>
</table>
<h3>Properties</h3>
<table>
<tbody>
<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="token.html">token</a></td>
<td>
<code><span class="keyword">val </span><span class="identifier">token</span><span class="symbol">: </span><span class="identifier">T</span></code></td>
</tr>
</tbody>
</table>
</BODY>
</HTML>

View File

@ -0,0 +1,16 @@
<HTML>
<HEAD>
<title>Cash.IssuanceDefinition.token - </title>
<link rel="stylesheet" href="../../../style.css">
</HEAD>
<BODY>
<a href="../../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">IssuanceDefinition</a>&nbsp;/&nbsp;<a href=".">token</a><br/>
<br/>
<h1>token</h1>
<a name="com.r3corda.contracts.cash.Cash.IssuanceDefinition$token"></a>
<code><span class="keyword">val </span><span class="identifier">token</span><span class="symbol">: </span><span class="identifier">T</span></code><br/>
Overrides <a href="../../-asset-issuance-definition/token.html">AssetIssuanceDefinition.token</a><br/>
<br/>
<br/>
</BODY>
</HTML>

View File

@ -0,0 +1,15 @@
<HTML>
<HEAD>
<title>Cash.State.<init> - </title>
<link rel="stylesheet" href="../../../style.css">
</HEAD>
<BODY>
<a href="../../index.html">com.r3corda.contracts.cash</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.cash.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, com.r3corda.core.crypto.Party)/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.cash.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((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="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.cash.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((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.cash.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((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></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.cash</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.cash.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="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/amount.html">State.amount</a><br/>
<br/>
<br/>
</BODY>
</HTML>

View File

@ -4,10 +4,10 @@
<link rel="stylesheet" href="../../../style.css">
</HEAD>
<BODY>
<a href="../../index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<a href=".">contract</a><br/>
<a href="../../index.html">com.r3corda.contracts.cash</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.Cash.State$contract"></a>
<a name="com.r3corda.contracts.cash.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>Contract by which the state belongs</p>

View File

@ -4,12 +4,12 @@
<link rel="stylesheet" href="../../../style.css">
</HEAD>
<BODY>
<a href="../../index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href="../index.html">Cash</a>&nbsp;/&nbsp;<a href="index.html">State</a>&nbsp;/&nbsp;<a href=".">deposit</a><br/>
<a href="../../index.html">com.r3corda.contracts.cash</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.Cash.State$deposit"></a>
<a name="com.r3corda.contracts.cash.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="../../../com.r3corda.contracts.cash/-common-cash-state/deposit.html">CommonCashState.deposit</a><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>
<br/>
<br/>

View File

@ -0,0 +1,107 @@
<HTML>
<HEAD>
<title>Cash.State - </title>
<link rel="stylesheet" href="../../../style.css">
</HEAD>
<BODY>
<a href="../../index.html">com.r3corda.contracts.cash</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.cash.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((java.util.Currency)), java.security.PublicKey, com.r3corda.core.crypto.Party)/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.cash.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((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="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.cash.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((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.cash.Cash.State$<init>(com.r3corda.core.contracts.PartyAndReference, com.r3corda.core.contracts.Amount((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></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="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="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>Contract by which the state belongs</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="../-issuance-definition/index.html"><span class="identifier">IssuanceDefinition</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="notary.html">notary</a></td>
<td>
<code><span class="keyword">val </span><span class="identifier">notary</span><span class="symbol">: </span><a href="../../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a></code><p>Identity of the notary that ensures this state is not used as an input to a transaction more than once</p>
</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>
</tbody>
</table>
<h3>Functions</h3>
<table>
<tbody>
<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.cash.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.cash.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></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.cash.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">State</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.cash</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.cash.Cash.State$issuanceDef"></a>
<code><span class="keyword">val </span><span class="identifier">issuanceDef</span><span class="symbol">: </span><a href="../-issuance-definition/index.html"><span class="identifier">IssuanceDefinition</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>

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