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
.classpath
.project
@ -88,3 +86,6 @@ atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
# docs related
docs/virtualenv/

View File

@ -1,11 +1,12 @@
import com.google.common.io.ByteStreams
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.nio.file.attribute.FileTime
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
buildscript {
repositories {

View File

@ -1,9 +1,9 @@
package com.r3corda.core.node
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.crypto.Party
import com.r3corda.core.contracts.PartyAndReference
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.Party
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder

View File

@ -1,13 +1,10 @@
package com.r3corda.contracts;
import com.r3corda.core.contracts.Amount;
import com.r3corda.core.contracts.ContractState;
import com.r3corda.core.contracts.PartyAndReference;
import com.r3corda.core.contracts.Issued;
import com.r3corda.core.contracts.*;
import java.security.*;
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
* either a Java implementation of the CommercialPaper or a kotlin implementation.

View File

@ -1,24 +1,18 @@
package com.r3corda.contracts;
import com.google.common.collect.ImmutableList;
import com.r3corda.contracts.asset.Cash;
import com.r3corda.contracts.asset.CashKt;
import com.r3corda.contracts.asset.InsufficientBalanceException;
import com.google.common.collect.*;
import com.r3corda.contracts.asset.*;
import com.r3corda.core.contracts.*;
import com.r3corda.core.contracts.TransactionForContract.InOutGroup;
import com.r3corda.core.crypto.NullPublicKey;
import com.r3corda.core.crypto.Party;
import com.r3corda.core.crypto.SecureHash;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.r3corda.core.contracts.TransactionForContract.*;
import com.r3corda.core.crypto.*;
import org.jetbrains.annotations.*;
import java.security.PublicKey;
import java.time.Instant;
import java.util.Currency;
import java.util.List;
import java.security.*;
import java.time.*;
import java.util.*;
import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand;
import static kotlin.collections.CollectionsKt.single;
import static com.r3corda.core.contracts.ContractsDSL.*;
import static kotlin.collections.CollectionsKt.*;
/**
@ -131,6 +125,12 @@ public class JavaCommercialPaper implements Contract {
}
public static class Redeem extends Commands {
private final Party notary;
public Redeem(Party setNotary) {
this.notary = setNotary;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Redeem;
@ -138,6 +138,12 @@ public class JavaCommercialPaper implements Contract {
}
public static class Issue extends Commands {
private final Party notary;
public Issue(Party setNotary) {
this.notary = setNotary;
}
@Override
public boolean equals(Object obj) {
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.
if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Issue) {
Commands.Issue issueCommand = (Commands.Issue) cmd.getValue();
State output = single(outputs);
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) {
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)
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())) {
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 {
// 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()))
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) {
TimestampCommand timestampCommand = tx.getTimestampByName("Notary Service");
TimestampCommand timestampCommand = tx.getTimestampBy(((Commands.Redeem) cmd.getValue()).notary);
if (timestampCommand == null)
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
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) {
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
TransactionState output = new TransactionState<>(state, notary);
return new TransactionType.General.Builder().withItems(output, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
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 {
new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet);
tx.addInputState(paper);
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner()));
tx.addCommand(new Command(new Commands.Redeem(paper.getState().getNotary()), paper.getState().getData().getOwner()));
}
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {

View File

@ -66,10 +66,10 @@ class CommercialPaper : Contract {
interface Commands : CommandData {
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.
// 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) {
@ -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
// it for cash on or after the maturity date.
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
// Here, we match acceptable timestamp authorities by name. The list of acceptable TSAs (oracles) must be
// hard coded into the contract because otherwise we could fail to gain consensus, if nodes disagree about
// who or what is a trusted authority.
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
// If it's an issue, we can't take notary from inputs, so it must be specified in the command
val cmdVal = command.value
val timestamp: TimestampCommand? = when (cmdVal) {
is Commands.Issue -> tx.getTimestampBy(cmdVal.notary)
is Commands.Redeem -> tx.getTimestampBy(cmdVal.notary)
else -> null
}
for ((inputs, outputs, key) in groups) {
when (command.value) {
@ -115,13 +117,13 @@ class CommercialPaper : Contract {
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
requireThat {
// 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)
"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)
// 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.
"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 {
val issuance = faceValue.token.issuer
val state = TransactionState(State(issuance, issuance.party.owningKey, faceValue, maturityDate), notary)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
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
* 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)
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) }
Cash().generateSpend(tx, amount, paper.state.data.owner, wallet)
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.
*/
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?
*/
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"
/**
* Used for making immutables
* Used for making immutables.
*/
fun withNewRate(newRate: Rate): FloatingRatePaymentEvent =
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)
* This contract has 4 significant data classes within it, the "Common", "Calculation", "FixedLeg" and "FloatingLeg"
* 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".
* 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).
* 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
* 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(
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 =
floatingLegPaymentSchedule.values.single { it.fixingDate == date }
@ -496,6 +496,9 @@ class InterestRateSwap() : Contract {
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
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
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(
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 {

View File

@ -43,7 +43,7 @@ open class PercentageRatioUnit(percentageAsString: String) : RatioUnit(BigDecima
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) {
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)

View File

@ -1,10 +1,7 @@
package com.r3corda.contracts.asset
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.newSecureRandom
import com.r3corda.core.crypto.toStringShort
import com.r3corda.core.crypto.*
import com.r3corda.core.node.services.Wallet
import com.r3corda.core.utilities.Emoji
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.
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
@ -41,7 +37,7 @@ class Cash : FungibleAsset<Currency>() {
* 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
* 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")
@ -242,3 +238,24 @@ val Wallet.cashBalances: Map<Currency, Amount<Currency>> get() = states.
groupBy { it.token.product }.
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
mapValues { it.value.map { Amount(it.quantity, it.token.product) }.sumOrThrow() }
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.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.SecureRandom
import java.util.*
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -27,7 +23,7 @@ class InsufficientBalanceException(val amountMissing: Amount<Currency>) : Except
* 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
* (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 {
/** 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>()
requireThat {
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
"output deposits are owned by a command signer" by (issuer in issueCommand.signingParties)
"output states are issued by a command signer" by (issuer in issueCommand.signingParties)
"output values sum to more than the inputs" by (outputAmount > inputAmount)
"there is only a single issue command" by (assetCommands.count() == 1)
}

View File

@ -1,7 +1,6 @@
package com.r3corda.contracts.asset
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.OwnableState
import java.security.PublicKey

View File

@ -1,15 +1,18 @@
package com.r3corda.contracts.asset
import com.google.common.annotations.VisibleForTesting
import com.r3corda.contracts.asset.FungibleAssetState
import com.r3corda.contracts.asset.sumFungibleOrNull
import com.r3corda.contracts.asset.Obligation.Lifecycle.NORMAL
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.NullPublicKey
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.toStringShort
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.NonEmptySet
import com.r3corda.core.utilities.nonEmptySetOf
import java.security.PublicKey
import java.time.Duration
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
* legalContractReference and a state object which directly references both. The latter allows the legal wording
* to evolve without requiring code changes. But creates a risk that users create objects governed by a program
* that is inconsistent with the legal contract
* 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")
@ -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
* 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>,
val amount: Amount<P>) : Commands, IssuanceCommands<P>
@ -390,9 +393,7 @@ class Obligation<P> : Contract {
for ((stateIdx, input) in inputs.withIndex()) {
val actualOutput = outputs[stateIdx]
val deadline = input.dueBefore
// TODO: Determining correct timestamp authority needs rework now that timestamping service is part of
// notary.
val timestamp: TimestampCommand? = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
val timestamp: TimestampCommand? = tx.timestamp
val expectedOutput: State<P> = input.copy(lifecycle = expectedOutputLifecycle)
requireThat {
@ -422,7 +423,7 @@ class Obligation<P> : Contract {
val outputAmount: Amount<P> = outputs.sumObligations<P>()
requireThat {
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
"output deposits are owned by a command signer" by (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)
"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>
= 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.
*
* @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>,
notary: Party = DUMMY_NOTARY,

View File

@ -1,20 +1,18 @@
package com.r3corda.contracts.asset;
import com.r3corda.core.contracts.PartyAndReference;
import com.r3corda.core.serialization.OpaqueBytes;
import kotlin.Unit;
import org.junit.Test;
import com.r3corda.core.contracts.*;
import com.r3corda.core.serialization.*;
import kotlin.*;
import org.junit.*;
import static com.r3corda.core.testing.JavaTestHelpers.*;
import static com.r3corda.core.contracts.JavaTestHelpers.*;
import static com.r3corda.contracts.testing.JavaTestHelpers.*;
import static com.r3corda.core.contracts.ContractsDSL.*;
import static com.r3corda.core.testing.CoreTestUtils.*;
/**
* This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL
*/
public class CashTestsJava {
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});;
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});
private PartyAndReference defaultIssuer = getMEGA_CORP().ref(defaultRef);
private Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), getDUMMY_PUBKEY_1());
private Cash.State outState = new Cash.State(inState.getAmount(), getDUMMY_PUBKEY_2());
@ -43,7 +41,9 @@ public class CashTestsJava {
});
tx.tweak(tw -> {
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());
return tw.failsWith("at least one asset input");
});

View File

@ -1,8 +1,8 @@
package com.r3corda.contracts
import com.r3corda.contracts.asset.Cash
import com.r3corda.contracts.testing.*
import com.r3corda.contracts.asset.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.days
import com.r3corda.core.node.services.testing.MockStorageService
@ -18,8 +18,8 @@ import kotlin.test.assertTrue
interface ICommercialPaperTestTemplate {
fun getPaper(): ICommercialPaperState
fun getIssueCommand(): CommandData
fun getRedeemCommand(): CommandData
fun getIssueCommand(notary: Party): CommandData
fun getRedeemCommand(notary: Party): CommandData
fun getMoveCommand(): CommandData
}
@ -31,8 +31,8 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
TEST_TX_TIME + 7.days
)
override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue()
override fun getRedeemCommand(): CommandData = JavaCommercialPaper.Commands.Redeem()
override fun getIssueCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Issue(notary)
override fun getRedeemCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Redeem(notary)
override fun getMoveCommand(): CommandData = JavaCommercialPaper.Commands.Move()
}
@ -44,8 +44,8 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
maturityDate = TEST_TX_TIME + 7.days
)
override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue()
override fun getRedeemCommand(): CommandData = CommercialPaper.Commands.Redeem()
override fun getIssueCommand(notary: Party): CommandData = CommercialPaper.Commands.Issue(notary)
override fun getRedeemCommand(notary: Party): CommandData = CommercialPaper.Commands.Redeem(notary)
override fun getMoveCommand(): CommandData = CommercialPaper.Commands.Move()
}
@ -74,7 +74,7 @@ class CommercialPaperTestsGeneric {
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output("paper") { thisTest.getPaper() }
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
timestamp(TEST_TX_TIME)
this.verifies()
}
@ -85,7 +85,7 @@ class CommercialPaperTestsGeneric {
input("paper")
input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output("alice's paper") { "paper".output<ICommercialPaperState>().data `owned by` ALICE_PUBKEY }
output("alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
this.verifies()
@ -97,13 +97,13 @@ class CommercialPaperTestsGeneric {
input("alice's paper")
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("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
}
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
command(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
command(ALICE_PUBKEY) { thisTest.getRedeemCommand(DUMMY_NOTARY) }
tweak {
outputs(700.DOLLARS `issued by` issuer)
@ -120,7 +120,7 @@ class CommercialPaperTestsGeneric {
timestamp(TEST_TX_TIME + 8.days)
tweak {
output { "paper".output<ICommercialPaperState>().data }
output { "paper".output<ICommercialPaperState>() }
this `fails with` "must be destroyed"
}
@ -133,9 +133,9 @@ class CommercialPaperTestsGeneric {
fun `key mismatch at issue`() {
transaction {
output { thisTest.getPaper() }
command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() }
command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand(DUMMY_NOTARY) }
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`() {
transaction {
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)
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`() {
transaction {
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)
this `fails with` "maturity date is not in the past"
}
@ -164,13 +164,13 @@ class CommercialPaperTestsGeneric {
transaction {
input(thisTest.getPaper())
output { thisTest.getPaper() }
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
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())
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 {
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
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
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
fun generateIRSandFixSome() {
@ -360,7 +360,7 @@ class IRSTests {
/**
* 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 bd = BigDecimal("0.0063518")
@ -377,11 +377,11 @@ class IRSTests {
input("irs post agreement")
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
output("irs post first fixing") {
postAgreement.data.copy(
postAgreement.data.fixedLeg,
postAgreement.data.floatingLeg,
postAgreement.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
postAgreement.data.common
postAgreement.copy(
postAgreement.fixedLeg,
postAgreement.floatingLeg,
postAgreement.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
postAgreement.common
)
}
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
fun `ensure same currency notionals`() {
@ -653,7 +653,7 @@ class IRSTests {
* result and the grouping won't work either.
* In reality, the only fields that should be in common will be the next fixing date and the reference rate.
*/
fun tradegroups(): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
fun tradegroups(): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
val ld1 = LocalDate.of(2016, 3, 8)
val bd1 = BigDecimal("0.0063518")
@ -693,20 +693,20 @@ class IRSTests {
input("irs post agreement2")
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
output("irs post first fixing1") {
postAgreement1.data.copy(
postAgreement1.data.fixedLeg,
postAgreement1.data.floatingLeg,
postAgreement1.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
postAgreement1.data.common.copy(tradeID = "t1")
postAgreement1.copy(
postAgreement1.fixedLeg,
postAgreement1.floatingLeg,
postAgreement1.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
postAgreement1.common.copy(tradeID = "t1")
)
}
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
output("irs post first fixing2") {
postAgreement2.data.copy(
postAgreement2.data.fixedLeg,
postAgreement2.data.floatingLeg,
postAgreement2.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
postAgreement2.data.common.copy(tradeID = "t2")
postAgreement2.copy(
postAgreement2.fixedLeg,
postAgreement2.floatingLeg,
postAgreement2.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
postAgreement2.common.copy(tradeID = "t2")
)
}

View File

@ -1,9 +1,5 @@
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.crypto.Party
import com.r3corda.core.crypto.SecureHash
@ -78,7 +74,7 @@ class CashTests {
transaction {
output { outState }
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 {
output {

View File

@ -1,18 +1,19 @@
package com.r3corda.contracts.asset
import com.r3corda.contracts.asset.Obligation.Lifecycle
import com.r3corda.contracts.testing.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.testing.*
import com.r3corda.core.testing.JavaTestHelpers
import com.r3corda.core.utilities.nonEmptySetOf
import org.junit.Test
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
import java.util.*
import kotlin.test.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class ObligationTests {
val defaultIssuer = MEGA_CORP.ref(1)
@ -35,12 +36,12 @@ class ObligationTests {
val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2)
private fun obligationTestRoots(
group: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
) = group.apply {
unverifiedTransaction {
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY))
output("Bob's $1,000,000 obligation to Alice", oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY))
output("MegaCorp's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, BOB_PUBKEY))
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY))
output("Bob's $1,000,000 obligation to Alice", oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY))
output("MegaCorp's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB_PUBKEY))
output("Alice's $1,000,000", 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY)
}
}
@ -96,7 +97,7 @@ class ObligationTests {
transaction {
output { outState }
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 {
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
fun `generate close-out net transaction`() {
val obligationAliceToBob = oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)
val obligationBobToAlice = oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY)
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, obligationAliceToBob, obligationBobToAlice)
signWith(ALICE_KEY)
@ -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
fun `generate close-out net transaction with remainder`() {
val obligationAliceToBob = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)
val obligationBobToAlice = oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)
val obligationAliceToBob = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB_PUBKEY)
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY)
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateCloseOutNetting(this, ALICE_PUBKEY, obligationAliceToBob, obligationBobToAlice)
signWith(ALICE_KEY)
@ -235,14 +236,14 @@ class ObligationTests {
assertEquals(1, tx.outputs.size)
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
fun `generate payment net transaction`() {
val obligationAliceToBob = oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)
val obligationBobToAlice = oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY)
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
Obligation<Currency>().generatePaymentNetting(this, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
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
fun `generate payment net transaction with remainder`() {
val obligationAliceToBob = oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)
val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE_PUBKEY)
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
Obligation<Currency>().generatePaymentNetting(this, defaultUsd, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
signWith(ALICE_KEY)
@ -353,7 +354,7 @@ class ObligationTests {
input("Alice's $1,000,000 obligation to Bob")
input("Bob's $1,000,000 obligation to Alice")
input("MegaCorp's $1,000,000 obligation to Bob")
output("change") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, BOB_PUBKEY) }
output("change") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB_PUBKEY) }
command(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
timestamp(TEST_TX_TIME)
this.verifies()
@ -367,7 +368,7 @@ class ObligationTests {
transaction("Issuance") {
input("Alice's $1,000,000 obligation to Bob")
input("Bob's $1,000,000 obligation to Alice")
output("change") { (oneMillionDollars / 2).OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) }
output("change") { (oneMillionDollars / 2).OBLIGATION between Pair(ALICE, BOB_PUBKEY) }
command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
timestamp(TEST_TX_TIME)
this `fails with` "amounts owed on input and output must match"
@ -421,7 +422,7 @@ class ObligationTests {
transaction("Issuance") {
input("Bob's $1,000,000 obligation to Alice")
input("MegaCorp's $1,000,000 obligation to Bob")
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, ALICE_PUBKEY) }
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) }
timestamp(TEST_TX_TIME)
this.verifies()
@ -435,7 +436,7 @@ class ObligationTests {
transaction("Issuance") {
input("Bob's $1,000,000 obligation to Alice")
input("MegaCorp's $1,000,000 obligation to Bob")
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, ALICE_PUBKEY) }
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) }
timestamp(TEST_TX_TIME)
this `fails with` "all involved parties have signed"
@ -445,7 +446,7 @@ class ObligationTests {
@Test
fun `settlement`() {
// Try netting out two obligations
// Try settling an obligation
ledger {
obligationTestRoots(this)
transaction("Settlement") {
@ -456,10 +457,36 @@ class ObligationTests {
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
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()
}
}
// 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
fun `payment default`() {
// Try defaulting an obligation without a timestamp
@ -467,7 +494,7 @@ class ObligationTests {
obligationTestRoots(this)
transaction("Settlement") {
input("Alice's $1,000,000 obligation to Bob")
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)).copy(lifecycle = Lifecycle.DEFAULTED) }
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)).copy(lifecycle = Lifecycle.DEFAULTED) }
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Lifecycle.DEFAULTED) }
this `fails with` "there is a timestamp from the authority"
}
@ -477,8 +504,8 @@ class ObligationTests {
val pastTestTime = TEST_TX_TIME - Duration.ofDays(7)
val futureTestTime = TEST_TX_TIME + Duration.ofDays(7)
transaction("Settlement") {
input(oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` futureTestTime)
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` futureTestTime)
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` futureTestTime, Lifecycle.DEFAULTED) }
timestamp(TEST_TX_TIME)
this `fails with` "the due date has passed"
@ -487,8 +514,8 @@ class ObligationTests {
// Try defaulting an obligation that is now in the past
ledger {
transaction("Settlement") {
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) }
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` pastTestTime)
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` pastTestTime, Lifecycle.DEFAULTED) }
timestamp(TEST_TX_TIME)
this.verifies()

View File

@ -142,7 +142,7 @@ inline fun <T> logElapsedTime(label: String, logger: Logger? = null, body: () ->
*
* 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
inline fun <R> locked(body: T.() -> R): R = lock.withLock { body(content) }
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
* 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
@Synchronized

View File

@ -1,3 +1,4 @@
@file:JvmName("ContractsDSL")
package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
@ -17,34 +18,27 @@ import java.util.*
//// Currencies ///////////////////////////////////////////////////////////////////////////////////////////////////////
fun currency(code: String) = Currency.getInstance(code)
fun currency(code: String) = Currency.getInstance(code)!!
// Java interop
object JavaTestHelpers {
@JvmStatic val USD: Currency get() = currency("USD")
@JvmStatic val GBP: Currency get() = currency("GBP")
@JvmStatic val CHF: Currency get() = currency("CHF")
@JvmField val USD = currency("USD")
@JvmField val GBP = currency("GBP")
@JvmField val CHF = currency("CHF")
@JvmStatic fun DOLLARS(amount: Int) = Amount(amount.toLong() * 100, USD)
@JvmStatic fun DOLLARS(amount: Double) = Amount((amount * 100).toLong(), USD)
@JvmStatic fun POUNDS(amount: Int) = Amount(amount.toLong() * 100, GBP)
@JvmStatic fun SWISS_FRANCS(amount: Int) = Amount(amount.toLong() * 100, CHF)
fun DOLLARS(amount: Int): Amount<Currency> = Amount(amount.toLong() * 100, USD)
fun DOLLARS(amount: Double): Amount<Currency> = Amount((amount * 100).toLong(), USD)
fun POUNDS(amount: Int): Amount<Currency> = Amount(amount.toLong() * 100, GBP)
fun SWISS_FRANCS(amount: Int): Amount<Currency> = Amount(amount.toLong() * 100, CHF)
@JvmStatic fun issuedBy(currency: Currency, deposit: PartyAndReference) = Issued<Currency>(deposit, currency)
@JvmStatic fun issuedBy(amount: Amount<Currency>, deposit: PartyAndReference) = Amount(amount.quantity, issuedBy(amount.token, deposit))
}
val Int.DOLLARS: Amount<Currency> get() = DOLLARS(this)
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
val GBP = JavaTestHelpers.GBP
val CHF = JavaTestHelpers.CHF
infix fun Currency.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
infix fun Amount<Currency>.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
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 /////////////////////////////////////////////////////////////////////////////////////////////////////
@ -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
* 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? {
val timestampCmd = filter { it.value is TimestampCommand }.singleOrNull() ?: return null
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.
*
* @param T the type of the move command
* @param T the type of the move command.
*/
@Throws(IllegalArgumentException::class)
// 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.
*
* @param T the type of the move command
* @param T the type of the move command.
*/
@Throws(IllegalArgumentException::class)
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,
* 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: 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].
*/
@ -153,14 +153,14 @@ enum class AccrualAdjustment {
/**
* 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) }
/**
* 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
* There are some additional rules which are explained in the individual cases below
* 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.
* There are some additional rules which are explained in the individual cases below.
*/
enum class DateRollConvention {
// 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,
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
val notary: Party) {
/**
* Copies the underlying state, replacing the notary field with the new value.
* To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState]
* 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
* 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].
*/
data class Issued<P>(
data class Issued<out P>(
val issuer: PartyAndReference,
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.
* 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.
*
@ -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
/**
* 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 {
/** 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.
*
* @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?
}
@ -212,18 +217,18 @@ interface DealState : LinearState {
/**
* 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: 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
}
/**
* Interface adding fixing specific methods
* Interface adding fixing specific methods.
*/
interface FixableDealState : DealState {
/**
@ -232,10 +237,10 @@ interface FixableDealState : DealState {
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
* the Contract from the ContractState are in
* the Contract from the ContractState are in.
*/
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
* 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 {
init {
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
* default notary will be generated automatically.
*/
abstract class TransactionBuilder(protected val type: TransactionType = TransactionType.General(),
protected val notary: Party? = null) {
protected val inputs: MutableList<StateRef> = arrayListOf()
protected val attachments: MutableList<SecureHash> = arrayListOf()
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf()
protected val commands: MutableList<Command> = arrayListOf()
protected val signers: MutableSet<PublicKey> = mutableSetOf()
open class TransactionBuilder(
protected val type: TransactionType = TransactionType.General(),
protected val notary: Party? = null,
protected val inputs: MutableList<StateRef> = arrayListOf(),
protected val attachments: MutableList<SecureHash> = arrayListOf(),
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
protected val commands: MutableList<Command> = arrayListOf(),
protected val signers: MutableSet<PublicKey> = mutableSetOf()) {
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
/**
* 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.
* 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
* 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.
*/
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.
*
* @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.
*/
fun checkSignature(sig: DigitalSignature.WithKey) {
@ -112,31 +127,32 @@ abstract class TransactionBuilder(protected val type: TransactionType = Transact
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())
val notaryKey = stateAndRef.state.notary.owningKey
signers.add(notaryKey)
inputs.add(stateAndRef.ref)
signers.add(notary.owningKey)
inputs.add(stateRef)
}
fun addAttachment(attachment: Attachment) {
fun addAttachment(attachmentId: SecureHash) {
check(currentSigs.isEmpty())
attachments.add(attachment.id)
attachments.add(attachmentId)
}
fun addOutputState(state: TransactionState<*>) {
fun addOutputState(state: TransactionState<*>): Int {
check(currentSigs.isEmpty())
outputs.add(state)
return outputs.size - 1
}
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
/** A default notary must be specified during builder construction to use this method */
fun addOutputState(state: ContractState) {
fun addOutputState(state: ContractState): Int {
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) {

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.
*
* @param transactions map of transaction id to [SignedTransaction]
* @param startPoints transactions to use as starting points for the search
* @param transactions map of transaction id to [SignedTransaction].
* @param startPoints transactions to use as starting points for the search.
*/
class TransactionGraphSearch(val transactions: ReadOnlyTransactionStorage,
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.
*/
fun verify(tx: TransactionForVerification) {
val missing = verifySigners(tx)
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.
* 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>
@ -53,7 +52,7 @@ sealed class TransactionType {
/**
* 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) {
// 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,
* and that all required signing public keys are present
* and that all required signing public keys are present.
*/
override fun verifyTransaction(tx: TransactionForVerification) {
try {

View File

@ -2,6 +2,7 @@ package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.toStringShort
import java.security.PublicKey
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.
*
* @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)
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 attachments: List<Attachment>,
val commands: List<AuthenticatedObject<CommandData>>,
val origHash: SecureHash) {
val origHash: SecureHash,
val inputNotary: Party? = null) {
override fun hashCode() = origHash.hashCode()
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
* 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. */
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
/** 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)
}
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) {
class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
class SignersMissing(tx: TransactionForVerification, missing: List<PublicKey>) : TransactionVerificationException(tx, null)
class 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)
}

View File

@ -142,7 +142,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
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> {
val requiredKeys = tx.signers.toSet()

View File

@ -6,7 +6,7 @@ import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.TransactionForContract
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

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 class SecureHash private constructor(bits: ByteArray) : OpaqueBytes(bits) {
sealed class SecureHash(bits: ByteArray) : OpaqueBytes(bits) {
class SHA256(bits: ByteArray) : SecureHash(bits) {
init {
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
* contained within.
*
* @param raw the raw serialized data
* @param sig the (unverified) signature for the data
* @param raw the raw serialized data.
* @param sig the (unverified) signature for the data.
*/
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
/** 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 {
init {
@ -105,8 +105,8 @@ class CubicSplineInterpolator(private val xs: DoubleArray, private val ys: Doubl
}
/**
* Represents a polynomial function of arbitrary degree
* @param coefficients polynomial coefficients in the order of degree (constant first, followed by higher degree term coefficients)
* Represents a polynomial function of arbitrary degree.
* @param coefficients polynomial coefficients in the order of degree (constant first, followed by higher degree term coefficients).
*/
class Polynomial(private val coefficients: DoubleArray) {
private val reversedCoefficients = coefficients.reversed()
@ -118,7 +118,7 @@ class Polynomial(private val coefficients: DoubleArray) {
* A *spline* is function piecewise-defined by polynomial functions.
* 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>) {
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.
* The key of each map entry should contain the ProtocolLogic<T> class name.
* The associated map values are the union of all concrete class names passed to the protocol constructor.
* Standard java.lang.* and kotlin.* types do not need to be included explicitly
* This is used to extend the white listed protocols that can be initiated from the ServiceHub invokeProtocolAsync method
* Standard java.lang.* and kotlin.* types do not need to be included explicitly.
* This is used to extend the white listed protocols that can be initiated from the ServiceHub invokeProtocolAsync method.
*/
val requiredProtocols: Map<String, Set<String>>
}

View File

@ -42,7 +42,7 @@ interface ServiceHub {
* Given a list of [SignedTransaction]s, writes them to the local storage for validated transactions and then
* sends them to the wallet for further processing.
*
* @param txs The transactions to record
* @param txs The transactions to record.
*/
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
* 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())
/**
* 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<*> {
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.
*
* @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>
}

View File

@ -22,7 +22,7 @@ interface AttachmentStorage {
* to the raw byte stream is required.
*
* @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.
*/
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.messaging.MessagingService
import com.r3corda.core.node.NodeInfo
import com.r3corda.core.node.services.ServiceType
import org.slf4j.LoggerFactory
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
* 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 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
* 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>
/**
* 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)
/**
* Removes a node from the local cache
* Removes a node from the local cache.
*/
fun removeNode(node: NodeInfo)
/**
* 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.
*/
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*.
* 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>>) {
@Suppress("UNCHECKED_CAST")
@ -80,7 +80,7 @@ interface WalletService {
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>>

View File

@ -7,7 +7,7 @@ import java.time.Clock
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(),
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
* 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 {
/** 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
* 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
* 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
* 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. */
lateinit var psm: ProtocolStateMachine<*>

View File

@ -15,7 +15,7 @@ import kotlin.reflect.jvm.javaType
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
* 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")
/**
* 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)
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> {
@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
* 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> {
val stream = ByteArrayOutputStream()
@ -326,7 +326,7 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
// no-arg constructor available.
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
// 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 {
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.crypto.SecureHash
import java.security.PublicKey
import java.util.*
class DummyLinearState(
override val thread: SecureHash = SecureHash.randomSHA256(),

View File

@ -43,7 +43,7 @@ open class InMemoryWalletService(private val services: ServiceHub) : SingletonSe
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>>
get() = currentWallet.let { wallet ->

View File

@ -4,36 +4,159 @@ import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
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 {
/**
* 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>
}
interface LedgerDSLInterpreter<R, out T : TransactionDSLInterpreter<R>> : OutputStateLookup {
fun transaction(transactionLabel: String?, dsl: TransactionDSL<R, T>.() -> R): WireTransaction
fun unverifiedTransaction(transactionLabel: String?, dsl: TransactionDSL<R, T>.() -> Unit): WireTransaction
fun tweak(dsl: LedgerDSL<R, T, LedgerDSLInterpreter<R, T>>.() -> Unit)
fun attachment(attachment: InputStream): SecureHash
fun verifies()
/**
* This interface asserts that the DSL at hand is capable of verifying its underlying construct(ledger/transaction).
*/
interface 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
}
/**
* This is the class the top-level primitives deal with. It delegates all other primitives to the contained interpreter.
* This way we have a decoupling of the DSL "AST" and the interpretation(s) of it. Note how the delegation forces
* covariance of the TransactionInterpreter parameter.
* 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 interface defines the bare bone functionality that a Ledger DSL interpreter should implement.
*
* TODO (Kotlin 1.1): Use type synonyms to make the type params less unwieldy
*/
class LedgerDSL<R, out T : TransactionDSLInterpreter<R>, out L : LedgerDSLInterpreter<R, T>> (val interpreter: L) :
LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>> by interpreter {
interface LedgerDSLInterpreter<out T : TransactionDSLInterpreter> : Verifies, OutputStateLookup {
/**
* 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)
fun unverifiedTransaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> Unit) =
unverifiedTransaction(null, dsl)
/**
* Creates and adds a transaction to the ledger that will not be verified by [verifies].
* @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 _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> =
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
/**
* @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.node.services.IdentityService
import com.r3corda.core.node.services.StorageService
import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.serialization.serialize
import java.io.InputStream
import java.security.KeyPair
import java.security.PublicKey
import java.util.*
fun transaction(
transactionLabel: String? = null,
dsl: TransactionDSL<
EnforceVerifyOrFail,
TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> EnforceVerifyOrFail
) = JavaTestHelpers.transaction(transactionLabel, dsl)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Here is 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"
// }
// }
//
fun ledger(
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
storageService: StorageService = MockStorageService(),
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
) = JavaTestHelpers.ledger(identityService, storageService, dsl)
/**
* Here follows implementations of the [LedgerDSLInterpreter] and [TransactionDSLInterpreter] interfaces to be used in
* tests. Top level primitives [ledger] and [transaction] that bind the interpreter types are also defined here.
*/
@Deprecated(
message = "ledger doesn't nest, use tweak",
replaceWith = ReplaceWith("tweak"),
level = DeprecationLevel.ERROR)
@Suppress("UNUSED_PARAMETER")
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
fun TransactionDSLInterpreter.ledger(
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
}
@Deprecated(
@ -42,11 +54,8 @@ fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
replaceWith = ReplaceWith("tweak"),
level = DeprecationLevel.ERROR)
@Suppress("UNUSED_PARAMETER")
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
dsl: TransactionDSL<
EnforceVerifyOrFail,
TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> EnforceVerifyOrFail) {
fun TransactionDSLInterpreter.transaction(
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) {
}
@Deprecated(
@ -54,12 +63,12 @@ fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
replaceWith = ReplaceWith("tweak"),
level = DeprecationLevel.ERROR)
@Suppress("UNUSED_PARAMETER")
fun LedgerDSLInterpreter<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.ledger(
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
fun LedgerDSLInterpreter<TransactionDSLInterpreter>.ledger(
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
* 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.
@ -68,57 +77,56 @@ sealed class 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
* 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,
private val inputStateRefs: ArrayList<StateRef> = arrayListOf(),
internal val outputStates: ArrayList<LabeledOutput> = arrayListOf(),
private val attachments: ArrayList<SecureHash> = arrayListOf(),
private val commands: ArrayList<Command> = arrayListOf(),
private val signers: LinkedHashSet<PublicKey> = LinkedHashSet(),
private val transactionType: TransactionType = TransactionType.General()
) : TransactionDSLInterpreter<EnforceVerifyOrFail>, OutputStateLookup by ledgerInterpreter {
val transactionBuilder: TransactionBuilder,
internal val labelToIndexMap: HashMap<String, Int>
) : TransactionDSLInterpreter, OutputStateLookup by ledgerInterpreter {
constructor(
ledgerInterpreter: TestLedgerDSLInterpreter,
transactionBuilder: TransactionBuilder
) : this(ledgerInterpreter, transactionBuilder, HashMap())
private fun copy(): TestTransactionDSLInterpreter =
TestTransactionDSLInterpreter(
ledgerInterpreter = ledgerInterpreter,
inputStateRefs = ArrayList(inputStateRefs),
outputStates = ArrayList(outputStates),
attachments = ArrayList(attachments),
commands = ArrayList(commands),
signers = LinkedHashSet(signers),
transactionType = transactionType
transactionBuilder = transactionBuilder.copy(),
labelToIndexMap = HashMap(labelToIndexMap)
)
internal fun toWireTransaction(): WireTransaction =
WireTransaction(
inputs = inputStateRefs,
outputs = outputStates.map { it.state },
attachments = attachments,
commands = commands,
signers = signers.toList(),
type = transactionType
)
internal fun toWireTransaction() = transactionBuilder.toWireTransaction()
override fun input(stateRef: StateRef) {
val notary = ledgerInterpreter.resolveStateRef<ContractState>(stateRef).notary
signers.add(notary.owningKey)
inputStateRefs.add(stateRef)
transactionBuilder.addInputState(stateRef, notary)
}
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) {
attachments.add(attachmentId)
transactionBuilder.addAttachment(attachmentId)
}
override fun _command(signers: List<PublicKey>, commandData: CommandData) {
this.signers.addAll(signers)
commands.add(Command(commandData, signers))
val command = Command(commandData, signers)
transactionBuilder.addCommand(command)
}
override fun verifies(): EnforceVerifyOrFail {
@ -127,52 +135,18 @@ data class TestTransactionDSLInterpreter(
return EnforceVerifyOrFail.Token
}
override fun failsWith(expectedMessage: String?): EnforceVerifyOrFail {
val exceptionThrown = try {
this.verifies()
false
} catch (exception: Exception) {
if (expectedMessage != null) {
val exceptionMessage = exception.message
if (exceptionMessage == null) {
throw AssertionError(
"Expected exception containing '$expectedMessage' but raised exception had no message"
)
} else if (!exceptionMessage.toLowerCase().contains(expectedMessage.toLowerCase())) {
throw AssertionError(
"Expected exception containing '$expectedMessage' but raised exception was '$exception'"
)
}
}
true
}
if (!exceptionThrown) {
throw AssertionError("Expected exception but didn't get one")
}
return EnforceVerifyOrFail.Token
}
override fun tweak(
dsl: TransactionDSL<
EnforceVerifyOrFail,
TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> EnforceVerifyOrFail
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = dsl(TransactionDSL(copy()))
}
class AttachmentResolutionException(attachmentId: SecureHash) :
Exception("Attachment with id $attachmentId not found")
data class TestLedgerDSLInterpreter private constructor (
private val identityService: IdentityService,
private val storageService: StorageService,
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(),
private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
) : LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter> {
) : LedgerDSLInterpreter<TestTransactionDSLInterpreter> {
val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
// We specify [labelToOutputStateAndRefs] just so that Kotlin picks the primary constructor instead of cycling
@ -181,19 +155,25 @@ data class TestLedgerDSLInterpreter private constructor (
)
companion object {
private fun getCallerLocation(offset: Int): String {
val stackTraceElement = Thread.currentThread().stackTrace[3 + offset]
private fun getCallerLocation(): String? {
val stackTrace = Thread.currentThread().stackTrace
for (i in 1 .. stackTrace.size) {
val stackTraceElement = stackTrace[i]
if (!stackTraceElement.fileName.contains("DSL")) {
return stackTraceElement.toString()
}
}
return null
}
}
internal data class WireTransactionWithLocation(
val label: String?,
val transaction: WireTransaction,
val location: String
val location: String?
)
class VerifiesFailed(transactionLocation: String, cause: Throwable) :
Exception("Transaction defined at ($transactionLocation) didn't verify: $cause", cause)
class VerifiesFailed(transactionName: String, cause: Throwable) :
Exception("Transaction ($transactionName) didn't verify: $cause", cause)
class TypeMismatch(requested: Class<*>, actual: Class<*>) :
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 =
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
private fun <Return> interpretTransactionDsl(
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Return
private fun <R> interpretTransactionDsl(
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> R
): TestTransactionDSLInterpreter {
val transactionInterpreter = TestTransactionDSLInterpreter(this)
val transactionInterpreter = TestTransactionDSLInterpreter(this, transactionBuilder)
dsl(TransactionDSL(transactionInterpreter))
return transactionInterpreter
}
@ -274,18 +255,20 @@ data class TestLedgerDSLInterpreter private constructor (
private fun <R> recordTransactionWithTransactionMap(
transactionLabel: String?,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> R,
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> R,
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
): WireTransaction {
val transactionLocation = getCallerLocation(3)
val transactionInterpreter = interpretTransactionDsl(dsl)
val transactionLocation = getCallerLocation()
val transactionInterpreter = interpretTransactionDsl(transactionBuilder, dsl)
// Create the WireTransaction
val wireTransaction = transactionInterpreter.toWireTransaction()
// Record the output states
transactionInterpreter.outputStates.forEachIndexed { index, labeledOutput ->
if (labeledOutput.label != null) {
labelToOutputStateAndRefs[labeledOutput.label] = wireTransaction.outRef(index)
transactionInterpreter.labelToIndexMap.forEach { label, index ->
if (label in labelToOutputStateAndRefs) {
throw DuplicateOutputLabel(label)
}
labelToOutputStateAndRefs[label] = wireTransaction.outRef(index)
}
transactionMap[wireTransaction.serialized.hash] =
@ -294,32 +277,38 @@ data class TestLedgerDSLInterpreter private constructor (
return wireTransaction
}
override fun transaction(
override fun _transaction(
transactionLabel: String?,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations)
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, transactionWithLocations)
override fun unverifiedTransaction(
override fun _unverifiedTransaction(
transactionLabel: String?,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Unit
) = recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations)
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> Unit
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, nonVerifiedTransactionWithLocations)
override fun tweak(
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter,
LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter>>.() -> Unit) =
dsl: LedgerDSL<TestTransactionDSLInterpreter,
LedgerDSLInterpreter<TestTransactionDSLInterpreter>>.() -> Unit) =
dsl(LedgerDSL(copy()))
override fun attachment(attachment: InputStream): SecureHash {
return storageService.attachments.importAttachment(attachment)
}
override fun verifies() {
override fun verifies(): EnforceVerifyOrFail {
val transactionGroup = toTransactionGroup()
try {
transactionGroup.verify()
} 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> {
@ -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 ->
val allPubKeys = wtx.signers.toMutableSet()
val bits = wtx.serialize()
require(bits == wtx.serialized)
val signatures = ArrayList<DigitalSignature.WithKey>()
for (key in ALL_TEST_KEYS + extraKeys) {
if (allPubKeys.contains(key.public)) {
if (key.public in allPubKeys) {
signatures += key.signWithECDSA(bits)
allPubKeys -= key.public
}
@ -349,6 +344,10 @@ fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyP
SignedTransaction(bits, signatures)
}
fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll(
transactionsToSign: List<WireTransaction> = this.interpreter.wireTransactions, vararg extraKeys: KeyPair) =
signAll(transactionsToSign, extraKeys)
/**
* Signs all transactions in the ledger.
* @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.seconds
import java.security.PublicKey
import java.time.Duration
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"
// }
// }
//
/**
* This interface defines the bare bone functionality that a Transaction DSL interpreter should implement.
* @param <R> The return type of [verifies]/[failsWith] and the like. It is generic so that we have control over whether
* we want to enforce users to call these methods (@see [EnforceVerifyOrFail]) or not.
*/
interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
/**
* A reference to the enclosing ledger{..}'s interpreter.
*/
val ledgerInterpreter: LedgerDSLInterpreter<TransactionDSLInterpreter>
/**
* The [TransactionDSLInterpreter] defines the interface DSL interpreters should satisfy. No
* overloading/default valuing should be done here, only the basic functions that are required to implement everything.
* Same goes for functions requiring reflection e.g. [OutputStateLookup.retrieveOutputStateAndRef]
* Put convenience functions in [TransactionDSL] instead. There are some cases where the overloads would clash with the
* Interpreter interface, in these cases define a "backing" function in the interface instead (e.g. [_command]).
*
* This way the responsibility of providing a nice frontend DSL and the implementation(s) are separated.
* 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)
/**
* 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)
/**
* Adds an [Attachment] reference to the transaction.
* @param attachmentId The hash of the attachment, possibly returned by [LedgerDSLInterpreter.attachment].
*/
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 verifies(): R
fun failsWith(expectedMessage: String?): R
fun tweak(
dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R
): R
/**
* Creates a local scoped copy of the transaction.
* @param dsl The transaction DSL to be interpreted using the copy.
*/
fun tweak(dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail): EnforceVerifyOrFail
}
class TransactionDSL<R, out T : TransactionDSLInterpreter<R>> (val interpreter: T) :
TransactionDSLInterpreter<R> by interpreter {
class TransactionDSL<out T : TransactionDSLInterpreter> (val interpreter: T) :
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) {
val transaction = ledgerInterpreter.unverifiedTransaction(null) {
val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder()) {
output { state }
}
input(transaction.outRef<ContractState>(0).ref)
}
fun input(stateClosure: () -> ContractState) = input(stateClosure())
/**
* @see TransactionDSLInterpreter._output
*/
@JvmOverloads
fun output(label: String? = null, notary: Party = DUMMY_NOTARY, contractStateClosure: () -> ContractState) =
_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)
fun output(contractState: ContractState) =
_output(null, DUMMY_NOTARY, contractState)
/**
* @see TransactionDSLInterpreter._command
*/
fun command(vararg signers: PublicKey, commandDataClosure: () -> CommandData) =
_command(listOf(*signers), commandDataClosure())
/**
* @see TransactionDSLInterpreter._command
*/
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
fun timestamp(time: Instant, notary: PublicKey = DUMMY_NOTARY.owningKey) =
timestamp(TimestampCommand(time, 30.seconds), notary)
fun timestamp(time: Instant, tolerance: Duration = 30.seconds, notary: PublicKey = DUMMY_NOTARY.owningKey) =
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
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 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 next(): T = iterator.next()
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.
interface Record
data class Get<K>(val key: K) : Record
data class Put<K, V>(val key: K, val value: V) : Record
data class Get<out K>(val key: K) : Record
data class Put<out K, out V>(val key: K, val value: V) : 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?
* - Is it suspiciously large or small?
*/
class UntrustworthyData<T>(private val fromUntrustedWorld: T) {
class UntrustworthyData<out T>(private val fromUntrustedWorld: T) {
val data: T
@Deprecated("Accessing the untrustworthy data directly without validating it first is a bad idea")
get() = fromUntrustedWorld

View File

@ -25,7 +25,7 @@ import java.util.*
* @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.
*/
abstract class FetchDataProtocol<T : NamedByHash, W : Any>(
abstract class FetchDataProtocol<T : NamedByHash, in W : Any>(
protected val requests: Set<SecureHash>,
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()
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
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
* 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
* by another transaction or the timestamp is invalid
* by another transaction or the timestamp is invalid.
*/
class Client(private val stx: SignedTransaction,
override val progressTracker: ProgressTracker = Client.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
@ -157,7 +157,7 @@ object NotaryProtocol {
* history chain.
* 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
* 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
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.
data class Handshake<T>(
data class Handshake<out T>(
val payload: T,
val publicKey: PublicKey,
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
* 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 {
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
* 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
* party has to reveal the whole transaction history; however, we avoid complex conflict resolution logic where a party
* has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
* indeed valid
* indeed valid.
*/
class ValidatingNotaryProtocol(otherSide: Party,
sessionIdForSend: Long,

View File

@ -26,7 +26,7 @@ import java.security.PublicKey
* use the new updated state for future transactions.
*/
abstract class AbstractStateReplacementProtocol<T> {
interface Proposal<T> {
interface Proposal<out T> {
val stateRef: StateRef
val modification: T
val stx: SignedTransaction

View File

@ -35,7 +35,7 @@ object NotaryChangeProtocol: AbstractStateReplacementProtocol<Party>() {
override fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
val state = originalState.state
val newState = state.withNewNotary(modification)
val newState = state.withNotary(modification)
val participants = state.data.participants
val tx = TransactionType.NotaryChange.Builder().withItems(originalState, newState)
tx.signWith(serviceHub.storageService.myLegalIdentityKey)

View File

@ -1,13 +1,10 @@
package com.r3corda.core.protocols;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.jetbrains.annotations.*;
import org.junit.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
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 org.junit.Test
import java.security.KeyPair
import java.security.SecureRandom
import kotlin.test.assertEquals
class TransactionGraphSearchTests {

View File

@ -7,7 +7,6 @@ import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.testing.*
import org.junit.Test
import java.security.PublicKey
import java.security.SecureRandom
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

View File

@ -1,14 +1,14 @@
package com.r3corda.core.crypto
import java.math.BigInteger
import java.util.Arrays
import org.junit.Test
import java.math.BigInteger
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
/**
* Modified from the bitcoinj library
* Modified from the bitcoinj library.
*/
class Base58Test {
@Test
@ -32,7 +32,7 @@ class Base58Test {
@Test
fun testDecode() {
val testbytes = "Hello World".toByteArray()
val actualbytes = Base58.decode("JxF12TrwUP45BMd");
val actualbytes = Base58.decode("JxF12TrwUP45BMd")
assertTrue(String(actualbytes)) { Arrays.equals(testbytes, actualbytes) }
assertTrue("1") { Arrays.equals(ByteArray(1), Base58.decode("1")) }

View File

@ -234,7 +234,7 @@ class AttachmentClassLoaderTests {
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction()
@ -265,7 +265,7 @@ class AttachmentClassLoaderTests {
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction()

View File

@ -9,7 +9,6 @@ import com.r3corda.core.testing.*
import org.junit.Before
import org.junit.Test
import java.security.PublicKey
import java.security.SecureRandom
import java.security.SignatureException
import java.util.*
import kotlin.test.assertEquals
@ -47,7 +46,7 @@ class TransactionSerializationTests {
val fakeStateRef = generateStateRef()
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public), DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, DUMMY_KEY_1.public), DUMMY_NOTARY)
lateinit var tx: TransactionBuilder
@ -55,14 +54,14 @@ class TransactionSerializationTests {
@Before
fun setup() {
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
fun signWireTX() {
tx.signWith(DUMMY_NOTARY_KEY)
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_KEY_1)
val signedTX = tx.toSignedTransaction()
// Now check that the signature we just made verifies.
@ -82,7 +81,7 @@ class TransactionSerializationTests {
tx.toSignedTransaction()
}
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_KEY_1)
tx.signWith(DUMMY_NOTARY_KEY)
val signedTX = tx.toSignedTransaction()
@ -94,9 +93,9 @@ class TransactionSerializationTests {
// If the signature was replaced in transit, we don't like it.
assertFailsWith(SignatureException::class) {
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(TestUtils.keypair2)
tx2.signWith(DUMMY_KEY_2)
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
}
@ -105,7 +104,7 @@ class TransactionSerializationTests {
@Test
fun timestamp() {
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_KEY_1)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
val ltx = stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments)

View File

@ -43,8 +43,8 @@ class NonEmptySetTest {
.suppressing(CollectionAddAllTester::class.java.getMethod("testAddAll_nullCollectionReference"))
// Disable tests that try to remove everything:
.suppressing(CollectionRemoveAllTester::class.java.getMethod("testRemoveAll_nullCollectionReferenceNonEmptySubject"))
.suppressing(CollectionClearTester::class.java.getMethods().toList())
.suppressing(CollectionRetainAllTester::class.java.getMethods().toList())
.suppressing(CollectionClearTester::class.java.methods.toList())
.suppressing(CollectionRetainAllTester::class.java.methods.toList())
.createTestSuite()
}

View File

@ -69,7 +69,7 @@ function ThemeNav () {
})
.on('click', "[data-toggle='rst-current-version']", function() {
$("[data-toggle='rst-versions']").toggleClass("shift-up");
})
});
// Make tables responsive
$("table.docutils:not(.field-list)")
@ -139,11 +139,10 @@ function ThemeNav () {
parent_li.siblings().find('li.current').removeClass('current');
parent_li.find('> ul li.current').removeClass('current');
parent_li.toggleClass('current');
}
return nav;
};
return nav;
}
module.exports.ThemeNav = ThemeNav();
if (typeof(window) != 'undefined') {

View File

@ -191,7 +191,7 @@ var Stemmer = function() {
w = firstch.toLowerCase() + w.substr(1);
return w;
}
}
};
@ -564,7 +564,7 @@ var Search = {
$u.each(_o, function(o) {
var _files = o.files;
if (_files === undefined)
return
return;
if (_files.length === undefined)
_files = [_files];
@ -574,7 +574,7 @@ var Search = {
for (j = 0; j < _files.length; j++) {
file = _files[j];
if (!(file in scoreMap))
scoreMap[file] = {}
scoreMap[file] = {};
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
tutorial-contract
tutorial-test-dsl
protocol-state-machines
oracles
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
/**
* Report whether this node is started up or not
* Report whether this node is started up or not.
*/
@GET
@Path("status")

View File

@ -1,7 +1,7 @@
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 {
companion object {

View File

@ -5,7 +5,6 @@ import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import com.r3corda.core.RunOnCallerThread
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.crypto.Party
import com.r3corda.core.messaging.MessagingService
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.AddOrRemove
import com.r3corda.node.utilities.AffinityExecutor
import com.r3corda.protocols.TwoPartyDealProtocol
import org.slf4j.Logger
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Files
import java.nio.file.Path
import java.security.KeyPair
import java.time.Clock
import java.time.Duration
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.
/**
* 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
@ -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 {
createNodeDir()

View File

@ -3,7 +3,6 @@ package com.r3corda.node.internal
import com.codahale.metrics.JmxReporter
import com.google.common.net.HostAndPort
import com.r3corda.core.messaging.MessagingService
import com.r3corda.core.node.CordaPluginRegistry
import com.r3corda.core.node.NodeInfo
import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.ServiceType
@ -30,7 +29,6 @@ import java.net.InetSocketAddress
import java.nio.channels.FileLock
import java.nio.file.Path
import java.time.Clock
import java.util.*
import javax.management.ObjectName
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 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.
* @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
* network map service, while bootstrapping a network.
* @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).
* @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.
* 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
* 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.
*/
class Node(dir: Path, val p2pAddr: HostAndPort, val webServerAddr: HostAndPort, configuration: NodeConfiguration,
networkMapAddress: NodeInfo?, advertisedServices: Set<ServiceType>,

View File

@ -88,7 +88,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
// 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.
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
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
* 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 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
* 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 handler a function to handle the deserialised request and return an optional response (if return type not Unit)
* @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).
*/
protected inline fun <reified Q : ServiceRequestMessage, reified R : Any>
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
* [recordTransactions].
*
* @param txs The transactions to record
* @param txs The transactions to record.
*/
internal fun recordTransactionsInternal(writableStorageService: TxWritableStorageService, txs: Iterable<SignedTransaction>) {
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
* 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>

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 {
override val webApis: List<Class<*>> = emptyList()
@ -252,7 +252,7 @@ object NodeInterestRates {
/**
* 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? {
return rates.getOrElse(tenor) {

View File

@ -17,9 +17,9 @@ import javax.annotation.concurrent.ThreadSafe
* 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 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
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.
*
* @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 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> {
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.crypto.SecureHash
import com.r3corda.core.extractZipFile
import com.r3corda.node.services.api.AcceptsFileUpload
import com.r3corda.core.node.services.AttachmentStorage
import com.r3corda.core.utilities.loggerFor
import com.r3corda.node.services.api.AcceptsFileUpload
import java.io.FilterInputStream
import java.io.InputStream
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.nio.file.*
import java.util.*
import java.util.jar.JarInputStream
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.node.services.AttachmentStorage
import com.r3corda.core.node.services.StorageService
import com.r3corda.core.node.services.TransactionStorage
import com.r3corda.core.node.services.TxWritableStorageService
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:
* - 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
*
* 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,
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
* and to organise serializers / deserializers for java.time.* classes as necessary
* and to organise serializers / deserializers for java.time.* classes as necessary.
*/
@Provider
class Config(val services: ServiceHub) : ContextResolver<ObjectMapper> {

View File

@ -1,8 +1,8 @@
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.node.internal.Node
import com.r3corda.node.services.api.AcceptsFileUpload
import org.apache.commons.fileupload.servlet.ServletFileUpload
import java.util.*
import javax.servlet.http.HttpServlet

View File

@ -6,7 +6,7 @@ import javax.ws.rs.container.ContainerResponseFilter
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
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'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", "*")

View File

@ -49,7 +49,7 @@ abstract class MutableClock : Clock() {
*/
val mutations: Observable<Long> by lazy {
Observable.create({ subscriber: Subscriber<in Long> ->
if (!subscriber.isUnsubscribed()) {
if (!subscriber.isUnsubscribed) {
mutationObservers.add(subscriber)
// This is not very intuitive, but subscribing to a subscriber observes unsubscribes.
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
* 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
@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
* 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
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
* of [ThreadBox]. i.e. in the context of the content.

View File

@ -29,7 +29,7 @@ object JsonSupport {
fun createDefaultMapper(identities: IdentityService): ObjectMapper {
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.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>() {
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.TopicStringValidator
import com.r3corda.core.messaging.send
import com.r3corda.core.serialization.deserialize
import com.r3corda.node.internal.testing.MockNetwork
import org.junit.Before
import org.junit.Test

View File

@ -2,10 +2,10 @@ package com.r3corda.node.messaging
import com.google.common.util.concurrent.ListenableFuture
import com.r3corda.contracts.CommercialPaper
import com.r3corda.contracts.asset.CASH
import com.r3corda.contracts.asset.Cash
import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.asset.`issued by`
import com.r3corda.contracts.asset.`owned by`
import com.r3corda.contracts.testing.fillWithSomeTestCash
import com.r3corda.core.contracts.*
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.Wallet
import com.r3corda.core.random63BitValue
import com.r3corda.core.seconds
import com.r3corda.core.testing.*
import com.r3corda.core.utilities.BriefLogFormatter
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,
aliceError: Boolean,
expectedMessageSubstring: String
@ -431,7 +430,7 @@ class TwoPartyTradeProtocolTests {
return signed.associateBy { it.id }
}
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
withError: Boolean,
owner: PublicKey = BOB_PUBKEY,
issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair<Wallet, List<WireTransaction>> {
@ -472,7 +471,7 @@ class TwoPartyTradeProtocolTests {
return Pair(wallet, listOf(eb1, bc1, bc2))
}
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
withError: Boolean,
owner: PublicKey,
amount: Amount<Issued<Currency>>,
@ -482,9 +481,9 @@ class TwoPartyTradeProtocolTests {
output("alice's paper") {
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)
command(notary.owningKey) { TimestampCommand(TEST_TX_TIME, 30.seconds) }
timestamp(time = TEST_TX_TIME, notary = notary.owningKey)
if (attachmentID != null)
attachment(attachmentID)
if (withError) {

View File

@ -9,11 +9,7 @@ import com.r3corda.node.services.persistence.NodeAttachmentService
import org.junit.Before
import org.junit.Test
import java.nio.charset.Charset
import java.nio.file.FileAlreadyExistsException
import java.nio.file.FileSystem
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.nio.file.*
import java.util.jar.JarEntry
import java.util.jar.JarOutputStream
import kotlin.test.assertEquals

View File

@ -1,14 +1,14 @@
package com.r3corda.node.services
import com.r3corda.contracts.asset.CASH
import com.r3corda.contracts.asset.Cash
import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.testing.`with notary`
import com.r3corda.contracts.asset.`issued by`
import com.r3corda.contracts.asset.`owned by`
import com.r3corda.core.bd
import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.Fix
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.contracts.`with notary`
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.testing.ALICE_PUBKEY

View File

@ -1,12 +1,13 @@
package com.r3corda.node.services
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
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_KEY
import com.r3corda.node.internal.AbstractNode
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.transactions.SimpleNotaryService
import org.junit.Before
@ -15,6 +16,8 @@ import protocols.NotaryChangeProtocol
import protocols.NotaryChangeProtocol.Instigator
import protocols.StateReplacementException
import protocols.StateReplacementRefused
import java.time.Instant
import java.util.*
import java.util.concurrent.ExecutionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@ -91,3 +94,35 @@ class NotaryChangeTests {
// - Multiple states in a single "notary change" transaction
// - 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.MINI_CORP_KEY
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.transactions.SimpleNotaryService
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.MINI_CORP_KEY
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.transactions.ValidatingNotaryService
import com.r3corda.protocols.NotaryError

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