diff --git a/contracts/isolated/src/main/kotlin/com/r3corda/contracts/AnotherDummyContract.kt b/contracts/isolated/src/main/kotlin/com/r3corda/contracts/AnotherDummyContract.kt index 79aee3b9fe..35b88403b4 100644 --- a/contracts/isolated/src/main/kotlin/com/r3corda/contracts/AnotherDummyContract.kt +++ b/contracts/isolated/src/main/kotlin/com/r3corda/contracts/AnotherDummyContract.kt @@ -18,19 +18,17 @@ import java.security.PublicKey val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract() 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 participants: List get() = emptyList() - - override fun withNewNotary(newNotary: Party) = copy(notary = newNotary) } interface Commands : CommandData { class Create : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForVerification) { + override fun verify(tx: TransactionForContract) { // Always accepts. } @@ -38,7 +36,7 @@ class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdo override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org") override fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder { - val state = State(magicNumber, notary) + val state = TransactionState(State(magicNumber), notary) return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey)) } diff --git a/contracts/src/main/java/com/r3corda/contracts/JavaCommercialPaper.java b/contracts/src/main/java/com/r3corda/contracts/JavaCommercialPaper.java index 5c323b85fd..43b31cef30 100644 --- a/contracts/src/main/java/com/r3corda/contracts/JavaCommercialPaper.java +++ b/contracts/src/main/java/com/r3corda/contracts/JavaCommercialPaper.java @@ -1,10 +1,11 @@ package com.r3corda.contracts; +import com.google.common.collect.ImmutableList; import com.r3corda.contracts.cash.Cash; import com.r3corda.contracts.cash.CashKt; import com.r3corda.contracts.cash.InsufficientBalanceException; import com.r3corda.core.contracts.*; -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.Party; import com.r3corda.core.crypto.SecureHash; @@ -14,7 +15,6 @@ import org.jetbrains.annotations.Nullable; import java.security.PublicKey; import java.time.Instant; import java.util.Currency; -import java.util.ArrayList; import java.util.List; import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand; @@ -34,38 +34,36 @@ public class JavaCommercialPaper implements Contract { private PublicKey owner; private Amount> faceValue; private Instant maturityDate; - private Party notary; public State() { } // For serialization public State(PartyAndReference issuance, PublicKey owner, Amount> faceValue, - Instant maturityDate, Party notary) { + Instant maturityDate) { this.issuance = issuance; this.owner = owner; this.faceValue = faceValue; this.maturityDate = maturityDate; - this.notary = notary; } public State copy() { - return new State(this.issuance, this.owner, this.faceValue, this.maturityDate, this.notary); + return new State(this.issuance, this.owner, this.faceValue, this.maturityDate); } public ICommercialPaperState withOwner(PublicKey newOwner) { - return new State(this.issuance, newOwner, this.faceValue, this.maturityDate, this.notary); + return new State(this.issuance, newOwner, this.faceValue, this.maturityDate); } public ICommercialPaperState withIssuance(PartyAndReference newIssuance) { - return new State(newIssuance, this.owner, this.faceValue, this.maturityDate, this.notary); + return new State(newIssuance, this.owner, this.faceValue, this.maturityDate); } public ICommercialPaperState withFaceValue(Amount> newFaceValue) { - return new State(this.issuance, this.owner, newFaceValue, this.maturityDate, this.notary); + return new State(this.issuance, this.owner, newFaceValue, this.maturityDate); } public ICommercialPaperState withMaturityDate(Instant newMaturityDate) { - return new State(this.issuance, this.owner, this.faceValue, newMaturityDate, this.notary); + return new State(this.issuance, this.owner, this.faceValue, newMaturityDate); } public PartyAndReference getIssuance() { @@ -84,12 +82,6 @@ public class JavaCommercialPaper implements Contract { return maturityDate; } - @NotNull - @Override - public Party getNotary() { - return notary; - } - @NotNull @Override 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 (owner != null ? !owner.equals(state.owner) : state.owner != null) return false; if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false; - if (notary != null ? !notary.equals(state.notary) : state.notary != null) return false; return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null); } @@ -117,26 +108,17 @@ public class JavaCommercialPaper implements Contract { result = 31 * result + (owner != null ? owner.hashCode() : 0); result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0); result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0); - result = 31 * result + (notary != null ? notary.hashCode() : 0); return result; } public State withoutOwner() { - return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate, notary); - } - - @NotNull - @Override - public ContractState withNewNotary(@NotNull Party newNotary) { - return new State(this.issuance, this.owner, this.faceValue, this.maturityDate, newNotary); + return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate); } @NotNull @Override public List getParticipants() { - List keys = new ArrayList<>(); - keys.add(this.owner); - return keys; + return ImmutableList.of(this.owner); } } @@ -164,7 +146,7 @@ public class JavaCommercialPaper implements Contract { } @Override - public void verify(@NotNull TransactionForVerification tx) { + public void verify(@NotNull TransactionForContract tx) { // There are three possible things that can be done with CP. // Issuance, trading (aka moving in this prototype) and redeeming. // Each command has it's own set of restrictions which the verify function ... verifies. @@ -248,19 +230,20 @@ public class JavaCommercialPaper implements Contract { } public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount faceValue, @Nullable Instant maturityDate, @NotNull Party notary) { - State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate, notary); - return new TransactionBuilder().withItems(state, new Command(new Commands.Issue(), issuance.getParty().getOwningKey())); + State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate); + 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 paper, List> wallet) throws InsufficientBalanceException { - new Cash().generateSpend(tx, paper.getState().getFaceValue(), paper.getState().getOwner(), wallet); - tx.addInputState(paper.getRef()); - tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getOwner())); + new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet); + tx.addInputState(paper); + tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner())); } public void generateMove(TransactionBuilder tx, StateAndRef paper, PublicKey newOwner) { - tx.addInputState(paper.getRef()); - tx.addOutputState(new State(paper.getState().getIssuance(), newOwner, paper.getState().getFaceValue(), paper.getState().getMaturityDate(), paper.getState().getNotary())); - tx.addCommand(new Command(new Commands.Move(), paper.getState().getOwner())); + tx.addInputState(paper); + tx.addOutputState(new TransactionState<>(new State(paper.getState().getData().getIssuance(), newOwner, paper.getState().getData().getFaceValue(), paper.getState().getData().getMaturityDate()), paper.getState().getNotary())); + tx.addCommand(new Command(new Commands.Move(), paper.getState().getData().getOwner())); } } diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/CommercialPaper.kt b/contracts/src/main/kotlin/com/r3corda/contracts/CommercialPaper.kt index 48040dbdc7..dd0c665bb7 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/CommercialPaper.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/CommercialPaper.kt @@ -11,7 +11,7 @@ import com.r3corda.core.crypto.toStringShort import com.r3corda.core.utilities.Emoji import java.security.PublicKey import java.time.Instant -import java.util.Currency +import java.util.* /** * This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate @@ -46,8 +46,7 @@ class CommercialPaper : Contract { val issuance: PartyAndReference, override val owner: PublicKey, val faceValue: Amount>, - val maturityDate: Instant, - override val notary: Party + val maturityDate: Instant ) : OwnableState, ICommercialPaperState { override val contract = CP_PROGRAM_ID override val participants: List @@ -63,8 +62,6 @@ class CommercialPaper : Contract { override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance) override fun withFaceValue(newFaceValue: Amount>): ICommercialPaperState = copy(faceValue = newFaceValue) override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate) - - override fun withNewNotary(newNotary: Party) = copy(notary = newNotary) } interface Commands : CommandData { @@ -75,7 +72,7 @@ class CommercialPaper : Contract { class Issue : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForVerification) { + override fun verify(tx: TransactionForContract) { // Group by everything except owner: any modification to the CP at all is considered changing it fundamentally. val groups = tx.groupStates() { it: State -> it.withoutOwner() } @@ -141,7 +138,7 @@ class CommercialPaper : Contract { */ fun generateIssue(faceValue: Amount>, maturityDate: Instant, notary: Party): TransactionBuilder { 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)) } @@ -149,9 +146,9 @@ class CommercialPaper : Contract { * Updates the given partial transaction with an input/output/command to reassign ownership of the paper. */ fun generateMove(tx: TransactionBuilder, paper: StateAndRef, newOwner: PublicKey) { - tx.addInputState(paper.ref) - tx.addOutputState(paper.state.copy(owner = newOwner)) - tx.addCommand(Commands.Move(), paper.state.owner) + tx.addInputState(paper) + tx.addOutputState(TransactionState(paper.state.data.copy(owner = newOwner), paper.state.notary)) + tx.addCommand(Commands.Move(), paper.state.data.owner) } /** @@ -164,10 +161,10 @@ class CommercialPaper : Contract { @Throws(InsufficientBalanceException::class) fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef, wallet: List>) { // Add the cash movement using the states in our wallet. - val amount = paper.state.faceValue.let { amount -> Amount(amount.quantity, amount.token.product) } - Cash().generateSpend(tx, amount, paper.state.owner, wallet) - tx.addInputState(paper.ref) - tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.owner) + val amount = paper.state.data.faceValue.let { amount -> Amount(amount.quantity, amount.token.product) } + Cash().generateSpend(tx, amount, paper.state.data.owner, wallet) + tx.addInputState(paper) + tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner) } } diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/IRS.kt b/contracts/src/main/kotlin/com/r3corda/contracts/IRS.kt index 774e7f8f45..a812ec1e6f 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/IRS.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/IRS.kt @@ -487,7 +487,7 @@ class InterestRateSwap() : Contract { /** * verify() with some examples of what needs to be checked. */ - override fun verify(tx: TransactionForVerification) { + override fun verify(tx: TransactionForContract) { // Group by Trade ID for in / out states val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID } @@ -587,8 +587,7 @@ class InterestRateSwap() : Contract { val fixedLeg: FixedLeg, val floatingLeg: FloatingLeg, val calculation: Calculation, - val common: Common, - override val notary: Party + val common: Common ) : FixableDealState { override val contract = IRS_PROGRAM_ID @@ -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, oldStateRef: StateRef, fix: Fix) { - InterestRateSwap().generateFix(ptx, StateAndRef(this, oldStateRef), Pair(fix.of.forDay, Rate(RatioUnit(fix.value)))) + override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) { + InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, oldState.state.notary), oldState.ref), Pair(fix.of.forDay, Rate(RatioUnit(fix.value)))) } override fun nextFixingOf(): FixOf? { @@ -720,8 +718,8 @@ class InterestRateSwap() : Contract { val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule) // Put all the above into a new State object. - val state = State(fixedLeg, floatingLeg, newCalculation, common, notary) - return TransactionBuilder().withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey))) + val state = TransactionState(State(fixedLeg, floatingLeg, newCalculation, common), notary) + 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 { @@ -734,8 +732,11 @@ class InterestRateSwap() : Contract { // TODO: Replace with rates oracle fun generateFix(tx: TransactionBuilder, irs: StateAndRef, fixing: Pair) { - tx.addInputState(irs.ref) - tx.addOutputState(irs.state.copy(calculation = irs.state.calculation.applyFixing(fixing.first, FixedRate(fixing.second)))) - tx.addCommand(Commands.Fix(), listOf(irs.state.floatingLeg.floatingRatePayer.owningKey, irs.state.fixedLeg.fixedRatePayer.owningKey)) + tx.addInputState(irs) + tx.addOutputState( + 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)) } } diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/cash/Cash.kt b/contracts/src/main/kotlin/com/r3corda/contracts/cash/Cash.kt index 4e19b69609..be46a304b1 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/cash/Cash.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/cash/Cash.kt @@ -49,12 +49,10 @@ class Cash : FungibleAsset() { override val amount: Amount>, /** There must be a MoveCommand signed by this key to claim the amount */ - override val owner: PublicKey, - - override val notary: Party + override val owner: PublicKey ) : FungibleAsset.State { - constructor(deposit: PartyAndReference, amount: Amount, owner: PublicKey, notary: Party) - : this(Amount(amount.quantity, Issued(deposit, amount.token)), owner, notary) + constructor(deposit: PartyAndReference, amount: Amount, owner: PublicKey) + : this(Amount(amount.quantity, Issued(deposit, amount.token)), owner) override val deposit: PartyAndReference get() = amount.token.issuer override val contract = CASH_PROGRAM_ID @@ -66,7 +64,6 @@ class Cash : FungibleAsset() { 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 withNewNotary(newNotary: Party) = copy(notary = newNotary) } // Just for grouping @@ -97,9 +94,9 @@ class Cash : FungibleAsset() { */ fun generateIssue(tx: TransactionBuilder, amount: Amount>, owner: PublicKey, notary: Party) { check(tx.inputStates().isEmpty()) - check(tx.outputStates().sumCashOrNull() == null) + check(tx.outputStates().map { it.data }.sumCashOrNull() == null) 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) } @@ -146,9 +143,9 @@ class Cash : FungibleAsset() { val currency = amount.token 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) - ofCurrency.filter { it.state.deposit.party in onlyFromParties } + ofCurrency.filter { it.state.data.deposit.party in onlyFromParties } else ofCurrency } @@ -159,7 +156,7 @@ class Cash : FungibleAsset() { for (c in acceptableCoins) { if (gatheredAmount >= amount) break gathered.add(c) - gatheredAmount += Amount(c.state.amount.quantity, currency) + gatheredAmount += Amount(c.state.data.amount.quantity, currency) takeChangeFrom = c } @@ -167,30 +164,30 @@ class Cash : FungibleAsset() { throw InsufficientBalanceException(amount - gatheredAmount) val change = if (takeChangeFrom != null && gatheredAmount > amount) { - Amount>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.issuanceDef) + Amount>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.state.issuanceDef) } else { 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 totalAmount = coins.map { it.state.amount }.sumOrThrow() - State(totalAmount, to, coins.first().state.notary) + val totalAmount = coins.map { it.state.data.amount }.sumOrThrow() + TransactionState(State(totalAmount, to), coins.first().state.notary) } val outputs = if (change != null) { // Just copy a key across as the change key. In real life of course, this works but leaks private data. // In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow // value flows through the transaction graph. - val changeKey = gathered.first().state.owner + val changeKey = gathered.first().state.data.owner // Add a change output and adjust the last output downwards. states.subList(0, states.lastIndex) + - states.last().let { it.copy(amount = it.amount - change) } + - State(change, changeKey, gathered.last().state.notary) + states.last().let { TransactionState(it.data.copy(amount = it.data.amount - change), it.notary) } + + TransactionState(State(change, changeKey), gathered.last().state.notary) } else states - for (state in gathered) tx.addInputState(state.ref) + for (state in gathered) tx.addInputState(state) for (state in outputs) tx.addOutputState(state) // What if we already have a move command with the right keys? Filter it out here or in platform code? val keysList = keysUsed.toList() diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/cash/FungibleAsset.kt b/contracts/src/main/kotlin/com/r3corda/contracts/cash/FungibleAsset.kt index 417d16392e..a865e6bcd0 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/cash/FungibleAsset.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/cash/FungibleAsset.kt @@ -37,7 +37,6 @@ abstract class FungibleAsset : Contract { override val amount: Amount> /** There must be a MoveCommand signed by this key to claim the amount */ override val owner: PublicKey - override val notary: Party } // Just for grouping @@ -58,7 +57,7 @@ abstract class FungibleAsset : Contract { } /** This is the function EVERYONE runs */ - override fun verify(tx: TransactionForVerification) { + override fun verify(tx: TransactionForContract) { // Each group is a set of input/output states with distinct issuance definitions. These assets are not fungible // and must be kept separated for bookkeeping purposes. val groups = tx.groupStates() { it: FungibleAsset.State -> it.issuanceDef } @@ -97,7 +96,7 @@ abstract class FungibleAsset : Contract { private fun verifyIssueCommand(inputs: List>, outputs: List>, - tx: TransactionForVerification, + tx: TransactionForContract, issueCommand: AuthenticatedObject, token: Issued, issuer: Party) { diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/testing/TestUtils.kt b/contracts/src/main/kotlin/com/r3corda/contracts/testing/TestUtils.kt index eb575cf7b1..e1d01a5079 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/testing/TestUtils.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/testing/TestUtils.kt @@ -9,10 +9,11 @@ import com.r3corda.core.contracts.DUMMY_PROGRAM_ID import com.r3corda.core.contracts.DummyContract import com.r3corda.core.contracts.PartyAndReference import com.r3corda.core.contracts.Issued +import com.r3corda.core.contracts.ContractState +import com.r3corda.core.contracts.TransactionState import com.r3corda.core.crypto.NullPublicKey import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.generateKeyPair -import com.r3corda.core.testing.DUMMY_NOTARY import java.security.PublicKey import java.util.* @@ -26,7 +27,7 @@ val TEST_PROGRAM_MAP: Map> = mapOf( 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.`issued by`(party: Party) = copy(amount = Amount>(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party)))) infix fun Cash.State.`issued by`(deposit: PartyAndReference) = copy(amount = Amount>(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.`with notary`(notary: Party) = TransactionState(this, notary) infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner) 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 */ val Amount.CASH: Cash.State get() = Cash.State( Amount>(this.quantity, Issued(DUMMY_CASH_ISSUER, this.token)), - NullPublicKey, DUMMY_NOTARY) + NullPublicKey) -val Amount>.STATE: Cash.State get() = Cash.State(this, NullPublicKey, DUMMY_NOTARY) +val Amount>.STATE: Cash.State get() = Cash.State(this, NullPublicKey) +infix fun ContractState.`with notary`(notary: Party) = TransactionState(this, notary) diff --git a/contracts/src/main/kotlin/com/r3corda/protocols/TwoPartyTradeProtocol.kt b/contracts/src/main/kotlin/com/r3corda/protocols/TwoPartyTradeProtocol.kt index 21bafe4fb0..b91de13496 100644 --- a/contracts/src/main/kotlin/com/r3corda/protocols/TwoPartyTradeProtocol.kt +++ b/contracts/src/main/kotlin/com/r3corda/protocols/TwoPartyTradeProtocol.kt @@ -17,7 +17,7 @@ import com.r3corda.core.utilities.trace import java.security.KeyPair import java.security.PublicKey import java.security.SignatureException -import java.util.Currency +import java.util.* /** * This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer @@ -129,7 +129,7 @@ object TwoPartyTradeProtocol { // This verifies that the transaction is contract-valid, even though it is missing signatures. serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)) - if (wtx.outputs.sumCashBy(myKeyPair.public) != price) + if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public) != price) throw IllegalArgumentException("Transaction is not sending us the right amount of cash") // There are all sorts of funny games a malicious secondary might play here, we should fix them: @@ -221,7 +221,7 @@ object TwoPartyTradeProtocol { progressTracker.currentStep = VERIFYING maybeTradeRequest.validate { // What is the seller trying to sell us? - val asset = it.assetForSale.state + val asset = it.assetForSale.state.data val assetTypeName = asset.javaClass.name logger.trace { "Got trade request for a $assetTypeName: ${it.assetForSale}" } @@ -272,15 +272,15 @@ object TwoPartyTradeProtocol { val cashStates = wallet.statesOfType() val cashSigningPubKeys = Cash().generateSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey, cashStates) // Add inputs/outputs/a command for the movement of the asset. - ptx.addInputState(tradeRequest.assetForSale.ref) + ptx.addInputState(tradeRequest.assetForSale) // Just pick some new public key for now. This won't be linked with our identity in any way, which is what // we want for privacy reasons: the key is here ONLY to manage and control ownership, it is not intended to // reveal who the owner actually is. The key management service is expected to derive a unique key from some // initial seed in order to provide privacy protection. val freshKey = serviceHub.keyManagementService.freshKey() - val (command, state) = tradeRequest.assetForSale.state.withNewOwner(freshKey.public) - ptx.addOutputState(state) - ptx.addCommand(command, tradeRequest.assetForSale.state.owner) + val (command, state) = tradeRequest.assetForSale.state.data.withNewOwner(freshKey.public) + ptx.addOutputState(TransactionState(state, tradeRequest.assetForSale.state.notary)) + ptx.addCommand(command, tradeRequest.assetForSale.state.data.owner) // And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt // to have one. diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt index 80d7a8814b..6feacc0e6a 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt @@ -1,10 +1,7 @@ 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.testing.STATE +import com.r3corda.contracts.testing.* import com.r3corda.core.contracts.* import com.r3corda.core.crypto.SecureHash import com.r3corda.core.days @@ -32,8 +29,7 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate { MEGA_CORP.ref(123), MEGA_CORP_PUBKEY, 1000.DOLLARS `issued by` MEGA_CORP.ref(123), - TEST_TX_TIME + 7.days, - DUMMY_NOTARY + TEST_TX_TIME + 7.days ) override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue() @@ -46,8 +42,7 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate { issuance = MEGA_CORP.ref(123), owner = MEGA_CORP_PUBKEY, faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), - maturityDate = TEST_TX_TIME + 7.days, - notary = DUMMY_NOTARY + maturityDate = TEST_TX_TIME + 7.days ) override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue() @@ -121,7 +116,7 @@ class CommercialPaperTestsGeneric { fun `issue cannot replace an existing state`() { transactionGroup { roots { - transaction(thisTest.getPaper() label "paper") + transaction(thisTest.getPaper() `with notary` DUMMY_NOTARY label "paper") } transaction { input("paper") @@ -144,9 +139,9 @@ class CommercialPaperTestsGeneric { trade(destroyPaperAtRedemption = false).expectFailureOfTx(3, "must be destroyed") } - fun cashOutputsToWallet(vararg states: Cash.State): Pair>> { - val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*states), emptyList(), SecureHash.randomSHA256()) - return Pair(ltx, states.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) }) + fun cashOutputsToWallet(vararg outputs: TransactionState): Pair>> { + val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*outputs), emptyList(), SecureHash.randomSHA256(), TransactionType.Business()) + return Pair(ltx, outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) }) } @Test @@ -163,9 +158,9 @@ class CommercialPaperTestsGeneric { } 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, - 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 `with notary` DUMMY_NOTARY, + 3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY ) // Alice pays $9000 to MiniCorp to own some of their debt. @@ -181,8 +176,8 @@ class CommercialPaperTestsGeneric { // Won't be validated. val (corpWalletTX, corpWallet) = cashOutputsToWallet( - 9000.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 + 9000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY `with notary` DUMMY_NOTARY, + 4000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY `with notary` DUMMY_NOTARY ) fun makeRedeemTX(time: Instant): LedgerTransaction { @@ -213,8 +208,8 @@ class CommercialPaperTestsGeneric { val someProfits = 1200.DOLLARS `issued by` issuer return transactionGroupFor() { roots { - transaction(900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY label "alice's $900") - transaction(someProfits.STATE `owned by` MEGA_CORP_PUBKEY label "some profits") + transaction(900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY label "alice's $900") + transaction(someProfits.STATE `owned by` MEGA_CORP_PUBKEY `with notary` DUMMY_NOTARY label "some profits") } // Some CP is issued onto the ledger by MegaCorp. @@ -230,7 +225,7 @@ class CommercialPaperTestsGeneric { input("paper") input("alice's $900") output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } - output("alice's paper") { "paper".output `owned by` ALICE_PUBKEY } + output("alice's paper") { "paper".output.data `owned by` ALICE_PUBKEY } arg(ALICE_PUBKEY) { Cash.Commands.Move() } arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() } } @@ -244,7 +239,7 @@ class CommercialPaperTestsGeneric { output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY } output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY } if (!destroyPaperAtRedemption) - output { "paper".output } + output { "paper".output.data } arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt index d1d8a64c50..abf8f02199 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt @@ -97,7 +97,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State { dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") ) - InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY) + InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common) } 2 -> { // 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides) @@ -187,7 +187,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State { dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360") ) - return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY) + return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common) } else -> TODO("IRS number $irsSelect not defined") @@ -204,8 +204,7 @@ class IRSTests { exampleIRS.fixedLeg, exampleIRS.floatingLeg, exampleIRS.calculation, - exampleIRS.common, - DUMMY_NOTARY + exampleIRS.common ) val outState = inState.copy() @@ -255,7 +254,7 @@ class IRSTests { * Utility so I don't have to keep typing this */ fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State { - return generateIRSTxn(irsSelector).outputs.filterIsInstance().single() + return generateIRSTxn(irsSelector).outputs.map { it.data }.filterIsInstance().single() } /** @@ -287,7 +286,7 @@ class IRSTests { newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.value))) } - val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common, DUMMY_NOTARY) + val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common) println(newIRS.exportIRSToCSV()) } @@ -306,7 +305,7 @@ class IRSTests { @Test fun generateIRSandFixSome() { var previousTXN = generateIRSTxn(1) - var currentIRS = previousTXN.outputs.filterIsInstance().single() + var currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance().single() println(currentIRS.prettyPrint()) while (true) { val nextFixingDate = currentIRS.calculation.nextFixingDate() ?: break @@ -323,7 +322,7 @@ class IRSTests { } tx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments) } - currentIRS = previousTXN.outputs.filterIsInstance().single() + currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance().single() println(currentIRS.prettyPrint()) previousTXN = fixTX } @@ -387,11 +386,11 @@ class IRSTests { transaction("Fix") { input("irs post agreement") output("irs post first fixing") { - "irs post agreement".output.copy( - "irs post agreement".output.fixedLeg, - "irs post agreement".output.floatingLeg, - "irs post agreement".output.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), - "irs post agreement".output.common + "irs post agreement".output.data.copy( + "irs post agreement".output.data.fixedLeg, + "irs post agreement".output.data.floatingLeg, + "irs post agreement".output.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), + "irs post agreement".output.data.common ) } arg(ORACLE_PUBKEY) { @@ -678,7 +677,6 @@ class IRSTests { irs.floatingLeg, irs.calculation, irs.common.copy(tradeID = "t1") - ) } arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } @@ -692,7 +690,6 @@ class IRSTests { irs.floatingLeg, irs.calculation, irs.common.copy(tradeID = "t2") - ) } arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } @@ -703,19 +700,19 @@ class IRSTests { input("irs post agreement1") input("irs post agreement2") output("irs post first fixing1") { - "irs post agreement1".output.copy( - "irs post agreement1".output.fixedLeg, - "irs post agreement1".output.floatingLeg, - "irs post agreement1".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), - "irs post agreement1".output.common.copy(tradeID = "t1") + "irs post agreement1".output.data.copy( + "irs post agreement1".output.data.fixedLeg, + "irs post agreement1".output.data.floatingLeg, + "irs post agreement1".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), + "irs post agreement1".output.data.common.copy(tradeID = "t1") ) } output("irs post first fixing2") { - "irs post agreement2".output.copy( - "irs post agreement2".output.fixedLeg, - "irs post agreement2".output.floatingLeg, - "irs post agreement2".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), - "irs post agreement2".output.common.copy(tradeID = "t2") + "irs post agreement2".output.data.copy( + "irs post agreement2".output.data.fixedLeg, + "irs post agreement2".output.data.floatingLeg, + "irs post agreement2".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), + "irs post agreement2".output.data.common.copy(tradeID = "t2") ) } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/cash/CashTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/cash/CashTests.kt index 2b7ee70510..81406f338a 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/cash/CashTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/cash/CashTests.kt @@ -4,6 +4,7 @@ import com.r3corda.core.contracts.DummyContract import com.r3corda.contracts.testing.`issued by` import com.r3corda.contracts.testing.`owned by` import com.r3corda.contracts.testing.`with deposit` +import com.r3corda.contracts.testing.`with notary` import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash @@ -12,19 +13,14 @@ import com.r3corda.core.testing.* import org.junit.Test import java.security.PublicKey import java.util.* -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotEquals -import kotlin.test.assertNull -import kotlin.test.assertTrue +import kotlin.test.* class CashTests { val defaultRef = OpaqueBytes(ByteArray(1, {1})) val defaultIssuer = MEGA_CORP.ref(defaultRef) val inState = Cash.State( amount = 1000.DOLLARS `issued by` defaultIssuer, - owner = DUMMY_PUBKEY_1, - notary = DUMMY_NOTARY + owner = DUMMY_PUBKEY_1 ) val outState = inState.copy(owner = DUMMY_PUBKEY_2) @@ -71,7 +67,7 @@ class CashTests { fun issueMoney() { // Check we can't "move" money into existence. transaction { - input { DummyContract.State(notary = DUMMY_NOTARY) } + input { DummyContract.State() } output { outState } arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() } @@ -89,8 +85,7 @@ class CashTests { output { Cash.State( amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34), - owner = DUMMY_PUBKEY_1, - notary = DUMMY_NOTARY + owner = DUMMY_PUBKEY_1 ) } tweak { @@ -105,7 +100,7 @@ class CashTests { val ptx = TransactionBuilder() Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) assertTrue(ptx.inputStates().isEmpty()) - val s = ptx.outputStates()[0] as Cash.State + val s = ptx.outputStates()[0].data as Cash.State assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount) assertEquals(MINI_CORP, s.deposit.party) assertEquals(DUMMY_PUBKEY_1, s.owner) @@ -189,7 +184,7 @@ class CashTests { // Include the previously issued cash in a new issuance command ptx = TransactionBuilder() - ptx.addInputState(tx.tx.outRef(0).ref) + ptx.addInputState(tx.tx.outRef(0)) 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() { // Check we can do an atomic currency trade tx. 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 { pounds } output { inState `owned by` DUMMY_PUBKEY_2 } @@ -379,7 +374,7 @@ class CashTests { fun makeCash(amount: Amount, corp: Party, depositRef: Byte = 1) = 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)) ) @@ -400,7 +395,7 @@ class CashTests { fun generateSimpleDirectSpend() { val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP) 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]) } @@ -415,8 +410,8 @@ class CashTests { fun generateSimpleSpendWithChange() { val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP) 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.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1]) + assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data) + assertEquals(WALLET[0].state.data.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } @@ -425,7 +420,7 @@ class CashTests { val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP) assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[1].ref, wtx.inputs[1]) - assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `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]) } @@ -436,8 +431,8 @@ class CashTests { assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[1].ref, wtx.inputs[1]) assertEquals(WALLET[2].ref, wtx.inputs[2]) - assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0]) - assertEquals(WALLET[2].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1]) + assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data) + assertEquals(WALLET[2].state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1].data) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } @@ -458,9 +453,9 @@ class CashTests { */ @Test fun aggregation() { - val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP_PUBKEY, DUMMY_NOTARY) - val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP_PUBKEY, DUMMY_NOTARY) - val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY, DUMMY_NOTARY) + val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP_PUBKEY) + val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP_PUBKEY) + val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY) // Obviously it must be possible to aggregate states with themselves assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef) @@ -474,7 +469,7 @@ class CashTests { // States cannot be aggregated if the currency differs 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 assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef) @@ -484,9 +479,9 @@ class CashTests { @Test fun `summing by owner`() { val states = listOf( - Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY, DUMMY_NOTARY), - Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), - Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY) + Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY), + Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY), + Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY) ) assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP_PUBKEY)) } @@ -494,8 +489,8 @@ class CashTests { @Test(expected = UnsupportedOperationException::class) fun `summing by owner throws`() { val states = listOf( - Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), - Cash.State(4000.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) ) states.sumCashBy(MINI_CORP_PUBKEY) } @@ -516,9 +511,9 @@ class CashTests { @Test fun `summing a single currency`() { val states = listOf( - Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), - Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), - Cash.State(4000.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), + Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY) ) // Test that summing everything produces the total number of dollars var expected = 7000.DOLLARS `issued by` defaultIssuer @@ -529,8 +524,8 @@ class CashTests { @Test(expected = IllegalArgumentException::class) fun `summing multiple currencies`() { val states = listOf( - Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY), - Cash.State(4000.POUNDS `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) ) // Test that summing everything fails because we're mixing units states.sumCash() diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/ContractsDSL.kt b/core/src/main/kotlin/com/r3corda/core/contracts/ContractsDSL.kt index 75555e18b0..d7018b9c3e 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/ContractsDSL.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/ContractsDSL.kt @@ -92,7 +92,7 @@ fun List>.getTimestampByName(vararg names: Stri */ @Throws(IllegalArgumentException::class) // TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here? -inline fun verifyMoveCommands(inputs: List, tx: TransactionForVerification) { +inline fun verifyMoveCommands(inputs: List, tx: TransactionForContract) { // Now check the digital signatures on the move command. Every input has an owning public key, and we must // see a signature from each of those keys. The actual signatures have been verified against the transaction // data by the platform before execution. diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/DummyContract.kt b/core/src/main/kotlin/com/r3corda/core/contracts/DummyContract.kt index 5ebf2413ed..0205ea5695 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/DummyContract.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/DummyContract.kt @@ -9,30 +9,33 @@ import java.security.PublicKey val DUMMY_PROGRAM_ID = DummyContract() class DummyContract : Contract { - data class State(val magicNumber: Int = 0, - override val notary: Party) : ContractState { + data class State(val magicNumber: Int = 0) : ContractState { override val contract = DUMMY_PROGRAM_ID override val participants: List 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 + get() = listOf(owner) + + override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner)) } data class MultiOwnerState(val magicNumber: Int = 0, - val owners: List, - override val notary: Party) : ContractState { + val owners: List) : ContractState { override val contract = DUMMY_PROGRAM_ID override val participants: List get() = owners - - override fun withNewNotary(newNotary: Party) = copy(notary = newNotary) } interface Commands : CommandData { class Create : TypeOnlyCommandData(), Commands + class Move : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForVerification) { + override fun verify(tx: TransactionForContract) { // Always accepts. } @@ -40,7 +43,7 @@ class DummyContract : Contract { override val legalContractReference: SecureHash = SecureHash.sha256("") 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)) } } \ No newline at end of file diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt index 4a9dc75b9a..32bba6809d 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt @@ -27,17 +27,20 @@ interface ContractState { /** Contract by which the state belongs */ 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. */ val participants: List +} +/** A wrapper for [ContractState] containing additional platform-level state information. This is the state */ +data class TransactionState( + 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. - * To replace the notary, we need an approval (signature) from _all_ participants. + * Copies the underlying state, replacing the notary field with the new value. + * To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState] */ - fun withNewNotary(newNotary: Party): 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 * 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 * 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!) */ @@ -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. */ -data class StateAndRef(val state: T, val ref: StateRef) +data class StateAndRef(val state: TransactionState, val ref: StateRef) /** Filters a list of [StateAndRef] objects according to the type of the states */ inline fun List>.filterStatesOfType(): List> { - 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 */ data class Command(val value: CommandData, val signers: List) { + init { + require(signers.isNotEmpty()) + } constructor(data: CommandData, key: PublicKey) : this(data, listOf(key)) 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, - * the contract code is not run, and special platform-level validation logic is used instead + * Command that has to be signed by all participants of the states in the transaction + * in order to perform a notary change */ 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 * 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. */ @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 diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionBuilder.kt b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionBuilder.kt index 820d9221f0..4c5222c9a7 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionBuilder.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionBuilder.kt @@ -1,8 +1,5 @@ package com.r3corda.core.contracts -import com.r3corda.core.contracts.SignedTransaction -import com.r3corda.core.contracts.WireTransaction -import com.r3corda.core.contracts.* import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash @@ -22,8 +19,9 @@ import java.util.* */ class TransactionBuilder(private val inputs: MutableList = arrayListOf(), private val attachments: MutableList = arrayListOf(), - private val outputs: MutableList = arrayListOf(), - private val commands: MutableList = arrayListOf()) { + private val outputs: MutableList> = arrayListOf(), + private val commands: MutableList = arrayListOf(), + private val type: TransactionType = TransactionType.Business()) { val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull() @@ -49,8 +47,8 @@ class TransactionBuilder(private val inputs: MutableList = arrayListOf fun withItems(vararg items: Any): TransactionBuilder { for (t in items) { when (t) { - is StateRef -> addInputState(t) - is ContractState -> addOutputState(t) + is StateAndRef<*> -> addInputState(t) + is TransactionState<*> -> addOutputState(t) is Command -> addCommand(t) else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}") } @@ -96,7 +94,7 @@ class TransactionBuilder(private val inputs: MutableList = arrayListOf } fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments), - ArrayList(outputs), ArrayList(commands)) + ArrayList(outputs), ArrayList(commands), type) fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction { if (checkSufficientSignatures) { @@ -109,9 +107,15 @@ class TransactionBuilder(private val inputs: MutableList = arrayListOf return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs)) } - fun addInputState(ref: StateRef) { + fun addInputState(stateAndRef: StateAndRef<*>) { 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) { @@ -119,7 +123,7 @@ class TransactionBuilder(private val inputs: MutableList = arrayListOf attachments.add(attachment.id) } - fun addOutputState(state: ContractState) { + fun addOutputState(state: TransactionState<*>) { check(currentSigs.isEmpty()) outputs.add(state) } @@ -136,7 +140,7 @@ class TransactionBuilder(private val inputs: MutableList = arrayListOf // Accessors that yield immutable snapshots. fun inputStates(): List = ArrayList(inputs) - fun outputStates(): List = ArrayList(outputs) + fun outputStates(): List> = ArrayList(outputs) fun commands(): List = ArrayList(commands) fun attachments(): List = ArrayList(attachments) } \ No newline at end of file diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTools.kt b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTools.kt index d749e31e95..39325f4528 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTools.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTools.kt @@ -1,9 +1,5 @@ package com.r3corda.core.contracts -import com.r3corda.core.contracts.AuthenticatedObject -import com.r3corda.core.contracts.LedgerTransaction -import com.r3corda.core.contracts.SignedTransaction -import com.r3corda.core.contracts.WireTransaction import com.r3corda.core.node.services.AttachmentStorage import com.r3corda.core.node.services.IdentityService import java.io.FileNotFoundException @@ -22,7 +18,7 @@ fun WireTransaction.toLedgerTransaction(identityService: IdentityService, val attachments = attachments.map { attachmentStorage.openAttachment(it) ?: throw FileNotFoundException(it.toString()) } - return LedgerTransaction(inputs, attachments, outputs, authenticatedArgs, id) + return LedgerTransaction(inputs, attachments, outputs, authenticatedArgs, id, type) } /** diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTypes.kt b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTypes.kt new file mode 100644 index 0000000000..5c3421e397 --- /dev/null +++ b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionTypes.kt @@ -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() + val requiredSigners = tx.inStates.flatMap { it.data.participants } + check(command.signers.containsAll(requiredSigners)) + } catch (e: IllegalStateException) { + throw TransactionVerificationException.InvalidNotaryChange(tx) + } + } + } +} + diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionVerification.kt b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionVerification.kt index 91140de88d..d7c576a3c3 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionVerification.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionVerification.kt @@ -29,7 +29,7 @@ class TransactionGroup(val transactions: Set, val nonVerified val resolved = HashSet(transactions.size) for (tx in transactions) { - val inputs = ArrayList(tx.inputs.size) + val inputs = ArrayList>(tx.inputs.size) for (ref in tx.inputs) { val conflict = refToConsumingTXMap[ref] if (conflict != null) @@ -41,31 +41,27 @@ class TransactionGroup(val transactions: Set, val nonVerified // Look up the output in that transaction by index. inputs.add(ltx.outputs[ref.index]) } - resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id)) + resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id, tx.type)) } for (tx in resolved) tx.verify() return resolved } - } /** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */ -data class TransactionForVerification(val inStates: List, - val outStates: List, +data class TransactionForVerification(val inStates: List>, + val outStates: List>, val attachments: List, val commands: List>, - val origHash: SecureHash) { + val origHash: SecureHash, + val type: TransactionType) { override fun hashCode() = origHash.hashCode() override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash /** - * Verifies that the transaction is valid: - * - Checks that the input states and the timestamp point to the same Notary - * - Runs the contracts for this transaction. If any contract fails to verify, the whole transaction is - * considered to be invalid. In case of a special type of transaction, e.g. for changing notary for a state, - * runs custom platform level validation logic instead. + * Verifies that the transaction is valid by running type-specific validation logic. * * TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated. * @@ -73,68 +69,22 @@ data class TransactionForVerification(val inStates: List, * (the original is in the cause field) */ @Throws(TransactionVerificationException::class) - fun verify() { - verifySingleNotary() + fun verify() = type.verify(this) - 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 - val notary = inStates.first().notary - if (inStates.any { it.notary != notary }) throw TransactionVerificationException.MoreThanOneNotary(this) - val timestampCmd = commands.singleOrNull { it.value is TimestampCommand } ?: return - if (!timestampCmd.signers.contains(notary.owningKey)) throw TransactionVerificationException.MoreThanOneNotary(this) - } - - 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() { - try { - check(commands.size == 1) - inStates.zip(outStates).forEach { - // TODO: Check that input and output state(s) differ only by notary pointer - check(it.first.notary != it.second.notary) - } - } 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(val inputs: List, val outputs: List, 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) +/** + * A transaction to be passed as input to a contract verification function. Defines helper methods to + * simplify verification logic in contracts. + */ +data class TransactionForContract(val inStates: List, + val outStates: List, + val attachments: List, + val commands: List>, + val origHash: SecureHash) { + override fun hashCode() = origHash.hashCode() + override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash /** * 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, 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(val inputs: List, val outputs: List, 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() @@ -194,5 +162,6 @@ class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTra sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) { class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause) class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null) + class NotaryMissing(tx: TransactionForVerification) : TransactionVerificationException(tx, null) class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null) } \ No newline at end of file diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/Transactions.kt b/core/src/main/kotlin/com/r3corda/core/contracts/Transactions.kt index 8a4b3eea79..d7eb239e78 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/Transactions.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/Transactions.kt @@ -43,8 +43,9 @@ import java.security.SignatureException /** Transaction ready for serialisation, without any signatures attached. */ data class WireTransaction(val inputs: List, val attachments: List, - val outputs: List, - val commands: List) : NamedByHash { + val outputs: List>, + val commands: List, + val type: TransactionType) : NamedByHash { // Cache the serialised form of the transaction and its hash to give us fast access to it. @Volatile @Transient private var cachedBits: SerializedBytes? = null @@ -63,11 +64,11 @@ data class WireTransaction(val inputs: List, @Suppress("UNCHECKED_CAST") fun outRef(index: Int): StateAndRef { require(index >= 0 && index < outputs.size) - return StateAndRef(outputs[index] as T, StateRef(id, index)) + return StateAndRef(outputs[index] as TransactionState, StateRef(id, index)) } /** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */ - fun outRef(state: ContractState): StateAndRef = outRef(outputs.indexOfOrThrow(state)) + fun outRef(state: ContractState): StateAndRef = outRef(outputs.map { it.data }.indexOfOrThrow(state)) override fun toString(): String { val buf = StringBuilder() @@ -130,7 +131,7 @@ data class SignedTransaction(val txBits: SerializedBytes, return copy(sigs = sigs + sig) } - fun withAdditionalSignatures(sigList: Collection): SignedTransaction { + fun withAdditionalSignatures(sigList: Iterable): SignedTransaction { 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. */ val attachments: List, /** The states that will be generated by the execution of this transaction. */ - val outputs: List, + val outputs: List>, /** Arbitrary data passed to the program of each input state. */ val commands: List>, /** The hash of the original serialised WireTransaction */ - override val id: SecureHash + override val id: SecureHash, + val type: TransactionType ) : NamedByHash { @Suppress("UNCHECKED_CAST") - fun outRef(index: Int) = StateAndRef(outputs[index] as T, StateRef(id, index)) + fun outRef(index: Int) = StateAndRef(outputs[index] as TransactionState, StateRef(id, index)) } \ No newline at end of file diff --git a/core/src/main/kotlin/com/r3corda/core/node/ServiceHub.kt b/core/src/main/kotlin/com/r3corda/core/node/ServiceHub.kt index a0e8c253bb..4f526b5b55 100644 --- a/core/src/main/kotlin/com/r3corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/com/r3corda/core/node/ServiceHub.kt @@ -53,7 +53,7 @@ interface ServiceHub { * * @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) return definingTx.tx.outputs[stateRef.index] } diff --git a/core/src/main/kotlin/com/r3corda/core/node/services/Services.kt b/core/src/main/kotlin/com/r3corda/core/node/services/Services.kt index 5d2c106311..946208d72a 100644 --- a/core/src/main/kotlin/com/r3corda/core/node/services/Services.kt +++ b/core/src/main/kotlin/com/r3corda/core/node/services/Services.kt @@ -30,7 +30,7 @@ abstract class Wallet { abstract val states: List> @Suppress("UNCHECKED_CAST") - inline fun statesOfType() = states.filter { it.state is T } as List> + inline fun statesOfType() = states.filter { it.state.data is T } as List> /** * 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. */ @Suppress("UNCHECKED_CAST") fun linearHeadsOfType_(stateType: Class): Map> { - 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, it.value.ref) } } - fun statesForRefs(refs: List): Map { + fun statesForRefs(refs: List): Map?> { val refsToStates = currentWallet.states.associateBy { it.ref } return refs.associateBy({ it }, { refsToStates[it]?.state }) } diff --git a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt index f7f5a1cff7..588656484c 100644 --- a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt @@ -232,6 +232,7 @@ object WireTransactionSerializer : Serializer() { kryo.writeClassAndObject(output, obj.attachments) kryo.writeClassAndObject(output, obj.outputs) kryo.writeClassAndObject(output, obj.commands) + kryo.writeClassAndObject(output, obj.type) } @Suppress("UNCHECKED_CAST") @@ -258,10 +259,11 @@ object WireTransactionSerializer : Serializer() { } else javaClass.classLoader kryo.useClassLoader(classLoader) { - val outputs = kryo.readClassAndObject(input) as List + val outputs = kryo.readClassAndObject(input) as List> val commands = kryo.readClassAndObject(input) as List + 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 register(Issued::class.java, ImmutableClassSerializer(Issued::class)) + register(TransactionState::class.java, ImmutableClassSerializer(TransactionState::class)) noReferencesWithin() } diff --git a/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt b/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt index 604ebd6ad8..de92c7342a 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt @@ -4,12 +4,12 @@ package com.r3corda.core.testing import com.google.common.base.Throwables import com.google.common.net.HostAndPort -import com.r3corda.core.* import com.r3corda.core.contracts.* import com.r3corda.core.crypto.* -import com.r3corda.core.serialization.serialize import com.r3corda.core.node.services.testing.MockIdentityService import com.r3corda.core.node.services.testing.MockStorageService +import com.r3corda.core.seconds +import com.r3corda.core.serialization.serialize import java.net.ServerSocket import java.security.KeyPair import java.security.PublicKey @@ -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 -class LabeledOutput(val label: String?, val state: ContractState) { +class LabeledOutput(val label: String?, val state: TransactionState<*>) { override fun toString() = state.toString() + (if (label != null) " ($label)" else "") override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state) override fun hashCode(): Int = state.hashCode() } -infix fun ContractState.label(label: String) = LabeledOutput(label, this) +infix fun TransactionState<*>.label(label: String) = LabeledOutput(label, this) abstract class AbstractTransactionForTest { protected val attachments = ArrayList() protected val outStates = ArrayList() protected val commands = ArrayList() + 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> { 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 open class TransactionForTest : AbstractTransactionForTest() { - private val inStates = arrayListOf() - fun input(s: () -> ContractState) = inStates.add(s()) + private val inStates = arrayListOf>() + fun input(s: () -> ContractState) = inStates.add(TransactionState(s(), DUMMY_NOTARY)) protected fun runCommandsAndVerify(time: Instant) { val cmds = commandsToAuthenticatedObjects() - val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256()) + val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256(), type) tx.verify() } @@ -240,16 +245,24 @@ class TransactionGroupDSL(private val stateType: Class) { private val inStates = ArrayList() 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) } - 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 + get() = + labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found") val String.outputRef: StateRef get() = labelToRefs[this] ?: throw IllegalArgumentException("Unknown label \"$this\"") - fun lookup(label: String) = StateAndRef(label.output as C, label.outputRef) + fun lookup(label: String): StateAndRef { + val output = label.output + val newOutput = TransactionState(output.data as C, output.notary) + return StateAndRef(newOutput, label.outputRef) + } private inner class InternalWireTransactionDSL : WireTransactionDSL() { fun finaliseAndInsertLabels(): WireTransaction { @@ -257,8 +270,8 @@ class TransactionGroupDSL(private val stateType: Class) { for ((index, labelledState) in outStates.withIndex()) { if (labelledState.label != null) { labelToRefs[labelledState.label] = StateRef(wtx.id, index) - if (stateType.isInstance(labelledState.state)) { - labelToOutputs[labelledState.label] = labelledState.state as T + if (stateType.isInstance(labelledState.state.data)) { + labelToOutputs[labelledState.label] = labelledState.state as TransactionState } outputsToLabels[labelledState.state] = labelledState.label } @@ -269,20 +282,20 @@ class TransactionGroupDSL(private val stateType: Class) { private val rootTxns = ArrayList() private val labelToRefs = HashMap() - private val labelToOutputs = HashMap() - private val outputsToLabels = HashMap() + private val labelToOutputs = HashMap>() + private val outputsToLabels = HashMap, String>() - fun labelForState(state: T): String? = outputsToLabels[state] + fun labelForState(output: TransactionState<*>): String? = outputsToLabels[output] inner class Roots { fun transaction(vararg outputStates: LabeledOutput) { 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()) { val label = state.label!! labelToRefs[label] = StateRef(wtx.id, index) outputsToLabels[state.state] = label - labelToOutputs[label] = state.state as T + labelToOutputs[label] = state.state as TransactionState } rootTxns.add(wtx) } diff --git a/core/src/main/kotlin/com/r3corda/protocols/TwoPartyDealProtocol.kt b/core/src/main/kotlin/com/r3corda/protocols/TwoPartyDealProtocol.kt index 81e0a2bcaa..10eeb69d4a 100644 --- a/core/src/main/kotlin/com/r3corda/protocols/TwoPartyDealProtocol.kt +++ b/core/src/main/kotlin/com/r3corda/protocols/TwoPartyDealProtocol.kt @@ -314,7 +314,7 @@ object TwoPartyDealProtocol { } override fun assembleSharedTX(handshake: Handshake): Pair> { - val ptx = handshake.payload.generateAgreement() + val ptx = handshake.payload.generateAgreement(notary) // And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt // to have one. @@ -336,7 +336,7 @@ object TwoPartyDealProtocol { val dealToFix: StateAndRef, sessionID: Long, val replacementProgressTracker: ProgressTracker? = null) : Secondary(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() @@ -358,11 +358,11 @@ object TwoPartyDealProtocol { @Suspendable override fun assembleSharedTX(handshake: Handshake): Pair> { - val fixOf = dealToFix.state.nextFixingOf()!! + val fixOf = dealToFix.state.data.nextFixingOf()!! // TODO Do we need/want to substitute in new public keys for the Parties? val myName = serviceHub.storageService.myLegalIdentity.name - val deal: T = dealToFix.state + val deal: T = dealToFix.state.data val myOldParty = deal.parties.single { it.name == myName } @Suppress("UNCHECKED_CAST") @@ -373,7 +373,7 @@ object TwoPartyDealProtocol { val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0], fixOf, BigDecimal.ZERO, BigDecimal.ONE) { @Suspendable override fun beforeSigning(fix: Fix) { - newDeal.generateFix(ptx, oldRef, fix) + newDeal.generateFix(ptx, dealToFix, fix) // And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt // to have one. diff --git a/core/src/main/kotlin/protocols/NotaryChangeProtocol.kt b/core/src/main/kotlin/protocols/NotaryChangeProtocol.kt index f120266561..142d4cea38 100644 --- a/core/src/main/kotlin/protocols/NotaryChangeProtocol.kt +++ b/core/src/main/kotlin/protocols/NotaryChangeProtocol.kt @@ -55,18 +55,13 @@ object NotaryChangeProtocol { progressTracker.currentStep = SIGNING - val signatures = mutableListOf() - val myKey = serviceHub.storageService.myLegalIdentity.owningKey val me = listOf(myKey) - if (participants == me) { - signatures.add(getNotarySignature(stx.tx)) + val signatures = if (participants == me) { + listOf(getNotarySignature(stx.tx)) } else { - val participantSessions = collectSignatures(participants - me, signatures, stx) - signatures.add(getNotarySignature(stx.tx)) - - participantSessions.forEach { send(TOPIC_CHANGE, it.first.address, it.second, signatures) } + collectSignatures(participants - me, stx) } val finalTx = stx + signatures @@ -77,9 +72,9 @@ object NotaryChangeProtocol { private fun assembleTx(): Pair> { val state = originalState.state val newState = state.withNewNotary(newNotary) - val participants = state.participants + val participants = state.data.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) val stx = tx.toSignedTransaction(false) @@ -87,21 +82,22 @@ object NotaryChangeProtocol { } @Suspendable - private fun collectSignatures(participants: List, signatures: MutableCollection, - stx: SignedTransaction): MutableList> { - val participantSessions = mutableListOf>() + private fun collectSignatures(participants: List, stx: SignedTransaction): List { + val sessions = mutableMapOf() - participants.forEach { + val participantSignatures = participants.map { val participantNode = serviceHub.networkMapCache.getNodeByPublicKey(it) ?: throw IllegalStateException("Participant $it to state $originalState not found on the network") val sessionIdForSend = random63BitValue() - val participantSignature = getParticipantSignature(participantNode, stx, sessionIdForSend) - signatures.add(participantSignature) + sessions[participantNode] = sessionIdForSend - 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 @@ -125,7 +121,7 @@ object NotaryChangeProtocol { @Suspendable private fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable { progressTracker.currentStep = NOTARY - return subProtocol(NotaryProtocol(wtx)) + return subProtocol(NotaryProtocol.Client(wtx)) } } @@ -153,19 +149,20 @@ object NotaryChangeProtocol { val mySignature = sign(proposedTx) val swapSignatures = sendAndReceive>(TOPIC_CHANGE, otherSide, sessionIdForSend, sessionIdForReceive, mySignature) - val allSignatures = swapSignatures.validate { - it.forEach { it.verifyWithECDSA(proposedTx.txBits) } - it + val allSignatures = swapSignatures.validate { signatures -> + signatures.forEach { it.verifyWithECDSA(proposedTx.txBits) } + signatures } val finalTx = proposedTx + allSignatures + finalTx.verify() serviceHub.recordTransactions(listOf(finalTx)) } @Suspendable private fun validateTx(stx: SignedTransaction): SignedTransaction { checkDependenciesValid(stx) - checkContractValid(stx) + checkValid(stx) checkCommand(stx.tx) return stx } @@ -184,7 +181,7 @@ object NotaryChangeProtocol { subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide)) } - private fun checkContractValid(stx: SignedTransaction) { + private fun checkValid(stx: SignedTransaction) { val ltx = stx.tx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments) serviceHub.verifyTransaction(ltx) } diff --git a/core/src/main/resources/com/r3corda/core/node/isolated.jar b/core/src/main/resources/com/r3corda/core/node/isolated.jar index b2c8673b8c..315a6e0989 100644 Binary files a/core/src/main/resources/com/r3corda/core/node/isolated.jar and b/core/src/main/resources/com/r3corda/core/node/isolated.jar differ diff --git a/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt b/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt index d08b997b8e..4143aaffcd 100644 --- a/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt +++ b/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt @@ -15,24 +15,22 @@ import kotlin.test.assertNotEquals val TEST_PROGRAM_ID = TransactionGroupTests.TestCash() class TransactionGroupTests { - val A_THOUSAND_POUNDS = TestCash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY, DUMMY_NOTARY) + val A_THOUSAND_POUNDS = TestCash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY) class TestCash : Contract { override val legalContractReference = SecureHash.sha256("TestCash") - override fun verify(tx: TransactionForVerification) { + override fun verify(tx: TransactionForContract) { } data class State( val deposit: PartyAndReference, val amount: Amount, - override val owner: PublicKey, - override val notary: Party) : OwnableState { + override val owner: PublicKey) : OwnableState { override val contract: Contract = TEST_PROGRAM_ID override val participants: List get() = listOf(owner) - override fun withNewNotary(newNotary: Party) = copy(notary = newNotary) 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.`with notary`(notary: Party) = TransactionState(this, notary) @Test fun success() { transactionGroup { roots { - transaction(A_THOUSAND_POUNDS label "£1000") + transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000") } transaction { @@ -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 // points nowhere. - val input = generateStateRef() + val input = StateAndRef(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY, generateStateRef()) tg.txns += TransactionBuilder().apply { addInputState(input) - addOutputState(A_THOUSAND_POUNDS) + addOutputState(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY) addCommand(TestCash.Commands.Move(), BOB_PUBKEY) }.toWireTransaction() val e = assertFailsWith(TransactionResolutionException::class) { tg.verify() } - assertEquals(e.hash, input.txhash) + assertEquals(e.hash, input.ref.txhash) } @Test @@ -140,7 +139,7 @@ class TransactionGroupTests { // Check that a transaction cannot refer to the same input more than once. transactionGroup { roots { - transaction(A_THOUSAND_POUNDS label "£1000") + transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000") } transaction { diff --git a/core/src/test/kotlin/com/r3corda/core/node/AttachmentClassLoaderTests.kt b/core/src/test/kotlin/com/r3corda/core/node/AttachmentClassLoaderTests.kt index dcf392a2f3..4d390a9476 100644 --- a/core/src/test/kotlin/com/r3corda/core/node/AttachmentClassLoaderTests.kt +++ b/core/src/test/kotlin/com/r3corda/core/node/AttachmentClassLoaderTests.kt @@ -33,20 +33,17 @@ class AttachmentClassLoaderTests { } class AttachmentDummyContract : Contract { - data class State(val magicNumber: Int = 0, - override val notary: Party) : ContractState { + data class State(val magicNumber: Int = 0) : ContractState { override val contract = ATTACHMENT_TEST_PROGRAM_ID override val participants: List get() = listOf() - - override fun withNewNotary(newNotary: Party) = copy(notary = newNotary) } interface Commands : CommandData { class Create : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForVerification) { + override fun verify(tx: TransactionForContract) { // Always accepts. } @@ -54,7 +51,7 @@ class AttachmentClassLoaderTests { override val legalContractReference: SecureHash = SecureHash.sha256("") 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)) } } @@ -220,7 +217,7 @@ class AttachmentClassLoaderTests { val copiedWireTransaction = bytes.deserialize() assertEquals(1, copiedWireTransaction.outputs.size) - assertEquals(42, (copiedWireTransaction.outputs[0] as AttachmentDummyContract.State).magicNumber) + assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber) } @Test @@ -250,8 +247,8 @@ class AttachmentClassLoaderTests { val copiedWireTransaction = bytes.deserialize(kryo2) assertEquals(1, copiedWireTransaction.outputs.size) - val contract2 = copiedWireTransaction.outputs[0].contract as DummyContractBackdoor - assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0])) + val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor + assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data)) } @Test diff --git a/core/src/test/kotlin/com/r3corda/core/serialization/TransactionSerializationTests.kt b/core/src/test/kotlin/com/r3corda/core/serialization/TransactionSerializationTests.kt index 5917e8d87d..b605018e99 100644 --- a/core/src/test/kotlin/com/r3corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/com/r3corda/core/serialization/TransactionSerializationTests.kt @@ -1,7 +1,6 @@ package com.r3corda.core.serialization import com.r3corda.core.contracts.* -import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash import com.r3corda.core.node.services.testing.MockStorageService import com.r3corda.core.seconds @@ -21,20 +20,18 @@ class TransactionSerializationTests { class TestCash : Contract { override val legalContractReference = SecureHash.sha256("TestCash") - override fun verify(tx: TransactionForVerification) { + override fun verify(tx: TransactionForContract) { } data class State( val deposit: PartyAndReference, val amount: Amount, - override val owner: PublicKey, - override val notary: Party) : OwnableState { + override val owner: PublicKey) : OwnableState { override val contract: Contract = TEST_PROGRAM_ID override val participants: List get() = listOf(owner) override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner)) - override fun withNewNotary(newNotary: Party) = copy(notary = newNotary) } interface Commands : CommandData { 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). // It refers to a fake TX/state that we don't bother creating here. val depositRef = MINI_CORP.ref(1) - val outputState = TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY) - val changeState = TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY) - val fakeStateRef = generateStateRef() + val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef) + val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY) + val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public), DUMMY_NOTARY) + + lateinit var tx: TransactionBuilder @Before fun setup() { tx = TransactionBuilder().withItems( - fakeStateRef, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public)) + inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public)) ) } @Test fun signWireTX() { + tx.signWith(DUMMY_NOTARY_KEY) tx.signWith(TestUtils.keypair) val signedTX = tx.toSignedTransaction() @@ -82,6 +82,7 @@ class TransactionSerializationTests { } tx.signWith(TestUtils.keypair) + tx.signWith(DUMMY_NOTARY_KEY) val signedTX = tx.toSignedTransaction() // 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. assertFailsWith(SignatureException::class) { - val tx2 = TransactionBuilder().withItems(fakeStateRef, outputState, changeState, + val tx2 = TransactionBuilder().withItems(inputState, outputState, changeState, Command(TestCash.Commands.Move(), TestUtils.keypair2.public)) + tx2.signWith(DUMMY_NOTARY_KEY) tx2.signWith(TestUtils.keypair2) signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify() diff --git a/node/src/main/kotlin/com/r3corda/node/api/APIServer.kt b/node/src/main/kotlin/com/r3corda/node/api/APIServer.kt index 0c2331244f..7e3979c16a 100644 --- a/node/src/main/kotlin/com/r3corda/node/api/APIServer.kt +++ b/node/src/main/kotlin/com/r3corda/node/api/APIServer.kt @@ -1,10 +1,7 @@ package com.r3corda.node.api +import com.r3corda.core.contracts.* 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.SecureHash import com.r3corda.core.serialization.SerializedBytes @@ -43,7 +40,7 @@ interface APIServer { */ fun queryStates(query: StatesQuery): List - fun fetchStates(states: List): Map + fun fetchStates(states: List): Map?> /** * Query for immutable transactions (results can be cached indefinitely by their id/hash). diff --git a/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt b/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt index bf9075f67e..4a8fd1f038 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt @@ -27,7 +27,7 @@ class APIServerImpl(val node: AbstractNode) : APIServer { return states.values.map { it.ref } } else if (query.criteria is StatesQuery.Criteria.Deal) { val states = node.services.walletService.linearHeadsOfType().filterValues { - it.state.ref == query.criteria.ref + it.state.data.ref == query.criteria.ref } return states.values.map { it.ref } } @@ -35,7 +35,7 @@ class APIServerImpl(val node: AbstractNode) : APIServer { return emptyList() } - override fun fetchStates(states: List): Map { + override fun fetchStates(states: List): Map?> { return node.services.walletService.statesForRefs(states) } diff --git a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt index 52af6ad13b..8a6e05c45f 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt @@ -102,7 +102,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, lateinit var smm: StateMachineManager lateinit var wallet: WalletService lateinit var keyManagement: E2ETestKeyManagementService - lateinit var notaryChangeService: NotaryChangeService var inNodeNetworkMapService: NetworkMapService? = null var inNodeNotaryService: NotaryService? = null lateinit var identity: IdentityService @@ -130,9 +129,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, net = makeMessagingService() wallet = NodeWalletService(services) makeInterestRatesOracleService() - api = APIServerImpl(this) - notaryChangeService = NotaryChangeService(net, smm) - identity = makeIdentityService() // 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 @@ -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 // service and so that keeps it from being collected. DataVendingService(net, storage) + NotaryChangeService(net, smm) buildAdvertisedServices() diff --git a/node/src/main/kotlin/com/r3corda/node/internal/testing/IRSSimulation.kt b/node/src/main/kotlin/com/r3corda/node/internal/testing/IRSSimulation.kt index 225f1a5d16..dc5c80a1cd 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/testing/IRSSimulation.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/testing/IRSSimulation.kt @@ -85,7 +85,7 @@ class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork val theDealRef: StateAndRef = swaps.values.single() // 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[node2] = "Fixing event on $nextFixingDate" diff --git a/node/src/main/kotlin/com/r3corda/node/internal/testing/TestUtils.kt b/node/src/main/kotlin/com/r3corda/node/internal/testing/TestUtils.kt index 05966b6540..ed6e3154f2 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/testing/TestUtils.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/testing/TestUtils.kt @@ -1,5 +1,6 @@ package com.r3corda.node.internal.testing +import com.r3corda.core.contracts.StateAndRef import com.r3corda.core.contracts.DummyContract import com.r3corda.core.contracts.StateRef import com.r3corda.core.crypto.Party @@ -10,20 +11,20 @@ import com.r3corda.node.internal.AbstractNode import java.time.Instant 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) tx.signWith(node.storage.myLegalIdentityKey) tx.signWith(DUMMY_NOTARY_KEY) val stx = tx.toSignedTransaction() 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) tx.setTime(Instant.now(), notary, 30.seconds) tx.signWith(node.storage.myLegalIdentityKey) val stx = tx.toSignedTransaction(false) node.services.recordTransactions(listOf(stx)) - return StateRef(stx.id, 0) + return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0)) } diff --git a/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt b/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt index 6f084636e3..81a2165a55 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt @@ -1,6 +1,5 @@ package com.r3corda.node.services -import com.r3corda.contracts.cash.Cash import com.r3corda.core.messaging.MessagingService import com.r3corda.core.messaging.SingleMessageRecipient 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 */ 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 isNotary = smm.serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary } 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 } } diff --git a/node/src/main/kotlin/com/r3corda/node/services/wallet/NodeWalletService.kt b/node/src/main/kotlin/com/r3corda/node/services/wallet/NodeWalletService.kt index b51d8a0f3f..c9a45975e0 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/wallet/NodeWalletService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/wallet/NodeWalletService.kt @@ -51,7 +51,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer */ override val linearHeads: Map> get() = mutex.locked { wallet }.let { wallet -> - wallet.states.filterStatesOfType().associateBy { it.state.thread }.mapValues { it.value } + wallet.states.filterStatesOfType().associateBy { it.state.data.thread }.mapValues { it.value } } override fun notifyAll(txns: Iterable): Wallet { @@ -103,8 +103,8 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer private fun Wallet.update(tx: WireTransaction, ourKeys: Set): Pair { val ourNewStates = tx.outputs. - filter { isRelevant(it, ourKeys) }. - map { tx.outRef(it) } + filter { isRelevant(it.data, ourKeys) }. + map { tx.outRef(it.data) } // Now calculate the states that are being spent by this transaction. val consumed: Set = states.map { it.ref }.intersect(tx.inputs) diff --git a/node/src/main/kotlin/com/r3corda/node/services/wallet/WalletImpl.kt b/node/src/main/kotlin/com/r3corda/node/services/wallet/WalletImpl.kt index fe6fdd03a5..745237ecfc 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/wallet/WalletImpl.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/wallet/WalletImpl.kt @@ -24,7 +24,7 @@ class WalletImpl(override val states: List>) : Wallet */ override val cashBalances: Map> get() = states. // 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> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) } groupBy { it.token.product }. // Collapse to Map by summing all the amounts of the same currency together. diff --git a/node/src/main/resources/com/r3corda/node/internal/testing/trade.json b/node/src/main/resources/com/r3corda/node/internal/testing/trade.json index 0d5d125897..c2b7d51a3f 100644 --- a/node/src/main/resources/com/r3corda/node/internal/testing/trade.json +++ b/node/src/main/resources/com/r3corda/node/internal/testing/trade.json @@ -99,6 +99,5 @@ "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360", "tradeID": "tradeXXX", "hashLegalDocs": "put hash here" - }, - "notary": "Notary Service" + } } diff --git a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt index 2d45aa3030..a82246bbaa 100644 --- a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt @@ -468,7 +468,7 @@ class TwoPartyTradeProtocolTests { attachmentID: SecureHash?): Pair> { val ap = transaction { 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() } if (!withError) diff --git a/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt b/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt index 0ce88da61e..77561353e7 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt @@ -4,6 +4,7 @@ import com.r3corda.contracts.cash.Cash import com.r3corda.contracts.testing.CASH import com.r3corda.contracts.testing.`issued by` import com.r3corda.contracts.testing.`owned by` +import com.r3corda.contracts.testing.`with notary` import com.r3corda.core.bd import com.r3corda.core.contracts.DOLLARS 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.generateKeyPair 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_KEY import com.r3corda.core.utilities.BriefLogFormatter @@ -117,5 +119,5 @@ class NodeInterestRatesTest { 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)) } \ No newline at end of file diff --git a/node/src/test/kotlin/com/r3corda/node/services/NodeWalletServiceTest.kt b/node/src/test/kotlin/com/r3corda/node/services/NodeWalletServiceTest.kt index 129d42ac5d..af37073722 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/NodeWalletServiceTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/NodeWalletServiceTest.kt @@ -51,14 +51,14 @@ class NodeWalletServiceTest { val w = wallet.currentWallet 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 myPartyRef = myIdentity.ref(ref) assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount) assertEquals(ALICE_PUBKEY, state.owner) - assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state as Cash.State).amount) - assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].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.data as Cash.State).amount) } @Test @@ -77,12 +77,14 @@ class NodeWalletServiceTest { val spendTX = TransactionBuilder().apply { Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput)) signWith(freshKey) + signWith(DUMMY_NOTARY_KEY) }.toSignedTransaction() // A tx that doesn't send us anything. val irrelevantTX = TransactionBuilder().apply { Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY) signWith(MEGA_CORP_KEY) + signWith(DUMMY_NOTARY_KEY) }.toSignedTransaction() assertNull(wallet.cashBalances[USD]) diff --git a/node/src/test/kotlin/com/r3corda/node/services/UniquenessProviderTests.kt b/node/src/test/kotlin/com/r3corda/node/services/UniquenessProviderTests.kt index f7b76fbc32..f3c8197488 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/UniquenessProviderTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/UniquenessProviderTests.kt @@ -1,6 +1,8 @@ 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.testing.MEGA_CORP import com.r3corda.core.testing.generateStateRef @@ -15,7 +17,8 @@ class UniquenessProviderTests { @Test fun `should commit a transaction with unused inputs without exception`() { val provider = InMemoryUniquenessProvider() val inputState = generateStateRef() - val tx = TransactionBuilder().withItems(inputState).toWireTransaction() + val tx = buildTransaction(inputState) + provider.commit(tx, identity) } @@ -23,10 +26,10 @@ class UniquenessProviderTests { val provider = InMemoryUniquenessProvider() val inputState = generateStateRef() - val tx1 = TransactionBuilder().withItems(inputState).toWireTransaction() + val tx1 = buildTransaction(inputState) provider.commit(tx1, identity) - val tx2 = TransactionBuilder().withItems(inputState).toWireTransaction() + val tx2 = buildTransaction(inputState) val ex = assertFailsWith { provider.commit(tx2, identity) } val consumingTx = ex.error.stateHistory[inputState]!! @@ -34,4 +37,6 @@ class UniquenessProviderTests { assertEquals(consumingTx.inputIndex, tx1.inputs.indexOf(inputState)) assertEquals(consumingTx.requestingParty, identity) } + + private fun buildTransaction(inputState: StateRef) = WireTransaction(listOf(inputState), emptyList(), emptyList(), emptyList(), TransactionType.Business()) } \ No newline at end of file diff --git a/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt b/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt index a36aa7731b..9cb29a4e18 100644 --- a/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt +++ b/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt @@ -2,6 +2,7 @@ package com.r3corda.node.visualiser import com.r3corda.core.contracts.CommandData import com.r3corda.core.contracts.ContractState +import com.r3corda.core.contracts.TransactionState import com.r3corda.core.crypto.SecureHash import com.r3corda.core.testing.TransactionGroupDSL import org.graphstream.graph.Edge @@ -30,7 +31,7 @@ class GraphVisualiser(val dsl: TransactionGroupDSL) { val node = graph.addNode(tx.outRef(outIndex).ref.toString()) val state = tx.outputs[outIndex] node.label = stateToLabel(state) - node.styleClass = stateToCSSClass(state) + ",state" + node.styleClass = stateToCSSClass(state.data) + ",state" node.setAttribute("state", state) val edge = graph.addEdge("tx$txIndex-out$outIndex", txNode, node, true) edge.weight = 0.7 @@ -55,8 +56,8 @@ class GraphVisualiser(val dsl: TransactionGroupDSL) { return graph } - private fun stateToLabel(state: ContractState): String { - return dsl.labelForState(state) ?: stateToTypeName(state) + private fun stateToLabel(state: TransactionState<*>): String { + return dsl.labelForState(state) ?: stateToTypeName(state.data) } private fun commandToTypeName(state: CommandData) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.') diff --git a/node/src/test/kotlin/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/node/services/NotaryChangeTests.kt index aca155f8d1..c76e176ba9 100644 --- a/node/src/test/kotlin/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/node/services/NotaryChangeTests.kt @@ -1,14 +1,12 @@ package node.services import com.r3corda.contracts.DummyContract -import com.r3corda.core.contracts.StateAndRef -import com.r3corda.core.contracts.StateRef -import com.r3corda.core.contracts.TransactionBuilder +import com.r3corda.core.contracts.* import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY_KEY 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.testutils.issueState import org.junit.Before import org.junit.Test import protocols.NotaryChangeProtocol @@ -35,12 +33,12 @@ class NotaryChangeTests { @Test fun `should change notary for a state with single participant`() { - val state = issueState(clientNodeA, DUMMY_NOTARY) - val ref = clientNodeA.services.loadState(state) + val ref = issueState(clientNodeA, DUMMY_NOTARY).ref + val state = clientNodeA.services.loadState(ref) 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) net.runNetwork() @@ -51,11 +49,10 @@ class NotaryChangeTests { @Test fun `should change notary for a state with multiple participants`() { - val state = DummyContract.MultiOwnerState(0, - listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey), - DUMMY_NOTARY) + val state = TransactionState(DummyContract.MultiOwnerState(0, + listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey)), DUMMY_NOTARY) - val tx = TransactionBuilder().withItems(state) + val tx = TransactionBuilder(type = TransactionType.NotaryChange()).withItems(state) tx.signWith(clientNodeA.storage.myLegalIdentityKey) tx.signWith(clientNodeB.storage.myLegalIdentityKey) tx.signWith(DUMMY_NOTARY_KEY) diff --git a/src/main/kotlin/com/r3corda/demos/RateFixDemo.kt b/src/main/kotlin/com/r3corda/demos/RateFixDemo.kt index 41fb88f3f9..a7916282cd 100644 --- a/src/main/kotlin/com/r3corda/demos/RateFixDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/RateFixDemo.kt @@ -1,10 +1,7 @@ package com.r3corda.demos import com.r3corda.contracts.cash.Cash -import com.r3corda.core.contracts.DOLLARS -import com.r3corda.core.contracts.FixOf -import com.r3corda.core.contracts.`issued by` -import com.r3corda.core.contracts.TransactionBuilder +import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party import com.r3corda.core.logElapsedTime import com.r3corda.core.node.NodeInfo @@ -87,7 +84,7 @@ fun main(args: Array) { // Make a garbage transaction that includes a rate fix. 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) node.smm.add("demo.ratefix", protocol).get() node.stop() diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index a7ccff583f..fb842a7e7d 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -356,7 +356,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort, // Sign it as ourselves. 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())) tx.addSignatureUnchecked(notarySig) diff --git a/src/main/kotlin/com/r3corda/demos/api/InterestRateSwapAPI.kt b/src/main/kotlin/com/r3corda/demos/api/InterestRateSwapAPI.kt index a9b206234f..9197216da7 100644 --- a/src/main/kotlin/com/r3corda/demos/api/InterestRateSwapAPI.kt +++ b/src/main/kotlin/com/r3corda/demos/api/InterestRateSwapAPI.kt @@ -44,14 +44,14 @@ class InterestRateSwapAPI(val api: APIServer) { private fun getDealByRef(ref: String): InterestRateSwap.State? { val states = api.queryStates(StatesQuery.selectDeal(ref)) 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] } } private fun getAllDeals(): Array { 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 } diff --git a/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt index e32bc610a3..452e315871 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt @@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable import com.r3corda.contracts.InterestRateSwap import com.r3corda.core.contracts.DealState import com.r3corda.core.contracts.StateAndRef +import com.r3corda.core.contracts.TransactionState import com.r3corda.core.node.NodeInfo import com.r3corda.core.node.services.linearHeadsOfType import com.r3corda.core.protocols.ProtocolLogic @@ -13,6 +14,7 @@ import com.r3corda.core.utilities.ProgressTracker import com.r3corda.demos.DemoClock import com.r3corda.node.internal.Node import com.r3corda.node.services.network.MockNetworkMapCache +import com.r3corda.node.utilities.ANSIProgressRenderer import com.r3corda.protocols.TwoPartyDealProtocol import java.time.LocalDate @@ -41,12 +43,12 @@ object UpdateBusinessDayProtocol { // Get deals progressTracker.currentStep = FETCHING val dealStateRefs = serviceHub.walletService.linearHeadsOfType() - 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 val sortedParties = otherPartyToDeals.keys.sortedBy { it.identity.name } 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) { progressTracker.currentStep = ITERATING_DEALS 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 @Suspendable fun processDeal(party: NodeInfo, deal: StateAndRef, date: LocalDate, sessionID: Long) { - val s = deal.state + val s = deal.state.data 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 fun processInterestRateSwap(party: NodeInfo, deal: StateAndRef, date: LocalDate, sessionID: Long) { var dealStateAndRef: StateAndRef? = deal - var nextFixingDate = deal.state.calculation.nextFixingDate() + var nextFixingDate = deal.state.data.calculation.nextFixingDate() while (nextFixingDate != null && !nextFixingDate.isAfter(date)) { 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 * 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) } else { 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 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 keyPair = serviceHub.keyManagementService.toKeyPair(myOldParty.owningKey) val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.notaryNodes[0], dealStateAndRef,