mirror of
https://github.com/corda/corda.git
synced 2025-06-22 17:09:00 +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:
@ -18,19 +18,17 @@ import java.security.PublicKey
|
|||||||
val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract()
|
val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract()
|
||||||
|
|
||||||
class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdoor {
|
class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdoor {
|
||||||
data class State(val magicNumber: Int = 0, override val notary: Party) : ContractState {
|
data class State(val magicNumber: Int = 0) : ContractState {
|
||||||
override val contract = ANOTHER_DUMMY_PROGRAM_ID
|
override val contract = ANOTHER_DUMMY_PROGRAM_ID
|
||||||
override val participants: List<PublicKey>
|
override val participants: List<PublicKey>
|
||||||
get() = emptyList()
|
get() = emptyList()
|
||||||
|
|
||||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Commands : CommandData {
|
interface Commands : CommandData {
|
||||||
class Create : TypeOnlyCommandData(), Commands
|
class Create : TypeOnlyCommandData(), Commands
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verify(tx: TransactionForVerification) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
// Always accepts.
|
// Always accepts.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +36,7 @@ class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdo
|
|||||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org")
|
override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org")
|
||||||
|
|
||||||
override fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
|
override fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
|
||||||
val state = State(magicNumber, notary)
|
val state = TransactionState(State(magicNumber), notary)
|
||||||
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package com.r3corda.contracts;
|
package com.r3corda.contracts;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.r3corda.contracts.cash.Cash;
|
import com.r3corda.contracts.cash.Cash;
|
||||||
import com.r3corda.contracts.cash.CashKt;
|
import com.r3corda.contracts.cash.CashKt;
|
||||||
import com.r3corda.contracts.cash.InsufficientBalanceException;
|
import com.r3corda.contracts.cash.InsufficientBalanceException;
|
||||||
import com.r3corda.core.contracts.*;
|
import com.r3corda.core.contracts.*;
|
||||||
import com.r3corda.core.contracts.TransactionForVerification.InOutGroup;
|
import com.r3corda.core.contracts.TransactionForContract.InOutGroup;
|
||||||
import com.r3corda.core.crypto.NullPublicKey;
|
import com.r3corda.core.crypto.NullPublicKey;
|
||||||
import com.r3corda.core.crypto.Party;
|
import com.r3corda.core.crypto.Party;
|
||||||
import com.r3corda.core.crypto.SecureHash;
|
import com.r3corda.core.crypto.SecureHash;
|
||||||
@ -14,7 +15,6 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand;
|
import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand;
|
||||||
@ -34,38 +34,36 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
private PublicKey owner;
|
private PublicKey owner;
|
||||||
private Amount<Issued<Currency>> faceValue;
|
private Amount<Issued<Currency>> faceValue;
|
||||||
private Instant maturityDate;
|
private Instant maturityDate;
|
||||||
private Party notary;
|
|
||||||
|
|
||||||
public State() {
|
public State() {
|
||||||
} // For serialization
|
} // For serialization
|
||||||
|
|
||||||
public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> faceValue,
|
public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> faceValue,
|
||||||
Instant maturityDate, Party notary) {
|
Instant maturityDate) {
|
||||||
this.issuance = issuance;
|
this.issuance = issuance;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.faceValue = faceValue;
|
this.faceValue = faceValue;
|
||||||
this.maturityDate = maturityDate;
|
this.maturityDate = maturityDate;
|
||||||
this.notary = notary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public State copy() {
|
public State copy() {
|
||||||
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate, this.notary);
|
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommercialPaperState withOwner(PublicKey newOwner) {
|
public ICommercialPaperState withOwner(PublicKey newOwner) {
|
||||||
return new State(this.issuance, newOwner, this.faceValue, this.maturityDate, this.notary);
|
return new State(this.issuance, newOwner, this.faceValue, this.maturityDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommercialPaperState withIssuance(PartyAndReference newIssuance) {
|
public ICommercialPaperState withIssuance(PartyAndReference newIssuance) {
|
||||||
return new State(newIssuance, this.owner, this.faceValue, this.maturityDate, this.notary);
|
return new State(newIssuance, this.owner, this.faceValue, this.maturityDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommercialPaperState withFaceValue(Amount<Issued<Currency>> newFaceValue) {
|
public ICommercialPaperState withFaceValue(Amount<Issued<Currency>> newFaceValue) {
|
||||||
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate, this.notary);
|
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommercialPaperState withMaturityDate(Instant newMaturityDate) {
|
public ICommercialPaperState withMaturityDate(Instant newMaturityDate) {
|
||||||
return new State(this.issuance, this.owner, this.faceValue, newMaturityDate, this.notary);
|
return new State(this.issuance, this.owner, this.faceValue, newMaturityDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartyAndReference getIssuance() {
|
public PartyAndReference getIssuance() {
|
||||||
@ -84,12 +82,6 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
return maturityDate;
|
return maturityDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Party getNotary() {
|
|
||||||
return notary;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Contract getContract() {
|
public Contract getContract() {
|
||||||
@ -107,7 +99,6 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
|
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
|
||||||
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
|
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
|
||||||
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
|
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
|
||||||
if (notary != null ? !notary.equals(state.notary) : state.notary != null) return false;
|
|
||||||
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
|
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,26 +108,17 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
result = 31 * result + (owner != null ? owner.hashCode() : 0);
|
result = 31 * result + (owner != null ? owner.hashCode() : 0);
|
||||||
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
|
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
|
||||||
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
|
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
|
||||||
result = 31 * result + (notary != null ? notary.hashCode() : 0);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public State withoutOwner() {
|
public State withoutOwner() {
|
||||||
return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate, notary);
|
return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate);
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public ContractState withNewNotary(@NotNull Party newNotary) {
|
|
||||||
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate, newNotary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public List<PublicKey> getParticipants() {
|
public List<PublicKey> getParticipants() {
|
||||||
List<PublicKey> keys = new ArrayList<>();
|
return ImmutableList.of(this.owner);
|
||||||
keys.add(this.owner);
|
|
||||||
return keys;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +146,7 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify(@NotNull TransactionForVerification tx) {
|
public void verify(@NotNull TransactionForContract tx) {
|
||||||
// There are three possible things that can be done with CP.
|
// There are three possible things that can be done with CP.
|
||||||
// Issuance, trading (aka moving in this prototype) and redeeming.
|
// Issuance, trading (aka moving in this prototype) and redeeming.
|
||||||
// Each command has it's own set of restrictions which the verify function ... verifies.
|
// Each command has it's own set of restrictions which the verify function ... verifies.
|
||||||
@ -248,19 +230,20 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
|
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
|
||||||
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate, notary);
|
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
|
||||||
return new TransactionBuilder().withItems(state, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
|
TransactionState output = new TransactionState<>(state, notary);
|
||||||
|
return new TransactionBuilder().withItems(output, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
|
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
|
||||||
new Cash().generateSpend(tx, paper.getState().getFaceValue(), paper.getState().getOwner(), wallet);
|
new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet);
|
||||||
tx.addInputState(paper.getRef());
|
tx.addInputState(paper);
|
||||||
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getOwner()));
|
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
|
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
|
||||||
tx.addInputState(paper.getRef());
|
tx.addInputState(paper);
|
||||||
tx.addOutputState(new State(paper.getState().getIssuance(), newOwner, paper.getState().getFaceValue(), paper.getState().getMaturityDate(), paper.getState().getNotary()));
|
tx.addOutputState(new TransactionState<>(new State(paper.getState().getData().getIssuance(), newOwner, paper.getState().getData().getFaceValue(), paper.getState().getData().getMaturityDate()), paper.getState().getNotary()));
|
||||||
tx.addCommand(new Command(new Commands.Move(), paper.getState().getOwner()));
|
tx.addCommand(new Command(new Commands.Move(), paper.getState().getData().getOwner()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import com.r3corda.core.crypto.toStringShort
|
|||||||
import com.r3corda.core.utilities.Emoji
|
import com.r3corda.core.utilities.Emoji
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.Currency
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate
|
* This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate
|
||||||
@ -46,8 +46,7 @@ class CommercialPaper : Contract {
|
|||||||
val issuance: PartyAndReference,
|
val issuance: PartyAndReference,
|
||||||
override val owner: PublicKey,
|
override val owner: PublicKey,
|
||||||
val faceValue: Amount<Issued<Currency>>,
|
val faceValue: Amount<Issued<Currency>>,
|
||||||
val maturityDate: Instant,
|
val maturityDate: Instant
|
||||||
override val notary: Party
|
|
||||||
) : OwnableState, ICommercialPaperState {
|
) : OwnableState, ICommercialPaperState {
|
||||||
override val contract = CP_PROGRAM_ID
|
override val contract = CP_PROGRAM_ID
|
||||||
override val participants: List<PublicKey>
|
override val participants: List<PublicKey>
|
||||||
@ -63,8 +62,6 @@ class CommercialPaper : Contract {
|
|||||||
override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance)
|
override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance)
|
||||||
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
|
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
|
||||||
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
|
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
|
||||||
|
|
||||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Commands : CommandData {
|
interface Commands : CommandData {
|
||||||
@ -75,7 +72,7 @@ class CommercialPaper : Contract {
|
|||||||
class Issue : TypeOnlyCommandData(), Commands
|
class Issue : TypeOnlyCommandData(), Commands
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verify(tx: TransactionForVerification) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
|
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
|
||||||
val groups = tx.groupStates() { it: State -> it.withoutOwner() }
|
val groups = tx.groupStates() { it: State -> it.withoutOwner() }
|
||||||
|
|
||||||
@ -141,7 +138,7 @@ class CommercialPaper : Contract {
|
|||||||
*/
|
*/
|
||||||
fun generateIssue(faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
|
fun generateIssue(faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
|
||||||
val issuance = faceValue.token.issuer
|
val issuance = faceValue.token.issuer
|
||||||
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate, notary)
|
val state = TransactionState(State(issuance, issuance.party.owningKey, faceValue, maturityDate), notary)
|
||||||
return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
|
return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,9 +146,9 @@ class CommercialPaper : Contract {
|
|||||||
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
|
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
|
||||||
*/
|
*/
|
||||||
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) {
|
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) {
|
||||||
tx.addInputState(paper.ref)
|
tx.addInputState(paper)
|
||||||
tx.addOutputState(paper.state.copy(owner = newOwner))
|
tx.addOutputState(TransactionState(paper.state.data.copy(owner = newOwner), paper.state.notary))
|
||||||
tx.addCommand(Commands.Move(), paper.state.owner)
|
tx.addCommand(Commands.Move(), paper.state.data.owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,10 +161,10 @@ class CommercialPaper : Contract {
|
|||||||
@Throws(InsufficientBalanceException::class)
|
@Throws(InsufficientBalanceException::class)
|
||||||
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
|
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
|
||||||
// Add the cash movement using the states in our wallet.
|
// Add the cash movement using the states in our wallet.
|
||||||
val amount = paper.state.faceValue.let { amount -> Amount<Currency>(amount.quantity, amount.token.product) }
|
val amount = paper.state.data.faceValue.let { amount -> Amount<Currency>(amount.quantity, amount.token.product) }
|
||||||
Cash().generateSpend(tx, amount, paper.state.owner, wallet)
|
Cash().generateSpend(tx, amount, paper.state.data.owner, wallet)
|
||||||
tx.addInputState(paper.ref)
|
tx.addInputState(paper)
|
||||||
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.owner)
|
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,7 +487,7 @@ class InterestRateSwap() : Contract {
|
|||||||
/**
|
/**
|
||||||
* verify() with some examples of what needs to be checked.
|
* verify() with some examples of what needs to be checked.
|
||||||
*/
|
*/
|
||||||
override fun verify(tx: TransactionForVerification) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
|
|
||||||
// Group by Trade ID for in / out states
|
// Group by Trade ID for in / out states
|
||||||
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
|
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
|
||||||
@ -587,8 +587,7 @@ class InterestRateSwap() : Contract {
|
|||||||
val fixedLeg: FixedLeg,
|
val fixedLeg: FixedLeg,
|
||||||
val floatingLeg: FloatingLeg,
|
val floatingLeg: FloatingLeg,
|
||||||
val calculation: Calculation,
|
val calculation: Calculation,
|
||||||
val common: Common,
|
val common: Common
|
||||||
override val notary: Party
|
|
||||||
) : FixableDealState {
|
) : FixableDealState {
|
||||||
|
|
||||||
override val contract = IRS_PROGRAM_ID
|
override val contract = IRS_PROGRAM_ID
|
||||||
@ -621,12 +620,11 @@ class InterestRateSwap() : Contract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
// TODO: pass a notary
|
||||||
|
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
|
||||||
|
|
||||||
override fun generateAgreement(): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
|
override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
|
||||||
|
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, oldState.state.notary), oldState.ref), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
|
||||||
override fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix) {
|
|
||||||
InterestRateSwap().generateFix(ptx, StateAndRef(this, oldStateRef), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nextFixingOf(): FixOf? {
|
override fun nextFixingOf(): FixOf? {
|
||||||
@ -720,8 +718,8 @@ class InterestRateSwap() : Contract {
|
|||||||
val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule)
|
val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule)
|
||||||
|
|
||||||
// Put all the above into a new State object.
|
// Put all the above into a new State object.
|
||||||
val state = State(fixedLeg, floatingLeg, newCalculation, common, notary)
|
val state = TransactionState(State(fixedLeg, floatingLeg, newCalculation, common), notary)
|
||||||
return TransactionBuilder().withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey)))
|
return TransactionBuilder().withItems(state, Command(Commands.Agree(), listOf(state.data.floatingLeg.floatingRatePayer.owningKey, state.data.fixedLeg.fixedRatePayer.owningKey)))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calcFixingDate(date: LocalDate, fixingPeriod: DateOffset, calendar: BusinessCalendar): LocalDate {
|
private fun calcFixingDate(date: LocalDate, fixingPeriod: DateOffset, calendar: BusinessCalendar): LocalDate {
|
||||||
@ -734,8 +732,11 @@ class InterestRateSwap() : Contract {
|
|||||||
|
|
||||||
// TODO: Replace with rates oracle
|
// TODO: Replace with rates oracle
|
||||||
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Pair<LocalDate, Rate>) {
|
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Pair<LocalDate, Rate>) {
|
||||||
tx.addInputState(irs.ref)
|
tx.addInputState(irs)
|
||||||
tx.addOutputState(irs.state.copy(calculation = irs.state.calculation.applyFixing(fixing.first, FixedRate(fixing.second))))
|
tx.addOutputState(
|
||||||
tx.addCommand(Commands.Fix(), listOf(irs.state.floatingLeg.floatingRatePayer.owningKey, irs.state.fixedLeg.fixedRatePayer.owningKey))
|
TransactionState(
|
||||||
|
irs.state.data.copy(calculation = irs.state.data.calculation.applyFixing(fixing.first, FixedRate(fixing.second))),
|
||||||
|
irs.state.notary))
|
||||||
|
tx.addCommand(Commands.Fix(), listOf(irs.state.data.floatingLeg.floatingRatePayer.owningKey, irs.state.data.fixedLeg.fixedRatePayer.owningKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,12 +49,10 @@ class Cash : FungibleAsset<Currency>() {
|
|||||||
override val amount: Amount<Issued<Currency>>,
|
override val amount: Amount<Issued<Currency>>,
|
||||||
|
|
||||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||||
override val owner: PublicKey,
|
override val owner: PublicKey
|
||||||
|
|
||||||
override val notary: Party
|
|
||||||
) : FungibleAsset.State<Currency> {
|
) : FungibleAsset.State<Currency> {
|
||||||
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey, notary: Party)
|
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey)
|
||||||
: this(Amount(amount.quantity, Issued<Currency>(deposit, amount.token)), owner, notary)
|
: this(Amount(amount.quantity, Issued<Currency>(deposit, amount.token)), owner)
|
||||||
override val deposit: PartyAndReference
|
override val deposit: PartyAndReference
|
||||||
get() = amount.token.issuer
|
get() = amount.token.issuer
|
||||||
override val contract = CASH_PROGRAM_ID
|
override val contract = CASH_PROGRAM_ID
|
||||||
@ -66,7 +64,6 @@ class Cash : FungibleAsset<Currency>() {
|
|||||||
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
|
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
|
||||||
|
|
||||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just for grouping
|
// Just for grouping
|
||||||
@ -97,9 +94,9 @@ class Cash : FungibleAsset<Currency>() {
|
|||||||
*/
|
*/
|
||||||
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: PublicKey, notary: Party) {
|
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: PublicKey, notary: Party) {
|
||||||
check(tx.inputStates().isEmpty())
|
check(tx.inputStates().isEmpty())
|
||||||
check(tx.outputStates().sumCashOrNull() == null)
|
check(tx.outputStates().map { it.data }.sumCashOrNull() == null)
|
||||||
val at = amount.token.issuer
|
val at = amount.token.issuer
|
||||||
tx.addOutputState(Cash.State(amount, owner, notary))
|
tx.addOutputState(TransactionState(Cash.State(amount, owner), notary))
|
||||||
tx.addCommand(Cash.Commands.Issue(), at.party.owningKey)
|
tx.addCommand(Cash.Commands.Issue(), at.party.owningKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,9 +143,9 @@ class Cash : FungibleAsset<Currency>() {
|
|||||||
|
|
||||||
val currency = amount.token
|
val currency = amount.token
|
||||||
val acceptableCoins = run {
|
val acceptableCoins = run {
|
||||||
val ofCurrency = cashStates.filter { it.state.amount.token.product == currency }
|
val ofCurrency = cashStates.filter { it.state.data.amount.token.product == currency }
|
||||||
if (onlyFromParties != null)
|
if (onlyFromParties != null)
|
||||||
ofCurrency.filter { it.state.deposit.party in onlyFromParties }
|
ofCurrency.filter { it.state.data.deposit.party in onlyFromParties }
|
||||||
else
|
else
|
||||||
ofCurrency
|
ofCurrency
|
||||||
}
|
}
|
||||||
@ -159,7 +156,7 @@ class Cash : FungibleAsset<Currency>() {
|
|||||||
for (c in acceptableCoins) {
|
for (c in acceptableCoins) {
|
||||||
if (gatheredAmount >= amount) break
|
if (gatheredAmount >= amount) break
|
||||||
gathered.add(c)
|
gathered.add(c)
|
||||||
gatheredAmount += Amount(c.state.amount.quantity, currency)
|
gatheredAmount += Amount(c.state.data.amount.quantity, currency)
|
||||||
takeChangeFrom = c
|
takeChangeFrom = c
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,30 +164,30 @@ class Cash : FungibleAsset<Currency>() {
|
|||||||
throw InsufficientBalanceException(amount - gatheredAmount)
|
throw InsufficientBalanceException(amount - gatheredAmount)
|
||||||
|
|
||||||
val change = if (takeChangeFrom != null && gatheredAmount > amount) {
|
val change = if (takeChangeFrom != null && gatheredAmount > amount) {
|
||||||
Amount<Issued<Currency>>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.issuanceDef)
|
Amount<Issued<Currency>>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.state.issuanceDef)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val keysUsed = gathered.map { it.state.owner }.toSet()
|
val keysUsed = gathered.map { it.state.data.owner }.toSet()
|
||||||
|
|
||||||
val states = gathered.groupBy { it.state.deposit }.map {
|
val states = gathered.groupBy { it.state.data.deposit }.map {
|
||||||
val (deposit, coins) = it
|
val (deposit, coins) = it
|
||||||
val totalAmount = coins.map { it.state.amount }.sumOrThrow()
|
val totalAmount = coins.map { it.state.data.amount }.sumOrThrow()
|
||||||
State(totalAmount, to, coins.first().state.notary)
|
TransactionState(State(totalAmount, to), coins.first().state.notary)
|
||||||
}
|
}
|
||||||
|
|
||||||
val outputs = if (change != null) {
|
val outputs = if (change != null) {
|
||||||
// Just copy a key across as the change key. In real life of course, this works but leaks private data.
|
// Just copy a key across as the change key. In real life of course, this works but leaks private data.
|
||||||
// In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow
|
// In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow
|
||||||
// value flows through the transaction graph.
|
// value flows through the transaction graph.
|
||||||
val changeKey = gathered.first().state.owner
|
val changeKey = gathered.first().state.data.owner
|
||||||
// Add a change output and adjust the last output downwards.
|
// Add a change output and adjust the last output downwards.
|
||||||
states.subList(0, states.lastIndex) +
|
states.subList(0, states.lastIndex) +
|
||||||
states.last().let { it.copy(amount = it.amount - change) } +
|
states.last().let { TransactionState(it.data.copy(amount = it.data.amount - change), it.notary) } +
|
||||||
State(change, changeKey, gathered.last().state.notary)
|
TransactionState(State(change, changeKey), gathered.last().state.notary)
|
||||||
} else states
|
} else states
|
||||||
|
|
||||||
for (state in gathered) tx.addInputState(state.ref)
|
for (state in gathered) tx.addInputState(state)
|
||||||
for (state in outputs) tx.addOutputState(state)
|
for (state in outputs) tx.addOutputState(state)
|
||||||
// What if we already have a move command with the right keys? Filter it out here or in platform code?
|
// What if we already have a move command with the right keys? Filter it out here or in platform code?
|
||||||
val keysList = keysUsed.toList()
|
val keysList = keysUsed.toList()
|
||||||
|
@ -37,7 +37,6 @@ abstract class FungibleAsset<T> : Contract {
|
|||||||
override val amount: Amount<Issued<T>>
|
override val amount: Amount<Issued<T>>
|
||||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||||
override val owner: PublicKey
|
override val owner: PublicKey
|
||||||
override val notary: Party
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just for grouping
|
// Just for grouping
|
||||||
@ -58,7 +57,7 @@ abstract class FungibleAsset<T> : Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** This is the function EVERYONE runs */
|
/** This is the function EVERYONE runs */
|
||||||
override fun verify(tx: TransactionForVerification) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
// Each group is a set of input/output states with distinct issuance definitions. These assets are not fungible
|
// Each group is a set of input/output states with distinct issuance definitions. These assets are not fungible
|
||||||
// and must be kept separated for bookkeeping purposes.
|
// and must be kept separated for bookkeeping purposes.
|
||||||
val groups = tx.groupStates() { it: FungibleAsset.State<T> -> it.issuanceDef }
|
val groups = tx.groupStates() { it: FungibleAsset.State<T> -> it.issuanceDef }
|
||||||
@ -97,7 +96,7 @@ abstract class FungibleAsset<T> : Contract {
|
|||||||
|
|
||||||
private fun verifyIssueCommand(inputs: List<State<T>>,
|
private fun verifyIssueCommand(inputs: List<State<T>>,
|
||||||
outputs: List<State<T>>,
|
outputs: List<State<T>>,
|
||||||
tx: TransactionForVerification,
|
tx: TransactionForContract,
|
||||||
issueCommand: AuthenticatedObject<Commands.Issue>,
|
issueCommand: AuthenticatedObject<Commands.Issue>,
|
||||||
token: Issued<T>,
|
token: Issued<T>,
|
||||||
issuer: Party) {
|
issuer: Party) {
|
||||||
|
@ -9,10 +9,11 @@ import com.r3corda.core.contracts.DUMMY_PROGRAM_ID
|
|||||||
import com.r3corda.core.contracts.DummyContract
|
import com.r3corda.core.contracts.DummyContract
|
||||||
import com.r3corda.core.contracts.PartyAndReference
|
import com.r3corda.core.contracts.PartyAndReference
|
||||||
import com.r3corda.core.contracts.Issued
|
import com.r3corda.core.contracts.Issued
|
||||||
|
import com.r3corda.core.contracts.ContractState
|
||||||
|
import com.r3corda.core.contracts.TransactionState
|
||||||
import com.r3corda.core.crypto.NullPublicKey
|
import com.r3corda.core.crypto.NullPublicKey
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.generateKeyPair
|
import com.r3corda.core.crypto.generateKeyPair
|
||||||
import com.r3corda.core.testing.DUMMY_NOTARY
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ val TEST_PROGRAM_MAP: Map<Contract, Class<out Contract>> = mapOf(
|
|||||||
IRS_PROGRAM_ID to InterestRateSwap::class.java
|
IRS_PROGRAM_ID to InterestRateSwap::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
fun generateState(notary: Party = DUMMY_NOTARY) = DummyContract.State(Random().nextInt(), notary)
|
fun generateState() = DummyContract.State(Random().nextInt())
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@ -50,8 +51,10 @@ fun generateState(notary: Party = DUMMY_NOTARY) = DummyContract.State(Random().n
|
|||||||
infix fun Cash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
|
infix fun Cash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
|
||||||
infix fun Cash.State.`issued by`(party: Party) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party))))
|
infix fun Cash.State.`issued by`(party: Party) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party))))
|
||||||
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit)))
|
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit)))
|
||||||
|
infix fun Cash.State.`with notary`(notary: Party) = TransactionState(this, notary)
|
||||||
|
|
||||||
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner = owner)
|
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner = owner)
|
||||||
|
infix fun CommercialPaper.State.`with notary`(notary: Party) = TransactionState(this, notary)
|
||||||
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
|
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
|
||||||
|
|
||||||
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State =
|
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State =
|
||||||
@ -62,7 +65,8 @@ val DUMMY_CASH_ISSUER = Party("Snake Oil Issuer", DUMMY_CASH_ISSUER_KEY.public).
|
|||||||
/** Allows you to write 100.DOLLARS.CASH */
|
/** Allows you to write 100.DOLLARS.CASH */
|
||||||
val Amount<Currency>.CASH: Cash.State get() = Cash.State(
|
val Amount<Currency>.CASH: Cash.State get() = Cash.State(
|
||||||
Amount<Issued<Currency>>(this.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, this.token)),
|
Amount<Issued<Currency>>(this.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, this.token)),
|
||||||
NullPublicKey, DUMMY_NOTARY)
|
NullPublicKey)
|
||||||
|
|
||||||
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey, DUMMY_NOTARY)
|
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey)
|
||||||
|
|
||||||
|
infix fun ContractState.`with notary`(notary: Party) = TransactionState(this, notary)
|
||||||
|
@ -17,7 +17,7 @@ import com.r3corda.core.utilities.trace
|
|||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
import java.util.Currency
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
|
* This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
|
||||||
@ -129,7 +129,7 @@ object TwoPartyTradeProtocol {
|
|||||||
// This verifies that the transaction is contract-valid, even though it is missing signatures.
|
// This verifies that the transaction is contract-valid, even though it is missing signatures.
|
||||||
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments))
|
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments))
|
||||||
|
|
||||||
if (wtx.outputs.sumCashBy(myKeyPair.public) != price)
|
if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public) != price)
|
||||||
throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
|
throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
|
||||||
|
|
||||||
// There are all sorts of funny games a malicious secondary might play here, we should fix them:
|
// There are all sorts of funny games a malicious secondary might play here, we should fix them:
|
||||||
@ -221,7 +221,7 @@ object TwoPartyTradeProtocol {
|
|||||||
progressTracker.currentStep = VERIFYING
|
progressTracker.currentStep = VERIFYING
|
||||||
maybeTradeRequest.validate {
|
maybeTradeRequest.validate {
|
||||||
// What is the seller trying to sell us?
|
// What is the seller trying to sell us?
|
||||||
val asset = it.assetForSale.state
|
val asset = it.assetForSale.state.data
|
||||||
val assetTypeName = asset.javaClass.name
|
val assetTypeName = asset.javaClass.name
|
||||||
logger.trace { "Got trade request for a $assetTypeName: ${it.assetForSale}" }
|
logger.trace { "Got trade request for a $assetTypeName: ${it.assetForSale}" }
|
||||||
|
|
||||||
@ -272,15 +272,15 @@ object TwoPartyTradeProtocol {
|
|||||||
val cashStates = wallet.statesOfType<Cash.State>()
|
val cashStates = wallet.statesOfType<Cash.State>()
|
||||||
val cashSigningPubKeys = Cash().generateSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey, cashStates)
|
val cashSigningPubKeys = Cash().generateSpend(ptx, tradeRequest.price, tradeRequest.sellerOwnerKey, cashStates)
|
||||||
// Add inputs/outputs/a command for the movement of the asset.
|
// Add inputs/outputs/a command for the movement of the asset.
|
||||||
ptx.addInputState(tradeRequest.assetForSale.ref)
|
ptx.addInputState(tradeRequest.assetForSale)
|
||||||
// Just pick some new public key for now. This won't be linked with our identity in any way, which is what
|
// Just pick some new public key for now. This won't be linked with our identity in any way, which is what
|
||||||
// we want for privacy reasons: the key is here ONLY to manage and control ownership, it is not intended to
|
// we want for privacy reasons: the key is here ONLY to manage and control ownership, it is not intended to
|
||||||
// reveal who the owner actually is. The key management service is expected to derive a unique key from some
|
// reveal who the owner actually is. The key management service is expected to derive a unique key from some
|
||||||
// initial seed in order to provide privacy protection.
|
// initial seed in order to provide privacy protection.
|
||||||
val freshKey = serviceHub.keyManagementService.freshKey()
|
val freshKey = serviceHub.keyManagementService.freshKey()
|
||||||
val (command, state) = tradeRequest.assetForSale.state.withNewOwner(freshKey.public)
|
val (command, state) = tradeRequest.assetForSale.state.data.withNewOwner(freshKey.public)
|
||||||
ptx.addOutputState(state)
|
ptx.addOutputState(TransactionState(state, tradeRequest.assetForSale.state.notary))
|
||||||
ptx.addCommand(command, tradeRequest.assetForSale.state.owner)
|
ptx.addCommand(command, tradeRequest.assetForSale.state.data.owner)
|
||||||
|
|
||||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||||
// to have one.
|
// to have one.
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package com.r3corda.contracts
|
package com.r3corda.contracts
|
||||||
|
|
||||||
import com.r3corda.contracts.testing.CASH
|
|
||||||
import com.r3corda.contracts.testing.`issued by`
|
|
||||||
import com.r3corda.contracts.testing.`owned by`
|
|
||||||
import com.r3corda.contracts.cash.Cash
|
import com.r3corda.contracts.cash.Cash
|
||||||
import com.r3corda.contracts.testing.STATE
|
import com.r3corda.contracts.testing.*
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.days
|
import com.r3corda.core.days
|
||||||
@ -32,8 +29,7 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
|
|||||||
MEGA_CORP.ref(123),
|
MEGA_CORP.ref(123),
|
||||||
MEGA_CORP_PUBKEY,
|
MEGA_CORP_PUBKEY,
|
||||||
1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
||||||
TEST_TX_TIME + 7.days,
|
TEST_TX_TIME + 7.days
|
||||||
DUMMY_NOTARY
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue()
|
override fun getIssueCommand(): CommandData = JavaCommercialPaper.Commands.Issue()
|
||||||
@ -46,8 +42,7 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
|
|||||||
issuance = MEGA_CORP.ref(123),
|
issuance = MEGA_CORP.ref(123),
|
||||||
owner = MEGA_CORP_PUBKEY,
|
owner = MEGA_CORP_PUBKEY,
|
||||||
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
||||||
maturityDate = TEST_TX_TIME + 7.days,
|
maturityDate = TEST_TX_TIME + 7.days
|
||||||
notary = DUMMY_NOTARY
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue()
|
override fun getIssueCommand(): CommandData = CommercialPaper.Commands.Issue()
|
||||||
@ -121,7 +116,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
fun `issue cannot replace an existing state`() {
|
fun `issue cannot replace an existing state`() {
|
||||||
transactionGroup {
|
transactionGroup {
|
||||||
roots {
|
roots {
|
||||||
transaction(thisTest.getPaper() label "paper")
|
transaction(thisTest.getPaper() `with notary` DUMMY_NOTARY label "paper")
|
||||||
}
|
}
|
||||||
transaction {
|
transaction {
|
||||||
input("paper")
|
input("paper")
|
||||||
@ -144,9 +139,9 @@ class CommercialPaperTestsGeneric {
|
|||||||
trade(destroyPaperAtRedemption = false).expectFailureOfTx(3, "must be destroyed")
|
trade(destroyPaperAtRedemption = false).expectFailureOfTx(3, "must be destroyed")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cashOutputsToWallet(vararg states: Cash.State): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> {
|
fun <T : ContractState> cashOutputsToWallet(vararg outputs: TransactionState<T>): Pair<LedgerTransaction, List<StateAndRef<T>>> {
|
||||||
val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*states), emptyList(), SecureHash.randomSHA256())
|
val ltx = LedgerTransaction(emptyList(), emptyList(), listOf(*outputs), emptyList(), SecureHash.randomSHA256(), TransactionType.Business())
|
||||||
return Pair(ltx, states.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
|
return Pair(ltx, outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -163,9 +158,9 @@ class CommercialPaperTestsGeneric {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val (alicesWalletTX, alicesWallet) = cashOutputsToWallet(
|
val (alicesWalletTX, alicesWallet) = cashOutputsToWallet(
|
||||||
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY,
|
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY,
|
||||||
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY,
|
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY,
|
||||||
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY
|
3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY
|
||||||
)
|
)
|
||||||
|
|
||||||
// Alice pays $9000 to MiniCorp to own some of their debt.
|
// Alice pays $9000 to MiniCorp to own some of their debt.
|
||||||
@ -181,8 +176,8 @@ class CommercialPaperTestsGeneric {
|
|||||||
|
|
||||||
// Won't be validated.
|
// Won't be validated.
|
||||||
val (corpWalletTX, corpWallet) = cashOutputsToWallet(
|
val (corpWalletTX, corpWallet) = cashOutputsToWallet(
|
||||||
9000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY,
|
9000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY `with notary` DUMMY_NOTARY,
|
||||||
4000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY
|
4000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY `with notary` DUMMY_NOTARY
|
||||||
)
|
)
|
||||||
|
|
||||||
fun makeRedeemTX(time: Instant): LedgerTransaction {
|
fun makeRedeemTX(time: Instant): LedgerTransaction {
|
||||||
@ -213,8 +208,8 @@ class CommercialPaperTestsGeneric {
|
|||||||
val someProfits = 1200.DOLLARS `issued by` issuer
|
val someProfits = 1200.DOLLARS `issued by` issuer
|
||||||
return transactionGroupFor() {
|
return transactionGroupFor() {
|
||||||
roots {
|
roots {
|
||||||
transaction(900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY label "alice's $900")
|
transaction(900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY label "alice's $900")
|
||||||
transaction(someProfits.STATE `owned by` MEGA_CORP_PUBKEY label "some profits")
|
transaction(someProfits.STATE `owned by` MEGA_CORP_PUBKEY `with notary` DUMMY_NOTARY label "some profits")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some CP is issued onto the ledger by MegaCorp.
|
// Some CP is issued onto the ledger by MegaCorp.
|
||||||
@ -230,7 +225,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
input("paper")
|
input("paper")
|
||||||
input("alice's $900")
|
input("alice's $900")
|
||||||
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
|
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
|
||||||
output("alice's paper") { "paper".output `owned by` ALICE_PUBKEY }
|
output("alice's paper") { "paper".output.data `owned by` ALICE_PUBKEY }
|
||||||
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
|
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||||
arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
||||||
}
|
}
|
||||||
@ -244,7 +239,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
|
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
|
||||||
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
|
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
|
||||||
if (!destroyPaperAtRedemption)
|
if (!destroyPaperAtRedemption)
|
||||||
output { "paper".output }
|
output { "paper".output.data }
|
||||||
|
|
||||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
|
arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
|
||||||
|
@ -97,7 +97,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
|||||||
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
|
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
|
||||||
)
|
)
|
||||||
|
|
||||||
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY)
|
InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
|
||||||
}
|
}
|
||||||
2 -> {
|
2 -> {
|
||||||
// 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides)
|
// 10y swap, we pay 1.3% fixed 30/360 semi, rec 3m usd libor act/360 Q on 25m notional (mod foll/adj on both sides)
|
||||||
@ -187,7 +187,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
|||||||
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
|
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
|
||||||
)
|
)
|
||||||
|
|
||||||
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, notary = DUMMY_NOTARY)
|
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common)
|
||||||
|
|
||||||
}
|
}
|
||||||
else -> TODO("IRS number $irsSelect not defined")
|
else -> TODO("IRS number $irsSelect not defined")
|
||||||
@ -204,8 +204,7 @@ class IRSTests {
|
|||||||
exampleIRS.fixedLeg,
|
exampleIRS.fixedLeg,
|
||||||
exampleIRS.floatingLeg,
|
exampleIRS.floatingLeg,
|
||||||
exampleIRS.calculation,
|
exampleIRS.calculation,
|
||||||
exampleIRS.common,
|
exampleIRS.common
|
||||||
DUMMY_NOTARY
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val outState = inState.copy()
|
val outState = inState.copy()
|
||||||
@ -255,7 +254,7 @@ class IRSTests {
|
|||||||
* Utility so I don't have to keep typing this
|
* Utility so I don't have to keep typing this
|
||||||
*/
|
*/
|
||||||
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
|
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
|
||||||
return generateIRSTxn(irsSelector).outputs.filterIsInstance<InterestRateSwap.State>().single()
|
return generateIRSTxn(irsSelector).outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -287,7 +286,7 @@ class IRSTests {
|
|||||||
newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.value)))
|
newCalculation = newCalculation.applyFixing(it.key, FixedRate(PercentageRatioUnit(it.value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common, DUMMY_NOTARY)
|
val newIRS = InterestRateSwap.State(irs.fixedLeg, irs.floatingLeg, newCalculation, irs.common)
|
||||||
println(newIRS.exportIRSToCSV())
|
println(newIRS.exportIRSToCSV())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,7 +305,7 @@ class IRSTests {
|
|||||||
@Test
|
@Test
|
||||||
fun generateIRSandFixSome() {
|
fun generateIRSandFixSome() {
|
||||||
var previousTXN = generateIRSTxn(1)
|
var previousTXN = generateIRSTxn(1)
|
||||||
var currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single()
|
var currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
||||||
println(currentIRS.prettyPrint())
|
println(currentIRS.prettyPrint())
|
||||||
while (true) {
|
while (true) {
|
||||||
val nextFixingDate = currentIRS.calculation.nextFixingDate() ?: break
|
val nextFixingDate = currentIRS.calculation.nextFixingDate() ?: break
|
||||||
@ -323,7 +322,7 @@ class IRSTests {
|
|||||||
}
|
}
|
||||||
tx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments)
|
tx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments)
|
||||||
}
|
}
|
||||||
currentIRS = previousTXN.outputs.filterIsInstance<InterestRateSwap.State>().single()
|
currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
||||||
println(currentIRS.prettyPrint())
|
println(currentIRS.prettyPrint())
|
||||||
previousTXN = fixTX
|
previousTXN = fixTX
|
||||||
}
|
}
|
||||||
@ -387,11 +386,11 @@ class IRSTests {
|
|||||||
transaction("Fix") {
|
transaction("Fix") {
|
||||||
input("irs post agreement")
|
input("irs post agreement")
|
||||||
output("irs post first fixing") {
|
output("irs post first fixing") {
|
||||||
"irs post agreement".output.copy(
|
"irs post agreement".output.data.copy(
|
||||||
"irs post agreement".output.fixedLeg,
|
"irs post agreement".output.data.fixedLeg,
|
||||||
"irs post agreement".output.floatingLeg,
|
"irs post agreement".output.data.floatingLeg,
|
||||||
"irs post agreement".output.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
"irs post agreement".output.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
||||||
"irs post agreement".output.common
|
"irs post agreement".output.data.common
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
arg(ORACLE_PUBKEY) {
|
arg(ORACLE_PUBKEY) {
|
||||||
@ -678,7 +677,6 @@ class IRSTests {
|
|||||||
irs.floatingLeg,
|
irs.floatingLeg,
|
||||||
irs.calculation,
|
irs.calculation,
|
||||||
irs.common.copy(tradeID = "t1")
|
irs.common.copy(tradeID = "t1")
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||||
@ -692,7 +690,6 @@ class IRSTests {
|
|||||||
irs.floatingLeg,
|
irs.floatingLeg,
|
||||||
irs.calculation,
|
irs.calculation,
|
||||||
irs.common.copy(tradeID = "t2")
|
irs.common.copy(tradeID = "t2")
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||||
@ -703,19 +700,19 @@ class IRSTests {
|
|||||||
input("irs post agreement1")
|
input("irs post agreement1")
|
||||||
input("irs post agreement2")
|
input("irs post agreement2")
|
||||||
output("irs post first fixing1") {
|
output("irs post first fixing1") {
|
||||||
"irs post agreement1".output.copy(
|
"irs post agreement1".output.data.copy(
|
||||||
"irs post agreement1".output.fixedLeg,
|
"irs post agreement1".output.data.fixedLeg,
|
||||||
"irs post agreement1".output.floatingLeg,
|
"irs post agreement1".output.data.floatingLeg,
|
||||||
"irs post agreement1".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
"irs post agreement1".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||||
"irs post agreement1".output.common.copy(tradeID = "t1")
|
"irs post agreement1".output.data.common.copy(tradeID = "t1")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
output("irs post first fixing2") {
|
output("irs post first fixing2") {
|
||||||
"irs post agreement2".output.copy(
|
"irs post agreement2".output.data.copy(
|
||||||
"irs post agreement2".output.fixedLeg,
|
"irs post agreement2".output.data.fixedLeg,
|
||||||
"irs post agreement2".output.floatingLeg,
|
"irs post agreement2".output.data.floatingLeg,
|
||||||
"irs post agreement2".output.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
"irs post agreement2".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||||
"irs post agreement2".output.common.copy(tradeID = "t2")
|
"irs post agreement2".output.data.common.copy(tradeID = "t2")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import com.r3corda.core.contracts.DummyContract
|
|||||||
import com.r3corda.contracts.testing.`issued by`
|
import com.r3corda.contracts.testing.`issued by`
|
||||||
import com.r3corda.contracts.testing.`owned by`
|
import com.r3corda.contracts.testing.`owned by`
|
||||||
import com.r3corda.contracts.testing.`with deposit`
|
import com.r3corda.contracts.testing.`with deposit`
|
||||||
|
import com.r3corda.contracts.testing.`with notary`
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
@ -12,19 +13,14 @@ import com.r3corda.core.testing.*
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.*
|
||||||
import kotlin.test.assertFailsWith
|
|
||||||
import kotlin.test.assertNotEquals
|
|
||||||
import kotlin.test.assertNull
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
class CashTests {
|
class CashTests {
|
||||||
val defaultRef = OpaqueBytes(ByteArray(1, {1}))
|
val defaultRef = OpaqueBytes(ByteArray(1, {1}))
|
||||||
val defaultIssuer = MEGA_CORP.ref(defaultRef)
|
val defaultIssuer = MEGA_CORP.ref(defaultRef)
|
||||||
val inState = Cash.State(
|
val inState = Cash.State(
|
||||||
amount = 1000.DOLLARS `issued by` defaultIssuer,
|
amount = 1000.DOLLARS `issued by` defaultIssuer,
|
||||||
owner = DUMMY_PUBKEY_1,
|
owner = DUMMY_PUBKEY_1
|
||||||
notary = DUMMY_NOTARY
|
|
||||||
)
|
)
|
||||||
val outState = inState.copy(owner = DUMMY_PUBKEY_2)
|
val outState = inState.copy(owner = DUMMY_PUBKEY_2)
|
||||||
|
|
||||||
@ -71,7 +67,7 @@ class CashTests {
|
|||||||
fun issueMoney() {
|
fun issueMoney() {
|
||||||
// Check we can't "move" money into existence.
|
// Check we can't "move" money into existence.
|
||||||
transaction {
|
transaction {
|
||||||
input { DummyContract.State(notary = DUMMY_NOTARY) }
|
input { DummyContract.State() }
|
||||||
output { outState }
|
output { outState }
|
||||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||||
|
|
||||||
@ -89,8 +85,7 @@ class CashTests {
|
|||||||
output {
|
output {
|
||||||
Cash.State(
|
Cash.State(
|
||||||
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
|
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
|
||||||
owner = DUMMY_PUBKEY_1,
|
owner = DUMMY_PUBKEY_1
|
||||||
notary = DUMMY_NOTARY
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
tweak {
|
tweak {
|
||||||
@ -105,7 +100,7 @@ class CashTests {
|
|||||||
val ptx = TransactionBuilder()
|
val ptx = TransactionBuilder()
|
||||||
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
|
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
|
||||||
assertTrue(ptx.inputStates().isEmpty())
|
assertTrue(ptx.inputStates().isEmpty())
|
||||||
val s = ptx.outputStates()[0] as Cash.State
|
val s = ptx.outputStates()[0].data as Cash.State
|
||||||
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
|
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
|
||||||
assertEquals(MINI_CORP, s.deposit.party)
|
assertEquals(MINI_CORP, s.deposit.party)
|
||||||
assertEquals(DUMMY_PUBKEY_1, s.owner)
|
assertEquals(DUMMY_PUBKEY_1, s.owner)
|
||||||
@ -189,7 +184,7 @@ class CashTests {
|
|||||||
|
|
||||||
// Include the previously issued cash in a new issuance command
|
// Include the previously issued cash in a new issuance command
|
||||||
ptx = TransactionBuilder()
|
ptx = TransactionBuilder()
|
||||||
ptx.addInputState(tx.tx.outRef<Cash.State>(0).ref)
|
ptx.addInputState(tx.tx.outRef<Cash.State>(0))
|
||||||
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +354,7 @@ class CashTests {
|
|||||||
fun multiCurrency() {
|
fun multiCurrency() {
|
||||||
// Check we can do an atomic currency trade tx.
|
// Check we can do an atomic currency trade tx.
|
||||||
transaction {
|
transaction {
|
||||||
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), DUMMY_PUBKEY_2, DUMMY_NOTARY)
|
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), DUMMY_PUBKEY_2)
|
||||||
input { inState `owned by` DUMMY_PUBKEY_1 }
|
input { inState `owned by` DUMMY_PUBKEY_1 }
|
||||||
input { pounds }
|
input { pounds }
|
||||||
output { inState `owned by` DUMMY_PUBKEY_2 }
|
output { inState `owned by` DUMMY_PUBKEY_2 }
|
||||||
@ -379,7 +374,7 @@ class CashTests {
|
|||||||
|
|
||||||
fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) =
|
fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) =
|
||||||
StateAndRef(
|
StateAndRef(
|
||||||
Cash.State(amount `issued by` corp.ref(depositRef), OUR_PUBKEY_1, DUMMY_NOTARY),
|
Cash.State(amount `issued by` corp.ref(depositRef), OUR_PUBKEY_1) `with notary` DUMMY_NOTARY,
|
||||||
StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
|
StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -400,7 +395,7 @@ class CashTests {
|
|||||||
fun generateSimpleDirectSpend() {
|
fun generateSimpleDirectSpend() {
|
||||||
val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
|
val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
|
||||||
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
||||||
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0])
|
assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0].data)
|
||||||
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,8 +410,8 @@ class CashTests {
|
|||||||
fun generateSimpleSpendWithChange() {
|
fun generateSimpleSpendWithChange() {
|
||||||
val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
|
val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
|
||||||
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
||||||
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0])
|
assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
|
||||||
assertEquals(WALLET[0].state.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1])
|
assertEquals(WALLET[0].state.data.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data)
|
||||||
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,7 +420,7 @@ class CashTests {
|
|||||||
val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
|
val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
|
||||||
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
||||||
assertEquals(WALLET[1].ref, wtx.inputs[1])
|
assertEquals(WALLET[1].ref, wtx.inputs[1])
|
||||||
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0])
|
assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
|
||||||
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,8 +431,8 @@ class CashTests {
|
|||||||
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
||||||
assertEquals(WALLET[1].ref, wtx.inputs[1])
|
assertEquals(WALLET[1].ref, wtx.inputs[1])
|
||||||
assertEquals(WALLET[2].ref, wtx.inputs[2])
|
assertEquals(WALLET[2].ref, wtx.inputs[2])
|
||||||
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0])
|
assertEquals(WALLET[0].state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
|
||||||
assertEquals(WALLET[2].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1])
|
assertEquals(WALLET[2].state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1].data)
|
||||||
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,9 +453,9 @@ class CashTests {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun aggregation() {
|
fun aggregation() {
|
||||||
val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP_PUBKEY, DUMMY_NOTARY)
|
val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP_PUBKEY)
|
||||||
val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP_PUBKEY, DUMMY_NOTARY)
|
val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP_PUBKEY)
|
||||||
val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY, DUMMY_NOTARY)
|
val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY)
|
||||||
|
|
||||||
// Obviously it must be possible to aggregate states with themselves
|
// Obviously it must be possible to aggregate states with themselves
|
||||||
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
|
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
|
||||||
@ -474,7 +469,7 @@ class CashTests {
|
|||||||
|
|
||||||
// States cannot be aggregated if the currency differs
|
// States cannot be aggregated if the currency differs
|
||||||
assertNotEquals(oneThousandDollarsFromMini.issuanceDef,
|
assertNotEquals(oneThousandDollarsFromMini.issuanceDef,
|
||||||
Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY, DUMMY_NOTARY).issuanceDef)
|
Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY).issuanceDef)
|
||||||
|
|
||||||
// States cannot be aggregated if the reference differs
|
// States cannot be aggregated if the reference differs
|
||||||
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef)
|
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef)
|
||||||
@ -484,9 +479,9 @@ class CashTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `summing by owner`() {
|
fun `summing by owner`() {
|
||||||
val states = listOf(
|
val states = listOf(
|
||||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY, DUMMY_NOTARY),
|
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY),
|
||||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
|
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
|
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
|
||||||
)
|
)
|
||||||
assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP_PUBKEY))
|
assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP_PUBKEY))
|
||||||
}
|
}
|
||||||
@ -494,8 +489,8 @@ class CashTests {
|
|||||||
@Test(expected = UnsupportedOperationException::class)
|
@Test(expected = UnsupportedOperationException::class)
|
||||||
fun `summing by owner throws`() {
|
fun `summing by owner throws`() {
|
||||||
val states = listOf(
|
val states = listOf(
|
||||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
|
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
|
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
|
||||||
)
|
)
|
||||||
states.sumCashBy(MINI_CORP_PUBKEY)
|
states.sumCashBy(MINI_CORP_PUBKEY)
|
||||||
}
|
}
|
||||||
@ -516,9 +511,9 @@ class CashTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `summing a single currency`() {
|
fun `summing a single currency`() {
|
||||||
val states = listOf(
|
val states = listOf(
|
||||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
|
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
|
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
|
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
|
||||||
)
|
)
|
||||||
// Test that summing everything produces the total number of dollars
|
// Test that summing everything produces the total number of dollars
|
||||||
var expected = 7000.DOLLARS `issued by` defaultIssuer
|
var expected = 7000.DOLLARS `issued by` defaultIssuer
|
||||||
@ -529,8 +524,8 @@ class CashTests {
|
|||||||
@Test(expected = IllegalArgumentException::class)
|
@Test(expected = IllegalArgumentException::class)
|
||||||
fun `summing multiple currencies`() {
|
fun `summing multiple currencies`() {
|
||||||
val states = listOf(
|
val states = listOf(
|
||||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
|
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY),
|
||||||
Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
|
Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP_PUBKEY)
|
||||||
)
|
)
|
||||||
// Test that summing everything fails because we're mixing units
|
// Test that summing everything fails because we're mixing units
|
||||||
states.sumCash()
|
states.sumCash()
|
||||||
|
@ -92,7 +92,7 @@ fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: Stri
|
|||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
// TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here?
|
// TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here?
|
||||||
inline fun <reified T : CommandData> verifyMoveCommands(inputs: List<OwnableState>, tx: TransactionForVerification) {
|
inline fun <reified T : CommandData> verifyMoveCommands(inputs: List<OwnableState>, tx: TransactionForContract) {
|
||||||
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
|
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
|
||||||
// see a signature from each of those keys. The actual signatures have been verified against the transaction
|
// see a signature from each of those keys. The actual signatures have been verified against the transaction
|
||||||
// data by the platform before execution.
|
// data by the platform before execution.
|
||||||
|
@ -9,30 +9,33 @@ import java.security.PublicKey
|
|||||||
val DUMMY_PROGRAM_ID = DummyContract()
|
val DUMMY_PROGRAM_ID = DummyContract()
|
||||||
|
|
||||||
class DummyContract : Contract {
|
class DummyContract : Contract {
|
||||||
data class State(val magicNumber: Int = 0,
|
data class State(val magicNumber: Int = 0) : ContractState {
|
||||||
override val notary: Party) : ContractState {
|
|
||||||
override val contract = DUMMY_PROGRAM_ID
|
override val contract = DUMMY_PROGRAM_ID
|
||||||
override val participants: List<PublicKey>
|
override val participants: List<PublicKey>
|
||||||
get() = emptyList()
|
get() = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
data class SingleOwnerState(val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState {
|
||||||
|
override val contract = DUMMY_PROGRAM_ID
|
||||||
|
override val participants: List<PublicKey>
|
||||||
|
get() = listOf(owner)
|
||||||
|
|
||||||
|
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MultiOwnerState(val magicNumber: Int = 0,
|
data class MultiOwnerState(val magicNumber: Int = 0,
|
||||||
val owners: List<PublicKey>,
|
val owners: List<PublicKey>) : ContractState {
|
||||||
override val notary: Party) : ContractState {
|
|
||||||
override val contract = DUMMY_PROGRAM_ID
|
override val contract = DUMMY_PROGRAM_ID
|
||||||
override val participants: List<PublicKey>
|
override val participants: List<PublicKey>
|
||||||
get() = owners
|
get() = owners
|
||||||
|
|
||||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Commands : CommandData {
|
interface Commands : CommandData {
|
||||||
class Create : TypeOnlyCommandData(), Commands
|
class Create : TypeOnlyCommandData(), Commands
|
||||||
|
class Move : TypeOnlyCommandData(), Commands
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verify(tx: TransactionForVerification) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
// Always accepts.
|
// Always accepts.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +43,7 @@ class DummyContract : Contract {
|
|||||||
override val legalContractReference: SecureHash = SecureHash.sha256("")
|
override val legalContractReference: SecureHash = SecureHash.sha256("")
|
||||||
|
|
||||||
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
|
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
|
||||||
val state = State(magicNumber, notary)
|
val state = TransactionState(SingleOwnerState(magicNumber, owner.party.owningKey), notary)
|
||||||
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -27,17 +27,20 @@ interface ContractState {
|
|||||||
/** Contract by which the state belongs */
|
/** Contract by which the state belongs */
|
||||||
val contract: Contract
|
val contract: Contract
|
||||||
|
|
||||||
/** Identity of the notary that ensures this state is not used as an input to a transaction more than once */
|
|
||||||
val notary: Party
|
|
||||||
|
|
||||||
/** List of public keys for each party that can consume this state in a valid transaction. */
|
/** List of public keys for each party that can consume this state in a valid transaction. */
|
||||||
val participants: List<PublicKey>
|
val participants: List<PublicKey>
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A wrapper for [ContractState] containing additional platform-level state information. This is the state */
|
||||||
|
data class TransactionState<out T : ContractState>(
|
||||||
|
val data: T,
|
||||||
|
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
|
||||||
|
val notary: Party) {
|
||||||
/**
|
/**
|
||||||
* Copies the underlying data structure, replacing the notary field with the new value.
|
* Copies the underlying state, replacing the notary field with the new value.
|
||||||
* To replace the notary, we need an approval (signature) from _all_ participants.
|
* To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState]
|
||||||
*/
|
*/
|
||||||
fun withNewNotary(newNotary: Party): ContractState
|
fun withNewNotary(newNotary: Party) = TransactionState(this.data, newNotary)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,7 +108,7 @@ interface DealState : LinearState {
|
|||||||
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a
|
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a
|
||||||
* Contract instance from a ContractState are imminent, at which point we can move this out of here
|
* Contract instance from a ContractState are imminent, at which point we can move this out of here
|
||||||
*/
|
*/
|
||||||
fun generateAgreement(): TransactionBuilder
|
fun generateAgreement(notary: Party): TransactionBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,7 +128,7 @@ interface FixableDealState : DealState {
|
|||||||
* TODO: This would also likely move to methods on the Contract once the changes to reference
|
* TODO: This would also likely move to methods on the Contract once the changes to reference
|
||||||
* the Contract from the ContractState are in
|
* the Contract from the ContractState are in
|
||||||
*/
|
*/
|
||||||
fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix)
|
fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
|
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
|
||||||
@ -140,11 +143,11 @@ data class StateRef(val txhash: SecureHash, val index: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** A StateAndRef is simply a (state, ref) pair. For instance, a wallet (which holds available assets) contains these. */
|
/** A StateAndRef is simply a (state, ref) pair. For instance, a wallet (which holds available assets) contains these. */
|
||||||
data class StateAndRef<out T : ContractState>(val state: T, val ref: StateRef)
|
data class StateAndRef<out T : ContractState>(val state: TransactionState<T>, val ref: StateRef)
|
||||||
|
|
||||||
/** Filters a list of [StateAndRef] objects according to the type of the states */
|
/** Filters a list of [StateAndRef] objects according to the type of the states */
|
||||||
inline fun <reified T : ContractState> List<StateAndRef<ContractState>>.filterStatesOfType(): List<StateAndRef<T>> {
|
inline fun <reified T : ContractState> List<StateAndRef<ContractState>>.filterStatesOfType(): List<StateAndRef<T>> {
|
||||||
return mapNotNull { if (it.state is T) StateAndRef(it.state, it.ref) else null }
|
return mapNotNull { if (it.state.data is T) StateAndRef(TransactionState(it.state.data, it.state.notary), it.ref) else null }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -166,6 +169,9 @@ abstract class TypeOnlyCommandData : CommandData {
|
|||||||
|
|
||||||
/** Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes */
|
/** Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes */
|
||||||
data class Command(val value: CommandData, val signers: List<PublicKey>) {
|
data class Command(val value: CommandData, val signers: List<PublicKey>) {
|
||||||
|
init {
|
||||||
|
require(signers.isNotEmpty())
|
||||||
|
}
|
||||||
constructor(data: CommandData, key: PublicKey) : this(data, listOf(key))
|
constructor(data: CommandData, key: PublicKey) : this(data, listOf(key))
|
||||||
|
|
||||||
private fun commandDataToString() = value.toString().let { if (it.contains("@")) it.replace('$', '.').split("@")[0] else it }
|
private fun commandDataToString() = value.toString().let { if (it.contains("@")) it.replace('$', '.').split("@")[0] else it }
|
||||||
@ -198,11 +204,14 @@ data class TimestampCommand(val after: Instant?, val before: Instant?) : Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the transaction is only used for changing the Notary for a state. If present in a transaction,
|
* Command that has to be signed by all participants of the states in the transaction
|
||||||
* the contract code is not run, and special platform-level validation logic is used instead
|
* in order to perform a notary change
|
||||||
*/
|
*/
|
||||||
class ChangeNotary : TypeOnlyCommandData()
|
class ChangeNotary : TypeOnlyCommandData()
|
||||||
|
|
||||||
|
/** Command that indicates the requirement of a Notary signature for the input states */
|
||||||
|
class NotaryCommand : TypeOnlyCommandData()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implemented by a program that implements business logic on the shared ledger. All participants run this code for
|
* Implemented by a program that implements business logic on the shared ledger. All participants run this code for
|
||||||
* every [LedgerTransaction] they see on the network, for every input and output state. All contracts must accept the
|
* every [LedgerTransaction] they see on the network, for every input and output state. All contracts must accept the
|
||||||
@ -217,7 +226,7 @@ interface Contract {
|
|||||||
* existing contract code.
|
* existing contract code.
|
||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
fun verify(tx: TransactionForVerification)
|
fun verify(tx: TransactionForContract)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unparsed reference to the natural language contract that this code is supposed to express (usually a hash of
|
* Unparsed reference to the natural language contract that this code is supposed to express (usually a hash of
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package com.r3corda.core.contracts
|
package com.r3corda.core.contracts
|
||||||
|
|
||||||
import com.r3corda.core.contracts.SignedTransaction
|
|
||||||
import com.r3corda.core.contracts.WireTransaction
|
|
||||||
import com.r3corda.core.contracts.*
|
|
||||||
import com.r3corda.core.crypto.DigitalSignature
|
import com.r3corda.core.crypto.DigitalSignature
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
@ -22,8 +19,9 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf(),
|
class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf(),
|
||||||
private val attachments: MutableList<SecureHash> = arrayListOf(),
|
private val attachments: MutableList<SecureHash> = arrayListOf(),
|
||||||
private val outputs: MutableList<ContractState> = arrayListOf(),
|
private val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
|
||||||
private val commands: MutableList<Command> = arrayListOf()) {
|
private val commands: MutableList<Command> = arrayListOf(),
|
||||||
|
private val type: TransactionType = TransactionType.Business()) {
|
||||||
|
|
||||||
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
|
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
|
||||||
|
|
||||||
@ -49,8 +47,8 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
|
|||||||
fun withItems(vararg items: Any): TransactionBuilder {
|
fun withItems(vararg items: Any): TransactionBuilder {
|
||||||
for (t in items) {
|
for (t in items) {
|
||||||
when (t) {
|
when (t) {
|
||||||
is StateRef -> addInputState(t)
|
is StateAndRef<*> -> addInputState(t)
|
||||||
is ContractState -> addOutputState(t)
|
is TransactionState<*> -> addOutputState(t)
|
||||||
is Command -> addCommand(t)
|
is Command -> addCommand(t)
|
||||||
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
|
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
|
||||||
}
|
}
|
||||||
@ -96,7 +94,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
|
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
|
||||||
ArrayList(outputs), ArrayList(commands))
|
ArrayList(outputs), ArrayList(commands), type)
|
||||||
|
|
||||||
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
|
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
|
||||||
if (checkSufficientSignatures) {
|
if (checkSufficientSignatures) {
|
||||||
@ -109,9 +107,15 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
|
|||||||
return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
|
return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addInputState(ref: StateRef) {
|
fun addInputState(stateAndRef: StateAndRef<*>) {
|
||||||
check(currentSigs.isEmpty())
|
check(currentSigs.isEmpty())
|
||||||
inputs.add(ref)
|
|
||||||
|
val notaryKey = stateAndRef.state.notary.owningKey
|
||||||
|
if (commands.none { it.signers.contains(notaryKey) }) {
|
||||||
|
commands.add(Command(NotaryCommand(), notaryKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.add(stateAndRef.ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addAttachment(attachment: Attachment) {
|
fun addAttachment(attachment: Attachment) {
|
||||||
@ -119,7 +123,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
|
|||||||
attachments.add(attachment.id)
|
attachments.add(attachment.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOutputState(state: ContractState) {
|
fun addOutputState(state: TransactionState<*>) {
|
||||||
check(currentSigs.isEmpty())
|
check(currentSigs.isEmpty())
|
||||||
outputs.add(state)
|
outputs.add(state)
|
||||||
}
|
}
|
||||||
@ -136,7 +140,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
|
|||||||
// Accessors that yield immutable snapshots.
|
// Accessors that yield immutable snapshots.
|
||||||
fun inputStates(): List<StateRef> = ArrayList(inputs)
|
fun inputStates(): List<StateRef> = ArrayList(inputs)
|
||||||
|
|
||||||
fun outputStates(): List<ContractState> = ArrayList(outputs)
|
fun outputStates(): List<TransactionState<*>> = ArrayList(outputs)
|
||||||
fun commands(): List<Command> = ArrayList(commands)
|
fun commands(): List<Command> = ArrayList(commands)
|
||||||
fun attachments(): List<SecureHash> = ArrayList(attachments)
|
fun attachments(): List<SecureHash> = ArrayList(attachments)
|
||||||
}
|
}
|
@ -1,9 +1,5 @@
|
|||||||
package com.r3corda.core.contracts
|
package com.r3corda.core.contracts
|
||||||
|
|
||||||
import com.r3corda.core.contracts.AuthenticatedObject
|
|
||||||
import com.r3corda.core.contracts.LedgerTransaction
|
|
||||||
import com.r3corda.core.contracts.SignedTransaction
|
|
||||||
import com.r3corda.core.contracts.WireTransaction
|
|
||||||
import com.r3corda.core.node.services.AttachmentStorage
|
import com.r3corda.core.node.services.AttachmentStorage
|
||||||
import com.r3corda.core.node.services.IdentityService
|
import com.r3corda.core.node.services.IdentityService
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
@ -22,7 +18,7 @@ fun WireTransaction.toLedgerTransaction(identityService: IdentityService,
|
|||||||
val attachments = attachments.map {
|
val attachments = attachments.map {
|
||||||
attachmentStorage.openAttachment(it) ?: throw FileNotFoundException(it.toString())
|
attachmentStorage.openAttachment(it) ?: throw FileNotFoundException(it.toString())
|
||||||
}
|
}
|
||||||
return LedgerTransaction(inputs, attachments, outputs, authenticatedArgs, id)
|
return LedgerTransaction(inputs, attachments, outputs, authenticatedArgs, id, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
val resolved = HashSet<TransactionForVerification>(transactions.size)
|
||||||
for (tx in transactions) {
|
for (tx in transactions) {
|
||||||
val inputs = ArrayList<ContractState>(tx.inputs.size)
|
val inputs = ArrayList<TransactionState<ContractState>>(tx.inputs.size)
|
||||||
for (ref in tx.inputs) {
|
for (ref in tx.inputs) {
|
||||||
val conflict = refToConsumingTXMap[ref]
|
val conflict = refToConsumingTXMap[ref]
|
||||||
if (conflict != null)
|
if (conflict != null)
|
||||||
@ -41,31 +41,27 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
|
|||||||
// Look up the output in that transaction by index.
|
// Look up the output in that transaction by index.
|
||||||
inputs.add(ltx.outputs[ref.index])
|
inputs.add(ltx.outputs[ref.index])
|
||||||
}
|
}
|
||||||
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id))
|
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id, tx.type))
|
||||||
}
|
}
|
||||||
|
|
||||||
for (tx in resolved)
|
for (tx in resolved)
|
||||||
tx.verify()
|
tx.verify()
|
||||||
return resolved
|
return resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */
|
/** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */
|
||||||
data class TransactionForVerification(val inStates: List<ContractState>,
|
data class TransactionForVerification(val inStates: List<TransactionState<ContractState>>,
|
||||||
val outStates: List<ContractState>,
|
val outStates: List<TransactionState<ContractState>>,
|
||||||
val attachments: List<Attachment>,
|
val attachments: List<Attachment>,
|
||||||
val commands: List<AuthenticatedObject<CommandData>>,
|
val commands: List<AuthenticatedObject<CommandData>>,
|
||||||
val origHash: SecureHash) {
|
val origHash: SecureHash,
|
||||||
|
val type: TransactionType) {
|
||||||
override fun hashCode() = origHash.hashCode()
|
override fun hashCode() = origHash.hashCode()
|
||||||
override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash
|
override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that the transaction is valid:
|
* Verifies that the transaction is valid by running type-specific validation logic.
|
||||||
* - Checks that the input states and the timestamp point to the same Notary
|
|
||||||
* - Runs the contracts for this transaction. If any contract fails to verify, the whole transaction is
|
|
||||||
* considered to be invalid. In case of a special type of transaction, e.g. for changing notary for a state,
|
|
||||||
* runs custom platform level validation logic instead.
|
|
||||||
*
|
*
|
||||||
* TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated.
|
* TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated.
|
||||||
*
|
*
|
||||||
@ -73,68 +69,22 @@ data class TransactionForVerification(val inStates: List<ContractState>,
|
|||||||
* (the original is in the cause field)
|
* (the original is in the cause field)
|
||||||
*/
|
*/
|
||||||
@Throws(TransactionVerificationException::class)
|
@Throws(TransactionVerificationException::class)
|
||||||
fun verify() {
|
fun verify() = type.verify(this)
|
||||||
verifySingleNotary()
|
|
||||||
|
|
||||||
if (isChangeNotaryTx()) verifyNotaryChange() else runContractVerify()
|
fun toTransactionForContract() = TransactionForContract(inStates.map { it.data }, outStates.map { it.data }, attachments, commands, origHash)
|
||||||
}
|
|
||||||
|
|
||||||
private fun verifySingleNotary() {
|
|
||||||
if (inStates.isEmpty()) return
|
|
||||||
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 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>,
|
||||||
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
|
val attachments: List<Attachment>,
|
||||||
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
|
val commands: List<AuthenticatedObject<CommandData>>,
|
||||||
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
|
val origHash: SecureHash) {
|
||||||
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
|
override fun hashCode() = origHash.hashCode()
|
||||||
* be used to simplify this logic.
|
override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
|
||||||
*/
|
|
||||||
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
|
|
||||||
|
|
||||||
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
|
|
||||||
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
|
|
||||||
|
|
||||||
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
|
|
||||||
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
|
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
|
||||||
@ -186,6 +136,24 @@ data class TransactionForVerification(val inStates: List<ContractState>,
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Utilities for contract writers to incorporate into their logic. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
|
||||||
|
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
|
||||||
|
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
|
||||||
|
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
|
||||||
|
* be used to simplify this logic.
|
||||||
|
*/
|
||||||
|
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
|
||||||
|
|
||||||
|
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
|
||||||
|
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
|
||||||
|
|
||||||
|
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
|
||||||
|
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransactionResolutionException(val hash: SecureHash) : Exception()
|
class TransactionResolutionException(val hash: SecureHash) : Exception()
|
||||||
@ -194,5 +162,6 @@ class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTra
|
|||||||
sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) {
|
sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) {
|
||||||
class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
|
class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
|
||||||
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
||||||
|
class NotaryMissing(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
||||||
class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
|
||||||
}
|
}
|
@ -43,8 +43,9 @@ import java.security.SignatureException
|
|||||||
/** Transaction ready for serialisation, without any signatures attached. */
|
/** Transaction ready for serialisation, without any signatures attached. */
|
||||||
data class WireTransaction(val inputs: List<StateRef>,
|
data class WireTransaction(val inputs: List<StateRef>,
|
||||||
val attachments: List<SecureHash>,
|
val attachments: List<SecureHash>,
|
||||||
val outputs: List<ContractState>,
|
val outputs: List<TransactionState<ContractState>>,
|
||||||
val commands: List<Command>) : NamedByHash {
|
val commands: List<Command>,
|
||||||
|
val type: TransactionType) : NamedByHash {
|
||||||
|
|
||||||
// Cache the serialised form of the transaction and its hash to give us fast access to it.
|
// Cache the serialised form of the transaction and its hash to give us fast access to it.
|
||||||
@Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null
|
@Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null
|
||||||
@ -63,11 +64,11 @@ data class WireTransaction(val inputs: List<StateRef>,
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : ContractState> outRef(index: Int): StateAndRef<T> {
|
fun <T : ContractState> outRef(index: Int): StateAndRef<T> {
|
||||||
require(index >= 0 && index < outputs.size)
|
require(index >= 0 && index < outputs.size)
|
||||||
return StateAndRef(outputs[index] as T, StateRef(id, index))
|
return StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */
|
/** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */
|
||||||
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.indexOfOrThrow(state))
|
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.map { it.data }.indexOfOrThrow(state))
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val buf = StringBuilder()
|
val buf = StringBuilder()
|
||||||
@ -130,7 +131,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
|||||||
return copy(sigs = sigs + sig)
|
return copy(sigs = sigs + sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun withAdditionalSignatures(sigList: Collection<DigitalSignature.WithKey>): SignedTransaction {
|
fun withAdditionalSignatures(sigList: Iterable<DigitalSignature.WithKey>): SignedTransaction {
|
||||||
return copy(sigs = sigs + sigList)
|
return copy(sigs = sigs + sigList)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,12 +166,13 @@ data class LedgerTransaction(
|
|||||||
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
||||||
val attachments: List<Attachment>,
|
val attachments: List<Attachment>,
|
||||||
/** The states that will be generated by the execution of this transaction. */
|
/** The states that will be generated by the execution of this transaction. */
|
||||||
val outputs: List<ContractState>,
|
val outputs: List<TransactionState<*>>,
|
||||||
/** Arbitrary data passed to the program of each input state. */
|
/** Arbitrary data passed to the program of each input state. */
|
||||||
val commands: List<AuthenticatedObject<CommandData>>,
|
val commands: List<AuthenticatedObject<CommandData>>,
|
||||||
/** The hash of the original serialised WireTransaction */
|
/** The hash of the original serialised WireTransaction */
|
||||||
override val id: SecureHash
|
override val id: SecureHash,
|
||||||
|
val type: TransactionType
|
||||||
) : NamedByHash {
|
) : NamedByHash {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as T, StateRef(id, index))
|
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
|
||||||
}
|
}
|
@ -53,7 +53,7 @@ interface ServiceHub {
|
|||||||
*
|
*
|
||||||
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction
|
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction
|
||||||
*/
|
*/
|
||||||
fun loadState(stateRef: StateRef): ContractState {
|
fun loadState(stateRef: StateRef): TransactionState<*> {
|
||||||
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||||
return definingTx.tx.outputs[stateRef.index]
|
return definingTx.tx.outputs[stateRef.index]
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ abstract class Wallet {
|
|||||||
abstract val states: List<StateAndRef<ContractState>>
|
abstract val states: List<StateAndRef<ContractState>>
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state is T } as List<StateAndRef<T>>
|
inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
|
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
|
||||||
@ -99,10 +99,10 @@ interface WalletService {
|
|||||||
/** Returns the [linearHeads] only when the type of the state would be considered an 'instanceof' the given type. */
|
/** Returns the [linearHeads] only when the type of the state would be considered an 'instanceof' the given type. */
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<SecureHash, StateAndRef<T>> {
|
fun <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<SecureHash, StateAndRef<T>> {
|
||||||
return linearHeads.filterValues { stateType.isInstance(it.state) }.mapValues { StateAndRef(it.value.state as T, it.value.ref) }
|
return linearHeads.filterValues { stateType.isInstance(it.state.data) }.mapValues { StateAndRef(it.value.state as TransactionState<T>, it.value.ref) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun statesForRefs(refs: List<StateRef>): Map<StateRef, ContractState?> {
|
fun statesForRefs(refs: List<StateRef>): Map<StateRef, TransactionState<*>?> {
|
||||||
val refsToStates = currentWallet.states.associateBy { it.ref }
|
val refsToStates = currentWallet.states.associateBy { it.ref }
|
||||||
return refs.associateBy({ it }, { refsToStates[it]?.state })
|
return refs.associateBy({ it }, { refsToStates[it]?.state })
|
||||||
}
|
}
|
||||||
|
@ -232,6 +232,7 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
|||||||
kryo.writeClassAndObject(output, obj.attachments)
|
kryo.writeClassAndObject(output, obj.attachments)
|
||||||
kryo.writeClassAndObject(output, obj.outputs)
|
kryo.writeClassAndObject(output, obj.outputs)
|
||||||
kryo.writeClassAndObject(output, obj.commands)
|
kryo.writeClassAndObject(output, obj.commands)
|
||||||
|
kryo.writeClassAndObject(output, obj.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@ -258,10 +259,11 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
|||||||
} else javaClass.classLoader
|
} else javaClass.classLoader
|
||||||
|
|
||||||
kryo.useClassLoader(classLoader) {
|
kryo.useClassLoader(classLoader) {
|
||||||
val outputs = kryo.readClassAndObject(input) as List<ContractState>
|
val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>>
|
||||||
val commands = kryo.readClassAndObject(input) as List<Command>
|
val commands = kryo.readClassAndObject(input) as List<Command>
|
||||||
|
val transactionType = kryo.readClassAndObject(input) as TransactionType
|
||||||
|
|
||||||
return WireTransaction(inputs, attachmentHashes, outputs, commands)
|
return WireTransaction(inputs, attachmentHashes, outputs, commands, transactionType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,6 +345,7 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
|
|||||||
|
|
||||||
// Work around a bug in Kryo handling nested generics
|
// Work around a bug in Kryo handling nested generics
|
||||||
register(Issued::class.java, ImmutableClassSerializer(Issued::class))
|
register(Issued::class.java, ImmutableClassSerializer(Issued::class))
|
||||||
|
register(TransactionState::class.java, ImmutableClassSerializer(TransactionState::class))
|
||||||
|
|
||||||
noReferencesWithin<WireTransaction>()
|
noReferencesWithin<WireTransaction>()
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,12 @@ package com.r3corda.core.testing
|
|||||||
|
|
||||||
import com.google.common.base.Throwables
|
import com.google.common.base.Throwables
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import com.r3corda.core.*
|
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.*
|
import com.r3corda.core.crypto.*
|
||||||
import com.r3corda.core.serialization.serialize
|
|
||||||
import com.r3corda.core.node.services.testing.MockIdentityService
|
import com.r3corda.core.node.services.testing.MockIdentityService
|
||||||
import com.r3corda.core.node.services.testing.MockStorageService
|
import com.r3corda.core.node.services.testing.MockStorageService
|
||||||
|
import com.r3corda.core.seconds
|
||||||
|
import com.r3corda.core.serialization.serialize
|
||||||
import java.net.ServerSocket
|
import java.net.ServerSocket
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -96,20 +96,25 @@ fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
|
|||||||
//
|
//
|
||||||
// TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block
|
// TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block
|
||||||
|
|
||||||
class LabeledOutput(val label: String?, val state: ContractState) {
|
class LabeledOutput(val label: String?, val state: TransactionState<*>) {
|
||||||
override fun toString() = state.toString() + (if (label != null) " ($label)" else "")
|
override fun toString() = state.toString() + (if (label != null) " ($label)" else "")
|
||||||
override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state)
|
override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state)
|
||||||
override fun hashCode(): Int = state.hashCode()
|
override fun hashCode(): Int = state.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
infix fun ContractState.label(label: String) = LabeledOutput(label, this)
|
infix fun TransactionState<*>.label(label: String) = LabeledOutput(label, this)
|
||||||
|
|
||||||
abstract class AbstractTransactionForTest {
|
abstract class AbstractTransactionForTest {
|
||||||
protected val attachments = ArrayList<SecureHash>()
|
protected val attachments = ArrayList<SecureHash>()
|
||||||
protected val outStates = ArrayList<LabeledOutput>()
|
protected val outStates = ArrayList<LabeledOutput>()
|
||||||
protected val commands = ArrayList<Command>()
|
protected val commands = ArrayList<Command>()
|
||||||
|
protected val type = TransactionType.Business()
|
||||||
|
|
||||||
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, s()).apply { outStates.add(this) }
|
init {
|
||||||
|
arg(DUMMY_NOTARY.owningKey) { NotaryCommand() }
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, TransactionState(s(), DUMMY_NOTARY)).apply { outStates.add(this) }
|
||||||
|
|
||||||
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
|
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
|
||||||
return commands.map { AuthenticatedObject(it.signers, it.signers.mapNotNull { MOCK_IDENTITY_SERVICE.partyFromKey(it) }, it.value) }
|
return commands.map { AuthenticatedObject(it.signers, it.signers.mapNotNull { MOCK_IDENTITY_SERVICE.partyFromKey(it) }, it.value) }
|
||||||
@ -150,12 +155,12 @@ sealed class LastLineShouldTestForAcceptOrFailure {
|
|||||||
|
|
||||||
// Corresponds to the args to Contract.verify
|
// Corresponds to the args to Contract.verify
|
||||||
open class TransactionForTest : AbstractTransactionForTest() {
|
open class TransactionForTest : AbstractTransactionForTest() {
|
||||||
private val inStates = arrayListOf<ContractState>()
|
private val inStates = arrayListOf<TransactionState<ContractState>>()
|
||||||
fun input(s: () -> ContractState) = inStates.add(s())
|
fun input(s: () -> ContractState) = inStates.add(TransactionState(s(), DUMMY_NOTARY))
|
||||||
|
|
||||||
protected fun runCommandsAndVerify(time: Instant) {
|
protected fun runCommandsAndVerify(time: Instant) {
|
||||||
val cmds = commandsToAuthenticatedObjects()
|
val cmds = commandsToAuthenticatedObjects()
|
||||||
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256())
|
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256(), type)
|
||||||
tx.verify()
|
tx.verify()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,16 +245,24 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
|
|||||||
private val inStates = ArrayList<StateRef>()
|
private val inStates = ArrayList<StateRef>()
|
||||||
|
|
||||||
fun input(label: String) {
|
fun input(label: String) {
|
||||||
|
val notaryKey = label.output.notary.owningKey
|
||||||
|
if (commands.none { it.signers.contains(notaryKey) }) commands.add(Command(NotaryCommand(), notaryKey))
|
||||||
inStates.add(label.outputRef)
|
inStates.add(label.outputRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toWireTransaction() = WireTransaction(inStates, attachments, outStates.map { it.state }, commands)
|
fun toWireTransaction() = WireTransaction(inStates, attachments, outStates.map { it.state }, commands, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
val String.output: T get() = labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found")
|
val String.output: TransactionState<T>
|
||||||
|
get() =
|
||||||
|
labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found")
|
||||||
val String.outputRef: StateRef get() = labelToRefs[this] ?: throw IllegalArgumentException("Unknown label \"$this\"")
|
val String.outputRef: StateRef get() = labelToRefs[this] ?: throw IllegalArgumentException("Unknown label \"$this\"")
|
||||||
|
|
||||||
fun <C : ContractState> lookup(label: String) = StateAndRef(label.output as C, label.outputRef)
|
fun <C : ContractState> lookup(label: String): StateAndRef<C> {
|
||||||
|
val output = label.output
|
||||||
|
val newOutput = TransactionState(output.data as C, output.notary)
|
||||||
|
return StateAndRef(newOutput, label.outputRef)
|
||||||
|
}
|
||||||
|
|
||||||
private inner class InternalWireTransactionDSL : WireTransactionDSL() {
|
private inner class InternalWireTransactionDSL : WireTransactionDSL() {
|
||||||
fun finaliseAndInsertLabels(): WireTransaction {
|
fun finaliseAndInsertLabels(): WireTransaction {
|
||||||
@ -257,8 +270,8 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
|
|||||||
for ((index, labelledState) in outStates.withIndex()) {
|
for ((index, labelledState) in outStates.withIndex()) {
|
||||||
if (labelledState.label != null) {
|
if (labelledState.label != null) {
|
||||||
labelToRefs[labelledState.label] = StateRef(wtx.id, index)
|
labelToRefs[labelledState.label] = StateRef(wtx.id, index)
|
||||||
if (stateType.isInstance(labelledState.state)) {
|
if (stateType.isInstance(labelledState.state.data)) {
|
||||||
labelToOutputs[labelledState.label] = labelledState.state as T
|
labelToOutputs[labelledState.label] = labelledState.state as TransactionState<T>
|
||||||
}
|
}
|
||||||
outputsToLabels[labelledState.state] = labelledState.label
|
outputsToLabels[labelledState.state] = labelledState.label
|
||||||
}
|
}
|
||||||
@ -269,20 +282,20 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
|
|||||||
|
|
||||||
private val rootTxns = ArrayList<WireTransaction>()
|
private val rootTxns = ArrayList<WireTransaction>()
|
||||||
private val labelToRefs = HashMap<String, StateRef>()
|
private val labelToRefs = HashMap<String, StateRef>()
|
||||||
private val labelToOutputs = HashMap<String, T>()
|
private val labelToOutputs = HashMap<String, TransactionState<T>>()
|
||||||
private val outputsToLabels = HashMap<ContractState, String>()
|
private val outputsToLabels = HashMap<TransactionState<*>, String>()
|
||||||
|
|
||||||
fun labelForState(state: T): String? = outputsToLabels[state]
|
fun labelForState(output: TransactionState<*>): String? = outputsToLabels[output]
|
||||||
|
|
||||||
inner class Roots {
|
inner class Roots {
|
||||||
fun transaction(vararg outputStates: LabeledOutput) {
|
fun transaction(vararg outputStates: LabeledOutput) {
|
||||||
val outs = outputStates.map { it.state }
|
val outs = outputStates.map { it.state }
|
||||||
val wtx = WireTransaction(emptyList(), emptyList(), outs, emptyList())
|
val wtx = WireTransaction(emptyList(), emptyList(), outs, emptyList(), TransactionType.Business())
|
||||||
for ((index, state) in outputStates.withIndex()) {
|
for ((index, state) in outputStates.withIndex()) {
|
||||||
val label = state.label!!
|
val label = state.label!!
|
||||||
labelToRefs[label] = StateRef(wtx.id, index)
|
labelToRefs[label] = StateRef(wtx.id, index)
|
||||||
outputsToLabels[state.state] = label
|
outputsToLabels[state.state] = label
|
||||||
labelToOutputs[label] = state.state as T
|
labelToOutputs[label] = state.state as TransactionState<T>
|
||||||
}
|
}
|
||||||
rootTxns.add(wtx)
|
rootTxns.add(wtx)
|
||||||
}
|
}
|
||||||
|
@ -314,7 +314,7 @@ object TwoPartyDealProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun assembleSharedTX(handshake: Handshake<T>): Pair<TransactionBuilder, List<PublicKey>> {
|
override fun assembleSharedTX(handshake: Handshake<T>): Pair<TransactionBuilder, List<PublicKey>> {
|
||||||
val ptx = handshake.payload.generateAgreement()
|
val ptx = handshake.payload.generateAgreement(notary)
|
||||||
|
|
||||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||||
// to have one.
|
// to have one.
|
||||||
@ -336,7 +336,7 @@ object TwoPartyDealProtocol {
|
|||||||
val dealToFix: StateAndRef<T>,
|
val dealToFix: StateAndRef<T>,
|
||||||
sessionID: Long,
|
sessionID: Long,
|
||||||
val replacementProgressTracker: ProgressTracker? = null) : Secondary<StateRef>(otherSide, notary, sessionID) {
|
val replacementProgressTracker: ProgressTracker? = null) : Secondary<StateRef>(otherSide, notary, sessionID) {
|
||||||
private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.nextFixingOf()!!.name)
|
private val ratesFixTracker = RatesFixProtocol.tracker(dealToFix.state.data.nextFixingOf()!!.name)
|
||||||
|
|
||||||
override val progressTracker: ProgressTracker = replacementProgressTracker ?: createTracker()
|
override val progressTracker: ProgressTracker = replacementProgressTracker ?: createTracker()
|
||||||
|
|
||||||
@ -358,11 +358,11 @@ object TwoPartyDealProtocol {
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun assembleSharedTX(handshake: Handshake<StateRef>): Pair<TransactionBuilder, List<PublicKey>> {
|
override fun assembleSharedTX(handshake: Handshake<StateRef>): Pair<TransactionBuilder, List<PublicKey>> {
|
||||||
val fixOf = dealToFix.state.nextFixingOf()!!
|
val fixOf = dealToFix.state.data.nextFixingOf()!!
|
||||||
|
|
||||||
// TODO Do we need/want to substitute in new public keys for the Parties?
|
// TODO Do we need/want to substitute in new public keys for the Parties?
|
||||||
val myName = serviceHub.storageService.myLegalIdentity.name
|
val myName = serviceHub.storageService.myLegalIdentity.name
|
||||||
val deal: T = dealToFix.state
|
val deal: T = dealToFix.state.data
|
||||||
val myOldParty = deal.parties.single { it.name == myName }
|
val myOldParty = deal.parties.single { it.name == myName }
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@ -373,7 +373,7 @@ object TwoPartyDealProtocol {
|
|||||||
val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0], fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
|
val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0], fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun beforeSigning(fix: Fix) {
|
override fun beforeSigning(fix: Fix) {
|
||||||
newDeal.generateFix(ptx, oldRef, fix)
|
newDeal.generateFix(ptx, dealToFix, fix)
|
||||||
|
|
||||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||||
// to have one.
|
// to have one.
|
||||||
|
@ -55,18 +55,13 @@ object NotaryChangeProtocol {
|
|||||||
|
|
||||||
progressTracker.currentStep = SIGNING
|
progressTracker.currentStep = SIGNING
|
||||||
|
|
||||||
val signatures = mutableListOf<DigitalSignature.WithKey>()
|
|
||||||
|
|
||||||
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
|
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
|
||||||
val me = listOf(myKey)
|
val me = listOf(myKey)
|
||||||
|
|
||||||
if (participants == me) {
|
val signatures = if (participants == me) {
|
||||||
signatures.add(getNotarySignature(stx.tx))
|
listOf(getNotarySignature(stx.tx))
|
||||||
} else {
|
} else {
|
||||||
val participantSessions = collectSignatures(participants - me, signatures, stx)
|
collectSignatures(participants - me, stx)
|
||||||
signatures.add(getNotarySignature(stx.tx))
|
|
||||||
|
|
||||||
participantSessions.forEach { send(TOPIC_CHANGE, it.first.address, it.second, signatures) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val finalTx = stx + signatures
|
val finalTx = stx + signatures
|
||||||
@ -77,9 +72,9 @@ object NotaryChangeProtocol {
|
|||||||
private fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
|
private fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
|
||||||
val state = originalState.state
|
val state = originalState.state
|
||||||
val newState = state.withNewNotary(newNotary)
|
val newState = state.withNewNotary(newNotary)
|
||||||
val participants = state.participants
|
val participants = state.data.participants
|
||||||
val cmd = Command(ChangeNotary(), participants)
|
val cmd = Command(ChangeNotary(), participants)
|
||||||
val tx = TransactionBuilder().withItems(originalState.ref, newState, cmd)
|
val tx = TransactionBuilder(type = TransactionType.NotaryChange()).withItems(originalState, newState, cmd)
|
||||||
tx.signWith(serviceHub.storageService.myLegalIdentityKey)
|
tx.signWith(serviceHub.storageService.myLegalIdentityKey)
|
||||||
|
|
||||||
val stx = tx.toSignedTransaction(false)
|
val stx = tx.toSignedTransaction(false)
|
||||||
@ -87,21 +82,22 @@ object NotaryChangeProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun collectSignatures(participants: List<PublicKey>, signatures: MutableCollection<DigitalSignature.WithKey>,
|
private fun collectSignatures(participants: List<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||||
stx: SignedTransaction): MutableList<Pair<NodeInfo, Long>> {
|
val sessions = mutableMapOf<NodeInfo, Long>()
|
||||||
val participantSessions = mutableListOf<Pair<NodeInfo, Long>>()
|
|
||||||
|
|
||||||
participants.forEach {
|
val participantSignatures = participants.map {
|
||||||
val participantNode = serviceHub.networkMapCache.getNodeByPublicKey(it) ?:
|
val participantNode = serviceHub.networkMapCache.getNodeByPublicKey(it) ?:
|
||||||
throw IllegalStateException("Participant $it to state $originalState not found on the network")
|
throw IllegalStateException("Participant $it to state $originalState not found on the network")
|
||||||
val sessionIdForSend = random63BitValue()
|
val sessionIdForSend = random63BitValue()
|
||||||
val participantSignature = getParticipantSignature(participantNode, stx, sessionIdForSend)
|
sessions[participantNode] = sessionIdForSend
|
||||||
signatures.add(participantSignature)
|
|
||||||
|
|
||||||
participantSessions.add(participantNode to sessionIdForSend)
|
getParticipantSignature(participantNode, stx, sessionIdForSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
return participantSessions
|
val allSignatures = participantSignatures + getNotarySignature(stx.tx)
|
||||||
|
sessions.forEach { send(TOPIC_CHANGE, it.key.address, it.value, allSignatures) }
|
||||||
|
|
||||||
|
return allSignatures
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -125,7 +121,7 @@ object NotaryChangeProtocol {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
private fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
|
private fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
|
||||||
progressTracker.currentStep = NOTARY
|
progressTracker.currentStep = NOTARY
|
||||||
return subProtocol(NotaryProtocol(wtx))
|
return subProtocol(NotaryProtocol.Client(wtx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,19 +149,20 @@ object NotaryChangeProtocol {
|
|||||||
val mySignature = sign(proposedTx)
|
val mySignature = sign(proposedTx)
|
||||||
val swapSignatures = sendAndReceive<List<DigitalSignature.WithKey>>(TOPIC_CHANGE, otherSide, sessionIdForSend, sessionIdForReceive, mySignature)
|
val swapSignatures = sendAndReceive<List<DigitalSignature.WithKey>>(TOPIC_CHANGE, otherSide, sessionIdForSend, sessionIdForReceive, mySignature)
|
||||||
|
|
||||||
val allSignatures = swapSignatures.validate {
|
val allSignatures = swapSignatures.validate { signatures ->
|
||||||
it.forEach { it.verifyWithECDSA(proposedTx.txBits) }
|
signatures.forEach { it.verifyWithECDSA(proposedTx.txBits) }
|
||||||
it
|
signatures
|
||||||
}
|
}
|
||||||
|
|
||||||
val finalTx = proposedTx + allSignatures
|
val finalTx = proposedTx + allSignatures
|
||||||
|
finalTx.verify()
|
||||||
serviceHub.recordTransactions(listOf(finalTx))
|
serviceHub.recordTransactions(listOf(finalTx))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun validateTx(stx: SignedTransaction): SignedTransaction {
|
private fun validateTx(stx: SignedTransaction): SignedTransaction {
|
||||||
checkDependenciesValid(stx)
|
checkDependenciesValid(stx)
|
||||||
checkContractValid(stx)
|
checkValid(stx)
|
||||||
checkCommand(stx.tx)
|
checkCommand(stx.tx)
|
||||||
return stx
|
return stx
|
||||||
}
|
}
|
||||||
@ -184,7 +181,7 @@ object NotaryChangeProtocol {
|
|||||||
subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide))
|
subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkContractValid(stx: SignedTransaction) {
|
private fun checkValid(stx: SignedTransaction) {
|
||||||
val ltx = stx.tx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
|
val ltx = stx.tx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
|
||||||
serviceHub.verifyTransaction(ltx)
|
serviceHub.verifyTransaction(ltx)
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -15,24 +15,22 @@ import kotlin.test.assertNotEquals
|
|||||||
val TEST_PROGRAM_ID = TransactionGroupTests.TestCash()
|
val TEST_PROGRAM_ID = TransactionGroupTests.TestCash()
|
||||||
|
|
||||||
class TransactionGroupTests {
|
class TransactionGroupTests {
|
||||||
val A_THOUSAND_POUNDS = TestCash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
|
val A_THOUSAND_POUNDS = TestCash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY)
|
||||||
|
|
||||||
class TestCash : Contract {
|
class TestCash : Contract {
|
||||||
override val legalContractReference = SecureHash.sha256("TestCash")
|
override val legalContractReference = SecureHash.sha256("TestCash")
|
||||||
|
|
||||||
override fun verify(tx: TransactionForVerification) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data class State(
|
data class State(
|
||||||
val deposit: PartyAndReference,
|
val deposit: PartyAndReference,
|
||||||
val amount: Amount<Currency>,
|
val amount: Amount<Currency>,
|
||||||
override val owner: PublicKey,
|
override val owner: PublicKey) : OwnableState {
|
||||||
override val notary: Party) : OwnableState {
|
|
||||||
override val contract: Contract = TEST_PROGRAM_ID
|
override val contract: Contract = TEST_PROGRAM_ID
|
||||||
override val participants: List<PublicKey>
|
override val participants: List<PublicKey>
|
||||||
get() = listOf(owner)
|
get() = listOf(owner)
|
||||||
|
|
||||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
|
||||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,12 +42,13 @@ class TransactionGroupTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
infix fun TestCash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
|
infix fun TestCash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
|
||||||
|
infix fun TestCash.State.`with notary`(notary: Party) = TransactionState(this, notary)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun success() {
|
fun success() {
|
||||||
transactionGroup {
|
transactionGroup {
|
||||||
roots {
|
roots {
|
||||||
transaction(A_THOUSAND_POUNDS label "£1000")
|
transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000")
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
@ -122,17 +121,17 @@ class TransactionGroupTests {
|
|||||||
|
|
||||||
// We have to do this manually without the DSL because transactionGroup { } won't let us create a tx that
|
// We have to do this manually without the DSL because transactionGroup { } won't let us create a tx that
|
||||||
// points nowhere.
|
// points nowhere.
|
||||||
val input = generateStateRef()
|
val input = StateAndRef(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY, generateStateRef())
|
||||||
tg.txns += TransactionBuilder().apply {
|
tg.txns += TransactionBuilder().apply {
|
||||||
addInputState(input)
|
addInputState(input)
|
||||||
addOutputState(A_THOUSAND_POUNDS)
|
addOutputState(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY)
|
||||||
addCommand(TestCash.Commands.Move(), BOB_PUBKEY)
|
addCommand(TestCash.Commands.Move(), BOB_PUBKEY)
|
||||||
}.toWireTransaction()
|
}.toWireTransaction()
|
||||||
|
|
||||||
val e = assertFailsWith(TransactionResolutionException::class) {
|
val e = assertFailsWith(TransactionResolutionException::class) {
|
||||||
tg.verify()
|
tg.verify()
|
||||||
}
|
}
|
||||||
assertEquals(e.hash, input.txhash)
|
assertEquals(e.hash, input.ref.txhash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -140,7 +139,7 @@ class TransactionGroupTests {
|
|||||||
// Check that a transaction cannot refer to the same input more than once.
|
// Check that a transaction cannot refer to the same input more than once.
|
||||||
transactionGroup {
|
transactionGroup {
|
||||||
roots {
|
roots {
|
||||||
transaction(A_THOUSAND_POUNDS label "£1000")
|
transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000")
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
|
@ -33,20 +33,17 @@ class AttachmentClassLoaderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentDummyContract : Contract {
|
class AttachmentDummyContract : Contract {
|
||||||
data class State(val magicNumber: Int = 0,
|
data class State(val magicNumber: Int = 0) : ContractState {
|
||||||
override val notary: Party) : ContractState {
|
|
||||||
override val contract = ATTACHMENT_TEST_PROGRAM_ID
|
override val contract = ATTACHMENT_TEST_PROGRAM_ID
|
||||||
override val participants: List<PublicKey>
|
override val participants: List<PublicKey>
|
||||||
get() = listOf()
|
get() = listOf()
|
||||||
|
|
||||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Commands : CommandData {
|
interface Commands : CommandData {
|
||||||
class Create : TypeOnlyCommandData(), Commands
|
class Create : TypeOnlyCommandData(), Commands
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verify(tx: TransactionForVerification) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
// Always accepts.
|
// Always accepts.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +51,7 @@ class AttachmentClassLoaderTests {
|
|||||||
override val legalContractReference: SecureHash = SecureHash.sha256("")
|
override val legalContractReference: SecureHash = SecureHash.sha256("")
|
||||||
|
|
||||||
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
|
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
|
||||||
val state = State(magicNumber, notary)
|
val state = TransactionState(State(magicNumber), notary)
|
||||||
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,7 +217,7 @@ class AttachmentClassLoaderTests {
|
|||||||
val copiedWireTransaction = bytes.deserialize()
|
val copiedWireTransaction = bytes.deserialize()
|
||||||
|
|
||||||
assertEquals(1, copiedWireTransaction.outputs.size)
|
assertEquals(1, copiedWireTransaction.outputs.size)
|
||||||
assertEquals(42, (copiedWireTransaction.outputs[0] as AttachmentDummyContract.State).magicNumber)
|
assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -250,8 +247,8 @@ class AttachmentClassLoaderTests {
|
|||||||
val copiedWireTransaction = bytes.deserialize(kryo2)
|
val copiedWireTransaction = bytes.deserialize(kryo2)
|
||||||
|
|
||||||
assertEquals(1, copiedWireTransaction.outputs.size)
|
assertEquals(1, copiedWireTransaction.outputs.size)
|
||||||
val contract2 = copiedWireTransaction.outputs[0].contract as DummyContractBackdoor
|
val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor
|
||||||
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0]))
|
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.r3corda.core.serialization
|
package com.r3corda.core.serialization
|
||||||
|
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.node.services.testing.MockStorageService
|
import com.r3corda.core.node.services.testing.MockStorageService
|
||||||
import com.r3corda.core.seconds
|
import com.r3corda.core.seconds
|
||||||
@ -21,20 +20,18 @@ class TransactionSerializationTests {
|
|||||||
class TestCash : Contract {
|
class TestCash : Contract {
|
||||||
override val legalContractReference = SecureHash.sha256("TestCash")
|
override val legalContractReference = SecureHash.sha256("TestCash")
|
||||||
|
|
||||||
override fun verify(tx: TransactionForVerification) {
|
override fun verify(tx: TransactionForContract) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data class State(
|
data class State(
|
||||||
val deposit: PartyAndReference,
|
val deposit: PartyAndReference,
|
||||||
val amount: Amount<Currency>,
|
val amount: Amount<Currency>,
|
||||||
override val owner: PublicKey,
|
override val owner: PublicKey) : OwnableState {
|
||||||
override val notary: Party) : OwnableState {
|
|
||||||
override val contract: Contract = TEST_PROGRAM_ID
|
override val contract: Contract = TEST_PROGRAM_ID
|
||||||
override val participants: List<PublicKey>
|
override val participants: List<PublicKey>
|
||||||
get() = listOf(owner)
|
get() = listOf(owner)
|
||||||
|
|
||||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||||
override fun withNewNotary(newNotary: Party) = copy(notary = newNotary)
|
|
||||||
}
|
}
|
||||||
interface Commands : CommandData {
|
interface Commands : CommandData {
|
||||||
class Move() : TypeOnlyCommandData(), Commands
|
class Move() : TypeOnlyCommandData(), Commands
|
||||||
@ -46,21 +43,24 @@ class TransactionSerializationTests {
|
|||||||
// Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change).
|
// Simple TX that takes 1000 pounds from me and sends 600 to someone else (with 400 change).
|
||||||
// It refers to a fake TX/state that we don't bother creating here.
|
// It refers to a fake TX/state that we don't bother creating here.
|
||||||
val depositRef = MINI_CORP.ref(1)
|
val depositRef = MINI_CORP.ref(1)
|
||||||
val outputState = TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY)
|
|
||||||
val changeState = TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY)
|
|
||||||
|
|
||||||
val fakeStateRef = generateStateRef()
|
val fakeStateRef = generateStateRef()
|
||||||
|
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
|
||||||
|
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY)
|
||||||
|
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public), DUMMY_NOTARY)
|
||||||
|
|
||||||
|
|
||||||
lateinit var tx: TransactionBuilder
|
lateinit var tx: TransactionBuilder
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
tx = TransactionBuilder().withItems(
|
tx = TransactionBuilder().withItems(
|
||||||
fakeStateRef, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
|
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun signWireTX() {
|
fun signWireTX() {
|
||||||
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
tx.signWith(TestUtils.keypair)
|
tx.signWith(TestUtils.keypair)
|
||||||
val signedTX = tx.toSignedTransaction()
|
val signedTX = tx.toSignedTransaction()
|
||||||
|
|
||||||
@ -82,6 +82,7 @@ class TransactionSerializationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tx.signWith(TestUtils.keypair)
|
tx.signWith(TestUtils.keypair)
|
||||||
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
val signedTX = tx.toSignedTransaction()
|
val signedTX = tx.toSignedTransaction()
|
||||||
|
|
||||||
// Cannot construct with an empty sigs list.
|
// Cannot construct with an empty sigs list.
|
||||||
@ -91,8 +92,9 @@ class TransactionSerializationTests {
|
|||||||
|
|
||||||
// If the signature was replaced in transit, we don't like it.
|
// If the signature was replaced in transit, we don't like it.
|
||||||
assertFailsWith(SignatureException::class) {
|
assertFailsWith(SignatureException::class) {
|
||||||
val tx2 = TransactionBuilder().withItems(fakeStateRef, outputState, changeState,
|
val tx2 = TransactionBuilder().withItems(inputState, outputState, changeState,
|
||||||
Command(TestCash.Commands.Move(), TestUtils.keypair2.public))
|
Command(TestCash.Commands.Move(), TestUtils.keypair2.public))
|
||||||
|
tx2.signWith(DUMMY_NOTARY_KEY)
|
||||||
tx2.signWith(TestUtils.keypair2)
|
tx2.signWith(TestUtils.keypair2)
|
||||||
|
|
||||||
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
|
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package com.r3corda.node.api
|
package com.r3corda.node.api
|
||||||
|
|
||||||
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.node.api.StatesQuery
|
import com.r3corda.node.api.StatesQuery
|
||||||
import com.r3corda.core.contracts.ContractState
|
|
||||||
import com.r3corda.core.contracts.SignedTransaction
|
|
||||||
import com.r3corda.core.contracts.StateRef
|
|
||||||
import com.r3corda.core.contracts.WireTransaction
|
|
||||||
import com.r3corda.core.crypto.DigitalSignature
|
import com.r3corda.core.crypto.DigitalSignature
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.serialization.SerializedBytes
|
import com.r3corda.core.serialization.SerializedBytes
|
||||||
@ -43,7 +40,7 @@ interface APIServer {
|
|||||||
*/
|
*/
|
||||||
fun queryStates(query: StatesQuery): List<StateRef>
|
fun queryStates(query: StatesQuery): List<StateRef>
|
||||||
|
|
||||||
fun fetchStates(states: List<StateRef>): Map<StateRef, ContractState?>
|
fun fetchStates(states: List<StateRef>): Map<StateRef, TransactionState<ContractState>?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query for immutable transactions (results can be cached indefinitely by their id/hash).
|
* Query for immutable transactions (results can be cached indefinitely by their id/hash).
|
||||||
|
@ -27,7 +27,7 @@ class APIServerImpl(val node: AbstractNode) : APIServer {
|
|||||||
return states.values.map { it.ref }
|
return states.values.map { it.ref }
|
||||||
} else if (query.criteria is StatesQuery.Criteria.Deal) {
|
} else if (query.criteria is StatesQuery.Criteria.Deal) {
|
||||||
val states = node.services.walletService.linearHeadsOfType<DealState>().filterValues {
|
val states = node.services.walletService.linearHeadsOfType<DealState>().filterValues {
|
||||||
it.state.ref == query.criteria.ref
|
it.state.data.ref == query.criteria.ref
|
||||||
}
|
}
|
||||||
return states.values.map { it.ref }
|
return states.values.map { it.ref }
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ class APIServerImpl(val node: AbstractNode) : APIServer {
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchStates(states: List<StateRef>): Map<StateRef, ContractState?> {
|
override fun fetchStates(states: List<StateRef>): Map<StateRef, TransactionState<ContractState>?> {
|
||||||
return node.services.walletService.statesForRefs(states)
|
return node.services.walletService.statesForRefs(states)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
|||||||
lateinit var smm: StateMachineManager
|
lateinit var smm: StateMachineManager
|
||||||
lateinit var wallet: WalletService
|
lateinit var wallet: WalletService
|
||||||
lateinit var keyManagement: E2ETestKeyManagementService
|
lateinit var keyManagement: E2ETestKeyManagementService
|
||||||
lateinit var notaryChangeService: NotaryChangeService
|
|
||||||
var inNodeNetworkMapService: NetworkMapService? = null
|
var inNodeNetworkMapService: NetworkMapService? = null
|
||||||
var inNodeNotaryService: NotaryService? = null
|
var inNodeNotaryService: NotaryService? = null
|
||||||
lateinit var identity: IdentityService
|
lateinit var identity: IdentityService
|
||||||
@ -130,9 +129,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
|||||||
net = makeMessagingService()
|
net = makeMessagingService()
|
||||||
wallet = NodeWalletService(services)
|
wallet = NodeWalletService(services)
|
||||||
makeInterestRatesOracleService()
|
makeInterestRatesOracleService()
|
||||||
api = APIServerImpl(this)
|
|
||||||
notaryChangeService = NotaryChangeService(net, smm)
|
|
||||||
|
|
||||||
identity = makeIdentityService()
|
identity = makeIdentityService()
|
||||||
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
|
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
|
||||||
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
||||||
@ -144,6 +140,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
|||||||
// This object doesn't need to be referenced from this class because it registers handlers on the network
|
// This object doesn't need to be referenced from this class because it registers handlers on the network
|
||||||
// service and so that keeps it from being collected.
|
// service and so that keeps it from being collected.
|
||||||
DataVendingService(net, storage)
|
DataVendingService(net, storage)
|
||||||
|
NotaryChangeService(net, smm)
|
||||||
|
|
||||||
buildAdvertisedServices()
|
buildAdvertisedServices()
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ class IRSSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork
|
|||||||
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
|
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
|
||||||
|
|
||||||
// Do we have any more days left in this deal's lifetime? If not, return.
|
// Do we have any more days left in this deal's lifetime? If not, return.
|
||||||
val nextFixingDate = theDealRef.state.calculation.nextFixingDate() ?: return null
|
val nextFixingDate = theDealRef.state.data.calculation.nextFixingDate() ?: return null
|
||||||
extraNodeLabels[node1] = "Fixing event on $nextFixingDate"
|
extraNodeLabels[node1] = "Fixing event on $nextFixingDate"
|
||||||
extraNodeLabels[node2] = "Fixing event on $nextFixingDate"
|
extraNodeLabels[node2] = "Fixing event on $nextFixingDate"
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.r3corda.node.internal.testing
|
package com.r3corda.node.internal.testing
|
||||||
|
|
||||||
|
import com.r3corda.core.contracts.StateAndRef
|
||||||
import com.r3corda.core.contracts.DummyContract
|
import com.r3corda.core.contracts.DummyContract
|
||||||
import com.r3corda.core.contracts.StateRef
|
import com.r3corda.core.contracts.StateRef
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
@ -10,20 +11,20 @@ import com.r3corda.node.internal.AbstractNode
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef {
|
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
|
||||||
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
||||||
tx.signWith(node.storage.myLegalIdentityKey)
|
tx.signWith(node.storage.myLegalIdentityKey)
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
val stx = tx.toSignedTransaction()
|
val stx = tx.toSignedTransaction()
|
||||||
node.services.recordTransactions(listOf(stx))
|
node.services.recordTransactions(listOf(stx))
|
||||||
return StateRef(stx.id, 0)
|
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef {
|
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
|
||||||
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
||||||
tx.setTime(Instant.now(), notary, 30.seconds)
|
tx.setTime(Instant.now(), notary, 30.seconds)
|
||||||
tx.signWith(node.storage.myLegalIdentityKey)
|
tx.signWith(node.storage.myLegalIdentityKey)
|
||||||
val stx = tx.toSignedTransaction(false)
|
val stx = tx.toSignedTransaction(false)
|
||||||
node.services.recordTransactions(listOf(stx))
|
node.services.recordTransactions(listOf(stx))
|
||||||
return StateRef(stx.id, 0)
|
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.r3corda.node.services
|
package com.r3corda.node.services
|
||||||
|
|
||||||
import com.r3corda.contracts.cash.Cash
|
|
||||||
import com.r3corda.core.messaging.MessagingService
|
import com.r3corda.core.messaging.MessagingService
|
||||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||||
import com.r3corda.node.services.api.AbstractNodeService
|
import com.r3corda.node.services.api.AbstractNodeService
|
||||||
@ -41,13 +40,14 @@ class NotaryChangeService(net: MessagingService, val smm: StateMachineManager) :
|
|||||||
* TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal
|
* TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal
|
||||||
*/
|
*/
|
||||||
private fun checkProposal(proposal: NotaryChangeProtocol.Proposal): Boolean {
|
private fun checkProposal(proposal: NotaryChangeProtocol.Proposal): Boolean {
|
||||||
val state = smm.serviceHub.loadState(proposal.stateRef)
|
|
||||||
if (state is Cash.State) return false // TODO: delete this example check
|
|
||||||
|
|
||||||
val newNotary = proposal.newNotary
|
val newNotary = proposal.newNotary
|
||||||
val isNotary = smm.serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary }
|
val isNotary = smm.serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary }
|
||||||
require(isNotary) { "The proposed node $newNotary does not run a Notary service " }
|
require(isNotary) { "The proposed node $newNotary does not run a Notary service " }
|
||||||
|
|
||||||
|
// An example requirement
|
||||||
|
val blacklist = listOf("Evil Notary")
|
||||||
|
require(!blacklist.contains(newNotary.name))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
|||||||
*/
|
*/
|
||||||
override val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
override val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
||||||
get() = mutex.locked { wallet }.let { wallet ->
|
get() = mutex.locked { wallet }.let { wallet ->
|
||||||
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.thread }.mapValues { it.value }
|
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.data.thread }.mapValues { it.value }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
|
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
|
||||||
@ -103,8 +103,8 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
|||||||
|
|
||||||
private fun Wallet.update(tx: WireTransaction, ourKeys: Set<PublicKey>): Pair<Wallet, Wallet.Update> {
|
private fun Wallet.update(tx: WireTransaction, ourKeys: Set<PublicKey>): Pair<Wallet, Wallet.Update> {
|
||||||
val ourNewStates = tx.outputs.
|
val ourNewStates = tx.outputs.
|
||||||
filter { isRelevant(it, ourKeys) }.
|
filter { isRelevant(it.data, ourKeys) }.
|
||||||
map { tx.outRef<ContractState>(it) }
|
map { tx.outRef<ContractState>(it.data) }
|
||||||
|
|
||||||
// Now calculate the states that are being spent by this transaction.
|
// Now calculate the states that are being spent by this transaction.
|
||||||
val consumed: Set<StateRef> = states.map { it.ref }.intersect(tx.inputs)
|
val consumed: Set<StateRef> = states.map { it.ref }.intersect(tx.inputs)
|
||||||
|
@ -24,7 +24,7 @@ class WalletImpl(override val states: List<StateAndRef<ContractState>>) : Wallet
|
|||||||
*/
|
*/
|
||||||
override val cashBalances: Map<Currency, Amount<Currency>> get() = states.
|
override val cashBalances: Map<Currency, Amount<Currency>> get() = states.
|
||||||
// Select the states we own which are cash, ignore the rest, take the amounts.
|
// Select the states we own which are cash, ignore the rest, take the amounts.
|
||||||
mapNotNull { (it.state as? Cash.State)?.amount }.
|
mapNotNull { (it.state.data as? Cash.State)?.amount }.
|
||||||
// Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
|
// Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
|
||||||
groupBy { it.token.product }.
|
groupBy { it.token.product }.
|
||||||
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
|
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
|
||||||
|
@ -99,6 +99,5 @@
|
|||||||
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
|
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
|
||||||
"tradeID": "tradeXXX",
|
"tradeID": "tradeXXX",
|
||||||
"hashLegalDocs": "put hash here"
|
"hashLegalDocs": "put hash here"
|
||||||
},
|
}
|
||||||
"notary": "Notary Service"
|
|
||||||
}
|
}
|
||||||
|
@ -468,7 +468,7 @@ class TwoPartyTradeProtocolTests {
|
|||||||
attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> {
|
attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> {
|
||||||
val ap = transaction {
|
val ap = transaction {
|
||||||
output("alice's paper") {
|
output("alice's paper") {
|
||||||
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days, notary)
|
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days)
|
||||||
}
|
}
|
||||||
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||||
if (!withError)
|
if (!withError)
|
||||||
|
@ -4,6 +4,7 @@ import com.r3corda.contracts.cash.Cash
|
|||||||
import com.r3corda.contracts.testing.CASH
|
import com.r3corda.contracts.testing.CASH
|
||||||
import com.r3corda.contracts.testing.`issued by`
|
import com.r3corda.contracts.testing.`issued by`
|
||||||
import com.r3corda.contracts.testing.`owned by`
|
import com.r3corda.contracts.testing.`owned by`
|
||||||
|
import com.r3corda.contracts.testing.`with notary`
|
||||||
import com.r3corda.core.bd
|
import com.r3corda.core.bd
|
||||||
import com.r3corda.core.contracts.DOLLARS
|
import com.r3corda.core.contracts.DOLLARS
|
||||||
import com.r3corda.core.contracts.Fix
|
import com.r3corda.core.contracts.Fix
|
||||||
@ -11,6 +12,7 @@ import com.r3corda.core.contracts.TransactionBuilder
|
|||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.generateKeyPair
|
import com.r3corda.core.crypto.generateKeyPair
|
||||||
import com.r3corda.core.testing.ALICE_PUBKEY
|
import com.r3corda.core.testing.ALICE_PUBKEY
|
||||||
|
import com.r3corda.core.testing.DUMMY_NOTARY
|
||||||
import com.r3corda.core.testing.MEGA_CORP
|
import com.r3corda.core.testing.MEGA_CORP
|
||||||
import com.r3corda.core.testing.MEGA_CORP_KEY
|
import com.r3corda.core.testing.MEGA_CORP_KEY
|
||||||
import com.r3corda.core.utilities.BriefLogFormatter
|
import com.r3corda.core.utilities.BriefLogFormatter
|
||||||
@ -117,5 +119,5 @@ class NodeInterestRatesTest {
|
|||||||
assertEquals("0.678".bd, fix.value)
|
assertEquals("0.678".bd, fix.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY))
|
private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY))
|
||||||
}
|
}
|
@ -51,14 +51,14 @@ class NodeWalletServiceTest {
|
|||||||
val w = wallet.currentWallet
|
val w = wallet.currentWallet
|
||||||
assertEquals(3, w.states.size)
|
assertEquals(3, w.states.size)
|
||||||
|
|
||||||
val state = w.states[0].state as Cash.State
|
val state = w.states[0].state.data as Cash.State
|
||||||
val myIdentity = services.storageService.myLegalIdentity
|
val myIdentity = services.storageService.myLegalIdentity
|
||||||
val myPartyRef = myIdentity.ref(ref)
|
val myPartyRef = myIdentity.ref(ref)
|
||||||
assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount)
|
assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount)
|
||||||
assertEquals(ALICE_PUBKEY, state.owner)
|
assertEquals(ALICE_PUBKEY, state.owner)
|
||||||
|
|
||||||
assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state as Cash.State).amount)
|
assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state.data as Cash.State).amount)
|
||||||
assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state as Cash.State).amount)
|
assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state.data as Cash.State).amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -77,12 +77,14 @@ class NodeWalletServiceTest {
|
|||||||
val spendTX = TransactionBuilder().apply {
|
val spendTX = TransactionBuilder().apply {
|
||||||
Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput))
|
Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput))
|
||||||
signWith(freshKey)
|
signWith(freshKey)
|
||||||
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
}.toSignedTransaction()
|
}.toSignedTransaction()
|
||||||
|
|
||||||
// A tx that doesn't send us anything.
|
// A tx that doesn't send us anything.
|
||||||
val irrelevantTX = TransactionBuilder().apply {
|
val irrelevantTX = TransactionBuilder().apply {
|
||||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
|
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
|
||||||
signWith(MEGA_CORP_KEY)
|
signWith(MEGA_CORP_KEY)
|
||||||
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
}.toSignedTransaction()
|
}.toSignedTransaction()
|
||||||
|
|
||||||
assertNull(wallet.cashBalances[USD])
|
assertNull(wallet.cashBalances[USD])
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.r3corda.node.services
|
package com.r3corda.node.services
|
||||||
|
|
||||||
import com.r3corda.core.contracts.TransactionBuilder
|
import com.r3corda.core.contracts.StateRef
|
||||||
|
import com.r3corda.core.contracts.TransactionType
|
||||||
|
import com.r3corda.core.contracts.WireTransaction
|
||||||
import com.r3corda.core.node.services.UniquenessException
|
import com.r3corda.core.node.services.UniquenessException
|
||||||
import com.r3corda.core.testing.MEGA_CORP
|
import com.r3corda.core.testing.MEGA_CORP
|
||||||
import com.r3corda.core.testing.generateStateRef
|
import com.r3corda.core.testing.generateStateRef
|
||||||
@ -15,7 +17,8 @@ class UniquenessProviderTests {
|
|||||||
@Test fun `should commit a transaction with unused inputs without exception`() {
|
@Test fun `should commit a transaction with unused inputs without exception`() {
|
||||||
val provider = InMemoryUniquenessProvider()
|
val provider = InMemoryUniquenessProvider()
|
||||||
val inputState = generateStateRef()
|
val inputState = generateStateRef()
|
||||||
val tx = TransactionBuilder().withItems(inputState).toWireTransaction()
|
val tx = buildTransaction(inputState)
|
||||||
|
|
||||||
provider.commit(tx, identity)
|
provider.commit(tx, identity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,10 +26,10 @@ class UniquenessProviderTests {
|
|||||||
val provider = InMemoryUniquenessProvider()
|
val provider = InMemoryUniquenessProvider()
|
||||||
val inputState = generateStateRef()
|
val inputState = generateStateRef()
|
||||||
|
|
||||||
val tx1 = TransactionBuilder().withItems(inputState).toWireTransaction()
|
val tx1 = buildTransaction(inputState)
|
||||||
provider.commit(tx1, identity)
|
provider.commit(tx1, identity)
|
||||||
|
|
||||||
val tx2 = TransactionBuilder().withItems(inputState).toWireTransaction()
|
val tx2 = buildTransaction(inputState)
|
||||||
val ex = assertFailsWith<UniquenessException> { provider.commit(tx2, identity) }
|
val ex = assertFailsWith<UniquenessException> { provider.commit(tx2, identity) }
|
||||||
|
|
||||||
val consumingTx = ex.error.stateHistory[inputState]!!
|
val consumingTx = ex.error.stateHistory[inputState]!!
|
||||||
@ -34,4 +37,6 @@ class UniquenessProviderTests {
|
|||||||
assertEquals(consumingTx.inputIndex, tx1.inputs.indexOf(inputState))
|
assertEquals(consumingTx.inputIndex, tx1.inputs.indexOf(inputState))
|
||||||
assertEquals(consumingTx.requestingParty, identity)
|
assertEquals(consumingTx.requestingParty, identity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildTransaction(inputState: StateRef) = WireTransaction(listOf(inputState), emptyList(), emptyList(), emptyList(), TransactionType.Business())
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ package com.r3corda.node.visualiser
|
|||||||
|
|
||||||
import com.r3corda.core.contracts.CommandData
|
import com.r3corda.core.contracts.CommandData
|
||||||
import com.r3corda.core.contracts.ContractState
|
import com.r3corda.core.contracts.ContractState
|
||||||
|
import com.r3corda.core.contracts.TransactionState
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.testing.TransactionGroupDSL
|
import com.r3corda.core.testing.TransactionGroupDSL
|
||||||
import org.graphstream.graph.Edge
|
import org.graphstream.graph.Edge
|
||||||
@ -30,7 +31,7 @@ class GraphVisualiser(val dsl: TransactionGroupDSL<in ContractState>) {
|
|||||||
val node = graph.addNode<Node>(tx.outRef<ContractState>(outIndex).ref.toString())
|
val node = graph.addNode<Node>(tx.outRef<ContractState>(outIndex).ref.toString())
|
||||||
val state = tx.outputs[outIndex]
|
val state = tx.outputs[outIndex]
|
||||||
node.label = stateToLabel(state)
|
node.label = stateToLabel(state)
|
||||||
node.styleClass = stateToCSSClass(state) + ",state"
|
node.styleClass = stateToCSSClass(state.data) + ",state"
|
||||||
node.setAttribute("state", state)
|
node.setAttribute("state", state)
|
||||||
val edge = graph.addEdge<Edge>("tx$txIndex-out$outIndex", txNode, node, true)
|
val edge = graph.addEdge<Edge>("tx$txIndex-out$outIndex", txNode, node, true)
|
||||||
edge.weight = 0.7
|
edge.weight = 0.7
|
||||||
@ -55,8 +56,8 @@ class GraphVisualiser(val dsl: TransactionGroupDSL<in ContractState>) {
|
|||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stateToLabel(state: ContractState): String {
|
private fun stateToLabel(state: TransactionState<*>): String {
|
||||||
return dsl.labelForState(state) ?: stateToTypeName(state)
|
return dsl.labelForState(state) ?: stateToTypeName(state.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun commandToTypeName(state: CommandData) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.')
|
private fun commandToTypeName(state: CommandData) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.')
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
package node.services
|
package node.services
|
||||||
|
|
||||||
import com.r3corda.contracts.DummyContract
|
import com.r3corda.contracts.DummyContract
|
||||||
import com.r3corda.core.contracts.StateAndRef
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.contracts.StateRef
|
|
||||||
import com.r3corda.core.contracts.TransactionBuilder
|
|
||||||
import com.r3corda.core.testing.DUMMY_NOTARY
|
import com.r3corda.core.testing.DUMMY_NOTARY
|
||||||
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
||||||
import com.r3corda.node.internal.testing.MockNetwork
|
import com.r3corda.node.internal.testing.MockNetwork
|
||||||
|
import com.r3corda.node.internal.testing.issueState
|
||||||
import com.r3corda.node.services.transactions.NotaryService
|
import com.r3corda.node.services.transactions.NotaryService
|
||||||
import com.r3corda.node.testutils.issueState
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import protocols.NotaryChangeProtocol
|
import protocols.NotaryChangeProtocol
|
||||||
@ -35,12 +33,12 @@ class NotaryChangeTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should change notary for a state with single participant`() {
|
fun `should change notary for a state with single participant`() {
|
||||||
val state = issueState(clientNodeA, DUMMY_NOTARY)
|
val ref = issueState(clientNodeA, DUMMY_NOTARY).ref
|
||||||
val ref = clientNodeA.services.loadState(state)
|
val state = clientNodeA.services.loadState(ref)
|
||||||
|
|
||||||
val newNotary = newNotaryNode.info.identity
|
val newNotary = newNotaryNode.info.identity
|
||||||
|
|
||||||
val protocol = Instigator(StateAndRef(ref, state), newNotary)
|
val protocol = Instigator(StateAndRef(state, ref), newNotary)
|
||||||
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
|
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
|
||||||
|
|
||||||
net.runNetwork()
|
net.runNetwork()
|
||||||
@ -51,11 +49,10 @@ class NotaryChangeTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should change notary for a state with multiple participants`() {
|
fun `should change notary for a state with multiple participants`() {
|
||||||
val state = DummyContract.MultiOwnerState(0,
|
val state = TransactionState(DummyContract.MultiOwnerState(0,
|
||||||
listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey),
|
listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey)), DUMMY_NOTARY)
|
||||||
DUMMY_NOTARY)
|
|
||||||
|
|
||||||
val tx = TransactionBuilder().withItems(state)
|
val tx = TransactionBuilder(type = TransactionType.NotaryChange()).withItems(state)
|
||||||
tx.signWith(clientNodeA.storage.myLegalIdentityKey)
|
tx.signWith(clientNodeA.storage.myLegalIdentityKey)
|
||||||
tx.signWith(clientNodeB.storage.myLegalIdentityKey)
|
tx.signWith(clientNodeB.storage.myLegalIdentityKey)
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package com.r3corda.demos
|
package com.r3corda.demos
|
||||||
|
|
||||||
import com.r3corda.contracts.cash.Cash
|
import com.r3corda.contracts.cash.Cash
|
||||||
import com.r3corda.core.contracts.DOLLARS
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.contracts.FixOf
|
|
||||||
import com.r3corda.core.contracts.`issued by`
|
|
||||||
import com.r3corda.core.contracts.TransactionBuilder
|
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.logElapsedTime
|
import com.r3corda.core.logElapsedTime
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
@ -87,7 +84,7 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
// Make a garbage transaction that includes a rate fix.
|
// Make a garbage transaction that includes a rate fix.
|
||||||
val tx = TransactionBuilder()
|
val tx = TransactionBuilder()
|
||||||
tx.addOutputState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.keyManagement.freshKey().public, notary.identity))
|
tx.addOutputState(TransactionState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.keyManagement.freshKey().public), notary.identity))
|
||||||
val protocol = RatesFixProtocol(tx, oracleNode, fixOf, expectedRate, rateTolerance)
|
val protocol = RatesFixProtocol(tx, oracleNode, fixOf, expectedRate, rateTolerance)
|
||||||
node.smm.add("demo.ratefix", protocol).get()
|
node.smm.add("demo.ratefix", protocol).get()
|
||||||
node.stop()
|
node.stop()
|
||||||
|
@ -356,7 +356,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
|
|||||||
// Sign it as ourselves.
|
// Sign it as ourselves.
|
||||||
tx.signWith(keyPair)
|
tx.signWith(keyPair)
|
||||||
|
|
||||||
// Get the notary to sign it, thus committing the outputs.
|
// Get the notary to sign the timestamp
|
||||||
val notarySig = subProtocol(NotaryProtocol.Client(tx.toWireTransaction()))
|
val notarySig = subProtocol(NotaryProtocol.Client(tx.toWireTransaction()))
|
||||||
tx.addSignatureUnchecked(notarySig)
|
tx.addSignatureUnchecked(notarySig)
|
||||||
|
|
||||||
|
@ -44,14 +44,14 @@ class InterestRateSwapAPI(val api: APIServer) {
|
|||||||
private fun getDealByRef(ref: String): InterestRateSwap.State? {
|
private fun getDealByRef(ref: String): InterestRateSwap.State? {
|
||||||
val states = api.queryStates(StatesQuery.selectDeal(ref))
|
val states = api.queryStates(StatesQuery.selectDeal(ref))
|
||||||
return if (states.isEmpty()) null else {
|
return if (states.isEmpty()) null else {
|
||||||
val deals = api.fetchStates(states).values.map { it as InterestRateSwap.State }.filterNotNull()
|
val deals = api.fetchStates(states).values.map { it?.data as InterestRateSwap.State }.filterNotNull()
|
||||||
return if (deals.isEmpty()) null else deals[0]
|
return if (deals.isEmpty()) null else deals[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAllDeals(): Array<InterestRateSwap.State> {
|
private fun getAllDeals(): Array<InterestRateSwap.State> {
|
||||||
val states = api.queryStates(StatesQuery.selectAllDeals())
|
val states = api.queryStates(StatesQuery.selectAllDeals())
|
||||||
val swaps = api.fetchStates(states).values.map { it as InterestRateSwap.State }.filterNotNull().toTypedArray()
|
val swaps = api.fetchStates(states).values.map { it?.data as InterestRateSwap.State }.filterNotNull().toTypedArray()
|
||||||
return swaps
|
return swaps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import com.r3corda.contracts.InterestRateSwap
|
import com.r3corda.contracts.InterestRateSwap
|
||||||
import com.r3corda.core.contracts.DealState
|
import com.r3corda.core.contracts.DealState
|
||||||
import com.r3corda.core.contracts.StateAndRef
|
import com.r3corda.core.contracts.StateAndRef
|
||||||
|
import com.r3corda.core.contracts.TransactionState
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
import com.r3corda.core.node.services.linearHeadsOfType
|
import com.r3corda.core.node.services.linearHeadsOfType
|
||||||
import com.r3corda.core.protocols.ProtocolLogic
|
import com.r3corda.core.protocols.ProtocolLogic
|
||||||
@ -13,6 +14,7 @@ import com.r3corda.core.utilities.ProgressTracker
|
|||||||
import com.r3corda.demos.DemoClock
|
import com.r3corda.demos.DemoClock
|
||||||
import com.r3corda.node.internal.Node
|
import com.r3corda.node.internal.Node
|
||||||
import com.r3corda.node.services.network.MockNetworkMapCache
|
import com.r3corda.node.services.network.MockNetworkMapCache
|
||||||
|
import com.r3corda.node.utilities.ANSIProgressRenderer
|
||||||
import com.r3corda.protocols.TwoPartyDealProtocol
|
import com.r3corda.protocols.TwoPartyDealProtocol
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
@ -41,12 +43,12 @@ object UpdateBusinessDayProtocol {
|
|||||||
// Get deals
|
// Get deals
|
||||||
progressTracker.currentStep = FETCHING
|
progressTracker.currentStep = FETCHING
|
||||||
val dealStateRefs = serviceHub.walletService.linearHeadsOfType<DealState>()
|
val dealStateRefs = serviceHub.walletService.linearHeadsOfType<DealState>()
|
||||||
val otherPartyToDeals = dealStateRefs.values.groupBy { otherParty(it.state) }
|
val otherPartyToDeals = dealStateRefs.values.groupBy { otherParty(it.state.data) }
|
||||||
|
|
||||||
// TODO we need to process these in parallel to stop there being an ordering problem across more than two nodes
|
// TODO we need to process these in parallel to stop there being an ordering problem across more than two nodes
|
||||||
val sortedParties = otherPartyToDeals.keys.sortedBy { it.identity.name }
|
val sortedParties = otherPartyToDeals.keys.sortedBy { it.identity.name }
|
||||||
for (party in sortedParties) {
|
for (party in sortedParties) {
|
||||||
val sortedDeals = otherPartyToDeals[party]!!.sortedBy { it.state.ref }
|
val sortedDeals = otherPartyToDeals[party]!!.sortedBy { it.state.data.ref }
|
||||||
for (deal in sortedDeals) {
|
for (deal in sortedDeals) {
|
||||||
progressTracker.currentStep = ITERATING_DEALS
|
progressTracker.currentStep = ITERATING_DEALS
|
||||||
processDeal(party, deal, date, sessionID)
|
processDeal(party, deal, date, sessionID)
|
||||||
@ -64,9 +66,9 @@ object UpdateBusinessDayProtocol {
|
|||||||
// TODO we should make this more object oriented when we can ask a state for it's contract
|
// TODO we should make this more object oriented when we can ask a state for it's contract
|
||||||
@Suspendable
|
@Suspendable
|
||||||
fun processDeal(party: NodeInfo, deal: StateAndRef<DealState>, date: LocalDate, sessionID: Long) {
|
fun processDeal(party: NodeInfo, deal: StateAndRef<DealState>, date: LocalDate, sessionID: Long) {
|
||||||
val s = deal.state
|
val s = deal.state.data
|
||||||
when (s) {
|
when (s) {
|
||||||
is InterestRateSwap.State -> processInterestRateSwap(party, StateAndRef(s, deal.ref), date, sessionID)
|
is InterestRateSwap.State -> processInterestRateSwap(party, StateAndRef(TransactionState(s, deal.state.notary), deal.ref), date, sessionID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +76,7 @@ object UpdateBusinessDayProtocol {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
fun processInterestRateSwap(party: NodeInfo, deal: StateAndRef<InterestRateSwap.State>, date: LocalDate, sessionID: Long) {
|
fun processInterestRateSwap(party: NodeInfo, deal: StateAndRef<InterestRateSwap.State>, date: LocalDate, sessionID: Long) {
|
||||||
var dealStateAndRef: StateAndRef<InterestRateSwap.State>? = deal
|
var dealStateAndRef: StateAndRef<InterestRateSwap.State>? = deal
|
||||||
var nextFixingDate = deal.state.calculation.nextFixingDate()
|
var nextFixingDate = deal.state.data.calculation.nextFixingDate()
|
||||||
while (nextFixingDate != null && !nextFixingDate.isAfter(date)) {
|
while (nextFixingDate != null && !nextFixingDate.isAfter(date)) {
|
||||||
progressTracker.currentStep = ITERATING_FIXINGS
|
progressTracker.currentStep = ITERATING_FIXINGS
|
||||||
/*
|
/*
|
||||||
@ -83,12 +85,12 @@ object UpdateBusinessDayProtocol {
|
|||||||
* One of the parties needs to take the lead in the coordination and this is a reliable deterministic way
|
* One of the parties needs to take the lead in the coordination and this is a reliable deterministic way
|
||||||
* to do it.
|
* to do it.
|
||||||
*/
|
*/
|
||||||
if (party.identity.name == deal.state.fixedLeg.fixedRatePayer.name) {
|
if (party.identity.name == deal.state.data.fixedLeg.fixedRatePayer.name) {
|
||||||
dealStateAndRef = nextFixingFloatingLeg(dealStateAndRef!!, party, sessionID)
|
dealStateAndRef = nextFixingFloatingLeg(dealStateAndRef!!, party, sessionID)
|
||||||
} else {
|
} else {
|
||||||
dealStateAndRef = nextFixingFixedLeg(dealStateAndRef!!, party, sessionID)
|
dealStateAndRef = nextFixingFixedLeg(dealStateAndRef!!, party, sessionID)
|
||||||
}
|
}
|
||||||
nextFixingDate = dealStateAndRef?.state?.calculation?.nextFixingDate()
|
nextFixingDate = dealStateAndRef?.state?.data?.calculation?.nextFixingDate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +100,7 @@ object UpdateBusinessDayProtocol {
|
|||||||
progressTracker.currentStep = FIXING
|
progressTracker.currentStep = FIXING
|
||||||
|
|
||||||
val myName = serviceHub.storageService.myLegalIdentity.name
|
val myName = serviceHub.storageService.myLegalIdentity.name
|
||||||
val deal: InterestRateSwap.State = dealStateAndRef.state
|
val deal: InterestRateSwap.State = dealStateAndRef.state.data
|
||||||
val myOldParty = deal.parties.single { it.name == myName }
|
val myOldParty = deal.parties.single { it.name == myName }
|
||||||
val keyPair = serviceHub.keyManagementService.toKeyPair(myOldParty.owningKey)
|
val keyPair = serviceHub.keyManagementService.toKeyPair(myOldParty.owningKey)
|
||||||
val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.notaryNodes[0], dealStateAndRef,
|
val participant = TwoPartyDealProtocol.Floater(party.address, sessionID, serviceHub.networkMapCache.notaryNodes[0], dealStateAndRef,
|
||||||
|
Reference in New Issue
Block a user