mirror of
https://github.com/corda/corda.git
synced 2025-02-01 08:48:09 +00:00
Introduce TransactionState, which wraps ContractState and holds the notary pointer.
Remove notary from ContractState. Introduce TransactionType, which specifies custom validation logic for a transaction.
This commit is contained in:
parent
3b1e020082
commit
70495a021e
@ -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<PublicKey>
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -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<Issued<Currency>> faceValue;
|
||||
private Instant maturityDate;
|
||||
private Party notary;
|
||||
|
||||
public State() {
|
||||
} // For serialization
|
||||
|
||||
public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> 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<Issued<Currency>> newFaceValue) {
|
||||
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate, this.notary);
|
||||
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate);
|
||||
}
|
||||
|
||||
public ICommercialPaperState withMaturityDate(Instant newMaturityDate) {
|
||||
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<PublicKey> getParticipants() {
|
||||
List<PublicKey> 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<State> paper, List<StateAndRef<Cash.State>> 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<State> 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()));
|
||||
}
|
||||
}
|
||||
|
@ -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<Issued<Currency>>,
|
||||
val maturityDate: Instant,
|
||||
override val notary: Party
|
||||
val maturityDate: Instant
|
||||
) : OwnableState, ICommercialPaperState {
|
||||
override val contract = CP_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
@ -63,8 +62,6 @@ class CommercialPaper : Contract {
|
||||
override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance)
|
||||
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): 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<Issued<Currency>>, 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<State>, 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<State>, wallet: List<StateAndRef<Cash.State>>) {
|
||||
// Add the cash movement using the states in our wallet.
|
||||
val amount = paper.state.faceValue.let { amount -> Amount<Currency>(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<Currency>(amount.quantity, amount.token.product) }
|
||||
Cash().generateSpend(tx, amount, paper.state.data.owner, wallet)
|
||||
tx.addInputState(paper)
|
||||
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<State>, fixing: Pair<LocalDate, Rate>) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -49,12 +49,10 @@ class Cash : FungibleAsset<Currency>() {
|
||||
override val amount: Amount<Issued<Currency>>,
|
||||
|
||||
/** 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<Currency> {
|
||||
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey, notary: Party)
|
||||
: this(Amount(amount.quantity, Issued<Currency>(deposit, amount.token)), owner, notary)
|
||||
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey)
|
||||
: this(Amount(amount.quantity, Issued<Currency>(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<Currency>() {
|
||||
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<Currency>() {
|
||||
*/
|
||||
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, 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<Currency>() {
|
||||
|
||||
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<Currency>() {
|
||||
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<Currency>() {
|
||||
throw InsufficientBalanceException(amount - gatheredAmount)
|
||||
|
||||
val change = if (takeChangeFrom != null && gatheredAmount > amount) {
|
||||
Amount<Issued<Currency>>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.issuanceDef)
|
||||
Amount<Issued<Currency>>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.state.issuanceDef)
|
||||
} else {
|
||||
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()
|
||||
|
@ -37,7 +37,6 @@ abstract class FungibleAsset<T> : Contract {
|
||||
override val amount: Amount<Issued<T>>
|
||||
/** 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<T> : 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<T> -> it.issuanceDef }
|
||||
@ -97,7 +96,7 @@ abstract class FungibleAsset<T> : Contract {
|
||||
|
||||
private fun verifyIssueCommand(inputs: List<State<T>>,
|
||||
outputs: List<State<T>>,
|
||||
tx: TransactionForVerification,
|
||||
tx: TransactionForContract,
|
||||
issueCommand: AuthenticatedObject<Commands.Issue>,
|
||||
token: Issued<T>,
|
||||
issuer: Party) {
|
||||
|
@ -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<Contract, Class<out Contract>> = 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<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party))))
|
||||
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit)))
|
||||
infix fun Cash.State.`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<Currency>.CASH: Cash.State get() = Cash.State(
|
||||
Amount<Issued<Currency>>(this.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, this.token)),
|
||||
NullPublicKey, DUMMY_NOTARY)
|
||||
NullPublicKey)
|
||||
|
||||
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey, DUMMY_NOTARY)
|
||||
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey)
|
||||
|
||||
infix fun ContractState.`with notary`(notary: Party) = TransactionState(this, notary)
|
||||
|
@ -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<Cash.State>()
|
||||
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.
|
||||
|
@ -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<LedgerTransaction, List<StateAndRef<Cash.State>>> {
|
||||
val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*states), emptyList(), SecureHash.randomSHA256())
|
||||
return Pair(ltx, states.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
|
||||
fun <T : ContractState> cashOutputsToWallet(vararg outputs: TransactionState<T>): Pair<LedgerTransaction, List<StateAndRef<T>>> {
|
||||
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() }
|
||||
|
@ -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<InterestRateSwap.State>().single()
|
||||
return generateIRSTxn(irsSelector).outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -287,7 +286,7 @@ class IRSTests {
|
||||
newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.value)))
|
||||
}
|
||||
|
||||
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<InterestRateSwap.State>().single()
|
||||
var currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().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<InterestRateSwap.State>().single()
|
||||
currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().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")
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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<Cash.State>(0).ref)
|
||||
ptx.addInputState(tx.tx.outRef<Cash.State>(0))
|
||||
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||
}
|
||||
|
||||
@ -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<Currency>, 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()
|
||||
|
@ -92,7 +92,7 @@ fun List<AuthenticatedObject<CommandData>>.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 <reified T : CommandData> verifyMoveCommands(inputs: List<OwnableState>, tx: TransactionForVerification) {
|
||||
inline fun <reified T : CommandData> verifyMoveCommands(inputs: List<OwnableState>, tx: TransactionForContract) {
|
||||
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
|
||||
// see a signature from each of those keys. The actual signatures have been verified against the transaction
|
||||
// data by the platform before execution.
|
||||
|
@ -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<PublicKey>
|
||||
get() = emptyList()
|
||||
}
|
||||
|
||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
||||
data class SingleOwnerState(val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
get() = listOf(owner)
|
||||
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
data class MultiOwnerState(val magicNumber: Int = 0,
|
||||
val owners: List<PublicKey>,
|
||||
override val notary: Party) : ContractState {
|
||||
val owners: List<PublicKey>) : ContractState {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
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))
|
||||
}
|
||||
}
|
@ -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<PublicKey>
|
||||
}
|
||||
|
||||
/** A wrapper for [ContractState] containing additional platform-level state information. This is the state */
|
||||
data class TransactionState<out T : ContractState>(
|
||||
val data: T,
|
||||
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
|
||||
val notary: Party) {
|
||||
/**
|
||||
* Copies the underlying data structure, replacing the notary field with the new value.
|
||||
* 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<out T : ContractState>(val state: T, val ref: StateRef)
|
||||
data class StateAndRef<out T : ContractState>(val state: TransactionState<T>, val ref: StateRef)
|
||||
|
||||
/** Filters a list of [StateAndRef] objects according to the type of the states */
|
||||
inline fun <reified T : ContractState> List<StateAndRef<ContractState>>.filterStatesOfType(): List<StateAndRef<T>> {
|
||||
return mapNotNull { if (it.state is T) StateAndRef(it.state, it.ref) else null }
|
||||
return mapNotNull { if (it.state.data is T) StateAndRef(TransactionState(it.state.data, it.state.notary), it.ref) else null }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +169,9 @@ abstract class TypeOnlyCommandData : CommandData {
|
||||
|
||||
/** Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes */
|
||||
data class Command(val value: CommandData, val signers: List<PublicKey>) {
|
||||
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
|
||||
|
@ -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<StateRef> = arrayListOf(),
|
||||
private val attachments: MutableList<SecureHash> = arrayListOf(),
|
||||
private val outputs: MutableList<ContractState> = arrayListOf(),
|
||||
private val commands: MutableList<Command> = arrayListOf()) {
|
||||
private val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
|
||||
private val commands: MutableList<Command> = 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<StateRef> = 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<StateRef> = 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<StateRef> = 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<StateRef> = 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<StateRef> = arrayListOf
|
||||
// Accessors that yield immutable snapshots.
|
||||
fun inputStates(): List<StateRef> = ArrayList(inputs)
|
||||
|
||||
fun outputStates(): List<ContractState> = ArrayList(outputs)
|
||||
fun outputStates(): List<TransactionState<*>> = ArrayList(outputs)
|
||||
fun commands(): List<Command> = ArrayList(commands)
|
||||
fun attachments(): List<SecureHash> = ArrayList(attachments)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,73 @@
|
||||
package com.r3corda.core.contracts
|
||||
|
||||
/** Defines transaction validation rules for a specific transaction type */
|
||||
sealed class TransactionType {
|
||||
override fun equals(other: Any?) = other?.javaClass == javaClass
|
||||
override fun hashCode() = javaClass.name.hashCode()
|
||||
|
||||
/**
|
||||
* Check that the transaction is valid based on:
|
||||
* - General platform rules
|
||||
* - Rules for the specific transaction type
|
||||
*
|
||||
* Note: Presence of _signatures_ is not checked, only the public keys to be signed for.
|
||||
*/
|
||||
fun verify(tx: TransactionForVerification) {
|
||||
verifyNotary(tx)
|
||||
typeSpecificVerify(tx)
|
||||
}
|
||||
|
||||
private fun verifyNotary(tx: TransactionForVerification) {
|
||||
if (tx.inStates.isEmpty()) return
|
||||
val notary = tx.inStates.first().notary
|
||||
if (tx.inStates.any { it.notary != notary }) throw TransactionVerificationException.MoreThanOneNotary(tx)
|
||||
if (tx.commands.none { it.signers.contains(notary.owningKey) }) throw TransactionVerificationException.NotaryMissing(tx)
|
||||
}
|
||||
|
||||
abstract fun typeSpecificVerify(tx: TransactionForVerification)
|
||||
|
||||
/** A general type used for business transactions, where transaction validity is determined by custom contract code */
|
||||
class Business : TransactionType() {
|
||||
/**
|
||||
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
|
||||
* If any contract fails to verify, the whole transaction is considered to be invalid.
|
||||
*/
|
||||
override fun typeSpecificVerify(tx: TransactionForVerification) {
|
||||
val ctx = tx.toTransactionForContract()
|
||||
|
||||
val contracts = (ctx.inStates.map { it.contract } + ctx.outStates.map { it.contract }).toSet()
|
||||
for (contract in contracts) {
|
||||
try {
|
||||
contract.verify(ctx)
|
||||
} catch(e: Throwable) {
|
||||
throw TransactionVerificationException.ContractRejection(tx, contract, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A special transaction type for reassigning a notary for a state. Validation does not involve running
|
||||
* any contract code, it just checks that the states are unmodified apart from the notary field.
|
||||
*/
|
||||
class NotaryChange : TransactionType() {
|
||||
/**
|
||||
* Check that the difference between inputs and outputs is only the notary field,
|
||||
* and that all required signing public keys are present
|
||||
*/
|
||||
override fun typeSpecificVerify(tx: TransactionForVerification) {
|
||||
try {
|
||||
tx.inStates.zip(tx.outStates).forEach {
|
||||
check(it.first.data == it.second.data)
|
||||
check(it.first.notary != it.second.notary)
|
||||
}
|
||||
val command = tx.commands.requireSingleCommand<ChangeNotary>()
|
||||
val requiredSigners = tx.inStates.flatMap { it.data.participants }
|
||||
check(command.signers.containsAll(requiredSigners))
|
||||
} catch (e: IllegalStateException) {
|
||||
throw TransactionVerificationException.InvalidNotaryChange(tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
|
||||
|
||||
val resolved = HashSet<TransactionForVerification>(transactions.size)
|
||||
for (tx in transactions) {
|
||||
val inputs = ArrayList<ContractState>(tx.inputs.size)
|
||||
val inputs = ArrayList<TransactionState<ContractState>>(tx.inputs.size)
|
||||
for (ref in tx.inputs) {
|
||||
val conflict = refToConsumingTXMap[ref]
|
||||
if (conflict != null)
|
||||
@ -41,31 +41,27 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, 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<ContractState>,
|
||||
val outStates: List<ContractState>,
|
||||
data class TransactionForVerification(val inStates: List<TransactionState<ContractState>>,
|
||||
val outStates: List<TransactionState<ContractState>>,
|
||||
val attachments: List<Attachment>,
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
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<ContractState>,
|
||||
* (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<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
|
||||
|
||||
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
|
||||
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
|
||||
|
||||
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
|
||||
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
|
||||
/**
|
||||
* 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<ContractState>,
|
||||
val outStates: List<ContractState>,
|
||||
val attachments: List<Attachment>,
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
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<ContractState>,
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/** Utilities for contract writers to incorporate into their logic. */
|
||||
|
||||
/**
|
||||
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
|
||||
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
|
||||
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
|
||||
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
|
||||
* be used to simplify this logic.
|
||||
*/
|
||||
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
|
||||
|
||||
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
|
||||
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
|
||||
|
||||
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
|
||||
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
|
||||
|
||||
}
|
||||
|
||||
class TransactionResolutionException(val hash: SecureHash) : Exception()
|
||||
@ -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)
|
||||
}
|
@ -43,8 +43,9 @@ import java.security.SignatureException
|
||||
/** Transaction ready for serialisation, without any signatures attached. */
|
||||
data class WireTransaction(val inputs: List<StateRef>,
|
||||
val attachments: List<SecureHash>,
|
||||
val outputs: List<ContractState>,
|
||||
val commands: List<Command>) : NamedByHash {
|
||||
val outputs: List<TransactionState<ContractState>>,
|
||||
val commands: List<Command>,
|
||||
val type: TransactionType) : NamedByHash {
|
||||
|
||||
// Cache the serialised form of the transaction and its hash to give us fast access to it.
|
||||
@Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null
|
||||
@ -63,11 +64,11 @@ data class WireTransaction(val inputs: List<StateRef>,
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : ContractState> outRef(index: Int): StateAndRef<T> {
|
||||
require(index >= 0 && index < outputs.size)
|
||||
return StateAndRef(outputs[index] as T, StateRef(id, index))
|
||||
return StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
|
||||
}
|
||||
|
||||
/** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */
|
||||
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.indexOfOrThrow(state))
|
||||
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.map { it.data }.indexOfOrThrow(state))
|
||||
|
||||
override fun toString(): String {
|
||||
val buf = StringBuilder()
|
||||
@ -130,7 +131,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
||||
return copy(sigs = sigs + sig)
|
||||
}
|
||||
|
||||
fun withAdditionalSignatures(sigList: Collection<DigitalSignature.WithKey>): SignedTransaction {
|
||||
fun withAdditionalSignatures(sigList: Iterable<DigitalSignature.WithKey>): 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<Attachment>,
|
||||
/** The states that will be generated by the execution of this transaction. */
|
||||
val outputs: List<ContractState>,
|
||||
val outputs: List<TransactionState<*>>,
|
||||
/** Arbitrary data passed to the program of each input state. */
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
/** The hash of the original serialised WireTransaction */
|
||||
override val id: SecureHash
|
||||
override val id: SecureHash,
|
||||
val type: TransactionType
|
||||
) : NamedByHash {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as T, StateRef(id, index))
|
||||
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
|
||||
}
|
@ -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]
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ abstract class Wallet {
|
||||
abstract val states: List<StateAndRef<ContractState>>
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state is T } as List<StateAndRef<T>>
|
||||
inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
|
||||
|
||||
/**
|
||||
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
|
||||
@ -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 <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<SecureHash, StateAndRef<T>> {
|
||||
return linearHeads.filterValues { stateType.isInstance(it.state) }.mapValues { StateAndRef(it.value.state as T, it.value.ref) }
|
||||
return linearHeads.filterValues { stateType.isInstance(it.state.data) }.mapValues { StateAndRef(it.value.state as TransactionState<T>, it.value.ref) }
|
||||
}
|
||||
|
||||
fun statesForRefs(refs: List<StateRef>): Map<StateRef, ContractState?> {
|
||||
fun statesForRefs(refs: List<StateRef>): Map<StateRef, TransactionState<*>?> {
|
||||
val refsToStates = currentWallet.states.associateBy { it.ref }
|
||||
return refs.associateBy({ it }, { refsToStates[it]?.state })
|
||||
}
|
||||
|
@ -232,6 +232,7 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
||||
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<WireTransaction>() {
|
||||
} else javaClass.classLoader
|
||||
|
||||
kryo.useClassLoader(classLoader) {
|
||||
val outputs = kryo.readClassAndObject(input) as List<ContractState>
|
||||
val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>>
|
||||
val commands = kryo.readClassAndObject(input) as List<Command>
|
||||
val 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<WireTransaction>()
|
||||
}
|
||||
|
@ -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<SecureHash>()
|
||||
protected val outStates = ArrayList<LabeledOutput>()
|
||||
protected val commands = ArrayList<Command>()
|
||||
protected val type = TransactionType.Business()
|
||||
|
||||
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, s()).apply { outStates.add(this) }
|
||||
init {
|
||||
arg(DUMMY_NOTARY.owningKey) { NotaryCommand() }
|
||||
}
|
||||
|
||||
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, TransactionState(s(), DUMMY_NOTARY)).apply { outStates.add(this) }
|
||||
|
||||
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
|
||||
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<ContractState>()
|
||||
fun input(s: () -> ContractState) = inStates.add(s())
|
||||
private val inStates = arrayListOf<TransactionState<ContractState>>()
|
||||
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<T : ContractState>(private val stateType: Class<T>) {
|
||||
private val inStates = ArrayList<StateRef>()
|
||||
|
||||
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<T>
|
||||
get() =
|
||||
labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found")
|
||||
val String.outputRef: StateRef get() = labelToRefs[this] ?: throw IllegalArgumentException("Unknown label \"$this\"")
|
||||
|
||||
fun <C : ContractState> lookup(label: String) = StateAndRef(label.output as C, label.outputRef)
|
||||
fun <C : ContractState> lookup(label: String): StateAndRef<C> {
|
||||
val output = label.output
|
||||
val newOutput = TransactionState(output.data as C, output.notary)
|
||||
return StateAndRef(newOutput, label.outputRef)
|
||||
}
|
||||
|
||||
private inner class InternalWireTransactionDSL : WireTransactionDSL() {
|
||||
fun finaliseAndInsertLabels(): WireTransaction {
|
||||
@ -257,8 +270,8 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
|
||||
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<T>
|
||||
}
|
||||
outputsToLabels[labelledState.state] = labelledState.label
|
||||
}
|
||||
@ -269,20 +282,20 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
|
||||
|
||||
private val rootTxns = ArrayList<WireTransaction>()
|
||||
private val labelToRefs = HashMap<String, StateRef>()
|
||||
private val labelToOutputs = HashMap<String, T>()
|
||||
private val outputsToLabels = HashMap<ContractState, String>()
|
||||
private val labelToOutputs = HashMap<String, TransactionState<T>>()
|
||||
private val outputsToLabels = HashMap<TransactionState<*>, 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<T>
|
||||
}
|
||||
rootTxns.add(wtx)
|
||||
}
|
||||
|
@ -314,7 +314,7 @@ object TwoPartyDealProtocol {
|
||||
}
|
||||
|
||||
override fun assembleSharedTX(handshake: Handshake<T>): Pair<TransactionBuilder, List<PublicKey>> {
|
||||
val ptx = handshake.payload.generateAgreement()
|
||||
val ptx = handshake.payload.generateAgreement(notary)
|
||||
|
||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||
// to have one.
|
||||
@ -336,7 +336,7 @@ object TwoPartyDealProtocol {
|
||||
val dealToFix: StateAndRef<T>,
|
||||
sessionID: Long,
|
||||
val replacementProgressTracker: ProgressTracker? = null) : Secondary<StateRef>(otherSide, notary, sessionID) {
|
||||
private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.nextFixingOf()!!.name)
|
||||
private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.data.nextFixingOf()!!.name)
|
||||
|
||||
override val progressTracker: ProgressTracker = replacementProgressTracker ?: createTracker()
|
||||
|
||||
@ -358,11 +358,11 @@ object TwoPartyDealProtocol {
|
||||
|
||||
@Suspendable
|
||||
override fun assembleSharedTX(handshake: Handshake<StateRef>): Pair<TransactionBuilder, List<PublicKey>> {
|
||||
val fixOf = dealToFix.state.nextFixingOf()!!
|
||||
val fixOf = dealToFix.state.data.nextFixingOf()!!
|
||||
|
||||
// TODO Do we need/want to substitute in new public keys for the Parties?
|
||||
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.
|
||||
|
@ -55,18 +55,13 @@ object NotaryChangeProtocol {
|
||||
|
||||
progressTracker.currentStep = SIGNING
|
||||
|
||||
val signatures = mutableListOf<DigitalSignature.WithKey>()
|
||||
|
||||
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<SignedTransaction, List<PublicKey>> {
|
||||
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<PublicKey>, signatures: MutableCollection<DigitalSignature.WithKey>,
|
||||
stx: SignedTransaction): MutableList<Pair<NodeInfo, Long>> {
|
||||
val participantSessions = mutableListOf<Pair<NodeInfo, Long>>()
|
||||
private fun collectSignatures(participants: List<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||
val sessions = mutableMapOf<NodeInfo, Long>()
|
||||
|
||||
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<List<DigitalSignature.WithKey>>(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)
|
||||
}
|
||||
|
Binary file not shown.
@ -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<Currency>,
|
||||
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<PublicKey>
|
||||
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 {
|
||||
|
@ -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<PublicKey>
|
||||
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
|
||||
|
@ -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<Currency>,
|
||||
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<PublicKey>
|
||||
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()
|
||||
|
@ -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<StateRef>
|
||||
|
||||
fun fetchStates(states: List<StateRef>): Map<StateRef, ContractState?>
|
||||
fun fetchStates(states: List<StateRef>): Map<StateRef, TransactionState<ContractState>?>
|
||||
|
||||
/**
|
||||
* Query for immutable transactions (results can be cached indefinitely by their id/hash).
|
||||
|
@ -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<DealState>().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<StateRef>): Map<StateRef, ContractState?> {
|
||||
override fun fetchStates(states: List<StateRef>): Map<StateRef, TransactionState<ContractState>?> {
|
||||
return node.services.walletService.statesForRefs(states)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -85,7 +85,7 @@ class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork
|
||||
val theDealRef: StateAndRef<InterestRateSwap.State> = 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"
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
||||
*/
|
||||
override val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
||||
get() = mutex.locked { wallet }.let { wallet ->
|
||||
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.thread }.mapValues { it.value }
|
||||
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.data.thread }.mapValues { it.value }
|
||||
}
|
||||
|
||||
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
|
||||
@ -103,8 +103,8 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
||||
|
||||
private fun Wallet.update(tx: WireTransaction, ourKeys: Set<PublicKey>): Pair<Wallet, Wallet.Update> {
|
||||
val ourNewStates = tx.outputs.
|
||||
filter { isRelevant(it, ourKeys) }.
|
||||
map { tx.outRef<ContractState>(it) }
|
||||
filter { isRelevant(it.data, ourKeys) }.
|
||||
map { tx.outRef<ContractState>(it.data) }
|
||||
|
||||
// Now calculate the states that are being spent by this transaction.
|
||||
val consumed: Set<StateRef> = states.map { it.ref }.intersect(tx.inputs)
|
||||
|
@ -24,7 +24,7 @@ class WalletImpl(override val states: List<StateAndRef<ContractState>>) : Wallet
|
||||
*/
|
||||
override val cashBalances: Map<Currency, Amount<Currency>> 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<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
|
||||
groupBy { it.token.product }.
|
||||
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
|
||||
|
@ -99,6 +99,5 @@
|
||||
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
|
||||
"tradeID": "tradeXXX",
|
||||
"hashLegalDocs": "put hash here"
|
||||
},
|
||||
"notary": "Notary Service"
|
||||
}
|
||||
}
|
||||
|
@ -468,7 +468,7 @@ class TwoPartyTradeProtocolTests {
|
||||
attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> {
|
||||
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)
|
||||
|
@ -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))
|
||||
}
|
@ -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])
|
||||
|
@ -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<UniquenessException> { 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())
|
||||
}
|
@ -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<in ContractState>) {
|
||||
val node = graph.addNode<Node>(tx.outRef<ContractState>(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<Edge>("tx$txIndex-out$outIndex", txNode, node, true)
|
||||
edge.weight = 0.7
|
||||
@ -55,8 +56,8 @@ class GraphVisualiser(val dsl: TransactionGroupDSL<in ContractState>) {
|
||||
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('$', '.')
|
||||
|
@ -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)
|
||||
|
@ -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<String>) {
|
||||
|
||||
// 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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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<InterestRateSwap.State> {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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<DealState>()
|
||||
val otherPartyToDeals = dealStateRefs.values.groupBy { otherParty(it.state) }
|
||||
val otherPartyToDeals = dealStateRefs.values.groupBy { otherParty(it.state.data) }
|
||||
|
||||
// TODO we need to process these in parallel to stop there being an ordering problem across more than two nodes
|
||||
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<DealState>, 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<InterestRateSwap.State>, date: LocalDate, sessionID: Long) {
|
||||
var dealStateAndRef: StateAndRef<InterestRateSwap.State>? = deal
|
||||
var nextFixingDate = deal.state.calculation.nextFixingDate()
|
||||
var nextFixingDate = deal.state.data.calculation.nextFixingDate()
|
||||
while (nextFixingDate != null && !nextFixingDate.isAfter(date)) {
|
||||
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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user