Introduce TransactionState, which wraps ContractState and holds the notary pointer.

Remove notary from ContractState.
Introduce TransactionType, which specifies custom validation logic for a transaction.
This commit is contained in:
Andrius Dagys
2016-06-07 10:42:50 +01:00
parent 3b1e020082
commit 70495a021e
48 changed files with 482 additions and 449 deletions

View File

@ -18,19 +18,17 @@ import java.security.PublicKey
val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract() val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract()
class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdoor { class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdoor {
data class State(val magicNumber: Int = 0, override val notary: Party) : ContractState { data class State(val magicNumber: Int = 0) : ContractState {
override val contract = ANOTHER_DUMMY_PROGRAM_ID override val contract = ANOTHER_DUMMY_PROGRAM_ID
override val participants: List<PublicKey> override val participants: List<PublicKey>
get() = emptyList() get() = emptyList()
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
} }
interface Commands : CommandData { interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands class Create : TypeOnlyCommandData(), Commands
} }
override fun verify(tx: TransactionForVerification) { override fun verify(tx: TransactionForContract) {
// Always accepts. // Always accepts.
} }
@ -38,7 +36,7 @@ class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdo
override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org") override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org")
override fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder { override fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary) val state = TransactionState(State(magicNumber), notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey)) return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
} }

View File

@ -1,10 +1,11 @@
package com.r3corda.contracts; package com.r3corda.contracts;
import com.google.common.collect.ImmutableList;
import com.r3corda.contracts.cash.Cash; import com.r3corda.contracts.cash.Cash;
import com.r3corda.contracts.cash.CashKt; import com.r3corda.contracts.cash.CashKt;
import com.r3corda.contracts.cash.InsufficientBalanceException; import com.r3corda.contracts.cash.InsufficientBalanceException;
import com.r3corda.core.contracts.*; import com.r3corda.core.contracts.*;
import com.r3corda.core.contracts.TransactionForVerification.InOutGroup; import com.r3corda.core.contracts.TransactionForContract.InOutGroup;
import com.r3corda.core.crypto.NullPublicKey; import com.r3corda.core.crypto.NullPublicKey;
import com.r3corda.core.crypto.Party; import com.r3corda.core.crypto.Party;
import com.r3corda.core.crypto.SecureHash; import com.r3corda.core.crypto.SecureHash;
@ -14,7 +15,6 @@ import org.jetbrains.annotations.Nullable;
import java.security.PublicKey; import java.security.PublicKey;
import java.time.Instant; import java.time.Instant;
import java.util.Currency; import java.util.Currency;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand; import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand;
@ -34,38 +34,36 @@ public class JavaCommercialPaper implements Contract {
private PublicKey owner; private PublicKey owner;
private Amount<Issued<Currency>> faceValue; private Amount<Issued<Currency>> faceValue;
private Instant maturityDate; private Instant maturityDate;
private Party notary;
public State() { public State() {
} // For serialization } // For serialization
public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> faceValue, public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> faceValue,
Instant maturityDate, Party notary) { Instant maturityDate) {
this.issuance = issuance; this.issuance = issuance;
this.owner = owner; this.owner = owner;
this.faceValue = faceValue; this.faceValue = faceValue;
this.maturityDate = maturityDate; this.maturityDate = maturityDate;
this.notary = notary;
} }
public State copy() { public State copy() {
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate, this.notary); return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
} }
public ICommercialPaperState withOwner(PublicKey newOwner) { public ICommercialPaperState withOwner(PublicKey newOwner) {
return new State(this.issuance, newOwner, this.faceValue, this.maturityDate, this.notary); return new State(this.issuance, newOwner, this.faceValue, this.maturityDate);
} }
public ICommercialPaperState withIssuance(PartyAndReference newIssuance) { public ICommercialPaperState withIssuance(PartyAndReference newIssuance) {
return new State(newIssuance, this.owner, this.faceValue, this.maturityDate, this.notary); return new State(newIssuance, this.owner, this.faceValue, this.maturityDate);
} }
public ICommercialPaperState withFaceValue(Amount<Issued<Currency>> newFaceValue) { public ICommercialPaperState withFaceValue(Amount<Issued<Currency>> newFaceValue) {
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate, this.notary); return new State(this.issuance, this.owner, newFaceValue, this.maturityDate);
} }
public ICommercialPaperState withMaturityDate(Instant newMaturityDate) { public ICommercialPaperState withMaturityDate(Instant newMaturityDate) {
return new State(this.issuance, this.owner, this.faceValue, newMaturityDate, this.notary); return new State(this.issuance, this.owner, this.faceValue, newMaturityDate);
} }
public PartyAndReference getIssuance() { public PartyAndReference getIssuance() {
@ -84,12 +82,6 @@ public class JavaCommercialPaper implements Contract {
return maturityDate; return maturityDate;
} }
@NotNull
@Override
public Party getNotary() {
return notary;
}
@NotNull @NotNull
@Override @Override
public Contract getContract() { public Contract getContract() {
@ -107,7 +99,6 @@ public class JavaCommercialPaper implements Contract {
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false; if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false; if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false; if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
if (notary != null ? !notary.equals(state.notary) : state.notary != null) return false;
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null); return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
} }
@ -117,26 +108,17 @@ public class JavaCommercialPaper implements Contract {
result = 31 * result + (owner != null ? owner.hashCode() : 0); result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0); result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0); result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
result = 31 * result + (notary != null ? notary.hashCode() : 0);
return result; return result;
} }
public State withoutOwner() { public State withoutOwner() {
return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate, notary); return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate);
}
@NotNull
@Override
public ContractState withNewNotary(@NotNull Party newNotary) {
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate, newNotary);
} }
@NotNull @NotNull
@Override @Override
public List<PublicKey> getParticipants() { public List<PublicKey> getParticipants() {
List<PublicKey> keys = new ArrayList<>(); return ImmutableList.of(this.owner);
keys.add(this.owner);
return keys;
} }
} }
@ -164,7 +146,7 @@ public class JavaCommercialPaper implements Contract {
} }
@Override @Override
public void verify(@NotNull TransactionForVerification tx) { public void verify(@NotNull TransactionForContract tx) {
// There are three possible things that can be done with CP. // There are three possible things that can be done with CP.
// Issuance, trading (aka moving in this prototype) and redeeming. // Issuance, trading (aka moving in this prototype) and redeeming.
// Each command has it's own set of restrictions which the verify function ... verifies. // Each command has it's own set of restrictions which the verify function ... verifies.
@ -248,19 +230,20 @@ public class JavaCommercialPaper implements Contract {
} }
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount faceValue, @Nullable Instant maturityDate, @NotNull Party notary) { public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate, notary); State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
return new TransactionBuilder().withItems(state, new Command(new Commands.Issue(), issuance.getParty().getOwningKey())); TransactionState output = new TransactionState<>(state, notary);
return new TransactionBuilder().withItems(output, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
} }
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException { public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
new Cash().generateSpend(tx, paper.getState().getFaceValue(), paper.getState().getOwner(), wallet); new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet);
tx.addInputState(paper.getRef()); tx.addInputState(paper);
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getOwner())); tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner()));
} }
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) { public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
tx.addInputState(paper.getRef()); tx.addInputState(paper);
tx.addOutputState(new State(paper.getState().getIssuance(), newOwner, paper.getState().getFaceValue(), paper.getState().getMaturityDate(), paper.getState().getNotary())); tx.addOutputState(new TransactionState<>(new State(paper.getState().getData().getIssuance(), newOwner, paper.getState().getData().getFaceValue(), paper.getState().getData().getMaturityDate()), paper.getState().getNotary()));
tx.addCommand(new Command(new Commands.Move(), paper.getState().getOwner())); tx.addCommand(new Command(new Commands.Move(), paper.getState().getData().getOwner()));
} }
} }

View File

@ -11,7 +11,7 @@ import com.r3corda.core.crypto.toStringShort
import com.r3corda.core.utilities.Emoji import com.r3corda.core.utilities.Emoji
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
import java.util.Currency import java.util.*
/** /**
* This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate * This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate
@ -46,8 +46,7 @@ class CommercialPaper : Contract {
val issuance: PartyAndReference, val issuance: PartyAndReference,
override val owner: PublicKey, override val owner: PublicKey,
val faceValue: Amount<Issued<Currency>>, val faceValue: Amount<Issued<Currency>>,
val maturityDate: Instant, val maturityDate: Instant
override val notary: Party
) : OwnableState, ICommercialPaperState { ) : OwnableState, ICommercialPaperState {
override val contract = CP_PROGRAM_ID override val contract = CP_PROGRAM_ID
override val participants: List<PublicKey> override val participants: List<PublicKey>
@ -63,8 +62,6 @@ class CommercialPaper : Contract {
override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance) override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance)
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue) override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate) override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
} }
interface Commands : CommandData { interface Commands : CommandData {
@ -75,7 +72,7 @@ class CommercialPaper : Contract {
class Issue : TypeOnlyCommandData(), Commands class Issue : TypeOnlyCommandData(), Commands
} }
override fun verify(tx: TransactionForVerification) { override fun verify(tx: TransactionForContract) {
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally. // Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
val groups = tx.groupStates() { it: State -> it.withoutOwner() } val groups = tx.groupStates() { it: State -> it.withoutOwner() }
@ -141,7 +138,7 @@ class CommercialPaper : Contract {
*/ */
fun generateIssue(faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder { fun generateIssue(faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
val issuance = faceValue.token.issuer val issuance = faceValue.token.issuer
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate, notary) val state = TransactionState(State(issuance, issuance.party.owningKey, faceValue, maturityDate), notary)
return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey)) return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
} }
@ -149,9 +146,9 @@ class CommercialPaper : Contract {
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper. * Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
*/ */
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) { fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) {
tx.addInputState(paper.ref) tx.addInputState(paper)
tx.addOutputState(paper.state.copy(owner = newOwner)) tx.addOutputState(TransactionState(paper.state.data.copy(owner = newOwner), paper.state.notary))
tx.addCommand(Commands.Move(), paper.state.owner) tx.addCommand(Commands.Move(), paper.state.data.owner)
} }
/** /**
@ -164,10 +161,10 @@ class CommercialPaper : Contract {
@Throws(InsufficientBalanceException::class) @Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) { fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
// Add the cash movement using the states in our wallet. // Add the cash movement using the states in our wallet.
val amount = paper.state.faceValue.let { amount -> Amount<Currency>(amount.quantity, amount.token.product) } val amount = paper.state.data.faceValue.let { amount -> Amount<Currency>(amount.quantity, amount.token.product) }
Cash().generateSpend(tx, amount, paper.state.owner, wallet) Cash().generateSpend(tx, amount, paper.state.data.owner, wallet)
tx.addInputState(paper.ref) tx.addInputState(paper)
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.owner) tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner)
} }
} }

View File

@ -487,7 +487,7 @@ class InterestRateSwap() : Contract {
/** /**
* verify() with some examples of what needs to be checked. * verify() with some examples of what needs to be checked.
*/ */
override fun verify(tx: TransactionForVerification) { override fun verify(tx: TransactionForContract) {
// Group by Trade ID for in / out states // Group by Trade ID for in / out states
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID } val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
@ -587,8 +587,7 @@ class InterestRateSwap() : Contract {
val fixedLeg: FixedLeg, val fixedLeg: FixedLeg,
val floatingLeg: FloatingLeg, val floatingLeg: FloatingLeg,
val calculation: Calculation, val calculation: Calculation,
val common: Common, val common: Common
override val notary: Party
) : FixableDealState { ) : FixableDealState {
override val contract = IRS_PROGRAM_ID override val contract = IRS_PROGRAM_ID
@ -621,12 +620,11 @@ class InterestRateSwap() : Contract {
} }
} }
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary) // TODO: pass a notary
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
override fun generateAgreement(): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary) override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, oldState.state.notary), oldState.ref), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
override fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix) {
InterestRateSwap().generateFix(ptx, StateAndRef(this, oldStateRef), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
} }
override fun nextFixingOf(): FixOf? { override fun nextFixingOf(): FixOf? {
@ -720,8 +718,8 @@ class InterestRateSwap() : Contract {
val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule) val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule)
// Put all the above into a new State object. // Put all the above into a new State object.
val state = State(fixedLeg, floatingLeg, newCalculation, common, notary) val state = TransactionState(State(fixedLeg, floatingLeg, newCalculation, common), notary)
return TransactionBuilder().withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey))) return TransactionBuilder().withItems(state, Command(Commands.Agree(), listOf(state.data.floatingLeg.floatingRatePayer.owningKey, state.data.fixedLeg.fixedRatePayer.owningKey)))
} }
private fun calcFixingDate(date: LocalDate, fixingPeriod: DateOffset, calendar: BusinessCalendar): LocalDate { private fun calcFixingDate(date: LocalDate, fixingPeriod: DateOffset, calendar: BusinessCalendar): LocalDate {
@ -734,8 +732,11 @@ class InterestRateSwap() : Contract {
// TODO: Replace with rates oracle // TODO: Replace with rates oracle
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Pair<LocalDate, Rate>) { fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Pair<LocalDate, Rate>) {
tx.addInputState(irs.ref) tx.addInputState(irs)
tx.addOutputState(irs.state.copy(calculation = irs.state.calculation.applyFixing(fixing.first, FixedRate(fixing.second)))) tx.addOutputState(
tx.addCommand(Commands.Fix(), listOf(irs.state.floatingLeg.floatingRatePayer.owningKey, irs.state.fixedLeg.fixedRatePayer.owningKey)) TransactionState(
irs.state.data.copy(calculation = irs.state.data.calculation.applyFixing(fixing.first, FixedRate(fixing.second))),
irs.state.notary))
tx.addCommand(Commands.Fix(), listOf(irs.state.data.floatingLeg.floatingRatePayer.owningKey, irs.state.data.fixedLeg.fixedRatePayer.owningKey))
} }
} }

View File

@ -49,12 +49,10 @@ class Cash : FungibleAsset<Currency>() {
override val amount: Amount<Issued<Currency>>, override val amount: Amount<Issued<Currency>>,
/** There must be a MoveCommand signed by this key to claim the amount */ /** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey, override val owner: PublicKey
override val notary: Party
) : FungibleAsset.State<Currency> { ) : FungibleAsset.State<Currency> {
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey, notary: Party) constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey)
: this(Amount(amount.quantity, Issued<Currency>(deposit, amount.token)), owner, notary) : this(Amount(amount.quantity, Issued<Currency>(deposit, amount.token)), owner)
override val deposit: PartyAndReference override val deposit: PartyAndReference
get() = amount.token.issuer get() = amount.token.issuer
override val contract = CASH_PROGRAM_ID override val contract = CASH_PROGRAM_ID
@ -66,7 +64,6 @@ class Cash : FungibleAsset<Currency>() {
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})" override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner)) override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
} }
// Just for grouping // Just for grouping
@ -97,9 +94,9 @@ class Cash : FungibleAsset<Currency>() {
*/ */
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: PublicKey, notary: Party) { fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: PublicKey, notary: Party) {
check(tx.inputStates().isEmpty()) check(tx.inputStates().isEmpty())
check(tx.outputStates().sumCashOrNull() == null) check(tx.outputStates().map { it.data }.sumCashOrNull() == null)
val at = amount.token.issuer val at = amount.token.issuer
tx.addOutputState(Cash.State(amount, owner, notary)) tx.addOutputState(TransactionState(Cash.State(amount, owner), notary))
tx.addCommand(Cash.Commands.Issue(), at.party.owningKey) tx.addCommand(Cash.Commands.Issue(), at.party.owningKey)
} }
@ -146,9 +143,9 @@ class Cash : FungibleAsset<Currency>() {
val currency = amount.token val currency = amount.token
val acceptableCoins = run { val acceptableCoins = run {
val ofCurrency = cashStates.filter { it.state.amount.token.product == currency } val ofCurrency = cashStates.filter { it.state.data.amount.token.product == currency }
if (onlyFromParties != null) if (onlyFromParties != null)
ofCurrency.filter { it.state.deposit.party in onlyFromParties } ofCurrency.filter { it.state.data.deposit.party in onlyFromParties }
else else
ofCurrency ofCurrency
} }
@ -159,7 +156,7 @@ class Cash : FungibleAsset<Currency>() {
for (c in acceptableCoins) { for (c in acceptableCoins) {
if (gatheredAmount >= amount) break if (gatheredAmount >= amount) break
gathered.add(c) gathered.add(c)
gatheredAmount += Amount(c.state.amount.quantity, currency) gatheredAmount += Amount(c.state.data.amount.quantity, currency)
takeChangeFrom = c takeChangeFrom = c
} }
@ -167,30 +164,30 @@ class Cash : FungibleAsset<Currency>() {
throw InsufficientBalanceException(amount - gatheredAmount) throw InsufficientBalanceException(amount - gatheredAmount)
val change = if (takeChangeFrom != null && gatheredAmount > amount) { val change = if (takeChangeFrom != null && gatheredAmount > amount) {
Amount<Issued<Currency>>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.issuanceDef) Amount<Issued<Currency>>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.state.issuanceDef)
} else { } else {
null null
} }
val keysUsed = gathered.map { it.state.owner }.toSet() val keysUsed = gathered.map { it.state.data.owner }.toSet()
val states = gathered.groupBy { it.state.deposit }.map { val states = gathered.groupBy { it.state.data.deposit }.map {
val (deposit, coins) = it val (deposit, coins) = it
val totalAmount = coins.map { it.state.amount }.sumOrThrow() val totalAmount = coins.map { it.state.data.amount }.sumOrThrow()
State(totalAmount, to, coins.first().state.notary) TransactionState(State(totalAmount, to), coins.first().state.notary)
} }
val outputs = if (change != null) { val outputs = if (change != null) {
// Just copy a key across as the change key. In real life of course, this works but leaks private data. // Just copy a key across as the change key. In real life of course, this works but leaks private data.
// In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow // In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow
// value flows through the transaction graph. // value flows through the transaction graph.
val changeKey = gathered.first().state.owner val changeKey = gathered.first().state.data.owner
// Add a change output and adjust the last output downwards. // Add a change output and adjust the last output downwards.
states.subList(0, states.lastIndex) + states.subList(0, states.lastIndex) +
states.last().let { it.copy(amount = it.amount - change) } + states.last().let { TransactionState(it.data.copy(amount = it.data.amount - change), it.notary) } +
State(change, changeKey, gathered.last().state.notary) TransactionState(State(change, changeKey), gathered.last().state.notary)
} else states } else states
for (state in gathered) tx.addInputState(state.ref) for (state in gathered) tx.addInputState(state)
for (state in outputs) tx.addOutputState(state) for (state in outputs) tx.addOutputState(state)
// What if we already have a move command with the right keys? Filter it out here or in platform code? // What if we already have a move command with the right keys? Filter it out here or in platform code?
val keysList = keysUsed.toList() val keysList = keysUsed.toList()

View File

@ -37,7 +37,6 @@ abstract class FungibleAsset<T> : Contract {
override val amount: Amount<Issued<T>> override val amount: Amount<Issued<T>>
/** There must be a MoveCommand signed by this key to claim the amount */ /** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey override val owner: PublicKey
override val notary: Party
} }
// Just for grouping // Just for grouping
@ -58,7 +57,7 @@ abstract class FungibleAsset<T> : Contract {
} }
/** This is the function EVERYONE runs */ /** This is the function EVERYONE runs */
override fun verify(tx: TransactionForVerification) { override fun verify(tx: TransactionForContract) {
// Each group is a set of input/output states with distinct issuance definitions. These assets are not fungible // Each group is a set of input/output states with distinct issuance definitions. These assets are not fungible
// and must be kept separated for bookkeeping purposes. // and must be kept separated for bookkeeping purposes.
val groups = tx.groupStates() { it: FungibleAsset.State<T> -> it.issuanceDef } val groups = tx.groupStates() { it: FungibleAsset.State<T> -> it.issuanceDef }
@ -97,7 +96,7 @@ abstract class FungibleAsset<T> : Contract {
private fun verifyIssueCommand(inputs: List<State<T>>, private fun verifyIssueCommand(inputs: List<State<T>>,
outputs: List<State<T>>, outputs: List<State<T>>,
tx: TransactionForVerification, tx: TransactionForContract,
issueCommand: AuthenticatedObject<Commands.Issue>, issueCommand: AuthenticatedObject<Commands.Issue>,
token: Issued<T>, token: Issued<T>,
issuer: Party) { issuer: Party) {

View File

@ -9,10 +9,11 @@ import com.r3corda.core.contracts.DUMMY_PROGRAM_ID
import com.r3corda.core.contracts.DummyContract import com.r3corda.core.contracts.DummyContract
import com.r3corda.core.contracts.PartyAndReference import com.r3corda.core.contracts.PartyAndReference
import com.r3corda.core.contracts.Issued import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.TransactionState
import com.r3corda.core.crypto.NullPublicKey import com.r3corda.core.crypto.NullPublicKey
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.testing.DUMMY_NOTARY
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -26,7 +27,7 @@ val TEST_PROGRAM_MAP: Map<Contract, Class<out Contract>> = mapOf(
IRS_PROGRAM_ID to InterestRateSwap::class.java IRS_PROGRAM_ID to InterestRateSwap::class.java
) )
fun generateState(notary: Party = DUMMY_NOTARY) = DummyContract.State(Random().nextInt(), notary) fun generateState() = DummyContract.State(Random().nextInt())
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
@ -50,8 +51,10 @@ fun generateState(notary: Party = DUMMY_NOTARY) = DummyContract.State(Random().n
infix fun Cash.State.`owned by`(owner: PublicKey) = copy(owner = owner) infix fun Cash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
infix fun Cash.State.`issued by`(party: Party) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party)))) infix fun Cash.State.`issued by`(party: Party) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party))))
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit))) infix fun Cash.State.`issued by`(deposit: PartyAndReference) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit)))
infix fun Cash.State.`with notary`(notary: Party) = TransactionState(this, notary)
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner = owner) infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner = owner)
infix fun CommercialPaper.State.`with notary`(notary: Party) = TransactionState(this, notary)
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner) infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State =
@ -62,7 +65,8 @@ val DUMMY_CASH_ISSUER = Party("Snake Oil Issuer", DUMMY_CASH_ISSUER_KEY.public).
/** Allows you to write 100.DOLLARS.CASH */ /** Allows you to write 100.DOLLARS.CASH */
val Amount<Currency>.CASH: Cash.State get() = Cash.State( val Amount<Currency>.CASH: Cash.State get() = Cash.State(
Amount<Issued<Currency>>(this.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, this.token)), Amount<Issued<Currency>>(this.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, this.token)),
NullPublicKey, DUMMY_NOTARY) NullPublicKey)
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey, DUMMY_NOTARY) val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey)
infix fun ContractState.`with notary`(notary: Party) = TransactionState(this, notary)

View File

@ -17,7 +17,7 @@ import com.r3corda.core.utilities.trace
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
import java.util.Currency import java.util.*
/** /**
* This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer * This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
@ -129,7 +129,7 @@ object TwoPartyTradeProtocol {
// This verifies that the transaction is contract-valid, even though it is missing signatures. // This verifies that the transaction is contract-valid, even though it is missing signatures.
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)) serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments))
if (wtx.outputs.sumCashBy(myKeyPair.public) != price) if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public) != price)
throw IllegalArgumentException("Transaction is not sending us the right amount of cash") throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
// There are all sorts of funny games a malicious secondary might play here, we should fix them: // There are all sorts of funny games a malicious secondary might play here, we should fix them:
@ -221,7 +221,7 @@ object TwoPartyTradeProtocol {
progressTracker.currentStep = VERIFYING progressTracker.currentStep = VERIFYING
maybeTradeRequest.validate { maybeTradeRequest.validate {
// What is the seller trying to sell us? // What is the seller trying to sell us?
val asset = it.assetForSale.state val asset = it.assetForSale.state.data
val assetTypeName = asset.javaClass.name val assetTypeName = asset.javaClass.name
logger.trace { "Got trade request for a $assetTypeName: ${it.assetForSale}" } logger.trace { "Got trade request for a $assetTypeName: ${it.assetForSale}" }
@ -272,15 +272,15 @@ object TwoPartyTradeProtocol {
val cashStates = wallet.statesOfType<Cash.State>() val cashStates = wallet.statesOfType<Cash.State>()
val cashSigningPubKeys = Cash().generateSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey, cashStates) val cashSigningPubKeys = Cash().generateSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey, cashStates)
// Add inputs/outputs/a command for the movement of the asset. // Add inputs/outputs/a command for the movement of the asset.
ptx.addInputState(tradeRequest.assetForSale.ref) ptx.addInputState(tradeRequest.assetForSale)
// Just pick some new public key for now. This won't be linked with our identity in any way, which is what // Just pick some new public key for now. This won't be linked with our identity in any way, which is what
// we want for privacy reasons: the key is here ONLY to manage and control ownership, it is not intended to // we want for privacy reasons: the key is here ONLY to manage and control ownership, it is not intended to
// reveal who the owner actually is. The key management service is expected to derive a unique key from some // reveal who the owner actually is. The key management service is expected to derive a unique key from some
// initial seed in order to provide privacy protection. // initial seed in order to provide privacy protection.
val freshKey = serviceHub.keyManagementService.freshKey() val freshKey = serviceHub.keyManagementService.freshKey()
val (command, state) = tradeRequest.assetForSale.state.withNewOwner(freshKey.public) val (command, state) = tradeRequest.assetForSale.state.data.withNewOwner(freshKey.public)
ptx.addOutputState(state) ptx.addOutputState(TransactionState(state, tradeRequest.assetForSale.state.notary))
ptx.addCommand(command, tradeRequest.assetForSale.state.owner) ptx.addCommand(command, tradeRequest.assetForSale.state.data.owner)
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt // And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one. // to have one.

View File

@ -1,10 +1,7 @@
package com.r3corda.contracts package com.r3corda.contracts
import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.cash.Cash import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.testing.STATE import com.r3corda.contracts.testing.*
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.days import com.r3corda.core.days
@ -32,8 +29,7 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
MEGA_CORP.ref(123), MEGA_CORP.ref(123),
MEGA_CORP_PUBKEY, MEGA_CORP_PUBKEY,
1000.DOLLARS `issued by` MEGA_CORP.ref(123), 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
TEST_TX_TIME + 7.days, TEST_TX_TIME + 7.days
DUMMY_NOTARY
) )
override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue() override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue()
@ -46,8 +42,7 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
issuance = MEGA_CORP.ref(123), issuance = MEGA_CORP.ref(123),
owner = MEGA_CORP_PUBKEY, owner = MEGA_CORP_PUBKEY,
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
maturityDate = TEST_TX_TIME + 7.days, maturityDate = TEST_TX_TIME + 7.days
notary = DUMMY_NOTARY
) )
override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue() override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue()
@ -121,7 +116,7 @@ class CommercialPaperTestsGeneric {
fun `issue cannot replace an existing state`() { fun `issue cannot replace an existing state`() {
transactionGroup { transactionGroup {
roots { roots {
transaction(thisTest.getPaper() label "paper") transaction(thisTest.getPaper() `with notary` DUMMY_NOTARY label "paper")
} }
transaction { transaction {
input("paper") input("paper")
@ -144,9 +139,9 @@ class CommercialPaperTestsGeneric {
trade(destroyPaperAtRedemption = false).expectFailureOfTx(3, "must be destroyed") trade(destroyPaperAtRedemption = false).expectFailureOfTx(3, "must be destroyed")
} }
fun cashOutputsToWallet(vararg states: Cash.State): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> { fun <T : ContractState> cashOutputsToWallet(vararg outputs: TransactionState<T>): Pair<LedgerTransaction, List<StateAndRef<T>>> {
val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*states), emptyList(), SecureHash.randomSHA256()) val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*outputs), emptyList(), SecureHash.randomSHA256(), TransactionType.Business())
return Pair(ltx, states.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) }) return Pair(ltx, outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
} }
@Test @Test
@ -163,9 +158,9 @@ class CommercialPaperTestsGeneric {
} }
val (alicesWalletTX, alicesWallet) = cashOutputsToWallet( val (alicesWalletTX, alicesWallet) = cashOutputsToWallet(
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY, 3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY,
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY, 3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY,
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY 3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY
) )
// Alice pays $9000 to MiniCorp to own some of their debt. // Alice pays $9000 to MiniCorp to own some of their debt.
@ -181,8 +176,8 @@ class CommercialPaperTestsGeneric {
// Won't be validated. // Won't be validated.
val (corpWalletTX, corpWallet) = cashOutputsToWallet( val (corpWalletTX, corpWallet) = cashOutputsToWallet(
9000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY, 9000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY `with notary` DUMMY_NOTARY,
4000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY 4000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY `with notary` DUMMY_NOTARY
) )
fun makeRedeemTX(time: Instant): LedgerTransaction { fun makeRedeemTX(time: Instant): LedgerTransaction {
@ -213,8 +208,8 @@ class CommercialPaperTestsGeneric {
val someProfits = 1200.DOLLARS `issued by` issuer val someProfits = 1200.DOLLARS `issued by` issuer
return transactionGroupFor() { return transactionGroupFor() {
roots { roots {
transaction(900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY label "alice's $900") transaction(900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY label "alice's $900")
transaction(someProfits.STATE `owned by` MEGA_CORP_PUBKEY label "some profits") transaction(someProfits.STATE `owned by` MEGA_CORP_PUBKEY `with notary` DUMMY_NOTARY label "some profits")
} }
// Some CP is issued onto the ledger by MegaCorp. // Some CP is issued onto the ledger by MegaCorp.
@ -230,7 +225,7 @@ class CommercialPaperTestsGeneric {
input("paper") input("paper")
input("alice's $900") input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output("alice's paper") { "paper".output `owned by` ALICE_PUBKEY } output("alice's paper") { "paper".output.data `owned by` ALICE_PUBKEY }
arg(ALICE_PUBKEY) { Cash.Commands.Move() } arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() } arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
} }
@ -244,7 +239,7 @@ class CommercialPaperTestsGeneric {
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY } output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY } output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
if (!destroyPaperAtRedemption) if (!destroyPaperAtRedemption)
output { "paper".output } output { "paper".output.data }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() } arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() }

View File

@ -97,7 +97,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
) )
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY) InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
} }
2 -> { 2 -> {
// 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides) // 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides)
@ -187,7 +187,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
) )
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY) return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
} }
else -> TODO("IRS number $irsSelect not defined") else -> TODO("IRS number $irsSelect not defined")
@ -204,8 +204,7 @@ class IRSTests {
exampleIRS.fixedLeg, exampleIRS.fixedLeg,
exampleIRS.floatingLeg, exampleIRS.floatingLeg,
exampleIRS.calculation, exampleIRS.calculation,
exampleIRS.common, exampleIRS.common
DUMMY_NOTARY
) )
val outState = inState.copy() val outState = inState.copy()
@ -255,7 +254,7 @@ class IRSTests {
* Utility so I don't have to keep typing this * Utility so I don't have to keep typing this
*/ */
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State { fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
return generateIRSTxn(irsSelector).outputs.filterIsInstance<InterestRateSwap.State>().single() return generateIRSTxn(irsSelector).outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
} }
/** /**
@ -287,7 +286,7 @@ class IRSTests {
newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.value))) newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.value)))
} }
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common, DUMMY_NOTARY) val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common)
println(newIRS.exportIRSToCSV()) println(newIRS.exportIRSToCSV())
} }
@ -306,7 +305,7 @@ class IRSTests {
@Test @Test
fun generateIRSandFixSome() { fun generateIRSandFixSome() {
var previousTXN = generateIRSTxn(1) var previousTXN = generateIRSTxn(1)
var currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single() var currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint()) println(currentIRS.prettyPrint())
while (true) { while (true) {
val nextFixingDate = currentIRS.calculation.nextFixingDate() ?: break val nextFixingDate = currentIRS.calculation.nextFixingDate() ?: break
@ -323,7 +322,7 @@ class IRSTests {
} }
tx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments) tx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments)
} }
currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single() currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint()) println(currentIRS.prettyPrint())
previousTXN = fixTX previousTXN = fixTX
} }
@ -387,11 +386,11 @@ class IRSTests {
transaction("Fix") { transaction("Fix") {
input("irs post agreement") input("irs post agreement")
output("irs post first fixing") { output("irs post first fixing") {
"irs post agreement".output.copy( "irs post agreement".output.data.copy(
"irs post agreement".output.fixedLeg, "irs post agreement".output.data.fixedLeg,
"irs post agreement".output.floatingLeg, "irs post agreement".output.data.floatingLeg,
"irs post agreement".output.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), "irs post agreement".output.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
"irs post agreement".output.common "irs post agreement".output.data.common
) )
} }
arg(ORACLE_PUBKEY) { arg(ORACLE_PUBKEY) {
@ -678,7 +677,6 @@ class IRSTests {
irs.floatingLeg, irs.floatingLeg,
irs.calculation, irs.calculation,
irs.common.copy(tradeID = "t1") irs.common.copy(tradeID = "t1")
) )
} }
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
@ -692,7 +690,6 @@ class IRSTests {
irs.floatingLeg, irs.floatingLeg,
irs.calculation, irs.calculation,
irs.common.copy(tradeID = "t2") irs.common.copy(tradeID = "t2")
) )
} }
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
@ -703,19 +700,19 @@ class IRSTests {
input("irs post agreement1") input("irs post agreement1")
input("irs post agreement2") input("irs post agreement2")
output("irs post first fixing1") { output("irs post first fixing1") {
"irs post agreement1".output.copy( "irs post agreement1".output.data.copy(
"irs post agreement1".output.fixedLeg, "irs post agreement1".output.data.fixedLeg,
"irs post agreement1".output.floatingLeg, "irs post agreement1".output.data.floatingLeg,
"irs post agreement1".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), "irs post agreement1".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
"irs post agreement1".output.common.copy(tradeID = "t1") "irs post agreement1".output.data.common.copy(tradeID = "t1")
) )
} }
output("irs post first fixing2") { output("irs post first fixing2") {
"irs post agreement2".output.copy( "irs post agreement2".output.data.copy(
"irs post agreement2".output.fixedLeg, "irs post agreement2".output.data.fixedLeg,
"irs post agreement2".output.floatingLeg, "irs post agreement2".output.data.floatingLeg,
"irs post agreement2".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), "irs post agreement2".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
"irs post agreement2".output.common.copy(tradeID = "t2") "irs post agreement2".output.data.common.copy(tradeID = "t2")
) )
} }

View File

@ -4,6 +4,7 @@ import com.r3corda.core.contracts.DummyContract
import com.r3corda.contracts.testing.`issued by` import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by` import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.testing.`with deposit` import com.r3corda.contracts.testing.`with deposit`
import com.r3corda.contracts.testing.`with notary`
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
@ -12,19 +13,14 @@ import com.r3corda.core.testing.*
import org.junit.Test import org.junit.Test
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
import kotlin.test.assertEquals import kotlin.test.*
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
class CashTests { class CashTests {
val defaultRef = OpaqueBytes(ByteArray(1, {1})) val defaultRef = OpaqueBytes(ByteArray(1, {1}))
val defaultIssuer = MEGA_CORP.ref(defaultRef) val defaultIssuer = MEGA_CORP.ref(defaultRef)
val inState = Cash.State( val inState = Cash.State(
amount = 1000.DOLLARS `issued by` defaultIssuer, amount = 1000.DOLLARS `issued by` defaultIssuer,
owner = DUMMY_PUBKEY_1, owner = DUMMY_PUBKEY_1
notary = DUMMY_NOTARY
) )
val outState = inState.copy(owner = DUMMY_PUBKEY_2) val outState = inState.copy(owner = DUMMY_PUBKEY_2)
@ -71,7 +67,7 @@ class CashTests {
fun issueMoney() { fun issueMoney() {
// Check we can't "move" money into existence. // Check we can't "move" money into existence.
transaction { transaction {
input { DummyContract.State(notary = DUMMY_NOTARY) } input { DummyContract.State() }
output { outState } output { outState }
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() } arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
@ -89,8 +85,7 @@ class CashTests {
output { output {
Cash.State( Cash.State(
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34), amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
owner = DUMMY_PUBKEY_1, owner = DUMMY_PUBKEY_1
notary = DUMMY_NOTARY
) )
} }
tweak { tweak {
@ -105,7 +100,7 @@ class CashTests {
val ptx = TransactionBuilder() val ptx = TransactionBuilder()
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(ptx.inputStates().isEmpty()) assertTrue(ptx.inputStates().isEmpty())
val s = ptx.outputStates()[0] as Cash.State val s = ptx.outputStates()[0].data as Cash.State
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount) assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
assertEquals(MINI_CORP, s.deposit.party) assertEquals(MINI_CORP, s.deposit.party)
assertEquals(DUMMY_PUBKEY_1, s.owner) assertEquals(DUMMY_PUBKEY_1, s.owner)
@ -189,7 +184,7 @@ class CashTests {
// Include the previously issued cash in a new issuance command // Include the previously issued cash in a new issuance command
ptx = TransactionBuilder() ptx = TransactionBuilder()
ptx.addInputState(tx.tx.outRef<Cash.State>(0).ref) ptx.addInputState(tx.tx.outRef<Cash.State>(0))
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
} }
@ -359,7 +354,7 @@ class CashTests {
fun multiCurrency() { fun multiCurrency() {
// Check we can do an atomic currency trade tx. // Check we can do an atomic currency trade tx.
transaction { transaction {
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), DUMMY_PUBKEY_2, DUMMY_NOTARY) val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), DUMMY_PUBKEY_2)
input { inState `owned by` DUMMY_PUBKEY_1 } input { inState `owned by` DUMMY_PUBKEY_1 }
input { pounds } input { pounds }
output { inState `owned by` DUMMY_PUBKEY_2 } output { inState `owned by` DUMMY_PUBKEY_2 }
@ -379,7 +374,7 @@ class CashTests {
fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) = fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) =
StateAndRef( StateAndRef(
Cash.State(amount `issued by` corp.ref(depositRef), OUR_PUBKEY_1, DUMMY_NOTARY), Cash.State(amount `issued by` corp.ref(depositRef), OUR_PUBKEY_1) `with notary` DUMMY_NOTARY,
StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
) )
@ -400,7 +395,7 @@ class CashTests {
fun generateSimpleDirectSpend() { fun generateSimpleDirectSpend() {
val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP) val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0]) assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0].data)
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
} }
@ -415,8 +410,8 @@ class CashTests {
fun generateSimpleSpendWithChange() { fun generateSimpleSpendWithChange() {
val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP) val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0]) assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
assertEquals(WALLET[0].state.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1]) assertEquals(WALLET[0].state.data.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data)
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
} }
@ -425,7 +420,7 @@ class CashTests {
val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP) val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[1].ref, wtx.inputs[1]) assertEquals(WALLET[1].ref, wtx.inputs[1])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0]) assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
} }
@ -436,8 +431,8 @@ class CashTests {
assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[1].ref, wtx.inputs[1]) assertEquals(WALLET[1].ref, wtx.inputs[1])
assertEquals(WALLET[2].ref, wtx.inputs[2]) assertEquals(WALLET[2].ref, wtx.inputs[2])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0]) assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
assertEquals(WALLET[2].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1]) assertEquals(WALLET[2].state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1].data)
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
} }
@ -458,9 +453,9 @@ class CashTests {
*/ */
@Test @Test
fun aggregation() { fun aggregation() {
val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP_PUBKEY, DUMMY_NOTARY) val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP_PUBKEY)
val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP_PUBKEY, DUMMY_NOTARY) val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP_PUBKEY)
val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY, DUMMY_NOTARY) val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY)
// Obviously it must be possible to aggregate states with themselves // Obviously it must be possible to aggregate states with themselves
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef) assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
@ -474,7 +469,7 @@ class CashTests {
// States cannot be aggregated if the currency differs // States cannot be aggregated if the currency differs
assertNotEquals(oneThousandDollarsFromMini.issuanceDef, assertNotEquals(oneThousandDollarsFromMini.issuanceDef,
Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY, DUMMY_NOTARY).issuanceDef) Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY).issuanceDef)
// States cannot be aggregated if the reference differs // States cannot be aggregated if the reference differs
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef) assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef)
@ -484,9 +479,9 @@ class CashTests {
@Test @Test
fun `summing by owner`() { fun `summing by owner`() {
val states = listOf( val states = listOf(
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY),
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY) Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
) )
assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP_PUBKEY)) assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP_PUBKEY))
} }
@ -494,8 +489,8 @@ class CashTests {
@Test(expected = UnsupportedOperationException::class) @Test(expected = UnsupportedOperationException::class)
fun `summing by owner throws`() { fun `summing by owner throws`() {
val states = listOf( val states = listOf(
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY) Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
) )
states.sumCashBy(MINI_CORP_PUBKEY) states.sumCashBy(MINI_CORP_PUBKEY)
} }
@ -516,9 +511,9 @@ class CashTests {
@Test @Test
fun `summing a single currency`() { fun `summing a single currency`() {
val states = listOf( val states = listOf(
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY) Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
) )
// Test that summing everything produces the total number of dollars // Test that summing everything produces the total number of dollars
var expected = 7000.DOLLARS `issued by` defaultIssuer var expected = 7000.DOLLARS `issued by` defaultIssuer
@ -529,8 +524,8 @@ class CashTests {
@Test(expected = IllegalArgumentException::class) @Test(expected = IllegalArgumentException::class)
fun `summing multiple currencies`() { fun `summing multiple currencies`() {
val states = listOf( val states = listOf(
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY) Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
) )
// Test that summing everything fails because we're mixing units // Test that summing everything fails because we're mixing units
states.sumCash() states.sumCash()

View File

@ -92,7 +92,7 @@ fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: Stri
*/ */
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
// TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here? // TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here?
inline fun <reified T : CommandData> verifyMoveCommands(inputs: List<OwnableState>, tx: TransactionForVerification) { inline fun <reified T : CommandData> verifyMoveCommands(inputs: List<OwnableState>, tx: TransactionForContract) {
// Now check the digital signatures on the move command. Every input has an owning public key, and we must // Now check the digital signatures on the move command. Every input has an owning public key, and we must
// see a signature from each of those keys. The actual signatures have been verified against the transaction // see a signature from each of those keys. The actual signatures have been verified against the transaction
// data by the platform before execution. // data by the platform before execution.

View File

@ -9,30 +9,33 @@ import java.security.PublicKey
val DUMMY_PROGRAM_ID = DummyContract() val DUMMY_PROGRAM_ID = DummyContract()
class DummyContract : Contract { class DummyContract : Contract {
data class State(val magicNumber: Int = 0, data class State(val magicNumber: Int = 0) : ContractState {
override val notary: Party) : ContractState {
override val contract = DUMMY_PROGRAM_ID override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey> override val participants: List<PublicKey>
get() = emptyList() get() = emptyList()
}
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary) data class SingleOwnerState(val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState {
override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
} }
data class MultiOwnerState(val magicNumber: Int = 0, data class MultiOwnerState(val magicNumber: Int = 0,
val owners: List<PublicKey>, val owners: List<PublicKey>) : ContractState {
override val notary: Party) : ContractState {
override val contract = DUMMY_PROGRAM_ID override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey> override val participants: List<PublicKey>
get() = owners get() = owners
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
} }
interface Commands : CommandData { interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands class Create : TypeOnlyCommandData(), Commands
class Move : TypeOnlyCommandData(), Commands
} }
override fun verify(tx: TransactionForVerification) { override fun verify(tx: TransactionForContract) {
// Always accepts. // Always accepts.
} }
@ -40,7 +43,7 @@ class DummyContract : Contract {
override val legalContractReference: SecureHash = SecureHash.sha256("") override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder { fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary) val state = TransactionState(SingleOwnerState(magicNumber, owner.party.owningKey), notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey)) return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
} }
} }

View File

@ -27,17 +27,20 @@ interface ContractState {
/** Contract by which the state belongs */ /** Contract by which the state belongs */
val contract: Contract val contract: Contract
/** Identity of the notary that ensures this state is not used as an input to a transaction more than once */
val notary: Party
/** List of public keys for each party that can consume this state in a valid transaction. */ /** List of public keys for each party that can consume this state in a valid transaction. */
val participants: List<PublicKey> val participants: List<PublicKey>
}
/** A wrapper for [ContractState] containing additional platform-level state information. This is the state */
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 data structure, replacing the notary field with the new value. * Copies the underlying state, replacing the notary field with the new value.
* To replace the notary, we need an approval (signature) from _all_ participants. * To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState]
*/ */
fun withNewNotary(newNotary: Party): ContractState fun withNewNotary(newNotary: Party) = TransactionState(this.data, newNotary)
} }
/** /**
@ -105,7 +108,7 @@ interface DealState : LinearState {
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a * 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(): TransactionBuilder fun generateAgreement(notary: Party): TransactionBuilder
} }
/** /**
@ -125,7 +128,7 @@ interface FixableDealState : DealState {
* TODO: This would also likely move to methods on the Contract once the changes to reference * TODO: This would also likely move to methods on the Contract once the changes to reference
* the Contract from the ContractState are in * the Contract from the ContractState are in
*/ */
fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix) fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix)
} }
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */ /** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
@ -140,11 +143,11 @@ data class StateRef(val txhash: SecureHash, val index: Int) {
} }
/** A StateAndRef is simply a (state, ref) pair. For instance, a wallet (which holds available assets) contains these. */ /** A StateAndRef is simply a (state, ref) pair. For instance, a wallet (which holds available assets) contains these. */
data class StateAndRef<out T : ContractState>(val state: T, val ref: StateRef) data class StateAndRef<out T : ContractState>(val state: TransactionState<T>, val ref: StateRef)
/** Filters a list of [StateAndRef] objects according to the type of the states */ /** Filters a list of [StateAndRef] objects according to the type of the states */
inline fun <reified T : ContractState> List<StateAndRef<ContractState>>.filterStatesOfType(): List<StateAndRef<T>> { inline fun <reified T : ContractState> List<StateAndRef<ContractState>>.filterStatesOfType(): List<StateAndRef<T>> {
return mapNotNull { if (it.state is T) StateAndRef(it.state, it.ref) else null } return mapNotNull { if (it.state.data is T) StateAndRef(TransactionState(it.state.data, it.state.notary), it.ref) else null }
} }
/** /**
@ -166,6 +169,9 @@ abstract class TypeOnlyCommandData : CommandData {
/** Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes */ /** Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes */
data class Command(val value: CommandData, val signers: List<PublicKey>) { data class Command(val value: CommandData, val signers: List<PublicKey>) {
init {
require(signers.isNotEmpty())
}
constructor(data: CommandData, key: PublicKey) : this(data, listOf(key)) constructor(data: CommandData, key: PublicKey) : this(data, listOf(key))
private fun commandDataToString() = value.toString().let { if (it.contains("@")) it.replace('$', '.').split("@")[0] else it } private fun commandDataToString() = value.toString().let { if (it.contains("@")) it.replace('$', '.').split("@")[0] else it }
@ -198,11 +204,14 @@ data class TimestampCommand(val after: Instant?, val before: Instant?) : Command
} }
/** /**
* Indicates that the transaction is only used for changing the Notary for a state. If present in a transaction, * Command that has to be signed by all participants of the states in the transaction
* the contract code is not run, and special platform-level validation logic is used instead * in order to perform a notary change
*/ */
class ChangeNotary : TypeOnlyCommandData() class ChangeNotary : TypeOnlyCommandData()
/** Command that indicates the requirement of a Notary signature for the input states */
class NotaryCommand : TypeOnlyCommandData()
/** /**
* Implemented by a program that implements business logic on the shared ledger. All participants run this code for * Implemented by a program that implements business logic on the shared ledger. All participants run this code for
* every [LedgerTransaction] they see on the network, for every input and output state. All contracts must accept the * every [LedgerTransaction] they see on the network, for every input and output state. All contracts must accept the
@ -217,7 +226,7 @@ interface Contract {
* existing contract code. * existing contract code.
*/ */
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
fun verify(tx: TransactionForVerification) fun verify(tx: TransactionForContract)
/** /**
* Unparsed reference to the natural language contract that this code is supposed to express (usually a hash of * Unparsed reference to the natural language contract that this code is supposed to express (usually a hash of

View File

@ -1,8 +1,5 @@
package com.r3corda.core.contracts package com.r3corda.core.contracts
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
@ -22,8 +19,9 @@ import java.util.*
*/ */
class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf(), class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf(),
private val attachments: MutableList<SecureHash> = arrayListOf(), private val attachments: MutableList<SecureHash> = arrayListOf(),
private val outputs: MutableList<ContractState> = arrayListOf(), private val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
private val commands: MutableList<Command> = arrayListOf()) { private val commands: MutableList<Command> = arrayListOf(),
private val type: TransactionType = TransactionType.Business()) {
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull() val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
@ -49,8 +47,8 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
fun withItems(vararg items: Any): TransactionBuilder { fun withItems(vararg items: Any): TransactionBuilder {
for (t in items) { for (t in items) {
when (t) { when (t) {
is StateRef -> addInputState(t) is StateAndRef<*> -> addInputState(t)
is ContractState -> addOutputState(t) is TransactionState<*> -> addOutputState(t)
is Command -> addCommand(t) is Command -> addCommand(t)
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}") else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
} }
@ -96,7 +94,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
} }
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments), fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
ArrayList(outputs), ArrayList(commands)) ArrayList(outputs), ArrayList(commands), type)
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction { fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
if (checkSufficientSignatures) { if (checkSufficientSignatures) {
@ -109,9 +107,15 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs)) return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
} }
fun addInputState(ref: StateRef) { fun addInputState(stateAndRef: StateAndRef<*>) {
check(currentSigs.isEmpty()) check(currentSigs.isEmpty())
inputs.add(ref)
val notaryKey = stateAndRef.state.notary.owningKey
if (commands.none { it.signers.contains(notaryKey) }) {
commands.add(Command(NotaryCommand(), notaryKey))
}
inputs.add(stateAndRef.ref)
} }
fun addAttachment(attachment: Attachment) { fun addAttachment(attachment: Attachment) {
@ -119,7 +123,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
attachments.add(attachment.id) attachments.add(attachment.id)
} }
fun addOutputState(state: ContractState) { fun addOutputState(state: TransactionState<*>) {
check(currentSigs.isEmpty()) check(currentSigs.isEmpty())
outputs.add(state) outputs.add(state)
} }
@ -136,7 +140,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
// Accessors that yield immutable snapshots. // Accessors that yield immutable snapshots.
fun inputStates(): List<StateRef> = ArrayList(inputs) fun inputStates(): List<StateRef> = ArrayList(inputs)
fun outputStates(): List<ContractState> = ArrayList(outputs) fun outputStates(): List<TransactionState<*>> = ArrayList(outputs)
fun commands(): List<Command> = ArrayList(commands) fun commands(): List<Command> = ArrayList(commands)
fun attachments(): List<SecureHash> = ArrayList(attachments) fun attachments(): List<SecureHash> = ArrayList(attachments)
} }

View File

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

View File

@ -0,0 +1,73 @@
package com.r3corda.core.contracts
/** Defines transaction validation rules for a specific transaction type */
sealed class TransactionType {
override fun equals(other: Any?) = other?.javaClass == javaClass
override fun hashCode() = javaClass.name.hashCode()
/**
* Check that the transaction is valid based on:
* - General platform rules
* - Rules for the specific transaction type
*
* Note: Presence of _signatures_ is not checked, only the public keys to be signed for.
*/
fun verify(tx: TransactionForVerification) {
verifyNotary(tx)
typeSpecificVerify(tx)
}
private fun verifyNotary(tx: TransactionForVerification) {
if (tx.inStates.isEmpty()) return
val notary = tx.inStates.first().notary
if (tx.inStates.any { it.notary != notary }) throw TransactionVerificationException.MoreThanOneNotary(tx)
if (tx.commands.none { it.signers.contains(notary.owningKey) }) throw TransactionVerificationException.NotaryMissing(tx)
}
abstract fun typeSpecificVerify(tx: TransactionForVerification)
/** A general type used for business transactions, where transaction validity is determined by custom contract code */
class Business : 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.
*/
override fun typeSpecificVerify(tx: TransactionForVerification) {
val ctx = tx.toTransactionForContract()
val contracts = (ctx.inStates.map { it.contract } + ctx.outStates.map { it.contract }).toSet()
for (contract in contracts) {
try {
contract.verify(ctx)
} catch(e: Throwable) {
throw TransactionVerificationException.ContractRejection(tx, contract, e)
}
}
}
}
/**
* A special transaction type for reassigning a notary for a state. Validation does not involve running
* any contract code, it just checks that the states are unmodified apart from the notary field.
*/
class NotaryChange : TransactionType() {
/**
* Check that the difference between inputs and outputs is only the notary field,
* and that all required signing public keys are present
*/
override fun typeSpecificVerify(tx: TransactionForVerification) {
try {
tx.inStates.zip(tx.outStates).forEach {
check(it.first.data == it.second.data)
check(it.first.notary != it.second.notary)
}
val command = tx.commands.requireSingleCommand<ChangeNotary>()
val requiredSigners = tx.inStates.flatMap { it.data.participants }
check(command.signers.containsAll(requiredSigners))
} catch (e: IllegalStateException) {
throw TransactionVerificationException.InvalidNotaryChange(tx)
}
}
}
}

View File

@ -29,7 +29,7 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
val resolved = HashSet<TransactionForVerification>(transactions.size) val resolved = HashSet<TransactionForVerification>(transactions.size)
for (tx in transactions) { for (tx in transactions) {
val inputs = ArrayList<ContractState>(tx.inputs.size) val inputs = ArrayList<TransactionState<ContractState>>(tx.inputs.size)
for (ref in tx.inputs) { for (ref in tx.inputs) {
val conflict = refToConsumingTXMap[ref] val conflict = refToConsumingTXMap[ref]
if (conflict != null) if (conflict != null)
@ -41,31 +41,27 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
// Look up the output in that transaction by index. // Look up the output in that transaction by index.
inputs.add(ltx.outputs[ref.index]) inputs.add(ltx.outputs[ref.index])
} }
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id)) resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id, tx.type))
} }
for (tx in resolved) for (tx in resolved)
tx.verify() tx.verify()
return resolved return resolved
} }
} }
/** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */ /** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */
data class TransactionForVerification(val inStates: List<ContractState>, data class TransactionForVerification(val inStates: List<TransactionState<ContractState>>,
val outStates: List<ContractState>, val outStates: List<TransactionState<ContractState>>,
val attachments: List<Attachment>, val attachments: List<Attachment>,
val commands: List<AuthenticatedObject<CommandData>>, val commands: List<AuthenticatedObject<CommandData>>,
val origHash: SecureHash) { val origHash: SecureHash,
val type: TransactionType) {
override fun hashCode() = origHash.hashCode() override fun hashCode() = origHash.hashCode()
override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash
/** /**
* Verifies that the transaction is valid: * Verifies that the transaction is valid by running type-specific validation logic.
* - Checks that the input states and the timestamp point to the same Notary
* - Runs the contracts for this transaction. If any contract fails to verify, the whole transaction is
* considered to be invalid. In case of a special type of transaction, e.g. for changing notary for a state,
* runs custom platform level validation logic instead.
* *
* TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated. * TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated.
* *
@ -73,68 +69,22 @@ data class TransactionForVerification(val inStates: List<ContractState>,
* (the original is in the cause field) * (the original is in the cause field)
*/ */
@Throws(TransactionVerificationException::class) @Throws(TransactionVerificationException::class)
fun verify() { fun verify() = type.verify(this)
verifySingleNotary()
if (isChangeNotaryTx()) verifyNotaryChange() else runContractVerify() fun toTransactionForContract() = TransactionForContract(inStates.map { it.data }, outStates.map { it.data }, attachments, commands, origHash)
} }
private fun verifySingleNotary() { /**
if (inStates.isEmpty()) return * A transaction to be passed as input to a contract verification function. Defines helper methods to
val notary = inStates.first().notary * simplify verification logic in contracts.
if (inStates.any { it.notary != notary }) throw TransactionVerificationException.MoreThanOneNotary(this)
val timestampCmd = commands.singleOrNull { it.value is TimestampCommand } ?: return
if (!timestampCmd.signers.contains(notary.owningKey)) throw TransactionVerificationException.MoreThanOneNotary(this)
}
private fun isChangeNotaryTx() = commands.any { it.value is ChangeNotary }
/**
* A notary change transaction is valid if:
* - It contains only a single command - [ChangeNotary]
* - Outputs are identical to inputs apart from the notary field (each input/output state pair must have the same index)
*/ */
private fun verifyNotaryChange() { data class TransactionForContract(val inStates: List<ContractState>,
try { val outStates: List<ContractState>,
check(commands.size == 1) val attachments: List<Attachment>,
inStates.zip(outStates).forEach { val commands: List<AuthenticatedObject<CommandData>>,
// TODO: Check that input and output state(s) differ only by notary pointer val origHash: SecureHash) {
check(it.first.notary != it.second.notary) override fun hashCode() = origHash.hashCode()
} override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
} catch (e: IllegalStateException) {
throw TransactionVerificationException.InvalidNotaryChange(this)
}
}
private fun runContractVerify() {
val contracts = (inStates.map { it.contract } + outStates.map { it.contract }).toSet()
for (contract in contracts) {
try {
contract.verify(this)
} catch(e: Throwable) {
throw TransactionVerificationException.ContractRejection(this, contract, e)
}
}
}
/**
* Utilities for contract writers to incorporate into their logic.
*/
/**
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
* be used to simplify this logic.
*/
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
/** /**
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they * Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
@ -186,6 +136,24 @@ data class TransactionForVerification(val inStates: List<ContractState>,
return result return result
} }
/** Utilities for contract writers to incorporate into their logic. */
/**
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
* be used to simplify this logic.
*/
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
} }
class TransactionResolutionException(val hash: SecureHash) : Exception() class TransactionResolutionException(val hash: SecureHash) : Exception()
@ -194,5 +162,6 @@ class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTra
sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) { sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) {
class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause) class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null) class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
class NotaryMissing(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null) class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
} }

View File

@ -43,8 +43,9 @@ import java.security.SignatureException
/** Transaction ready for serialisation, without any signatures attached. */ /** Transaction ready for serialisation, without any signatures attached. */
data class WireTransaction(val inputs: List<StateRef>, data class WireTransaction(val inputs: List<StateRef>,
val attachments: List<SecureHash>, val attachments: List<SecureHash>,
val outputs: List<ContractState>, val outputs: List<TransactionState<ContractState>>,
val commands: List<Command>) : NamedByHash { val commands: List<Command>,
val type: TransactionType) : NamedByHash {
// Cache the serialised form of the transaction and its hash to give us fast access to it. // Cache the serialised form of the transaction and its hash to give us fast access to it.
@Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null @Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null
@ -63,11 +64,11 @@ data class WireTransaction(val inputs: List<StateRef>,
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : ContractState> outRef(index: Int): StateAndRef<T> { fun <T : ContractState> outRef(index: Int): StateAndRef<T> {
require(index >= 0 && index < outputs.size) require(index >= 0 && index < outputs.size)
return StateAndRef(outputs[index] as T, StateRef(id, index)) return StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
} }
/** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */ /** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.indexOfOrThrow(state)) fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.map { it.data }.indexOfOrThrow(state))
override fun toString(): String { override fun toString(): String {
val buf = StringBuilder() val buf = StringBuilder()
@ -130,7 +131,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
return copy(sigs = sigs + sig) return copy(sigs = sigs + sig)
} }
fun withAdditionalSignatures(sigList: Collection<DigitalSignature.WithKey>): SignedTransaction { fun withAdditionalSignatures(sigList: Iterable<DigitalSignature.WithKey>): SignedTransaction {
return copy(sigs = sigs + sigList) return copy(sigs = sigs + sigList)
} }
@ -165,12 +166,13 @@ data class LedgerTransaction(
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */ /** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
val attachments: List<Attachment>, val attachments: List<Attachment>,
/** The states that will be generated by the execution of this transaction. */ /** The states that will be generated by the execution of this transaction. */
val outputs: List<ContractState>, val outputs: List<TransactionState<*>>,
/** Arbitrary data passed to the program of each input state. */ /** Arbitrary data passed to the program of each input state. */
val commands: List<AuthenticatedObject<CommandData>>, val commands: List<AuthenticatedObject<CommandData>>,
/** The hash of the original serialised WireTransaction */ /** The hash of the original serialised WireTransaction */
override val id: SecureHash override val id: SecureHash,
val type: TransactionType
) : NamedByHash { ) : NamedByHash {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as T, StateRef(id, index)) fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
} }

View File

@ -53,7 +53,7 @@ interface ServiceHub {
* *
* @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): ContractState { fun loadState(stateRef: StateRef): TransactionState<*> {
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash) val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return definingTx.tx.outputs[stateRef.index] return definingTx.tx.outputs[stateRef.index]
} }

View File

@ -30,7 +30,7 @@ abstract class Wallet {
abstract val states: List<StateAndRef<ContractState>> abstract val states: List<StateAndRef<ContractState>>
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state is T } as List<StateAndRef<T>> inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
/** /**
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for * Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
@ -99,10 +99,10 @@ interface WalletService {
/** Returns the [linearHeads] only when the type of the state would be considered an 'instanceof' the given type. */ /** Returns the [linearHeads] only when the type of the state would be considered an 'instanceof' the given type. */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<SecureHash, StateAndRef<T>> { fun <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<SecureHash, StateAndRef<T>> {
return linearHeads.filterValues { stateType.isInstance(it.state) }.mapValues { StateAndRef(it.value.state as T, it.value.ref) } return linearHeads.filterValues { stateType.isInstance(it.state.data) }.mapValues { StateAndRef(it.value.state as TransactionState<T>, it.value.ref) }
} }
fun statesForRefs(refs: List<StateRef>): Map<StateRef, ContractState?> { fun statesForRefs(refs: List<StateRef>): Map<StateRef, TransactionState<*>?> {
val refsToStates = currentWallet.states.associateBy { it.ref } val refsToStates = currentWallet.states.associateBy { it.ref }
return refs.associateBy({ it }, { refsToStates[it]?.state }) return refs.associateBy({ it }, { refsToStates[it]?.state })
} }

View File

@ -232,6 +232,7 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
kryo.writeClassAndObject(output, obj.attachments) kryo.writeClassAndObject(output, obj.attachments)
kryo.writeClassAndObject(output, obj.outputs) kryo.writeClassAndObject(output, obj.outputs)
kryo.writeClassAndObject(output, obj.commands) kryo.writeClassAndObject(output, obj.commands)
kryo.writeClassAndObject(output, obj.type)
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -258,10 +259,11 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
} else javaClass.classLoader } else javaClass.classLoader
kryo.useClassLoader(classLoader) { kryo.useClassLoader(classLoader) {
val outputs = kryo.readClassAndObject(input) as List<ContractState> val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>>
val commands = kryo.readClassAndObject(input) as List<Command> val commands = kryo.readClassAndObject(input) as List<Command>
val transactionType = kryo.readClassAndObject(input) as TransactionType
return WireTransaction(inputs, attachmentHashes, outputs, commands) return WireTransaction(inputs, attachmentHashes, outputs, commands, transactionType)
} }
} }
} }
@ -343,6 +345,7 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
// Work around a bug in Kryo handling nested generics // Work around a bug in Kryo handling nested generics
register(Issued::class.java, ImmutableClassSerializer(Issued::class)) register(Issued::class.java, ImmutableClassSerializer(Issued::class))
register(TransactionState::class.java, ImmutableClassSerializer(TransactionState::class))
noReferencesWithin<WireTransaction>() noReferencesWithin<WireTransaction>()
} }

View File

@ -4,12 +4,12 @@ package com.r3corda.core.testing
import com.google.common.base.Throwables import com.google.common.base.Throwables
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import com.r3corda.core.*
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.* import com.r3corda.core.crypto.*
import com.r3corda.core.serialization.serialize
import com.r3corda.core.node.services.testing.MockIdentityService import com.r3corda.core.node.services.testing.MockIdentityService
import com.r3corda.core.node.services.testing.MockStorageService import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.seconds
import com.r3corda.core.serialization.serialize
import java.net.ServerSocket import java.net.ServerSocket
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
@ -96,20 +96,25 @@ fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
// //
// TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block // TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block
class LabeledOutput(val label: String?, val state: ContractState) { class LabeledOutput(val label: String?, val state: TransactionState<*>) {
override fun toString() = state.toString() + (if (label != null) " ($label)" else "") override fun toString() = state.toString() + (if (label != null) " ($label)" else "")
override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state) override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state)
override fun hashCode(): Int = state.hashCode() override fun hashCode(): Int = state.hashCode()
} }
infix fun ContractState.label(label: String) = LabeledOutput(label, this) infix fun TransactionState<*>.label(label: String) = LabeledOutput(label, this)
abstract class AbstractTransactionForTest { abstract class AbstractTransactionForTest {
protected val attachments = ArrayList<SecureHash>() protected val attachments = ArrayList<SecureHash>()
protected val outStates = ArrayList<LabeledOutput>() protected val outStates = ArrayList<LabeledOutput>()
protected val commands = ArrayList<Command>() protected val commands = ArrayList<Command>()
protected val type = TransactionType.Business()
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, s()).apply { outStates.add(this) } init {
arg(DUMMY_NOTARY.owningKey) { NotaryCommand() }
}
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, TransactionState(s(), DUMMY_NOTARY)).apply { outStates.add(this) }
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> { protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
return commands.map { AuthenticatedObject(it.signers, it.signers.mapNotNull { MOCK_IDENTITY_SERVICE.partyFromKey(it) }, it.value) } return commands.map { AuthenticatedObject(it.signers, it.signers.mapNotNull { MOCK_IDENTITY_SERVICE.partyFromKey(it) }, it.value) }
@ -150,12 +155,12 @@ sealed class LastLineShouldTestForAcceptOrFailure {
// Corresponds to the args to Contract.verify // Corresponds to the args to Contract.verify
open class TransactionForTest : AbstractTransactionForTest() { open class TransactionForTest : AbstractTransactionForTest() {
private val inStates = arrayListOf<ContractState>() private val inStates = arrayListOf<TransactionState<ContractState>>()
fun input(s: () -> ContractState) = inStates.add(s()) fun input(s: () -> ContractState) = inStates.add(TransactionState(s(), DUMMY_NOTARY))
protected fun runCommandsAndVerify(time: Instant) { protected fun runCommandsAndVerify(time: Instant) {
val cmds = commandsToAuthenticatedObjects() val cmds = commandsToAuthenticatedObjects()
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256()) val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256(), type)
tx.verify() tx.verify()
} }
@ -240,16 +245,24 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
private val inStates = ArrayList<StateRef>() private val inStates = ArrayList<StateRef>()
fun input(label: String) { fun input(label: String) {
val notaryKey = label.output.notary.owningKey
if (commands.none { it.signers.contains(notaryKey) }) commands.add(Command(NotaryCommand(), notaryKey))
inStates.add(label.outputRef) inStates.add(label.outputRef)
} }
fun toWireTransaction() = WireTransaction(inStates, attachments, outStates.map { it.state }, commands) fun toWireTransaction() = WireTransaction(inStates, attachments, outStates.map { it.state }, commands, type)
} }
val String.output: T get() = labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found") val String.output: TransactionState<T>
get() =
labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found")
val String.outputRef: StateRef get() = labelToRefs[this] ?: throw IllegalArgumentException("Unknown label \"$this\"") val String.outputRef: StateRef get() = labelToRefs[this] ?: throw IllegalArgumentException("Unknown label \"$this\"")
fun <C : ContractState> lookup(label: String) = StateAndRef(label.output as C, label.outputRef) fun <C : ContractState> lookup(label: String): StateAndRef<C> {
val output = label.output
val newOutput = TransactionState(output.data as C, output.notary)
return StateAndRef(newOutput, label.outputRef)
}
private inner class InternalWireTransactionDSL : WireTransactionDSL() { private inner class InternalWireTransactionDSL : WireTransactionDSL() {
fun finaliseAndInsertLabels(): WireTransaction { fun finaliseAndInsertLabels(): WireTransaction {
@ -257,8 +270,8 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
for ((index, labelledState) in outStates.withIndex()) { for ((index, labelledState) in outStates.withIndex()) {
if (labelledState.label != null) { if (labelledState.label != null) {
labelToRefs[labelledState.label] = StateRef(wtx.id, index) labelToRefs[labelledState.label] = StateRef(wtx.id, index)
if (stateType.isInstance(labelledState.state)) { if (stateType.isInstance(labelledState.state.data)) {
labelToOutputs[labelledState.label] = labelledState.state as T labelToOutputs[labelledState.label] = labelledState.state as TransactionState<T>
} }
outputsToLabels[labelledState.state] = labelledState.label outputsToLabels[labelledState.state] = labelledState.label
} }
@ -269,20 +282,20 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
private val rootTxns = ArrayList<WireTransaction>() private val rootTxns = ArrayList<WireTransaction>()
private val labelToRefs = HashMap<String, StateRef>() private val labelToRefs = HashMap<String, StateRef>()
private val labelToOutputs = HashMap<String, T>() private val labelToOutputs = HashMap<String, TransactionState<T>>()
private val outputsToLabels = HashMap<ContractState, String>() private val outputsToLabels = HashMap<TransactionState<*>, String>()
fun labelForState(state: T): String? = outputsToLabels[state] fun labelForState(output: TransactionState<*>): String? = outputsToLabels[output]
inner class Roots { inner class Roots {
fun transaction(vararg outputStates: LabeledOutput) { fun transaction(vararg outputStates: LabeledOutput) {
val outs = outputStates.map { it.state } val outs = outputStates.map { it.state }
val wtx = WireTransaction(emptyList(), emptyList(), outs, emptyList()) val wtx = WireTransaction(emptyList(), emptyList(), outs, emptyList(), TransactionType.Business())
for ((index, state) in outputStates.withIndex()) { for ((index, state) in outputStates.withIndex()) {
val label = state.label!! val label = state.label!!
labelToRefs[label] = StateRef(wtx.id, index) labelToRefs[label] = StateRef(wtx.id, index)
outputsToLabels[state.state] = label outputsToLabels[state.state] = label
labelToOutputs[label] = state.state as T labelToOutputs[label] = state.state as TransactionState<T>
} }
rootTxns.add(wtx) rootTxns.add(wtx)
} }

View File

@ -314,7 +314,7 @@ object TwoPartyDealProtocol {
} }
override fun assembleSharedTX(handshake: Handshake<T>): Pair<TransactionBuilder, List<PublicKey>> { override fun assembleSharedTX(handshake: Handshake<T>): Pair<TransactionBuilder, List<PublicKey>> {
val ptx = handshake.payload.generateAgreement() val ptx = handshake.payload.generateAgreement(notary)
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt // And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one. // to have one.
@ -336,7 +336,7 @@ object TwoPartyDealProtocol {
val dealToFix: StateAndRef<T>, val dealToFix: StateAndRef<T>,
sessionID: Long, sessionID: Long,
val replacementProgressTracker: ProgressTracker? = null) : Secondary<StateRef>(otherSide, notary, sessionID) { val replacementProgressTracker: ProgressTracker? = null) : Secondary<StateRef>(otherSide, notary, sessionID) {
private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.nextFixingOf()!!.name) private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.data.nextFixingOf()!!.name)
override val progressTracker: ProgressTracker = replacementProgressTracker ?: createTracker() override val progressTracker: ProgressTracker = replacementProgressTracker ?: createTracker()
@ -358,11 +358,11 @@ object TwoPartyDealProtocol {
@Suspendable @Suspendable
override fun assembleSharedTX(handshake: Handshake<StateRef>): Pair<TransactionBuilder, List<PublicKey>> { override fun assembleSharedTX(handshake: Handshake<StateRef>): Pair<TransactionBuilder, List<PublicKey>> {
val fixOf = dealToFix.state.nextFixingOf()!! val fixOf = dealToFix.state.data.nextFixingOf()!!
// TODO Do we need/want to substitute in new public keys for the Parties? // TODO Do we need/want to substitute in new public keys for the Parties?
val myName = serviceHub.storageService.myLegalIdentity.name val myName = serviceHub.storageService.myLegalIdentity.name
val deal: T = dealToFix.state val deal: T = dealToFix.state.data
val myOldParty = deal.parties.single { it.name == myName } val myOldParty = deal.parties.single { it.name == myName }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -373,7 +373,7 @@ object TwoPartyDealProtocol {
val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0], fixOf, BigDecimal.ZERO, BigDecimal.ONE) { val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0], fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
@Suspendable @Suspendable
override fun beforeSigning(fix: Fix) { override fun beforeSigning(fix: Fix) {
newDeal.generateFix(ptx, oldRef, fix) newDeal.generateFix(ptx, dealToFix, fix)
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt // And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one. // to have one.

View File

@ -55,18 +55,13 @@ object NotaryChangeProtocol {
progressTracker.currentStep = SIGNING progressTracker.currentStep = SIGNING
val signatures = mutableListOf<DigitalSignature.WithKey>()
val myKey = serviceHub.storageService.myLegalIdentity.owningKey val myKey = serviceHub.storageService.myLegalIdentity.owningKey
val me = listOf(myKey) val me = listOf(myKey)
if (participants == me) { val signatures = if (participants == me) {
signatures.add(getNotarySignature(stx.tx)) listOf(getNotarySignature(stx.tx))
} else { } else {
val participantSessions = collectSignatures(participants - me, signatures, stx) collectSignatures(participants - me, stx)
signatures.add(getNotarySignature(stx.tx))
participantSessions.forEach { send(TOPIC_CHANGE, it.first.address, it.second, signatures) }
} }
val finalTx = stx + signatures val finalTx = stx + signatures
@ -77,9 +72,9 @@ object NotaryChangeProtocol {
private fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> { private fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
val state = originalState.state val state = originalState.state
val newState = state.withNewNotary(newNotary) val newState = state.withNewNotary(newNotary)
val participants = state.participants val participants = state.data.participants
val cmd = Command(ChangeNotary(), participants) val cmd = Command(ChangeNotary(), participants)
val tx = TransactionBuilder().withItems(originalState.ref, newState, cmd) val tx = TransactionBuilder(type = TransactionType.NotaryChange()).withItems(originalState, newState, cmd)
tx.signWith(serviceHub.storageService.myLegalIdentityKey) tx.signWith(serviceHub.storageService.myLegalIdentityKey)
val stx = tx.toSignedTransaction(false) val stx = tx.toSignedTransaction(false)
@ -87,21 +82,22 @@ object NotaryChangeProtocol {
} }
@Suspendable @Suspendable
private fun collectSignatures(participants: List<PublicKey>, signatures: MutableCollection<DigitalSignature.WithKey>, private fun collectSignatures(participants: List<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
stx: SignedTransaction): MutableList<Pair<NodeInfo, Long>> { val sessions = mutableMapOf<NodeInfo, Long>()
val participantSessions = mutableListOf<Pair<NodeInfo, Long>>()
participants.forEach { val participantSignatures = participants.map {
val participantNode = serviceHub.networkMapCache.getNodeByPublicKey(it) ?: val participantNode = serviceHub.networkMapCache.getNodeByPublicKey(it) ?:
throw IllegalStateException("Participant $it to state $originalState not found on the network") throw IllegalStateException("Participant $it to state $originalState not found on the network")
val sessionIdForSend = random63BitValue() val sessionIdForSend = random63BitValue()
val participantSignature = getParticipantSignature(participantNode, stx, sessionIdForSend) sessions[participantNode] = sessionIdForSend
signatures.add(participantSignature)
participantSessions.add(participantNode to sessionIdForSend) getParticipantSignature(participantNode, stx, sessionIdForSend)
} }
return participantSessions val allSignatures = participantSignatures + getNotarySignature(stx.tx)
sessions.forEach { send(TOPIC_CHANGE, it.key.address, it.value, allSignatures) }
return allSignatures
} }
@Suspendable @Suspendable
@ -125,7 +121,7 @@ object NotaryChangeProtocol {
@Suspendable @Suspendable
private fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable { private fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol(wtx)) return subProtocol(NotaryProtocol.Client(wtx))
} }
} }
@ -153,19 +149,20 @@ object NotaryChangeProtocol {
val mySignature = sign(proposedTx) val mySignature = sign(proposedTx)
val swapSignatures = sendAndReceive<List<DigitalSignature.WithKey>>(TOPIC_CHANGE, otherSide, sessionIdForSend, sessionIdForReceive, mySignature) val swapSignatures = sendAndReceive<List<DigitalSignature.WithKey>>(TOPIC_CHANGE, otherSide, sessionIdForSend, sessionIdForReceive, mySignature)
val allSignatures = swapSignatures.validate { val allSignatures = swapSignatures.validate { signatures ->
it.forEach { it.verifyWithECDSA(proposedTx.txBits) } signatures.forEach { it.verifyWithECDSA(proposedTx.txBits) }
it signatures
} }
val finalTx = proposedTx + allSignatures val finalTx = proposedTx + allSignatures
finalTx.verify()
serviceHub.recordTransactions(listOf(finalTx)) serviceHub.recordTransactions(listOf(finalTx))
} }
@Suspendable @Suspendable
private fun validateTx(stx: SignedTransaction): SignedTransaction { private fun validateTx(stx: SignedTransaction): SignedTransaction {
checkDependenciesValid(stx) checkDependenciesValid(stx)
checkContractValid(stx) checkValid(stx)
checkCommand(stx.tx) checkCommand(stx.tx)
return stx return stx
} }
@ -184,7 +181,7 @@ object NotaryChangeProtocol {
subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide)) subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide))
} }
private fun checkContractValid(stx: SignedTransaction) { private fun checkValid(stx: SignedTransaction) {
val ltx = stx.tx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments) val ltx = stx.tx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
serviceHub.verifyTransaction(ltx) serviceHub.verifyTransaction(ltx)
} }

View File

@ -15,24 +15,22 @@ import kotlin.test.assertNotEquals
val TEST_PROGRAM_ID = TransactionGroupTests.TestCash() val TEST_PROGRAM_ID = TransactionGroupTests.TestCash()
class TransactionGroupTests { class TransactionGroupTests {
val A_THOUSAND_POUNDS = TestCash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY, DUMMY_NOTARY) val A_THOUSAND_POUNDS = TestCash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY)
class TestCash : Contract { class TestCash : Contract {
override val legalContractReference = SecureHash.sha256("TestCash") override val legalContractReference = SecureHash.sha256("TestCash")
override fun verify(tx: TransactionForVerification) { override fun verify(tx: TransactionForContract) {
} }
data class State( data class State(
val deposit: PartyAndReference, val deposit: PartyAndReference,
val amount: Amount<Currency>, val amount: Amount<Currency>,
override val owner: PublicKey, override val owner: PublicKey) : OwnableState {
override val notary: Party) : OwnableState {
override val contract: Contract = TEST_PROGRAM_ID override val contract: Contract = TEST_PROGRAM_ID
override val participants: List<PublicKey> override val participants: List<PublicKey>
get() = listOf(owner) get() = listOf(owner)
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner)) override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
} }
@ -44,12 +42,13 @@ class TransactionGroupTests {
} }
infix fun TestCash.State.`owned by`(owner: PublicKey) = copy(owner = owner) infix fun TestCash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
infix fun TestCash.State.`with notary`(notary: Party) = TransactionState(this, notary)
@Test @Test
fun success() { fun success() {
transactionGroup { transactionGroup {
roots { roots {
transaction(A_THOUSAND_POUNDS label "£1000") transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000")
} }
transaction { transaction {
@ -122,17 +121,17 @@ class TransactionGroupTests {
// We have to do this manually without the DSL because transactionGroup { } won't let us create a tx that // We have to do this manually without the DSL because transactionGroup { } won't let us create a tx that
// points nowhere. // points nowhere.
val input = generateStateRef() val input = StateAndRef(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY, generateStateRef())
tg.txns += TransactionBuilder().apply { tg.txns += TransactionBuilder().apply {
addInputState(input) addInputState(input)
addOutputState(A_THOUSAND_POUNDS) addOutputState(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY)
addCommand(TestCash.Commands.Move(), BOB_PUBKEY) addCommand(TestCash.Commands.Move(), BOB_PUBKEY)
}.toWireTransaction() }.toWireTransaction()
val e = assertFailsWith(TransactionResolutionException::class) { val e = assertFailsWith(TransactionResolutionException::class) {
tg.verify() tg.verify()
} }
assertEquals(e.hash, input.txhash) assertEquals(e.hash, input.ref.txhash)
} }
@Test @Test
@ -140,7 +139,7 @@ class TransactionGroupTests {
// Check that a transaction cannot refer to the same input more than once. // Check that a transaction cannot refer to the same input more than once.
transactionGroup { transactionGroup {
roots { roots {
transaction(A_THOUSAND_POUNDS label "£1000") transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000")
} }
transaction { transaction {

View File

@ -33,20 +33,17 @@ class AttachmentClassLoaderTests {
} }
class AttachmentDummyContract : Contract { class AttachmentDummyContract : Contract {
data class State(val magicNumber: Int = 0, data class State(val magicNumber: Int = 0) : ContractState {
override val notary: Party) : ContractState {
override val contract = ATTACHMENT_TEST_PROGRAM_ID override val contract = ATTACHMENT_TEST_PROGRAM_ID
override val participants: List<PublicKey> override val participants: List<PublicKey>
get() = listOf() get() = listOf()
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
} }
interface Commands : CommandData { interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands class Create : TypeOnlyCommandData(), Commands
} }
override fun verify(tx: TransactionForVerification) { override fun verify(tx: TransactionForContract) {
// Always accepts. // Always accepts.
} }
@ -54,7 +51,7 @@ class AttachmentClassLoaderTests {
override val legalContractReference: SecureHash = SecureHash.sha256("") override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder { fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary) val state = TransactionState(State(magicNumber), notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey)) return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
} }
} }
@ -220,7 +217,7 @@ class AttachmentClassLoaderTests {
val copiedWireTransaction = bytes.deserialize() val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size) assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0] as AttachmentDummyContract.State).magicNumber) assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
} }
@Test @Test
@ -250,8 +247,8 @@ class AttachmentClassLoaderTests {
val copiedWireTransaction = bytes.deserialize(kryo2) val copiedWireTransaction = bytes.deserialize(kryo2)
assertEquals(1, copiedWireTransaction.outputs.size) assertEquals(1, copiedWireTransaction.outputs.size)
val contract2 = copiedWireTransaction.outputs[0].contract as DummyContractBackdoor val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0])) assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
} }
@Test @Test

View File

@ -1,7 +1,6 @@
package com.r3corda.core.serialization package com.r3corda.core.serialization
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.testing.MockStorageService import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.seconds import com.r3corda.core.seconds
@ -21,20 +20,18 @@ class TransactionSerializationTests {
class TestCash : Contract { class TestCash : Contract {
override val legalContractReference = SecureHash.sha256("TestCash") override val legalContractReference = SecureHash.sha256("TestCash")
override fun verify(tx: TransactionForVerification) { override fun verify(tx: TransactionForContract) {
} }
data class State( data class State(
val deposit: PartyAndReference, val deposit: PartyAndReference,
val amount: Amount<Currency>, val amount: Amount<Currency>,
override val owner: PublicKey, override val owner: PublicKey) : OwnableState {
override val notary: Party) : OwnableState {
override val contract: Contract = TEST_PROGRAM_ID override val contract: Contract = TEST_PROGRAM_ID
override val participants: List<PublicKey> override val participants: List<PublicKey>
get() = listOf(owner) get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner)) override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
} }
interface Commands : CommandData { interface Commands : CommandData {
class Move() : TypeOnlyCommandData(), Commands class Move() : TypeOnlyCommandData(), Commands
@ -46,21 +43,24 @@ class TransactionSerializationTests {
// Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change). // Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change).
// It refers to a fake TX/state that we don't bother creating here. // It refers to a fake TX/state that we don't bother creating here.
val depositRef = MINI_CORP.ref(1) val depositRef = MINI_CORP.ref(1)
val outputState = TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY)
val changeState = TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY)
val fakeStateRef = generateStateRef() val fakeStateRef = generateStateRef()
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public), DUMMY_NOTARY)
lateinit var tx: TransactionBuilder lateinit var tx: TransactionBuilder
@Before @Before
fun setup() { fun setup() {
tx = TransactionBuilder().withItems( tx = TransactionBuilder().withItems(
fakeStateRef, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public)) inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
) )
} }
@Test @Test
fun signWireTX() { fun signWireTX() {
tx.signWith(DUMMY_NOTARY_KEY)
tx.signWith(TestUtils.keypair) tx.signWith(TestUtils.keypair)
val signedTX = tx.toSignedTransaction() val signedTX = tx.toSignedTransaction()
@ -82,6 +82,7 @@ class TransactionSerializationTests {
} }
tx.signWith(TestUtils.keypair) tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_NOTARY_KEY)
val signedTX = tx.toSignedTransaction() val signedTX = tx.toSignedTransaction()
// Cannot construct with an empty sigs list. // Cannot construct with an empty sigs list.
@ -91,8 +92,9 @@ class TransactionSerializationTests {
// If the signature was replaced in transit, we don't like it. // If the signature was replaced in transit, we don't like it.
assertFailsWith(SignatureException::class) { assertFailsWith(SignatureException::class) {
val tx2 = TransactionBuilder().withItems(fakeStateRef, outputState, changeState, val tx2 = TransactionBuilder().withItems(inputState, outputState, changeState,
Command(TestCash.Commands.Move(), TestUtils.keypair2.public)) Command(TestCash.Commands.Move(), TestUtils.keypair2.public))
tx2.signWith(DUMMY_NOTARY_KEY)
tx2.signWith(TestUtils.keypair2) tx2.signWith(TestUtils.keypair2)
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify() signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()

View File

@ -1,10 +1,7 @@
package com.r3corda.node.api package com.r3corda.node.api
import com.r3corda.core.contracts.*
import com.r3corda.node.api.StatesQuery import com.r3corda.node.api.StatesQuery
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.serialization.SerializedBytes import com.r3corda.core.serialization.SerializedBytes
@ -43,7 +40,7 @@ interface APIServer {
*/ */
fun queryStates(query: StatesQuery): List<StateRef> fun queryStates(query: StatesQuery): List<StateRef>
fun fetchStates(states: List<StateRef>): Map<StateRef, ContractState?> fun fetchStates(states: List<StateRef>): Map<StateRef, TransactionState<ContractState>?>
/** /**
* Query for immutable transactions (results can be cached indefinitely by their id/hash). * Query for immutable transactions (results can be cached indefinitely by their id/hash).

View File

@ -27,7 +27,7 @@ class APIServerImpl(val node: AbstractNode) : APIServer {
return states.values.map { it.ref } return states.values.map { it.ref }
} else if (query.criteria is StatesQuery.Criteria.Deal) { } else if (query.criteria is StatesQuery.Criteria.Deal) {
val states = node.services.walletService.linearHeadsOfType<DealState>().filterValues { val states = node.services.walletService.linearHeadsOfType<DealState>().filterValues {
it.state.ref == query.criteria.ref it.state.data.ref == query.criteria.ref
} }
return states.values.map { it.ref } return states.values.map { it.ref }
} }
@ -35,7 +35,7 @@ class APIServerImpl(val node: AbstractNode) : APIServer {
return emptyList() return emptyList()
} }
override fun fetchStates(states: List<StateRef>): Map<StateRef, ContractState?> { override fun fetchStates(states: List<StateRef>): Map<StateRef, TransactionState<ContractState>?> {
return node.services.walletService.statesForRefs(states) return node.services.walletService.statesForRefs(states)
} }

View File

@ -102,7 +102,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
lateinit var smm: StateMachineManager lateinit var smm: StateMachineManager
lateinit var wallet: WalletService lateinit var wallet: WalletService
lateinit var keyManagement: E2ETestKeyManagementService lateinit var keyManagement: E2ETestKeyManagementService
lateinit var notaryChangeService: NotaryChangeService
var inNodeNetworkMapService: NetworkMapService? = null var inNodeNetworkMapService: NetworkMapService? = null
var inNodeNotaryService: NotaryService? = null var inNodeNotaryService: NotaryService? = null
lateinit var identity: IdentityService lateinit var identity: IdentityService
@ -130,9 +129,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
net = makeMessagingService() net = makeMessagingService()
wallet = NodeWalletService(services) wallet = NodeWalletService(services)
makeInterestRatesOracleService() makeInterestRatesOracleService()
api = APIServerImpl(this)
notaryChangeService = NotaryChangeService(net, smm)
identity = makeIdentityService() identity = makeIdentityService()
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
@ -144,6 +140,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
// This object doesn't need to be referenced from this class because it registers handlers on the network // This object doesn't need to be referenced from this class because it registers handlers on the network
// service and so that keeps it from being collected. // service and so that keeps it from being collected.
DataVendingService(net, storage) DataVendingService(net, storage)
NotaryChangeService(net, smm)
buildAdvertisedServices() buildAdvertisedServices()

View File

@ -85,7 +85,7 @@ class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single() val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
// Do we have any more days left in this deal's lifetime? If not, return. // Do we have any more days left in this deal's lifetime? If not, return.
val nextFixingDate = theDealRef.state.calculation.nextFixingDate() ?: return null val nextFixingDate = theDealRef.state.data.calculation.nextFixingDate() ?: return null
extraNodeLabels[node1] = "Fixing event on $nextFixingDate" extraNodeLabels[node1] = "Fixing event on $nextFixingDate"
extraNodeLabels[node2] = "Fixing event on $nextFixingDate" extraNodeLabels[node2] = "Fixing event on $nextFixingDate"

View File

@ -1,5 +1,6 @@
package com.r3corda.node.internal.testing package com.r3corda.node.internal.testing
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.DummyContract import com.r3corda.core.contracts.DummyContract
import com.r3corda.core.contracts.StateRef import com.r3corda.core.contracts.StateRef
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
@ -10,20 +11,20 @@ import com.r3corda.node.internal.AbstractNode
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef { fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary) val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
tx.signWith(node.storage.myLegalIdentityKey) tx.signWith(node.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY) tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction() val stx = tx.toSignedTransaction()
node.services.recordTransactions(listOf(stx)) node.services.recordTransactions(listOf(stx))
return StateRef(stx.id, 0) return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
} }
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef { fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary) val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
tx.setTime(Instant.now(), notary, 30.seconds) tx.setTime(Instant.now(), notary, 30.seconds)
tx.signWith(node.storage.myLegalIdentityKey) tx.signWith(node.storage.myLegalIdentityKey)
val stx = tx.toSignedTransaction(false) val stx = tx.toSignedTransaction(false)
node.services.recordTransactions(listOf(stx)) node.services.recordTransactions(listOf(stx))
return StateRef(stx.id, 0) return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
} }

View File

@ -1,6 +1,5 @@
package com.r3corda.node.services package com.r3corda.node.services
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.messaging.MessagingService import com.r3corda.core.messaging.MessagingService
import com.r3corda.core.messaging.SingleMessageRecipient import com.r3corda.core.messaging.SingleMessageRecipient
import com.r3corda.node.services.api.AbstractNodeService import com.r3corda.node.services.api.AbstractNodeService
@ -41,13 +40,14 @@ class NotaryChangeService(net: MessagingService, val smm: StateMachineManager) :
* TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal * TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal
*/ */
private fun checkProposal(proposal: NotaryChangeProtocol.Proposal): Boolean { private fun checkProposal(proposal: NotaryChangeProtocol.Proposal): Boolean {
val state = smm.serviceHub.loadState(proposal.stateRef)
if (state is Cash.State) return false // TODO: delete this example check
val newNotary = proposal.newNotary val newNotary = proposal.newNotary
val isNotary = smm.serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary } val isNotary = smm.serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary }
require(isNotary) { "The proposed node $newNotary does not run a Notary service " } require(isNotary) { "The proposed node $newNotary does not run a Notary service " }
// An example requirement
val blacklist = listOf("Evil Notary")
require(!blacklist.contains(newNotary.name))
return true return true
} }
} }

View File

@ -51,7 +51,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
*/ */
override val linearHeads: Map<SecureHash, StateAndRef<LinearState>> override val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
get() = mutex.locked { wallet }.let { wallet -> get() = mutex.locked { wallet }.let { wallet ->
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.thread }.mapValues { it.value } wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.data.thread }.mapValues { it.value }
} }
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet { override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
@ -103,8 +103,8 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
private fun Wallet.update(tx: WireTransaction, ourKeys: Set<PublicKey>): Pair<Wallet, Wallet.Update> { private fun Wallet.update(tx: WireTransaction, ourKeys: Set<PublicKey>): Pair<Wallet, Wallet.Update> {
val ourNewStates = tx.outputs. val ourNewStates = tx.outputs.
filter { isRelevant(it, ourKeys) }. filter { isRelevant(it.data, ourKeys) }.
map { tx.outRef<ContractState>(it) } map { tx.outRef<ContractState>(it.data) }
// Now calculate the states that are being spent by this transaction. // Now calculate the states that are being spent by this transaction.
val consumed: Set<StateRef> = states.map { it.ref }.intersect(tx.inputs) val consumed: Set<StateRef> = states.map { it.ref }.intersect(tx.inputs)

View File

@ -24,7 +24,7 @@ class WalletImpl(override val states: List<StateAndRef<ContractState>>) : Wallet
*/ */
override val cashBalances: Map<Currency, Amount<Currency>> get() = states. override val cashBalances: Map<Currency, Amount<Currency>> get() = states.
// Select the states we own which are cash, ignore the rest, take the amounts. // Select the states we own which are cash, ignore the rest, take the amounts.
mapNotNull { (it.state as? Cash.State)?.amount }. mapNotNull { (it.state.data as? Cash.State)?.amount }.
// Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) } // Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
groupBy { it.token.product }. groupBy { it.token.product }.
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together. // Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.

View File

@ -99,6 +99,5 @@
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360", "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
"tradeID": "tradeXXX", "tradeID": "tradeXXX",
"hashLegalDocs": "put hash here" "hashLegalDocs": "put hash here"
}, }
"notary": "Notary Service"
} }

View File

@ -468,7 +468,7 @@ class TwoPartyTradeProtocolTests {
attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> { attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> {
val ap = transaction { val ap = transaction {
output("alice's paper") { output("alice's paper") {
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days, notary) CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days)
} }
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() } arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
if (!withError) if (!withError)

View File

@ -4,6 +4,7 @@ import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.testing.CASH import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`issued by` import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by` import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.testing.`with notary`
import com.r3corda.core.bd import com.r3corda.core.bd
import com.r3corda.core.contracts.DOLLARS import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.Fix import com.r3corda.core.contracts.Fix
@ -11,6 +12,7 @@ import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.testing.ALICE_PUBKEY import com.r3corda.core.testing.ALICE_PUBKEY
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.MEGA_CORP import com.r3corda.core.testing.MEGA_CORP
import com.r3corda.core.testing.MEGA_CORP_KEY import com.r3corda.core.testing.MEGA_CORP_KEY
import com.r3corda.core.utilities.BriefLogFormatter import com.r3corda.core.utilities.BriefLogFormatter
@ -117,5 +119,5 @@ class NodeInterestRatesTest {
assertEquals("0.678".bd, fix.value) assertEquals("0.678".bd, fix.value)
} }
private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY)) private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY))
} }

View File

@ -51,14 +51,14 @@ class NodeWalletServiceTest {
val w = wallet.currentWallet val w = wallet.currentWallet
assertEquals(3, w.states.size) assertEquals(3, w.states.size)
val state = w.states[0].state as Cash.State val state = w.states[0].state.data as Cash.State
val myIdentity = services.storageService.myLegalIdentity val myIdentity = services.storageService.myLegalIdentity
val myPartyRef = myIdentity.ref(ref) val myPartyRef = myIdentity.ref(ref)
assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount) assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount)
assertEquals(ALICE_PUBKEY, state.owner) assertEquals(ALICE_PUBKEY, state.owner)
assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state as Cash.State).amount) assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state.data as Cash.State).amount)
assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state as Cash.State).amount) assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state.data as Cash.State).amount)
} }
@Test @Test
@ -77,12 +77,14 @@ class NodeWalletServiceTest {
val spendTX = TransactionBuilder().apply { val spendTX = TransactionBuilder().apply {
Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput)) Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput))
signWith(freshKey) signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction() }.toSignedTransaction()
// A tx that doesn't send us anything. // A tx that doesn't send us anything.
val irrelevantTX = TransactionBuilder().apply { val irrelevantTX = TransactionBuilder().apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY) Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction() }.toSignedTransaction()
assertNull(wallet.cashBalances[USD]) assertNull(wallet.cashBalances[USD])

View File

@ -1,6 +1,8 @@
package com.r3corda.node.services package com.r3corda.node.services
import com.r3corda.core.contracts.TransactionBuilder import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.node.services.UniquenessException import com.r3corda.core.node.services.UniquenessException
import com.r3corda.core.testing.MEGA_CORP import com.r3corda.core.testing.MEGA_CORP
import com.r3corda.core.testing.generateStateRef import com.r3corda.core.testing.generateStateRef
@ -15,7 +17,8 @@ class UniquenessProviderTests {
@Test fun `should commit a transaction with unused inputs without exception`() { @Test fun `should commit a transaction with unused inputs without exception`() {
val provider = InMemoryUniquenessProvider() val provider = InMemoryUniquenessProvider()
val inputState = generateStateRef() val inputState = generateStateRef()
val tx = TransactionBuilder().withItems(inputState).toWireTransaction() val tx = buildTransaction(inputState)
provider.commit(tx, identity) provider.commit(tx, identity)
} }
@ -23,10 +26,10 @@ class UniquenessProviderTests {
val provider = InMemoryUniquenessProvider() val provider = InMemoryUniquenessProvider()
val inputState = generateStateRef() val inputState = generateStateRef()
val tx1 = TransactionBuilder().withItems(inputState).toWireTransaction() val tx1 = buildTransaction(inputState)
provider.commit(tx1, identity) provider.commit(tx1, identity)
val tx2 = TransactionBuilder().withItems(inputState).toWireTransaction() val tx2 = buildTransaction(inputState)
val ex = assertFailsWith<UniquenessException> { provider.commit(tx2, identity) } val ex = assertFailsWith<UniquenessException> { provider.commit(tx2, identity) }
val consumingTx = ex.error.stateHistory[inputState]!! val consumingTx = ex.error.stateHistory[inputState]!!
@ -34,4 +37,6 @@ class UniquenessProviderTests {
assertEquals(consumingTx.inputIndex, tx1.inputs.indexOf(inputState)) assertEquals(consumingTx.inputIndex, tx1.inputs.indexOf(inputState))
assertEquals(consumingTx.requestingParty, identity) assertEquals(consumingTx.requestingParty, identity)
} }
private fun buildTransaction(inputState: StateRef) = WireTransaction(listOf(inputState), emptyList(), emptyList(), emptyList(), TransactionType.Business())
} }

View File

@ -2,6 +2,7 @@ package com.r3corda.node.visualiser
import com.r3corda.core.contracts.CommandData import com.r3corda.core.contracts.CommandData
import com.r3corda.core.contracts.ContractState import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.TransactionState
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.testing.TransactionGroupDSL import com.r3corda.core.testing.TransactionGroupDSL
import org.graphstream.graph.Edge import org.graphstream.graph.Edge
@ -30,7 +31,7 @@ class GraphVisualiser(val dsl: TransactionGroupDSL<in ContractState>) {
val node = graph.addNode<Node>(tx.outRef<ContractState>(outIndex).ref.toString()) val node = graph.addNode<Node>(tx.outRef<ContractState>(outIndex).ref.toString())
val state = tx.outputs[outIndex] val state = tx.outputs[outIndex]
node.label = stateToLabel(state) node.label = stateToLabel(state)
node.styleClass = stateToCSSClass(state) + ",state" node.styleClass = stateToCSSClass(state.data) + ",state"
node.setAttribute("state", state) node.setAttribute("state", state)
val edge = graph.addEdge<Edge>("tx$txIndex-out$outIndex", txNode, node, true) val edge = graph.addEdge<Edge>("tx$txIndex-out$outIndex", txNode, node, true)
edge.weight = 0.7 edge.weight = 0.7
@ -55,8 +56,8 @@ class GraphVisualiser(val dsl: TransactionGroupDSL<in ContractState>) {
return graph return graph
} }
private fun stateToLabel(state: ContractState): String { private fun stateToLabel(state: TransactionState<*>): String {
return dsl.labelForState(state) ?: stateToTypeName(state) return dsl.labelForState(state) ?: stateToTypeName(state.data)
} }
private fun commandToTypeName(state: CommandData) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.') private fun commandToTypeName(state: CommandData) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.')

View File

@ -1,14 +1,12 @@
package node.services package node.services
import com.r3corda.contracts.DummyContract import com.r3corda.contracts.DummyContract
import com.r3corda.core.contracts.StateAndRef import com.r3corda.core.contracts.*
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import com.r3corda.node.internal.testing.MockNetwork import com.r3corda.node.internal.testing.MockNetwork
import com.r3corda.node.internal.testing.issueState
import com.r3corda.node.services.transactions.NotaryService import com.r3corda.node.services.transactions.NotaryService
import com.r3corda.node.testutils.issueState
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import protocols.NotaryChangeProtocol import protocols.NotaryChangeProtocol
@ -35,12 +33,12 @@ class NotaryChangeTests {
@Test @Test
fun `should change notary for a state with single participant`() { fun `should change notary for a state with single participant`() {
val state = issueState(clientNodeA, DUMMY_NOTARY) val ref = issueState(clientNodeA, DUMMY_NOTARY).ref
val ref = clientNodeA.services.loadState(state) val state = clientNodeA.services.loadState(ref)
val newNotary = newNotaryNode.info.identity val newNotary = newNotaryNode.info.identity
val protocol = Instigator(StateAndRef(ref, state), newNotary) val protocol = Instigator(StateAndRef(state, ref), newNotary)
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol) val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
net.runNetwork() net.runNetwork()
@ -51,11 +49,10 @@ class NotaryChangeTests {
@Test @Test
fun `should change notary for a state with multiple participants`() { fun `should change notary for a state with multiple participants`() {
val state = DummyContract.MultiOwnerState(0, val state = TransactionState(DummyContract.MultiOwnerState(0,
listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey), listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey)), DUMMY_NOTARY)
DUMMY_NOTARY)
val tx = TransactionBuilder().withItems(state) val tx = TransactionBuilder(type = TransactionType.NotaryChange()).withItems(state)
tx.signWith(clientNodeA.storage.myLegalIdentityKey) tx.signWith(clientNodeA.storage.myLegalIdentityKey)
tx.signWith(clientNodeB.storage.myLegalIdentityKey) tx.signWith(clientNodeB.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY) tx.signWith(DUMMY_NOTARY_KEY)

View File

@ -1,10 +1,7 @@
package com.r3corda.demos package com.r3corda.demos
import com.r3corda.contracts.cash.Cash import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.DOLLARS import com.r3corda.core.contracts.*
import com.r3corda.core.contracts.FixOf
import com.r3corda.core.contracts.`issued by`
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.logElapsedTime import com.r3corda.core.logElapsedTime
import com.r3corda.core.node.NodeInfo import com.r3corda.core.node.NodeInfo
@ -87,7 +84,7 @@ fun main(args: Array<String>) {
// Make a garbage transaction that includes a rate fix. // Make a garbage transaction that includes a rate fix.
val tx = TransactionBuilder() val tx = TransactionBuilder()
tx.addOutputState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.keyManagement.freshKey().public, notary.identity)) tx.addOutputState(TransactionState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.keyManagement.freshKey().public), notary.identity))
val protocol = RatesFixProtocol(tx, oracleNode, fixOf, expectedRate, rateTolerance) val protocol = RatesFixProtocol(tx, oracleNode, fixOf, expectedRate, rateTolerance)
node.smm.add("demo.ratefix", protocol).get() node.smm.add("demo.ratefix", protocol).get()
node.stop() node.stop()

View File

@ -356,7 +356,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
// Sign it as ourselves. // Sign it as ourselves.
tx.signWith(keyPair) tx.signWith(keyPair)
// Get the notary to sign it, thus committing the outputs. // Get the notary to sign the timestamp
val notarySig = subProtocol(NotaryProtocol.Client(tx.toWireTransaction())) val notarySig = subProtocol(NotaryProtocol.Client(tx.toWireTransaction()))
tx.addSignatureUnchecked(notarySig) tx.addSignatureUnchecked(notarySig)

View File

@ -44,14 +44,14 @@ class InterestRateSwapAPI(val api: APIServer) {
private fun getDealByRef(ref: String): InterestRateSwap.State? { private fun getDealByRef(ref: String): InterestRateSwap.State? {
val states = api.queryStates(StatesQuery.selectDeal(ref)) val states = api.queryStates(StatesQuery.selectDeal(ref))
return if (states.isEmpty()) null else { return if (states.isEmpty()) null else {
val deals = api.fetchStates(states).values.map { it as InterestRateSwap.State }.filterNotNull() val deals = api.fetchStates(states).values.map { it?.data as InterestRateSwap.State }.filterNotNull()
return if (deals.isEmpty()) null else deals[0] return if (deals.isEmpty()) null else deals[0]
} }
} }
private fun getAllDeals(): Array<InterestRateSwap.State> { private fun getAllDeals(): Array<InterestRateSwap.State> {
val states = api.queryStates(StatesQuery.selectAllDeals()) val states = api.queryStates(StatesQuery.selectAllDeals())
val swaps = api.fetchStates(states).values.map { it as InterestRateSwap.State }.filterNotNull().toTypedArray() val swaps = api.fetchStates(states).values.map { it?.data as InterestRateSwap.State }.filterNotNull().toTypedArray()
return swaps return swaps
} }

View File

@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import com.r3corda.contracts.InterestRateSwap import com.r3corda.contracts.InterestRateSwap
import com.r3corda.core.contracts.DealState import com.r3corda.core.contracts.DealState
import com.r3corda.core.contracts.StateAndRef import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.TransactionState
import com.r3corda.core.node.NodeInfo import com.r3corda.core.node.NodeInfo
import com.r3corda.core.node.services.linearHeadsOfType import com.r3corda.core.node.services.linearHeadsOfType
import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.protocols.ProtocolLogic
@ -13,6 +14,7 @@ import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.demos.DemoClock import com.r3corda.demos.DemoClock
import com.r3corda.node.internal.Node import com.r3corda.node.internal.Node
import com.r3corda.node.services.network.MockNetworkMapCache import com.r3corda.node.services.network.MockNetworkMapCache
import com.r3corda.node.utilities.ANSIProgressRenderer
import com.r3corda.protocols.TwoPartyDealProtocol import com.r3corda.protocols.TwoPartyDealProtocol
import java.time.LocalDate import java.time.LocalDate
@ -41,12 +43,12 @@ object UpdateBusinessDayProtocol {
// Get deals // Get deals
progressTracker.currentStep = FETCHING progressTracker.currentStep = FETCHING
val dealStateRefs = serviceHub.walletService.linearHeadsOfType<DealState>() val dealStateRefs = serviceHub.walletService.linearHeadsOfType<DealState>()
val otherPartyToDeals = dealStateRefs.values.groupBy { otherParty(it.state) } val otherPartyToDeals = dealStateRefs.values.groupBy { otherParty(it.state.data) }
// TODO we need to process these in parallel to stop there being an ordering problem across more than two nodes // TODO we need to process these in parallel to stop there being an ordering problem across more than two nodes
val sortedParties = otherPartyToDeals.keys.sortedBy { it.identity.name } val sortedParties = otherPartyToDeals.keys.sortedBy { it.identity.name }
for (party in sortedParties) { for (party in sortedParties) {
val sortedDeals = otherPartyToDeals[party]!!.sortedBy { it.state.ref } val sortedDeals = otherPartyToDeals[party]!!.sortedBy { it.state.data.ref }
for (deal in sortedDeals) { for (deal in sortedDeals) {
progressTracker.currentStep = ITERATING_DEALS progressTracker.currentStep = ITERATING_DEALS
processDeal(party, deal, date, sessionID) processDeal(party, deal, date, sessionID)
@ -64,9 +66,9 @@ object UpdateBusinessDayProtocol {
// TODO we should make this more object oriented when we can ask a state for it's contract // TODO we should make this more object oriented when we can ask a state for it's contract
@Suspendable @Suspendable
fun processDeal(party: NodeInfo, deal: StateAndRef<DealState>, date: LocalDate, sessionID: Long) { fun processDeal(party: NodeInfo, deal: StateAndRef<DealState>, date: LocalDate, sessionID: Long) {
val s = deal.state val s = deal.state.data
when (s) { when (s) {
is InterestRateSwap.State -> processInterestRateSwap(party, StateAndRef(s, deal.ref), date, sessionID) is InterestRateSwap.State -> processInterestRateSwap(party, StateAndRef(TransactionState(s, deal.state.notary), deal.ref), date, sessionID)
} }
} }
@ -74,7 +76,7 @@ object UpdateBusinessDayProtocol {
@Suspendable @Suspendable
fun processInterestRateSwap(party: NodeInfo, deal: StateAndRef<InterestRateSwap.State>, date: LocalDate, sessionID: Long) { fun processInterestRateSwap(party: NodeInfo, deal: StateAndRef<InterestRateSwap.State>, date: LocalDate, sessionID: Long) {
var dealStateAndRef: StateAndRef<InterestRateSwap.State>? = deal var dealStateAndRef: StateAndRef<InterestRateSwap.State>? = deal
var nextFixingDate = deal.state.calculation.nextFixingDate() var nextFixingDate = deal.state.data.calculation.nextFixingDate()
while (nextFixingDate != null && !nextFixingDate.isAfter(date)) { while (nextFixingDate != null && !nextFixingDate.isAfter(date)) {
progressTracker.currentStep = ITERATING_FIXINGS progressTracker.currentStep = ITERATING_FIXINGS
/* /*
@ -83,12 +85,12 @@ object UpdateBusinessDayProtocol {
* One of the parties needs to take the lead in the coordination and this is a reliable deterministic way * One of the parties needs to take the lead in the coordination and this is a reliable deterministic way
* to do it. * to do it.
*/ */
if (party.identity.name == deal.state.fixedLeg.fixedRatePayer.name) { if (party.identity.name == deal.state.data.fixedLeg.fixedRatePayer.name) {
dealStateAndRef = nextFixingFloatingLeg(dealStateAndRef!!, party, sessionID) dealStateAndRef = nextFixingFloatingLeg(dealStateAndRef!!, party, sessionID)
} else { } else {
dealStateAndRef = nextFixingFixedLeg(dealStateAndRef!!, party, sessionID) dealStateAndRef = nextFixingFixedLeg(dealStateAndRef!!, party, sessionID)
} }
nextFixingDate = dealStateAndRef?.state?.calculation?.nextFixingDate() nextFixingDate = dealStateAndRef?.state?.data?.calculation?.nextFixingDate()
} }
} }
@ -98,7 +100,7 @@ object UpdateBusinessDayProtocol {
progressTracker.currentStep = FIXING progressTracker.currentStep = FIXING
val myName = serviceHub.storageService.myLegalIdentity.name val myName = serviceHub.storageService.myLegalIdentity.name
val deal: InterestRateSwap.State = dealStateAndRef.state val deal: InterestRateSwap.State = dealStateAndRef.state.data
val myOldParty = deal.parties.single { it.name == myName } val myOldParty = deal.parties.single { it.name == myName }
val keyPair = serviceHub.keyManagementService.toKeyPair(myOldParty.owningKey) val keyPair = serviceHub.keyManagementService.toKeyPair(myOldParty.owningKey)
val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.notaryNodes[0], dealStateAndRef, val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.notaryNodes[0], dealStateAndRef,