Merged in multiple-notary-support (pull request #114)

Multiple notary support - data model changes & protocol
This commit is contained in:
Andrius Dagys 2016-06-17 15:26:11 +01:00
commit f02e6580f3
56 changed files with 1039 additions and 445 deletions

View File

@ -8,25 +8,27 @@
package com.r3corda.contracts.isolated
import com.r3corda.core.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
// The dummy contract doesn't do anything useful. It exists for testing purposes.
val ANOTHER_DUMMY_PROGRAM_ID = AnotherDummyContract()
class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdoor {
class State(val magicNumber: Int = 0, override val notary: Party) : ContractState {
data class State(val magicNumber: Int = 0) : ContractState {
override val contract = ANOTHER_DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = emptyList()
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Always accepts.
}
@ -34,8 +36,8 @@ class AnotherDummyContract : Contract, com.r3corda.core.node.DummyContractBackdo
override val legalContractReference: SecureHash = SecureHash.sha256("https://anotherdummy.org")
override fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
val state = State(magicNumber)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
override fun inspectState(state: ContractState): Int = (state as State).magicNumber

View File

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

View File

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

View File

@ -487,7 +487,7 @@ class InterestRateSwap() : Contract {
/**
* verify() with some examples of what needs to be checked.
*/
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Group by Trade ID for in / out states
val groups = tx.groupStates() { state: InterestRateSwap.State -> state.common.tradeID }
@ -587,14 +587,16 @@ class InterestRateSwap() : Contract {
val fixedLeg: FixedLeg,
val floatingLeg: FloatingLeg,
val calculation: Calculation,
val common: Common,
override val notary: Party
val common: Common
) : FixableDealState {
override val contract = IRS_PROGRAM_ID
override val thread = SecureHash.sha256(common.tradeID)
override val ref = common.tradeID
override val participants: List<PublicKey>
get() = parties.map { it.owningKey }
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
return (fixedLeg.fixedRatePayer.owningKey in ourKeys) || (floatingLeg.floatingRatePayer.owningKey in ourKeys)
}
@ -618,10 +620,10 @@ class InterestRateSwap() : Contract {
}
}
override fun generateAgreement(): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
override fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix) {
InterestRateSwap().generateFix(ptx, StateAndRef(this, oldStateRef), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, oldState.state.notary), oldState.ref), Pair(fix.of.forDay, Rate(RatioUnit(fix.value))))
}
override fun nextFixingOf(): FixOf? {
@ -715,8 +717,8 @@ class InterestRateSwap() : Contract {
val newCalculation = Calculation(calculation.expression, floatingLegPaymentSchedule, fixedLegPaymentSchedule)
// Put all the above into a new State object.
val state = State(fixedLeg, floatingLeg, newCalculation, common, notary)
return TransactionBuilder().withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey)))
val state = State(fixedLeg, floatingLeg, newCalculation, common)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Agree(), listOf(state.floatingLeg.floatingRatePayer.owningKey, state.fixedLeg.fixedRatePayer.owningKey)))
}
private fun calcFixingDate(date: LocalDate, fixingPeriod: DateOffset, calendar: BusinessCalendar): LocalDate {
@ -729,8 +731,11 @@ class InterestRateSwap() : Contract {
// TODO: Replace with rates oracle
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Pair<LocalDate, Rate>) {
tx.addInputState(irs.ref)
tx.addOutputState(irs.state.copy(calculation = irs.state.calculation.applyFixing(fixing.first, FixedRate(fixing.second))))
tx.addCommand(Commands.Fix(), listOf(irs.state.floatingLeg.floatingRatePayer.owningKey, irs.state.fixedLeg.fixedRatePayer.owningKey))
tx.addInputState(irs)
tx.addOutputState(
irs.state.data.copy(calculation = irs.state.data.calculation.applyFixing(fixing.first, FixedRate(fixing.second))),
irs.state.notary
)
tx.addCommand(Commands.Fix(), listOf(irs.state.data.floatingLeg.floatingRatePayer.owningKey, irs.state.data.fixedLeg.fixedRatePayer.owningKey))
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,22 +2,40 @@ package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
// The dummy contract doesn't do anything useful. It exists for testing purposes.
val DUMMY_PROGRAM_ID = DummyContract()
class DummyContract : Contract {
class State(val magicNumber: Int = 0,
override val notary: Party) : ContractState {
data class State(val magicNumber: Int = 0) : ContractState {
override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = emptyList()
}
data class SingleOwnerState(val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState {
override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
}
data class MultiOwnerState(val magicNumber: Int = 0,
val owners: List<PublicKey>) : ContractState {
override val contract = DUMMY_PROGRAM_ID
override val participants: List<PublicKey>
get() = owners
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
class Move : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Always accepts.
}
@ -25,7 +43,7 @@ class DummyContract : Contract {
override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
val state = SingleOwnerState(magicNumber, owner.party.owningKey)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
}

View File

@ -1,9 +1,5 @@
package com.r3corda.core.contracts
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.contracts.TransactionForVerification
import com.r3corda.core.contracts.Fix
import com.r3corda.core.contracts.FixOf
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.toStringShort
@ -31,8 +27,30 @@ interface ContractState {
/** Contract by which the state belongs */
val contract: Contract
/** Identity of the notary that ensures this state is not used as an input to a transaction more than once */
val notary: Party
/**
* A _participant_ is any party that is able to consume this state in a valid transaction.
*
* The list of participants is required for certain types of transactions. For example, when changing the notary
* for this state ([TransactionType.NotaryChange]), every participants has to be involved and approve the transaction
* so that they receive the updated state, and don't end up in a situation where they can no longer use a state
* they possess, since someone consumed that state during the notary change process.
*
* The participants list should normally be derived from the contents of the state. E.g. for [Cash] the participants
* list should just contain the owner.
*/
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 state, replacing the notary field with the new value.
* To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState]
*/
fun withNewNotary(newNotary: Party) = TransactionState(this.data, newNotary)
}
/**
@ -100,7 +118,7 @@ interface DealState : LinearState {
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a
* Contract instance from a ContractState are imminent, at which point we can move this out of here
*/
fun generateAgreement(): TransactionBuilder
fun generateAgreement(notary: Party): TransactionBuilder
}
/**
@ -120,7 +138,7 @@ interface FixableDealState : DealState {
* TODO: This would also likely move to methods on the Contract once the changes to reference
* the Contract from the ContractState are in
*/
fun generateFix(ptx: TransactionBuilder, oldStateRef: StateRef, fix: Fix)
fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix)
}
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
@ -135,11 +153,11 @@ data class StateRef(val txhash: SecureHash, val index: Int) {
}
/** A StateAndRef is simply a (state, ref) pair. For instance, a wallet (which holds available assets) contains these. */
data class StateAndRef<out T : ContractState>(val state: T, val ref: StateRef)
data class StateAndRef<out T : ContractState>(val state: TransactionState<T>, val ref: StateRef)
/** Filters a list of [StateAndRef] objects according to the type of the states */
inline fun <reified T : ContractState> List<StateAndRef<ContractState>>.filterStatesOfType(): List<StateAndRef<T>> {
return mapNotNull { if (it.state is T) StateAndRef(it.state, it.ref) else null }
return mapNotNull { if (it.state.data is T) StateAndRef(TransactionState(it.state.data, it.state.notary), it.ref) else null }
}
/**
@ -210,7 +228,7 @@ interface Contract {
* existing contract code.
*/
@Throws(IllegalArgumentException::class)
fun verify(tx: TransactionForVerification)
fun verify(tx: TransactionForContract)
/**
* Unparsed reference to the natural language contract that this code is supposed to express (usually a hash of

View File

@ -1,12 +1,6 @@
package com.r3corda.core.contracts
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.signWithECDSA
import com.r3corda.core.crypto.*
import com.r3corda.core.serialization.serialize
import java.security.KeyPair
import java.security.PublicKey
@ -16,14 +10,19 @@ import java.util.*
/**
* A TransactionBuilder is a transaction class that's mutable (unlike the others which are all immutable). It is
* intended to be passed around contracts that may edit it by adding new states/commands or modifying the existing set.
* Then once the states and commands are right, this class can be used as a holding bucket to gather signatures from
* multiple parties.
* intended to be passed around contracts that may edit it by adding new states/commands. Then once the states
* and commands are right, this class can be used as a holding bucket to gather signatures from multiple parties.
*
* The builder can be customised for specific transaction types, e.g. where additional processing is needed
* before adding a state/command.
*/
class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf(),
private val attachments: MutableList<SecureHash> = arrayListOf(),
private val outputs: MutableList<ContractState> = arrayListOf(),
private val commands: MutableList<Command> = arrayListOf()) {
abstract class TransactionBuilder(protected val type: TransactionType = TransactionType.General(),
protected val notary: Party? = null) {
protected val inputs: MutableList<StateRef> = arrayListOf()
protected val attachments: MutableList<SecureHash> = arrayListOf()
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf()
protected val commands: MutableList<Command> = arrayListOf()
protected val signers: MutableSet<PublicKey> = mutableSetOf()
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
@ -49,7 +48,8 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
fun withItems(vararg items: Any): TransactionBuilder {
for (t in items) {
when (t) {
is StateRef -> addInputState(t)
is StateAndRef<*> -> addInputState(t)
is TransactionState<*> -> addOutputState(t)
is ContractState -> addOutputState(t)
is Command -> addCommand(t)
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
@ -59,7 +59,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
}
/** The signatures that have been collected so far - might be incomplete! */
private val currentSigs = arrayListOf<DigitalSignature.WithKey>()
protected val currentSigs = arrayListOf<DigitalSignature.WithKey>()
fun signWith(key: KeyPair) {
check(currentSigs.none { it.by == key.public }) { "This partial transaction was already signed by ${key.public}" }
@ -96,22 +96,25 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
}
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
ArrayList(outputs), ArrayList(commands))
ArrayList(outputs), ArrayList(commands), signers.toList(), type)
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
if (checkSufficientSignatures) {
val gotKeys = currentSigs.map { it.by }.toSet()
for (command in commands) {
if (!gotKeys.containsAll(command.signers))
throw IllegalStateException("Missing signatures on the transaction for a ${command.value.javaClass.canonicalName} command")
}
val missing = signers - gotKeys
if (missing.isNotEmpty())
throw IllegalStateException("Missing signatures on the transaction for the public keys: ${missing.map { it.toStringShort() }}")
}
return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
}
fun addInputState(ref: StateRef) {
open fun addInputState(stateAndRef: StateAndRef<*>) {
check(currentSigs.isEmpty())
inputs.add(ref)
val notaryKey = stateAndRef.state.notary.owningKey
signers.add(notaryKey)
inputs.add(stateAndRef.ref)
}
fun addAttachment(attachment: Attachment) {
@ -119,14 +122,22 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
attachments.add(attachment.id)
}
fun addOutputState(state: ContractState) {
fun addOutputState(state: TransactionState<*>) {
check(currentSigs.isEmpty())
outputs.add(state)
}
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
fun addOutputState(state: ContractState) {
checkNotNull(notary) { "Need to specify a Notary for the state, or set a default one on TransactionBuilder initialisation" }
addOutputState(state, notary!!)
}
fun addCommand(arg: Command) {
check(currentSigs.isEmpty())
// We should probably merge the lists of pubkeys for identical commands here.
// TODO: replace pubkeys in commands with 'pointers' to keys in signers
signers.addAll(arg.signers)
commands.add(arg)
}
@ -136,7 +147,7 @@ class TransactionBuilder(private val inputs: MutableList<StateRef> = arrayListOf
// Accessors that yield immutable snapshots.
fun inputStates(): List<StateRef> = ArrayList(inputs)
fun outputStates(): List<ContractState> = ArrayList(outputs)
fun outputStates(): List<TransactionState<*>> = ArrayList(outputs)
fun commands(): List<Command> = ArrayList(commands)
fun attachments(): List<SecureHash> = ArrayList(attachments)
}

View File

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

View File

@ -0,0 +1,115 @@
package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.noneOrSingle
import java.security.PublicKey
/** Defines transaction build & validation logic 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) {
val missing = verifySigners(tx)
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
verifyTransaction(tx)
}
/** Check that the list of signers includes all the necessary keys */
fun verifySigners(tx: TransactionForVerification): Set<PublicKey> {
val timestamp = tx.commands.noneOrSingle { it.value is TimestampCommand }
val timestampKey = timestamp?.signers.orEmpty()
val notaryKey = (tx.inStates.map { it.notary.owningKey } + timestampKey).toSet()
if (notaryKey.size > 1) throw TransactionVerificationException.MoreThanOneNotary(tx)
val requiredKeys = getRequiredSigners(tx) + notaryKey
val missing = requiredKeys - tx.signers
return missing
}
/**
* Return the list of public keys that that require signatures for the transaction type.
* Note: the notary key is checked separately for all transactions and need not be included
*/
abstract fun getRequiredSigners(tx: TransactionForVerification): Set<PublicKey>
/** Implement type specific transaction validation logic */
abstract fun verifyTransaction(tx: TransactionForVerification)
/** A general transaction type where transaction validity is determined by custom contract code */
class General : TransactionType() {
/** Just uses the default [TransactionBuilder] with no special logic */
class Builder(notary: Party? = null) : TransactionBuilder(General(), notary) {}
/**
* 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 verifyTransaction(tx: TransactionForVerification) {
// TODO: Check that notary is unchanged
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)
}
}
}
override fun getRequiredSigners(tx: TransactionForVerification): Set<PublicKey> {
val commandKeys = tx.commands.flatMap { it.signers }.toSet()
return commandKeys
}
}
/**
* 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() {
/**
* A transaction builder that automatically sets the transaction type to [NotaryChange]
* and adds the list of participants to the signers set for every input state.
*/
class Builder(notary: Party? = null) : TransactionBuilder(NotaryChange(), notary) {
override fun addInputState(stateAndRef: StateAndRef<*>) {
signers.addAll(stateAndRef.state.data.participants)
super.addInputState(stateAndRef)
}
}
/**
* Check that the difference between inputs and outputs is only the notary field,
* and that all required signing public keys are present
*/
override fun verifyTransaction(tx: TransactionForVerification) {
try {
tx.inStates.zip(tx.outStates).forEach {
check(it.first.data == it.second.data)
check(it.first.notary != it.second.notary)
}
check(tx.commands.isEmpty())
} catch (e: IllegalStateException) {
throw TransactionVerificationException.InvalidNotaryChange(tx)
}
}
override fun getRequiredSigners(tx: TransactionForVerification): Set<PublicKey> {
val participantKeys = tx.inStates.flatMap { it.data.participants }.toSet()
return participantKeys
}
}
}

View File

@ -1,8 +1,8 @@
package com.r3corda.core.contracts
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
import java.util.*
// TODO: Consider moving this out of the core module and providing a different way for unit tests to test contracts.
@ -30,7 +30,7 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
val resolved = HashSet<TransactionForVerification>(transactions.size)
for (tx in transactions) {
val inputs = ArrayList<ContractState>(tx.inputs.size)
val inputs = ArrayList<TransactionState<ContractState>>(tx.inputs.size)
for (ref in tx.inputs) {
val conflict = refToConsumingTXMap[ref]
if (conflict != null)
@ -42,74 +42,51 @@ class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerified
// Look up the output in that transaction by index.
inputs.add(ltx.outputs[ref.index])
}
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id))
resolved.add(TransactionForVerification(inputs, tx.outputs, tx.attachments, tx.commands, tx.id, tx.signers, tx.type))
}
for (tx in resolved)
tx.verify()
return resolved
}
}
/** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */
data class TransactionForVerification(val inStates: List<ContractState>,
val outStates: List<ContractState>,
data class TransactionForVerification(val inStates: List<TransactionState<ContractState>>,
val outStates: List<TransactionState<ContractState>>,
val attachments: List<Attachment>,
val commands: List<AuthenticatedObject<CommandData>>,
val origHash: SecureHash) {
val origHash: SecureHash,
val signers: List<PublicKey>,
val type: TransactionType) {
override fun hashCode() = origHash.hashCode()
override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash
/**
* Verifies that the transaction is valid:
* - Checks that the input states and the timestamp point to the same Notary
* - Runs the contracts for this transaction. If any contract fails to verify, the whole transaction
* is considered to be invalid
* Verifies that the transaction is valid by running type-specific validation logic.
*
* TODO: Move this out of the core data structure definitions, once unit tests are more cleanly separated.
*
* @throws TransactionVerificationException if a contract throws an exception (the original is in the cause field)
* or the transaction has references to more than one Notary
* @throws TransactionVerificationException if validation logic fails or if a contract throws an exception
* (the original is in the cause field)
*/
@Throws(TransactionVerificationException::class)
fun verify() {
verifySingleNotary()
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)
}
}
}
fun verify() = type.verify(this)
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)
}
fun toTransactionForContract() = TransactionForContract(inStates.map { it.data }, outStates.map { it.data }, attachments, commands, origHash)
}
/**
* Utilities for contract writers to incorporate into their logic.
*/
/**
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
* be used to simplify this logic.
*/
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
/**
* A transaction to be passed as input to a contract verification function. Defines helper methods to
* simplify verification logic in contracts.
*/
data class TransactionForContract(val inStates: List<ContractState>,
val outStates: List<ContractState>,
val attachments: List<Attachment>,
val commands: List<AuthenticatedObject<CommandData>>,
val origHash: SecureHash) {
override fun hashCode() = origHash.hashCode()
override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
/**
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
@ -161,6 +138,24 @@ data class TransactionForVerification(val inStates: List<ContractState>,
return result
}
/** Utilities for contract writers to incorporate into their logic. */
/**
* A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated
* using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated
* state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add
* up on both sides of the transaction, but the values must be summed independently per currency. Grouping can
* be used to simplify this logic.
*/
data class InOutGroup<T : ContractState, K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
}
class TransactionResolutionException(val hash: SecureHash) : Exception()
@ -169,4 +164,6 @@ class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTra
sealed class TransactionVerificationException(val tx: TransactionForVerification, cause: Throwable?) : Exception(cause) {
class ContractRejection(tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
class MoreThanOneNotary(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
class SignersMissing(tx: TransactionForVerification, missing: List<PublicKey>) : TransactionVerificationException(tx, null)
class InvalidNotaryChange(tx: TransactionForVerification) : TransactionVerificationException(tx, null)
}

View File

@ -1,7 +1,6 @@
package com.r3corda.core.contracts
import com.esotericsoftware.kryo.Kryo
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.toStringShort
@ -44,8 +43,10 @@ import java.security.SignatureException
/** Transaction ready for serialisation, without any signatures attached. */
data class WireTransaction(val inputs: List<StateRef>,
val attachments: List<SecureHash>,
val outputs: List<ContractState>,
val commands: List<Command>) : NamedByHash {
val outputs: List<TransactionState<ContractState>>,
val commands: List<Command>,
val signers: List<PublicKey>,
val type: TransactionType) : NamedByHash {
// Cache the serialised form of the transaction and its hash to give us fast access to it.
@Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null
@ -64,11 +65,11 @@ data class WireTransaction(val inputs: List<StateRef>,
@Suppress("UNCHECKED_CAST")
fun <T : ContractState> outRef(index: Int): StateAndRef<T> {
require(index >= 0 && index < outputs.size)
return StateAndRef(outputs[index] as T, StateRef(id, index))
return StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
}
/** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.indexOfOrThrow(state))
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.map { it.data }.indexOfOrThrow(state))
override fun toString(): String {
val buf = StringBuilder()
@ -110,7 +111,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
/**
* Verify the signatures, deserialise the wire transaction and then check that the set of signatures found contains
* the set of pubkeys in the commands. If any signatures are missing, either throws an exception (by default) or
* the set of pubkeys in the signers list. If any signatures are missing, either throws an exception (by default) or
* returns the list of keys that have missing signatures, depending on the parameter.
*
* @throws SignatureException if a signature is invalid, does not match or if any signature is missing.
@ -131,15 +132,20 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
return copy(sigs = sigs + sig)
}
fun withAdditionalSignatures(sigList: Iterable<DigitalSignature.WithKey>): SignedTransaction {
return copy(sigs = sigs + sigList)
}
/** Alias for [withAdditionalSignature] to let you use Kotlin operator overloading. */
operator fun plus(sig: DigitalSignature.WithKey) = withAdditionalSignature(sig)
operator fun plus(sigList: Collection<DigitalSignature.WithKey>) = withAdditionalSignatures(sigList)
/**
* Returns the set of missing signatures - a signature must be present for every command pub key
* and the Notary (if it is specified)
* Returns the set of missing signatures - a signature must be present for each signer public key
*/
fun getMissingSignatures(): Set<PublicKey> {
val requiredKeys = tx.commands.flatMap { it.signers }.toSet()
val requiredKeys = tx.signers.toSet()
val sigKeys = sigs.map { it.by }.toSet()
if (sigKeys.containsAll(requiredKeys)) return emptySet()
@ -160,12 +166,14 @@ data class LedgerTransaction(
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
val attachments: List<Attachment>,
/** The states that will be generated by the execution of this transaction. */
val outputs: List<ContractState>,
val outputs: List<TransactionState<*>>,
/** Arbitrary data passed to the program of each input state. */
val commands: List<AuthenticatedObject<CommandData>>,
/** The hash of the original serialised WireTransaction */
override val id: SecureHash
override val id: SecureHash,
val signers: List<PublicKey>,
val type: TransactionType
) : NamedByHash {
@Suppress("UNCHECKED_CAST")
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as T, StateRef(id, index))
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
}

View File

@ -53,7 +53,7 @@ interface ServiceHub {
*
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction
*/
fun loadState(stateRef: StateRef): ContractState {
fun loadState(stateRef: StateRef): TransactionState<*> {
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return definingTx.tx.outputs[stateRef.index]
}

View File

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

View File

@ -1,8 +1,7 @@
package com.r3corda.core.node.services
import com.r3corda.core.crypto.Party
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
/**
@ -11,7 +10,7 @@ import com.r3corda.core.crypto.SecureHash
*/
interface UniquenessProvider {
/** Commits all input states of the given transaction */
fun commit(tx: WireTransaction, callerIdentity: Party)
fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party)
/** Specifies the consuming transaction for every conflicting state */
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)

View File

@ -29,6 +29,7 @@ import java.io.ObjectOutputStream
import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
import java.nio.file.Path
import java.security.PublicKey
import java.time.Instant
import java.util.*
import javax.annotation.concurrent.ThreadSafe
@ -232,6 +233,8 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
kryo.writeClassAndObject(output, obj.attachments)
kryo.writeClassAndObject(output, obj.outputs)
kryo.writeClassAndObject(output, obj.commands)
kryo.writeClassAndObject(output, obj.signers)
kryo.writeClassAndObject(output, obj.type)
}
@Suppress("UNCHECKED_CAST")
@ -258,10 +261,12 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
} else javaClass.classLoader
kryo.useClassLoader(classLoader) {
val outputs = kryo.readClassAndObject(input) as List<ContractState>
val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>>
val commands = kryo.readClassAndObject(input) as List<Command>
val signers = kryo.readClassAndObject(input) as List<PublicKey>
val transactionType = kryo.readClassAndObject(input) as TransactionType
return WireTransaction(inputs, attachmentHashes, outputs, commands)
return WireTransaction(inputs, attachmentHashes, outputs, commands, signers, transactionType)
}
}
}
@ -343,6 +348,7 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
// Work around a bug in Kryo handling nested generics
register(Issued::class.java, ImmutableClassSerializer(Issued::class))
register(TransactionState::class.java, ImmutableClassSerializer(TransactionState::class))
noReferencesWithin<WireTransaction>()
}

View File

@ -4,12 +4,12 @@ package com.r3corda.core.testing
import com.google.common.base.Throwables
import com.google.common.net.HostAndPort
import com.r3corda.core.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.*
import com.r3corda.core.serialization.serialize
import com.r3corda.core.node.services.testing.MockIdentityService
import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.seconds
import com.r3corda.core.serialization.serialize
import java.net.ServerSocket
import java.security.KeyPair
import java.security.PublicKey
@ -96,20 +96,22 @@ fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
//
// TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block
class LabeledOutput(val label: String?, val state: ContractState) {
class LabeledOutput(val label: String?, val state: TransactionState<*>) {
override fun toString() = state.toString() + (if (label != null) " ($label)" else "")
override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state)
override fun hashCode(): Int = state.hashCode()
}
infix fun ContractState.label(label: String) = LabeledOutput(label, this)
infix fun TransactionState<*>.label(label: String) = LabeledOutput(label, this)
abstract class AbstractTransactionForTest {
protected val attachments = ArrayList<SecureHash>()
protected val outStates = ArrayList<LabeledOutput>()
protected val commands = ArrayList<Command>()
protected val signers = LinkedHashSet<PublicKey>()
protected val type = TransactionType.General()
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, s()).apply { outStates.add(this) }
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, TransactionState(s(), DUMMY_NOTARY)).apply { outStates.add(this) }
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
return commands.map { AuthenticatedObject(it.signers, it.signers.mapNotNull { MOCK_IDENTITY_SERVICE.partyFromKey(it) }, it.value) }
@ -121,7 +123,7 @@ abstract class AbstractTransactionForTest {
fun arg(vararg key: PublicKey, c: () -> CommandData) {
val keys = listOf(*key)
commands.add(Command(c(), keys))
addCommand(Command(c(), keys))
}
fun timestamp(time: Instant) {
@ -130,7 +132,12 @@ abstract class AbstractTransactionForTest {
}
fun timestamp(data: TimestampCommand) {
commands.add(Command(data, DUMMY_NOTARY.owningKey))
addCommand(Command(data, DUMMY_NOTARY.owningKey))
}
fun addCommand(cmd: Command) {
signers.addAll(cmd.signers)
commands.add(cmd)
}
// Forbid patterns like: transaction { ... transaction { ... } }
@ -150,12 +157,15 @@ sealed class LastLineShouldTestForAcceptOrFailure {
// Corresponds to the args to Contract.verify
open class TransactionForTest : AbstractTransactionForTest() {
private val inStates = arrayListOf<ContractState>()
fun input(s: () -> ContractState) = inStates.add(s())
private val inStates = arrayListOf<TransactionState<ContractState>>()
fun input(s: () -> ContractState) {
signers.add(DUMMY_NOTARY.owningKey)
inStates.add(TransactionState(s(), DUMMY_NOTARY))
}
protected fun runCommandsAndVerify(time: Instant) {
val cmds = commandsToAuthenticatedObjects()
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256())
val tx = TransactionForVerification(inStates, outStates.map { it.state }, emptyList(), cmds, SecureHash.Companion.randomSHA256(), signers.toList(), type)
tx.verify()
}
@ -210,6 +220,9 @@ open class TransactionForTest : AbstractTransactionForTest() {
tx.inStates.addAll(inStates)
tx.outStates.addAll(outStates)
tx.commands.addAll(commands)
tx.signers.addAll(tx.inStates.map { it.notary.owningKey })
tx.signers.addAll(commands.flatMap { it.signers })
return tx.body()
}
@ -240,16 +253,24 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
private val inStates = ArrayList<StateRef>()
fun input(label: String) {
val notaryKey = label.output.notary.owningKey
signers.add(notaryKey)
inStates.add(label.outputRef)
}
fun toWireTransaction() = WireTransaction(inStates, attachments, outStates.map { it.state }, commands)
fun toWireTransaction() = WireTransaction(inStates, attachments, outStates.map { it.state }, commands, signers.toList(), type)
}
val String.output: T get() = labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found")
val String.output: TransactionState<T>
get() =
labelToOutputs[this] ?: throw IllegalArgumentException("State with label '$this' was not found")
val String.outputRef: StateRef get() = labelToRefs[this] ?: throw IllegalArgumentException("Unknown label \"$this\"")
fun <C : ContractState> lookup(label: String) = StateAndRef(label.output as C, label.outputRef)
fun <C : ContractState> lookup(label: String): StateAndRef<C> {
val output = label.output
val newOutput = TransactionState(output.data as C, output.notary)
return StateAndRef(newOutput, label.outputRef)
}
private inner class InternalWireTransactionDSL : WireTransactionDSL() {
fun finaliseAndInsertLabels(): WireTransaction {
@ -257,8 +278,8 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
for ((index, labelledState) in outStates.withIndex()) {
if (labelledState.label != null) {
labelToRefs[labelledState.label] = StateRef(wtx.id, index)
if (stateType.isInstance(labelledState.state)) {
labelToOutputs[labelledState.label] = labelledState.state as T
if (stateType.isInstance(labelledState.state.data)) {
labelToOutputs[labelledState.label] = labelledState.state as TransactionState<T>
}
outputsToLabels[labelledState.state] = labelledState.label
}
@ -269,20 +290,20 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
private val rootTxns = ArrayList<WireTransaction>()
private val labelToRefs = HashMap<String, StateRef>()
private val labelToOutputs = HashMap<String, T>()
private val outputsToLabels = HashMap<ContractState, String>()
private val labelToOutputs = HashMap<String, TransactionState<T>>()
private val outputsToLabels = HashMap<TransactionState<*>, String>()
fun labelForState(state: T): String? = outputsToLabels[state]
fun labelForState(output: TransactionState<*>): String? = outputsToLabels[output]
inner class Roots {
fun transaction(vararg outputStates: LabeledOutput) {
val outs = outputStates.map { it.state }
val wtx = WireTransaction(emptyList(), emptyList(), outs, emptyList())
val wtx = WireTransaction(emptyList(), emptyList(), outs, emptyList(), emptyList(), TransactionType.General())
for ((index, state) in outputStates.withIndex()) {
val label = state.label!!
labelToRefs[label] = StateRef(wtx.id, index)
outputsToLabels[state.state] = label
labelToOutputs[label] = state.state as T
labelToOutputs[label] = state.state as TransactionState<T>
}
rootTxns.add(wtx)
}
@ -357,7 +378,7 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
fun signAll(txnsToSign: List<WireTransaction> = txns, vararg extraKeys: KeyPair): List<SignedTransaction> {
return txnsToSign.map { wtx ->
val allPubKeys = wtx.commands.flatMap { it.signers }.toMutableSet()
val allPubKeys = wtx.signers.toMutableSet()
val bits = wtx.serialize()
require(bits == wtx.serialized)
val sigs = ArrayList<DigitalSignature.WithKey>()

View File

@ -163,7 +163,7 @@ object NotaryProtocol {
private fun commitInputStates(tx: WireTransaction, reqIdentity: Party) {
try {
uniquenessProvider.commit(tx, reqIdentity)
uniquenessProvider.commit(tx.inputs, tx.id, reqIdentity)
} catch (e: UniquenessException) {
val conflictData = e.error.serialize()
val signedConflict = SignedData(conflictData, sign(conflictData))

View File

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

View File

@ -0,0 +1,260 @@
package protocols
import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.signWithECDSA
import com.r3corda.core.messaging.SingleMessageRecipient
import com.r3corda.core.node.NodeInfo
import com.r3corda.core.protocols.ProtocolLogic
import com.r3corda.core.random63BitValue
import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.protocols.AbstractRequestMessage
import com.r3corda.protocols.NotaryProtocol
import com.r3corda.protocols.ResolveTransactionsProtocol
import java.security.PublicKey
/**
* A protocol to be used for changing a state's Notary. This is required since all input states to a transaction
* must point to the same notary.
*
* The [Instigator] assembles the transaction for notary replacement and sends out change proposals to all participants
* ([Acceptor]) of that state. If participants agree to the proposed change, they each sign the transaction.
* Finally, [Instigator] sends the transaction containing all signatures back to each participant so they can record it and
* use the new updated state for future transactions.
*/
object NotaryChangeProtocol {
val TOPIC_INITIATE = "platform.notary.change.initiate"
val TOPIC_CHANGE = "platform.notary.change.execute"
data class Proposal(val stateRef: StateRef,
val newNotary: Party,
val stx: SignedTransaction)
class Handshake(val sessionIdForSend: Long,
replyTo: SingleMessageRecipient,
replySessionId: Long) : AbstractRequestMessage(replyTo, replySessionId)
class Instigator<T : ContractState>(val originalState: StateAndRef<T>,
val newNotary: Party,
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<StateAndRef<T>>() {
companion object {
object SIGNING : ProgressTracker.Step("Requesting signatures from other parties")
object NOTARY : ProgressTracker.Step("Requesting current Notary signature")
fun tracker() = ProgressTracker(SIGNING, NOTARY)
}
@Suspendable
override fun call(): StateAndRef<T> {
val (stx, participants) = assembleTx()
progressTracker.currentStep = SIGNING
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
val me = listOf(myKey)
val signatures = if (participants == me) {
listOf(getNotarySignature(stx.tx))
} else {
collectSignatures(participants - me, stx)
}
val finalTx = stx + signatures
serviceHub.recordTransactions(listOf(finalTx))
return finalTx.tx.outRef(0)
}
private fun assembleTx(): Pair<SignedTransaction, List<PublicKey>> {
val state = originalState.state
val newState = state.withNewNotary(newNotary)
val participants = state.data.participants
val tx = TransactionType.NotaryChange.Builder().withItems(originalState, newState)
tx.signWith(serviceHub.storageService.myLegalIdentityKey)
val stx = tx.toSignedTransaction(false)
return Pair(stx, participants)
}
@Suspendable
private fun collectSignatures(participants: List<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
val sessions = mutableMapOf<NodeInfo, Long>()
val participantSignatures = participants.map {
val participantNode = serviceHub.networkMapCache.getNodeByPublicKey(it) ?:
throw IllegalStateException("Participant $it to state $originalState not found on the network")
val sessionIdForSend = random63BitValue()
sessions[participantNode] = sessionIdForSend
getParticipantSignature(participantNode, stx, sessionIdForSend)
}
val allSignatures = participantSignatures + getNotarySignature(stx.tx)
sessions.forEach { send(TOPIC_CHANGE, it.key.address, it.value, allSignatures) }
return allSignatures
}
@Suspendable
private fun getParticipantSignature(node: NodeInfo, stx: SignedTransaction, sessionIdForSend: Long): DigitalSignature.WithKey {
val sessionIdForReceive = random63BitValue()
val proposal = Proposal(originalState.ref, newNotary, stx)
val handshake = Handshake(sessionIdForSend, serviceHub.networkService.myAddress, sessionIdForReceive)
sendAndReceive<Unit>(TOPIC_INITIATE, node.address, 0, sessionIdForReceive, handshake)
val response = sendAndReceive<Result>(TOPIC_CHANGE, node.address, sessionIdForSend, sessionIdForReceive, proposal)
val participantSignature = response.validate {
if (it.sig == null) throw NotaryChangeException(it.error!!)
else {
check(it.sig.by == node.identity.owningKey) { "Not signed by the required participant" }
it.sig.verifyWithECDSA(stx.txBits)
it.sig
}
}
return participantSignature
}
@Suspendable
private fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol.Client(wtx))
}
}
class Acceptor(val otherSide: SingleMessageRecipient,
val sessionIdForSend: Long,
val sessionIdForReceive: Long,
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<Unit>() {
companion object {
object VERIFYING : ProgressTracker.Step("Verifying Notary change proposal")
object APPROVING : ProgressTracker.Step("Notary change approved")
object REJECTING : ProgressTracker.Step("Notary change rejected")
fun tracker() = ProgressTracker(VERIFYING, APPROVING, REJECTING)
}
@Suspendable
override fun call() {
progressTracker.currentStep = VERIFYING
val proposal = receive<Proposal>(TOPIC_CHANGE, sessionIdForReceive).validate { it }
try {
verifyProposal(proposal)
verifyTx(proposal.stx)
} catch(e: Exception) {
// TODO: catch only specific exceptions. However, there are numerous validation exceptions
// that might occur (tx validation/resolution, invalid proposal). Need to rethink how
// we manage exceptions and maybe introduce some platform exception hierarchy
val myIdentity = serviceHub.storageService.myLegalIdentity
val state = proposal.stateRef
val reason = NotaryChangeRefused(myIdentity, state, e.message)
reject(reason)
return
}
approve(proposal.stx)
}
@Suspendable
private fun approve(stx: SignedTransaction) {
progressTracker.currentStep = APPROVING
val mySignature = sign(stx)
val response = Result.noError(mySignature)
val swapSignatures = sendAndReceive<List<DigitalSignature.WithKey>>(TOPIC_CHANGE, otherSide, sessionIdForSend, sessionIdForReceive, response)
val allSignatures = swapSignatures.validate { signatures ->
signatures.forEach { it.verifyWithECDSA(stx.txBits) }
signatures
}
val finalTx = stx + allSignatures
finalTx.verify()
serviceHub.recordTransactions(listOf(finalTx))
}
@Suspendable
private fun reject(e: NotaryChangeRefused) {
progressTracker.currentStep = REJECTING
val response = Result.withError(e)
send(TOPIC_CHANGE, otherSide, sessionIdForSend, response)
}
/**
* Check the notary change proposal.
*
* For example, if the proposed new notary has the same behaviour (e.g. both are non-validating)
* and is also in a geographically convenient location we can just automatically approve the change.
* TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal
*/
@Suspendable
private fun verifyProposal(proposal: NotaryChangeProtocol.Proposal) {
val newNotary = proposal.newNotary
val isNotary = serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary }
require(isNotary) { "The proposed node $newNotary does not run a Notary service " }
val state = proposal.stateRef
val proposedTx = proposal.stx.tx
require(proposedTx.inputs.contains(state)) { "The proposed state $state is not in the proposed transaction inputs" }
// An example requirement
val blacklist = listOf("Evil Notary")
require(!blacklist.contains(newNotary.name)) { "The proposed new notary $newNotary is not trusted by the party" }
}
@Suspendable
private fun verifyTx(stx: SignedTransaction) {
checkMySignatureRequired(stx.tx)
checkDependenciesValid(stx)
checkValid(stx)
}
private fun checkMySignatureRequired(tx: WireTransaction) {
// TODO: use keys from the keyManagementService instead
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
require(tx.signers.contains(myKey)) { "Party is not a participant for any of the input states of transaction ${tx.id}" }
}
@Suspendable
private fun checkDependenciesValid(stx: SignedTransaction) {
val dependencyTxIDs = stx.tx.inputs.map { it.txhash }.toSet()
subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherSide))
}
private fun checkValid(stx: SignedTransaction) {
val ltx = stx.tx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
serviceHub.verifyTransaction(ltx)
}
private fun sign(stx: SignedTransaction): DigitalSignature.WithKey {
val myKeyPair = serviceHub.storageService.myLegalIdentityKey
return myKeyPair.signWithECDSA(stx.txBits)
}
}
// TODO: similar classes occur in other places (NotaryProtocol), need to consolidate
data class Result private constructor(val sig: DigitalSignature.WithKey?, val error: NotaryChangeRefused?) {
companion object {
fun withError(error: NotaryChangeRefused) = Result(null, error)
fun noError(sig: DigitalSignature.WithKey) = Result(sig, null)
}
}
}
/** Thrown when a participant refuses to change the notary of the state */
class NotaryChangeRefused(val identity: Party, val state: StateRef, val cause: String?) {
override fun toString() = "A participant $identity refused to change the notary of state $state"
}
class NotaryChangeException(val error: NotaryChangeRefused) : Exception() {
override fun toString() = "${super.toString()}: Notary change failed - ${error.toString()}"
}

View File

@ -26,13 +26,13 @@ class TransactionGraphSearchTests {
* @param signer signer for the two transactions and their commands.
*/
fun buildTransactions(command: CommandData, signer: KeyPair): GraphTransactionStorage {
val originTx = TransactionBuilder().apply {
addOutputState(DummyContract.State(random31BitValue(), DUMMY_NOTARY))
val originTx = TransactionType.General.Builder().apply {
addOutputState(DummyContract.State(random31BitValue()), DUMMY_NOTARY)
addCommand(command, signer.public)
signWith(signer)
}.toSignedTransaction(false)
val inputTx = TransactionBuilder().apply {
addInputState(originTx.tx.outRef<DummyContract.State>(0).ref)
val inputTx = TransactionType.General.Builder().apply {
addInputState(originTx.tx.outRef<DummyContract.State>(0))
signWith(signer)
}.toSignedTransaction(false)
return GraphTransactionStorage(originTx, inputTx)

View File

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

View File

@ -13,6 +13,7 @@ import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.net.URLClassLoader
import java.security.PublicKey
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertEquals
@ -32,16 +33,17 @@ class AttachmentClassLoaderTests {
}
class AttachmentDummyContract : Contract {
class State(val magicNumber: Int = 0,
override val notary: Party) : ContractState {
data class State(val magicNumber: Int = 0) : ContractState {
override val contract = ATTACHMENT_TEST_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf()
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Always accepts.
}
@ -49,8 +51,8 @@ class AttachmentClassLoaderTests {
override val legalContractReference: SecureHash = SecureHash.sha256("")
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber, notary)
return TransactionBuilder().withItems(state, Command(Commands.Create(), owner.party.owningKey))
val state = State(magicNumber)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
}
@ -215,7 +217,7 @@ class AttachmentClassLoaderTests {
val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0] as AttachmentDummyContract.State).magicNumber)
assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
}
@Test
@ -245,8 +247,8 @@ class AttachmentClassLoaderTests {
val copiedWireTransaction = bytes.deserialize(kryo2)
assertEquals(1, copiedWireTransaction.outputs.size)
val contract2 = copiedWireTransaction.outputs[0].contract as DummyContractBackdoor
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0]))
val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor
assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data))
}
@Test

View File

@ -5,6 +5,7 @@ import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.Wallet
import com.r3corda.core.testing.DUMMY_NOTARY
import org.junit.Test
import java.security.PublicKey
import kotlin.test.assertEquals
@ -12,14 +13,15 @@ class WalletUpdateTests {
object DummyContract : Contract {
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
}
override val legalContractReference: SecureHash = SecureHash.sha256("")
}
private class DummyState : ContractState {
override val notary = DUMMY_NOTARY
override val participants: List<PublicKey>
get() = emptyList()
override val contract = WalletUpdateTests.DummyContract
}
@ -29,11 +31,11 @@ class WalletUpdateTests {
private val stateRef3 = StateRef(SecureHash.randomSHA256(), 3)
private val stateRef4 = StateRef(SecureHash.randomSHA256(), 4)
private val stateAndRef0 = StateAndRef<DummyState>(DummyState(), stateRef0)
private val stateAndRef1 = StateAndRef<DummyState>(DummyState(), stateRef1)
private val stateAndRef2 = StateAndRef<DummyState>(DummyState(), stateRef2)
private val stateAndRef3 = StateAndRef<DummyState>(DummyState(), stateRef3)
private val stateAndRef4 = StateAndRef<DummyState>(DummyState(), stateRef4)
private val stateAndRef0 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef0)
private val stateAndRef1 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef1)
private val stateAndRef2 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef2)
private val stateAndRef3 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef3)
private val stateAndRef4 = StateAndRef(TransactionState(DummyState(), DUMMY_NOTARY), stateRef4)
@Test
fun `nothing plus nothing is nothing`() {

View File

@ -1,7 +1,6 @@
package com.r3corda.core.serialization
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.testing.MockStorageService
import com.r3corda.core.seconds
@ -11,7 +10,7 @@ import org.junit.Test
import java.security.PublicKey
import java.security.SecureRandom
import java.security.SignatureException
import java.util.Currency
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@ -21,15 +20,17 @@ class TransactionSerializationTests {
class TestCash : Contract {
override val legalContractReference = SecureHash.sha256("TestCash")
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
}
data class State(
val deposit: PartyAndReference,
val amount: Amount<Currency>,
override val owner: PublicKey,
override val notary: Party) : OwnableState {
override val owner: PublicKey) : OwnableState {
override val contract: Contract = TEST_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
}
interface Commands : CommandData {
@ -39,24 +40,27 @@ 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.
val depositRef = MINI_CORP.ref(1)
val outputState = TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1, DUMMY_NOTARY)
val changeState = TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public, DUMMY_NOTARY)
val fakeStateRef = generateStateRef()
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public), DUMMY_NOTARY)
lateinit var tx: TransactionBuilder
@Before
fun setup() {
tx = TransactionBuilder().withItems(
fakeStateRef, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
tx = TransactionType.General.Builder().withItems(
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
)
}
@Test
fun signWireTX() {
tx.signWith(DUMMY_NOTARY_KEY)
tx.signWith(TestUtils.keypair)
val signedTX = tx.toSignedTransaction()
@ -78,6 +82,7 @@ class TransactionSerializationTests {
}
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_NOTARY_KEY)
val signedTX = tx.toSignedTransaction()
// Cannot construct with an empty sigs list.
@ -87,8 +92,9 @@ class TransactionSerializationTests {
// If the signature was replaced in transit, we don't like it.
assertFailsWith(SignatureException::class) {
val tx2 = TransactionBuilder().withItems(fakeStateRef, outputState, changeState,
val tx2 = TransactionType.General.Builder().withItems(inputState, outputState, changeState,
Command(TestCash.Commands.Move(), TestUtils.keypair2.public))
tx2.signWith(DUMMY_NOTARY_KEY)
tx2.signWith(TestUtils.keypair2)
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()

View File

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

View File

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

View File

@ -16,6 +16,7 @@ import com.r3corda.core.seconds
import com.r3corda.core.serialization.deserialize
import com.r3corda.core.serialization.serialize
import com.r3corda.node.api.APIServer
import com.r3corda.node.services.NotaryChangeService
import com.r3corda.node.services.api.AcceptsFileUpload
import com.r3corda.node.services.api.CheckpointStorage
import com.r3corda.node.services.api.MonitoringService
@ -139,6 +140,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
// This object doesn't need to be referenced from this class because it registers handlers on the network
// service and so that keeps it from being collected.
DataVendingService(net, storage)
NotaryChangeService(net, smm)
buildAdvertisedServices()

View File

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

View File

@ -1,7 +1,10 @@
package com.r3corda.node.internal.testing
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.DummyContract
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.TransactionState
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.crypto.Party
import com.r3corda.core.seconds
import com.r3corda.core.testing.DUMMY_NOTARY
@ -10,20 +13,34 @@ import com.r3corda.node.internal.AbstractNode
import java.time.Instant
import java.util.*
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
fun issueState(node: AbstractNode): StateAndRef<*> {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), DUMMY_NOTARY)
tx.signWith(node.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
node.services.recordTransactions(listOf(stx))
return StateRef(stx.id, 0)
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
}
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateRef {
fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<DummyContract.MultiOwnerState> {
val state = TransactionState(DummyContract.MultiOwnerState(0,
listOf(nodeA.info.identity.owningKey, nodeB.info.identity.owningKey)), DUMMY_NOTARY)
val tx = TransactionType.NotaryChange.Builder().withItems(state)
tx.signWith(nodeA.storage.myLegalIdentityKey)
tx.signWith(nodeB.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
nodeA.services.recordTransactions(listOf(stx))
nodeB.services.recordTransactions(listOf(stx))
val stateAndRef = StateAndRef(state, StateRef(stx.id, 0))
return stateAndRef
}
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
tx.setTime(Instant.now(), notary, 30.seconds)
tx.signWith(node.storage.myLegalIdentityKey)
val stx = tx.toSignedTransaction(false)
node.services.recordTransactions(listOf(stx))
return StateRef(stx.id, 0)
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
}

View File

@ -3,7 +3,7 @@ package com.r3corda.node.internal.testing
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.crypto.Party
import com.r3corda.core.node.ServiceHub
import com.r3corda.core.serialization.OpaqueBytes
@ -34,7 +34,7 @@ object WalletFiller {
// this field as there's no other database or source of truth we need to sync with.
val depositRef = myIdentity.ref(ref)
val issuance = TransactionBuilder()
val issuance = TransactionType.General.Builder()
val freshKey = services.keyManagementService.freshKey()
cash.generateIssue(issuance, Amount(pennies, Issued(depositRef, howMuch.token)), freshKey.public, notary)
issuance.signWith(myKey)

View File

@ -0,0 +1,27 @@
package com.r3corda.node.services
import com.r3corda.core.messaging.MessagingService
import com.r3corda.core.messaging.SingleMessageRecipient
import com.r3corda.node.services.api.AbstractNodeService
import com.r3corda.node.services.statemachine.StateMachineManager
import protocols.NotaryChangeProtocol
/**
* A service that monitors the network for requests for changing the notary of a state,
* and immediately runs the [NotaryChangeProtocol] if the auto-accept criteria are met.
*/
class NotaryChangeService(net: MessagingService, val smm: StateMachineManager) : AbstractNodeService(net) {
init {
addMessageHandler(NotaryChangeProtocol.TOPIC_INITIATE,
{ req: NotaryChangeProtocol.Handshake -> handleChangeNotaryRequest(req) }
)
}
private fun handleChangeNotaryRequest(req: NotaryChangeProtocol.Handshake) {
val protocol = NotaryChangeProtocol.Acceptor(
req.replyTo as SingleMessageRecipient,
req.sessionID!!,
req.sessionIdForSend)
smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
}
}

View File

@ -2,8 +2,8 @@ package com.r3corda.node.services.transactions
import com.r3corda.core.ThreadBox
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.UniquenessException
import com.r3corda.core.node.services.UniquenessProvider
import java.util.*
@ -15,12 +15,10 @@ class InMemoryUniquenessProvider() : UniquenessProvider {
/** For each input state store the consuming transaction information */
private val committedStates = ThreadBox(HashMap<StateRef, UniquenessProvider.ConsumingTx>())
// TODO: the uniqueness provider shouldn't be able to see all tx outputs and commands
override fun commit(tx: WireTransaction, callerIdentity: Party) {
val inputStates = tx.inputs
override fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party) {
committedStates.locked {
val conflictingStates = LinkedHashMap<StateRef, UniquenessProvider.ConsumingTx>()
for (inputState in inputStates) {
for (inputState in states) {
val consumingTx = get(inputState)
if (consumingTx != null) conflictingStates[inputState] = consumingTx
}
@ -28,8 +26,8 @@ class InMemoryUniquenessProvider() : UniquenessProvider {
val conflict = UniquenessProvider.Conflict(conflictingStates)
throw UniquenessException(conflict)
} else {
inputStates.forEachIndexed { i, stateRef ->
put(stateRef, UniquenessProvider.ConsumingTx(tx.id, i, callerIdentity))
states.forEachIndexed { i, stateRef ->
put(stateRef, UniquenessProvider.ConsumingTx(txId, i, callerIdentity))
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -4,13 +4,15 @@ import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.testing.`with notary`
import com.r3corda.core.bd
import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.Fix
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.testing.ALICE_PUBKEY
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.MEGA_CORP
import com.r3corda.core.testing.MEGA_CORP_KEY
import com.r3corda.core.utilities.BriefLogFormatter
@ -102,7 +104,7 @@ class NodeInterestRatesTest {
val (n1, n2) = net.createTwoNodes()
n2.interestRatesService.oracle.knownFixes = TEST_DATA
val tx = TransactionBuilder()
val tx = TransactionType.General.Builder()
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val protocol = RatesFixProtocol(tx, n2.info, fixOf, "0.675".bd, "0.1".bd)
BriefLogFormatter.initVerbose("rates")
@ -117,5 +119,5 @@ class NodeInterestRatesTest {
assertEquals("0.678".bd, fix.value)
}
private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY))
private fun makeTX() = TransactionType.General.Builder().withItems(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY)
}

View File

@ -3,7 +3,7 @@ package com.r3corda.node.services
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.`issued by`
import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.contracts.USD
import com.r3corda.core.contracts.verifyToLedgerTransaction
import com.r3corda.core.node.ServiceHub
@ -51,14 +51,14 @@ class NodeWalletServiceTest {
val w = wallet.currentWallet
assertEquals(3, w.states.size)
val state = w.states[0].state as Cash.State
val state = w.states[0].state.data as Cash.State
val myIdentity = services.storageService.myLegalIdentity
val myPartyRef = myIdentity.ref(ref)
assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount)
assertEquals(ALICE_PUBKEY, state.owner)
assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state as Cash.State).amount)
assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state as Cash.State).amount)
assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state.data as Cash.State).amount)
assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state.data as Cash.State).amount)
}
@Test
@ -67,22 +67,24 @@ class NodeWalletServiceTest {
// A tx that sends us money.
val freshKey = services.keyManagementService.freshKey()
val usefulTX = TransactionBuilder().apply {
val usefulTX = TransactionType.General.Builder().apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY)
}.toSignedTransaction()
val myOutput = usefulTX.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments).outRef<Cash.State>(0)
// A tx that spends our money.
val spendTX = TransactionBuilder().apply {
val spendTX = TransactionType.General.Builder().apply {
Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput))
signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction()
// A tx that doesn't send us anything.
val irrelevantTX = TransactionBuilder().apply {
val irrelevantTX = TransactionType.General.Builder().apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction()
assertNull(wallet.cashBalances[USD])

View File

@ -1,7 +1,7 @@
package com.r3corda.node.services
import com.r3corda.core.contracts.TimestampCommand
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.seconds
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
@ -38,7 +38,7 @@ class NotaryServiceTests {
@Test fun `should sign a unique transaction with a valid timestamp`() {
val inputState = issueState(clientNode)
val tx = TransactionBuilder().withItems(inputState)
val tx = TransactionType.General.Builder().withItems(inputState)
tx.setTime(Instant.now(), DUMMY_NOTARY, 30.seconds)
val wtx = tx.toWireTransaction()
@ -52,7 +52,7 @@ class NotaryServiceTests {
@Test fun `should sign a unique transaction without a timestamp`() {
val inputState = issueState(clientNode)
val wtx = TransactionBuilder().withItems(inputState).toWireTransaction()
val wtx = TransactionType.General.Builder().withItems(inputState).toWireTransaction()
val protocol = NotaryProtocol.Client(wtx)
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
@ -64,7 +64,7 @@ class NotaryServiceTests {
@Test fun `should report error for transaction with an invalid timestamp`() {
val inputState = issueState(clientNode)
val tx = TransactionBuilder().withItems(inputState)
val tx = TransactionType.General.Builder().withItems(inputState)
tx.setTime(Instant.now().plusSeconds(3600), DUMMY_NOTARY, 30.seconds)
val wtx = tx.toWireTransaction()
@ -79,7 +79,7 @@ class NotaryServiceTests {
@Test fun `should report error for transaction with more than one timestamp`() {
val inputState = issueState(clientNode)
val tx = TransactionBuilder().withItems(inputState)
val tx = TransactionType.General.Builder().withItems(inputState)
val timestamp = TimestampCommand(Instant.now(), 30.seconds)
tx.addCommand(timestamp, DUMMY_NOTARY.owningKey)
tx.addCommand(timestamp, DUMMY_NOTARY.owningKey)
@ -96,7 +96,7 @@ class NotaryServiceTests {
@Test fun `should report conflict for a duplicate transaction`() {
val inputState = issueState(clientNode)
val wtx = TransactionBuilder().withItems(inputState).toWireTransaction()
val wtx = TransactionType.General.Builder().withItems(inputState).toWireTransaction()
val firstSpend = NotaryProtocol.Client(wtx)
val secondSpend = NotaryProtocol.Client(wtx)

View File

@ -1,6 +1,6 @@
package com.r3corda.node.services
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.UniquenessException
import com.r3corda.core.testing.MEGA_CORP
import com.r3corda.core.testing.generateStateRef
@ -11,27 +11,27 @@ import kotlin.test.assertFailsWith
class UniquenessProviderTests {
val identity = MEGA_CORP
val txID = SecureHash.randomSHA256()
@Test fun `should commit a transaction with unused inputs without exception`() {
val provider = InMemoryUniquenessProvider()
val inputState = generateStateRef()
val tx = TransactionBuilder().withItems(inputState).toWireTransaction()
provider.commit(tx, identity)
provider.commit(listOf(inputState), txID, identity)
}
@Test fun `should report a conflict for a transaction with previously used inputs`() {
val provider = InMemoryUniquenessProvider()
val inputState = generateStateRef()
val tx1 = TransactionBuilder().withItems(inputState).toWireTransaction()
provider.commit(tx1, identity)
val inputs = listOf(inputState)
provider.commit(inputs, txID, identity)
val tx2 = TransactionBuilder().withItems(inputState).toWireTransaction()
val ex = assertFailsWith<UniquenessException> { provider.commit(tx2, identity) }
val ex = assertFailsWith<UniquenessException> { provider.commit(inputs, txID, identity) }
val consumingTx = ex.error.stateHistory[inputState]!!
assertEquals(consumingTx.id, tx1.id)
assertEquals(consumingTx.inputIndex, tx1.inputs.indexOf(inputState))
assertEquals(consumingTx.id, txID)
assertEquals(consumingTx.inputIndex, inputs.indexOf(inputState))
assertEquals(consumingTx.requestingParty, identity)
}
}

View File

@ -1,6 +1,6 @@
package com.r3corda.node.services
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import com.r3corda.node.internal.testing.MockNetwork
@ -34,7 +34,7 @@ class ValidatingNotaryServiceTests {
@Test fun `should report error for invalid transaction dependency`() {
val inputState = issueInvalidState(clientNode)
val wtx = TransactionBuilder().withItems(inputState).toWireTransaction()
val wtx = TransactionType.General.Builder().withItems(inputState).toWireTransaction()
val protocol = NotaryProtocol.Client(wtx)
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)

View File

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

View File

@ -0,0 +1,93 @@
package node.services
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import com.r3corda.node.internal.testing.MockNetwork
import com.r3corda.node.internal.testing.issueMultiPartyState
import com.r3corda.node.internal.testing.issueState
import com.r3corda.node.services.network.NetworkMapService
import com.r3corda.node.services.transactions.SimpleNotaryService
import org.junit.Before
import org.junit.Test
import protocols.NotaryChangeException
import protocols.NotaryChangeProtocol
import protocols.NotaryChangeProtocol.Instigator
import protocols.NotaryChangeRefused
import java.util.concurrent.ExecutionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class NotaryChangeTests {
lateinit var net: MockNetwork
lateinit var oldNotaryNode: MockNetwork.MockNode
lateinit var newNotaryNode: MockNetwork.MockNode
lateinit var clientNodeA: MockNetwork.MockNode
lateinit var clientNodeB: MockNetwork.MockNode
@Before
fun setup() {
net = MockNetwork()
oldNotaryNode = net.createNode(
legalName = DUMMY_NOTARY.name,
keyPair = DUMMY_NOTARY_KEY,
advertisedServices = *arrayOf(NetworkMapService.Type, SimpleNotaryService.Type))
clientNodeA = net.createNode(networkMapAddress = oldNotaryNode.info)
clientNodeB = net.createNode(networkMapAddress = oldNotaryNode.info)
newNotaryNode = net.createNode(networkMapAddress = oldNotaryNode.info, advertisedServices = SimpleNotaryService.Type)
net.runNetwork() // Clear network map registration messages
}
@Test
fun `should change notary for a state with single participant`() {
val state = issueState(clientNodeA)
val newNotary = newNotaryNode.info.identity
val protocol = Instigator(state, newNotary)
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
net.runNetwork()
val newState = future.get()
assertEquals(newState.state.notary, newNotary)
}
@Test
fun `should change notary for a state with multiple participants`() {
val state = issueMultiPartyState(clientNodeA, clientNodeB)
val newNotary = newNotaryNode.info.identity
val protocol = Instigator(state, newNotary)
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
net.runNetwork()
val newState = future.get()
assertEquals(newState.state.notary, newNotary)
val loadedStateA = clientNodeA.services.loadState(newState.ref)
val loadedStateB = clientNodeB.services.loadState(newState.ref)
assertEquals(loadedStateA, loadedStateB)
}
@Test
fun `should throw when a participant refuses to change Notary`() {
val state = issueMultiPartyState(clientNodeA, clientNodeB)
val newEvilNotary = Party("Evil Notary", generateKeyPair().public)
val protocol = Instigator(state, newEvilNotary)
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
net.runNetwork()
val ex = assertFailsWith(ExecutionException::class) { future.get() }
val error = (ex.cause as NotaryChangeException).error
assertTrue(error is NotaryChangeRefused)
}
// TODO: Add more test cases once we have a general protocol/service exception handling mechanism:
// - A participant is offline/can't be found on the network
// - The requesting party is not a participant
// - The requesting party wants to change additional state fields
// - Multiple states in a single "notary change" transaction
// - Transaction contains additional states and commands with business logic
}

View File

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

View File

@ -356,7 +356,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
// Sign it as ourselves.
tx.signWith(keyPair)
// Get the notary to sign it, thus committing the outputs.
// Get the notary to sign the timestamp
val notarySig = subProtocol(NotaryProtocol.Client(tx.toWireTransaction()))
tx.addSignatureUnchecked(notarySig)
@ -369,7 +369,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
// Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works.
val move: SignedTransaction = run {
val builder = TransactionBuilder()
val builder = TransactionType.General.Builder()
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy)
builder.signWith(keyPair)
builder.addSignatureUnchecked(subProtocol(NotaryProtocol.Client(builder.toWireTransaction())))

View File

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

View File

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