mirror of
https://github.com/corda/corda.git
synced 2025-04-16 07:27:17 +00:00
Merge branch 'master' into sofus-generic-contract
This commit is contained in:
commit
0bdabc3a0b
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
6
.idea/modules.xml
generated
@ -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" />
|
||||
|
58
build.gradle
58
build.gradle
@ -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)
|
||||
|
57
buildSrc/src/main/groovy/QuasarPlugin.groovy
Normal file
57
buildSrc/src/main/groovy/QuasarPlugin.groovy
Normal 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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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() }
|
@ -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)
|
@ -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>
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
@ -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.
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -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() }
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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")
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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')
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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))
|
||||
}
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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]
|
||||
}
|
||||
}
|
@ -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?
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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>)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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) }
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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>()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
@ -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.
|
||||
|
@ -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)
|
||||
|
261
core/src/main/kotlin/protocols/NotaryChangeProtocol.kt
Normal file
261
core/src/main/kotlin/protocols/NotaryChangeProtocol.kt
Normal 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()}"
|
||||
}
|
Binary file not shown.
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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())
|
||||
|
1
docs/build/html/_sources/index.txt
vendored
1
docs/build/html/_sources/index.txt
vendored
@ -26,6 +26,7 @@ Read on to learn:
|
||||
inthebox
|
||||
getting-set-up
|
||||
data-model
|
||||
transaction-data-types
|
||||
consensus
|
||||
messaging
|
||||
running-the-demos
|
||||
|
5
docs/build/html/_sources/release-notes.txt
vendored
5
docs/build/html/_sources/release-notes.txt
vendored
@ -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
|
||||
|
21
docs/build/html/_sources/release-process.txt
vendored
21
docs/build/html/_sources/release-process.txt
vendored
@ -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.
|
66
docs/build/html/_sources/running-the-demos.txt
vendored
66
docs/build/html/_sources/running-the-demos.txt
vendored
@ -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
|
||||
|
100
docs/build/html/_sources/transaction-data-types.txt
vendored
Normal file
100
docs/build/html/_sources/transaction-data-types.txt
vendored
Normal 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.
|
225
docs/build/html/api/alltypes/index.html
vendored
225
docs/build/html/api/alltypes/index.html
vendored
@ -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,
|
||||
|
@ -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> / <a href="index.html">CashIssuanceDefinition</a> / <a href=".">deposit</a><br/>
|
||||
<a href="../index.html">com.r3corda.contracts.cash</a> / <a href="index.html">AssetIssuanceDefinition</a> / <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>
|
45
docs/build/html/api/com.r3corda.contracts.cash/-asset-issuance-definition/index.html
vendored
Normal file
45
docs/build/html/api/com.r3corda.contracts.cash/-asset-issuance-definition/index.html
vendored
Normal 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> / <a href=".">AssetIssuanceDefinition</a><br/>
|
||||
<br/>
|
||||
<h1>AssetIssuanceDefinition</h1>
|
||||
<code><span class="keyword">interface </span><span class="identifier">AssetIssuanceDefinition</span><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">></span> <span class="symbol">:</span> <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"><</span><span class="identifier">T</span><span class="symbol">></span> <span class="symbol">:</span> <span class="identifier">AssetIssuanceDefinition</span><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
15
docs/build/html/api/com.r3corda.contracts.cash/-asset-issuance-definition/token.html
vendored
Normal file
15
docs/build/html/api/com.r3corda.contracts.cash/-asset-issuance-definition/token.html
vendored
Normal 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> / <a href="index.html">AssetIssuanceDefinition</a> / <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>
|
@ -4,10 +4,10 @@
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="index.html">com.r3corda.contracts</a> / <a href=".">CASH_PROGRAM_ID</a><br/>
|
||||
<a href="index.html">com.r3corda.contracts.cash</a> / <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/>
|
@ -7,7 +7,7 @@
|
||||
<a href="../index.html">com.r3corda.contracts.cash</a> / <a href="index.html">CashIssuanceDefinition</a> / <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/>
|
||||
|
@ -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> / <a href=".">CashIssuanceDefinition</a><br/>
|
||||
<br/>
|
||||
<h1>CashIssuanceDefinition</h1>
|
||||
<code><span class="keyword">interface </span><span class="identifier">CashIssuanceDefinition</span> <span class="symbol">:</span> <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> <span class="symbol">:</span> <span class="identifier">CashIssuanceDefinition</span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-exit/-init-.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-exit/-init-.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Exit.<init> - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.cash</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Exit</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">Exit</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.cash.Cash.Commands.Exit$<init>(com.r3corda.core.contracts.Amount((java.util.Currency)))/amount">amount</span><span class="symbol">:</span> <a href="../../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">)</span></code><br/>
|
||||
<p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-exit/amount.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-exit/amount.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Exit.amount - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.cash</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Exit</a> / <a href=".">amount</a><br/>
|
||||
<br/>
|
||||
<h1>amount</h1>
|
||||
<a name="com.r3corda.contracts.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"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
Overrides <a href="../../../-fungible-asset/-commands/-exit/amount.html">Exit.amount</a><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
40
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-exit/index.html
vendored
Normal file
40
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-exit/index.html
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Exit - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.cash</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href=".">Exit</a><br/>
|
||||
<br/>
|
||||
<h1>Exit</h1>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Exit</span> <span class="symbol">:</span> <a href="../index.html"><span class="identifier">Commands</span></a><span class="symbol">, </span><a href="../../../-fungible-asset/-commands/-exit/index.html"><span class="identifier">Exit</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
<p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Constructors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-init-.html"><init></a></td>
|
||||
<td>
|
||||
<code><span class="identifier">Exit</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.cash.Cash.Commands.Exit$<init>(com.r3corda.core.contracts.Amount((java.util.Currency)))/amount">amount</span><span class="symbol">:</span> <a href="../../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">)</span></code><p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Properties</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="amount.html">amount</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">val </span><span class="identifier">amount</span><span class="symbol">: </span><a href="../../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</BODY>
|
||||
</HTML>
|
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-issue/-init-.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-issue/-init-.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Issue.<init> - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.cash</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Issue</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">Issue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.cash.Cash.Commands.Issue$<init>(kotlin.Long)/nonce">nonce</span><span class="symbol">:</span> <span class="identifier">Long</span> <span class="symbol">=</span> 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>
|
@ -4,10 +4,10 @@
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href=".">Issue</a><br/>
|
||||
<a href="../../../index.html">com.r3corda.contracts.cash</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href=".">Issue</a><br/>
|
||||
<br/>
|
||||
<h1>Issue</h1>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Issue</span> <span class="symbol">:</span> <a href="../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> <span class="symbol">:</span> <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"><init></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> <span class="identifier">Long</span> <span class="symbol">=</span> 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> <span class="identifier">Long</span> <span class="symbol">=</span> 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>
|
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-issue/nonce.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-commands/-issue/nonce.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.Commands.Issue.nonce - </title>
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts.cash</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Issue</a> / <a href=".">nonce</a><br/>
|
||||
<br/>
|
||||
<h1>nonce</h1>
|
||||
<a name="com.r3corda.contracts.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>
|
@ -4,7 +4,7 @@
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Move</a> / <a href="."><init></a><br/>
|
||||
<a href="../../../index.html">com.r3corda.contracts.cash</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href="index.html">Move</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">Move</span><span class="symbol">(</span><span class="symbol">)</span></code><br/>
|
@ -4,10 +4,10 @@
|
||||
<link rel="stylesheet" href="../../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../../index.html">com.r3corda.contracts</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href=".">Move</a><br/>
|
||||
<a href="../../../index.html">com.r3corda.contracts.cash</a> / <a href="../../index.html">Cash</a> / <a href="../index.html">Commands</a> / <a href=".">Move</a><br/>
|
||||
<br/>
|
||||
<h1>Move</h1>
|
||||
<code><span class="keyword">class </span><span class="identifier">Move</span> <span class="symbol">:</span> <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> <span class="symbol">:</span> <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>
|
@ -4,7 +4,7 @@
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts</a> / <a href="../index.html">Cash</a> / <a href=".">Commands</a><br/>
|
||||
<a href="../../index.html">com.r3corda.contracts.cash</a> / <a href="../index.html">Cash</a> / <a href=".">Commands</a><br/>
|
||||
<br/>
|
||||
<h1>Commands</h1>
|
||||
<code><span class="keyword">interface </span><span class="identifier">Commands</span> <span class="symbol">:</span> <a href="../../../com.r3corda.core.contracts/-command-data.html"><span class="identifier">CommandData</span></a></code><br/>
|
||||
@ -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> <span class="symbol">:</span> <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> <span class="symbol">:</span> <span class="identifier">Commands</span><span class="symbol">, </span><a href="../../-fungible-asset/-commands/-exit/index.html"><span class="identifier">Exit</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -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> <span class="symbol">:</span> <span class="identifier">Commands</span></code><p>Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Issue</span> <span class="symbol">:</span> <a href="../../-fungible-asset/-commands/-issue/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> <span class="symbol">:</span> <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> <span class="symbol">:</span> <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> <span class="symbol">:</span> <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> <span class="symbol">:</span> <span class="identifier">Commands</span><span class="symbol">, </span><a href="../../-fungible-asset/-commands/-exit/index.html"><span class="identifier">Exit</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><p>A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
in some other way.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-issue/index.html">Issue</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">Issue</span> <span class="symbol">:</span> <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> <span class="symbol">:</span> <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>
|
@ -4,7 +4,7 @@
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../index.html">com.r3corda.contracts</a> / <a href="index.html">Cash</a> / <a href="."><init></a><br/>
|
||||
<a href="../index.html">com.r3corda.contracts.cash</a> / <a href="index.html">Cash</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">Cash</span><span class="symbol">(</span><span class="symbol">)</span></code><br/>
|
14
docs/build/html/api/com.r3corda.contracts.cash/-cash/-issuance-definition/-init-.html
vendored
Normal file
14
docs/build/html/api/com.r3corda.contracts.cash/-cash/-issuance-definition/-init-.html
vendored
Normal 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> / <a href="../index.html">Cash</a> / <a href="index.html">IssuanceDefinition</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></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> <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> <span class="identifier">T</span><span class="symbol">)</span></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
@ -4,12 +4,12 @@
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts</a> / <a href="../index.html">Cash</a> / <a href="index.html">IssuanceDefinition</a> / <a href=".">deposit</a><br/>
|
||||
<a href="../../index.html">com.r3corda.contracts.cash</a> / <a href="../index.html">Cash</a> / <a href="index.html">IssuanceDefinition</a> / <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/>
|
43
docs/build/html/api/com.r3corda.contracts.cash/-cash/-issuance-definition/index.html
vendored
Normal file
43
docs/build/html/api/com.r3corda.contracts.cash/-cash/-issuance-definition/index.html
vendored
Normal 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> / <a href="../index.html">Cash</a> / <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"><</span><span class="identifier">T</span><span class="symbol">></span> <span class="symbol">:</span> <a href="../../-asset-issuance-definition/index.html"><span class="identifier">AssetIssuanceDefinition</span></a><span class="symbol"><</span><span class="identifier">T</span><span class="symbol">></span></code><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Constructors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-init-.html"><init></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> <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> <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>
|
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-issuance-definition/token.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-issuance-definition/token.html
vendored
Normal 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> / <a href="../index.html">Cash</a> / <a href="index.html">IssuanceDefinition</a> / <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>
|
15
docs/build/html/api/com.r3corda.contracts.cash/-cash/-state/-init-.html
vendored
Normal file
15
docs/build/html/api/com.r3corda.contracts.cash/-cash/-state/-init-.html
vendored
Normal 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> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href="."><init></a><br/>
|
||||
<br/>
|
||||
<h1><init></h1>
|
||||
<code><span class="identifier">State</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.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> <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> <a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.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> <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> <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>
|
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-state/amount.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-state/amount.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.amount - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.cash</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">amount</a><br/>
|
||||
<br/>
|
||||
<h1>amount</h1>
|
||||
<a name="com.r3corda.contracts.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"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
Overrides <a href="../../-fungible-asset/-state/amount.html">State.amount</a><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
@ -4,10 +4,10 @@
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">contract</a><br/>
|
||||
<a href="../../index.html">com.r3corda.contracts.cash</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">contract</a><br/>
|
||||
<br/>
|
||||
<h1>contract</h1>
|
||||
<a name="com.r3corda.contracts.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>
|
@ -4,12 +4,12 @@
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">deposit</a><br/>
|
||||
<a href="../../index.html">com.r3corda.contracts.cash</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">deposit</a><br/>
|
||||
<br/>
|
||||
<h1>deposit</h1>
|
||||
<a name="com.r3corda.contracts.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/>
|
107
docs/build/html/api/com.r3corda.contracts.cash/-cash/-state/index.html
vendored
Normal file
107
docs/build/html/api/com.r3corda.contracts.cash/-cash/-state/index.html
vendored
Normal 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> / <a href="../index.html">Cash</a> / <a href=".">State</a><br/>
|
||||
<br/>
|
||||
<h1>State</h1>
|
||||
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">State</span> <span class="symbol">:</span> <a href="../../-fungible-asset/-state/index.html"><span class="identifier">State</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
<p>A state representing a cash claim against some party</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3>Constructors</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="-init-.html"><init></a></td>
|
||||
<td>
|
||||
<code><span class="identifier">State</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.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> <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> <a href="../../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.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> <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> <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"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="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"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="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> <a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier"><ERROR CLASS></span></code><p>Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Extension Functions</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="../../../com.r3corda.contracts.testing/issued by.html">issued by</a></td>
|
||||
<td>
|
||||
<code><span class="keyword">infix</span> <span class="keyword">fun </span><span class="identifier">State</span><span class="symbol">.</span><span class="identifier">issued by</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.testing$issued by(com.r3corda.contracts.cash.Cash.State, com.r3corda.core.crypto.Party)/party">party</span><span class="symbol">:</span> <a href="../../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">State</span></code></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> <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>
|
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-state/issuance-def.html
vendored
Normal file
16
docs/build/html/api/com.r3corda.contracts.cash/-cash/-state/issuance-def.html
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Cash.State.issuanceDef - </title>
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<a href="../../index.html">com.r3corda.contracts.cash</a> / <a href="../index.html">Cash</a> / <a href="index.html">State</a> / <a href=".">issuanceDef</a><br/>
|
||||
<br/>
|
||||
<h1>issuanceDef</h1>
|
||||
<a name="com.r3corda.contracts.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"><</span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a><span class="symbol">></span></code><br/>
|
||||
Overrides <a href="../../-fungible-asset-state/issuance-def.html">FungibleAssetState.issuanceDef</a><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</BODY>
|
||||
</HTML>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user