Merged in rnicoll-tx-timestamp (pull request #271)

Replace timestamp commands with a field on the transaction itself
This commit is contained in:
Ross Nicoll 2016-08-12 15:59:49 +01:00
commit 316917f271
51 changed files with 319 additions and 266 deletions

View File

@ -3,6 +3,9 @@ package com.r3corda.contracts;
import com.google.common.collect.*; import com.google.common.collect.*;
import com.r3corda.contracts.asset.*; import com.r3corda.contracts.asset.*;
import com.r3corda.core.contracts.*; import com.r3corda.core.contracts.*;
import static com.r3corda.core.contracts.ContractsDSL.requireThat;
import com.r3corda.core.contracts.Timestamp;
import com.r3corda.core.contracts.TransactionForContract.*; import com.r3corda.core.contracts.TransactionForContract.*;
import com.r3corda.core.contracts.clauses.*; import com.r3corda.core.contracts.clauses.*;
import com.r3corda.core.crypto.*; import com.r3corda.core.crypto.*;
@ -219,15 +222,14 @@ public class JavaCommercialPaper implements Contract {
if (!cmd.getSigners().contains(input.getOwner())) if (!cmd.getSigners().contains(input.getOwner()))
throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP"); throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP");
Party notary = cmd.getValue().notary; Timestamp timestamp = tx.getTimestamp();
TimestampCommand timestampCommand = tx.getTimestampBy(notary); Instant time = null == timestamp
Instant time = null == timestampCommand
? null ? null
: timestampCommand.getBefore(); : timestamp.getBefore();
Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutputs(), input.getOwner()); Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutputs(), input.getOwner());
requireThat(require -> { requireThat(require -> {
require.by("must be timestamped", timestampCommand != null); require.by("must be timestamped", timestamp != null);
require.by("received amount equals the face value: " require.by("received amount equals the face value: "
+ received + " vs " + input.getFaceValue(), received.equals(input.getFaceValue())); + received + " vs " + input.getFaceValue(), received.equals(input.getFaceValue()));
require.by("the paper must have matured", time != null && !time.isBefore(input.getMaturityDate())); require.by("the paper must have matured", time != null && !time.isBefore(input.getMaturityDate()));
@ -257,7 +259,7 @@ public class JavaCommercialPaper implements Contract {
AuthenticatedObject<Commands.Issue> cmd = requireSingleCommand(tx.getCommands(), Commands.Issue.class); AuthenticatedObject<Commands.Issue> cmd = requireSingleCommand(tx.getCommands(), Commands.Issue.class);
State output = single(outputs); State output = single(outputs);
Party notary = cmd.getValue().notary; Party notary = cmd.getValue().notary;
TimestampCommand timestampCommand = tx.getTimestampBy(notary); Timestamp timestampCommand = tx.getTimestamp();
Instant time = null == timestampCommand Instant time = null == timestampCommand
? null ? null
: timestampCommand.getBefore(); : timestampCommand.getBefore();
@ -338,7 +340,7 @@ public class JavaCommercialPaper implements Contract {
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) { public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate); State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
TransactionState output = new TransactionState<>(state, notary); TransactionState output = new TransactionState<>(state, notary);
return new TransactionType.General.Builder().withItems(output, new Command(new Commands.Issue(notary), issuance.getParty().getOwningKey())); return new TransactionType.General.Builder(notary).withItems(output, new Command(new Commands.Issue(notary), issuance.getParty().getOwningKey()));
} }
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException { public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {

View File

@ -109,8 +109,7 @@ class CommercialPaper : Contract {
token: Issued<Terms>): Set<CommandData> { token: Issued<Terms>): Set<CommandData> {
val consumedCommands = super.verify(tx, inputs, outputs, commands, token) val consumedCommands = super.verify(tx, inputs, outputs, commands, token)
val command = commands.requireSingleCommand<Commands.Issue>() val command = commands.requireSingleCommand<Commands.Issue>()
// If it's an issue, we can't take notary from inputs, so it must be specified in the command val timestamp = tx.timestamp
val timestamp: TimestampCommand? = tx.getTimestampBy(command.value.notary)
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped") val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
require(outputs.all { time < it.maturityDate }) { "maturity date is not in the past" } require(outputs.all { time < it.maturityDate }) { "maturity date is not in the past" }
@ -151,8 +150,7 @@ class CommercialPaper : Contract {
// TODO: This should filter commands down to those with compatible subjects (underlying product and maturity date) // TODO: This should filter commands down to those with compatible subjects (underlying product and maturity date)
// before requiring a single command // before requiring a single command
val command = commands.requireSingleCommand<Commands.Redeem>() val command = commands.requireSingleCommand<Commands.Redeem>()
// If it's an issue, we can't take notary from inputs, so it must be specified in the command val timestamp = tx.timestamp
val timestamp: TimestampCommand? = tx.getTimestampBy(command.value.notary)
val input = inputs.single() val input = inputs.single()
val received = tx.outputs.sumCashBy(input.owner) val received = tx.outputs.sumCashBy(input.owner)

View File

@ -60,13 +60,7 @@ class CommercialPaperLegacy : Contract {
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming // There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
// it for cash on or after the maturity date. // it for cash on or after the maturity date.
val command = tx.commands.requireSingleCommand<CommercialPaperLegacy.Commands>() val command = tx.commands.requireSingleCommand<CommercialPaperLegacy.Commands>()
// If it's an issue, we can't take notary from inputs, so it must be specified in the command val timestamp: Timestamp? = tx.timestamp
val cmdVal = command.value
val timestamp: TimestampCommand? = when (cmdVal) {
is Commands.Issue -> tx.getTimestampBy(cmdVal.notary)
is Commands.Redeem -> tx.getTimestampBy(cmdVal.notary)
else -> null
}
for ((inputs, outputs, key) in groups) { for ((inputs, outputs, key) in groups) {
when (command.value) { when (command.value) {

View File

@ -447,8 +447,8 @@ class InterestRateSwap() : Contract {
fixingCalendar, index, indexSource, indexTenor) fixingCalendar, index, indexSource, indexTenor)
} }
private fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>> fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
= tx.commands.select<Commands>() + tx.commands.select<TimestampCommand>() = tx.commands.select<Commands>()
override fun verify(tx: TransactionForContract) = verifyClauses(tx, listOf(Clause.Timestamped(), Clause.Group()), extractCommands(tx)) override fun verify(tx: TransactionForContract) = verifyClauses(tx, listOf(Clause.Timestamped(), Clause.Group()), extractCommands(tx))
@ -519,12 +519,9 @@ class InterestRateSwap() : Contract {
override val requiredCommands = emptySet<Class<out CommandData>>() override val requiredCommands = emptySet<Class<out CommandData>>()
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> { override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> {
// TODO: This needs to either be the notary used for the inputs, or otherwise require(tx.timestamp?.midpoint != null) { "must be timestamped" }
// derived as the correct notary // We return an empty set because we don't process any commands
@Suppress("DEPRECATION") return emptySet()
val command = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
?: throw IllegalArgumentException("must be timestamped")
return setOf(command)
} }
} }

View File

@ -403,7 +403,7 @@ class Obligation<P> : Contract {
if (input is State<P>) { if (input is State<P>) {
val actualOutput = outputs[stateIdx] val actualOutput = outputs[stateIdx]
val deadline = input.dueBefore val deadline = input.dueBefore
val timestamp: TimestampCommand? = tx.timestamp val timestamp = tx.timestamp
val expectedOutput = input.copy(lifecycle = expectedOutputLifecycle) val expectedOutput = input.copy(lifecycle = expectedOutputLifecycle)
requireThat { requireThat {
@ -541,7 +541,7 @@ class Obligation<P> : Contract {
} }
tx.addCommand(Commands.SetLifecycle(lifecycle), partiesUsed.distinct()) tx.addCommand(Commands.SetLifecycle(lifecycle), partiesUsed.distinct())
} }
tx.setTime(issuanceDef.dueBefore, notary, issuanceDef.timeTolerance) tx.setTime(issuanceDef.dueBefore, issuanceDef.timeTolerance)
} }
/** /**

View File

@ -66,11 +66,11 @@ abstract class AbstractConserveAmount<S: FungibleAsset<T>, T: Any> : GroupClause
val currency = amountIssued.token.product val currency = amountIssued.token.product
val amount = Amount(amountIssued.quantity, currency) val amount = Amount(amountIssued.quantity, currency)
var acceptableCoins = assetStates.filter { ref -> ref.state.data.amount.token == amountIssued.token } var acceptableCoins = assetStates.filter { ref -> ref.state.data.amount.token == amountIssued.token }
val notary = acceptableCoins.firstOrNull()?.state?.notary tx.notary = acceptableCoins.firstOrNull()?.state?.notary
// TODO: We should be prepared to produce multiple transactions exiting inputs from // TODO: We should be prepared to produce multiple transactions exiting inputs from
// different notaries, or at least group states by notary and take the set with the // different notaries, or at least group states by notary and take the set with the
// highest total value // highest total value
acceptableCoins = acceptableCoins.filter { it.state.notary == notary } acceptableCoins = acceptableCoins.filter { it.state.notary == tx.notary }
val (gathered, gatheredAmount) = gatherCoins(acceptableCoins, Amount(amount.quantity, currency)) val (gathered, gatheredAmount) = gatherCoins(acceptableCoins, Amount(amount.quantity, currency))
val takeChangeFrom = gathered.lastOrNull() val takeChangeFrom = gathered.lastOrNull()
@ -128,13 +128,18 @@ abstract class AbstractConserveAmount<S: FungibleAsset<T>, T: Any> : GroupClause
// Finally, we add the states to the provided partial transaction. // Finally, we add the states to the provided partial transaction.
val currency = amount.token val currency = amount.token
val acceptableCoins = run { var acceptableCoins = run {
val ofCurrency = assetsStates.filter { it.state.data.amount.token.product == currency } val ofCurrency = assetsStates.filter { it.state.data.amount.token.product == currency }
if (onlyFromParties != null) if (onlyFromParties != null)
ofCurrency.filter { it.state.data.deposit.party in onlyFromParties } ofCurrency.filter { it.state.data.deposit.party in onlyFromParties }
else else
ofCurrency ofCurrency
} }
tx.notary = acceptableCoins.firstOrNull()?.state?.notary
// TODO: We should be prepared to produce multiple transactions spending inputs from
// different notaries, or at least group states by notary and take the set with the
// highest total value
acceptableCoins = acceptableCoins.filter { it.state.notary == tx.notary }
val (gathered, gatheredAmount) = gatherCoins(acceptableCoins, amount) val (gathered, gatheredAmount) = gatherCoins(acceptableCoins, amount)
val takeChangeFrom = gathered.firstOrNull() val takeChangeFrom = gathered.firstOrNull()

View File

@ -13,6 +13,8 @@ import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.Wallet import com.r3corda.core.node.services.Wallet
import com.r3corda.core.serialization.OpaqueBytes import com.r3corda.core.serialization.OpaqueBytes
import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -24,10 +26,11 @@ import java.util.*
* *
* The service hub needs to provide at least a key management service and a storage service. * The service hub needs to provide at least a key management service and a storage service.
* *
* @param outputNotary the notary to use for output states. The transaction is NOT signed by this notary.
* @return a wallet object that represents the generated states (it will NOT be the full wallet from the service hub!). * @return a wallet object that represents the generated states (it will NOT be the full wallet from the service hub!).
*/ */
fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>, fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>,
notary: Party = DUMMY_NOTARY, outputNotary: Party = DUMMY_NOTARY,
atLeastThisManyStates: Int = 3, atLeastThisManyStates: Int = 3,
atMostThisManyStates: Int = 10, atMostThisManyStates: Int = 10,
rng: Random = Random(), rng: Random = Random(),
@ -40,8 +43,8 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>,
// We will allocate one state to one transaction, for simplicities sake. // We will allocate one state to one transaction, for simplicities sake.
val cash = Cash() val cash = Cash()
val transactions: List<SignedTransaction> = amounts.map { pennies -> val transactions: List<SignedTransaction> = amounts.map { pennies ->
val issuance = TransactionType.General.Builder() val issuance = TransactionType.General.Builder(null)
cash.generateIssue(issuance, Amount(pennies, Issued(DUMMY_CASH_ISSUER.copy(reference = ref), howMuch.token)), myKey, notary) cash.generateIssue(issuance, Amount(pennies, Issued(DUMMY_CASH_ISSUER.copy(reference = ref), howMuch.token)), myKey, outputNotary)
issuance.signWith(DUMMY_CASH_ISSUER_KEY) issuance.signWith(DUMMY_CASH_ISSUER_KEY)
return@map issuance.toSignedTransaction(true) return@map issuance.toSignedTransaction(true)

View File

@ -254,7 +254,7 @@ object TwoPartyTradeProtocol {
} }
private fun assembleSharedTX(tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> { private fun assembleSharedTX(tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> {
val ptx = TransactionType.General.Builder() val ptx = TransactionType.General.Builder(notary)
// Add input and output states for the movement of cash, by using the Cash contract to generate the states. // 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 wallet = serviceHub.walletService.currentWallet
val cashStates = wallet.statesOfType<Cash.State>() val cashStates = wallet.statesOfType<Cash.State>()
@ -273,7 +273,7 @@ object TwoPartyTradeProtocol {
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt // And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one. // to have one.
val currentTime = serviceHub.clock.instant() val currentTime = serviceHub.clock.instant()
ptx.setTime(currentTime, notary, 30.seconds) ptx.setTime(currentTime, 30.seconds)
return Pair(ptx, cashSigningPubKeys) return Pair(ptx, cashSigningPubKeys)
} }
} }

View File

@ -184,7 +184,7 @@ class CommercialPaperTestsGeneric {
} }
fun cashOutputsToWallet(vararg outputs: TransactionState<Cash.State>): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> { fun cashOutputsToWallet(vararg outputs: TransactionState<Cash.State>): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> {
val ltx = LedgerTransaction(emptyList(), listOf(*outputs), emptyList(), emptyList(), SecureHash.randomSHA256(), emptyList(), TransactionType.General()) val ltx = LedgerTransaction(emptyList(), listOf(*outputs), emptyList(), emptyList(), SecureHash.randomSHA256(), null, emptyList(), null, TransactionType.General())
return Pair(ltx, outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) }) return Pair(ltx, outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
} }
@ -205,14 +205,14 @@ class CommercialPaperTestsGeneric {
val issuance = bigCorpServices.storageService.myLegalIdentity.ref(1) val issuance = bigCorpServices.storageService.myLegalIdentity.ref(1)
val issueTX: SignedTransaction = val issueTX: SignedTransaction =
CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply { CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds) setTime(TEST_TX_TIME, 30.seconds)
signWith(bigCorpServices.key) signWith(bigCorpServices.key)
signWith(DUMMY_NOTARY_KEY) signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction() }.toSignedTransaction()
// Alice pays $9000 to BigCorp to own some of their debt. // Alice pays $9000 to BigCorp to own some of their debt.
val moveTX: SignedTransaction = run { val moveTX: SignedTransaction = run {
val ptx = TransactionType.General.Builder() val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
Cash().generateSpend(ptx, 9000.DOLLARS, bigCorpServices.key.public, alicesWallet.statesOfType<Cash.State>()) Cash().generateSpend(ptx, 9000.DOLLARS, bigCorpServices.key.public, alicesWallet.statesOfType<Cash.State>())
CommercialPaper().generateMove(ptx, issueTX.tx.outRef(0), aliceServices.key.public) CommercialPaper().generateMove(ptx, issueTX.tx.outRef(0), aliceServices.key.public)
ptx.signWith(bigCorpServices.key) ptx.signWith(bigCorpServices.key)
@ -222,8 +222,8 @@ class CommercialPaperTestsGeneric {
} }
fun makeRedeemTX(time: Instant): SignedTransaction { fun makeRedeemTX(time: Instant): SignedTransaction {
val ptx = TransactionType.General.Builder() val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
ptx.setTime(time, DUMMY_NOTARY, 30.seconds) ptx.setTime(time, 30.seconds)
CommercialPaper().generateRedeem(ptx, moveTX.tx.outRef(1), bigCorpWallet.statesOfType<Cash.State>()) CommercialPaper().generateRedeem(ptx, moveTX.tx.outRef(1), bigCorpWallet.statesOfType<Cash.State>())
ptx.signWith(aliceServices.key) ptx.signWith(aliceServices.key)
ptx.signWith(bigCorpServices.key) ptx.signWith(bigCorpServices.key)

View File

@ -217,7 +217,7 @@ class IRSTests {
calculation = dummyIRS.calculation, calculation = dummyIRS.calculation,
common = dummyIRS.common, common = dummyIRS.common,
notary = DUMMY_NOTARY).apply { notary = DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds) setTime(TEST_TX_TIME, 30.seconds)
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
signWith(MINI_CORP_KEY) signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY) signWith(DUMMY_NOTARY_KEY)
@ -299,11 +299,11 @@ class IRSTests {
while (true) { while (true) {
val nextFix: FixOf = currentIRS().nextFixingOf() ?: break val nextFix: FixOf = currentIRS().nextFixingOf() ?: break
val fixTX: SignedTransaction = run { val fixTX: SignedTransaction = run {
val tx = TransactionType.General.Builder() val tx = TransactionType.General.Builder(DUMMY_NOTARY)
val fixing = Fix(nextFix, "0.052".percent.value) val fixing = Fix(nextFix, "0.052".percent.value)
InterestRateSwap().generateFix(tx, previousTXN.tx.outRef(0), fixing) InterestRateSwap().generateFix(tx, previousTXN.tx.outRef(0), fixing)
with(tx) { with(tx) {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds) setTime(TEST_TX_TIME, 30.seconds)
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
signWith(MINI_CORP_KEY) signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY) signWith(DUMMY_NOTARY_KEY)

View File

@ -92,7 +92,7 @@ class CashTests {
} }
// Test generation works. // Test generation works.
val ptx = TransactionType.General.Builder() val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(ptx.inputStates().isEmpty()) assertTrue(ptx.inputStates().isEmpty())
val s = ptx.outputStates()[0].data as Cash.State val s = ptx.outputStates()[0].data as Cash.State
@ -104,7 +104,7 @@ class CashTests {
// Test issuance from the issuance definition // Test issuance from the issuance definition
val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34) val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34)
val templatePtx = TransactionType.General.Builder() val templatePtx = TransactionType.General.Builder(DUMMY_NOTARY)
Cash().generateIssue(templatePtx, amount, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) Cash().generateIssue(templatePtx, amount, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(templatePtx.inputStates().isEmpty()) assertTrue(templatePtx.inputStates().isEmpty())
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0]) assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
@ -171,14 +171,14 @@ class CashTests {
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun `reject issuance with inputs`() { fun `reject issuance with inputs`() {
// Issue some cash // Issue some cash
var ptx = TransactionType.General.Builder() var ptx = TransactionType.General.Builder(DUMMY_NOTARY)
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
ptx.signWith(MINI_CORP_KEY) ptx.signWith(MINI_CORP_KEY)
val tx = ptx.toSignedTransaction() val tx = ptx.toSignedTransaction()
// Include the previously issued cash in a new issuance command // Include the previously issued cash in a new issuance command
ptx = TransactionType.General.Builder() ptx = TransactionType.General.Builder(DUMMY_NOTARY)
ptx.addInputState(tx.tx.outRef<Cash.State>(0)) ptx.addInputState(tx.tx.outRef<Cash.State>(0))
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
} }
@ -384,13 +384,13 @@ class CashTests {
* Generate an exit transaction, removing some amount of cash from the ledger. * Generate an exit transaction, removing some amount of cash from the ledger.
*/ */
fun makeExit(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1): WireTransaction { fun makeExit(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1): WireTransaction {
val tx = TransactionType.General.Builder() val tx = TransactionType.General.Builder(DUMMY_NOTARY)
Cash().generateExit(tx, Amount(amount.quantity, Issued(corp.ref(depositRef), amount.token)), OUR_PUBKEY_1, WALLET) Cash().generateExit(tx, Amount(amount.quantity, Issued(corp.ref(depositRef), amount.token)), OUR_PUBKEY_1, WALLET)
return tx.toWireTransaction() return tx.toWireTransaction()
} }
fun makeSpend(amount: Amount<Currency>, dest: PublicKey): WireTransaction { fun makeSpend(amount: Amount<Currency>, dest: PublicKey): WireTransaction {
val tx = TransactionType.General.Builder() val tx = TransactionType.General.Builder(DUMMY_NOTARY)
Cash().generateSpend(tx, amount, dest, WALLET) Cash().generateSpend(tx, amount, dest, WALLET)
return tx.toWireTransaction() return tx.toWireTransaction()
} }
@ -454,7 +454,7 @@ class CashTests {
@Test @Test
fun generateSimpleSpendWithParties() { fun generateSimpleSpendWithParties() {
val tx = TransactionType.General.Builder() val tx = TransactionType.General.Builder(DUMMY_NOTARY)
Cash().generateSpend(tx, 80.DOLLARS, ALICE_PUBKEY, WALLET, setOf(MINI_CORP)) Cash().generateSpend(tx, 80.DOLLARS, ALICE_PUBKEY, WALLET, setOf(MINI_CORP))
assertEquals(WALLET[2].ref, tx.inputStates()[0]) assertEquals(WALLET[2].ref, tx.inputStates()[0])
} }

View File

@ -256,7 +256,7 @@ class ObligationTests {
fun `generate payment net transaction with remainder`() { fun `generate payment net transaction with remainder`() {
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY)
val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE_PUBKEY) val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE_PUBKEY)
val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply { val tx = TransactionType.General.Builder(null).apply {
Obligation<Currency>().generatePaymentNetting(this, obligationAliceToBob.issuanceDef, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice) Obligation<Currency>().generatePaymentNetting(this, obligationAliceToBob.issuanceDef, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
signWith(ALICE_KEY) signWith(ALICE_KEY)
signWith(BOB_KEY) signWith(BOB_KEY)
@ -274,7 +274,7 @@ class ObligationTests {
val dueBefore = TEST_TX_TIME - Duration.ofDays(7) val dueBefore = TEST_TX_TIME - Duration.ofDays(7)
// Generate a transaction issuing the obligation // Generate a transaction issuing the obligation
var tx = TransactionType.General.Builder(DUMMY_NOTARY).apply { var tx = TransactionType.General.Builder(null).apply {
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement.copy(dueBefore = dueBefore), 100.DOLLARS.quantity, Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement.copy(dueBefore = dueBefore), 100.DOLLARS.quantity,
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY) beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
signWith(MINI_CORP_KEY) signWith(MINI_CORP_KEY)
@ -306,13 +306,13 @@ class ObligationTests {
/** Test generating a transaction to settle an obligation. */ /** Test generating a transaction to settle an obligation. */
@Test @Test
fun `generate settlement transaction`() { fun `generate settlement transaction`() {
val cashTx = TransactionType.General.Builder(DUMMY_NOTARY).apply { val cashTx = TransactionType.General.Builder(null).apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY, DUMMY_NOTARY) Cash().generateIssue(this, 100.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
}.toSignedTransaction().tx }.toSignedTransaction().tx
// Generate a transaction issuing the obligation // Generate a transaction issuing the obligation
val obligationTx = TransactionType.General.Builder(DUMMY_NOTARY).apply { val obligationTx = TransactionType.General.Builder(null).apply {
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity, Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY) beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
signWith(MINI_CORP_KEY) signWith(MINI_CORP_KEY)

View File

@ -84,29 +84,6 @@ inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>
fun <C : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingleCommand(klass: Class<C>) = fun <C : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingleCommand(klass: Class<C>) =
mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as AuthenticatedObject<C> else null }.single() mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as AuthenticatedObject<C> else null }.single()
/** Returns a timestamp that was signed by the given authority, or returns null if missing. */
fun List<AuthenticatedObject<CommandData>>.getTimestampBy(timestampingAuthority: Party): TimestampCommand? {
val timestampCmds = filter { it.signers.contains(timestampingAuthority.owningKey) && it.value is TimestampCommand }
return timestampCmds.singleOrNull()?.value as? TimestampCommand
}
/**
* Returns a timestamp that was signed by any of the the named authorities, or returns null if missing.
* Note that matching here is done by (verified, legal) name, not by public key. Any signature by any
* party with a name that matches (case insensitively) any of the given names will yield a match.
*/
@Deprecated(message = "Timestamping authority should always be notary for the transaction")
fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: String): TimestampCommand? {
val timestampCmd = filter { it.value is TimestampCommand }.singleOrNull() ?: return null
val tsaNames = timestampCmd.signingParties.map { it.name.toLowerCase() }
val acceptableNames = names.map(String::toLowerCase)
val acceptableNameFound = tsaNames.intersect(acceptableNames).isNotEmpty()
if (acceptableNameFound)
return timestampCmd.value as TimestampCommand
else
return null
}
/** /**
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key. * Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
* *

View File

@ -338,13 +338,10 @@ data class AuthenticatedObject<out T : Any>(
) )
/** /**
* If present in a transaction, contains a time that was verified by the timestamping authority/authorities whose * If present in a transaction, contains a time that was verified by the uniqueness service. The true time must be
* public keys are identified in the containing [Command] object. The true time must be between (after, before). * between (after, before).
*/ */
// TODO: Timestamps are now always provided by the consensus service for the transaction, rather than potentially data class Timestamp(val after: Instant?, val before: Instant?) {
// having multiple timestamps on a transaction. As such, it likely makes more sense for time to be a field on the
// transaction, rather than a command
data class TimestampCommand(val after: Instant?, val before: Instant?) : CommandData {
init { init {
if (after == null && before == null) if (after == null && before == null)
throw IllegalArgumentException("At least one of before/after must be specified") throw IllegalArgumentException("At least one of before/after must be specified")

View File

@ -16,20 +16,26 @@ import java.util.*
* The builder can be customised for specific transaction types, e.g. where additional processing is needed * The builder can be customised for specific transaction types, e.g. where additional processing is needed
* before adding a state/command. * before adding a state/command.
* *
* @param notary The default notary that will be used for outputs that don't have a notary specified. When this is set, * @param notary Notary used for the transaction. If null, this indicates the transaction DOES NOT have a notary.
* an output state can be added by just passing in a [ContractState] a [TransactionState] with the * When this is set to a non-null value, an output state can be added by just passing in a [ContractState] a
* default notary will be generated automatically. * [TransactionState] with this notary specified will be generated automatically.
*/ */
open class TransactionBuilder( open class TransactionBuilder(
protected val type: TransactionType = TransactionType.General(), protected val type: TransactionType = TransactionType.General(),
protected val notary: Party? = null, var notary: Party? = null,
protected val inputs: MutableList<StateRef> = arrayListOf(), protected val inputs: MutableList<StateRef> = arrayListOf(),
protected val attachments: MutableList<SecureHash> = arrayListOf(), protected val attachments: MutableList<SecureHash> = arrayListOf(),
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(), protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
protected val commands: MutableList<Command> = arrayListOf(), protected val commands: MutableList<Command> = arrayListOf(),
protected val signers: MutableSet<PublicKey> = mutableSetOf()) { protected val signers: MutableSet<PublicKey> = mutableSetOf(),
protected var timestamp: Timestamp? = null) {
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull() @Deprecated("use timestamp instead")
val time: Timestamp? get() = timestamp
init {
notary?.let { signers.add(it.owningKey) }
}
/** /**
* Creates a copy of the builder. * Creates a copy of the builder.
@ -42,7 +48,8 @@ open class TransactionBuilder(
attachments = ArrayList(attachments), attachments = ArrayList(attachments),
outputs = ArrayList(outputs), outputs = ArrayList(outputs),
commands = ArrayList(commands), commands = ArrayList(commands),
signers = LinkedHashSet(signers) signers = LinkedHashSet(signers),
timestamp = timestamp
) )
/** /**
@ -57,10 +64,13 @@ open class TransactionBuilder(
* collaborating parties may therefore require a higher time tolerance than a transaction being built by a single * collaborating parties may therefore require a higher time tolerance than a transaction being built by a single
* node. * node.
*/ */
fun setTime(time: Instant, authority: Party, timeTolerance: Duration) { fun setTime(time: Instant, timeTolerance: Duration)
= setTime(Timestamp(time, timeTolerance))
fun setTime(newTimestamp: Timestamp) {
check(notary != null) { "Only notarised transactions can have a timestamp" }
check(currentSigs.isEmpty()) { "Cannot change timestamp after signing" } check(currentSigs.isEmpty()) { "Cannot change timestamp after signing" }
commands.removeAll { it.value is TimestampCommand } this.timestamp = newTimestamp
addCommand(TimestampCommand(time, timeTolerance), authority.owningKey)
} }
/** A more convenient way to add items to this transaction that calls the add* methods for you based on type */ /** A more convenient way to add items to this transaction that calls the add* methods for you based on type */
@ -115,7 +125,7 @@ open class TransactionBuilder(
} }
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments), fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
ArrayList(outputs), ArrayList(commands), signers.toList(), type) ArrayList(outputs), ArrayList(commands), notary, signers.toList(), type, timestamp)
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction { fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
if (checkSufficientSignatures) { if (checkSufficientSignatures) {
@ -131,6 +141,7 @@ open class TransactionBuilder(
fun addInputState(stateRef: StateRef, notary: Party) { fun addInputState(stateRef: StateRef, notary: Party) {
check(currentSigs.isEmpty()) check(currentSigs.isEmpty())
require(notary == this.notary) { "Input state requires notary \"${notary}\" which does not match the transaction notary \"${this.notary}\"." }
signers.add(notary.owningKey) signers.add(notary.owningKey)
inputs.add(stateRef) inputs.add(stateRef)
} }

View File

@ -23,7 +23,7 @@ fun WireTransaction.toLedgerTransaction(services: ServiceHub): LedgerTransaction
services.storageService.attachments.openAttachment(it) ?: throw FileNotFoundException(it.toString()) services.storageService.attachments.openAttachment(it) ?: throw FileNotFoundException(it.toString())
} }
val resolvedInputs = inputs.map { StateAndRef(services.loadState(it), it) } val resolvedInputs = inputs.map { StateAndRef(services.loadState(it), it) }
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, signers, type) return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, signers, timestamp, type)
} }
/** /**

View File

@ -17,6 +17,7 @@ sealed class TransactionType {
* Note: Presence of _signatures_ is not checked, only the public keys to be signed for. * Note: Presence of _signatures_ is not checked, only the public keys to be signed for.
*/ */
fun verify(tx: LedgerTransaction) { fun verify(tx: LedgerTransaction) {
require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." }
val missing = verifySigners(tx) val missing = verifySigners(tx)
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList()) if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
verifyTransaction(tx) verifyTransaction(tx)
@ -24,9 +25,7 @@ sealed class TransactionType {
/** Check that the list of signers includes all the necessary keys */ /** Check that the list of signers includes all the necessary keys */
fun verifySigners(tx: LedgerTransaction): Set<PublicKey> { fun verifySigners(tx: LedgerTransaction): Set<PublicKey> {
val timestamp = tx.commands.noneOrSingle { it.value is TimestampCommand } val notaryKey = tx.inputs.map { it.state.notary.owningKey }.toSet()
val timestampKey = timestamp?.signers.orEmpty()
val notaryKey = (tx.inputs.map { it.state.notary.owningKey } + timestampKey).toSet()
if (notaryKey.size > 1) throw TransactionVerificationException.MoreThanOneNotary(tx) if (notaryKey.size > 1) throw TransactionVerificationException.MoreThanOneNotary(tx)
val requiredKeys = getRequiredSigners(tx) + notaryKey val requiredKeys = getRequiredSigners(tx) + notaryKey
@ -47,14 +46,26 @@ sealed class TransactionType {
/** A general transaction type where transaction validity is determined by custom contract code */ /** A general transaction type where transaction validity is determined by custom contract code */
class General : TransactionType() { class General : TransactionType() {
/** Just uses the default [TransactionBuilder] with no special logic */ /** Just uses the default [TransactionBuilder] with no special logic */
class Builder(notary: Party? = null) : TransactionBuilder(General(), notary) {} class Builder(notary: Party?) : TransactionBuilder(General(), notary) {}
/** /**
* Check the transaction is contract-valid by running the verify() for each input and output state contract. * 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. * If any contract fails to verify, the whole transaction is considered to be invalid.
*/ */
override fun verifyTransaction(tx: LedgerTransaction) { override fun verifyTransaction(tx: LedgerTransaction) {
// TODO: Check that notary is unchanged // Make sure the notary has stayed the same. As we can't tell how inputs and outputs connect, if there
// are any inputs, all outputs must have the same notary.
// TODO: Is that the correct set of restrictions? May need to come back to this, see if we can be more
// flexible on output notaries.
if (tx.notary != null
&& tx.inputs.isNotEmpty()) {
tx.outputs.forEach {
if (it.notary != tx.notary) {
throw TransactionVerificationException.NotaryChangeInWrongTransactionType(tx, it.notary)
}
}
}
val ctx = tx.toTransactionForContract() val ctx = tx.toTransactionForContract()
// TODO: This will all be replaced in future once the sandbox and contract constraints work is done. // TODO: This will all be replaced in future once the sandbox and contract constraints work is done.
@ -80,7 +91,7 @@ sealed class TransactionType {
* A transaction builder that automatically sets the transaction type to [NotaryChange] * 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. * and adds the list of participants to the signers set for every input state.
*/ */
class Builder(notary: Party? = null) : TransactionBuilder(NotaryChange(), notary) { class Builder(notary: Party) : TransactionBuilder(NotaryChange(), notary) {
override fun addInputState(stateAndRef: StateAndRef<*>) { override fun addInputState(stateAndRef: StateAndRef<*>) {
signers.addAll(stateAndRef.state.data.participants) signers.addAll(stateAndRef.state.data.participants)
super.addInputState(stateAndRef) super.addInputState(stateAndRef)

View File

@ -17,7 +17,8 @@ data class TransactionForContract(val inputs: List<ContractState>,
val attachments: List<Attachment>, val attachments: List<Attachment>,
val commands: List<AuthenticatedObject<CommandData>>, val commands: List<AuthenticatedObject<CommandData>>,
val origHash: SecureHash, val origHash: SecureHash,
val inputNotary: Party? = null) { val inputNotary: Party? = null,
val timestamp: Timestamp? = null) {
override fun hashCode() = origHash.hashCode() override fun hashCode() = origHash.hashCode()
override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
@ -82,18 +83,6 @@ data class TransactionForContract(val inputs: List<ContractState>,
* be used to simplify this logic. * be used to simplify this logic.
*/ */
data class InOutGroup<out T : ContractState, out K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K) data class InOutGroup<out T : ContractState, out K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
/** Get the timestamp command for this transaction, using the notary from the input states. */
val timestamp: TimestampCommand?
get() = if (inputNotary == null) null else commands.getTimestampBy(inputNotary)
/** 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. */
@Suppress("DEPRECATION")
@Deprecated(message = "Timestamping authority should always be notary for the transaction")
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
} }
class TransactionResolutionException(val hash: SecureHash) : Exception() { class TransactionResolutionException(val hash: SecureHash) : Exception() {
@ -108,4 +97,7 @@ sealed class TransactionVerificationException(val tx: LedgerTransaction, cause:
override fun toString() = "Signers missing: ${missing.map { it.toStringShort() }}" override fun toString() = "Signers missing: ${missing.map { it.toStringShort() }}"
} }
class InvalidNotaryChange(tx: LedgerTransaction) : TransactionVerificationException(tx, null) class InvalidNotaryChange(tx: LedgerTransaction) : TransactionVerificationException(tx, null)
class NotaryChangeInWrongTransactionType(tx: LedgerTransaction, val outputNotary: Party) : TransactionVerificationException(tx, null) {
override fun toString(): String = "Found unexpected notary change in transaction. Tx notary: ${tx.notary}, found: ${outputNotary}"
}
} }

View File

@ -2,8 +2,9 @@ package com.r3corda.core.contracts
import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Kryo
import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.toStringShort import com.r3corda.core.crypto.toStringsShort
import com.r3corda.core.indexOfOrThrow import com.r3corda.core.indexOfOrThrow
import com.r3corda.core.serialization.SerializedBytes import com.r3corda.core.serialization.SerializedBytes
import com.r3corda.core.serialization.THREAD_LOCAL_KRYO import com.r3corda.core.serialization.THREAD_LOCAL_KRYO
@ -12,6 +13,7 @@ import com.r3corda.core.serialization.serialize
import com.r3corda.core.utilities.Emoji import com.r3corda.core.utilities.Emoji
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
import java.util.*
/** /**
* Views of a transaction as it progresses through the pipeline, from bytes loaded from disk/network to the object * Views of a transaction as it progresses through the pipeline, from bytes loaded from disk/network to the object
@ -48,8 +50,10 @@ data class WireTransaction(val inputs: List<StateRef>,
val attachments: List<SecureHash>, val attachments: List<SecureHash>,
val outputs: List<TransactionState<ContractState>>, val outputs: List<TransactionState<ContractState>>,
val commands: List<Command>, val commands: List<Command>,
val notary: Party?,
val signers: List<PublicKey>, val signers: List<PublicKey>,
val type: TransactionType) : NamedByHash { val type: TransactionType,
val timestamp: Timestamp?) : NamedByHash {
// Cache the serialised form of the transaction and its hash to give us fast access to it. // Cache the serialised form of the transaction and its hash to give us fast access to it.
@Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null @Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null
@ -114,12 +118,32 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
// Now examine the contents and ensure the sigs we have line up with the advertised list of signers. // Now examine the contents and ensure the sigs we have line up with the advertised list of signers.
val missing = getMissingSignatures() val missing = getMissingSignatures()
if (missing.isNotEmpty() && throwIfSignaturesAreMissing) if (missing.isNotEmpty() && throwIfSignaturesAreMissing) {
throw SignatureException("Missing signatures on transaction ${id.prefixChars()} for: ${missing.map { it.toStringShort() }}") val missingElements = getMissingKeyDescriptions(missing)
throw SignatureException("Missing signatures for ${missingElements} on transaction ${id.prefixChars()} for ${missing.toStringsShort()}")
}
return missing return missing
} }
/**
* Get a human readable description of where signatures are required from, and are missing, to assist in debugging
* the underlying cause.
*/
private fun getMissingKeyDescriptions(missing: Set<PublicKey>): ArrayList<String> {
// TODO: We need a much better way of structuring this data
val missingElements = ArrayList<String>()
this.tx.commands.forEach { command ->
if (command.signers.any { signer -> missing.contains(signer) })
missingElements.add(command.toString())
}
this.tx.notary?.owningKey.apply {
if (missing.contains(this))
missingElements.add("notary")
}
return missingElements
}
/** Returns the same transaction but with an additional (unchecked) signature */ /** Returns the same transaction but with an additional (unchecked) signature */
fun withAdditionalSignature(sig: DigitalSignature.WithKey): SignedTransaction { fun withAdditionalSignature(sig: DigitalSignature.WithKey): SignedTransaction {
// TODO: need to make sure the Notary signs last // TODO: need to make sure the Notary signs last
@ -139,6 +163,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
* Returns the set of missing signatures - a signature must be present for each signer public key. * Returns the set of missing signatures - a signature must be present for each signer public key.
*/ */
private fun getMissingSignatures(): Set<PublicKey> { private fun getMissingSignatures(): Set<PublicKey> {
val notaryKey = tx.notary?.owningKey
val requiredKeys = tx.signers.toSet() val requiredKeys = tx.signers.toSet()
val sigKeys = sigs.map { it.by }.toSet() val sigKeys = sigs.map { it.by }.toSet()
@ -161,10 +186,13 @@ data class LedgerTransaction(
val commands: List<AuthenticatedObject<CommandData>>, val commands: List<AuthenticatedObject<CommandData>>,
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */ /** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
val attachments: List<Attachment>, val attachments: List<Attachment>,
/** The hash of the original serialised WireTransaction */ /** The hash of the original serialised WireTransaction. */
override val id: SecureHash, override val id: SecureHash,
/** The notary for this party, may be null for transactions with no notary. */
val notary: Party?,
/** The notary key and the command keys together: a signed transaction must provide signatures for all of these. */ /** The notary key and the command keys together: a signed transaction must provide signatures for all of these. */
val signers: List<PublicKey>, val signers: List<PublicKey>,
val timestamp: Timestamp?,
val type: TransactionType val type: TransactionType
) : NamedByHash { ) : NamedByHash {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -175,7 +203,7 @@ data class LedgerTransaction(
/** Strips the transaction down to a form that is usable by the contract verify functions */ /** Strips the transaction down to a form that is usable by the contract verify functions */
fun toTransactionForContract(): TransactionForContract { fun toTransactionForContract(): TransactionForContract {
return TransactionForContract(inputs.map { it.state.data }, outputs.map { it.data }, attachments, commands, id, return TransactionForContract(inputs.map { it.state.data }, outputs.map { it.data }, attachments, commands, id,
inputs.map { it.state.notary }.singleOrNull()) inputs.map { it.state.notary }.singleOrNull(), timestamp)
} }
/** /**

View File

@ -1,6 +1,6 @@
package com.r3corda.core.node.services package com.r3corda.core.node.services
import com.r3corda.core.contracts.TimestampCommand import com.r3corda.core.contracts.Timestamp
import com.r3corda.core.seconds import com.r3corda.core.seconds
import com.r3corda.core.until import com.r3corda.core.until
import java.time.Clock import java.time.Clock
@ -11,7 +11,7 @@ import java.time.Duration
*/ */
class TimestampChecker(val clock: Clock = Clock.systemUTC(), class TimestampChecker(val clock: Clock = Clock.systemUTC(),
val tolerance: Duration = 30.seconds) { val tolerance: Duration = 30.seconds) {
fun isValid(timestampCommand: TimestampCommand): Boolean { fun isValid(timestampCommand: Timestamp): Boolean {
val before = timestampCommand.before val before = timestampCommand.before
val after = timestampCommand.after val after = timestampCommand.after

View File

@ -230,8 +230,10 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
kryo.writeClassAndObject(output, obj.attachments) kryo.writeClassAndObject(output, obj.attachments)
kryo.writeClassAndObject(output, obj.outputs) kryo.writeClassAndObject(output, obj.outputs)
kryo.writeClassAndObject(output, obj.commands) kryo.writeClassAndObject(output, obj.commands)
kryo.writeClassAndObject(output, obj.notary)
kryo.writeClassAndObject(output, obj.signers) kryo.writeClassAndObject(output, obj.signers)
kryo.writeClassAndObject(output, obj.type) kryo.writeClassAndObject(output, obj.type)
kryo.writeClassAndObject(output, obj.timestamp)
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -260,10 +262,12 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
kryo.useClassLoader(classLoader) { kryo.useClassLoader(classLoader) {
val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>> val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>>
val commands = kryo.readClassAndObject(input) as List<Command> val commands = kryo.readClassAndObject(input) as List<Command>
val notary = kryo.readClassAndObject(input) as Party?
val signers = kryo.readClassAndObject(input) as List<PublicKey> val signers = kryo.readClassAndObject(input) as List<PublicKey>
val transactionType = kryo.readClassAndObject(input) as TransactionType val transactionType = kryo.readClassAndObject(input) as TransactionType
val timestamp = kryo.readClassAndObject(input) as Timestamp?
return WireTransaction(inputs, attachmentHashes, outputs, commands, signers, transactionType) return WireTransaction(inputs, attachmentHashes, outputs, commands, notary, signers, transactionType, timestamp)
} }
} }
} }

View File

@ -111,6 +111,6 @@ fun freeLocalHostAndPort(): HostAndPort {
*/ */
@JvmOverloads fun transaction( @JvmOverloads fun transaction(
transactionLabel: String? = null, transactionLabel: String? = null,
transactionBuilder: TransactionBuilder = TransactionBuilder(), transactionBuilder: TransactionBuilder = TransactionBuilder(notary = DUMMY_NOTARY),
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = ledger { this.transaction(transactionLabel, transactionBuilder, dsl) } ) = ledger { this.transaction(transactionLabel, transactionBuilder, dsl) }

View File

@ -126,14 +126,14 @@ class LedgerDSL<out T : TransactionDSLInterpreter, out L : LedgerDSLInterpreter<
* @see LedgerDSLInterpreter._transaction * @see LedgerDSLInterpreter._transaction
*/ */
@JvmOverloads @JvmOverloads
fun transaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(), fun transaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(notary = DUMMY_NOTARY),
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) =
_transaction(label, transactionBuilder, dsl) _transaction(label, transactionBuilder, dsl)
/** /**
* @see LedgerDSLInterpreter._unverifiedTransaction * @see LedgerDSLInterpreter._unverifiedTransaction
*/ */
@JvmOverloads @JvmOverloads
fun unverifiedTransaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(), fun unverifiedTransaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(notary = DUMMY_NOTARY),
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> Unit) = dsl: TransactionDSL<TransactionDSLInterpreter>.() -> Unit) =
_unverifiedTransaction(label, transactionBuilder, dsl) _unverifiedTransaction(label, transactionBuilder, dsl)

View File

@ -130,10 +130,19 @@ data class TestTransactionDSLInterpreter private constructor(
} }
override fun verifies(): EnforceVerifyOrFail { override fun verifies(): EnforceVerifyOrFail {
// Verify on a copy of the transaction builder, so if it's then further modified it doesn't error due to
// the existing signature
transactionBuilder.copy().apply {
signWith(DUMMY_NOTARY_KEY)
toWireTransaction().toLedgerTransaction(services).verify() toWireTransaction().toLedgerTransaction(services).verify()
}
return EnforceVerifyOrFail.Token return EnforceVerifyOrFail.Token
} }
override fun timestamp(data: Timestamp) {
transactionBuilder.setTime(data)
}
override fun tweak( override fun tweak(
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = dsl(TransactionDSL(copy())) ) = dsl(TransactionDSL(copy()))
@ -301,15 +310,18 @@ data class TestLedgerDSLInterpreter private constructor (
* @return List of [SignedTransaction]s. * @return List of [SignedTransaction]s.
*/ */
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: List<KeyPair>) = transactionsToSign.map { wtx -> fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: List<KeyPair>) = transactionsToSign.map { wtx ->
val allPubKeys = wtx.signers.toMutableSet() check(wtx.signers.isNotEmpty())
val bits = wtx.serialize() val bits = wtx.serialize()
require(bits == wtx.serialized) require(bits == wtx.serialized)
val signatures = ArrayList<DigitalSignature.WithKey>() val signatures = ArrayList<DigitalSignature.WithKey>()
for (key in ALL_TEST_KEYS + extraKeys) { val keyLookup = HashMap<PublicKey, KeyPair>()
if (key.public in allPubKeys) {
signatures += key.signWithECDSA(bits) (ALL_TEST_KEYS + extraKeys).forEach {
allPubKeys -= key.public keyLookup[it.public] = it
} }
wtx.signers.forEach {
val key = keyLookup[it] ?: throw IllegalArgumentException("Missing required key for ${it.toStringShort()}")
signatures += key.signWithECDSA(bits)
} }
SignedTransaction(bits, signatures) SignedTransaction(bits, signatures)
} }

View File

@ -46,6 +46,12 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
*/ */
fun _command(signers: List<PublicKey>, commandData: CommandData) fun _command(signers: List<PublicKey>, commandData: CommandData)
/**
* Adds a timestamp to the transaction.
* @param data The [TimestampCommand].
*/
fun timestamp(data: Timestamp)
/** /**
* Creates a local scoped copy of the transaction. * Creates a local scoped copy of the transaction.
* @param dsl The transaction DSL to be interpreted using the copy. * @param dsl The transaction DSL to be interpreted using the copy.
@ -102,16 +108,8 @@ class TransactionDSL<out T : TransactionDSLInterpreter>(val interpreter: T) : Tr
* Adds a timestamp command to the transaction. * Adds a timestamp command to the transaction.
* @param time The [Instant] of the [TimestampCommand]. * @param time The [Instant] of the [TimestampCommand].
* @param tolerance The tolerance of the [TimestampCommand]. * @param tolerance The tolerance of the [TimestampCommand].
* @param notary The notary to sign the command.
*/ */
@JvmOverloads @JvmOverloads
fun timestamp(time: Instant, tolerance: Duration = 30.seconds, notary: PublicKey = DUMMY_NOTARY.owningKey) = fun timestamp(time: Instant, tolerance: Duration = 30.seconds) =
timestamp(TimestampCommand(time, tolerance), notary) timestamp(Timestamp(time, tolerance))
/**
* Adds a timestamp command to the transaction.
* @param data The [TimestampCommand].
* @param notary The notary to sign the command.
*/
@JvmOverloads
fun timestamp(data: TimestampCommand, notary: PublicKey = DUMMY_NOTARY.owningKey) = command(notary, data)
} }

View File

@ -37,7 +37,7 @@ object NotaryChangeProtocol: AbstractStateReplacementProtocol<Party>() {
val state = originalState.state val state = originalState.state
val newState = state.withNotary(modification) val newState = state.withNotary(modification)
val participants = state.data.participants val participants = state.data.participants
val tx = TransactionType.NotaryChange.Builder().withItems(originalState, newState) val tx = TransactionType.NotaryChange.Builder(originalState.state.notary).withItems(originalState, newState)
tx.signWith(serviceHub.storageService.myLegalIdentityKey) tx.signWith(serviceHub.storageService.myLegalIdentityKey)
val stx = tx.toSignedTransaction(false) val stx = tx.toSignedTransaction(false)

View File

@ -2,7 +2,8 @@ package com.r3corda.protocols
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.contracts.SignedTransaction import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.TimestampCommand import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.Timestamp
import com.r3corda.core.contracts.WireTransaction import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
@ -51,7 +52,9 @@ object NotaryProtocol {
@Suspendable @Suspendable
override fun call(): DigitalSignature.LegallyIdentifiable { override fun call(): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = REQUESTING progressTracker.currentStep = REQUESTING
notaryParty = findNotaryParty() val wtx = stx.tx
notaryParty = wtx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
check(wtx.inputs.all { stateRef -> serviceHub.loadState(stateRef).notary == notaryParty }) { "Input states must have the same Notary" }
val sendSessionID = random63BitValue() val sendSessionID = random63BitValue()
val receiveSessionID = random63BitValue() val receiveSessionID = random63BitValue()
@ -82,25 +85,6 @@ object NotaryProtocol {
check(sig.signer == notaryParty) { "Notary result not signed by the correct service" } check(sig.signer == notaryParty) { "Notary result not signed by the correct service" }
sig.verifyWithECDSA(data) sig.verifyWithECDSA(data)
} }
private fun findNotaryParty(): Party {
var maybeNotaryKey: PublicKey? = null
val wtx = stx.tx
val timestampCommand = wtx.commands.singleOrNull { it.value is TimestampCommand }
if (timestampCommand != null) maybeNotaryKey = timestampCommand.signers.first()
for (stateRef in wtx.inputs) {
val inputNotaryKey = serviceHub.loadState(stateRef).notary.owningKey
if (maybeNotaryKey != null)
check(maybeNotaryKey == inputNotaryKey) { "Input states and timestamp must have the same Notary" }
else maybeNotaryKey = inputNotaryKey
}
val notaryKey = maybeNotaryKey ?: throw IllegalStateException("Transaction does not specify a Notary")
val notaryParty = serviceHub.networkMapCache.getNodeByPublicKey(notaryKey)?.identity
return notaryParty ?: throw IllegalStateException("No Notary node can be found with the specified public key")
}
} }
/** /**
@ -139,15 +123,8 @@ object NotaryProtocol {
} }
private fun validateTimestamp(tx: WireTransaction) { private fun validateTimestamp(tx: WireTransaction) {
val timestampCmd = try { if (tx.timestamp != null
tx.commands.noneOrSingle { it.value is TimestampCommand } ?: return && !timestampChecker.isValid(tx.timestamp))
} catch (e: IllegalArgumentException) {
throw NotaryException(NotaryError.MoreThanOneTimestamp())
}
val myIdentity = serviceHub.storageService.myLegalIdentity
if (!timestampCmd.signers.contains(myIdentity.owningKey))
throw NotaryException(NotaryError.NotForMe())
if (!timestampChecker.isValid(timestampCmd.value as TimestampCommand))
throw NotaryException(NotaryError.TimestampInvalid()) throw NotaryException(NotaryError.TimestampInvalid())
} }
@ -223,11 +200,6 @@ sealed class NotaryError {
override fun toString() = "One or more input states for transaction ${tx.id} have been used in another transaction" override fun toString() = "One or more input states for transaction ${tx.id} have been used in another transaction"
} }
class MoreThanOneTimestamp : NotaryError()
/** Thrown if the timestamp command in the transaction doesn't list this Notary as a signer */
class NotForMe : NotaryError()
/** Thrown if the time specified in the timestamp command is outside the allowed tolerance */ /** Thrown if the time specified in the timestamp command is outside the allowed tolerance */
class TimestampInvalid : NotaryError() class TimestampInvalid : NotaryError()

View File

@ -323,7 +323,7 @@ object TwoPartyDealProtocol {
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt // And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one. // to have one.
ptx.setTime(serviceHub.clock.instant(), notary, 30.seconds) ptx.setTime(serviceHub.clock.instant(), 30.seconds)
return Pair(ptx, arrayListOf(handshake.payload.parties.single { it.name == serviceHub.storageService.myLegalIdentity.name }.owningKey)) return Pair(ptx, arrayListOf(handshake.payload.parties.single { it.name == serviceHub.storageService.myLegalIdentity.name }.owningKey))
} }
@ -375,7 +375,7 @@ object TwoPartyDealProtocol {
val newDeal = deal val newDeal = deal
val ptx = TransactionType.General.Builder() val ptx = TransactionType.General.Builder(txState.notary)
val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0].identity, fixOf, BigDecimal.ZERO, BigDecimal.ONE) { val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0].identity, fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
@Suspendable @Suspendable
override fun beforeSigning(fix: Fix) { override fun beforeSigning(fix: Fix) {
@ -383,7 +383,7 @@ object TwoPartyDealProtocol {
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt // And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one. // to have one.
ptx.setTime(serviceHub.clock.instant(), txState.notary, 30.seconds) ptx.setTime(serviceHub.clock.instant(), 30.seconds)
} }
} }
subProtocol(addFixing) subProtocol(addFixing)

View File

@ -3,6 +3,7 @@ package com.r3corda.core.contracts
import com.r3corda.core.crypto.newSecureRandom import com.r3corda.core.crypto.newSecureRandom
import com.r3corda.core.node.services.testing.MockTransactionStorage import com.r3corda.core.node.services.testing.MockTransactionStorage
import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import com.r3corda.core.testing.MEGA_CORP_KEY import com.r3corda.core.testing.MEGA_CORP_KEY
import org.junit.Test import org.junit.Test
import java.security.KeyPair import java.security.KeyPair
@ -26,14 +27,16 @@ class TransactionGraphSearchTests {
* @param signer signer for the two transactions and their commands. * @param signer signer for the two transactions and their commands.
*/ */
fun buildTransactions(command: CommandData, signer: KeyPair): GraphTransactionStorage { fun buildTransactions(command: CommandData, signer: KeyPair): GraphTransactionStorage {
val originTx = TransactionType.General.Builder().apply { val originTx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
addOutputState(DummyState(random31BitValue()), DUMMY_NOTARY) addOutputState(DummyState(random31BitValue()))
addCommand(command, signer.public) addCommand(command, signer.public)
signWith(signer) signWith(signer)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction(false) }.toSignedTransaction(false)
val inputTx = TransactionType.General.Builder().apply { val inputTx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
addInputState(originTx.tx.outRef<DummyState>(0)) addInputState(originTx.tx.outRef<DummyState>(0))
signWith(signer) signWith(signer)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction(false) }.toSignedTransaction(false)
return GraphTransactionStorage(originTx, inputTx) return GraphTransactionStorage(originTx, inputTx)
} }

View File

@ -0,0 +1,61 @@
package com.r3corda.core.contracts
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.testing.*
import org.junit.Test
import kotlin.test.assertFailsWith
class TransactionTypeTests {
@Test
fun `transactions with no inputs can have any notary`() {
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE_PUBKEY), DUMMY_NOTARY)
val inputs = emptyList<StateAndRef<*>>()
val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB))
val commands = emptyList<AuthenticatedObject<CommandData>>()
val attachments = emptyList<Attachment>()
val id = SecureHash.randomSHA256()
val signers = listOf(DUMMY_NOTARY_KEY.public)
val timestamp: Timestamp? = null
val transaction: LedgerTransaction = LedgerTransaction(
inputs,
outputs,
commands,
attachments,
id,
null,
signers,
timestamp,
TransactionType.General()
)
transaction.type.verify(transaction)
}
@Test
fun `general transactions cannot change notary`() {
val notary: Party = DUMMY_NOTARY
val inState = TransactionState(DummyContract.SingleOwnerState(0, ALICE_PUBKEY), notary)
val outState = inState.copy(notary = ALICE)
val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0)))
val outputs = listOf(outState)
val commands = emptyList<AuthenticatedObject<CommandData>>()
val attachments = emptyList<Attachment>()
val id = SecureHash.randomSHA256()
val signers = listOf(DUMMY_NOTARY_KEY.public)
val timestamp: Timestamp? = null
val transaction: LedgerTransaction = LedgerTransaction(
inputs,
outputs,
commands,
attachments,
id,
notary,
signers,
timestamp,
TransactionType.General()
)
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { transaction.type.verify(transaction) }
}
}

View File

@ -49,7 +49,7 @@ class TransactionSerializationTests {
@Before @Before
fun setup() { fun setup() {
tx = TransactionType.General.Builder().withItems( tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(DUMMY_KEY_1.public)) inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(DUMMY_KEY_1.public))
) )
} }
@ -88,7 +88,7 @@ class TransactionSerializationTests {
// If the signature was replaced in transit, we don't like it. // If the signature was replaced in transit, we don't like it.
assertFailsWith(SignatureException::class) { assertFailsWith(SignatureException::class) {
val tx2 = TransactionType.General.Builder().withItems(inputState, outputState, changeState, val tx2 = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState, outputState, changeState,
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public)) Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
tx2.signWith(DUMMY_NOTARY_KEY) tx2.signWith(DUMMY_NOTARY_KEY)
tx2.signWith(DUMMY_KEY_2) tx2.signWith(DUMMY_KEY_2)
@ -99,10 +99,10 @@ class TransactionSerializationTests {
@Test @Test
fun timestamp() { fun timestamp() {
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds) tx.setTime(TEST_TX_TIME, 30.seconds)
tx.signWith(DUMMY_KEY_1) tx.signWith(DUMMY_KEY_1)
tx.signWith(DUMMY_NOTARY_KEY) tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction() val stx = tx.toSignedTransaction()
assertEquals(TEST_TX_TIME, (stx.tx.commands[1].value as TimestampCommand).midpoint) assertEquals(TEST_TX_TIME, stx.tx.timestamp?.midpoint)
} }
} }

View File

@ -84,7 +84,7 @@ class AccountReceivable : Contract {
throw IllegalArgumentException("Cannot build AR with an already assigned invoice") throw IllegalArgumentException("Cannot build AR with an already assigned invoice")
} }
val ar = createARFromInvoice(invoice.state.data, discountRate, notary) val ar = createARFromInvoice(invoice.state.data, discountRate, notary)
val tx = TransactionType.General.Builder() val tx = TransactionType.General.Builder(notary)
tx.addInputState(invoice) tx.addInputState(invoice)
tx.addOutputState(invoice.state.data.copy(assigned = true)) tx.addOutputState(invoice.state.data.copy(assigned = true))
tx.addCommand(Invoice.Commands.Assign(), invoice.state.data.owner.owningKey) tx.addCommand(Invoice.Commands.Assign(), invoice.state.data.owner.owningKey)
@ -108,7 +108,7 @@ class AccountReceivable : Contract {
override fun verify(tx: TransactionForContract) { override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<AccountReceivable.Commands>() val command = tx.commands.requireSingleCommand<AccountReceivable.Commands>()
val time = tx.commands.getTimestampByName("Notary Service", "Seller")?.midpoint ?: val time = tx.timestamp?.midpoint ?:
throw IllegalArgumentException("must be timestamped") throw IllegalArgumentException("must be timestamped")
when (command.value) { when (command.value) {

View File

@ -74,7 +74,7 @@ class BillOfLadingAgreement : Contract {
override fun verify(tx: TransactionForContract) { override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<BillOfLadingAgreement.Commands>() val command = tx.commands.requireSingleCommand<BillOfLadingAgreement.Commands>()
val time = tx.commands.getTimestampByName("Notary Service")?.midpoint val time = tx.timestamp?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped") if (time == null) throw IllegalArgumentException("must be timestamped")
val txOutputStates: List<BillOfLadingAgreement.State> = tx.outputs.filterIsInstance<BillOfLadingAgreement.State>() val txOutputStates: List<BillOfLadingAgreement.State> = tx.outputs.filterIsInstance<BillOfLadingAgreement.State>()
@ -114,7 +114,7 @@ class BillOfLadingAgreement : Contract {
fun generateIssue(owner: PublicKey, beneficiary: Party, props: BillOfLadingProperties, notary: Party): TransactionBuilder { fun generateIssue(owner: PublicKey, beneficiary: Party, props: BillOfLadingProperties, notary: Party): TransactionBuilder {
val state = State(owner, beneficiary, props) val state = State(owner, beneficiary, props)
val builder = TransactionType.General.Builder(notary = notary) val builder = TransactionType.General.Builder(notary = notary)
builder.setTime(Instant.now(), notary, 1.days) builder.setTime(Instant.now(), 1.days)
return builder.withItems(state, Command(Commands.IssueBL(), props.carrierOwner.owningKey)) return builder.withItems(state, Command(Commands.IssueBL(), props.carrierOwner.owningKey))
} }

View File

@ -113,7 +113,7 @@ class Invoice : Contract {
override fun verify(tx: TransactionForContract) { override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<Invoice.Commands>() val command = tx.commands.requireSingleCommand<Invoice.Commands>()
val time = tx.commands.getTimestampByName("Notary Service", "Seller")?.midpoint ?: val time = tx.timestamp?.midpoint ?:
throw IllegalArgumentException("must be timestamped") throw IllegalArgumentException("must be timestamped")
when (command.value) { when (command.value) {

View File

@ -30,11 +30,6 @@ class LCApplication : Contract {
val inputs = tx.inputs.filterIsInstance<State>() val inputs = tx.inputs.filterIsInstance<State>()
val outputs = tx.outputs.filterIsInstance<State>() val outputs = tx.outputs.filterIsInstance<State>()
// Here, we match acceptable timestamp authorities by name. The list of acceptable TSAs (oracles) must be
// hard coded into the contract because otherwise we could fail to gain consensus, if nodes disagree about
// who or what is a trusted authority.
tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
when (command.value) { when (command.value) {
is Commands.ApplyForLC -> { is Commands.ApplyForLC -> {
verifyApply(inputs, outputs, command as AuthenticatedObject<Commands.ApplyForLC>, tx) verifyApply(inputs, outputs, command as AuthenticatedObject<Commands.ApplyForLC>, tx)
@ -134,7 +129,7 @@ class LCApplication : Contract {
fun generateApply(props: LCApplicationProperties, notary: Party, purchaseOrder: Attachment): TransactionBuilder { fun generateApply(props: LCApplicationProperties, notary: Party, purchaseOrder: Attachment): TransactionBuilder {
val state = State(props.issuer.owningKey, Status.PENDING_ISSUER_REVIEW, props) val state = State(props.issuer.owningKey, Status.PENDING_ISSUER_REVIEW, props)
val txBuilder = TransactionType.General.Builder().withItems(state, Command(Commands.ApplyForLC(), props.applicant.owningKey)) val txBuilder = TransactionType.General.Builder(notary).withItems(state, Command(Commands.ApplyForLC(), props.applicant.owningKey))
txBuilder.addAttachment(purchaseOrder.id) txBuilder.addAttachment(purchaseOrder.id)
return txBuilder return txBuilder
} }

View File

@ -71,7 +71,7 @@ class LOC : Contract {
override fun verify(tx: TransactionForContract) { override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<LOC.Commands>() val command = tx.commands.requireSingleCommand<LOC.Commands>()
val time = tx.commands.getTimestampByName("Notary Service")?.midpoint val time = tx.timestamp?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped") if (time == null) throw IllegalArgumentException("must be timestamped")
when (command.value) { when (command.value) {
@ -146,7 +146,7 @@ class LOC : Contract {
fun generateIssue(beneficiaryPaid: Boolean, issued: Boolean, terminated: Boolean, props: LOCProperties, notary: Party): TransactionBuilder { fun generateIssue(beneficiaryPaid: Boolean, issued: Boolean, terminated: Boolean, props: LOCProperties, notary: Party): TransactionBuilder {
val state = State(beneficiaryPaid, issued, terminated, props) val state = State(beneficiaryPaid, issued, terminated, props)
val builder = TransactionType.General.Builder(notary = notary) val builder = TransactionType.General.Builder(notary = notary)
builder.setTime(Instant.now(), notary, 1.days) builder.setTime(Instant.now(), 1.days)
return builder.withItems(state, Command(Commands.Issuance(), props.issuingbank.owningKey)) return builder.withItems(state, Command(Commands.Issuance(), props.issuingbank.owningKey))
} }

View File

@ -85,7 +85,7 @@ class AccountReceivableTests {
} }
val gtx = invoice.generateInvoice(DUMMY_NOTARY).apply { val gtx = invoice.generateInvoice(DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds) setTime(TEST_TX_TIME, 30.seconds)
signWith(MINI_CORP_KEY) signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY) signWith(DUMMY_NOTARY_KEY)
} }

View File

@ -93,7 +93,7 @@ class BillOfLadingAgreementTests {
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun transferAndEndorseGenerationMethod_MissingBeneficiarySignature() { fun transferAndEndorseGenerationMethod_MissingBeneficiarySignature() {
val ptx:TransactionBuilder = TransactionType.General.Builder() val ptx:TransactionBuilder = TransactionType.General.Builder(notary = DUMMY_NOTARY)
val sr = StateAndRef( val sr = StateAndRef(
TransactionState(Bill, DUMMY_NOTARY), TransactionState(Bill, DUMMY_NOTARY),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
@ -105,7 +105,7 @@ class BillOfLadingAgreementTests {
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun transferAndEndorseGenerationMethod_MissingOwnerSignature() { fun transferAndEndorseGenerationMethod_MissingOwnerSignature() {
val ptx:TransactionBuilder = TransactionType.General.Builder() val ptx:TransactionBuilder = TransactionType.General.Builder(notary = DUMMY_NOTARY)
val sr = StateAndRef( val sr = StateAndRef(
TransactionState(Bill, DUMMY_NOTARY), TransactionState(Bill, DUMMY_NOTARY),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
@ -129,7 +129,7 @@ class BillOfLadingAgreementTests {
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun transferPossessionGenerationMethod_Unsigned() { fun transferPossessionGenerationMethod_Unsigned() {
val ptx:TransactionBuilder = TransactionType.General.Builder() val ptx:TransactionBuilder = TransactionType.General.Builder(notary = DUMMY_NOTARY)
val sr = StateAndRef( val sr = StateAndRef(
TransactionState(Bill, DUMMY_NOTARY), TransactionState(Bill, DUMMY_NOTARY),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) StateRef(SecureHash.randomSHA256(), Random().nextInt(32))

View File

@ -34,7 +34,7 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
val issuance = run { val issuance = run {
val tx = CommercialPaper().generateIssue(seller.info.identity.ref(1, 2, 3), 1100.DOLLARS `issued by` DUMMY_CASH_ISSUER, val tx = CommercialPaper().generateIssue(seller.info.identity.ref(1, 2, 3), 1100.DOLLARS `issued by` DUMMY_CASH_ISSUER,
Instant.now() + 10.days, notary.info.identity) Instant.now() + 10.days, notary.info.identity)
tx.setTime(Instant.now(), notary.info.identity, 30.seconds) tx.setTime(Instant.now(), 30.seconds)
tx.signWith(notary.storage.myLegalIdentityKey) tx.signWith(notary.storage.myLegalIdentityKey)
tx.signWith(seller.storage.myLegalIdentityKey) tx.signWith(seller.storage.myLegalIdentityKey)
tx.toSignedTransaction(true) tx.toSignedTransaction(true)

View File

@ -164,7 +164,7 @@ class WalletMonitorService(net: MessagingService, val smm: StateMachineManager,
// TODO: Make a lightweight protocol that manages this workflow, rather than embedding it directly in the service // TODO: Make a lightweight protocol that manages this workflow, rather than embedding it directly in the service
private fun initatePayment(req: ClientToServiceCommand.PayCash): TransactionBuildResult { private fun initatePayment(req: ClientToServiceCommand.PayCash): TransactionBuildResult {
val builder: TransactionBuilder = TransactionType.General.Builder() val builder: TransactionBuilder = TransactionType.General.Builder(null)
// TODO: Have some way of restricting this to states the caller controls // TODO: Have some way of restricting this to states the caller controls
try { try {
Cash().generateSpend(builder, Amount(req.pennies, req.tokenDef.product), req.owner, Cash().generateSpend(builder, Amount(req.pennies, req.tokenDef.product), req.owner,
@ -185,7 +185,7 @@ class WalletMonitorService(net: MessagingService, val smm: StateMachineManager,
// TODO: Make a lightweight protocol that manages this workflow, rather than embedding it directly in the service // TODO: Make a lightweight protocol that manages this workflow, rather than embedding it directly in the service
private fun exitCash(req: ClientToServiceCommand.ExitCash): TransactionBuildResult { private fun exitCash(req: ClientToServiceCommand.ExitCash): TransactionBuildResult {
val builder: TransactionBuilder = TransactionType.General.Builder() val builder: TransactionBuilder = TransactionType.General.Builder(null)
val issuer = PartyAndReference(services.storageService.myLegalIdentity, req.issueRef) val issuer = PartyAndReference(services.storageService.myLegalIdentity, req.issueRef)
Cash().generateExit(builder, Amount(req.pennies, Issued(issuer, req.currency)), Cash().generateExit(builder, Amount(req.pennies, Issued(issuer, req.currency)),
issuer.party.owningKey, services.walletService.currentWallet.statesOfType<Cash.State>()) issuer.party.owningKey, services.walletService.currentWallet.statesOfType<Cash.State>())

View File

@ -39,6 +39,7 @@ import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
/** /**
@ -407,7 +408,14 @@ class TwoPartyTradeProtocolTests {
bobResult.get() bobResult.get()
} }
assertTrue(e.cause is TransactionVerificationException) assertTrue(e.cause is TransactionVerificationException)
assertTrue(e.cause!!.cause!!.message!!.contains(expectedMessageSubstring)) assertNotNull(e.cause!!.cause)
assertNotNull(e.cause!!.cause!!.message)
val underlyingMessage = e.cause!!.cause!!.message!!
if (underlyingMessage.contains(expectedMessageSubstring)) {
assertTrue(underlyingMessage.contains(expectedMessageSubstring))
} else {
assertEquals(expectedMessageSubstring, underlyingMessage)
}
} }
private fun insertFakeTransactions( private fun insertFakeTransactions(
@ -425,8 +433,8 @@ class TwoPartyTradeProtocolTests {
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer( private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
withError: Boolean, withError: Boolean,
owner: PublicKey = BOB_PUBKEY, owner: PublicKey = BOB_PUBKEY): Pair<Wallet, List<WireTransaction>> {
issuer: PartyAndReference = DUMMY_CASH_ISSUER): Pair<Wallet, List<WireTransaction>> { val issuer = DUMMY_CASH_ISSUER
// Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she // Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she
// wants to sell to Bob. // wants to sell to Bob.
val eb1 = transaction { val eb1 = transaction {
@ -435,6 +443,9 @@ class TwoPartyTradeProtocolTests {
output("elbonian money 2") { 1000.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } output("elbonian money 2") { 1000.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
if (!withError) if (!withError)
command(DUMMY_CASH_ISSUER_KEY.public) { Cash.Commands.Issue() } command(DUMMY_CASH_ISSUER_KEY.public) { Cash.Commands.Issue() }
else
// Put a broken command on so at least a signature is created
command(DUMMY_CASH_ISSUER_KEY.public) { Cash.Commands.Move() }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
if (withError) { if (withError) {
this.fails() this.fails()
@ -475,7 +486,7 @@ class TwoPartyTradeProtocolTests {
} }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue(notary) } command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue(notary) }
if (!withError) if (!withError)
timestamp(time = TEST_TX_TIME, notary = notary.owningKey) timestamp(time = TEST_TX_TIME)
if (attachmentID != null) if (attachmentID != null)
attachment(attachmentID) attachment(attachmentID)
if (withError) { if (withError) {

View File

@ -106,7 +106,7 @@ class NodeInterestRatesTest {
val (n1, n2) = net.createTwoNodes() val (n1, n2) = net.createTwoNodes()
n2.findService<NodeInterestRates.Service>().oracle.knownFixes = TEST_DATA n2.findService<NodeInterestRates.Service>().oracle.knownFixes = TEST_DATA
val tx = TransactionType.General.Builder() val tx = TransactionType.General.Builder(null)
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val protocol = RatesFixProtocol(tx, n2.info.identity, fixOf, "0.675".bd, "0.1".bd) val protocol = RatesFixProtocol(tx, n2.info.identity, fixOf, "0.675".bd, "0.1".bd)
LogHelper.setLevel("rates") LogHelper.setLevel("rates")
@ -122,5 +122,5 @@ class NodeInterestRatesTest {
assertEquals("0.678".bd, fix.value) assertEquals("0.678".bd, fix.value)
} }
private fun makeTX() = TransactionType.General.Builder().withItems(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY) private fun makeTX() = TransactionType.General.Builder(DUMMY_NOTARY).withItems(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY)
} }

View File

@ -232,8 +232,8 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
apply { apply {
val freshKey = services.keyManagementService.freshKey() val freshKey = services.keyManagementService.freshKey()
val state = TestState(factory.create(TestProtocolLogic::class.java, increment), instant) val state = TestState(factory.create(TestProtocolLogic::class.java, increment), instant)
val usefulTX = TransactionType.General.Builder(DUMMY_NOTARY).apply { val usefulTX = TransactionType.General.Builder(null).apply {
addOutputState(state) addOutputState(state, DUMMY_NOTARY)
addCommand(Command(), freshKey.public) addCommand(Command(), freshKey.public)
signWith(freshKey) signWith(freshKey)
}.toSignedTransaction() }.toSignedTransaction()

View File

@ -107,7 +107,7 @@ fun issueState(node: AbstractNode): StateAndRef<*> {
fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<DummyContract.MultiOwnerState> { fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<DummyContract.MultiOwnerState> {
val state = TransactionState(DummyContract.MultiOwnerState(0, val state = TransactionState(DummyContract.MultiOwnerState(0,
listOf(nodeA.info.identity.owningKey, nodeB.info.identity.owningKey)), DUMMY_NOTARY) listOf(nodeA.info.identity.owningKey, nodeB.info.identity.owningKey)), DUMMY_NOTARY)
val tx = TransactionType.NotaryChange.Builder().withItems(state) val tx = TransactionType.NotaryChange.Builder(DUMMY_NOTARY).withItems(state)
tx.signWith(nodeA.storage.myLegalIdentityKey) tx.signWith(nodeA.storage.myLegalIdentityKey)
tx.signWith(nodeB.storage.myLegalIdentityKey) tx.signWith(nodeB.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY) tx.signWith(DUMMY_NOTARY_KEY)
@ -120,7 +120,7 @@ fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> { fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary) val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
tx.setTime(Instant.now(), notary, 30.seconds) tx.setTime(Instant.now(), 30.seconds)
tx.signWith(node.storage.myLegalIdentityKey) tx.signWith(node.storage.myLegalIdentityKey)
val stx = tx.toSignedTransaction(false) val stx = tx.toSignedTransaction(false)
node.services.recordTransactions(listOf(stx)) node.services.recordTransactions(listOf(stx))

View File

@ -1,6 +1,6 @@
package com.r3corda.node.services package com.r3corda.node.services
import com.r3corda.core.contracts.TimestampCommand import com.r3corda.core.contracts.Timestamp
import com.r3corda.core.contracts.TransactionType import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.seconds import com.r3corda.core.seconds
import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY
@ -39,8 +39,8 @@ class NotaryServiceTests {
@Test fun `should sign a unique transaction with a valid timestamp`() { @Test fun `should sign a unique transaction with a valid timestamp`() {
val stx = run { val stx = run {
val inputState = issueState(clientNode) val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState) val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
tx.setTime(Instant.now(), DUMMY_NOTARY, 30.seconds) tx.setTime(Instant.now(), 30.seconds)
tx.signWith(clientNode.keyPair!!) tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false) tx.toSignedTransaction(false)
} }
@ -56,7 +56,7 @@ class NotaryServiceTests {
@Test fun `should sign a unique transaction without a timestamp`() { @Test fun `should sign a unique transaction without a timestamp`() {
val stx = run { val stx = run {
val inputState = issueState(clientNode) val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState) val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
tx.signWith(clientNode.keyPair!!) tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false) tx.toSignedTransaction(false)
} }
@ -72,8 +72,8 @@ class NotaryServiceTests {
@Test fun `should report error for transaction with an invalid timestamp`() { @Test fun `should report error for transaction with an invalid timestamp`() {
val stx = run { val stx = run {
val inputState = issueState(clientNode) val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState) val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
tx.setTime(Instant.now().plusSeconds(3600), DUMMY_NOTARY, 30.seconds) tx.setTime(Instant.now().plusSeconds(3600), 30.seconds)
tx.signWith(clientNode.keyPair!!) tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false) tx.toSignedTransaction(false)
} }
@ -88,31 +88,10 @@ class NotaryServiceTests {
} }
@Test fun `should report error for transaction with more than one timestamp`() {
val stx = run {
val inputState = issueState(clientNode)
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)
tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
}
val protocol = NotaryProtocol.Client(stx)
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork()
val ex = assertFailsWith(ExecutionException::class) { future.get() }
val error = (ex.cause as NotaryException).error
assertTrue(error is NotaryError.MoreThanOneTimestamp)
}
@Test fun `should report conflict for a duplicate transaction`() { @Test fun `should report conflict for a duplicate transaction`() {
val stx = run { val stx = run {
val inputState = issueState(clientNode) val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState) val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
tx.signWith(clientNode.keyPair!!) tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false) tx.toSignedTransaction(false)
} }

View File

@ -1,6 +1,6 @@
package com.r3corda.node.services package com.r3corda.node.services
import com.r3corda.core.contracts.TimestampCommand import com.r3corda.core.contracts.Timestamp
import com.r3corda.core.node.services.TimestampChecker import com.r3corda.core.node.services.TimestampChecker
import com.r3corda.core.seconds import com.r3corda.core.seconds
import org.junit.Test import org.junit.Test
@ -17,8 +17,8 @@ class TimestampCheckerTests {
@Test @Test
fun `should return true for valid timestamp`() { fun `should return true for valid timestamp`() {
val now = clock.instant() val now = clock.instant()
val timestampPast = TimestampCommand(now - 60.seconds, now - 29.seconds) val timestampPast = Timestamp(now - 60.seconds, now - 29.seconds)
val timestampFuture = TimestampCommand(now + 29.seconds, now + 60.seconds) val timestampFuture = Timestamp(now + 29.seconds, now + 60.seconds)
assertTrue { timestampChecker.isValid(timestampPast) } assertTrue { timestampChecker.isValid(timestampPast) }
assertTrue { timestampChecker.isValid(timestampFuture) } assertTrue { timestampChecker.isValid(timestampFuture) }
} }
@ -26,8 +26,8 @@ class TimestampCheckerTests {
@Test @Test
fun `should return false for invalid timestamp`() { fun `should return false for invalid timestamp`() {
val now = clock.instant() val now = clock.instant()
val timestampPast = TimestampCommand(now - 60.seconds, now - 31.seconds) val timestampPast = Timestamp(now - 60.seconds, now - 31.seconds)
val timestampFuture = TimestampCommand(now + 31.seconds, now + 60.seconds) val timestampFuture = Timestamp(now + 31.seconds, now + 60.seconds)
assertFalse { timestampChecker.isValid(timestampPast) } assertFalse { timestampChecker.isValid(timestampPast) }
assertFalse { timestampChecker.isValid(timestampFuture) } assertFalse { timestampChecker.isValid(timestampFuture) }
} }

View File

@ -39,7 +39,7 @@ class ValidatingNotaryServiceTests {
@Test fun `should report error for invalid transaction dependency`() { @Test fun `should report error for invalid transaction dependency`() {
val stx = run { val stx = run {
val inputState = issueInvalidState(clientNode) val inputState = issueInvalidState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState) val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
tx.signWith(clientNode.keyPair!!) tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false) tx.toSignedTransaction(false)
} }
@ -59,7 +59,7 @@ class ValidatingNotaryServiceTests {
val inputState = issueState(clientNode) val inputState = issueState(clientNode)
val command = Command(DummyContract.Commands.Move(), expectedMissingKey) val command = Command(DummyContract.Commands.Move(), expectedMissingKey)
val tx = TransactionType.General.Builder().withItems(inputState, command) val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState, command)
tx.signWith(clientNode.keyPair!!) tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false) tx.toSignedTransaction(false)
} }

View File

@ -71,21 +71,21 @@ class WalletWithCashTest {
fun basics() { fun basics() {
// A tx that sends us money. // A tx that sends us money.
val freshKey = services.keyManagementService.freshKey() val freshKey = services.keyManagementService.freshKey()
val usefulTX = TransactionType.General.Builder().apply { val usefulTX = TransactionType.General.Builder(null).apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY) Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
}.toSignedTransaction() }.toSignedTransaction()
val myOutput = usefulTX.toLedgerTransaction(services).outRef<Cash.State>(0) val myOutput = usefulTX.toLedgerTransaction(services).outRef<Cash.State>(0)
// A tx that spends our money. // A tx that spends our money.
val spendTX = TransactionType.General.Builder().apply { val spendTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
Cash().generateSpend(this, 80.DOLLARS, BOB_PUBKEY, listOf(myOutput)) Cash().generateSpend(this, 80.DOLLARS, BOB_PUBKEY, listOf(myOutput))
signWith(freshKey) signWith(freshKey)
signWith(DUMMY_NOTARY_KEY) signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction() }.toSignedTransaction()
// A tx that doesn't send us anything. // A tx that doesn't send us anything.
val irrelevantTX = TransactionType.General.Builder().apply { val irrelevantTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY) Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
signWith(DUMMY_NOTARY_KEY) signWith(DUMMY_NOTARY_KEY)
@ -112,6 +112,7 @@ class WalletWithCashTest {
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply { val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public))) addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
signWith(freshKey) signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction() }.toSignedTransaction()
wallet.notify(dummyIssue.tx) wallet.notify(dummyIssue.tx)
@ -121,6 +122,7 @@ class WalletWithCashTest {
val dummyIssue2 = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply { val dummyIssue2 = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public))) addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
signWith(freshKey) signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction() }.toSignedTransaction()
assertThatThrownBy { assertThatThrownBy {
@ -139,6 +141,7 @@ class WalletWithCashTest {
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply { val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public))) addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
signWith(freshKey) signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction() }.toSignedTransaction()
wallet.notify(dummyIssue.tx) wallet.notify(dummyIssue.tx)

View File

@ -34,7 +34,7 @@ class DataVendingServiceTests {
network.runNetwork() network.runNetwork()
// Generate an issuance transaction // Generate an issuance transaction
val ptx = TransactionType.General.Builder() val ptx = TransactionType.General.Builder(null)
Cash().generateIssue(ptx, Amount(100, Issued(deposit, USD)), beneficiary, DUMMY_NOTARY) Cash().generateIssue(ptx, Amount(100, Issued(deposit, USD)), beneficiary, DUMMY_NOTARY)
// Complete the cash transaction, and then manually relay it // Complete the cash transaction, and then manually relay it
@ -66,7 +66,7 @@ class DataVendingServiceTests {
network.runNetwork() network.runNetwork()
// Generate an issuance transaction // Generate an issuance transaction
val ptx = TransactionType.General.Builder() val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
Cash().generateIssue(ptx, Amount(100, Issued(deposit, USD)), beneficiary, DUMMY_NOTARY) Cash().generateIssue(ptx, Amount(100, Issued(deposit, USD)), beneficiary, DUMMY_NOTARY)
// The transaction tries issuing MEGA_CORP cash, but we aren't the issuer, so it's invalid // The transaction tries issuing MEGA_CORP cash, but we aren't the issuer, so it's invalid

View File

@ -85,11 +85,11 @@ fun main(args: Array<String>) {
val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, apiAddr, config, networkMapAddress, val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, apiAddr, config, networkMapAddress,
advertisedServices, DemoClock()).setup().start() } advertisedServices, DemoClock()).setup().start() }
node.networkMapRegistrationFuture.get() node.networkMapRegistrationFuture.get()
val notary = node.services.networkMapCache.notaryNodes[0] val notaryNode = node.services.networkMapCache.notaryNodes[0]
// Make a garbage transaction that includes a rate fix. // Make a garbage transaction that includes a rate fix.
val tx = TransactionType.General.Builder() val tx = TransactionType.General.Builder(notaryNode.identity)
tx.addOutputState(TransactionState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.keyManagement.freshKey().public), notary.identity)) tx.addOutputState(TransactionState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.keyManagement.freshKey().public), notaryNode.identity))
val protocol = RatesFixProtocol(tx, oracleNode.identity, fixOf, expectedRate, rateTolerance) val protocol = RatesFixProtocol(tx, oracleNode.identity, fixOf, expectedRate, rateTolerance)
node.smm.add("demo.ratefix", protocol).get() node.smm.add("demo.ratefix", protocol).get()
node.stop() node.stop()

View File

@ -208,7 +208,7 @@ private fun runBuyer(node: Node, amount: Amount<Currency>) {
// //
// TODO: At some point this demo should be extended to have a central bank node. // TODO: At some point this demo should be extended to have a central bank node.
node.services.fillWithSomeTestCash(3000.DOLLARS, node.services.fillWithSomeTestCash(3000.DOLLARS,
notary = node.info.identity, // In this demo, the buyer and notary are the same. outputNotary = node.info.identity, // In this demo, the buyer and notary are the same.
ownedBy = node.services.keyManagementService.freshKey().public) ownedBy = node.services.keyManagementService.freshKey().public)
// Wait around until a node asks to start a trade with us. In a real system, this part would happen out of band // Wait around until a node asks to start a trade with us. In a real system, this part would happen out of band
@ -350,7 +350,7 @@ private class TraderDemoProtocolSeller(val otherSide: Party,
tx.addAttachment(serviceHub.storageService.attachments.openAttachment(PROSPECTUS_HASH)!!.id) tx.addAttachment(serviceHub.storageService.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
// Requesting timestamping, all CP must be timestamped. // Requesting timestamping, all CP must be timestamped.
tx.setTime(Instant.now(), notaryNode.identity, 30.seconds) tx.setTime(Instant.now(), 30.seconds)
// Sign it as ourselves. // Sign it as ourselves.
tx.signWith(keyPair) tx.signWith(keyPair)
@ -368,7 +368,7 @@ private class TraderDemoProtocolSeller(val otherSide: Party,
// Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works. // Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works.
val move: SignedTransaction = run { val move: SignedTransaction = run {
val builder = TransactionType.General.Builder() val builder = TransactionType.General.Builder(notaryNode.identity)
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy) CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy)
builder.signWith(keyPair) builder.signWith(keyPair)
val notarySignature = subProtocol(NotaryProtocol.Client(builder.toSignedTransaction(false))) val notarySignature = subProtocol(NotaryProtocol.Client(builder.toSignedTransaction(false)))