Merge branch 'master' into sofus-generic-contract

This commit is contained in:
sofusmortensen 2016-07-12 07:47:43 +02:00
commit 7b241b6b12
113 changed files with 1589 additions and 933 deletions

5
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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