mirror of
https://github.com/corda/corda.git
synced 2025-06-01 07:00:54 +00:00
Merge branch 'master' into sofus-generic-contract
This commit is contained in:
commit
7b241b6b12
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,5 +1,3 @@
|
|||||||
TODO
|
|
||||||
|
|
||||||
# Eclipse, ctags, Mac metadata, log files
|
# Eclipse, ctags, Mac metadata, log files
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
@ -88,3 +86,6 @@ atlassian-ide-plugin.xml
|
|||||||
com_crashlytics_export_strings.xml
|
com_crashlytics_export_strings.xml
|
||||||
crashlytics.properties
|
crashlytics.properties
|
||||||
crashlytics-build.properties
|
crashlytics-build.properties
|
||||||
|
|
||||||
|
# docs related
|
||||||
|
docs/virtualenv/
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import com.google.common.io.ByteStreams
|
import com.google.common.io.ByteStreams
|
||||||
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
import java.nio.file.attribute.FileTime
|
import java.nio.file.attribute.FileTime
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipOutputStream
|
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package com.r3corda.core.node
|
package com.r3corda.core.node
|
||||||
|
|
||||||
import com.r3corda.core.contracts.ContractState
|
import com.r3corda.core.contracts.ContractState
|
||||||
import com.r3corda.core.crypto.Party
|
|
||||||
import com.r3corda.core.contracts.PartyAndReference
|
import com.r3corda.core.contracts.PartyAndReference
|
||||||
import com.r3corda.core.contracts.TransactionBuilder
|
import com.r3corda.core.contracts.TransactionBuilder
|
||||||
|
import com.r3corda.core.crypto.Party
|
||||||
|
|
||||||
interface DummyContractBackdoor {
|
interface DummyContractBackdoor {
|
||||||
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
|
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package com.r3corda.contracts;
|
package com.r3corda.contracts;
|
||||||
|
|
||||||
import com.r3corda.core.contracts.Amount;
|
import com.r3corda.core.contracts.*;
|
||||||
import com.r3corda.core.contracts.ContractState;
|
|
||||||
import com.r3corda.core.contracts.PartyAndReference;
|
|
||||||
import com.r3corda.core.contracts.Issued;
|
|
||||||
|
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.time.*;
|
import java.time.*;
|
||||||
import java.util.Currency;
|
import java.util.*;
|
||||||
|
|
||||||
/* This is an interface solely created to demonstrate that the same kotlin tests can be run against
|
/* This is an interface solely created to demonstrate that the same kotlin tests can be run against
|
||||||
* either a Java implementation of the CommercialPaper or a kotlin implementation.
|
* either a Java implementation of the CommercialPaper or a kotlin implementation.
|
||||||
|
@ -1,24 +1,18 @@
|
|||||||
package com.r3corda.contracts;
|
package com.r3corda.contracts;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.*;
|
||||||
import com.r3corda.contracts.asset.Cash;
|
import com.r3corda.contracts.asset.*;
|
||||||
import com.r3corda.contracts.asset.CashKt;
|
|
||||||
import com.r3corda.contracts.asset.InsufficientBalanceException;
|
|
||||||
import com.r3corda.core.contracts.*;
|
import com.r3corda.core.contracts.*;
|
||||||
import com.r3corda.core.contracts.TransactionForContract.InOutGroup;
|
import com.r3corda.core.contracts.TransactionForContract.*;
|
||||||
import com.r3corda.core.crypto.NullPublicKey;
|
import com.r3corda.core.crypto.*;
|
||||||
import com.r3corda.core.crypto.Party;
|
import org.jetbrains.annotations.*;
|
||||||
import com.r3corda.core.crypto.SecureHash;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.*;
|
||||||
import java.time.Instant;
|
import java.time.*;
|
||||||
import java.util.Currency;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand;
|
import static com.r3corda.core.contracts.ContractsDSL.*;
|
||||||
import static kotlin.collections.CollectionsKt.single;
|
import static kotlin.collections.CollectionsKt.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,6 +125,12 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Redeem extends Commands {
|
public static class Redeem extends Commands {
|
||||||
|
private final Party notary;
|
||||||
|
|
||||||
|
public Redeem(Party setNotary) {
|
||||||
|
this.notary = setNotary;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
return obj instanceof Redeem;
|
return obj instanceof Redeem;
|
||||||
@ -138,6 +138,12 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Issue extends Commands {
|
public static class Issue extends Commands {
|
||||||
|
private final Party notary;
|
||||||
|
|
||||||
|
public Issue(Party setNotary) {
|
||||||
|
this.notary = setNotary;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
return obj instanceof Issue;
|
return obj instanceof Issue;
|
||||||
@ -163,15 +169,16 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
|
|
||||||
// For now do not allow multiple pieces of CP to trade in a single transaction.
|
// For now do not allow multiple pieces of CP to trade in a single transaction.
|
||||||
if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Issue) {
|
if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Issue) {
|
||||||
|
Commands.Issue issueCommand = (Commands.Issue) cmd.getValue();
|
||||||
State output = single(outputs);
|
State output = single(outputs);
|
||||||
if (!inputs.isEmpty()) {
|
if (!inputs.isEmpty()) {
|
||||||
throw new IllegalStateException("Failed Requirement: there is no input state");
|
throw new IllegalStateException("Failed Requirement: output values sum to more than the inputs");
|
||||||
}
|
}
|
||||||
if (output.faceValue.getQuantity() == 0) {
|
if (output.faceValue.getQuantity() == 0) {
|
||||||
throw new IllegalStateException("Failed Requirement: the face value is not zero");
|
throw new IllegalStateException("Failed Requirement: output values sum to more than the inputs");
|
||||||
}
|
}
|
||||||
|
|
||||||
TimestampCommand timestampCommand = tx.getTimestampByName("Notary Service");
|
TimestampCommand timestampCommand = tx.getTimestampBy(issueCommand.notary);
|
||||||
if (timestampCommand == null)
|
if (timestampCommand == null)
|
||||||
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
|
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
|
||||||
|
|
||||||
@ -182,7 +189,7 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!cmd.getSigners().contains(output.issuance.getParty().getOwningKey())) {
|
if (!cmd.getSigners().contains(output.issuance.getParty().getOwningKey())) {
|
||||||
throw new IllegalStateException("Failed Requirement: the issuance is signed by the claimed issuer of the paper");
|
throw new IllegalStateException("Failed Requirement: output states are issued by a command signer");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Everything else (Move, Redeem) requires inputs (they are not first to be actioned)
|
// Everything else (Move, Redeem) requires inputs (they are not first to be actioned)
|
||||||
@ -201,7 +208,7 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
!output.getMaturityDate().equals(input.getMaturityDate()))
|
!output.getMaturityDate().equals(input.getMaturityDate()))
|
||||||
throw new IllegalStateException("Failed requirement: the output state is the same as the input state except for owner");
|
throw new IllegalStateException("Failed requirement: the output state is the same as the input state except for owner");
|
||||||
} else if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Redeem) {
|
} else if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Redeem) {
|
||||||
TimestampCommand timestampCommand = tx.getTimestampByName("Notary Service");
|
TimestampCommand timestampCommand = tx.getTimestampBy(((Commands.Redeem) cmd.getValue()).notary);
|
||||||
if (timestampCommand == null)
|
if (timestampCommand == null)
|
||||||
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
|
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
|
||||||
Instant time = timestampCommand.getBefore();
|
Instant time = timestampCommand.getBefore();
|
||||||
@ -232,13 +239,13 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
|
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);
|
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
|
||||||
TransactionState output = new TransactionState<>(state, notary);
|
TransactionState output = new TransactionState<>(state, notary);
|
||||||
return new TransactionType.General.Builder().withItems(output, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
|
return new TransactionType.General.Builder().withItems(output, new Command(new Commands.Issue(notary), issuance.getParty().getOwningKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
|
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
|
||||||
new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet);
|
new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet);
|
||||||
tx.addInputState(paper);
|
tx.addInputState(paper);
|
||||||
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner()));
|
tx.addCommand(new Command(new Commands.Redeem(paper.getState().getNotary()), paper.getState().getData().getOwner()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
|
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
|
||||||
|
@ -65,11 +65,11 @@ class CommercialPaper : Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Commands : CommandData {
|
interface Commands : CommandData {
|
||||||
class Move : TypeOnlyCommandData(), Commands
|
class Move: TypeOnlyCommandData(), Commands
|
||||||
class Redeem : TypeOnlyCommandData(), Commands
|
data class Redeem(val notary: Party) : Commands
|
||||||
// We don't need a nonce in the issue command, because the issuance.reference field should already be unique per CP.
|
// We don't need a nonce in the issue command, because the issuance.reference field should already be unique per CP.
|
||||||
// However, nothing in the platform enforces that uniqueness: it's up to the issuer.
|
// However, nothing in the platform enforces that uniqueness: it's up to the issuer.
|
||||||
class Issue : TypeOnlyCommandData(), Commands
|
data class Issue(val notary: Party) : Commands
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verify(tx: TransactionForContract) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
@ -79,11 +79,13 @@ class CommercialPaper : Contract {
|
|||||||
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
|
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
|
||||||
// it for cash on or after the maturity date.
|
// it for cash on or after the maturity date.
|
||||||
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
|
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
|
||||||
|
// If it's an issue, we can't take notary from inputs, so it must be specified in the command
|
||||||
// Here, we match acceptable timestamp authorities by name. The list of acceptable TSAs (oracles) must be
|
val cmdVal = command.value
|
||||||
// hard coded into the contract because otherwise we could fail to gain consensus, if nodes disagree about
|
val timestamp: TimestampCommand? = when (cmdVal) {
|
||||||
// who or what is a trusted authority.
|
is Commands.Issue -> tx.getTimestampBy(cmdVal.notary)
|
||||||
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
|
is Commands.Redeem -> tx.getTimestampBy(cmdVal.notary)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
for ((inputs, outputs, key) in groups) {
|
for ((inputs, outputs, key) in groups) {
|
||||||
when (command.value) {
|
when (command.value) {
|
||||||
@ -115,13 +117,13 @@ class CommercialPaper : Contract {
|
|||||||
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
|
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
|
||||||
requireThat {
|
requireThat {
|
||||||
// Don't allow people to issue commercial paper under other entities identities.
|
// Don't allow people to issue commercial paper under other entities identities.
|
||||||
"the issuance is signed by the claimed issuer of the paper" by
|
"output states are issued by a command signer" by
|
||||||
(output.issuance.party.owningKey in command.signers)
|
(output.issuance.party.owningKey in command.signers)
|
||||||
"the face value is not zero" by (output.faceValue.quantity > 0)
|
"output values sum to more than the inputs" by (output.faceValue.quantity > 0)
|
||||||
"the maturity date is not in the past" by (time < output.maturityDate)
|
"the maturity date is not in the past" by (time < output.maturityDate)
|
||||||
// Don't allow an existing CP state to be replaced by this issuance.
|
// Don't allow an existing CP state to be replaced by this issuance.
|
||||||
// TODO: Consider how to handle the case of mistaken issuances, or other need to patch.
|
// TODO: Consider how to handle the case of mistaken issuances, or other need to patch.
|
||||||
"there is no input state" by inputs.isEmpty()
|
"output values sum to more than the inputs" by inputs.isEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +141,7 @@ class CommercialPaper : Contract {
|
|||||||
fun generateIssue(faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
|
fun generateIssue(faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
|
||||||
val issuance = faceValue.token.issuer
|
val issuance = faceValue.token.issuer
|
||||||
val state = TransactionState(State(issuance, issuance.party.owningKey, faceValue, maturityDate), notary)
|
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))
|
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issue(notary), issuance.party.owningKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,7 +158,7 @@ class CommercialPaper : Contract {
|
|||||||
* to redeem the paper. We must therefore send enough money to the key that owns the paper to satisfy the face
|
* to redeem the paper. We must therefore send enough money to the key that owns the paper to satisfy the face
|
||||||
* value, and then ensure the paper is removed from the ledger.
|
* value, and then ensure the paper is removed from the ledger.
|
||||||
*
|
*
|
||||||
* @throws InsufficientBalanceException if the wallet doesn't contain enough money to pay the redeemer
|
* @throws InsufficientBalanceException if the wallet doesn't contain enough money to pay the redeemer.
|
||||||
*/
|
*/
|
||||||
@Throws(InsufficientBalanceException::class)
|
@Throws(InsufficientBalanceException::class)
|
||||||
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
|
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
|
||||||
@ -164,7 +166,12 @@ class CommercialPaper : Contract {
|
|||||||
val amount = paper.state.data.faceValue.let { amount -> Amount<Currency>(amount.quantity, amount.token.product) }
|
val amount = paper.state.data.faceValue.let { amount -> Amount<Currency>(amount.quantity, amount.token.product) }
|
||||||
Cash().generateSpend(tx, amount, paper.state.data.owner, wallet)
|
Cash().generateSpend(tx, amount, paper.state.data.owner, wallet)
|
||||||
tx.addInputState(paper)
|
tx.addInputState(paper)
|
||||||
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner)
|
tx.addCommand(CommercialPaper.Commands.Redeem(paper.state.notary), paper.state.data.owner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = copy(owner = owner)
|
||||||
|
infix fun CommercialPaper.State.`with notary`(notary: Party) = TransactionState(this, notary)
|
||||||
|
infix fun ICommercialPaperState.`owned by`(newOwner: PublicKey) = withOwner(newOwner)
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ abstract class RatePaymentEvent(date: LocalDate,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic class for the Fixed Rate Payments on the fixed leg - see [RatePaymentEvent]
|
* Basic class for the Fixed Rate Payments on the fixed leg - see [RatePaymentEvent].
|
||||||
* Assumes that the rate is valid.
|
* Assumes that the rate is valid.
|
||||||
*/
|
*/
|
||||||
class FixedRatePaymentEvent(date: LocalDate,
|
class FixedRatePaymentEvent(date: LocalDate,
|
||||||
@ -121,7 +121,7 @@ class FixedRatePaymentEvent(date: LocalDate,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic class for the Floating Rate Payments on the floating leg - see [RatePaymentEvent]
|
* Basic class for the Floating Rate Payments on the floating leg - see [RatePaymentEvent].
|
||||||
* If the rate is null returns a zero payment. // TODO: Is this the desired behaviour?
|
* If the rate is null returns a zero payment. // TODO: Is this the desired behaviour?
|
||||||
*/
|
*/
|
||||||
class FloatingRatePaymentEvent(date: LocalDate,
|
class FloatingRatePaymentEvent(date: LocalDate,
|
||||||
@ -148,7 +148,7 @@ class FloatingRatePaymentEvent(date: LocalDate,
|
|||||||
override fun asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.token},${notional},$fixingDate,$rate,$flow"
|
override fun asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.token},${notional},$fixingDate,$rate,$flow"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for making immutables
|
* Used for making immutables.
|
||||||
*/
|
*/
|
||||||
fun withNewRate(newRate: Rate): FloatingRatePaymentEvent =
|
fun withNewRate(newRate: Rate): FloatingRatePaymentEvent =
|
||||||
FloatingRatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay,
|
FloatingRatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay,
|
||||||
@ -177,8 +177,8 @@ class FloatingRatePaymentEvent(date: LocalDate,
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Interest Rate Swap class. For a quick overview of what an IRS is, see here - http://www.pimco.co.uk/EN/Education/Pages/InterestRateSwapsBasics1-08.aspx (no endorsement)
|
* The Interest Rate Swap class. For a quick overview of what an IRS is, see here - http://www.pimco.co.uk/EN/Education/Pages/InterestRateSwapsBasics1-08.aspx (no endorsement).
|
||||||
* This contract has 4 significant data classes within it, the "Common", "Calculation", "FixedLeg" and "FloatingLeg"
|
* This contract has 4 significant data classes within it, the "Common", "Calculation", "FixedLeg" and "FloatingLeg".
|
||||||
* It also has 4 commands, "Agree", "Fix", "Pay" and "Mature".
|
* It also has 4 commands, "Agree", "Fix", "Pay" and "Mature".
|
||||||
* Currently, we are not interested (excuse pun) in valuing the swap, calculating the PVs, DFs and all that good stuff (soon though).
|
* Currently, we are not interested (excuse pun) in valuing the swap, calculating the PVs, DFs and all that good stuff (soon though).
|
||||||
* This is just a representation of a vanilla Fixed vs Floating (same currency) IRS in the R3 prototype model.
|
* This is just a representation of a vanilla Fixed vs Floating (same currency) IRS in the R3 prototype model.
|
||||||
@ -212,7 +212,7 @@ class InterestRateSwap() : Contract {
|
|||||||
/**
|
/**
|
||||||
* The Calculation data class is "mutable" through out the life of the swap, as in, it's the only thing that contains
|
* The Calculation data class is "mutable" through out the life of the swap, as in, it's the only thing that contains
|
||||||
* data that will changed from state to state (Recall that the design insists that everything is immutable, so we actually
|
* data that will changed from state to state (Recall that the design insists that everything is immutable, so we actually
|
||||||
* copy / update for each transition)
|
* copy / update for each transition).
|
||||||
*/
|
*/
|
||||||
data class Calculation(
|
data class Calculation(
|
||||||
val expression: Expression,
|
val expression: Expression,
|
||||||
@ -230,7 +230,7 @@ class InterestRateSwap() : Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the fixing for that date
|
* Returns the fixing for that date.
|
||||||
*/
|
*/
|
||||||
fun getFixing(date: LocalDate): FloatingRatePaymentEvent =
|
fun getFixing(date: LocalDate): FloatingRatePaymentEvent =
|
||||||
floatingLegPaymentSchedule.values.single { it.fixingDate == date }
|
floatingLegPaymentSchedule.values.single { it.fixingDate == date }
|
||||||
@ -496,6 +496,9 @@ class InterestRateSwap() : Contract {
|
|||||||
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
|
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
|
||||||
|
|
||||||
val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>()
|
val command = tx.commands.requireSingleCommand<InterestRateSwap.Commands>()
|
||||||
|
// TODO: This needs to either be the notary used for the inputs, or otherwise
|
||||||
|
// derived as the correct notary
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
val time = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")?.midpoint
|
val time = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")?.midpoint
|
||||||
if (time == null) throw IllegalArgumentException("must be timestamped")
|
if (time == null) throw IllegalArgumentException("must be timestamped")
|
||||||
|
|
||||||
@ -584,7 +587,7 @@ class InterestRateSwap() : Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state class contains the 4 major data classes
|
* The state class contains the 4 major data classes.
|
||||||
*/
|
*/
|
||||||
data class State(
|
data class State(
|
||||||
val fixedLeg: FixedLeg,
|
val fixedLeg: FixedLeg,
|
||||||
@ -647,7 +650,7 @@ class InterestRateSwap() : Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For evaluating arbitrary java on the platform
|
* For evaluating arbitrary java on the platform.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fun evaluateCalculation(businessDate: LocalDate, expression: Expression = calculation.expression): Any {
|
fun evaluateCalculation(businessDate: LocalDate, expression: Expression = calculation.expression): Any {
|
||||||
|
@ -43,7 +43,7 @@ open class PercentageRatioUnit(percentageAsString: String) : RatioUnit(BigDecima
|
|||||||
val String.percent: PercentageRatioUnit get() = PercentageRatioUnit(this)
|
val String.percent: PercentageRatioUnit get() = PercentageRatioUnit(this)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parent of the Rate family. Used to denote fixed rates, floating rates, reference rates etc
|
* Parent of the Rate family. Used to denote fixed rates, floating rates, reference rates etc.
|
||||||
*/
|
*/
|
||||||
open class Rate(val ratioUnit: RatioUnit? = null) {
|
open class Rate(val ratioUnit: RatioUnit? = null) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@ -82,7 +82,7 @@ class FixedRate(ratioUnit: RatioUnit) : Rate(ratioUnit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parent class of the Floating rate classes
|
* The parent class of the Floating rate classes.
|
||||||
*/
|
*/
|
||||||
open class FloatingRate : Rate(null)
|
open class FloatingRate : Rate(null)
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package com.r3corda.contracts.asset
|
package com.r3corda.contracts.asset
|
||||||
|
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.*
|
||||||
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.node.services.Wallet
|
||||||
import com.r3corda.core.utilities.Emoji
|
import com.r3corda.core.utilities.Emoji
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -17,7 +14,6 @@ import java.util.*
|
|||||||
|
|
||||||
// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
|
// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
|
||||||
val CASH_PROGRAM_ID = Cash()
|
val CASH_PROGRAM_ID = Cash()
|
||||||
//SecureHash.sha256("cash")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple
|
* A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple
|
||||||
@ -41,7 +37,7 @@ class Cash : FungibleAsset<Currency>() {
|
|||||||
* Motivation: it's the difference between a state object referencing a programRef, which references a
|
* Motivation: it's the difference between a state object referencing a programRef, which references a
|
||||||
* legalContractReference and a state object which directly references both. The latter allows the legal wording
|
* legalContractReference and a state object which directly references both. The latter allows the legal wording
|
||||||
* to evolve without requiring code changes. But creates a risk that users create objects governed by a program
|
* to evolve without requiring code changes. But creates a risk that users create objects governed by a program
|
||||||
* that is inconsistent with the legal contract
|
* that is inconsistent with the legal contract.
|
||||||
*/
|
*/
|
||||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.gov/cash-claims.html")
|
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.gov/cash-claims.html")
|
||||||
|
|
||||||
@ -242,3 +238,24 @@ val Wallet.cashBalances: Map<Currency, Amount<Currency>> get() = states.
|
|||||||
groupBy { it.token.product }.
|
groupBy { it.token.product }.
|
||||||
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
|
// 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() }
|
mapValues { it.value.map { Amount(it.quantity, it.token.product) }.sumOrThrow() }
|
||||||
|
|
||||||
|
fun Cash.State.ownedBy(owner: PublicKey) = copy(owner = owner)
|
||||||
|
fun Cash.State.issuedBy(party: Party) = copy(amount = Amount(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party))))
|
||||||
|
fun Cash.State.issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, issuanceDef.copy(issuer = deposit)))
|
||||||
|
fun Cash.State.withDeposit(deposit: PartyAndReference): Cash.State = copy(amount = amount.copy(token = amount.token.copy(issuer = deposit)))
|
||||||
|
|
||||||
|
infix fun Cash.State.`owned by`(owner: PublicKey) = ownedBy(owner)
|
||||||
|
infix fun Cash.State.`issued by`(party: Party) = issuedBy(party)
|
||||||
|
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||||
|
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = withDeposit(deposit)
|
||||||
|
|
||||||
|
// Unit testing helpers. These could go in a separate file but it's hardly worth it for just a few functions.
|
||||||
|
|
||||||
|
/** A randomly generated key. */
|
||||||
|
val DUMMY_CASH_ISSUER_KEY by lazy { generateKeyPair() }
|
||||||
|
/** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */
|
||||||
|
val DUMMY_CASH_ISSUER by lazy { Party("Snake Oil Issuer", DUMMY_CASH_ISSUER_KEY.public).ref(1) }
|
||||||
|
/** An extension property that lets you write 100.DOLLARS.CASH */
|
||||||
|
val Amount<Currency>.CASH: Cash.State get() = Cash.State(Amount(quantity, Issued(DUMMY_CASH_ISSUER, token)), NullPublicKey)
|
||||||
|
/** An extension property that lets you get a cash state from an issued token, under the [NullPublicKey] */
|
||||||
|
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey)
|
||||||
|
@ -2,11 +2,7 @@ package com.r3corda.contracts.asset
|
|||||||
|
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SecureHash
|
|
||||||
import com.r3corda.core.crypto.toStringShort
|
|
||||||
import com.r3corda.core.utilities.Emoji
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -27,7 +23,7 @@ class InsufficientBalanceException(val amountMissing: Amount<Currency>) : Except
|
|||||||
* See [Cash] for an example subclass that implements currency.
|
* See [Cash] for an example subclass that implements currency.
|
||||||
*
|
*
|
||||||
* @param T a type that represents the asset in question. This should describe the basic type of the asset
|
* @param T a type that represents the asset in question. This should describe the basic type of the asset
|
||||||
* (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.)
|
* (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.).
|
||||||
*/
|
*/
|
||||||
abstract class FungibleAsset<T> : Contract {
|
abstract class FungibleAsset<T> : Contract {
|
||||||
/** A state representing a cash claim against some party */
|
/** A state representing a cash claim against some party */
|
||||||
@ -116,7 +112,7 @@ abstract class FungibleAsset<T> : Contract {
|
|||||||
val assetCommands = tx.commands.select<FungibleAsset.Commands>()
|
val assetCommands = tx.commands.select<FungibleAsset.Commands>()
|
||||||
requireThat {
|
requireThat {
|
||||||
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
|
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
|
||||||
"output deposits are owned by a command signer" by (issuer in issueCommand.signingParties)
|
"output states are issued by a command signer" by (issuer in issueCommand.signingParties)
|
||||||
"output values sum to more than the inputs" by (outputAmount > inputAmount)
|
"output values sum to more than the inputs" by (outputAmount > inputAmount)
|
||||||
"there is only a single issue command" by (assetCommands.count() == 1)
|
"there is only a single issue command" by (assetCommands.count() == 1)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.r3corda.contracts.asset
|
package com.r3corda.contracts.asset
|
||||||
|
|
||||||
import com.r3corda.core.contracts.Amount
|
import com.r3corda.core.contracts.Amount
|
||||||
import com.r3corda.core.contracts.Issued
|
|
||||||
import com.r3corda.core.contracts.OwnableState
|
import com.r3corda.core.contracts.OwnableState
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
package com.r3corda.contracts.asset
|
package com.r3corda.contracts.asset
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting
|
import com.google.common.annotations.VisibleForTesting
|
||||||
import com.r3corda.contracts.asset.FungibleAssetState
|
import com.r3corda.contracts.asset.Obligation.Lifecycle.NORMAL
|
||||||
import com.r3corda.contracts.asset.sumFungibleOrNull
|
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
|
import com.r3corda.core.crypto.NullPublicKey
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.crypto.toStringShort
|
import com.r3corda.core.crypto.toStringShort
|
||||||
import com.r3corda.core.random63BitValue
|
import com.r3corda.core.random63BitValue
|
||||||
|
import com.r3corda.core.testing.MINI_CORP
|
||||||
|
import com.r3corda.core.testing.TEST_TX_TIME
|
||||||
import com.r3corda.core.utilities.Emoji
|
import com.r3corda.core.utilities.Emoji
|
||||||
import com.r3corda.core.utilities.NonEmptySet
|
import com.r3corda.core.utilities.NonEmptySet
|
||||||
|
import com.r3corda.core.utilities.nonEmptySetOf
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@ -36,7 +39,7 @@ class Obligation<P> : Contract {
|
|||||||
* Motivation: it's the difference between a state object referencing a programRef, which references a
|
* Motivation: it's the difference between a state object referencing a programRef, which references a
|
||||||
* legalContractReference and a state object which directly references both. The latter allows the legal wording
|
* legalContractReference and a state object which directly references both. The latter allows the legal wording
|
||||||
* to evolve without requiring code changes. But creates a risk that users create objects governed by a program
|
* to evolve without requiring code changes. But creates a risk that users create objects governed by a program
|
||||||
* that is inconsistent with the legal contract
|
* that is inconsistent with the legal contract.
|
||||||
*/
|
*/
|
||||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.example.gov/cash-settlement.html")
|
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.example.gov/cash-settlement.html")
|
||||||
|
|
||||||
@ -221,7 +224,7 @@ class Obligation<P> : Contract {
|
|||||||
/**
|
/**
|
||||||
* A command stating that the obligor is settling some or all of the amount owed by transferring a suitable
|
* A command stating that the obligor is settling some or all of the amount owed by transferring a suitable
|
||||||
* state object to the beneficiary. If this reduces the balance to zero, the state object is destroyed.
|
* state object to the beneficiary. If this reduces the balance to zero, the state object is destroyed.
|
||||||
* @see [MoveCommand]
|
* @see [MoveCommand].
|
||||||
*/
|
*/
|
||||||
data class Settle<P>(override val aggregateState: IssuanceDefinition<P>,
|
data class Settle<P>(override val aggregateState: IssuanceDefinition<P>,
|
||||||
val amount: Amount<P>) : Commands, IssuanceCommands<P>
|
val amount: Amount<P>) : Commands, IssuanceCommands<P>
|
||||||
@ -390,9 +393,7 @@ class Obligation<P> : Contract {
|
|||||||
for ((stateIdx, input) in inputs.withIndex()) {
|
for ((stateIdx, input) in inputs.withIndex()) {
|
||||||
val actualOutput = outputs[stateIdx]
|
val actualOutput = outputs[stateIdx]
|
||||||
val deadline = input.dueBefore
|
val deadline = input.dueBefore
|
||||||
// TODO: Determining correct timestamp authority needs rework now that timestamping service is part of
|
val timestamp: TimestampCommand? = tx.timestamp
|
||||||
// notary.
|
|
||||||
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
|
|
||||||
val expectedOutput: State<P> = input.copy(lifecycle = expectedOutputLifecycle)
|
val expectedOutput: State<P> = input.copy(lifecycle = expectedOutputLifecycle)
|
||||||
|
|
||||||
requireThat {
|
requireThat {
|
||||||
@ -422,7 +423,7 @@ class Obligation<P> : Contract {
|
|||||||
val outputAmount: Amount<P> = outputs.sumObligations<P>()
|
val outputAmount: Amount<P> = outputs.sumObligations<P>()
|
||||||
requireThat {
|
requireThat {
|
||||||
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
|
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
|
||||||
"output deposits are owned by a command signer" by (obligor in issueCommand.signingParties)
|
"output states are issued by a command signer" by (obligor in issueCommand.signingParties)
|
||||||
"output values sum to more than the inputs" by (outputAmount > inputAmount)
|
"output values sum to more than the inputs" by (outputAmount > inputAmount)
|
||||||
"valid settlement issuance definition is not this issuance definition" by inputs.none { it.issuanceDef in it.acceptableIssuanceDefinitions }
|
"valid settlement issuance definition is not this issuance definition" by inputs.none { it.issuanceDef in it.acceptableIssuanceDefinitions }
|
||||||
}
|
}
|
||||||
@ -814,3 +815,16 @@ fun <P> Iterable<ContractState>.sumObligationsOrNull(): Amount<P>?
|
|||||||
fun <P> Iterable<ContractState>.sumObligationsOrZero(product: P): Amount<P>
|
fun <P> Iterable<ContractState>.sumObligationsOrZero(product: P): Amount<P>
|
||||||
= filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(product)
|
= filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(product)
|
||||||
|
|
||||||
|
infix fun <T> Obligation.State<T>.at(dueBefore: Instant) = copy(template = template.copy(dueBefore = dueBefore))
|
||||||
|
infix fun <T> Obligation.IssuanceDefinition<T>.at(dueBefore: Instant) = copy(template = template.copy(dueBefore = dueBefore))
|
||||||
|
infix fun <T> Obligation.State<T>.between(parties: Pair<Party, PublicKey>) = copy(obligor = parties.first, beneficiary = parties.second)
|
||||||
|
infix fun <T> Obligation.State<T>.`owned by`(owner: PublicKey) = copy(beneficiary = owner)
|
||||||
|
infix fun <T> Obligation.State<T>.`issued by`(party: Party) = copy(obligor = party)
|
||||||
|
// For Java users:
|
||||||
|
fun <T> Obligation.State<T>.ownedBy(owner: PublicKey) = copy(beneficiary = owner)
|
||||||
|
fun <T> Obligation.State<T>.issuedBy(party: Party) = copy(obligor = party)
|
||||||
|
|
||||||
|
val Issued<Currency>.OBLIGATION_DEF: Obligation.StateTemplate<Currency>
|
||||||
|
get() = Obligation.StateTemplate(nonEmptySetOf(Cash().legalContractReference), nonEmptySetOf(this), TEST_TX_TIME)
|
||||||
|
val Amount<Issued<Currency>>.OBLIGATION: Obligation.State<Currency>
|
||||||
|
get() = Obligation.State(Obligation.Lifecycle.NORMAL, MINI_CORP, token.OBLIGATION_DEF, quantity, NullPublicKey)
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
package com.r3corda.contracts.testing
|
|
||||||
|
|
||||||
import com.r3corda.contracts.*
|
|
||||||
import com.r3corda.contracts.asset.CASH_PROGRAM_ID
|
|
||||||
import com.r3corda.contracts.asset.Cash
|
|
||||||
import com.r3corda.contracts.asset.Obligation
|
|
||||||
import com.r3corda.core.contracts.Amount
|
|
||||||
import com.r3corda.core.contracts.Contract
|
|
||||||
import com.r3corda.core.contracts.ContractState
|
|
||||||
import com.r3corda.core.contracts.DUMMY_PROGRAM_ID
|
|
||||||
import com.r3corda.core.contracts.DummyContract
|
|
||||||
import com.r3corda.core.contracts.DummyState
|
|
||||||
import com.r3corda.core.contracts.PartyAndReference
|
|
||||||
import com.r3corda.core.contracts.Issued
|
|
||||||
import com.r3corda.core.contracts.TransactionState
|
|
||||||
import com.r3corda.core.crypto.NullPublicKey
|
|
||||||
import com.r3corda.core.crypto.Party
|
|
||||||
import com.r3corda.core.crypto.generateKeyPair
|
|
||||||
import com.r3corda.core.testing.MINI_CORP
|
|
||||||
import com.r3corda.core.testing.TEST_TX_TIME
|
|
||||||
import com.r3corda.core.utilities.nonEmptySetOf
|
|
||||||
import java.security.PublicKey
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
// In a real system this would be a persistent map of hash to bytecode and we'd instantiate the object as needed inside
|
|
||||||
// a sandbox. For unit tests we just have a hard-coded list.
|
|
||||||
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,
|
|
||||||
DUMMY_PROGRAM_ID to DummyContract::class.java,
|
|
||||||
IRS_PROGRAM_ID to InterestRateSwap::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
fun generateState() = DummyState(Random().nextInt())
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
|
|
||||||
//
|
|
||||||
// Define a transaction like this:
|
|
||||||
//
|
|
||||||
// transaction {
|
|
||||||
// input { someExpression }
|
|
||||||
// output { someExpression }
|
|
||||||
// arg { someExpression }
|
|
||||||
//
|
|
||||||
// tweak {
|
|
||||||
// ... same thing but works with a copy of the parent, can add inputs/outputs/args just within this scope.
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// contract.accepts() -> should pass
|
|
||||||
// contract `fails requirement` "some substring of the error message"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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 <T> at(state: Obligation.State<T>, dueBefore: Instant) = state.copy(template = state.template.copy(dueBefore = dueBefore))
|
|
||||||
@JvmStatic fun <T> at(issuanceDef: Obligation.IssuanceDefinition<T>, dueBefore: Instant) = issuanceDef.copy(template = issuanceDef.template.copy(dueBefore = dueBefore))
|
|
||||||
@JvmStatic fun <T> between(state: Obligation.State<T>, parties: Pair<Party, PublicKey>) = state.copy(obligor = parties.first, beneficiary = parties.second)
|
|
||||||
@JvmStatic fun <T> ownedBy(state: Obligation.State<T>, owner: PublicKey) = state.copy(beneficiary = owner)
|
|
||||||
@JvmStatic fun <T> issuedBy(state: Obligation.State<T>, party: Party) = state.copy(obligor = party)
|
|
||||||
|
|
||||||
@JvmStatic fun ownedBy(state: CommercialPaper.State, owner: PublicKey) = state.copy(owner = owner)
|
|
||||||
@JvmStatic fun withNotary(state: CommercialPaper.State, notary: Party) = TransactionState(state, notary)
|
|
||||||
@JvmStatic fun ownedBy(state: ICommercialPaperState, new_owner: PublicKey) = state.withOwner(new_owner)
|
|
||||||
|
|
||||||
@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)
|
|
||||||
|
|
||||||
// Allows you to write 100.DOLLARS.OBLIGATION
|
|
||||||
@JvmStatic fun OBLIGATION_DEF(issued: Issued<Currency>)
|
|
||||||
= Obligation.StateTemplate(nonEmptySetOf(Cash().legalContractReference), nonEmptySetOf(issued), TEST_TX_TIME)
|
|
||||||
@JvmStatic fun OBLIGATION(amount: Amount<Issued<Currency>>) = Obligation.State(Obligation.Lifecycle.NORMAL, MINI_CORP,
|
|
||||||
OBLIGATION_DEF(amount.token), amount.quantity, NullPublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 <T> Obligation.State<T>.`at`(dueBefore: Instant) = JavaTestHelpers.at(this, dueBefore)
|
|
||||||
infix fun <T> Obligation.IssuanceDefinition<T>.`at`(dueBefore: Instant) = JavaTestHelpers.at(this, dueBefore)
|
|
||||||
infix fun <T> Obligation.State<T>.`between`(parties: Pair<Party, PublicKey>) = JavaTestHelpers.between(this, parties)
|
|
||||||
infix fun <T> Obligation.State<T>.`owned by`(owner: PublicKey) = JavaTestHelpers.ownedBy(this, owner)
|
|
||||||
infix fun <T> Obligation.State<T>.`issued by`(party: Party) = JavaTestHelpers.issuedBy(this, party)
|
|
||||||
|
|
||||||
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = JavaTestHelpers.ownedBy(this, owner)
|
|
||||||
infix fun CommercialPaper.State.`with notary`(notary: Party) = JavaTestHelpers.withNotary(this, notary)
|
|
||||||
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = JavaTestHelpers.ownedBy(this, new_owner)
|
|
||||||
|
|
||||||
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 Issued<Currency>.OBLIGATION_DEF: Obligation.StateTemplate<Currency> get() = JavaTestHelpers.OBLIGATION_DEF(this)
|
|
||||||
val Amount<Issued<Currency>>.OBLIGATION: Obligation.State<Currency> get() = JavaTestHelpers.OBLIGATION(this)
|
|
@ -24,7 +24,7 @@ import java.util.*
|
|||||||
*
|
*
|
||||||
* The service hub needs to provide at least a key management service and a storage service.
|
* 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!)
|
* @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>,
|
fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>,
|
||||||
notary: Party = DUMMY_NOTARY,
|
notary: Party = DUMMY_NOTARY,
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
package com.r3corda.contracts.asset;
|
package com.r3corda.contracts.asset;
|
||||||
|
|
||||||
import com.r3corda.core.contracts.PartyAndReference;
|
import com.r3corda.core.contracts.*;
|
||||||
import com.r3corda.core.serialization.OpaqueBytes;
|
import com.r3corda.core.serialization.*;
|
||||||
import kotlin.Unit;
|
import kotlin.*;
|
||||||
import org.junit.Test;
|
import org.junit.*;
|
||||||
|
|
||||||
import static com.r3corda.core.testing.JavaTestHelpers.*;
|
import static com.r3corda.core.contracts.ContractsDSL.*;
|
||||||
import static com.r3corda.core.contracts.JavaTestHelpers.*;
|
import static com.r3corda.core.testing.CoreTestUtils.*;
|
||||||
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
|
* This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL
|
||||||
*/
|
*/
|
||||||
public class CashTestsJava {
|
public class CashTestsJava {
|
||||||
|
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});
|
||||||
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});;
|
|
||||||
private PartyAndReference defaultIssuer = getMEGA_CORP().ref(defaultRef);
|
private PartyAndReference defaultIssuer = getMEGA_CORP().ref(defaultRef);
|
||||||
private Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), getDUMMY_PUBKEY_1());
|
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());
|
private Cash.State outState = new Cash.State(inState.getAmount(), getDUMMY_PUBKEY_2());
|
||||||
@ -43,7 +41,9 @@ public class CashTestsJava {
|
|||||||
});
|
});
|
||||||
tx.tweak(tw -> {
|
tx.tweak(tw -> {
|
||||||
tw.output(outState);
|
tw.output(outState);
|
||||||
tw.output(issuedBy(outState, getMINI_CORP()));
|
// issuedBy() can't be directly imported because it conflicts with other identically named functions
|
||||||
|
// with different overloads (for some reason).
|
||||||
|
tw.output(com.r3corda.contracts.asset.CashKt.issuedBy(outState, getMINI_CORP()));
|
||||||
tw.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
|
tw.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
|
||||||
return tw.failsWith("at least one asset input");
|
return tw.failsWith("at least one asset input");
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.r3corda.contracts
|
package com.r3corda.contracts
|
||||||
|
|
||||||
import com.r3corda.contracts.asset.Cash
|
import com.r3corda.contracts.asset.*
|
||||||
import com.r3corda.contracts.testing.*
|
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.days
|
import com.r3corda.core.days
|
||||||
import com.r3corda.core.node.services.testing.MockStorageService
|
import com.r3corda.core.node.services.testing.MockStorageService
|
||||||
@ -18,8 +18,8 @@ import kotlin.test.assertTrue
|
|||||||
|
|
||||||
interface ICommercialPaperTestTemplate {
|
interface ICommercialPaperTestTemplate {
|
||||||
fun getPaper(): ICommercialPaperState
|
fun getPaper(): ICommercialPaperState
|
||||||
fun getIssueCommand(): CommandData
|
fun getIssueCommand(notary: Party): CommandData
|
||||||
fun getRedeemCommand(): CommandData
|
fun getRedeemCommand(notary: Party): CommandData
|
||||||
fun getMoveCommand(): CommandData
|
fun getMoveCommand(): CommandData
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,8 +31,8 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
|
|||||||
TEST_TX_TIME + 7.days
|
TEST_TX_TIME + 7.days
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue()
|
override fun getIssueCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Issue(notary)
|
||||||
override fun getRedeemCommand(): CommandData = JavaCommercialPaper.Commands.Redeem()
|
override fun getRedeemCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Redeem(notary)
|
||||||
override fun getMoveCommand(): CommandData = JavaCommercialPaper.Commands.Move()
|
override fun getMoveCommand(): CommandData = JavaCommercialPaper.Commands.Move()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +44,8 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
|
|||||||
maturityDate = TEST_TX_TIME + 7.days
|
maturityDate = TEST_TX_TIME + 7.days
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue()
|
override fun getIssueCommand(notary: Party): CommandData = CommercialPaper.Commands.Issue(notary)
|
||||||
override fun getRedeemCommand(): CommandData = CommercialPaper.Commands.Redeem()
|
override fun getRedeemCommand(notary: Party): CommandData = CommercialPaper.Commands.Redeem(notary)
|
||||||
override fun getMoveCommand(): CommandData = CommercialPaper.Commands.Move()
|
override fun getMoveCommand(): CommandData = CommercialPaper.Commands.Move()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
// Some CP is issued onto the ledger by MegaCorp.
|
// Some CP is issued onto the ledger by MegaCorp.
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
output("paper") { thisTest.getPaper() }
|
output("paper") { thisTest.getPaper() }
|
||||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this.verifies()
|
this.verifies()
|
||||||
}
|
}
|
||||||
@ -85,7 +85,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
input("paper")
|
input("paper")
|
||||||
input("alice's $900")
|
input("alice's $900")
|
||||||
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
|
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
|
||||||
output("alice's paper") { "paper".output<ICommercialPaperState>().data `owned by` ALICE_PUBKEY }
|
output("alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
|
||||||
command(ALICE_PUBKEY) { Cash.Commands.Move() }
|
command(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||||
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
||||||
this.verifies()
|
this.verifies()
|
||||||
@ -97,13 +97,13 @@ class CommercialPaperTestsGeneric {
|
|||||||
input("alice's paper")
|
input("alice's paper")
|
||||||
input("some profits")
|
input("some profits")
|
||||||
|
|
||||||
fun TransactionDSL<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.outputs(aliceGetsBack: Amount<Issued<Currency>>) {
|
fun TransactionDSL<TransactionDSLInterpreter>.outputs(aliceGetsBack: Amount<Issued<Currency>>) {
|
||||||
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
|
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
|
||||||
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
|
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
|
||||||
}
|
}
|
||||||
|
|
||||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
command(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
|
command(ALICE_PUBKEY) { thisTest.getRedeemCommand(DUMMY_NOTARY) }
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
outputs(700.DOLLARS `issued by` issuer)
|
outputs(700.DOLLARS `issued by` issuer)
|
||||||
@ -120,7 +120,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
timestamp(TEST_TX_TIME + 8.days)
|
timestamp(TEST_TX_TIME + 8.days)
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
output { "paper".output<ICommercialPaperState>().data }
|
output { "paper".output<ICommercialPaperState>() }
|
||||||
this `fails with` "must be destroyed"
|
this `fails with` "must be destroyed"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,9 +133,9 @@ class CommercialPaperTestsGeneric {
|
|||||||
fun `key mismatch at issue`() {
|
fun `key mismatch at issue`() {
|
||||||
transaction {
|
transaction {
|
||||||
output { thisTest.getPaper() }
|
output { thisTest.getPaper() }
|
||||||
command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() }
|
command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this `fails with` "signed by the claimed issuer"
|
this `fails with` "output states are issued by a command signer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,9 +143,9 @@ class CommercialPaperTestsGeneric {
|
|||||||
fun `face value is not zero`() {
|
fun `face value is not zero`() {
|
||||||
transaction {
|
transaction {
|
||||||
output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
|
output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
|
||||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this `fails with` "face value is not zero"
|
this `fails with` "output values sum to more than the inputs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
fun `maturity date not in the past`() {
|
fun `maturity date not in the past`() {
|
||||||
transaction {
|
transaction {
|
||||||
output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) }
|
output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) }
|
||||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this `fails with` "maturity date is not in the past"
|
this `fails with` "maturity date is not in the past"
|
||||||
}
|
}
|
||||||
@ -164,13 +164,13 @@ class CommercialPaperTestsGeneric {
|
|||||||
transaction {
|
transaction {
|
||||||
input(thisTest.getPaper())
|
input(thisTest.getPaper())
|
||||||
output { thisTest.getPaper() }
|
output { thisTest.getPaper() }
|
||||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
|
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this `fails with` "there is no input state"
|
this `fails with` "output values sum to more than the inputs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : ContractState> cashOutputsToWallet(vararg outputs: TransactionState<T>): Pair<LedgerTransaction, List<StateAndRef<T>>> {
|
fun cashOutputsToWallet(vararg outputs: TransactionState<Cash.State>): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> {
|
||||||
val ltx = LedgerTransaction(emptyList(), listOf(*outputs), emptyList(), emptyList(), SecureHash.randomSHA256(), emptyList(), TransactionType.General())
|
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)) })
|
return Pair(ltx, outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
|
||||||
}
|
}
|
||||||
|
@ -240,7 +240,7 @@ class IRSTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility so I don't have to keep typing this
|
* Utility so I don't have to keep typing this.
|
||||||
*/
|
*/
|
||||||
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
|
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
|
||||||
return generateIRSTxn(irsSelector).outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
return generateIRSTxn(irsSelector).outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
||||||
@ -256,7 +256,7 @@ class IRSTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Testing a simple IRS, add a few fixings and then display as CSV
|
* Testing a simple IRS, add a few fixings and then display as CSV.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun `IRS Export test`() {
|
fun `IRS Export test`() {
|
||||||
@ -280,7 +280,7 @@ class IRSTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure it has a schedule and the schedule has some unfixed rates
|
* Make sure it has a schedule and the schedule has some unfixed rates.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun `next fixing date`() {
|
fun `next fixing date`() {
|
||||||
@ -289,7 +289,7 @@ class IRSTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate through all the fix dates and add something
|
* Iterate through all the fix dates and add something.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun generateIRSandFixSome() {
|
fun generateIRSandFixSome() {
|
||||||
@ -360,7 +360,7 @@ class IRSTests {
|
|||||||
/**
|
/**
|
||||||
* Generates a typical transactional history for an IRS.
|
* Generates a typical transactional history for an IRS.
|
||||||
*/
|
*/
|
||||||
fun trade(): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
fun trade(): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||||
|
|
||||||
val ld = LocalDate.of(2016, 3, 8)
|
val ld = LocalDate.of(2016, 3, 8)
|
||||||
val bd = BigDecimal("0.0063518")
|
val bd = BigDecimal("0.0063518")
|
||||||
@ -377,11 +377,11 @@ class IRSTests {
|
|||||||
input("irs post agreement")
|
input("irs post agreement")
|
||||||
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
|
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
|
||||||
output("irs post first fixing") {
|
output("irs post first fixing") {
|
||||||
postAgreement.data.copy(
|
postAgreement.copy(
|
||||||
postAgreement.data.fixedLeg,
|
postAgreement.fixedLeg,
|
||||||
postAgreement.data.floatingLeg,
|
postAgreement.floatingLeg,
|
||||||
postAgreement.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
postAgreement.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
||||||
postAgreement.data.common
|
postAgreement.common
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
command(ORACLE_PUBKEY) {
|
command(ORACLE_PUBKEY) {
|
||||||
@ -472,7 +472,7 @@ class IRSTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will be modified once we adapt the IRS to be cross currency
|
* This will be modified once we adapt the IRS to be cross currency.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun `ensure same currency notionals`() {
|
fun `ensure same currency notionals`() {
|
||||||
@ -653,7 +653,7 @@ class IRSTests {
|
|||||||
* result and the grouping won't work either.
|
* result and the grouping won't work either.
|
||||||
* In reality, the only fields that should be in common will be the next fixing date and the reference rate.
|
* In reality, the only fields that should be in common will be the next fixing date and the reference rate.
|
||||||
*/
|
*/
|
||||||
fun tradegroups(): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
fun tradegroups(): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||||
val ld1 = LocalDate.of(2016, 3, 8)
|
val ld1 = LocalDate.of(2016, 3, 8)
|
||||||
val bd1 = BigDecimal("0.0063518")
|
val bd1 = BigDecimal("0.0063518")
|
||||||
|
|
||||||
@ -693,20 +693,20 @@ class IRSTests {
|
|||||||
input("irs post agreement2")
|
input("irs post agreement2")
|
||||||
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
|
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
|
||||||
output("irs post first fixing1") {
|
output("irs post first fixing1") {
|
||||||
postAgreement1.data.copy(
|
postAgreement1.copy(
|
||||||
postAgreement1.data.fixedLeg,
|
postAgreement1.fixedLeg,
|
||||||
postAgreement1.data.floatingLeg,
|
postAgreement1.floatingLeg,
|
||||||
postAgreement1.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
postAgreement1.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||||
postAgreement1.data.common.copy(tradeID = "t1")
|
postAgreement1.common.copy(tradeID = "t1")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
|
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
|
||||||
output("irs post first fixing2") {
|
output("irs post first fixing2") {
|
||||||
postAgreement2.data.copy(
|
postAgreement2.copy(
|
||||||
postAgreement2.data.fixedLeg,
|
postAgreement2.fixedLeg,
|
||||||
postAgreement2.data.floatingLeg,
|
postAgreement2.floatingLeg,
|
||||||
postAgreement2.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
postAgreement2.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||||
postAgreement2.data.common.copy(tradeID = "t2")
|
postAgreement2.common.copy(tradeID = "t2")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
package com.r3corda.contracts.asset
|
package com.r3corda.contracts.asset
|
||||||
|
|
||||||
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.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
@ -78,7 +74,7 @@ class CashTests {
|
|||||||
transaction {
|
transaction {
|
||||||
output { outState }
|
output { outState }
|
||||||
command(DUMMY_PUBKEY_1) { Cash.Commands.Issue() }
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Issue() }
|
||||||
this `fails with` "output deposits are owned by a command signer"
|
this `fails with` "output states are issued by a command signer"
|
||||||
}
|
}
|
||||||
transaction {
|
transaction {
|
||||||
output {
|
output {
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
package com.r3corda.contracts.asset
|
package com.r3corda.contracts.asset
|
||||||
|
|
||||||
import com.r3corda.contracts.asset.Obligation.Lifecycle
|
import com.r3corda.contracts.asset.Obligation.Lifecycle
|
||||||
import com.r3corda.contracts.testing.*
|
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.testing.*
|
import com.r3corda.core.testing.*
|
||||||
import com.r3corda.core.testing.JavaTestHelpers
|
|
||||||
import com.r3corda.core.utilities.nonEmptySetOf
|
import com.r3corda.core.utilities.nonEmptySetOf
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.*
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertNotEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class ObligationTests {
|
class ObligationTests {
|
||||||
val defaultIssuer = MEGA_CORP.ref(1)
|
val defaultIssuer = MEGA_CORP.ref(1)
|
||||||
@ -35,12 +36,12 @@ class ObligationTests {
|
|||||||
val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2)
|
val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2)
|
||||||
|
|
||||||
private fun obligationTestRoots(
|
private fun obligationTestRoots(
|
||||||
group: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
|
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
|
||||||
) = group.apply {
|
) = group.apply {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY))
|
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY))
|
||||||
output("Bob's $1,000,000 obligation to Alice", oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY))
|
output("Bob's $1,000,000 obligation to Alice", oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY))
|
||||||
output("MegaCorp's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, BOB_PUBKEY))
|
output("MegaCorp's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB_PUBKEY))
|
||||||
output("Alice's $1,000,000", 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY)
|
output("Alice's $1,000,000", 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +97,7 @@ class ObligationTests {
|
|||||||
transaction {
|
transaction {
|
||||||
output { outState }
|
output { outState }
|
||||||
command(DUMMY_PUBKEY_1) { Obligation.Commands.Issue(outState.issuanceDef) }
|
command(DUMMY_PUBKEY_1) { Obligation.Commands.Issue(outState.issuanceDef) }
|
||||||
this `fails with` "output deposits are owned by a command signer"
|
this `fails with` "output states are issued by a command signer"
|
||||||
}
|
}
|
||||||
transaction {
|
transaction {
|
||||||
output {
|
output {
|
||||||
@ -212,8 +213,8 @@ class ObligationTests {
|
|||||||
/** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */
|
/** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */
|
||||||
@Test
|
@Test
|
||||||
fun `generate close-out net transaction`() {
|
fun `generate close-out net transaction`() {
|
||||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)
|
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
|
||||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)
|
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY)
|
||||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||||
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, obligationAliceToBob, obligationBobToAlice)
|
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, obligationAliceToBob, obligationBobToAlice)
|
||||||
signWith(ALICE_KEY)
|
signWith(ALICE_KEY)
|
||||||
@ -225,8 +226,8 @@ class ObligationTests {
|
|||||||
/** Test generating a transaction to net two obligations of the different sizes, and confirm the balance is correct. */
|
/** Test generating a transaction to net two obligations of the different sizes, and confirm the balance is correct. */
|
||||||
@Test
|
@Test
|
||||||
fun `generate close-out net transaction with remainder`() {
|
fun `generate close-out net transaction with remainder`() {
|
||||||
val obligationAliceToBob = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)
|
val obligationAliceToBob = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB_PUBKEY)
|
||||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)
|
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY)
|
||||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||||
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, obligationAliceToBob, obligationBobToAlice)
|
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, obligationAliceToBob, obligationBobToAlice)
|
||||||
signWith(ALICE_KEY)
|
signWith(ALICE_KEY)
|
||||||
@ -235,14 +236,14 @@ class ObligationTests {
|
|||||||
assertEquals(1, tx.outputs.size)
|
assertEquals(1, tx.outputs.size)
|
||||||
|
|
||||||
val actual = tx.outputs[0].data
|
val actual = tx.outputs[0].data
|
||||||
assertEquals((1000000.DOLLARS `issued by` defaultIssuer).OBLIGATION `between` Pair(ALICE, BOB_PUBKEY), actual)
|
assertEquals((1000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB_PUBKEY), actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */
|
/** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */
|
||||||
@Test
|
@Test
|
||||||
fun `generate payment net transaction`() {
|
fun `generate payment net transaction`() {
|
||||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)
|
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
|
||||||
val obligationBobToAlice = oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)
|
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY)
|
||||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||||
Obligation<Currency>().generatePaymentNetting(this, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
Obligation<Currency>().generatePaymentNetting(this, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
||||||
signWith(ALICE_KEY)
|
signWith(ALICE_KEY)
|
||||||
@ -255,8 +256,8 @@ class ObligationTests {
|
|||||||
/** Test generating a transaction to two obligations, where one is bigger than the other and therefore there is a remainder. */
|
/** Test generating a transaction to two obligations, where one is bigger than the other and therefore there is a remainder. */
|
||||||
@Test
|
@Test
|
||||||
fun `generate payment net transaction with remainder`() {
|
fun `generate payment net transaction with remainder`() {
|
||||||
val obligationAliceToBob = oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)
|
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
|
||||||
val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)
|
val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE_PUBKEY)
|
||||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||||
Obligation<Currency>().generatePaymentNetting(this, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
Obligation<Currency>().generatePaymentNetting(this, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
|
||||||
signWith(ALICE_KEY)
|
signWith(ALICE_KEY)
|
||||||
@ -353,7 +354,7 @@ class ObligationTests {
|
|||||||
input("Alice's $1,000,000 obligation to Bob")
|
input("Alice's $1,000,000 obligation to Bob")
|
||||||
input("Bob's $1,000,000 obligation to Alice")
|
input("Bob's $1,000,000 obligation to Alice")
|
||||||
input("MegaCorp's $1,000,000 obligation to Bob")
|
input("MegaCorp's $1,000,000 obligation to Bob")
|
||||||
output("change") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, BOB_PUBKEY) }
|
output("change") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB_PUBKEY) }
|
||||||
command(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
command(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this.verifies()
|
this.verifies()
|
||||||
@ -367,7 +368,7 @@ class ObligationTests {
|
|||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
input("Alice's $1,000,000 obligation to Bob")
|
input("Alice's $1,000,000 obligation to Bob")
|
||||||
input("Bob's $1,000,000 obligation to Alice")
|
input("Bob's $1,000,000 obligation to Alice")
|
||||||
output("change") { (oneMillionDollars / 2).OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) }
|
output("change") { (oneMillionDollars / 2).OBLIGATION between Pair(ALICE, BOB_PUBKEY) }
|
||||||
command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this `fails with` "amounts owed on input and output must match"
|
this `fails with` "amounts owed on input and output must match"
|
||||||
@ -421,7 +422,7 @@ class ObligationTests {
|
|||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
input("Bob's $1,000,000 obligation to Alice")
|
input("Bob's $1,000,000 obligation to Alice")
|
||||||
input("MegaCorp's $1,000,000 obligation to Bob")
|
input("MegaCorp's $1,000,000 obligation to Bob")
|
||||||
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, ALICE_PUBKEY) }
|
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE_PUBKEY) }
|
||||||
command(ALICE_PUBKEY, BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
command(ALICE_PUBKEY, BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this.verifies()
|
this.verifies()
|
||||||
@ -435,7 +436,7 @@ class ObligationTests {
|
|||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
input("Bob's $1,000,000 obligation to Alice")
|
input("Bob's $1,000,000 obligation to Alice")
|
||||||
input("MegaCorp's $1,000,000 obligation to Bob")
|
input("MegaCorp's $1,000,000 obligation to Bob")
|
||||||
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, ALICE_PUBKEY) }
|
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE_PUBKEY) }
|
||||||
command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this `fails with` "all involved parties have signed"
|
this `fails with` "all involved parties have signed"
|
||||||
@ -445,7 +446,7 @@ class ObligationTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `settlement`() {
|
fun `settlement`() {
|
||||||
// Try netting out two obligations
|
// Try settling an obligation
|
||||||
ledger {
|
ledger {
|
||||||
obligationTestRoots(this)
|
obligationTestRoots(this)
|
||||||
transaction("Settlement") {
|
transaction("Settlement") {
|
||||||
@ -456,10 +457,36 @@ class ObligationTests {
|
|||||||
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
||||||
this.verifies()
|
this.verifies()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try partial settling of an obligation
|
||||||
|
val halfAMillionDollars = 500000.DOLLARS `issued by` defaultIssuer
|
||||||
|
ledger {
|
||||||
|
transaction("Settlement") {
|
||||||
|
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY))
|
||||||
|
input(500000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY)
|
||||||
|
output("Alice's $5,000,000 obligation to Bob") { halfAMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) }
|
||||||
|
output("Bob's $500,000") { 500000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY }
|
||||||
|
command(ALICE_PUBKEY) { Obligation.Commands.Settle<Currency>(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Amount(oneMillionDollars.quantity, USD)) }
|
||||||
|
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
||||||
this.verifies()
|
this.verifies()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure we can't settle an obligation that's defaulted
|
||||||
|
val defaultedObligation: Obligation.State<Currency> = (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)).copy(lifecycle = Lifecycle.DEFAULTED)
|
||||||
|
ledger {
|
||||||
|
transaction("Settlement") {
|
||||||
|
input(defaultedObligation) // Alice's defaulted $1,000,000 obligation to Bob
|
||||||
|
input(1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY)
|
||||||
|
output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY }
|
||||||
|
command(ALICE_PUBKEY) { Obligation.Commands.Settle<Currency>(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Amount(oneMillionDollars.quantity, USD)) }
|
||||||
|
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
|
||||||
|
this `fails with` "all inputs are in the normal state"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `payment default`() {
|
fun `payment default`() {
|
||||||
// Try defaulting an obligation without a timestamp
|
// Try defaulting an obligation without a timestamp
|
||||||
@ -467,7 +494,7 @@ class ObligationTests {
|
|||||||
obligationTestRoots(this)
|
obligationTestRoots(this)
|
||||||
transaction("Settlement") {
|
transaction("Settlement") {
|
||||||
input("Alice's $1,000,000 obligation to Bob")
|
input("Alice's $1,000,000 obligation to Bob")
|
||||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)).copy(lifecycle = Lifecycle.DEFAULTED) }
|
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Lifecycle.DEFAULTED) }
|
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Lifecycle.DEFAULTED) }
|
||||||
this `fails with` "there is a timestamp from the authority"
|
this `fails with` "there is a timestamp from the authority"
|
||||||
}
|
}
|
||||||
@ -477,8 +504,8 @@ class ObligationTests {
|
|||||||
val pastTestTime = TEST_TX_TIME - Duration.ofDays(7)
|
val pastTestTime = TEST_TX_TIME - Duration.ofDays(7)
|
||||||
val futureTestTime = TEST_TX_TIME + Duration.ofDays(7)
|
val futureTestTime = TEST_TX_TIME + Duration.ofDays(7)
|
||||||
transaction("Settlement") {
|
transaction("Settlement") {
|
||||||
input(oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` futureTestTime)
|
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` futureTestTime)
|
||||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` futureTestTime, Lifecycle.DEFAULTED) }
|
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` futureTestTime, Lifecycle.DEFAULTED) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this `fails with` "the due date has passed"
|
this `fails with` "the due date has passed"
|
||||||
@ -487,8 +514,8 @@ class ObligationTests {
|
|||||||
// Try defaulting an obligation that is now in the past
|
// Try defaulting an obligation that is now in the past
|
||||||
ledger {
|
ledger {
|
||||||
transaction("Settlement") {
|
transaction("Settlement") {
|
||||||
input(oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` pastTestTime)
|
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` pastTestTime)
|
||||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` pastTestTime, Lifecycle.DEFAULTED) }
|
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` pastTestTime, Lifecycle.DEFAULTED) }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
this.verifies()
|
this.verifies()
|
||||||
|
@ -142,7 +142,7 @@ inline fun <T> logElapsedTime(label: String, logger: Logger? = null, body: () ->
|
|||||||
*
|
*
|
||||||
* val ii = state.locked { i }
|
* val ii = state.locked { i }
|
||||||
*/
|
*/
|
||||||
class ThreadBox<T>(content: T, val lock: ReentrantLock = ReentrantLock()) {
|
class ThreadBox<out T>(content: T, val lock: ReentrantLock = ReentrantLock()) {
|
||||||
val content = content
|
val content = content
|
||||||
inline fun <R> locked(body: T.() -> R): R = lock.withLock { body(content) }
|
inline fun <R> locked(body: T.() -> R): R = lock.withLock { body(content) }
|
||||||
inline fun <R> alreadyLocked(body: T.() -> R): R {
|
inline fun <R> alreadyLocked(body: T.() -> R): R {
|
||||||
@ -164,7 +164,7 @@ abstract class RetryableException(message: String) : Exception(message)
|
|||||||
* will not be serialized to disk, and if it's missing (or the first time it's accessed), the initializer will be
|
* will not be serialized to disk, and if it's missing (or the first time it's accessed), the initializer will be
|
||||||
* used to set it up. Note that the initializer will be called with the TransientProperty object locked.
|
* used to set it up. Note that the initializer will be called with the TransientProperty object locked.
|
||||||
*/
|
*/
|
||||||
class TransientProperty<T>(private val initializer: () -> T) {
|
class TransientProperty<out T>(private val initializer: () -> T) {
|
||||||
@Transient private var v: T? = null
|
@Transient private var v: T? = null
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
@file:JvmName("ContractsDSL")
|
||||||
package com.r3corda.core.contracts
|
package com.r3corda.core.contracts
|
||||||
|
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
@ -17,34 +18,27 @@ import java.util.*
|
|||||||
|
|
||||||
//// Currencies ///////////////////////////////////////////////////////////////////////////////////////////////////////
|
//// Currencies ///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
fun currency(code: String) = Currency.getInstance(code)
|
fun currency(code: String) = Currency.getInstance(code)!!
|
||||||
|
|
||||||
// Java interop
|
@JvmField val USD = currency("USD")
|
||||||
object JavaTestHelpers {
|
@JvmField val GBP = currency("GBP")
|
||||||
@JvmStatic val USD: Currency get() = currency("USD")
|
@JvmField val CHF = currency("CHF")
|
||||||
@JvmStatic val GBP: Currency get() = currency("GBP")
|
|
||||||
@JvmStatic val CHF: Currency get() = currency("CHF")
|
|
||||||
|
|
||||||
@JvmStatic fun DOLLARS(amount: Int) = Amount(amount.toLong() * 100, USD)
|
fun DOLLARS(amount: Int): Amount<Currency> = Amount(amount.toLong() * 100, USD)
|
||||||
@JvmStatic fun DOLLARS(amount: Double) = Amount((amount * 100).toLong(), USD)
|
fun DOLLARS(amount: Double): Amount<Currency> = Amount((amount * 100).toLong(), USD)
|
||||||
@JvmStatic fun POUNDS(amount: Int) = Amount(amount.toLong() * 100, GBP)
|
fun POUNDS(amount: Int): Amount<Currency> = Amount(amount.toLong() * 100, GBP)
|
||||||
@JvmStatic fun SWISS_FRANCS(amount: Int) = Amount(amount.toLong() * 100, CHF)
|
fun SWISS_FRANCS(amount: Int): Amount<Currency> = Amount(amount.toLong() * 100, CHF)
|
||||||
|
|
||||||
@JvmStatic fun issuedBy(currency: Currency, deposit: PartyAndReference) = Issued<Currency>(deposit, currency)
|
val Int.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||||
@JvmStatic fun issuedBy(amount: Amount<Currency>, deposit: PartyAndReference) = Amount(amount.quantity, issuedBy(amount.token, deposit))
|
val Double.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||||
}
|
val Int.POUNDS: Amount<Currency> get() = POUNDS(this)
|
||||||
|
val Int.SWISS_FRANCS: Amount<Currency> get() = SWISS_FRANCS(this)
|
||||||
|
|
||||||
val USD = JavaTestHelpers.USD
|
infix fun Currency.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||||
val GBP = JavaTestHelpers.GBP
|
infix fun Amount<Currency>.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||||
val CHF = JavaTestHelpers.CHF
|
infix fun Currency.issuedBy(deposit: PartyAndReference) = Issued<Currency>(deposit, this)
|
||||||
|
infix fun Amount<Currency>.issuedBy(deposit: PartyAndReference) = Amount(quantity, token.issuedBy(deposit))
|
||||||
|
|
||||||
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 /////////////////////////////////////////////////////////////////////////////////////////////////////
|
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@ -96,6 +90,7 @@ fun List<AuthenticatedObject<CommandData>>.getTimestampBy(timestampingAuthority:
|
|||||||
* Note that matching here is done by (verified, legal) name, not by public key. Any signature by any
|
* Note that matching here is done by (verified, legal) name, not by public key. Any signature by any
|
||||||
* party with a name that matches (case insensitively) any of the given names will yield a match.
|
* party with a name that matches (case insensitively) any of the given names will yield a match.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(message = "Timestamping authority should always be notary for the transaction")
|
||||||
fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: String): TimestampCommand? {
|
fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: String): TimestampCommand? {
|
||||||
val timestampCmd = filter { it.value is TimestampCommand }.singleOrNull() ?: return null
|
val timestampCmd = filter { it.value is TimestampCommand }.singleOrNull() ?: return null
|
||||||
val tsaNames = timestampCmd.signingParties.map { it.name.toLowerCase() }
|
val tsaNames = timestampCmd.signingParties.map { it.name.toLowerCase() }
|
||||||
@ -110,7 +105,7 @@ fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: Stri
|
|||||||
/**
|
/**
|
||||||
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
|
* 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
|
* @param T the type of the move command.
|
||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
// TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here?
|
// TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here?
|
||||||
@ -122,7 +117,7 @@ inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState
|
|||||||
/**
|
/**
|
||||||
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
|
* 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
|
* @param T the type of the move command.
|
||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState>,
|
inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState>,
|
||||||
|
@ -28,7 +28,7 @@ import java.util.*
|
|||||||
* TODO: It may make sense to replace this with convenience extensions over the JSR 354 MonetaryAmount interface,
|
* TODO: It may make sense to replace this with convenience extensions over the JSR 354 MonetaryAmount interface,
|
||||||
* in particular for use during calculations. This may also resolve...
|
* in particular for use during calculations. This may also resolve...
|
||||||
* TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system.
|
* TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system.
|
||||||
* TODO: Add either a scaling factor, or a variant for use in calculations
|
* TODO: Add either a scaling factor, or a variant for use in calculations.
|
||||||
*
|
*
|
||||||
* @param T the type of the token, for example [Currency].
|
* @param T the type of the token, for example [Currency].
|
||||||
*/
|
*/
|
||||||
@ -153,14 +153,14 @@ enum class AccrualAdjustment {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This is utilised in the [DateRollConvention] class to determine which way we should initially step when
|
* This is utilised in the [DateRollConvention] class to determine which way we should initially step when
|
||||||
* finding a business day
|
* finding a business day.
|
||||||
*/
|
*/
|
||||||
enum class DateRollDirection(val value: Long) { FORWARD(1), BACKWARD(-1) }
|
enum class DateRollDirection(val value: Long) { FORWARD(1), BACKWARD(-1) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This reflects what happens if a date on which a business event is supposed to happen actually falls upon a non-working day
|
* This reflects what happens if a date on which a business event is supposed to happen actually falls upon a non-working day.
|
||||||
* Depending on the accounting requirement, we can move forward until we get to a business day, or backwards
|
* Depending on the accounting requirement, we can move forward until we get to a business day, or backwards.
|
||||||
* There are some additional rules which are explained in the individual cases below
|
* There are some additional rules which are explained in the individual cases below.
|
||||||
*/
|
*/
|
||||||
enum class DateRollConvention {
|
enum class DateRollConvention {
|
||||||
// direction() cannot be a val due to the throw in the Actual instance
|
// direction() cannot be a val due to the throw in the Actual instance
|
||||||
|
@ -108,13 +108,18 @@ data class TransactionState<out T : ContractState>(
|
|||||||
val data: T,
|
val data: T,
|
||||||
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
|
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
|
||||||
val notary: Party) {
|
val notary: Party) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the underlying state, replacing the notary field with the new value.
|
* 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]
|
* To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState].
|
||||||
*/
|
*/
|
||||||
fun withNewNotary(newNotary: Party) = TransactionState(this.data, newNotary)
|
fun withNotary(newNotary: Party) = TransactionState(this.data, newNotary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Wraps the [ContractState] in a [TransactionState] object */
|
||||||
|
infix fun <T : ContractState> T.`with notary`(newNotary: Party) = withNotary(newNotary)
|
||||||
|
infix fun <T : ContractState> T.withNotary(newNotary: Party) = TransactionState(this, newNotary)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marker interface for data classes that represent the issuance state for a contract. These are intended as templates
|
* Marker interface for data classes that represent the issuance state for a contract. These are intended as templates
|
||||||
* from which the state object is initialised.
|
* from which the state object is initialised.
|
||||||
@ -127,7 +132,7 @@ interface IssuanceDefinition
|
|||||||
*
|
*
|
||||||
* @param P the type of product underlying the definition, for example [Currency].
|
* @param P the type of product underlying the definition, for example [Currency].
|
||||||
*/
|
*/
|
||||||
data class Issued<P>(
|
data class Issued<out P>(
|
||||||
val issuer: PartyAndReference,
|
val issuer: PartyAndReference,
|
||||||
val product: P
|
val product: P
|
||||||
)
|
)
|
||||||
@ -159,7 +164,7 @@ data class ScheduledStateRef(val ref: StateRef, override val scheduledAt: Instan
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents the lifecycle activity that a contract state of type [LinearState] would like to perform at a given point in time.
|
* This class represents the lifecycle activity that a contract state of type [LinearState] would like to perform at a given point in time.
|
||||||
* e.g. run a fixing protocol
|
* e.g. run a fixing protocol.
|
||||||
*
|
*
|
||||||
* Note the use of [ProtocolLogicRef] to represent a safe way to transport a [ProtocolLogic] out of the contract sandbox.
|
* Note the use of [ProtocolLogicRef] to represent a safe way to transport a [ProtocolLogic] out of the contract sandbox.
|
||||||
*
|
*
|
||||||
@ -171,9 +176,9 @@ data class ScheduledStateRef(val ref: StateRef, override val scheduledAt: Instan
|
|||||||
data class ScheduledActivity(val logicRef: ProtocolLogicRef, override val scheduledAt: Instant) : Scheduled
|
data class ScheduledActivity(val logicRef: ProtocolLogicRef, override val scheduledAt: Instant) : Scheduled
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A state that evolves by superseding itself, all of which share the common "thread"
|
* A state that evolves by superseding itself, all of which share the common "thread".
|
||||||
*
|
*
|
||||||
* This simplifies the job of tracking the current version of certain types of state in e.g. a wallet
|
* This simplifies the job of tracking the current version of certain types of state in e.g. a wallet.
|
||||||
*/
|
*/
|
||||||
interface LinearState : ContractState {
|
interface LinearState : ContractState {
|
||||||
/** Unique thread id within the wallets of all parties */
|
/** Unique thread id within the wallets of all parties */
|
||||||
@ -191,7 +196,7 @@ interface SchedulableState : ContractState {
|
|||||||
*
|
*
|
||||||
* The state has no reference to it's own StateRef, so supply that for use as input to any ProtocolLogic constructed.
|
* The state has no reference to it's own StateRef, so supply that for use as input to any ProtocolLogic constructed.
|
||||||
*
|
*
|
||||||
* @return null if there is no activity to schedule
|
* @return null if there is no activity to schedule.
|
||||||
*/
|
*/
|
||||||
fun nextScheduledActivity(thisStateRef: StateRef, protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity?
|
fun nextScheduledActivity(thisStateRef: StateRef, protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity?
|
||||||
}
|
}
|
||||||
@ -212,18 +217,18 @@ interface DealState : LinearState {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a partial transaction representing an agreement (command) to this deal, allowing a general
|
* Generate a partial transaction representing an agreement (command) to this deal, allowing a general
|
||||||
* deal/agreement protocol to generate the necessary transaction for potential implementations
|
* deal/agreement protocol to generate the necessary transaction for potential implementations.
|
||||||
*
|
*
|
||||||
* TODO: Currently this is the "inception" transaction but in future an offer of some description might be an input state ref
|
* TODO: Currently this is the "inception" transaction but in future an offer of some description might be an input state ref
|
||||||
*
|
*
|
||||||
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a
|
* 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
|
* Contract instance from a ContractState are imminent, at which point we can move this out of here.
|
||||||
*/
|
*/
|
||||||
fun generateAgreement(notary: Party): TransactionBuilder
|
fun generateAgreement(notary: Party): TransactionBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface adding fixing specific methods
|
* Interface adding fixing specific methods.
|
||||||
*/
|
*/
|
||||||
interface FixableDealState : DealState {
|
interface FixableDealState : DealState {
|
||||||
/**
|
/**
|
||||||
@ -232,10 +237,10 @@ interface FixableDealState : DealState {
|
|||||||
fun nextFixingOf(): FixOf?
|
fun nextFixingOf(): FixOf?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a fixing command for this deal and fix
|
* Generate a fixing command for this deal and fix.
|
||||||
*
|
*
|
||||||
* TODO: This would also likely move to methods on the Contract once the changes to reference
|
* TODO: This would also likely move to methods on the Contract once the changes to reference
|
||||||
* the Contract from the ContractState are in
|
* the Contract from the ContractState are in.
|
||||||
*/
|
*/
|
||||||
fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix)
|
fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix)
|
||||||
}
|
}
|
||||||
@ -314,8 +319,11 @@ data class AuthenticatedObject<out T : Any>(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* If present in a transaction, contains a time that was verified by the timestamping authority/authorities whose
|
* If present in a transaction, contains a time that was verified by the timestamping authority/authorities whose
|
||||||
* public keys are identified in the containing [Command] object. The true time must be between (after, before)
|
* public keys are identified in the containing [Command] object. The true time must be between (after, before).
|
||||||
*/
|
*/
|
||||||
|
// TODO: Timestamps are now always provided by the consensus service for the transaction, rather than potentially
|
||||||
|
// having multiple timestamps on a transaction. As such, it likely makes more sense for time to be a field on the
|
||||||
|
// transaction, rather than a command
|
||||||
data class TimestampCommand(val after: Instant?, val before: Instant?) : CommandData {
|
data class TimestampCommand(val after: Instant?, val before: Instant?) : CommandData {
|
||||||
init {
|
init {
|
||||||
if (after == null && before == null)
|
if (after == null && before == null)
|
||||||
|
@ -20,16 +20,31 @@ import java.util.*
|
|||||||
* an output state can be added by just passing in a [ContractState] – a [TransactionState] with the
|
* an output state can be added by just passing in a [ContractState] – a [TransactionState] with the
|
||||||
* default notary will be generated automatically.
|
* default notary will be generated automatically.
|
||||||
*/
|
*/
|
||||||
abstract class TransactionBuilder(protected val type: TransactionType = TransactionType.General(),
|
open class TransactionBuilder(
|
||||||
protected val notary: Party? = null) {
|
protected val type: TransactionType = TransactionType.General(),
|
||||||
protected val inputs: MutableList<StateRef> = arrayListOf()
|
protected val notary: Party? = null,
|
||||||
protected val attachments: MutableList<SecureHash> = arrayListOf()
|
protected val inputs: MutableList<StateRef> = arrayListOf(),
|
||||||
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf()
|
protected val attachments: MutableList<SecureHash> = arrayListOf(),
|
||||||
protected val commands: MutableList<Command> = arrayListOf()
|
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
|
||||||
protected val signers: MutableSet<PublicKey> = mutableSetOf()
|
protected val commands: MutableList<Command> = arrayListOf(),
|
||||||
|
protected val signers: MutableSet<PublicKey> = mutableSetOf()) {
|
||||||
|
|
||||||
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
|
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a copy of the builder.
|
||||||
|
*/
|
||||||
|
fun copy(): TransactionBuilder =
|
||||||
|
TransactionBuilder(
|
||||||
|
type = type,
|
||||||
|
notary = notary,
|
||||||
|
inputs = ArrayList(inputs),
|
||||||
|
attachments = ArrayList(attachments),
|
||||||
|
outputs = ArrayList(outputs),
|
||||||
|
commands = ArrayList(commands),
|
||||||
|
signers = LinkedHashSet(signers)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Places a [TimestampCommand] in this transaction, removing any existing command if there is one.
|
* Places a [TimestampCommand] in this transaction, removing any existing command if there is one.
|
||||||
* The command requires a signature from the Notary service, which acts as a Timestamp Authority.
|
* The command requires a signature from the Notary service, which acts as a Timestamp Authority.
|
||||||
@ -75,7 +90,7 @@ abstract class TransactionBuilder(protected val type: TransactionType = Transact
|
|||||||
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx, then
|
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx, then
|
||||||
* adds it.
|
* adds it.
|
||||||
*
|
*
|
||||||
* @throws SignatureException if the signature didn't match the transaction contents
|
* @throws SignatureException if the signature didn't match the transaction contents.
|
||||||
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
|
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
|
||||||
*/
|
*/
|
||||||
fun checkAndAddSignature(sig: DigitalSignature.WithKey) {
|
fun checkAndAddSignature(sig: DigitalSignature.WithKey) {
|
||||||
@ -86,7 +101,7 @@ abstract class TransactionBuilder(protected val type: TransactionType = Transact
|
|||||||
/**
|
/**
|
||||||
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx.
|
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx.
|
||||||
*
|
*
|
||||||
* @throws SignatureException if the signature didn't match the transaction contents
|
* @throws SignatureException if the signature didn't match the transaction contents.
|
||||||
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
|
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
|
||||||
*/
|
*/
|
||||||
fun checkSignature(sig: DigitalSignature.WithKey) {
|
fun checkSignature(sig: DigitalSignature.WithKey) {
|
||||||
@ -112,31 +127,32 @@ abstract class TransactionBuilder(protected val type: TransactionType = Transact
|
|||||||
return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
|
return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun addInputState(stateAndRef: StateAndRef<*>) {
|
open fun addInputState(stateAndRef: StateAndRef<*>) = addInputState(stateAndRef.ref, stateAndRef.state.notary)
|
||||||
|
|
||||||
|
fun addInputState(stateRef: StateRef, notary: Party) {
|
||||||
check(currentSigs.isEmpty())
|
check(currentSigs.isEmpty())
|
||||||
|
|
||||||
val notaryKey = stateAndRef.state.notary.owningKey
|
signers.add(notary.owningKey)
|
||||||
signers.add(notaryKey)
|
inputs.add(stateRef)
|
||||||
|
|
||||||
inputs.add(stateAndRef.ref)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addAttachment(attachment: Attachment) {
|
fun addAttachment(attachmentId: SecureHash) {
|
||||||
check(currentSigs.isEmpty())
|
check(currentSigs.isEmpty())
|
||||||
attachments.add(attachment.id)
|
attachments.add(attachmentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOutputState(state: TransactionState<*>) {
|
fun addOutputState(state: TransactionState<*>): Int {
|
||||||
check(currentSigs.isEmpty())
|
check(currentSigs.isEmpty())
|
||||||
outputs.add(state)
|
outputs.add(state)
|
||||||
|
return outputs.size - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
|
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
|
||||||
|
|
||||||
/** A default notary must be specified during builder construction to use this method */
|
/** A default notary must be specified during builder construction to use this method */
|
||||||
fun addOutputState(state: ContractState) {
|
fun addOutputState(state: ContractState): Int {
|
||||||
checkNotNull(notary) { "Need to specify a Notary for the state, or set a default one on TransactionBuilder initialisation" }
|
checkNotNull(notary) { "Need to specify a Notary for the state, or set a default one on TransactionBuilder initialisation" }
|
||||||
addOutputState(state, notary!!)
|
return addOutputState(state, notary!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addCommand(arg: Command) {
|
fun addCommand(arg: Command) {
|
||||||
|
@ -13,8 +13,8 @@ import java.util.concurrent.Callable
|
|||||||
*
|
*
|
||||||
* In future, this should support restricting the search by time, and other types of useful query.
|
* In future, this should support restricting the search by time, and other types of useful query.
|
||||||
*
|
*
|
||||||
* @param transactions map of transaction id to [SignedTransaction]
|
* @param transactions map of transaction id to [SignedTransaction].
|
||||||
* @param startPoints transactions to use as starting points for the search
|
* @param startPoints transactions to use as starting points for the search.
|
||||||
*/
|
*/
|
||||||
class TransactionGraphSearch(val transactions: ReadOnlyTransactionStorage,
|
class TransactionGraphSearch(val transactions: ReadOnlyTransactionStorage,
|
||||||
val startPoints: List<WireTransaction>) : Callable<List<WireTransaction>> {
|
val startPoints: List<WireTransaction>) : Callable<List<WireTransaction>> {
|
||||||
|
@ -17,7 +17,6 @@ sealed class TransactionType {
|
|||||||
* Note: Presence of _signatures_ is not checked, only the public keys to be signed for.
|
* Note: Presence of _signatures_ is not checked, only the public keys to be signed for.
|
||||||
*/
|
*/
|
||||||
fun verify(tx: TransactionForVerification) {
|
fun verify(tx: TransactionForVerification) {
|
||||||
|
|
||||||
val missing = verifySigners(tx)
|
val missing = verifySigners(tx)
|
||||||
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
|
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ sealed class TransactionType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the list of public keys that that require signatures for the transaction type.
|
* 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
|
* Note: the notary key is checked separately for all transactions and need not be included.
|
||||||
*/
|
*/
|
||||||
abstract fun getRequiredSigners(tx: TransactionForVerification): Set<PublicKey>
|
abstract fun getRequiredSigners(tx: TransactionForVerification): Set<PublicKey>
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ sealed class TransactionType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
|
* 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
|
* If any contract fails to verify, the whole transaction is considered to be invalid.
|
||||||
*/
|
*/
|
||||||
override fun verifyTransaction(tx: TransactionForVerification) {
|
override fun verifyTransaction(tx: TransactionForVerification) {
|
||||||
// TODO: Check that notary is unchanged
|
// TODO: Check that notary is unchanged
|
||||||
@ -93,7 +92,7 @@ sealed class TransactionType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that the difference between inputs and outputs is only the notary field,
|
* Check that the difference between inputs and outputs is only the notary field,
|
||||||
* and that all required signing public keys are present
|
* and that all required signing public keys are present.
|
||||||
*/
|
*/
|
||||||
override fun verifyTransaction(tx: TransactionForVerification) {
|
override fun verifyTransaction(tx: TransactionForVerification) {
|
||||||
try {
|
try {
|
||||||
|
@ -2,6 +2,7 @@ package com.r3corda.core.contracts
|
|||||||
|
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
|
import com.r3corda.core.crypto.toStringShort
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -68,12 +69,13 @@ data class TransactionForVerification(val inputs: List<TransactionState<Contract
|
|||||||
* TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated.
|
* TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated.
|
||||||
*
|
*
|
||||||
* @throws TransactionVerificationException if validation logic fails or if a contract throws an exception
|
* @throws TransactionVerificationException if validation logic fails or if a contract throws an exception
|
||||||
* (the original is in the cause field)
|
* (the original is in the cause field).
|
||||||
*/
|
*/
|
||||||
@Throws(TransactionVerificationException::class)
|
@Throws(TransactionVerificationException::class)
|
||||||
fun verify() = type.verify(this)
|
fun verify() = type.verify(this)
|
||||||
|
|
||||||
fun toTransactionForContract() = TransactionForContract(inputs.map { it.data }, outputs.map { it.data }, attachments, commands, origHash)
|
fun toTransactionForContract() = TransactionForContract(inputs.map { it.data }, outputs.map { it.data },
|
||||||
|
attachments, commands, origHash, inputs.map { it.notary }.singleOrNull())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,7 +86,8 @@ data class TransactionForContract(val inputs: List<ContractState>,
|
|||||||
val outputs: List<ContractState>,
|
val outputs: List<ContractState>,
|
||||||
val attachments: List<Attachment>,
|
val attachments: List<Attachment>,
|
||||||
val commands: List<AuthenticatedObject<CommandData>>,
|
val commands: List<AuthenticatedObject<CommandData>>,
|
||||||
val origHash: SecureHash) {
|
val origHash: SecureHash,
|
||||||
|
val inputNotary: Party? = null) {
|
||||||
override fun hashCode() = origHash.hashCode()
|
override fun hashCode() = origHash.hashCode()
|
||||||
override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
|
override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
|
||||||
|
|
||||||
@ -156,14 +159,19 @@ data class TransactionForContract(val inputs: List<ContractState>,
|
|||||||
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
|
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
|
||||||
* be used to simplify this logic.
|
* be used to simplify this logic.
|
||||||
*/
|
*/
|
||||||
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
|
data class InOutGroup<out T : ContractState, out K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
|
||||||
|
|
||||||
|
/** Get the timestamp command for this transaction, using the notary from the input states. */
|
||||||
|
val timestamp: TimestampCommand?
|
||||||
|
get() = if (inputNotary == null) null else commands.getTimestampBy(inputNotary)
|
||||||
|
|
||||||
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
|
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
|
||||||
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
|
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
|
||||||
|
|
||||||
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
|
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@Deprecated(message = "Timestamping authority should always be notary for the transaction")
|
||||||
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
|
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransactionResolutionException(val hash: SecureHash) : Exception()
|
class TransactionResolutionException(val hash: SecureHash) : Exception()
|
||||||
@ -172,6 +180,8 @@ class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTra
|
|||||||
sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) {
|
sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) {
|
||||||
class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
|
class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
|
||||||
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
||||||
class SignersMissing(tx: TransactionForVerification, missing: List<PublicKey>) : TransactionVerificationException(tx, null)
|
class SignersMissing(tx: TransactionForVerification, val missing: List<PublicKey>) : TransactionVerificationException(tx, null) {
|
||||||
|
override fun toString() = "Signers missing: ${missing.map { it.toStringShort() }}"
|
||||||
|
}
|
||||||
class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
||||||
}
|
}
|
@ -142,7 +142,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
|||||||
operator fun plus(sigList: Collection<DigitalSignature.WithKey>) = withAdditionalSignatures(sigList)
|
operator fun plus(sigList: Collection<DigitalSignature.WithKey>) = withAdditionalSignatures(sigList)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the set of missing signatures - a signature must be present for each signer public key
|
* Returns the set of missing signatures - a signature must be present for each signer public key.
|
||||||
*/
|
*/
|
||||||
fun getMissingSignatures(): Set<PublicKey> {
|
fun getMissingSignatures(): Set<PublicKey> {
|
||||||
val requiredKeys = tx.signers.toSet()
|
val requiredKeys = tx.signers.toSet()
|
||||||
|
@ -6,7 +6,7 @@ import com.r3corda.core.contracts.ContractState
|
|||||||
import com.r3corda.core.contracts.TransactionForContract
|
import com.r3corda.core.contracts.TransactionForContract
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
interface GroupVerify<S, T : Any> {
|
interface GroupVerify<in S, in T : Any> {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @return the set of commands that are consumed IF this clause is matched, and cannot be used to match a
|
* @return the set of commands that are consumed IF this clause is matched, and cannot be used to match a
|
||||||
|
@ -19,7 +19,7 @@ fun newSecureRandom(): SecureRandom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// "sealed" here means there can't be any subclasses other than the ones defined here.
|
// "sealed" here means there can't be any subclasses other than the ones defined here.
|
||||||
sealed class SecureHash private constructor(bits: ByteArray) : OpaqueBytes(bits) {
|
sealed class SecureHash(bits: ByteArray) : OpaqueBytes(bits) {
|
||||||
class SHA256(bits: ByteArray) : SecureHash(bits) {
|
class SHA256(bits: ByteArray) : SecureHash(bits) {
|
||||||
init {
|
init {
|
||||||
require(bits.size == 32)
|
require(bits.size == 32)
|
||||||
@ -77,8 +77,8 @@ open class DigitalSignature(bits: ByteArray, val covering: Int = 0) : OpaqueByte
|
|||||||
* A serialized piece of data and its signature. Enforces signature validity in order to deserialize the data
|
* A serialized piece of data and its signature. Enforces signature validity in order to deserialize the data
|
||||||
* contained within.
|
* contained within.
|
||||||
*
|
*
|
||||||
* @param raw the raw serialized data
|
* @param raw the raw serialized data.
|
||||||
* @param sig the (unverified) signature for the data
|
* @param sig the (unverified) signature for the data.
|
||||||
*/
|
*/
|
||||||
open class SignedData<T : Any>(val raw: SerializedBytes<T>, val sig: DigitalSignature.WithKey) {
|
open class SignedData<T : Any>(val raw: SerializedBytes<T>, val sig: DigitalSignature.WithKey) {
|
||||||
/**
|
/**
|
||||||
@ -169,4 +169,4 @@ operator fun KeyPair.component1() = this.private
|
|||||||
operator fun KeyPair.component2() = this.public
|
operator fun KeyPair.component2() = this.public
|
||||||
|
|
||||||
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */
|
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */
|
||||||
fun generateKeyPair() = EddsaKeyPairGenerator().generateKeyPair()
|
fun generateKeyPair(): KeyPair = EddsaKeyPairGenerator().generateKeyPair()
|
||||||
|
@ -11,7 +11,7 @@ interface InterpolatorFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interpolates values between the given data points using straight lines
|
* Interpolates values between the given data points using straight lines.
|
||||||
*/
|
*/
|
||||||
class LinearInterpolator(private val xs: DoubleArray, private val ys: DoubleArray) : Interpolator {
|
class LinearInterpolator(private val xs: DoubleArray, private val ys: DoubleArray) : Interpolator {
|
||||||
init {
|
init {
|
||||||
@ -105,8 +105,8 @@ class CubicSplineInterpolator(private val xs: DoubleArray, private val ys: Doubl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a polynomial function of arbitrary degree
|
* Represents a polynomial function of arbitrary degree.
|
||||||
* @param coefficients polynomial coefficients in the order of degree (constant first, followed by higher degree term coefficients)
|
* @param coefficients polynomial coefficients in the order of degree (constant first, followed by higher degree term coefficients).
|
||||||
*/
|
*/
|
||||||
class Polynomial(private val coefficients: DoubleArray) {
|
class Polynomial(private val coefficients: DoubleArray) {
|
||||||
private val reversedCoefficients = coefficients.reversed()
|
private val reversedCoefficients = coefficients.reversed()
|
||||||
@ -118,7 +118,7 @@ class Polynomial(private val coefficients: DoubleArray) {
|
|||||||
* A *spline* is function piecewise-defined by polynomial functions.
|
* A *spline* is function piecewise-defined by polynomial functions.
|
||||||
* Points at which polynomial pieces connect are known as *knots*.
|
* Points at which polynomial pieces connect are known as *knots*.
|
||||||
*
|
*
|
||||||
* @param segmentMap a mapping between a knot and the polynomial that covers the subsequent interval
|
* @param segmentMap a mapping between a knot and the polynomial that covers the subsequent interval.
|
||||||
*/
|
*/
|
||||||
class SplineFunction(private val segmentMap: TreeMap<Double, Polynomial>) {
|
class SplineFunction(private val segmentMap: TreeMap<Double, Polynomial>) {
|
||||||
fun getValue(x: Double): Double {
|
fun getValue(x: Double): Double {
|
||||||
|
@ -16,8 +16,8 @@ interface CordaPluginRegistry {
|
|||||||
* A Map with an entry for each consumed protocol used by the webAPIs.
|
* A Map with an entry for each consumed protocol used by the webAPIs.
|
||||||
* The key of each map entry should contain the ProtocolLogic<T> class name.
|
* The key of each map entry should contain the ProtocolLogic<T> class name.
|
||||||
* The associated map values are the union of all concrete class names passed to the protocol constructor.
|
* The associated map values are the union of all concrete class names passed to the protocol constructor.
|
||||||
* Standard java.lang.* and kotlin.* types do not need to be included explicitly
|
* Standard java.lang.* and kotlin.* types do not need to be included explicitly.
|
||||||
* This is used to extend the white listed protocols that can be initiated from the ServiceHub invokeProtocolAsync method
|
* This is used to extend the white listed protocols that can be initiated from the ServiceHub invokeProtocolAsync method.
|
||||||
*/
|
*/
|
||||||
val requiredProtocols: Map<String, Set<String>>
|
val requiredProtocols: Map<String, Set<String>>
|
||||||
}
|
}
|
@ -42,7 +42,7 @@ interface ServiceHub {
|
|||||||
* Given a list of [SignedTransaction]s, writes them to the local storage for validated transactions and then
|
* 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.
|
* sends them to the wallet for further processing.
|
||||||
*
|
*
|
||||||
* @param txs The transactions to record
|
* @param txs The transactions to record.
|
||||||
*/
|
*/
|
||||||
fun recordTransactions(txs: Iterable<SignedTransaction>)
|
fun recordTransactions(txs: Iterable<SignedTransaction>)
|
||||||
|
|
||||||
@ -50,14 +50,14 @@ interface ServiceHub {
|
|||||||
* Given some [SignedTransaction]s, writes them to the local storage for validated transactions and then
|
* Given some [SignedTransaction]s, writes them to the local storage for validated transactions and then
|
||||||
* sends them to the wallet for further processing.
|
* sends them to the wallet for further processing.
|
||||||
*
|
*
|
||||||
* @param txs The transactions to record
|
* @param txs The transactions to record.
|
||||||
*/
|
*/
|
||||||
fun recordTransactions(vararg txs: SignedTransaction) = recordTransactions(txs.toList())
|
fun recordTransactions(vararg txs: SignedTransaction) = recordTransactions(txs.toList())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]
|
* 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
|
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction.
|
||||||
*/
|
*/
|
||||||
fun loadState(stateRef: StateRef): TransactionState<*> {
|
fun loadState(stateRef: StateRef): TransactionState<*> {
|
||||||
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||||
@ -67,7 +67,7 @@ interface ServiceHub {
|
|||||||
/**
|
/**
|
||||||
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the protocol.
|
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the protocol.
|
||||||
*
|
*
|
||||||
* @throws IllegalProtocolLogicException or IllegalArgumentException if there are problems with the [logicType] or [args]
|
* @throws IllegalProtocolLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
|
||||||
*/
|
*/
|
||||||
fun <T : Any> invokeProtocolAsync(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ListenableFuture<T>
|
fun <T : Any> invokeProtocolAsync(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ListenableFuture<T>
|
||||||
}
|
}
|
@ -22,7 +22,7 @@ interface AttachmentStorage {
|
|||||||
* to the raw byte stream is required.
|
* to the raw byte stream is required.
|
||||||
*
|
*
|
||||||
* @throws FileAlreadyExistsException if the given byte stream has already been inserted.
|
* @throws FileAlreadyExistsException if the given byte stream has already been inserted.
|
||||||
* @throws IllegalArgumentException if the given byte stream is empty or a [JarInputStream]
|
* @throws IllegalArgumentException if the given byte stream is empty or a [JarInputStream].
|
||||||
* @throws IOException if something went wrong.
|
* @throws IOException if something went wrong.
|
||||||
*/
|
*/
|
||||||
fun importAttachment(jar: InputStream): SecureHash
|
fun importAttachment(jar: InputStream): SecureHash
|
||||||
|
@ -5,7 +5,6 @@ import com.r3corda.core.contracts.Contract
|
|||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.messaging.MessagingService
|
import com.r3corda.core.messaging.MessagingService
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
import com.r3corda.core.node.services.ServiceType
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
@ -66,9 +65,9 @@ interface NetworkMapCache {
|
|||||||
* Add a network map service; fetches a copy of the latest map from the service and subscribes to any further
|
* Add a network map service; fetches a copy of the latest map from the service and subscribes to any further
|
||||||
* updates.
|
* updates.
|
||||||
*
|
*
|
||||||
* @param net the network messaging service
|
* @param net the network messaging service.
|
||||||
* @param service the network map service to fetch current state from.
|
* @param service the network map service to fetch current state from.
|
||||||
* @param subscribe if the cache should subscribe to updates
|
* @param subscribe if the cache should subscribe to updates.
|
||||||
* @param ifChangedSinceVer an optional version number to limit updating the map based on. If the latest map
|
* @param ifChangedSinceVer an optional version number to limit updating the map based on. If the latest map
|
||||||
* version is less than or equal to the given version, no update is fetched.
|
* version is less than or equal to the given version, no update is fetched.
|
||||||
*/
|
*/
|
||||||
@ -76,19 +75,19 @@ interface NetworkMapCache {
|
|||||||
subscribe: Boolean, ifChangedSinceVer: Int? = null): ListenableFuture<Unit>
|
subscribe: Boolean, ifChangedSinceVer: Int? = null): ListenableFuture<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a node to the local cache (generally only used for adding ourselves)
|
* Adds a node to the local cache (generally only used for adding ourselves).
|
||||||
*/
|
*/
|
||||||
fun addNode(node: NodeInfo)
|
fun addNode(node: NodeInfo)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a node from the local cache
|
* Removes a node from the local cache.
|
||||||
*/
|
*/
|
||||||
fun removeNode(node: NodeInfo)
|
fun removeNode(node: NodeInfo)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deregister from updates from the given map service.
|
* Deregister from updates from the given map service.
|
||||||
*
|
*
|
||||||
* @param net the network messaging service
|
* @param net the network messaging service.
|
||||||
* @param service the network map service to fetch current state from.
|
* @param service the network map service to fetch current state from.
|
||||||
*/
|
*/
|
||||||
fun deregisterForUpdates(net: MessagingService, service: NodeInfo): ListenableFuture<Unit>
|
fun deregisterForUpdates(net: MessagingService, service: NodeInfo): ListenableFuture<Unit>
|
||||||
|
@ -29,7 +29,7 @@ val TOPIC_DEFAULT_POSTFIX = ".0"
|
|||||||
*
|
*
|
||||||
* [states] Holds the list of states that are *active* and *relevant*.
|
* [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).
|
* 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
|
* Relevant means they contain at least one of our pubkeys.
|
||||||
*/
|
*/
|
||||||
class Wallet(val states: List<StateAndRef<ContractState>>) {
|
class Wallet(val states: List<StateAndRef<ContractState>>) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@ -80,7 +80,7 @@ interface WalletService {
|
|||||||
val currentWallet: Wallet
|
val currentWallet: Wallet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a snapshot of the heads of LinearStates
|
* Returns a snapshot of the heads of LinearStates.
|
||||||
*/
|
*/
|
||||||
val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import java.time.Clock
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given timestamp falls within the allowed tolerance interval
|
* Checks if the given timestamp falls within the allowed tolerance interval.
|
||||||
*/
|
*/
|
||||||
class TimestampChecker(val clock: Clock = Clock.systemUTC(),
|
class TimestampChecker(val clock: Clock = Clock.systemUTC(),
|
||||||
val tolerance: Duration = 30.seconds) {
|
val tolerance: Duration = 30.seconds) {
|
||||||
|
@ -6,7 +6,7 @@ import com.r3corda.core.crypto.SecureHash
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A service that records input states of the given transaction and provides conflict information
|
* A service that records input states of the given transaction and provides conflict information
|
||||||
* if any of the inputs have already been used in another transaction
|
* if any of the inputs have already been used in another transaction.
|
||||||
*/
|
*/
|
||||||
interface UniquenessProvider {
|
interface UniquenessProvider {
|
||||||
/** Commits all input states of the given transaction */
|
/** Commits all input states of the given transaction */
|
||||||
@ -17,7 +17,7 @@ interface UniquenessProvider {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the transaction id, the position of the consumed state in the inputs, and
|
* Specifies the transaction id, the position of the consumed state in the inputs, and
|
||||||
* the caller identity requesting the commit
|
* the caller identity requesting the commit.
|
||||||
*
|
*
|
||||||
* TODO: need to do more design work to prevent privacy problems: knowing the id of a
|
* TODO: need to do more design work to prevent privacy problems: knowing the id of a
|
||||||
* transaction, by the rules of our system the party can obtain it and see its contents.
|
* transaction, by the rules of our system the party can obtain it and see its contents.
|
||||||
|
@ -24,7 +24,7 @@ import org.slf4j.Logger
|
|||||||
* If you'd like to use another ProtocolLogic class as a component of your own, construct it on the fly and then pass
|
* If you'd like to use another ProtocolLogic class as a component of your own, construct it on the fly and then pass
|
||||||
* it to the [subProtocol] method. It will return the result of that protocol when it completes.
|
* it to the [subProtocol] method. It will return the result of that protocol when it completes.
|
||||||
*/
|
*/
|
||||||
abstract class ProtocolLogic<T> {
|
abstract class ProtocolLogic<out T> {
|
||||||
|
|
||||||
/** Reference to the [Fiber] instance that is the top level controller for the entire flow. */
|
/** Reference to the [Fiber] instance that is the top level controller for the entire flow. */
|
||||||
lateinit var psm: ProtocolStateMachine<*>
|
lateinit var psm: ProtocolStateMachine<*>
|
||||||
|
@ -15,7 +15,7 @@ import kotlin.reflect.jvm.javaType
|
|||||||
import kotlin.reflect.primaryConstructor
|
import kotlin.reflect.primaryConstructor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for conversion to and from [ProtocolLogic] and [ProtocolLogicRef] instances
|
* A class for conversion to and from [ProtocolLogic] and [ProtocolLogicRef] instances.
|
||||||
*
|
*
|
||||||
* Validation of types is performed on the way in and way out in case this object is passed between JVMs which might have differing
|
* Validation of types is performed on the way in and way out in case this object is passed between JVMs which might have differing
|
||||||
* whitelists.
|
* whitelists.
|
||||||
@ -151,9 +151,9 @@ class ProtocolLogicRefFactory(private val protocolWhitelist: Map<String, Set<Str
|
|||||||
class IllegalProtocolLogicException(type: Class<*>, msg: String) : IllegalArgumentException("${ProtocolLogicRef::class.java.simpleName} cannot be constructed for ${ProtocolLogic::class.java.simpleName} of type ${type.name} $msg")
|
class IllegalProtocolLogicException(type: Class<*>, msg: String) : IllegalArgumentException("${ProtocolLogicRef::class.java.simpleName} cannot be constructed for ${ProtocolLogic::class.java.simpleName} of type ${type.name} $msg")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class representing a [ProtocolLogic] instance which would be possible to safely pass out of the contract sandbox
|
* A class representing a [ProtocolLogic] instance which would be possible to safely pass out of the contract sandbox.
|
||||||
*
|
*
|
||||||
* Only allows a String reference to the ProtocolLogic class, and only allows restricted argument types as per [ProtocolLogicRefFactory]
|
* Only allows a String reference to the ProtocolLogic class, and only allows restricted argument types as per [ProtocolLogicRefFactory].
|
||||||
*/
|
*/
|
||||||
// TODO: align this with the existing [ProtocolRef] in the bank-side API (probably replace some of the API classes)
|
// TODO: align this with the existing [ProtocolRef] in the bank-side API (probably replace some of the API classes)
|
||||||
data class ProtocolLogicRef internal constructor(val protocolLogicClassName: String, val appContext: AppContext, val args: Map<String, Any?>)
|
data class ProtocolLogicRef internal constructor(val protocolLogicClassName: String, val appContext: AppContext, val args: Map<String, Any?>)
|
||||||
|
@ -8,7 +8,7 @@ import org.slf4j.Logger
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The interface of [ProtocolStateMachineImpl] exposing methods and properties required by ProtocolLogic for compilation
|
* The interface of [ProtocolStateMachineImpl] exposing methods and properties required by ProtocolLogic for compilation.
|
||||||
*/
|
*/
|
||||||
interface ProtocolStateMachine<R> {
|
interface ProtocolStateMachine<R> {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
|
@ -113,7 +113,7 @@ object SerializedBytesSerializer : Serializer<SerializedBytes<Any>>() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Can be called on any object to convert it to a byte array (wrapped by [SerializedBytes]), regardless of whether
|
* Can be called on any object to convert it to a byte array (wrapped by [SerializedBytes]), regardless of whether
|
||||||
* the type is marked as serializable or was designed for it (so be careful!)
|
* the type is marked as serializable or was designed for it (so be careful!).
|
||||||
*/
|
*/
|
||||||
fun <T : Any> T.serialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): SerializedBytes<T> {
|
fun <T : Any> T.serialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): SerializedBytes<T> {
|
||||||
val stream = ByteArrayOutputStream()
|
val stream = ByteArrayOutputStream()
|
||||||
@ -326,7 +326,7 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
|
|||||||
// no-arg constructor available.
|
// no-arg constructor available.
|
||||||
instantiatorStrategy = DefaultInstantiatorStrategy(StdInstantiatorStrategy())
|
instantiatorStrategy = DefaultInstantiatorStrategy(StdInstantiatorStrategy())
|
||||||
|
|
||||||
register(Arrays.asList("").javaClass, ArraysAsListSerializer());
|
register(Arrays.asList("").javaClass, ArraysAsListSerializer())
|
||||||
|
|
||||||
// Because we like to stick a Kryo object in a ThreadLocal to speed things up a bit, we can end up trying to
|
// Because we like to stick a Kryo object in a ThreadLocal to speed things up a bit, we can end up trying to
|
||||||
// serialise the Kryo object itself when suspending a fiber. That's dumb, useless AND can cause crashes, so
|
// serialise the Kryo object itself when suspending a fiber. That's dumb, useless AND can cause crashes, so
|
||||||
|
@ -27,7 +27,7 @@ interface SerializeAsToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This represents a token in the serialized stream for an instance of a type that implements [SerializeAsToken]
|
* This represents a token in the serialized stream for an instance of a type that implements [SerializeAsToken].
|
||||||
*/
|
*/
|
||||||
interface SerializationToken {
|
interface SerializationToken {
|
||||||
fun fromToken(context: SerializeAsTokenContext): Any
|
fun fromToken(context: SerializeAsTokenContext): Any
|
||||||
|
120
core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt
Normal file
120
core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
@file:Suppress("UNUSED_PARAMETER", "UNCHECKED_CAST")
|
||||||
|
@file:JvmName("CoreTestUtils")
|
||||||
|
package com.r3corda.core.testing
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables
|
||||||
|
import com.google.common.net.HostAndPort
|
||||||
|
import com.r3corda.core.contracts.Attachment
|
||||||
|
import com.r3corda.core.contracts.StateRef
|
||||||
|
import com.r3corda.core.contracts.TransactionBuilder
|
||||||
|
import com.r3corda.core.crypto.DummyPublicKey
|
||||||
|
import com.r3corda.core.crypto.Party
|
||||||
|
import com.r3corda.core.crypto.SecureHash
|
||||||
|
import com.r3corda.core.crypto.generateKeyPair
|
||||||
|
import com.r3corda.core.node.services.IdentityService
|
||||||
|
import com.r3corda.core.node.services.StorageService
|
||||||
|
import com.r3corda.core.node.services.testing.MockIdentityService
|
||||||
|
import com.r3corda.core.node.services.testing.MockStorageService
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 vals are trickier. *DO NOT USE @JvmField at the top level!* 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. Use the DOLLARS(int) function instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// A dummy time at which we will be pretending test transactions are created.
|
||||||
|
val TEST_TX_TIME: Instant get() = Instant.parse("2015-04-17T12:00:00.00Z")
|
||||||
|
|
||||||
|
// A few dummy values for testing.
|
||||||
|
val MEGA_CORP_KEY: KeyPair by lazy { generateKeyPair() }
|
||||||
|
val MEGA_CORP_PUBKEY: PublicKey get() = MEGA_CORP_KEY.public
|
||||||
|
|
||||||
|
val MINI_CORP_KEY: KeyPair by lazy { generateKeyPair() }
|
||||||
|
val MINI_CORP_PUBKEY: PublicKey get() = MINI_CORP_KEY.public
|
||||||
|
|
||||||
|
val ORACLE_KEY: KeyPair by lazy { generateKeyPair() }
|
||||||
|
val ORACLE_PUBKEY: PublicKey get() = ORACLE_KEY.public
|
||||||
|
|
||||||
|
val DUMMY_PUBKEY_1: PublicKey get() = DummyPublicKey("x1")
|
||||||
|
val DUMMY_PUBKEY_2: PublicKey get() = DummyPublicKey("x2")
|
||||||
|
|
||||||
|
val DUMMY_KEY_1: KeyPair by lazy { generateKeyPair() }
|
||||||
|
val DUMMY_KEY_2: KeyPair by lazy { generateKeyPair() }
|
||||||
|
val DUMMY_KEY_3: KeyPair by lazy { generateKeyPair() }
|
||||||
|
|
||||||
|
val ALICE_KEY: KeyPair by lazy { generateKeyPair() }
|
||||||
|
val ALICE_PUBKEY: PublicKey get() = ALICE_KEY.public
|
||||||
|
val ALICE: Party get() = Party("Alice", ALICE_PUBKEY)
|
||||||
|
|
||||||
|
val BOB_KEY: KeyPair by lazy { generateKeyPair() }
|
||||||
|
val BOB_PUBKEY: PublicKey get() = BOB_KEY.public
|
||||||
|
val BOB: Party get() = Party("Bob", BOB_PUBKEY)
|
||||||
|
|
||||||
|
val MEGA_CORP: Party get() = Party("MegaCorp", MEGA_CORP_PUBKEY)
|
||||||
|
val MINI_CORP: Party get() = Party("MiniCorp", MINI_CORP_PUBKEY)
|
||||||
|
|
||||||
|
val DUMMY_NOTARY_KEY: KeyPair by lazy { generateKeyPair() }
|
||||||
|
val DUMMY_NOTARY: Party get() = Party("Notary Service", DUMMY_NOTARY_KEY.public)
|
||||||
|
|
||||||
|
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 get() = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
|
||||||
|
|
||||||
|
fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
|
||||||
|
|
||||||
|
/** If an exception is thrown by the body, rethrows the root cause exception. */
|
||||||
|
inline fun <R> rootCauseExceptions(body: () -> R): R {
|
||||||
|
try {
|
||||||
|
return body()
|
||||||
|
} catch(e: Exception) {
|
||||||
|
throw Throwables.getRootCause(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun freeLocalHostAndPort(): HostAndPort {
|
||||||
|
val freePort = ServerSocket(0).use { it.localPort }
|
||||||
|
return HostAndPort.fromParts("localhost", freePort)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and tests a ledger built by the passed in dsl.
|
||||||
|
* @param identityService: The [IdentityService] to be used while building the ledger.
|
||||||
|
* @param storageService: The [StorageService] to be used for storing e.g. [Attachment]s.
|
||||||
|
* @param dsl: The dsl building the ledger.
|
||||||
|
*/
|
||||||
|
@JvmOverloads fun ledger(
|
||||||
|
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
||||||
|
storageService: StorageService = MockStorageService(),
|
||||||
|
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
||||||
|
): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||||
|
val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService))
|
||||||
|
dsl(ledgerDsl)
|
||||||
|
return ledgerDsl
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ledger with a single transaction, built by the passed in dsl.
|
||||||
|
*
|
||||||
|
* @see LedgerDSLInterpreter._transaction
|
||||||
|
*/
|
||||||
|
@JvmOverloads fun transaction(
|
||||||
|
transactionLabel: String? = null,
|
||||||
|
transactionBuilder: TransactionBuilder = TransactionBuilder(),
|
||||||
|
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||||
|
) = ledger { this.transaction(transactionLabel, transactionBuilder, dsl) }
|
@ -4,7 +4,6 @@ import com.r3corda.core.contracts.Contract
|
|||||||
import com.r3corda.core.contracts.LinearState
|
import com.r3corda.core.contracts.LinearState
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class DummyLinearState(
|
class DummyLinearState(
|
||||||
override val thread: SecureHash = SecureHash.randomSHA256(),
|
override val thread: SecureHash = SecureHash.randomSHA256(),
|
||||||
|
@ -43,7 +43,7 @@ open class InMemoryWalletService(private val services: ServiceHub) : SingletonSe
|
|||||||
get() = _updatesPublisher
|
get() = _updatesPublisher
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a snapshot of the heads of LinearStates
|
* Returns a snapshot of the heads of LinearStates.
|
||||||
*/
|
*/
|
||||||
override val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
override val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
||||||
get() = currentWallet.let { wallet ->
|
get() = currentWallet.let { wallet ->
|
||||||
|
@ -4,36 +4,159 @@ import com.r3corda.core.contracts.*
|
|||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines output state lookup by label. It is split from the interpreter interfaces so that outputs may
|
||||||
|
* be looked up both in ledger{..} and transaction{..} blocks.
|
||||||
|
*/
|
||||||
interface OutputStateLookup {
|
interface OutputStateLookup {
|
||||||
|
/**
|
||||||
|
* Retrieves an output previously defined by [TransactionDSLInterpreter._output] with a label passed in.
|
||||||
|
* @param clazz The class object holding the type of the output state expected.
|
||||||
|
* @param label The label of the to-be-retrieved output state.
|
||||||
|
* @return The output [StateAndRef].
|
||||||
|
*/
|
||||||
fun <S : ContractState> retrieveOutputStateAndRef(clazz: Class<S>, label: String): StateAndRef<S>
|
fun <S : ContractState> retrieveOutputStateAndRef(clazz: Class<S>, label: String): StateAndRef<S>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LedgerDSLInterpreter<R, out T : TransactionDSLInterpreter<R>> : OutputStateLookup {
|
/**
|
||||||
fun transaction(transactionLabel: String?, dsl: TransactionDSL<R, T>.() -> R): WireTransaction
|
* This interface asserts that the DSL at hand is capable of verifying its underlying construct(ledger/transaction).
|
||||||
fun unverifiedTransaction(transactionLabel: String?, dsl: TransactionDSL<R, T>.() -> Unit): WireTransaction
|
*/
|
||||||
fun tweak(dsl: LedgerDSL<R, T, LedgerDSLInterpreter<R, T>>.() -> Unit)
|
interface Verifies {
|
||||||
fun attachment(attachment: InputStream): SecureHash
|
/**
|
||||||
fun verifies()
|
* Verifies the ledger/transaction, throws if the verification fails.
|
||||||
|
*/
|
||||||
|
fun verifies(): EnforceVerifyOrFail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that verifies() throws.
|
||||||
|
* @param expectedMessage An optional string to be searched for in the raised exception.
|
||||||
|
*/
|
||||||
|
fun failsWith(expectedMessage: String?): EnforceVerifyOrFail {
|
||||||
|
val exceptionThrown = try {
|
||||||
|
verifies()
|
||||||
|
false
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
if (expectedMessage != null) {
|
||||||
|
val exceptionMessage = exception.message
|
||||||
|
if (exceptionMessage == null) {
|
||||||
|
throw AssertionError(
|
||||||
|
"Expected exception containing '$expectedMessage' but raised exception had no message"
|
||||||
|
)
|
||||||
|
} else if (!exceptionMessage.toLowerCase().contains(expectedMessage.toLowerCase())) {
|
||||||
|
throw AssertionError(
|
||||||
|
"Expected exception containing '$expectedMessage' but raised exception was '$exception'"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exceptionThrown) {
|
||||||
|
throw AssertionError("Expected exception but didn't get one")
|
||||||
|
}
|
||||||
|
|
||||||
|
return EnforceVerifyOrFail.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that [verifies] throws, with no condition on the exception message.
|
||||||
|
*/
|
||||||
|
fun fails() = failsWith(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see failsWith
|
||||||
|
*/
|
||||||
|
infix fun `fails with`(msg: String) = failsWith(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the class the top-level primitives deal with. It delegates all other primitives to the contained interpreter.
|
* This interface defines the bare bone functionality that a Ledger DSL interpreter should implement.
|
||||||
* This way we have a decoupling of the DSL "AST" and the interpretation(s) of it. Note how the delegation forces
|
|
||||||
* covariance of the TransactionInterpreter parameter.
|
|
||||||
*
|
*
|
||||||
* TODO (Kotlin 1.1): Use type synonyms to make the type params less unwieldy
|
* TODO (Kotlin 1.1): Use type synonyms to make the type params less unwieldy
|
||||||
*/
|
*/
|
||||||
class LedgerDSL<R, out T : TransactionDSLInterpreter<R>, out L : LedgerDSLInterpreter<R, T>> (val interpreter: L) :
|
interface LedgerDSLInterpreter<out T : TransactionDSLInterpreter> : Verifies, OutputStateLookup {
|
||||||
LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>> by interpreter {
|
/**
|
||||||
|
* Creates and adds a transaction to the ledger.
|
||||||
|
* @param transactionLabel Optional label of the transaction, to be used in diagnostic messages.
|
||||||
|
* @param transactionBuilder The base transactionBuilder that will be used to build the transaction.
|
||||||
|
* @param dsl The dsl that should be interpreted for building the transaction.
|
||||||
|
* @return The final [WireTransaction] of the built transaction.
|
||||||
|
*/
|
||||||
|
fun _transaction(transactionLabel: String?, transactionBuilder: TransactionBuilder,
|
||||||
|
dsl: TransactionDSL<T>.() -> EnforceVerifyOrFail): WireTransaction
|
||||||
|
|
||||||
fun transaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R) =
|
/**
|
||||||
transaction(null, dsl)
|
* Creates and adds a transaction to the ledger that will not be verified by [verifies].
|
||||||
fun unverifiedTransaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> Unit) =
|
* @param transactionLabel Optional label of the transaction, to be used in diagnostic messages.
|
||||||
unverifiedTransaction(null, dsl)
|
* @param transactionBuilder The base transactionBuilder that will be used to build the transaction.
|
||||||
|
* @param dsl The dsl that should be interpreted for building the transaction.
|
||||||
|
* @return The final [WireTransaction] of the built transaction.
|
||||||
|
*/
|
||||||
|
fun _unverifiedTransaction(transactionLabel: String?, transactionBuilder: TransactionBuilder,
|
||||||
|
dsl: TransactionDSL<T>.() -> Unit): WireTransaction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a local scoped copy of the ledger.
|
||||||
|
* @param dsl The ledger DSL to be interpreted using the copy.
|
||||||
|
*/
|
||||||
|
fun tweak(dsl: LedgerDSL<T, LedgerDSLInterpreter<T>>.() -> Unit)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attachment to the ledger.
|
||||||
|
* @param attachment The [InputStream] defining the contents of the attachment.
|
||||||
|
* @return The [SecureHash] that identifies the attachment, to be used in transactions.
|
||||||
|
*/
|
||||||
|
fun attachment(attachment: InputStream): SecureHash
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the class that defines the syntactic sugar of the ledger Test DSL and delegates to the contained interpreter,
|
||||||
|
* and what is actually used in `ledger { (...) }`. Add convenience functions here, or if you want to extend the DSL
|
||||||
|
* functionality then first add your primitive to [LedgerDSLInterpreter] and then add the convenience defaults/extension
|
||||||
|
* methods here.
|
||||||
|
*/
|
||||||
|
class LedgerDSL<out T : TransactionDSLInterpreter, out L : LedgerDSLInterpreter<T>> (val interpreter: L) :
|
||||||
|
LedgerDSLInterpreter<TransactionDSLInterpreter> by interpreter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see LedgerDSLInterpreter._transaction
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun transaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(),
|
||||||
|
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) =
|
||||||
|
_transaction(label, transactionBuilder, dsl)
|
||||||
|
/**
|
||||||
|
* @see LedgerDSLInterpreter._unverifiedTransaction
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun unverifiedTransaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(),
|
||||||
|
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> Unit) =
|
||||||
|
_unverifiedTransaction(label, transactionBuilder, dsl)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see OutputStateLookup.retrieveOutputStateAndRef
|
||||||
|
*/
|
||||||
inline fun <reified S : ContractState> String.outputStateAndRef(): StateAndRef<S> =
|
inline fun <reified S : ContractState> String.outputStateAndRef(): StateAndRef<S> =
|
||||||
retrieveOutputStateAndRef(S::class.java, this)
|
retrieveOutputStateAndRef(S::class.java, this)
|
||||||
inline fun <reified S : ContractState> String.output(): TransactionState<S> =
|
|
||||||
outputStateAndRef<S>().state
|
/**
|
||||||
|
* Retrieves the output [TransactionState] based on the label.
|
||||||
|
* @see OutputStateLookup.retrieveOutputStateAndRef
|
||||||
|
*/
|
||||||
|
inline fun <reified S : ContractState> String.output(): S =
|
||||||
|
outputStateAndRef<S>().state.data
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the output [StateRef] based on the label.
|
||||||
|
* @see OutputStateLookup.retrieveOutputStateAndRef
|
||||||
|
*/
|
||||||
fun String.outputRef(): StateRef = outputStateAndRef<ContractState>().ref
|
fun String.outputRef(): StateRef = outputStateAndRef<ContractState>().ref
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see OutputStateLookup.retrieveOutputStateAndRef
|
||||||
|
*/
|
||||||
|
fun <S : ContractState> retrieveOutput(clazz: Class<S>, label: String) =
|
||||||
|
retrieveOutputStateAndRef(clazz, label).state.data
|
||||||
}
|
}
|
||||||
|
@ -7,34 +7,46 @@ import com.r3corda.core.crypto.SecureHash
|
|||||||
import com.r3corda.core.crypto.signWithECDSA
|
import com.r3corda.core.crypto.signWithECDSA
|
||||||
import com.r3corda.core.node.services.IdentityService
|
import com.r3corda.core.node.services.IdentityService
|
||||||
import com.r3corda.core.node.services.StorageService
|
import com.r3corda.core.node.services.StorageService
|
||||||
import com.r3corda.core.node.services.testing.MockStorageService
|
|
||||||
import com.r3corda.core.serialization.serialize
|
import com.r3corda.core.serialization.serialize
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun transaction(
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
transactionLabel: String? = null,
|
//
|
||||||
dsl: TransactionDSL<
|
// Here is a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
|
||||||
EnforceVerifyOrFail,
|
//
|
||||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
// Define a transaction like this:
|
||||||
>.() -> EnforceVerifyOrFail
|
//
|
||||||
) = JavaTestHelpers.transaction(transactionLabel, dsl)
|
// ledger {
|
||||||
|
// transaction {
|
||||||
|
// input { someExpression }
|
||||||
|
// output { someExpression }
|
||||||
|
// command { someExpression }
|
||||||
|
//
|
||||||
|
// tweak {
|
||||||
|
// ... same thing but works with a copy of the parent, can add inputs/outputs/commands just within this scope.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// contract.verifies() -> verify() should pass
|
||||||
|
// contract `fails with` "some substring of the error message"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
|
||||||
fun ledger(
|
/**
|
||||||
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
* Here follows implementations of the [LedgerDSLInterpreter] and [TransactionDSLInterpreter] interfaces to be used in
|
||||||
storageService: StorageService = MockStorageService(),
|
* tests. Top level primitives [ledger] and [transaction] that bind the interpreter types are also defined here.
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
*/
|
||||||
) = JavaTestHelpers.ledger(identityService, storageService, dsl)
|
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
message = "ledger doesn't nest, use tweak",
|
message = "ledger doesn't nest, use tweak",
|
||||||
replaceWith = ReplaceWith("tweak"),
|
replaceWith = ReplaceWith("tweak"),
|
||||||
level = DeprecationLevel.ERROR)
|
level = DeprecationLevel.ERROR)
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
|
fun TransactionDSLInterpreter.ledger(
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
@ -42,11 +54,8 @@ fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
|
|||||||
replaceWith = ReplaceWith("tweak"),
|
replaceWith = ReplaceWith("tweak"),
|
||||||
level = DeprecationLevel.ERROR)
|
level = DeprecationLevel.ERROR)
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
|
fun TransactionDSLInterpreter.transaction(
|
||||||
dsl: TransactionDSL<
|
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) {
|
||||||
EnforceVerifyOrFail,
|
|
||||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
|
||||||
>.() -> EnforceVerifyOrFail) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
@ -54,12 +63,12 @@ fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
|
|||||||
replaceWith = ReplaceWith("tweak"),
|
replaceWith = ReplaceWith("tweak"),
|
||||||
level = DeprecationLevel.ERROR)
|
level = DeprecationLevel.ERROR)
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun LedgerDSLInterpreter<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.ledger(
|
fun LedgerDSLInterpreter<TransactionDSLInterpreter>.ledger(
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If you jumped here from a compiler error make sure the last line of your test tests for a transaction verify or fail
|
* If you jumped here from a compiler error make sure the last line of your test tests for a transaction verify or fail.
|
||||||
* This is a dummy type that can only be instantiated by functions in this module. This way we can ensure that all tests
|
* 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
|
* 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.
|
* the triggered diagnostic.
|
||||||
@ -68,57 +77,56 @@ sealed class EnforceVerifyOrFail {
|
|||||||
internal object Token: EnforceVerifyOrFail()
|
internal object Token: EnforceVerifyOrFail()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DuplicateOutputLabel(label: String) : Exception("Output label '$label' already used")
|
||||||
|
class AttachmentResolutionException(attachmentId: SecureHash) : Exception("Attachment with id $attachmentId not found")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interpreter builds a transaction, and [TransactionDSL.verifies] that the resolved transaction is correct. Note
|
* This interpreter builds a transaction, and [TransactionDSL.verifies] that the resolved transaction is correct. Note
|
||||||
* that transactions corresponding to input states are not verified. Use [LedgerDSL.verifies] for that.
|
* that transactions corresponding to input states are not verified. Use [LedgerDSL.verifies] for that.
|
||||||
*/
|
*/
|
||||||
data class TestTransactionDSLInterpreter(
|
data class TestTransactionDSLInterpreter private constructor(
|
||||||
override val ledgerInterpreter: TestLedgerDSLInterpreter,
|
override val ledgerInterpreter: TestLedgerDSLInterpreter,
|
||||||
private val inputStateRefs: ArrayList<StateRef> = arrayListOf(),
|
val transactionBuilder: TransactionBuilder,
|
||||||
internal val outputStates: ArrayList<LabeledOutput> = arrayListOf(),
|
internal val labelToIndexMap: HashMap<String, Int>
|
||||||
private val attachments: ArrayList<SecureHash> = arrayListOf(),
|
) : TransactionDSLInterpreter, OutputStateLookup by ledgerInterpreter {
|
||||||
private val commands: ArrayList<Command> = arrayListOf(),
|
|
||||||
private val signers: LinkedHashSet<PublicKey> = LinkedHashSet(),
|
constructor(
|
||||||
private val transactionType: TransactionType = TransactionType.General()
|
ledgerInterpreter: TestLedgerDSLInterpreter,
|
||||||
) : TransactionDSLInterpreter<EnforceVerifyOrFail>, OutputStateLookup by ledgerInterpreter {
|
transactionBuilder: TransactionBuilder
|
||||||
|
) : this(ledgerInterpreter, transactionBuilder, HashMap())
|
||||||
|
|
||||||
private fun copy(): TestTransactionDSLInterpreter =
|
private fun copy(): TestTransactionDSLInterpreter =
|
||||||
TestTransactionDSLInterpreter(
|
TestTransactionDSLInterpreter(
|
||||||
ledgerInterpreter = ledgerInterpreter,
|
ledgerInterpreter = ledgerInterpreter,
|
||||||
inputStateRefs = ArrayList(inputStateRefs),
|
transactionBuilder = transactionBuilder.copy(),
|
||||||
outputStates = ArrayList(outputStates),
|
labelToIndexMap = HashMap(labelToIndexMap)
|
||||||
attachments = ArrayList(attachments),
|
|
||||||
commands = ArrayList(commands),
|
|
||||||
signers = LinkedHashSet(signers),
|
|
||||||
transactionType = transactionType
|
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun toWireTransaction(): WireTransaction =
|
internal fun toWireTransaction() = transactionBuilder.toWireTransaction()
|
||||||
WireTransaction(
|
|
||||||
inputs = inputStateRefs,
|
|
||||||
outputs = outputStates.map { it.state },
|
|
||||||
attachments = attachments,
|
|
||||||
commands = commands,
|
|
||||||
signers = signers.toList(),
|
|
||||||
type = transactionType
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun input(stateRef: StateRef) {
|
override fun input(stateRef: StateRef) {
|
||||||
val notary = ledgerInterpreter.resolveStateRef<ContractState>(stateRef).notary
|
val notary = ledgerInterpreter.resolveStateRef<ContractState>(stateRef).notary
|
||||||
signers.add(notary.owningKey)
|
transactionBuilder.addInputState(stateRef, notary)
|
||||||
inputStateRefs.add(stateRef)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun _output(label: String?, notary: Party, contractState: ContractState) {
|
override fun _output(label: String?, notary: Party, contractState: ContractState) {
|
||||||
outputStates.add(LabeledOutput(label, TransactionState(contractState, notary)))
|
val outputIndex = transactionBuilder.addOutputState(contractState, notary)
|
||||||
|
if (label != null) {
|
||||||
|
if (label in labelToIndexMap) {
|
||||||
|
throw DuplicateOutputLabel(label)
|
||||||
|
} else {
|
||||||
|
labelToIndexMap[label] = outputIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachment(attachmentId: SecureHash) {
|
override fun attachment(attachmentId: SecureHash) {
|
||||||
attachments.add(attachmentId)
|
transactionBuilder.addAttachment(attachmentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun _command(signers: List<PublicKey>, commandData: CommandData) {
|
override fun _command(signers: List<PublicKey>, commandData: CommandData) {
|
||||||
this.signers.addAll(signers)
|
val command = Command(commandData, signers)
|
||||||
commands.add(Command(commandData, signers))
|
transactionBuilder.addCommand(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verifies(): EnforceVerifyOrFail {
|
override fun verifies(): EnforceVerifyOrFail {
|
||||||
@ -127,52 +135,18 @@ data class TestTransactionDSLInterpreter(
|
|||||||
return EnforceVerifyOrFail.Token
|
return EnforceVerifyOrFail.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun failsWith(expectedMessage: String?): EnforceVerifyOrFail {
|
|
||||||
val exceptionThrown = try {
|
|
||||||
this.verifies()
|
|
||||||
false
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
if (expectedMessage != null) {
|
|
||||||
val exceptionMessage = exception.message
|
|
||||||
if (exceptionMessage == null) {
|
|
||||||
throw AssertionError(
|
|
||||||
"Expected exception containing '$expectedMessage' but raised exception had no message"
|
|
||||||
)
|
|
||||||
} else if (!exceptionMessage.toLowerCase().contains(expectedMessage.toLowerCase())) {
|
|
||||||
throw AssertionError(
|
|
||||||
"Expected exception containing '$expectedMessage' but raised exception was '$exception'"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!exceptionThrown) {
|
|
||||||
throw AssertionError("Expected exception but didn't get one")
|
|
||||||
}
|
|
||||||
|
|
||||||
return EnforceVerifyOrFail.Token
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tweak(
|
override fun tweak(
|
||||||
dsl: TransactionDSL<
|
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||||
EnforceVerifyOrFail,
|
|
||||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
|
||||||
>.() -> EnforceVerifyOrFail
|
|
||||||
) = dsl(TransactionDSL(copy()))
|
) = dsl(TransactionDSL(copy()))
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentResolutionException(attachmentId: SecureHash) :
|
|
||||||
Exception("Attachment with id $attachmentId not found")
|
|
||||||
|
|
||||||
data class TestLedgerDSLInterpreter private constructor (
|
data class TestLedgerDSLInterpreter private constructor (
|
||||||
private val identityService: IdentityService,
|
private val identityService: IdentityService,
|
||||||
private val storageService: StorageService,
|
private val storageService: StorageService,
|
||||||
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
|
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
|
||||||
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(),
|
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(),
|
||||||
private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
||||||
) : LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter> {
|
) : LedgerDSLInterpreter<TestTransactionDSLInterpreter> {
|
||||||
|
|
||||||
val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
|
val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
|
||||||
|
|
||||||
// We specify [labelToOutputStateAndRefs] just so that Kotlin picks the primary constructor instead of cycling
|
// We specify [labelToOutputStateAndRefs] just so that Kotlin picks the primary constructor instead of cycling
|
||||||
@ -181,19 +155,25 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun getCallerLocation(offset: Int): String {
|
private fun getCallerLocation(): String? {
|
||||||
val stackTraceElement = Thread.currentThread().stackTrace[3 + offset]
|
val stackTrace = Thread.currentThread().stackTrace
|
||||||
|
for (i in 1 .. stackTrace.size) {
|
||||||
|
val stackTraceElement = stackTrace[i]
|
||||||
|
if (!stackTraceElement.fileName.contains("DSL")) {
|
||||||
return stackTraceElement.toString()
|
return stackTraceElement.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal data class WireTransactionWithLocation(
|
internal data class WireTransactionWithLocation(
|
||||||
val label: String?,
|
val label: String?,
|
||||||
val transaction: WireTransaction,
|
val transaction: WireTransaction,
|
||||||
val location: String
|
val location: String?
|
||||||
)
|
)
|
||||||
class VerifiesFailed(transactionLocation: String, cause: Throwable) :
|
class VerifiesFailed(transactionName: String, cause: Throwable) :
|
||||||
Exception("Transaction defined at ($transactionLocation) didn't verify: $cause", cause)
|
Exception("Transaction ($transactionName) didn't verify: $cause", cause)
|
||||||
class TypeMismatch(requested: Class<*>, actual: Class<*>) :
|
class TypeMismatch(requested: Class<*>, actual: Class<*>) :
|
||||||
Exception("Actual type $actual is not a subtype of requested type $requested")
|
Exception("Actual type $actual is not a subtype of requested type $requested")
|
||||||
|
|
||||||
@ -242,10 +222,11 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
internal fun resolveAttachment(attachmentId: SecureHash): Attachment =
|
internal fun resolveAttachment(attachmentId: SecureHash): Attachment =
|
||||||
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
|
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
|
||||||
|
|
||||||
private fun <Return> interpretTransactionDsl(
|
private fun <R> interpretTransactionDsl(
|
||||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Return
|
transactionBuilder: TransactionBuilder,
|
||||||
|
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> R
|
||||||
): TestTransactionDSLInterpreter {
|
): TestTransactionDSLInterpreter {
|
||||||
val transactionInterpreter = TestTransactionDSLInterpreter(this)
|
val transactionInterpreter = TestTransactionDSLInterpreter(this, transactionBuilder)
|
||||||
dsl(TransactionDSL(transactionInterpreter))
|
dsl(TransactionDSL(transactionInterpreter))
|
||||||
return transactionInterpreter
|
return transactionInterpreter
|
||||||
}
|
}
|
||||||
@ -274,18 +255,20 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
|
|
||||||
private fun <R> recordTransactionWithTransactionMap(
|
private fun <R> recordTransactionWithTransactionMap(
|
||||||
transactionLabel: String?,
|
transactionLabel: String?,
|
||||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> R,
|
transactionBuilder: TransactionBuilder,
|
||||||
|
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> R,
|
||||||
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
||||||
): WireTransaction {
|
): WireTransaction {
|
||||||
val transactionLocation = getCallerLocation(3)
|
val transactionLocation = getCallerLocation()
|
||||||
val transactionInterpreter = interpretTransactionDsl(dsl)
|
val transactionInterpreter = interpretTransactionDsl(transactionBuilder, dsl)
|
||||||
// Create the WireTransaction
|
// Create the WireTransaction
|
||||||
val wireTransaction = transactionInterpreter.toWireTransaction()
|
val wireTransaction = transactionInterpreter.toWireTransaction()
|
||||||
// Record the output states
|
// Record the output states
|
||||||
transactionInterpreter.outputStates.forEachIndexed { index, labeledOutput ->
|
transactionInterpreter.labelToIndexMap.forEach { label, index ->
|
||||||
if (labeledOutput.label != null) {
|
if (label in labelToOutputStateAndRefs) {
|
||||||
labelToOutputStateAndRefs[labeledOutput.label] = wireTransaction.outRef(index)
|
throw DuplicateOutputLabel(label)
|
||||||
}
|
}
|
||||||
|
labelToOutputStateAndRefs[label] = wireTransaction.outRef(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionMap[wireTransaction.serialized.hash] =
|
transactionMap[wireTransaction.serialized.hash] =
|
||||||
@ -294,32 +277,38 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
return wireTransaction
|
return wireTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transaction(
|
override fun _transaction(
|
||||||
transactionLabel: String?,
|
transactionLabel: String?,
|
||||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
transactionBuilder: TransactionBuilder,
|
||||||
) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations)
|
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||||
|
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, transactionWithLocations)
|
||||||
|
|
||||||
override fun unverifiedTransaction(
|
override fun _unverifiedTransaction(
|
||||||
transactionLabel: String?,
|
transactionLabel: String?,
|
||||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Unit
|
transactionBuilder: TransactionBuilder,
|
||||||
) = recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations)
|
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> Unit
|
||||||
|
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, nonVerifiedTransactionWithLocations)
|
||||||
|
|
||||||
override fun tweak(
|
override fun tweak(
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter,
|
dsl: LedgerDSL<TestTransactionDSLInterpreter,
|
||||||
LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter>>.() -> Unit) =
|
LedgerDSLInterpreter<TestTransactionDSLInterpreter>>.() -> Unit) =
|
||||||
dsl(LedgerDSL(copy()))
|
dsl(LedgerDSL(copy()))
|
||||||
|
|
||||||
override fun attachment(attachment: InputStream): SecureHash {
|
override fun attachment(attachment: InputStream): SecureHash {
|
||||||
return storageService.attachments.importAttachment(attachment)
|
return storageService.attachments.importAttachment(attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verifies() {
|
override fun verifies(): EnforceVerifyOrFail {
|
||||||
val transactionGroup = toTransactionGroup()
|
val transactionGroup = toTransactionGroup()
|
||||||
try {
|
try {
|
||||||
transactionGroup.verify()
|
transactionGroup.verify()
|
||||||
} catch (exception: TransactionVerificationException) {
|
} catch (exception: TransactionVerificationException) {
|
||||||
throw VerifiesFailed(transactionWithLocations[exception.tx.origHash]?.location ?: "<unknown>", exception)
|
val transactionWithLocation = transactionWithLocations[exception.tx.origHash]
|
||||||
|
val transactionName = transactionWithLocation?.label ?: transactionWithLocation?.location ?: "<unknown>"
|
||||||
|
throw VerifiesFailed(transactionName, exception)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return EnforceVerifyOrFail.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <S : ContractState> retrieveOutputStateAndRef(clazz: Class<S>, label: String): StateAndRef<S> {
|
override fun <S : ContractState> retrieveOutputStateAndRef(clazz: Class<S>, label: String): StateAndRef<S> {
|
||||||
@ -335,13 +324,19 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs all transactions passed in.
|
||||||
|
* @param transactionsToSign Transactions to be signed.
|
||||||
|
* @param extraKeys extra keys to sign transactions with.
|
||||||
|
* @return List of [SignedTransaction]s.
|
||||||
|
*/
|
||||||
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyPair>) = transactionsToSign.map { wtx ->
|
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyPair>) = transactionsToSign.map { wtx ->
|
||||||
val allPubKeys = wtx.signers.toMutableSet()
|
val allPubKeys = wtx.signers.toMutableSet()
|
||||||
val bits = wtx.serialize()
|
val bits = wtx.serialize()
|
||||||
require(bits == wtx.serialized)
|
require(bits == wtx.serialized)
|
||||||
val signatures = ArrayList<DigitalSignature.WithKey>()
|
val signatures = ArrayList<DigitalSignature.WithKey>()
|
||||||
for (key in ALL_TEST_KEYS + extraKeys) {
|
for (key in ALL_TEST_KEYS + extraKeys) {
|
||||||
if (allPubKeys.contains(key.public)) {
|
if (key.public in allPubKeys) {
|
||||||
signatures += key.signWithECDSA(bits)
|
signatures += key.signWithECDSA(bits)
|
||||||
allPubKeys -= key.public
|
allPubKeys -= key.public
|
||||||
}
|
}
|
||||||
@ -349,6 +344,10 @@ fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyP
|
|||||||
SignedTransaction(bits, signatures)
|
SignedTransaction(bits, signatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll(
|
/**
|
||||||
transactionsToSign: List<WireTransaction> = this.interpreter.wireTransactions, vararg extraKeys: KeyPair) =
|
* Signs all transactions in the ledger.
|
||||||
signAll(transactionsToSign, extraKeys)
|
* @param extraKeys extra keys to sign transactions with.
|
||||||
|
* @return List of [SignedTransaction]s.
|
||||||
|
*/
|
||||||
|
fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll(
|
||||||
|
vararg extraKeys: KeyPair) = signAll(this.interpreter.wireTransactions, extraKeys)
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
@file:Suppress("UNUSED_PARAMETER", "UNCHECKED_CAST")
|
|
||||||
|
|
||||||
package com.r3corda.core.testing
|
|
||||||
|
|
||||||
import com.google.common.base.Throwables
|
|
||||||
import com.google.common.net.HostAndPort
|
|
||||||
import com.r3corda.core.contracts.*
|
|
||||||
import com.r3corda.core.crypto.*
|
|
||||||
import com.r3corda.core.node.services.IdentityService
|
|
||||||
import com.r3corda.core.node.services.StorageService
|
|
||||||
import com.r3corda.core.node.services.testing.MockIdentityService
|
|
||||||
import com.r3corda.core.node.services.testing.MockStorageService
|
|
||||||
import java.net.ServerSocket
|
|
||||||
import java.security.KeyPair
|
|
||||||
import java.security.PublicKey
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
/** If an exception is thrown by the body, rethrows the root cause exception. */
|
|
||||||
inline fun <R> rootCauseExceptions(body: () -> R): R {
|
|
||||||
try {
|
|
||||||
return body()
|
|
||||||
} catch(e: Exception) {
|
|
||||||
throw Throwables.getRootCause(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun freeLocalHostAndPort(): HostAndPort {
|
|
||||||
val freePort = ServerSocket(0).use { it.localPort }
|
|
||||||
return HostAndPort.fromParts("localhost", freePort)
|
|
||||||
}
|
|
||||||
|
|
||||||
object TestUtils {
|
|
||||||
val keypair = generateKeyPair()
|
|
||||||
val keypair2 = generateKeyPair()
|
|
||||||
val keypair3 = generateKeyPair()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
@JvmStatic val MEGA_CORP_KEY: KeyPair get() = TestUtils.keypair
|
|
||||||
@JvmStatic val MEGA_CORP_PUBKEY: PublicKey get() = MEGA_CORP_KEY.public
|
|
||||||
|
|
||||||
@JvmStatic val MINI_CORP_KEY: KeyPair get() = TestUtils.keypair2
|
|
||||||
@JvmStatic val MINI_CORP_PUBKEY: PublicKey get() = MINI_CORP_KEY.public
|
|
||||||
|
|
||||||
@JvmStatic val ORACLE_KEY: KeyPair get() = TestUtils.keypair3
|
|
||||||
@JvmStatic val ORACLE_PUBKEY: PublicKey get() = ORACLE_KEY.public
|
|
||||||
|
|
||||||
@JvmStatic val DUMMY_PUBKEY_1: PublicKey get() = DummyPublicKey("x1")
|
|
||||||
@JvmStatic val DUMMY_PUBKEY_2: PublicKey get() = DummyPublicKey("x2")
|
|
||||||
|
|
||||||
@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)
|
|
||||||
|
|
||||||
@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)
|
|
||||||
|
|
||||||
@JvmStatic val MEGA_CORP: Party get() = Party("MegaCorp", MEGA_CORP_PUBKEY)
|
|
||||||
@JvmStatic val MINI_CORP: Party get() = Party("MiniCorp", MINI_CORP_PUBKEY)
|
|
||||||
|
|
||||||
@JvmStatic val DUMMY_NOTARY_KEY: KeyPair by lazy { generateKeyPair() }
|
|
||||||
@JvmStatic val DUMMY_NOTARY: Party get() = Party("Notary Service", DUMMY_NOTARY_KEY.public)
|
|
||||||
|
|
||||||
@JvmStatic val ALL_TEST_KEYS: List<KeyPair> get() = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY)
|
|
||||||
|
|
||||||
@JvmStatic val MOCK_IDENTITY_SERVICE: MockIdentityService get() = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
|
|
||||||
|
|
||||||
@JvmStatic fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
|
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun ledger(
|
|
||||||
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
|
||||||
storageService: StorageService = MockStorageService(),
|
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
|
||||||
): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
|
||||||
val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService))
|
|
||||||
dsl(ledgerDsl)
|
|
||||||
return ledgerDsl
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic @JvmOverloads fun transaction(
|
|
||||||
transactionLabel: String? = null,
|
|
||||||
dsl: TransactionDSL<
|
|
||||||
EnforceVerifyOrFail,
|
|
||||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
|
||||||
>.() -> EnforceVerifyOrFail
|
|
||||||
) = ledger { transaction(transactionLabel, dsl) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val TEST_TX_TIME = JavaTestHelpers.TEST_TX_TIME
|
|
||||||
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()
|
|
||||||
|
|
||||||
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 TransactionState<*>.label(label: String) = LabeledOutput(label, this)
|
|
||||||
|
|
@ -5,84 +5,115 @@ import com.r3corda.core.crypto.Party
|
|||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.seconds
|
import com.r3corda.core.seconds
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
|
|
||||||
//
|
|
||||||
// Define a transaction like this:
|
|
||||||
//
|
|
||||||
// ledger {
|
|
||||||
// transaction {
|
|
||||||
// input { someExpression }
|
|
||||||
// output { someExpression }
|
|
||||||
// command { someExpression }
|
|
||||||
//
|
|
||||||
// tweak {
|
|
||||||
// ... same thing but works with a copy of the parent, can add inputs/outputs/commands just within this scope.
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// contract.verifies() -> verify() should pass
|
|
||||||
// contract `fails with` "some substring of the error message"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [TransactionDSLInterpreter] defines the interface DSL interpreters should satisfy. No
|
* This interface defines the bare bone functionality that a Transaction DSL interpreter should implement.
|
||||||
* overloading/default valuing should be done here, only the basic functions that are required to implement everything.
|
* @param <R> The return type of [verifies]/[failsWith] and the like. It is generic so that we have control over whether
|
||||||
* Same goes for functions requiring reflection e.g. [OutputStateLookup.retrieveOutputStateAndRef]
|
* we want to enforce users to call these methods (@see [EnforceVerifyOrFail]) or not.
|
||||||
* Put convenience functions in [TransactionDSL] instead. There are some cases where the overloads would clash with the
|
*/
|
||||||
* Interpreter interface, in these cases define a "backing" function in the interface instead (e.g. [_command]).
|
interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
|
||||||
*
|
/**
|
||||||
* This way the responsibility of providing a nice frontend DSL and the implementation(s) are separated.
|
* A reference to the enclosing ledger{..}'s interpreter.
|
||||||
|
*/
|
||||||
|
val ledgerInterpreter: LedgerDSLInterpreter<TransactionDSLInterpreter>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an input reference to the transaction. Note that [verifies] will resolve this reference.
|
||||||
|
* @param stateRef The input [StateRef].
|
||||||
*/
|
*/
|
||||||
interface TransactionDSLInterpreter<R> : OutputStateLookup {
|
|
||||||
val ledgerInterpreter: LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>>
|
|
||||||
fun input(stateRef: StateRef)
|
fun input(stateRef: StateRef)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an output to the transaction.
|
||||||
|
* @param label An optional label that may be later used to retrieve the output probably in other transactions.
|
||||||
|
* @param notary The associated notary.
|
||||||
|
* @param contractState The state itself.
|
||||||
|
*/
|
||||||
fun _output(label: String?, notary: Party, contractState: ContractState)
|
fun _output(label: String?, notary: Party, contractState: ContractState)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an [Attachment] reference to the transaction.
|
||||||
|
* @param attachmentId The hash of the attachment, possibly returned by [LedgerDSLInterpreter.attachment].
|
||||||
|
*/
|
||||||
fun attachment(attachmentId: SecureHash)
|
fun attachment(attachmentId: SecureHash)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a command to the transaction.
|
||||||
|
* @param signers The signer public keys.
|
||||||
|
* @param commandData The contents of the command.
|
||||||
|
*/
|
||||||
fun _command(signers: List<PublicKey>, commandData: CommandData)
|
fun _command(signers: List<PublicKey>, commandData: CommandData)
|
||||||
fun verifies(): R
|
|
||||||
fun failsWith(expectedMessage: String?): R
|
/**
|
||||||
fun tweak(
|
* Creates a local scoped copy of the transaction.
|
||||||
dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R
|
* @param dsl The transaction DSL to be interpreted using the copy.
|
||||||
): R
|
*/
|
||||||
|
fun tweak(dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail): EnforceVerifyOrFail
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransactionDSL<R, out T : TransactionDSLInterpreter<R>> (val interpreter: T) :
|
class TransactionDSL<out T : TransactionDSLInterpreter> (val interpreter: T) :
|
||||||
TransactionDSLInterpreter<R> by interpreter {
|
TransactionDSLInterpreter by interpreter {
|
||||||
|
|
||||||
fun input(stateLabel: String) = input(retrieveOutputStateAndRef(ContractState::class.java, stateLabel).ref)
|
|
||||||
/**
|
/**
|
||||||
* Adds the passed in state as a non-verified transaction output to the ledger and adds that as an input.
|
* Looks up the output label and adds the found state as an input.
|
||||||
|
* @param stateLabel The label of the output state specified when calling [TransactionDSLInterpreter._output] and friends.
|
||||||
|
*/
|
||||||
|
fun input(stateLabel: String) = input(retrieveOutputStateAndRef(ContractState::class.java, stateLabel).ref)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an [LedgerDSLInterpreter._unverifiedTransaction] with a single output state and adds it's reference as an
|
||||||
|
* input to the current transaction.
|
||||||
|
* @param state The state to be added.
|
||||||
*/
|
*/
|
||||||
fun input(state: ContractState) {
|
fun input(state: ContractState) {
|
||||||
val transaction = ledgerInterpreter.unverifiedTransaction(null) {
|
val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder()) {
|
||||||
output { state }
|
output { state }
|
||||||
}
|
}
|
||||||
input(transaction.outRef<ContractState>(0).ref)
|
input(transaction.outRef<ContractState>(0).ref)
|
||||||
}
|
}
|
||||||
fun input(stateClosure: () -> ContractState) = input(stateClosure())
|
fun input(stateClosure: () -> ContractState) = input(stateClosure())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TransactionDSLInterpreter._output
|
||||||
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun output(label: String? = null, notary: Party = DUMMY_NOTARY, contractStateClosure: () -> ContractState) =
|
fun output(label: String? = null, notary: Party = DUMMY_NOTARY, contractStateClosure: () -> ContractState) =
|
||||||
_output(label, notary, contractStateClosure())
|
_output(label, notary, contractStateClosure())
|
||||||
@JvmOverloads
|
/**
|
||||||
fun output(label: String? = null, contractState: ContractState) =
|
* @see TransactionDSLInterpreter._output
|
||||||
|
*/
|
||||||
|
fun output(label: String, contractState: ContractState) =
|
||||||
_output(label, DUMMY_NOTARY, contractState)
|
_output(label, DUMMY_NOTARY, contractState)
|
||||||
|
|
||||||
|
fun output(contractState: ContractState) =
|
||||||
|
_output(null, DUMMY_NOTARY, contractState)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TransactionDSLInterpreter._command
|
||||||
|
*/
|
||||||
fun command(vararg signers: PublicKey, commandDataClosure: () -> CommandData) =
|
fun command(vararg signers: PublicKey, commandDataClosure: () -> CommandData) =
|
||||||
_command(listOf(*signers), commandDataClosure())
|
_command(listOf(*signers), commandDataClosure())
|
||||||
|
/**
|
||||||
|
* @see TransactionDSLInterpreter._command
|
||||||
|
*/
|
||||||
fun command(signer: PublicKey, commandData: CommandData) = _command(listOf(signer), commandData)
|
fun command(signer: PublicKey, commandData: CommandData) = _command(listOf(signer), commandData)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a timestamp command to the transaction.
|
||||||
|
* @param time The [Instant] of the [TimestampCommand].
|
||||||
|
* @param tolerance The tolerance of the [TimestampCommand].
|
||||||
|
* @param notary The notary to sign the command.
|
||||||
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun timestamp(time: Instant, notary: PublicKey = DUMMY_NOTARY.owningKey) =
|
fun timestamp(time: Instant, tolerance: Duration = 30.seconds, notary: PublicKey = DUMMY_NOTARY.owningKey) =
|
||||||
timestamp(TimestampCommand(time, 30.seconds), notary)
|
timestamp(TimestampCommand(time, tolerance), notary)
|
||||||
|
/**
|
||||||
|
* Adds a timestamp command to the transaction.
|
||||||
|
* @param data The [TimestampCommand].
|
||||||
|
* @param notary The notary to sign the command.
|
||||||
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun timestamp(data: TimestampCommand, notary: PublicKey = DUMMY_NOTARY.owningKey) = command(notary, data)
|
fun timestamp(data: TimestampCommand, notary: PublicKey = DUMMY_NOTARY.owningKey) = command(notary, data)
|
||||||
|
|
||||||
fun fails() = failsWith(null)
|
|
||||||
infix fun `fails with`(msg: String) = failsWith(msg)
|
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ class NonEmptySet<T>(initial: T) : MutableSet<T> {
|
|||||||
override fun hashCode(): Int = set.hashCode()
|
override fun hashCode(): Int = set.hashCode()
|
||||||
override fun toString(): String = set.toString()
|
override fun toString(): String = set.toString()
|
||||||
|
|
||||||
inner class Iterator<T>(val iterator: MutableIterator<T>) : MutableIterator<T> {
|
inner class Iterator<out T>(val iterator: MutableIterator<T>) : MutableIterator<T> {
|
||||||
override fun hasNext(): Boolean = iterator.hasNext()
|
override fun hasNext(): Boolean = iterator.hasNext()
|
||||||
override fun next(): T = iterator.next()
|
override fun next(): T = iterator.next()
|
||||||
override fun remove() =
|
override fun remove() =
|
||||||
|
@ -16,8 +16,8 @@ class RecordingMap<K, V>(private val wrappedMap: MutableMap<K, V>,
|
|||||||
// If/when Kotlin supports data classes inside sealed classes, that would be preferable to this.
|
// If/when Kotlin supports data classes inside sealed classes, that would be preferable to this.
|
||||||
interface Record
|
interface Record
|
||||||
|
|
||||||
data class Get<K>(val key: K) : Record
|
data class Get<out K>(val key: K) : Record
|
||||||
data class Put<K, V>(val key: K, val value: V) : Record
|
data class Put<out K, out V>(val key: K, val value: V) : Record
|
||||||
|
|
||||||
private val _records = Collections.synchronizedList(ArrayList<Record>())
|
private val _records = Collections.synchronizedList(ArrayList<Record>())
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ package com.r3corda.core.utilities
|
|||||||
* - Are any objects *reachable* from this object mismatched or not what you expected?
|
* - Are any objects *reachable* from this object mismatched or not what you expected?
|
||||||
* - Is it suspiciously large or small?
|
* - Is it suspiciously large or small?
|
||||||
*/
|
*/
|
||||||
class UntrustworthyData<T>(private val fromUntrustedWorld: T) {
|
class UntrustworthyData<out T>(private val fromUntrustedWorld: T) {
|
||||||
val data: T
|
val data: T
|
||||||
@Deprecated("Accessing the untrustworthy data directly without validating it first is a bad idea")
|
@Deprecated("Accessing the untrustworthy data directly without validating it first is a bad idea")
|
||||||
get() = fromUntrustedWorld
|
get() = fromUntrustedWorld
|
||||||
|
@ -25,7 +25,7 @@ import java.util.*
|
|||||||
* @param T The ultimate type of the data being fetched.
|
* @param T The ultimate type of the data being fetched.
|
||||||
* @param W The wire type of the data being fetched, for when it isn't the same as the ultimate type.
|
* @param W The wire type of the data being fetched, for when it isn't the same as the ultimate type.
|
||||||
*/
|
*/
|
||||||
abstract class FetchDataProtocol<T : NamedByHash, W : Any>(
|
abstract class FetchDataProtocol<T : NamedByHash, in W : Any>(
|
||||||
protected val requests: Set<SecureHash>,
|
protected val requests: Set<SecureHash>,
|
||||||
protected val otherSide: Party) : ProtocolLogic<FetchDataProtocol.Result<T>>() {
|
protected val otherSide: Party) : ProtocolLogic<FetchDataProtocol.Result<T>>() {
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ abstract class FetchDataProtocol<T : NamedByHash, W : Any>(
|
|||||||
class DownloadedVsRequestedDataMismatch(val requested: SecureHash, val got: SecureHash) : BadAnswer()
|
class DownloadedVsRequestedDataMismatch(val requested: SecureHash, val got: SecureHash) : BadAnswer()
|
||||||
|
|
||||||
data class Request(val hashes: List<SecureHash>, override val replyToParty: Party, override val sessionID: Long) : PartyRequestMessage
|
data class Request(val hashes: List<SecureHash>, override val replyToParty: Party, override val sessionID: Long) : PartyRequestMessage
|
||||||
data class Result<T : NamedByHash>(val fromDisk: List<T>, val downloaded: List<T>)
|
data class Result<out T : NamedByHash>(val fromDisk: List<T>, val downloaded: List<T>)
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Result<T> {
|
override fun call(): Result<T> {
|
||||||
|
@ -27,10 +27,10 @@ object NotaryProtocol {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A protocol to be used for obtaining a signature from a [NotaryService] ascertaining the transaction
|
* A protocol to be used for obtaining a signature from a [NotaryService] ascertaining the transaction
|
||||||
* timestamp is correct and none of its inputs have been used in another completed transaction
|
* timestamp is correct and none of its inputs have been used in another completed transaction.
|
||||||
*
|
*
|
||||||
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
|
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
|
||||||
* by another transaction or the timestamp is invalid
|
* by another transaction or the timestamp is invalid.
|
||||||
*/
|
*/
|
||||||
class Client(private val stx: SignedTransaction,
|
class Client(private val stx: SignedTransaction,
|
||||||
override val progressTracker: ProgressTracker = Client.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
|
override val progressTracker: ProgressTracker = Client.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
|
||||||
@ -157,7 +157,7 @@ object NotaryProtocol {
|
|||||||
* history chain.
|
* history chain.
|
||||||
* As a result, the Notary _will commit invalid transactions_ as well, but as it also records the identity of
|
* As a result, the Notary _will commit invalid transactions_ as well, but as it also records the identity of
|
||||||
* the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently
|
* the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently
|
||||||
* undo the commit of the input states (the exact mechanism still needs to be worked out)
|
* undo the commit of the input states (the exact mechanism still needs to be worked out).
|
||||||
*/
|
*/
|
||||||
@Suspendable
|
@Suspendable
|
||||||
open fun beforeCommit(stx: SignedTransaction, reqIdentity: Party) {
|
open fun beforeCommit(stx: SignedTransaction, reqIdentity: Party) {
|
||||||
|
@ -44,7 +44,7 @@ object TwoPartyDealProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
|
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
|
||||||
data class Handshake<T>(
|
data class Handshake<out T>(
|
||||||
val payload: T,
|
val payload: T,
|
||||||
val publicKey: PublicKey,
|
val publicKey: PublicKey,
|
||||||
val sessionID: Long
|
val sessionID: Long
|
||||||
@ -58,7 +58,7 @@ object TwoPartyDealProtocol {
|
|||||||
* There's a good chance we can push at least some of this logic down into core protocol logic
|
* There's a good chance we can push at least some of this logic down into core protocol logic
|
||||||
* and helper methods etc.
|
* and helper methods etc.
|
||||||
*/
|
*/
|
||||||
abstract class Primary<U>(override val progressTracker: ProgressTracker = Primary.tracker()) : ProtocolLogic<SignedTransaction>() {
|
abstract class Primary<out U>(override val progressTracker: ProgressTracker = Primary.tracker()) : ProtocolLogic<SignedTransaction>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
object AWAITING_PROPOSAL : ProgressTracker.Step("Handshaking and awaiting transaction proposal")
|
object AWAITING_PROPOSAL : ProgressTracker.Step("Handshaking and awaiting transaction proposal")
|
||||||
@ -400,7 +400,7 @@ object TwoPartyDealProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One side of the fixing protocol for an interest rate swap, but could easily be generalised furher
|
* One side of the fixing protocol for an interest rate swap, but could easily be generalised furher.
|
||||||
*
|
*
|
||||||
* As per the [Fixer], do not infer too much from this class name in terms of business roles. This
|
* As per the [Fixer], do not infer too much from this class name in terms of business roles. This
|
||||||
* is just the "side" of the protocol run by the party with the floating leg as a way of deciding who
|
* is just the "side" of the protocol run by the party with the floating leg as a way of deciding who
|
||||||
|
@ -14,7 +14,7 @@ import java.security.SignatureException
|
|||||||
* A notary commit protocol that makes sure a given transaction is valid before committing it. This does mean that the calling
|
* 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
|
* 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
|
* has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
|
||||||
* indeed valid
|
* indeed valid.
|
||||||
*/
|
*/
|
||||||
class ValidatingNotaryProtocol(otherSide: Party,
|
class ValidatingNotaryProtocol(otherSide: Party,
|
||||||
sessionIdForSend: Long,
|
sessionIdForSend: Long,
|
||||||
|
@ -26,7 +26,7 @@ import java.security.PublicKey
|
|||||||
* use the new updated state for future transactions.
|
* use the new updated state for future transactions.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractStateReplacementProtocol<T> {
|
abstract class AbstractStateReplacementProtocol<T> {
|
||||||
interface Proposal<T> {
|
interface Proposal<out T> {
|
||||||
val stateRef: StateRef
|
val stateRef: StateRef
|
||||||
val modification: T
|
val modification: T
|
||||||
val stx: SignedTransaction
|
val stx: SignedTransaction
|
||||||
|
@ -35,7 +35,7 @@ object NotaryChangeProtocol: AbstractStateReplacementProtocol<Party>() {
|
|||||||
|
|
||||||
override fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
|
override fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
|
||||||
val state = originalState.state
|
val state = originalState.state
|
||||||
val newState = state.withNewNotary(modification)
|
val newState = state.withNotary(modification)
|
||||||
val participants = state.data.participants
|
val participants = state.data.participants
|
||||||
val tx = TransactionType.NotaryChange.Builder().withItems(originalState, newState)
|
val tx = TransactionType.NotaryChange.Builder().withItems(originalState, newState)
|
||||||
tx.signWith(serviceHub.storageService.myLegalIdentityKey)
|
tx.signWith(serviceHub.storageService.myLegalIdentityKey)
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package com.r3corda.core.protocols;
|
package com.r3corda.core.protocols;
|
||||||
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.*;
|
||||||
import org.junit.Test;
|
import org.junit.*;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class ProtocolLogicRefFromJavaTest {
|
public class ProtocolLogicRefFromJavaTest {
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import com.r3corda.core.testing.DUMMY_NOTARY
|
|||||||
import com.r3corda.core.testing.MEGA_CORP_KEY
|
import com.r3corda.core.testing.MEGA_CORP_KEY
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.SecureRandom
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class TransactionGraphSearchTests {
|
class TransactionGraphSearchTests {
|
||||||
|
@ -7,7 +7,6 @@ import com.r3corda.core.node.services.testing.MockStorageService
|
|||||||
import com.r3corda.core.testing.*
|
import com.r3corda.core.testing.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package com.r3corda.core.crypto
|
package com.r3corda.core.crypto
|
||||||
|
|
||||||
import java.math.BigInteger
|
|
||||||
import java.util.Arrays
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modified from the bitcoinj library
|
* Modified from the bitcoinj library.
|
||||||
*/
|
*/
|
||||||
class Base58Test {
|
class Base58Test {
|
||||||
@Test
|
@Test
|
||||||
@ -32,7 +32,7 @@ class Base58Test {
|
|||||||
@Test
|
@Test
|
||||||
fun testDecode() {
|
fun testDecode() {
|
||||||
val testbytes = "Hello World".toByteArray()
|
val testbytes = "Hello World".toByteArray()
|
||||||
val actualbytes = Base58.decode("JxF12TrwUP45BMd");
|
val actualbytes = Base58.decode("JxF12TrwUP45BMd")
|
||||||
assertTrue(String(actualbytes)) { Arrays.equals(testbytes, actualbytes) }
|
assertTrue(String(actualbytes)) { Arrays.equals(testbytes, actualbytes) }
|
||||||
|
|
||||||
assertTrue("1") { Arrays.equals(ByteArray(1), Base58.decode("1")) }
|
assertTrue("1") { Arrays.equals(ByteArray(1), Base58.decode("1")) }
|
||||||
|
@ -234,7 +234,7 @@ class AttachmentClassLoaderTests {
|
|||||||
|
|
||||||
val attachmentRef = importJar(storage)
|
val attachmentRef = importJar(storage)
|
||||||
|
|
||||||
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
|
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
|
||||||
|
|
||||||
val wireTransaction = tx.toWireTransaction()
|
val wireTransaction = tx.toWireTransaction()
|
||||||
|
|
||||||
@ -265,7 +265,7 @@ class AttachmentClassLoaderTests {
|
|||||||
|
|
||||||
val attachmentRef = importJar(storage)
|
val attachmentRef = importJar(storage)
|
||||||
|
|
||||||
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
|
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
|
||||||
|
|
||||||
val wireTransaction = tx.toWireTransaction()
|
val wireTransaction = tx.toWireTransaction()
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import com.r3corda.core.testing.*
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -47,7 +46,7 @@ class TransactionSerializationTests {
|
|||||||
val fakeStateRef = generateStateRef()
|
val fakeStateRef = generateStateRef()
|
||||||
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
|
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 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)
|
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, DUMMY_KEY_1.public), DUMMY_NOTARY)
|
||||||
|
|
||||||
|
|
||||||
lateinit var tx: TransactionBuilder
|
lateinit var tx: TransactionBuilder
|
||||||
@ -55,14 +54,14 @@ class TransactionSerializationTests {
|
|||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
tx = TransactionType.General.Builder().withItems(
|
tx = TransactionType.General.Builder().withItems(
|
||||||
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
|
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(DUMMY_KEY_1.public))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun signWireTX() {
|
fun signWireTX() {
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
tx.signWith(TestUtils.keypair)
|
tx.signWith(DUMMY_KEY_1)
|
||||||
val signedTX = tx.toSignedTransaction()
|
val signedTX = tx.toSignedTransaction()
|
||||||
|
|
||||||
// Now check that the signature we just made verifies.
|
// Now check that the signature we just made verifies.
|
||||||
@ -82,7 +81,7 @@ class TransactionSerializationTests {
|
|||||||
tx.toSignedTransaction()
|
tx.toSignedTransaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.signWith(TestUtils.keypair)
|
tx.signWith(DUMMY_KEY_1)
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
val signedTX = tx.toSignedTransaction()
|
val signedTX = tx.toSignedTransaction()
|
||||||
|
|
||||||
@ -94,9 +93,9 @@ class TransactionSerializationTests {
|
|||||||
// If the signature was replaced in transit, we don't like it.
|
// If the signature was replaced in transit, we don't like it.
|
||||||
assertFailsWith(SignatureException::class) {
|
assertFailsWith(SignatureException::class) {
|
||||||
val tx2 = TransactionType.General.Builder().withItems(inputState, outputState, changeState,
|
val tx2 = TransactionType.General.Builder().withItems(inputState, outputState, changeState,
|
||||||
Command(TestCash.Commands.Move(), TestUtils.keypair2.public))
|
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
|
||||||
tx2.signWith(DUMMY_NOTARY_KEY)
|
tx2.signWith(DUMMY_NOTARY_KEY)
|
||||||
tx2.signWith(TestUtils.keypair2)
|
tx2.signWith(DUMMY_KEY_2)
|
||||||
|
|
||||||
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
|
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
|
||||||
}
|
}
|
||||||
@ -105,7 +104,7 @@ class TransactionSerializationTests {
|
|||||||
@Test
|
@Test
|
||||||
fun timestamp() {
|
fun timestamp() {
|
||||||
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
|
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
|
||||||
tx.signWith(TestUtils.keypair)
|
tx.signWith(DUMMY_KEY_1)
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
val stx = tx.toSignedTransaction()
|
val stx = tx.toSignedTransaction()
|
||||||
val ltx = stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments)
|
val ltx = stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments)
|
||||||
|
@ -43,8 +43,8 @@ class NonEmptySetTest {
|
|||||||
.suppressing(CollectionAddAllTester::class.java.getMethod("testAddAll_nullCollectionReference"))
|
.suppressing(CollectionAddAllTester::class.java.getMethod("testAddAll_nullCollectionReference"))
|
||||||
// Disable tests that try to remove everything:
|
// Disable tests that try to remove everything:
|
||||||
.suppressing(CollectionRemoveAllTester::class.java.getMethod("testRemoveAll_nullCollectionReferenceNonEmptySubject"))
|
.suppressing(CollectionRemoveAllTester::class.java.getMethod("testRemoveAll_nullCollectionReferenceNonEmptySubject"))
|
||||||
.suppressing(CollectionClearTester::class.java.getMethods().toList())
|
.suppressing(CollectionClearTester::class.java.methods.toList())
|
||||||
.suppressing(CollectionRetainAllTester::class.java.getMethods().toList())
|
.suppressing(CollectionRetainAllTester::class.java.methods.toList())
|
||||||
.createTestSuite()
|
.createTestSuite()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
docs/build/html/_static/js/theme.js
vendored
9
docs/build/html/_static/js/theme.js
vendored
@ -69,7 +69,7 @@ function ThemeNav () {
|
|||||||
})
|
})
|
||||||
.on('click', "[data-toggle='rst-current-version']", function() {
|
.on('click', "[data-toggle='rst-current-version']", function() {
|
||||||
$("[data-toggle='rst-versions']").toggleClass("shift-up");
|
$("[data-toggle='rst-versions']").toggleClass("shift-up");
|
||||||
})
|
});
|
||||||
|
|
||||||
// Make tables responsive
|
// Make tables responsive
|
||||||
$("table.docutils:not(.field-list)")
|
$("table.docutils:not(.field-list)")
|
||||||
@ -139,12 +139,11 @@ function ThemeNav () {
|
|||||||
parent_li.siblings().find('li.current').removeClass('current');
|
parent_li.siblings().find('li.current').removeClass('current');
|
||||||
parent_li.find('> ul li.current').removeClass('current');
|
parent_li.find('> ul li.current').removeClass('current');
|
||||||
parent_li.toggleClass('current');
|
parent_li.toggleClass('current');
|
||||||
}
|
};
|
||||||
|
|
||||||
return nav;
|
return nav;
|
||||||
};
|
}
|
||||||
|
module.exports.ThemeNav = ThemeNav();
|
||||||
module.exports.ThemeNav = ThemeNav();
|
|
||||||
|
|
||||||
if (typeof(window) != 'undefined') {
|
if (typeof(window) != 'undefined') {
|
||||||
window.SphinxRtdTheme = { StickyNav: module.exports.ThemeNav };
|
window.SphinxRtdTheme = { StickyNav: module.exports.ThemeNav };
|
||||||
|
6
docs/build/html/_static/searchtools.js
vendored
6
docs/build/html/_static/searchtools.js
vendored
@ -191,7 +191,7 @@ var Stemmer = function() {
|
|||||||
w = firstch.toLowerCase() + w.substr(1);
|
w = firstch.toLowerCase() + w.substr(1);
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -564,7 +564,7 @@ var Search = {
|
|||||||
$u.each(_o, function(o) {
|
$u.each(_o, function(o) {
|
||||||
var _files = o.files;
|
var _files = o.files;
|
||||||
if (_files === undefined)
|
if (_files === undefined)
|
||||||
return
|
return;
|
||||||
|
|
||||||
if (_files.length === undefined)
|
if (_files.length === undefined)
|
||||||
_files = [_files];
|
_files = [_files];
|
||||||
@ -574,7 +574,7 @@ var Search = {
|
|||||||
for (j = 0; j < _files.length; j++) {
|
for (j = 0; j < _files.length; j++) {
|
||||||
file = _files[j];
|
file = _files[j];
|
||||||
if (!(file in scoreMap))
|
if (!(file in scoreMap))
|
||||||
scoreMap[file] = {}
|
scoreMap[file] = {};
|
||||||
scoreMap[file][word] = o.score;
|
scoreMap[file][word] = o.score;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
17
docs/build_docs.sh
Executable file
17
docs/build_docs.sh
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -xeo pipefail
|
||||||
|
|
||||||
|
if [ ! -d "virtualenv" ]
|
||||||
|
then
|
||||||
|
virtualenv -p python2.7 virtualenv
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
. virtualenv/bin/activate
|
||||||
|
if [ ! -d "virtualenv/lib/python2.7/site-packages/sphinx" ]
|
||||||
|
then
|
||||||
|
pip install -r requirements.txt
|
||||||
|
fi
|
||||||
|
make html
|
||||||
|
)
|
12
docs/requirements.txt
Normal file
12
docs/requirements.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
alabaster==0.7.8
|
||||||
|
Babel==2.3.4
|
||||||
|
docutils==0.12
|
||||||
|
imagesize==0.7.1
|
||||||
|
Jinja2==2.8
|
||||||
|
MarkupSafe==0.23
|
||||||
|
Pygments==2.1.3
|
||||||
|
pytz==2016.4
|
||||||
|
six==1.10.0
|
||||||
|
snowballstemmer==1.2.1
|
||||||
|
Sphinx==1.4.4
|
||||||
|
sphinx-rtd-theme==0.1.9
|
@ -39,6 +39,7 @@ Read on to learn:
|
|||||||
|
|
||||||
where-to-start
|
where-to-start
|
||||||
tutorial-contract
|
tutorial-contract
|
||||||
|
tutorial-test-dsl
|
||||||
protocol-state-machines
|
protocol-state-machines
|
||||||
oracles
|
oracles
|
||||||
event-scheduling
|
event-scheduling
|
||||||
|
557
docs/source/tutorial-test-dsl.rst
Normal file
557
docs/source/tutorial-test-dsl.rst
Normal file
@ -0,0 +1,557 @@
|
|||||||
|
.. highlight:: kotlin
|
||||||
|
.. role:: kotlin(code)
|
||||||
|
:language: kotlin
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||||
|
|
||||||
|
Writing a contract test
|
||||||
|
=======================
|
||||||
|
|
||||||
|
This tutorial will take you through the steps required to write a contract test using Kotlin and/or Java.
|
||||||
|
|
||||||
|
The testing DSL allows one to define a piece of the ledger with transactions referring to each other, and ways of
|
||||||
|
verifying their correctness.
|
||||||
|
|
||||||
|
Testing single transactions
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
We start with the empty ledger:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun emptyLedger() {
|
||||||
|
ledger {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
import static com.r3corda.core.testing.JavaTestHelpers.*;
|
||||||
|
import static com.r3corda.core.contracts.JavaTestHelpers.*;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyLedger() {
|
||||||
|
ledger(l -> {
|
||||||
|
return Unit.INSTANCE; // We need to return this explicitly
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
The DSL keyword ``ledger`` takes a closure that can build up several transactions and may verify their overall
|
||||||
|
correctness.
|
||||||
|
|
||||||
|
Let's add a Cash transaction:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleCashDoesntCompile() {
|
||||||
|
val inState = Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = DUMMY_PUBKEY_1
|
||||||
|
)
|
||||||
|
ledger {
|
||||||
|
transaction {
|
||||||
|
input(inState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleCashDoesntCompile() {
|
||||||
|
Cash.State inState = new Cash.State(
|
||||||
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
|
||||||
|
getDUMMY_PUBKEY_1()
|
||||||
|
);
|
||||||
|
ledger(l -> {
|
||||||
|
l.transaction(tx -> {
|
||||||
|
tx.input(inState);
|
||||||
|
});
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
We can add a transaction to the ledger using the ``transaction`` primitive. The transaction in turn may be defined by
|
||||||
|
specifying ``input``-s, ``output``-s, ``command``-s and ``attachment``-s.
|
||||||
|
|
||||||
|
The above ``input`` call is a bit special: Transactions don't actually contain input states, just references
|
||||||
|
to output states of other transactions. Under the hood the above ``input`` call creates a dummy transaction in the
|
||||||
|
ledger (that won't be verified) which outputs the specified state, and references that from this transaction.
|
||||||
|
|
||||||
|
The above code however doesn't compile:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
Error:(26, 21) Kotlin: Type mismatch: inferred type is Unit but EnforceVerifyOrFail was expected
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
Error:(26, 31) java: incompatible types: bad return type in lambda expression missing return value
|
||||||
|
|
||||||
|
This is deliberate: The DSL forces us to specify either ``this.verifies()`` or ``this `fails with` "some text"`` on the
|
||||||
|
last line of ``transaction``:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleCash() {
|
||||||
|
val inState = Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = DUMMY_PUBKEY_1
|
||||||
|
)
|
||||||
|
ledger {
|
||||||
|
transaction {
|
||||||
|
input(inState)
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleCash() {
|
||||||
|
Cash.State inState = new Cash.State(
|
||||||
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
|
||||||
|
getDUMMY_PUBKEY_1()
|
||||||
|
);
|
||||||
|
ledger(l -> {
|
||||||
|
l.transaction(tx -> {
|
||||||
|
tx.input(inState);
|
||||||
|
return tx.verifies();
|
||||||
|
});
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
The code finally compiles. When run, it produces the following error::
|
||||||
|
|
||||||
|
com.r3corda.core.contracts.TransactionVerificationException$ContractRejection: java.lang.IllegalArgumentException: Failed requirement: for deposit [0101] at issuer MegaCorp the amounts balance
|
||||||
|
|
||||||
|
The transaction verification failed, because the sum of inputs does not equal the sum of outputs. We can specify that
|
||||||
|
this is intended behaviour by changing ``this.verifies()`` to ``this `fails with` "the amounts balance"``:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleCashFailsWith() {
|
||||||
|
val inState = Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = DUMMY_PUBKEY_1
|
||||||
|
)
|
||||||
|
ledger {
|
||||||
|
transaction {
|
||||||
|
input(inState)
|
||||||
|
this `fails with` "the amounts balance"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleCashFailsWith() {
|
||||||
|
Cash.State inState = new Cash.State(
|
||||||
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
|
||||||
|
getDUMMY_PUBKEY_1()
|
||||||
|
);
|
||||||
|
ledger(l -> {
|
||||||
|
l.transaction(tx -> {
|
||||||
|
tx.input(inState);
|
||||||
|
return tx.failsWith("the amounts balance");
|
||||||
|
});
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
We can continue to build the transaction until it ``verifies``:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleCashSuccess() {
|
||||||
|
val inState = Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = DUMMY_PUBKEY_1
|
||||||
|
)
|
||||||
|
ledger {
|
||||||
|
transaction {
|
||||||
|
input(inState)
|
||||||
|
this `fails with` "the amounts balance"
|
||||||
|
output(inState.copy(owner = DUMMY_PUBKEY_2))
|
||||||
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleCashSuccess() {
|
||||||
|
Cash.State inState = new Cash.State(
|
||||||
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
|
||||||
|
getDUMMY_PUBKEY_1()
|
||||||
|
);
|
||||||
|
ledger(l -> {
|
||||||
|
l.transaction(tx -> {
|
||||||
|
tx.input(inState);
|
||||||
|
tx.failsWith("the amounts balance");
|
||||||
|
tx.output(inState.copy(inState.getAmount(), getDUMMY_PUBKEY_2()));
|
||||||
|
tx.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
|
||||||
|
return tx.verifies();
|
||||||
|
});
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
``output`` specifies that we want the input state to be transferred to ``DUMMY_PUBKEY_2`` and ``command`` adds the
|
||||||
|
``Move`` command itself, signed by the current owner of the input state, ``DUMMY_PUBKEY_1``.
|
||||||
|
|
||||||
|
We constructed a complete signed cash transaction from ``DUMMY_PUBKEY_1`` to ``DUMMY_PUBKEY_2`` and verified it. Note
|
||||||
|
how we left in the ``fails with`` line - this is fine, the failure will be tested on the partially constructed
|
||||||
|
transaction.
|
||||||
|
|
||||||
|
What should we do if we wanted to test what happens when the wrong party signs the transaction? If we simply add a
|
||||||
|
``command`` it will ruin the transaction for good... Enter ``tweak``:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleCashTweakSuccess() {
|
||||||
|
val inState = Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = DUMMY_PUBKEY_1
|
||||||
|
)
|
||||||
|
ledger {
|
||||||
|
transaction {
|
||||||
|
input(inState)
|
||||||
|
this `fails with` "the amounts balance"
|
||||||
|
output(inState.copy(owner = DUMMY_PUBKEY_2))
|
||||||
|
|
||||||
|
tweak {
|
||||||
|
command(DUMMY_PUBKEY_2) { Cash.Commands.Move() }
|
||||||
|
this `fails with` "the owning keys are the same as the signing keys"
|
||||||
|
}
|
||||||
|
|
||||||
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleCashTweakSuccess() {
|
||||||
|
Cash.State inState = new Cash.State(
|
||||||
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
|
||||||
|
getDUMMY_PUBKEY_1()
|
||||||
|
);
|
||||||
|
ledger(l -> {
|
||||||
|
l.transaction(tx -> {
|
||||||
|
tx.input(inState);
|
||||||
|
tx.failsWith("the amounts balance");
|
||||||
|
tx.output(inState.copy(inState.getAmount(), getDUMMY_PUBKEY_2()));
|
||||||
|
|
||||||
|
tx.tweak(tw -> {
|
||||||
|
tw.command(getDUMMY_PUBKEY_2(), new Cash.Commands.Move());
|
||||||
|
return tw.failsWith("the owning keys are the same as the signing keys");
|
||||||
|
});
|
||||||
|
tx.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
|
||||||
|
return tx.verifies();
|
||||||
|
});
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
``tweak`` creates a local copy of the transaction. This allows the local "ruining" of the transaction allowing testing
|
||||||
|
of different error conditions.
|
||||||
|
|
||||||
|
We now have a neat little test that tests a single transaction. This is already useful, and in fact testing of a single
|
||||||
|
transaction in this way is very common. There is even a shorthand toplevel ``transaction`` primitive that creates a
|
||||||
|
ledger with a single transaction:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleCashTweakSuccessTopLevelTransaction() {
|
||||||
|
val inState = Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = DUMMY_PUBKEY_1
|
||||||
|
)
|
||||||
|
transaction {
|
||||||
|
input(inState)
|
||||||
|
this `fails with` "the amounts balance"
|
||||||
|
output(inState.copy(owner = DUMMY_PUBKEY_2))
|
||||||
|
|
||||||
|
tweak {
|
||||||
|
command(DUMMY_PUBKEY_2) { Cash.Commands.Move() }
|
||||||
|
this `fails with` "the owning keys are the same as the signing keys"
|
||||||
|
}
|
||||||
|
|
||||||
|
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleCashTweakSuccessTopLevelTransaction() {
|
||||||
|
Cash.State inState = new Cash.State(
|
||||||
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
|
||||||
|
getDUMMY_PUBKEY_1()
|
||||||
|
);
|
||||||
|
transaction(tx -> {
|
||||||
|
tx.input(inState);
|
||||||
|
tx.failsWith("the amounts balance");
|
||||||
|
tx.output(inState.copy(inState.getAmount(), getDUMMY_PUBKEY_2()));
|
||||||
|
|
||||||
|
tx.tweak(tw -> {
|
||||||
|
tw.command(getDUMMY_PUBKEY_2(), new Cash.Commands.Move());
|
||||||
|
return tw.failsWith("the owning keys are the same as the signing keys");
|
||||||
|
});
|
||||||
|
tx.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
|
||||||
|
return tx.verifies();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Chaining transactions
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Now that we know how to define a single transaction, let's look at how to define a chain of them:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun chainCash() {
|
||||||
|
ledger {
|
||||||
|
unverifiedTransaction {
|
||||||
|
output("MEGA_CORP cash") {
|
||||||
|
Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = MEGA_CORP_PUBKEY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
input("MEGA_CORP cash")
|
||||||
|
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
|
||||||
|
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void chainCash() {
|
||||||
|
ledger(l -> {
|
||||||
|
l.unverifiedTransaction(tx -> {
|
||||||
|
tx.output("MEGA_CORP cash",
|
||||||
|
new Cash.State(
|
||||||
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
|
||||||
|
getMEGA_CORP_PUBKEY()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
|
||||||
|
l.transaction(tx -> {
|
||||||
|
tx.input("MEGA_CORP cash");
|
||||||
|
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
|
||||||
|
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_1()));
|
||||||
|
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
|
||||||
|
return tx.verifies();
|
||||||
|
});
|
||||||
|
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
In this example we declare that ``MEGA_CORP`` has a thousand dollars but we don't care where from, for this we can use
|
||||||
|
``unverifiedTransaction``. Note how we don't need to specify ``this.verifies()``.
|
||||||
|
|
||||||
|
The ``output`` cash was labelled with ``"MEGA_CORP cash"``, we can subsequently referred to this other transactions, e.g.
|
||||||
|
by ``input("MEGA_CORP cash")`` or ``"MEGA_CORP cash".output<Cash.State>()``.
|
||||||
|
|
||||||
|
What happens if we reuse the output cash twice?
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun chainCashDoubleSpend() {
|
||||||
|
ledger {
|
||||||
|
unverifiedTransaction {
|
||||||
|
output("MEGA_CORP cash") {
|
||||||
|
Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = MEGA_CORP_PUBKEY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
input("MEGA_CORP cash")
|
||||||
|
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
|
||||||
|
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
input("MEGA_CORP cash")
|
||||||
|
// We send it to another pubkey so that the transaction is not identical to the previous one
|
||||||
|
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_2))
|
||||||
|
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void chainCashDoubleSpend() {
|
||||||
|
ledger(l -> {
|
||||||
|
l.unverifiedTransaction(tx -> {
|
||||||
|
tx.output("MEGA_CORP cash",
|
||||||
|
new Cash.State(
|
||||||
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
|
||||||
|
getMEGA_CORP_PUBKEY()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
|
||||||
|
l.transaction(tx -> {
|
||||||
|
tx.input("MEGA_CORP cash");
|
||||||
|
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
|
||||||
|
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_1()));
|
||||||
|
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
|
||||||
|
return tx.verifies();
|
||||||
|
});
|
||||||
|
|
||||||
|
l.transaction(tx -> {
|
||||||
|
tx.input("MEGA_CORP cash");
|
||||||
|
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
|
||||||
|
// We send it to another pubkey so that the transaction is not identical to the previous one
|
||||||
|
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_2()));
|
||||||
|
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
|
||||||
|
return tx.verifies();
|
||||||
|
});
|
||||||
|
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
The transactions ``verifies()`` individually, however the state was spent twice!
|
||||||
|
|
||||||
|
We can also verify the complete ledger by calling ``verifies``/``fails`` on the ledger level. We can also use
|
||||||
|
``tweak`` to create a local copy of the whole ledger:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun chainCashDoubleSpendFailsWith() {
|
||||||
|
ledger {
|
||||||
|
unverifiedTransaction {
|
||||||
|
output("MEGA_CORP cash") {
|
||||||
|
Cash.State(
|
||||||
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
|
owner = MEGA_CORP_PUBKEY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
input("MEGA_CORP cash")
|
||||||
|
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
|
||||||
|
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
|
||||||
|
tweak {
|
||||||
|
transaction {
|
||||||
|
input("MEGA_CORP cash")
|
||||||
|
// We send it to another pubkey so that the transaction is not identical to the previous one
|
||||||
|
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
|
||||||
|
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
this.fails()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.verifies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void chainCashDoubleSpendFailsWith() {
|
||||||
|
ledger(l -> {
|
||||||
|
l.unverifiedTransaction(tx -> {
|
||||||
|
tx.output("MEGA_CORP cash",
|
||||||
|
new Cash.State(
|
||||||
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
|
||||||
|
getMEGA_CORP_PUBKEY()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
|
||||||
|
l.transaction(tx -> {
|
||||||
|
tx.input("MEGA_CORP cash");
|
||||||
|
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
|
||||||
|
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_1()));
|
||||||
|
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
|
||||||
|
return tx.verifies();
|
||||||
|
});
|
||||||
|
|
||||||
|
l.tweak(lw -> {
|
||||||
|
lw.transaction(tx -> {
|
||||||
|
tx.input("MEGA_CORP cash");
|
||||||
|
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
|
||||||
|
// We send it to another pubkey so that the transaction is not identical to the previous one
|
||||||
|
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_2()));
|
||||||
|
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
|
||||||
|
return tx.verifies();
|
||||||
|
});
|
||||||
|
lw.fails();
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
|
||||||
|
l.verifies();
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
}
|
@ -32,7 +32,7 @@ interface APIServer {
|
|||||||
fun serverTime(): LocalDateTime
|
fun serverTime(): LocalDateTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Report whether this node is started up or not
|
* Report whether this node is started up or not.
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("status")
|
@Path("status")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.r3corda.node.api
|
package com.r3corda.node.api
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extremely rudimentary query language which should most likely be replaced with a product
|
* Extremely rudimentary query language which should most likely be replaced with a product.
|
||||||
*/
|
*/
|
||||||
interface StatesQuery {
|
interface StatesQuery {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -5,7 +5,6 @@ import com.google.common.util.concurrent.ListenableFuture
|
|||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import com.r3corda.core.RunOnCallerThread
|
import com.r3corda.core.RunOnCallerThread
|
||||||
import com.r3corda.core.contracts.SignedTransaction
|
import com.r3corda.core.contracts.SignedTransaction
|
||||||
import com.r3corda.core.contracts.StateRef
|
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.messaging.MessagingService
|
import com.r3corda.core.messaging.MessagingService
|
||||||
import com.r3corda.core.messaging.runOnNextMessage
|
import com.r3corda.core.messaging.runOnNextMessage
|
||||||
@ -48,14 +47,12 @@ import com.r3corda.node.services.wallet.NodeWalletService
|
|||||||
import com.r3corda.node.utilities.ANSIProgressObserver
|
import com.r3corda.node.utilities.ANSIProgressObserver
|
||||||
import com.r3corda.node.utilities.AddOrRemove
|
import com.r3corda.node.utilities.AddOrRemove
|
||||||
import com.r3corda.node.utilities.AffinityExecutor
|
import com.r3corda.node.utilities.AffinityExecutor
|
||||||
import com.r3corda.protocols.TwoPartyDealProtocol
|
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.nio.file.FileAlreadyExistsException
|
import java.nio.file.FileAlreadyExistsException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Duration
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,7 +71,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
// TODO: Persist this, as well as whether the node is registered.
|
// TODO: Persist this, as well as whether the node is registered.
|
||||||
/**
|
/**
|
||||||
* Sequence number of changes sent to the network map service, when registering/de-registering this node
|
* Sequence number of changes sent to the network map service, when registering/de-registering this node.
|
||||||
*/
|
*/
|
||||||
var networkMapSeq: Long = 1
|
var networkMapSeq: Long = 1
|
||||||
|
|
||||||
@ -205,7 +202,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run any tasks that are needed to ensure the node is in a correct state before running start()
|
* Run any tasks that are needed to ensure the node is in a correct state before running start().
|
||||||
*/
|
*/
|
||||||
open fun setup(): AbstractNode {
|
open fun setup(): AbstractNode {
|
||||||
createNodeDir()
|
createNodeDir()
|
||||||
|
@ -3,7 +3,6 @@ package com.r3corda.node.internal
|
|||||||
import com.codahale.metrics.JmxReporter
|
import com.codahale.metrics.JmxReporter
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import com.r3corda.core.messaging.MessagingService
|
import com.r3corda.core.messaging.MessagingService
|
||||||
import com.r3corda.core.node.CordaPluginRegistry
|
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
import com.r3corda.core.node.ServiceHub
|
import com.r3corda.core.node.ServiceHub
|
||||||
import com.r3corda.core.node.services.ServiceType
|
import com.r3corda.core.node.services.ServiceType
|
||||||
@ -30,7 +29,6 @@ import java.net.InetSocketAddress
|
|||||||
import java.nio.channels.FileLock
|
import java.nio.channels.FileLock
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.util.*
|
|
||||||
import javax.management.ObjectName
|
import javax.management.ObjectName
|
||||||
|
|
||||||
class ConfigurationException(message: String) : Exception(message)
|
class ConfigurationException(message: String) : Exception(message)
|
||||||
@ -44,15 +42,15 @@ class ConfigurationException(message: String) : Exception(message)
|
|||||||
* @param dir A [Path] to a location on disk where working files can be found or stored.
|
* @param dir A [Path] to a location on disk where working files can be found or stored.
|
||||||
* @param p2pAddr The host and port that this server will use. It can't find out its own external hostname, so you
|
* @param p2pAddr The host and port that this server will use. It can't find out its own external hostname, so you
|
||||||
* have to specify that yourself.
|
* have to specify that yourself.
|
||||||
* @param configuration This is typically loaded from a .properties file
|
* @param configuration This is typically loaded from a .properties file.
|
||||||
* @param networkMapAddress An external network map service to use. Should only ever be null when creating the first
|
* @param networkMapAddress An external network map service to use. Should only ever be null when creating the first
|
||||||
* network map service, while bootstrapping a network.
|
* network map service, while bootstrapping a network.
|
||||||
* @param advertisedServices The services this node advertises. This must be a subset of the services it runs,
|
* @param advertisedServices The services this node advertises. This must be a subset of the services it runs,
|
||||||
* but nodes are not required to advertise services they run (hence subset).
|
* but nodes are not required to advertise services they run (hence subset).
|
||||||
* @param clientAPIs A list of JAX-RS annotated classes to register
|
* @param clientAPIs A list of JAX-RS annotated classes to register
|
||||||
* which will be used to register any extra client web interfaces the node requires for demos to use.
|
* which will be used to register any extra client web interfaces the node requires for demos to use.
|
||||||
* Listed clientAPI classes are assumed to have to take a single APIServer constructor parameter
|
* Listed clientAPI classes are assumed to have to take a single APIServer constructor parameter.
|
||||||
* @param clock The clock used within the node and by all protocols etc
|
* @param clock The clock used within the node and by all protocols etc.
|
||||||
*/
|
*/
|
||||||
class Node(dir: Path, val p2pAddr: HostAndPort, val webServerAddr: HostAndPort, configuration: NodeConfiguration,
|
class Node(dir: Path, val p2pAddr: HostAndPort, val webServerAddr: HostAndPort, configuration: NodeConfiguration,
|
||||||
networkMapAddress: NodeInfo?, advertisedServices: Set<ServiceType>,
|
networkMapAddress: NodeInfo?, advertisedServices: Set<ServiceType>,
|
||||||
|
@ -88,7 +88,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
|||||||
// Nothing to do
|
// Nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun generateKeyPair(): KeyPair? = keyPair ?: super.generateKeyPair()
|
override fun generateKeyPair(): KeyPair = keyPair ?: super.generateKeyPair()
|
||||||
|
|
||||||
// It's OK to not have a network map service in the mock network.
|
// It's OK to not have a network map service in the mock network.
|
||||||
override fun noNetworkMapConfigured() = Futures.immediateFuture(Unit)
|
override fun noNetworkMapConfigured() = Futures.immediateFuture(Unit)
|
||||||
|
@ -12,7 +12,7 @@ import javax.annotation.concurrent.ThreadSafe
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [Clock] that can have the time advanced for use in testing
|
* A [Clock] that can have the time advanced for use in testing.
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class TestClock(private var delegateClock: Clock = Clock.systemUTC()) : MutableClock(), SerializeAsToken {
|
class TestClock(private var delegateClock: Clock = Clock.systemUTC()) : MutableClock(), SerializeAsToken {
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
package com.r3corda.node.internal.testing
|
|
||||||
|
|
||||||
import com.r3corda.core.contracts.StateAndRef
|
|
||||||
import com.r3corda.core.contracts.DummyContract
|
|
||||||
import com.r3corda.core.contracts.StateRef
|
|
||||||
import com.r3corda.core.contracts.TransactionState
|
|
||||||
import com.r3corda.core.contracts.TransactionType
|
|
||||||
import com.r3corda.core.crypto.Party
|
|
||||||
import com.r3corda.core.seconds
|
|
||||||
import com.r3corda.core.testing.DUMMY_NOTARY
|
|
||||||
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
|
||||||
import com.r3corda.node.internal.AbstractNode
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
fun issueState(node: AbstractNode): StateAndRef<*> {
|
|
||||||
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), DUMMY_NOTARY)
|
|
||||||
tx.signWith(node.storage.myLegalIdentityKey)
|
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
|
||||||
val stx = tx.toSignedTransaction()
|
|
||||||
node.services.recordTransactions(listOf(stx))
|
|
||||||
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<DummyContract.MultiOwnerState> {
|
|
||||||
val state = TransactionState(DummyContract.MultiOwnerState(0,
|
|
||||||
listOf(nodeA.info.identity.owningKey, nodeB.info.identity.owningKey)), DUMMY_NOTARY)
|
|
||||||
val tx = TransactionType.NotaryChange.Builder().withItems(state)
|
|
||||||
tx.signWith(nodeA.storage.myLegalIdentityKey)
|
|
||||||
tx.signWith(nodeB.storage.myLegalIdentityKey)
|
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
|
||||||
val stx = tx.toSignedTransaction()
|
|
||||||
nodeA.services.recordTransactions(listOf(stx))
|
|
||||||
nodeB.services.recordTransactions(listOf(stx))
|
|
||||||
val stateAndRef = StateAndRef(state, StateRef(stx.id, 0))
|
|
||||||
return stateAndRef
|
|
||||||
}
|
|
||||||
|
|
||||||
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
|
|
||||||
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
|
||||||
tx.setTime(Instant.now(), notary, 30.seconds)
|
|
||||||
tx.signWith(node.storage.myLegalIdentityKey)
|
|
||||||
val stx = tx.toSignedTransaction(false)
|
|
||||||
node.services.recordTransactions(listOf(stx))
|
|
||||||
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
|
||||||
}
|
|
@ -19,9 +19,9 @@ abstract class AbstractNodeService(val net: MessagingService, val networkMapCach
|
|||||||
/**
|
/**
|
||||||
* Register a handler for a message topic. In comparison to using net.addMessageHandler() this manages a lot of
|
* Register a handler for a message topic. In comparison to using net.addMessageHandler() this manages a lot of
|
||||||
* common boilerplate code. Exceptions are caught and passed to the provided consumer. If you just want a simple
|
* common boilerplate code. Exceptions are caught and passed to the provided consumer. If you just want a simple
|
||||||
* acknowledgement response with no content, use [com.r3corda.core.messaging.Ack]
|
* acknowledgement response with no content, use [com.r3corda.core.messaging.Ack].
|
||||||
*
|
*
|
||||||
* @param topic the topic, without the default session ID postfix (".0)
|
* @param topic the topic, without the default session ID postfix (".0).
|
||||||
* @param handler a function to handle the deserialised request and return an optional response (if return type not Unit)
|
* @param handler a function to handle the deserialised request and return an optional response (if return type not Unit)
|
||||||
* @param exceptionConsumer a function to which any thrown exception is passed.
|
* @param exceptionConsumer a function to which any thrown exception is passed.
|
||||||
*/
|
*/
|
||||||
@ -47,10 +47,10 @@ abstract class AbstractNodeService(val net: MessagingService, val networkMapCach
|
|||||||
/**
|
/**
|
||||||
* Register a handler for a message topic. In comparison to using net.addMessageHandler() this manages a lot of
|
* Register a handler for a message topic. In comparison to using net.addMessageHandler() this manages a lot of
|
||||||
* common boilerplate code. Exceptions are propagated to the messaging layer. If you just want a simple
|
* common boilerplate code. Exceptions are propagated to the messaging layer. If you just want a simple
|
||||||
* acknowledgement response with no content, use [com.r3corda.core.messaging.Ack]
|
* acknowledgement response with no content, use [com.r3corda.core.messaging.Ack].
|
||||||
*
|
*
|
||||||
* @param topic the topic, without the default session ID postfix (".0)
|
* @param topic the topic, without the default session ID postfix (".0).
|
||||||
* @param handler a function to handle the deserialised request and return an optional response (if return type not Unit)
|
* @param handler a function to handle the deserialised request and return an optional response (if return type not Unit).
|
||||||
*/
|
*/
|
||||||
protected inline fun <reified Q : ServiceRequestMessage, reified R : Any>
|
protected inline fun <reified Q : ServiceRequestMessage, reified R : Any>
|
||||||
addMessageHandler(topic: String,
|
addMessageHandler(topic: String,
|
||||||
|
@ -16,7 +16,7 @@ abstract class ServiceHubInternal : ServiceHub {
|
|||||||
* sends them to the wallet for further processing. This is intended for implementations to call from
|
* sends them to the wallet for further processing. This is intended for implementations to call from
|
||||||
* [recordTransactions].
|
* [recordTransactions].
|
||||||
*
|
*
|
||||||
* @param txs The transactions to record
|
* @param txs The transactions to record.
|
||||||
*/
|
*/
|
||||||
internal fun recordTransactionsInternal(writableStorageService: TxWritableStorageService, txs: Iterable<SignedTransaction>) {
|
internal fun recordTransactionsInternal(writableStorageService: TxWritableStorageService, txs: Iterable<SignedTransaction>) {
|
||||||
txs.forEach { writableStorageService.validatedTransactions.addTransaction(it) }
|
txs.forEach { writableStorageService.validatedTransactions.addTransaction(it) }
|
||||||
@ -26,7 +26,7 @@ abstract class ServiceHubInternal : ServiceHub {
|
|||||||
/**
|
/**
|
||||||
* TODO: borrowing this method from service manager work in another branch. It's required to avoid circular dependency
|
* TODO: borrowing this method from service manager work in another branch. It's required to avoid circular dependency
|
||||||
* between SMM and the scheduler. That particular problem should also be resolved by the service manager work
|
* between SMM and the scheduler. That particular problem should also be resolved by the service manager work
|
||||||
* itself, at which point this method would not be needed (by the scheduler)
|
* itself, at which point this method would not be needed (by the scheduler).
|
||||||
*/
|
*/
|
||||||
abstract fun <T> startProtocol(loggerName: String, logic: ProtocolLogic<T>): ListenableFuture<T>
|
abstract fun <T> startProtocol(loggerName: String, logic: ProtocolLogic<T>): ListenableFuture<T>
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ object NodeInterestRates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the protocol that is used with the Fixing integration tests
|
* Register the protocol that is used with the Fixing integration tests.
|
||||||
*/
|
*/
|
||||||
class FixingServicePlugin : CordaPluginRegistry {
|
class FixingServicePlugin : CordaPluginRegistry {
|
||||||
override val webApis: List<Class<*>> = emptyList()
|
override val webApis: List<Class<*>> = emptyList()
|
||||||
@ -252,7 +252,7 @@ object NodeInterestRates {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the interest rate for a given [Tenor],
|
* Returns the interest rate for a given [Tenor],
|
||||||
* or _null_ if the rate is not found and cannot be interpolated
|
* or _null_ if the rate is not found and cannot be interpolated.
|
||||||
*/
|
*/
|
||||||
fun getRate(tenor: Tenor): BigDecimal? {
|
fun getRate(tenor: Tenor): BigDecimal? {
|
||||||
return rates.getOrElse(tenor) {
|
return rates.getOrElse(tenor) {
|
||||||
|
@ -17,9 +17,9 @@ import javax.annotation.concurrent.ThreadSafe
|
|||||||
* on a separate/firewalled service.
|
* on a separate/firewalled service.
|
||||||
* - Use the protocol framework so requests to fetch keys can be suspended whilst a human signs off on the request.
|
* - Use the protocol framework so requests to fetch keys can be suspended whilst a human signs off on the request.
|
||||||
* - Use deterministic key derivation.
|
* - Use deterministic key derivation.
|
||||||
* - Possibly have some sort of TREZOR-like two-factor authentication ability
|
* - Possibly have some sort of TREZOR-like two-factor authentication ability.
|
||||||
*
|
*
|
||||||
* etc
|
* etc.
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class E2ETestKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService {
|
class E2ETestKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService {
|
||||||
|
@ -85,9 +85,9 @@ class InMemoryMessagingNetwork(val sendManuallyPumped: Boolean) : SingletonSeria
|
|||||||
/**
|
/**
|
||||||
* Creates a node at the given address: useful if you want to recreate a node to simulate a restart.
|
* Creates a node at the given address: useful if you want to recreate a node to simulate a restart.
|
||||||
*
|
*
|
||||||
* @param manuallyPumped see [createNode]
|
* @param manuallyPumped see [createNode].
|
||||||
* @param id the numeric ID to use, e.g. set to whatever ID the node used last time.
|
* @param id the numeric ID to use, e.g. set to whatever ID the node used last time.
|
||||||
* @param description text string that identifies this node for message logging (if is enabled) or null to autogenerate
|
* @param description text string that identifies this node for message logging (if is enabled) or null to autogenerate.
|
||||||
*/
|
*/
|
||||||
fun createNodeWithID(manuallyPumped: Boolean, id: Int, description: String? = null): MessagingServiceBuilder<InMemoryMessaging> {
|
fun createNodeWithID(manuallyPumped: Boolean, id: Int, description: String? = null): MessagingServiceBuilder<InMemoryMessaging> {
|
||||||
return Builder(manuallyPumped, Handle(id, description ?: "In memory node $id"))
|
return Builder(manuallyPumped, Handle(id, description ?: "In memory node $id"))
|
||||||
|
@ -8,16 +8,12 @@ import com.google.common.io.CountingInputStream
|
|||||||
import com.r3corda.core.contracts.Attachment
|
import com.r3corda.core.contracts.Attachment
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.extractZipFile
|
import com.r3corda.core.extractZipFile
|
||||||
import com.r3corda.node.services.api.AcceptsFileUpload
|
|
||||||
import com.r3corda.core.node.services.AttachmentStorage
|
import com.r3corda.core.node.services.AttachmentStorage
|
||||||
import com.r3corda.core.utilities.loggerFor
|
import com.r3corda.core.utilities.loggerFor
|
||||||
|
import com.r3corda.node.services.api.AcceptsFileUpload
|
||||||
import java.io.FilterInputStream
|
import java.io.FilterInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.file.FileAlreadyExistsException
|
import java.nio.file.*
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.nio.file.StandardCopyOption
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.jar.JarInputStream
|
import java.util.jar.JarInputStream
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
|
@ -2,7 +2,6 @@ package com.r3corda.node.services.persistence
|
|||||||
|
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.node.services.AttachmentStorage
|
import com.r3corda.core.node.services.AttachmentStorage
|
||||||
import com.r3corda.core.node.services.StorageService
|
|
||||||
import com.r3corda.core.node.services.TransactionStorage
|
import com.r3corda.core.node.services.TransactionStorage
|
||||||
import com.r3corda.core.node.services.TxWritableStorageService
|
import com.r3corda.core.node.services.TxWritableStorageService
|
||||||
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
@ -12,12 +12,12 @@ import com.r3corda.protocols.NotaryProtocol
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A Notary service acts as the final signer of a transaction ensuring two things:
|
* A Notary service acts as the final signer of a transaction ensuring two things:
|
||||||
* - The (optional) timestamp of the transaction is valid
|
* - The (optional) timestamp of the transaction is valid.
|
||||||
* - None of the referenced input states have previously been consumed by a transaction signed by this Notary
|
* - None of the referenced input states have previously been consumed by a transaction signed by this Notary
|
||||||
*
|
*
|
||||||
* A transaction has to be signed by a Notary to be considered valid (except for output-only transactions without a timestamp).
|
* A transaction has to be signed by a Notary to be considered valid (except for output-only transactions without a timestamp).
|
||||||
*
|
*
|
||||||
* This is the base implementation that can be customised with specific Notary transaction commit protocol
|
* This is the base implementation that can be customised with specific Notary transaction commit protocol.
|
||||||
*/
|
*/
|
||||||
abstract class NotaryService(val smm: StateMachineManager,
|
abstract class NotaryService(val smm: StateMachineManager,
|
||||||
net: MessagingService,
|
net: MessagingService,
|
||||||
|
@ -8,7 +8,7 @@ import javax.ws.rs.ext.Provider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary purpose is to install Kotlin extensions for Jackson ObjectMapper so data classes work
|
* Primary purpose is to install Kotlin extensions for Jackson ObjectMapper so data classes work
|
||||||
* and to organise serializers / deserializers for java.time.* classes as necessary
|
* and to organise serializers / deserializers for java.time.* classes as necessary.
|
||||||
*/
|
*/
|
||||||
@Provider
|
@Provider
|
||||||
class Config(val services: ServiceHub) : ContextResolver<ObjectMapper> {
|
class Config(val services: ServiceHub) : ContextResolver<ObjectMapper> {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.r3corda.node.servlets
|
package com.r3corda.node.servlets
|
||||||
|
|
||||||
import com.r3corda.node.services.api.AcceptsFileUpload
|
|
||||||
import com.r3corda.node.internal.Node
|
|
||||||
import com.r3corda.core.utilities.loggerFor
|
import com.r3corda.core.utilities.loggerFor
|
||||||
|
import com.r3corda.node.internal.Node
|
||||||
|
import com.r3corda.node.services.api.AcceptsFileUpload
|
||||||
import org.apache.commons.fileupload.servlet.ServletFileUpload
|
import org.apache.commons.fileupload.servlet.ServletFileUpload
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.servlet.http.HttpServlet
|
import javax.servlet.http.HttpServlet
|
||||||
|
@ -6,7 +6,7 @@ import javax.ws.rs.container.ContainerResponseFilter
|
|||||||
import javax.ws.rs.ext.Provider
|
import javax.ws.rs.ext.Provider
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This adds headers needed for cross site scripting on API clients
|
* This adds headers needed for cross site scripting on API clients.
|
||||||
*/
|
*/
|
||||||
@Provider
|
@Provider
|
||||||
class ResponseFilter : ContainerResponseFilter {
|
class ResponseFilter : ContainerResponseFilter {
|
||||||
@ -18,7 +18,7 @@ class ResponseFilter : ContainerResponseFilter {
|
|||||||
*
|
*
|
||||||
* We don't want this scriptable from any web page anywhere, but for demo reasons
|
* We don't want this scriptable from any web page anywhere, but for demo reasons
|
||||||
* we're making this really easy to access pending a proper security approach including
|
* we're making this really easy to access pending a proper security approach including
|
||||||
* access control and authentication at a network and software level
|
* access control and authentication at a network and software level.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
headers.add("Access-Control-Allow-Origin", "*")
|
headers.add("Access-Control-Allow-Origin", "*")
|
||||||
|
@ -49,7 +49,7 @@ abstract class MutableClock : Clock() {
|
|||||||
*/
|
*/
|
||||||
val mutations: Observable<Long> by lazy {
|
val mutations: Observable<Long> by lazy {
|
||||||
Observable.create({ subscriber: Subscriber<in Long> ->
|
Observable.create({ subscriber: Subscriber<in Long> ->
|
||||||
if (!subscriber.isUnsubscribed()) {
|
if (!subscriber.isUnsubscribed) {
|
||||||
mutationObservers.add(subscriber)
|
mutationObservers.add(subscriber)
|
||||||
// This is not very intuitive, but subscribing to a subscriber observes unsubscribes.
|
// This is not very intuitive, but subscribing to a subscriber observes unsubscribes.
|
||||||
subscriber.add(Subscriptions.create { mutationObservers.remove(subscriber) })
|
subscriber.add(Subscriptions.create { mutationObservers.remove(subscriber) })
|
||||||
@ -77,7 +77,7 @@ abstract class MutableClock : Clock() {
|
|||||||
* of it being mutated. Just returns if [MutableClock] mutates, so needs to be
|
* of it being mutated. Just returns if [MutableClock] mutates, so needs to be
|
||||||
* called in a loop.
|
* called in a loop.
|
||||||
*
|
*
|
||||||
* @throws InterruptedException if interrupted by something other than a [MutableClock]
|
* @throws InterruptedException if interrupted by something other than a [MutableClock].
|
||||||
*/
|
*/
|
||||||
@Suppress("UNUSED_VALUE") // This is here due to the compiler thinking version is not used
|
@Suppress("UNUSED_VALUE") // This is here due to the compiler thinking version is not used
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -107,9 +107,9 @@ private fun Clock.doInterruptibly(runnable: SuspendableRunnable) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait until the given [Future] is complete or the deadline is reached, with support for [MutableClock] implementations
|
* Wait until the given [Future] is complete or the deadline is reached, with support for [MutableClock] implementations
|
||||||
* used in demos or testing. This will also substitute a Fiber compatible Future if required
|
* used in demos or testing. This will also substitute a Fiber compatible Future if required.
|
||||||
*
|
*
|
||||||
* @return true if the [Future] is complete, false if the deadline was reached
|
* @return true if the [Future] is complete, false if the deadline was reached.
|
||||||
*/
|
*/
|
||||||
@Suspendable
|
@Suspendable
|
||||||
fun Clock.awaitWithDeadline(deadline: Instant, future: Future<*> = SettableFuture<Any>()): Boolean {
|
fun Clock.awaitWithDeadline(deadline: Instant, future: Future<*> = SettableFuture<Any>()): Boolean {
|
||||||
|
@ -12,7 +12,7 @@ import kotlin.concurrent.withLock
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modelled on [ThreadBox], but with support for waiting that is compatible with Quasar [Fiber]s and [MutableClock]s
|
* Modelled on [ThreadBox], but with support for waiting that is compatible with Quasar [Fiber]s and [MutableClock]s.
|
||||||
*
|
*
|
||||||
* It supports 3 main operations, all of which operate in a similar context to the [locked] method
|
* It supports 3 main operations, all of which operate in a similar context to the [locked] method
|
||||||
* of [ThreadBox]. i.e. in the context of the content.
|
* of [ThreadBox]. i.e. in the context of the content.
|
||||||
|
@ -29,7 +29,7 @@ object JsonSupport {
|
|||||||
|
|
||||||
fun createDefaultMapper(identities: IdentityService): ObjectMapper {
|
fun createDefaultMapper(identities: IdentityService): ObjectMapper {
|
||||||
val mapper = ServiceHubObjectMapper(identities)
|
val mapper = ServiceHubObjectMapper(identities)
|
||||||
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
mapper.enable(SerializationFeature.INDENT_OUTPUT)
|
||||||
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
||||||
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
|
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ object JsonSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implemented as a class so that we can instantiate for T
|
* Implemented as a class so that we can instantiate for T.
|
||||||
*/
|
*/
|
||||||
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
|
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
|
||||||
|
@ -4,8 +4,6 @@ package com.r3corda.node.messaging
|
|||||||
|
|
||||||
import com.r3corda.core.messaging.Message
|
import com.r3corda.core.messaging.Message
|
||||||
import com.r3corda.core.messaging.TopicStringValidator
|
import com.r3corda.core.messaging.TopicStringValidator
|
||||||
import com.r3corda.core.messaging.send
|
|
||||||
import com.r3corda.core.serialization.deserialize
|
|
||||||
import com.r3corda.node.internal.testing.MockNetwork
|
import com.r3corda.node.internal.testing.MockNetwork
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -2,10 +2,10 @@ package com.r3corda.node.messaging
|
|||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.r3corda.contracts.CommercialPaper
|
import com.r3corda.contracts.CommercialPaper
|
||||||
|
import com.r3corda.contracts.asset.CASH
|
||||||
import com.r3corda.contracts.asset.Cash
|
import com.r3corda.contracts.asset.Cash
|
||||||
import com.r3corda.contracts.testing.CASH
|
import com.r3corda.contracts.asset.`issued by`
|
||||||
import com.r3corda.contracts.testing.`issued by`
|
import com.r3corda.contracts.asset.`owned by`
|
||||||
import com.r3corda.contracts.testing.`owned by`
|
|
||||||
import com.r3corda.contracts.testing.fillWithSomeTestCash
|
import com.r3corda.contracts.testing.fillWithSomeTestCash
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
@ -17,7 +17,6 @@ import com.r3corda.core.node.services.ServiceType
|
|||||||
import com.r3corda.core.node.services.TransactionStorage
|
import com.r3corda.core.node.services.TransactionStorage
|
||||||
import com.r3corda.core.node.services.Wallet
|
import com.r3corda.core.node.services.Wallet
|
||||||
import com.r3corda.core.random63BitValue
|
import com.r3corda.core.random63BitValue
|
||||||
import com.r3corda.core.seconds
|
|
||||||
import com.r3corda.core.testing.*
|
import com.r3corda.core.testing.*
|
||||||
import com.r3corda.core.utilities.BriefLogFormatter
|
import com.r3corda.core.utilities.BriefLogFormatter
|
||||||
import com.r3corda.node.internal.testing.MockNetwork
|
import com.r3corda.node.internal.testing.MockNetwork
|
||||||
@ -366,7 +365,7 @@ class TwoPartyTradeProtocolTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError(
|
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError(
|
||||||
bobError: Boolean,
|
bobError: Boolean,
|
||||||
aliceError: Boolean,
|
aliceError: Boolean,
|
||||||
expectedMessageSubstring: String
|
expectedMessageSubstring: String
|
||||||
@ -431,7 +430,7 @@ class TwoPartyTradeProtocolTests {
|
|||||||
return signed.associateBy { it.id }
|
return signed.associateBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
|
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
|
||||||
withError: Boolean,
|
withError: Boolean,
|
||||||
owner: PublicKey = BOB_PUBKEY,
|
owner: PublicKey = BOB_PUBKEY,
|
||||||
issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair<Wallet, List<WireTransaction>> {
|
issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair<Wallet, List<WireTransaction>> {
|
||||||
@ -472,7 +471,7 @@ class TwoPartyTradeProtocolTests {
|
|||||||
return Pair(wallet, listOf(eb1, bc1, bc2))
|
return Pair(wallet, listOf(eb1, bc1, bc2))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
|
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
|
||||||
withError: Boolean,
|
withError: Boolean,
|
||||||
owner: PublicKey,
|
owner: PublicKey,
|
||||||
amount: Amount<Issued<Currency>>,
|
amount: Amount<Issued<Currency>>,
|
||||||
@ -482,9 +481,9 @@ class TwoPartyTradeProtocolTests {
|
|||||||
output("alice's paper") {
|
output("alice's paper") {
|
||||||
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days)
|
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days)
|
||||||
}
|
}
|
||||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue(notary) }
|
||||||
if (!withError)
|
if (!withError)
|
||||||
command(notary.owningKey) { TimestampCommand(TEST_TX_TIME, 30.seconds) }
|
timestamp(time = TEST_TX_TIME, notary = notary.owningKey)
|
||||||
if (attachmentID != null)
|
if (attachmentID != null)
|
||||||
attachment(attachmentID)
|
attachment(attachmentID)
|
||||||
if (withError) {
|
if (withError) {
|
||||||
|
@ -9,11 +9,7 @@ import com.r3corda.node.services.persistence.NodeAttachmentService
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.nio.file.FileAlreadyExistsException
|
import java.nio.file.*
|
||||||
import java.nio.file.FileSystem
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.StandardOpenOption
|
|
||||||
import java.util.jar.JarEntry
|
import java.util.jar.JarEntry
|
||||||
import java.util.jar.JarOutputStream
|
import java.util.jar.JarOutputStream
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package com.r3corda.node.services
|
package com.r3corda.node.services
|
||||||
|
|
||||||
|
import com.r3corda.contracts.asset.CASH
|
||||||
import com.r3corda.contracts.asset.Cash
|
import com.r3corda.contracts.asset.Cash
|
||||||
import com.r3corda.contracts.testing.CASH
|
import com.r3corda.contracts.asset.`issued by`
|
||||||
import com.r3corda.contracts.testing.`issued by`
|
import com.r3corda.contracts.asset.`owned by`
|
||||||
import com.r3corda.contracts.testing.`owned by`
|
|
||||||
import com.r3corda.contracts.testing.`with notary`
|
|
||||||
import com.r3corda.core.bd
|
import com.r3corda.core.bd
|
||||||
import com.r3corda.core.contracts.DOLLARS
|
import com.r3corda.core.contracts.DOLLARS
|
||||||
import com.r3corda.core.contracts.Fix
|
import com.r3corda.core.contracts.Fix
|
||||||
import com.r3corda.core.contracts.TransactionType
|
import com.r3corda.core.contracts.TransactionType
|
||||||
|
import com.r3corda.core.contracts.`with notary`
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.generateKeyPair
|
import com.r3corda.core.crypto.generateKeyPair
|
||||||
import com.r3corda.core.testing.ALICE_PUBKEY
|
import com.r3corda.core.testing.ALICE_PUBKEY
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package com.r3corda.node.services
|
package com.r3corda.node.services
|
||||||
|
|
||||||
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.generateKeyPair
|
import com.r3corda.core.crypto.generateKeyPair
|
||||||
|
import com.r3corda.core.seconds
|
||||||
import com.r3corda.core.testing.DUMMY_NOTARY
|
import com.r3corda.core.testing.DUMMY_NOTARY
|
||||||
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
||||||
|
import com.r3corda.node.internal.AbstractNode
|
||||||
import com.r3corda.node.internal.testing.MockNetwork
|
import com.r3corda.node.internal.testing.MockNetwork
|
||||||
import com.r3corda.node.internal.testing.issueMultiPartyState
|
|
||||||
import com.r3corda.node.internal.testing.issueState
|
|
||||||
import com.r3corda.node.services.network.NetworkMapService
|
import com.r3corda.node.services.network.NetworkMapService
|
||||||
import com.r3corda.node.services.transactions.SimpleNotaryService
|
import com.r3corda.node.services.transactions.SimpleNotaryService
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -15,6 +16,8 @@ import protocols.NotaryChangeProtocol
|
|||||||
import protocols.NotaryChangeProtocol.Instigator
|
import protocols.NotaryChangeProtocol.Instigator
|
||||||
import protocols.StateReplacementException
|
import protocols.StateReplacementException
|
||||||
import protocols.StateReplacementRefused
|
import protocols.StateReplacementRefused
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
@ -91,3 +94,35 @@ class NotaryChangeTests {
|
|||||||
// - Multiple states in a single "notary change" transaction
|
// - Multiple states in a single "notary change" transaction
|
||||||
// - Transaction contains additional states and commands with business logic
|
// - Transaction contains additional states and commands with business logic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun issueState(node: AbstractNode): StateAndRef<*> {
|
||||||
|
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), DUMMY_NOTARY)
|
||||||
|
tx.signWith(node.storage.myLegalIdentityKey)
|
||||||
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
|
val stx = tx.toSignedTransaction()
|
||||||
|
node.services.recordTransactions(listOf(stx))
|
||||||
|
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<DummyContract.MultiOwnerState> {
|
||||||
|
val state = TransactionState(DummyContract.MultiOwnerState(0,
|
||||||
|
listOf(nodeA.info.identity.owningKey, nodeB.info.identity.owningKey)), DUMMY_NOTARY)
|
||||||
|
val tx = TransactionType.NotaryChange.Builder().withItems(state)
|
||||||
|
tx.signWith(nodeA.storage.myLegalIdentityKey)
|
||||||
|
tx.signWith(nodeB.storage.myLegalIdentityKey)
|
||||||
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
|
val stx = tx.toSignedTransaction()
|
||||||
|
nodeA.services.recordTransactions(listOf(stx))
|
||||||
|
nodeB.services.recordTransactions(listOf(stx))
|
||||||
|
val stateAndRef = StateAndRef(state, StateRef(stx.id, 0))
|
||||||
|
return stateAndRef
|
||||||
|
}
|
||||||
|
|
||||||
|
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
|
||||||
|
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
||||||
|
tx.setTime(Instant.now(), notary, 30.seconds)
|
||||||
|
tx.signWith(node.storage.myLegalIdentityKey)
|
||||||
|
val stx = tx.toSignedTransaction(false)
|
||||||
|
node.services.recordTransactions(listOf(stx))
|
||||||
|
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
||||||
|
}
|
||||||
|
@ -7,7 +7,6 @@ import com.r3corda.core.testing.DUMMY_NOTARY
|
|||||||
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
||||||
import com.r3corda.core.testing.MINI_CORP_KEY
|
import com.r3corda.core.testing.MINI_CORP_KEY
|
||||||
import com.r3corda.node.internal.testing.MockNetwork
|
import com.r3corda.node.internal.testing.MockNetwork
|
||||||
import com.r3corda.node.internal.testing.issueState
|
|
||||||
import com.r3corda.node.services.network.NetworkMapService
|
import com.r3corda.node.services.network.NetworkMapService
|
||||||
import com.r3corda.node.services.transactions.SimpleNotaryService
|
import com.r3corda.node.services.transactions.SimpleNotaryService
|
||||||
import com.r3corda.protocols.NotaryError
|
import com.r3corda.protocols.NotaryError
|
||||||
|
@ -8,8 +8,6 @@ import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
|||||||
import com.r3corda.core.testing.MEGA_CORP_KEY
|
import com.r3corda.core.testing.MEGA_CORP_KEY
|
||||||
import com.r3corda.core.testing.MINI_CORP_KEY
|
import com.r3corda.core.testing.MINI_CORP_KEY
|
||||||
import com.r3corda.node.internal.testing.MockNetwork
|
import com.r3corda.node.internal.testing.MockNetwork
|
||||||
import com.r3corda.node.internal.testing.issueInvalidState
|
|
||||||
import com.r3corda.node.internal.testing.issueState
|
|
||||||
import com.r3corda.node.services.network.NetworkMapService
|
import com.r3corda.node.services.network.NetworkMapService
|
||||||
import com.r3corda.node.services.transactions.ValidatingNotaryService
|
import com.r3corda.node.services.transactions.ValidatingNotaryService
|
||||||
import com.r3corda.protocols.NotaryError
|
import com.r3corda.protocols.NotaryError
|
||||||
|
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