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.r3corda.contracts.asset.*;
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.clauses.*;
import com.r3corda.core.crypto.*;
@ -219,15 +222,14 @@ public class JavaCommercialPaper implements Contract {
if (!cmd.getSigners().contains(input.getOwner()))
throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP");
Party notary = cmd.getValue().notary;
TimestampCommand timestampCommand = tx.getTimestampBy(notary);
Instant time = null == timestampCommand
Timestamp timestamp = tx.getTimestamp();
Instant time = null == timestamp
? null
: timestampCommand.getBefore();
: timestamp.getBefore();
Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutputs(), input.getOwner());
requireThat(require -> {
require.by("must be timestamped", timestampCommand != null);
require.by("must be timestamped", timestamp != null);
require.by("received amount equals the face value: "
+ received + " vs " + input.getFaceValue(), received.equals(input.getFaceValue()));
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);
State output = single(outputs);
Party notary = cmd.getValue().notary;
TimestampCommand timestampCommand = tx.getTimestampBy(notary);
Timestamp timestampCommand = tx.getTimestamp();
Instant time = null == timestampCommand
? null
: 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) {
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
TransactionState output = new TransactionState<>(state, notary);
return new TransactionType.General.Builder().withItems(output, new Command(new Commands.Issue(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 {

View File

@ -109,8 +109,7 @@ class CommercialPaper : Contract {
token: Issued<Terms>): Set<CommandData> {
val consumedCommands = super.verify(tx, inputs, outputs, commands, token)
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: TimestampCommand? = tx.getTimestampBy(command.value.notary)
val timestamp = tx.timestamp
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
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)
// before requiring a single command
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: TimestampCommand? = tx.getTimestampBy(command.value.notary)
val timestamp = tx.timestamp
val input = inputs.single()
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
// it for cash on or after the maturity date.
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 cmdVal = command.value
val timestamp: TimestampCommand? = when (cmdVal) {
is Commands.Issue -> tx.getTimestampBy(cmdVal.notary)
is Commands.Redeem -> tx.getTimestampBy(cmdVal.notary)
else -> null
}
val timestamp: Timestamp? = tx.timestamp
for ((inputs, outputs, key) in groups) {
when (command.value) {

View File

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

View File

@ -403,7 +403,7 @@ class Obligation<P> : Contract {
if (input is State<P>) {
val actualOutput = outputs[stateIdx]
val deadline = input.dueBefore
val timestamp: TimestampCommand? = tx.timestamp
val timestamp = tx.timestamp
val expectedOutput = input.copy(lifecycle = expectedOutputLifecycle)
requireThat {
@ -541,7 +541,7 @@ class Obligation<P> : Contract {
}
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 amount = Amount(amountIssued.quantity, currency)
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
// different notaries, or at least group states by notary and take the set with the
// 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 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.
val currency = amount.token
val acceptableCoins = run {
var acceptableCoins = run {
val ofCurrency = assetsStates.filter { it.state.data.amount.token.product == currency }
if (onlyFromParties != null)
ofCurrency.filter { it.state.data.deposit.party in onlyFromParties }
else
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 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.serialization.OpaqueBytes
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.util.*
@ -24,10 +26,11 @@ import java.util.*
*
* 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!).
*/
fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>,
notary: Party = DUMMY_NOTARY,
outputNotary: Party = DUMMY_NOTARY,
atLeastThisManyStates: Int = 3,
atMostThisManyStates: Int = 10,
rng: Random = Random(),
@ -40,8 +43,8 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>,
// We will allocate one state to one transaction, for simplicities sake.
val cash = Cash()
val transactions: List<SignedTransaction> = amounts.map { pennies ->
val issuance = TransactionType.General.Builder()
cash.generateIssue(issuance, Amount(pennies, Issued(DUMMY_CASH_ISSUER.copy(reference = ref), howMuch.token)), myKey, notary)
val issuance = TransactionType.General.Builder(null)
cash.generateIssue(issuance, Amount(pennies, Issued(DUMMY_CASH_ISSUER.copy(reference = ref), howMuch.token)), myKey, outputNotary)
issuance.signWith(DUMMY_CASH_ISSUER_KEY)
return@map issuance.toSignedTransaction(true)

View File

@ -254,7 +254,7 @@ object TwoPartyTradeProtocol {
}
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.
val wallet = serviceHub.walletService.currentWallet
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
// to have one.
val currentTime = serviceHub.clock.instant()
ptx.setTime(currentTime, notary, 30.seconds)
ptx.setTime(currentTime, 30.seconds)
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>>> {
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)) })
}
@ -205,14 +205,14 @@ class CommercialPaperTestsGeneric {
val issuance = bigCorpServices.storageService.myLegalIdentity.ref(1)
val issueTX: SignedTransaction =
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(DUMMY_NOTARY_KEY)
}.toSignedTransaction()
// Alice pays $9000 to BigCorp to own some of their debt.
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>())
CommercialPaper().generateMove(ptx, issueTX.tx.outRef(0), aliceServices.key.public)
ptx.signWith(bigCorpServices.key)
@ -222,8 +222,8 @@ class CommercialPaperTestsGeneric {
}
fun makeRedeemTX(time: Instant): SignedTransaction {
val ptx = TransactionType.General.Builder()
ptx.setTime(time, DUMMY_NOTARY, 30.seconds)
val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
ptx.setTime(time, 30.seconds)
CommercialPaper().generateRedeem(ptx, moveTX.tx.outRef(1), bigCorpWallet.statesOfType<Cash.State>())
ptx.signWith(aliceServices.key)
ptx.signWith(bigCorpServices.key)

View File

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

View File

@ -92,7 +92,7 @@ class CashTests {
}
// 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)
assertTrue(ptx.inputStates().isEmpty())
val s = ptx.outputStates()[0].data as Cash.State
@ -104,7 +104,7 @@ class CashTests {
// Test issuance from the issuance definition
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)
assertTrue(templatePtx.inputStates().isEmpty())
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
@ -171,14 +171,14 @@ class CashTests {
@Test(expected = IllegalStateException::class)
fun `reject issuance with inputs`() {
// 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)
ptx.signWith(MINI_CORP_KEY)
val tx = ptx.toSignedTransaction()
// 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))
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.
*/
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)
return tx.toWireTransaction()
}
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)
return tx.toWireTransaction()
}
@ -454,7 +454,7 @@ class CashTests {
@Test
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))
assertEquals(WALLET[2].ref, tx.inputStates()[0])
}

View File

@ -256,7 +256,7 @@ class ObligationTests {
fun `generate payment net transaction with remainder`() {
val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_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)
signWith(ALICE_KEY)
signWith(BOB_KEY)
@ -274,7 +274,7 @@ class ObligationTests {
val dueBefore = TEST_TX_TIME - Duration.ofDays(7)
// 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,
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
signWith(MINI_CORP_KEY)
@ -306,13 +306,13 @@ class ObligationTests {
/** Test generating a transaction to settle an obligation. */
@Test
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)
signWith(MEGA_CORP_KEY)
}.toSignedTransaction().tx
// 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,
beneficiary = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
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>) =
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.
*

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
* public keys are identified in the containing [Command] object. The true time must be between (after, before).
* If present in a transaction, contains a time that was verified by the uniqueness service. The true time must be
* between (after, before).
*/
// TODO: Timestamps are now always provided by the consensus service for the transaction, rather than potentially
// 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 {
data class Timestamp(val after: Instant?, val before: Instant?) {
init {
if (after == null && before == null)
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
* 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,
* an output state can be added by just passing in a [ContractState] a [TransactionState] with the
* default notary will be generated automatically.
* @param notary Notary used for the transaction. If null, this indicates the transaction DOES NOT have a notary.
* When this is set to a non-null value, an output state can be added by just passing in a [ContractState] a
* [TransactionState] with this notary specified will be generated automatically.
*/
open class TransactionBuilder(
protected val type: TransactionType = TransactionType.General(),
protected val notary: Party? = null,
var notary: Party? = null,
protected val inputs: MutableList<StateRef> = arrayListOf(),
protected val attachments: MutableList<SecureHash> = arrayListOf(),
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
protected val commands: MutableList<Command> = arrayListOf(),
protected val signers: MutableSet<PublicKey> = mutableSetOf()) {
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.
@ -42,7 +48,8 @@ open class TransactionBuilder(
attachments = ArrayList(attachments),
outputs = ArrayList(outputs),
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
* 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" }
commands.removeAll { it.value is TimestampCommand }
addCommand(TimestampCommand(time, timeTolerance), authority.owningKey)
this.timestamp = newTimestamp
}
/** 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),
ArrayList(outputs), ArrayList(commands), signers.toList(), type)
ArrayList(outputs), ArrayList(commands), notary, signers.toList(), type, timestamp)
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
if (checkSufficientSignatures) {
@ -131,6 +141,7 @@ open class TransactionBuilder(
fun addInputState(stateRef: StateRef, notary: Party) {
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)
inputs.add(stateRef)
}

View File

@ -23,7 +23,7 @@ fun WireTransaction.toLedgerTransaction(services: ServiceHub): LedgerTransaction
services.storageService.attachments.openAttachment(it) ?: throw FileNotFoundException(it.toString())
}
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.
*/
fun verify(tx: LedgerTransaction) {
require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." }
val missing = verifySigners(tx)
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
verifyTransaction(tx)
@ -24,9 +25,7 @@ sealed class TransactionType {
/** Check that the list of signers includes all the necessary keys */
fun verifySigners(tx: LedgerTransaction): Set<PublicKey> {
val timestamp = tx.commands.noneOrSingle { it.value is TimestampCommand }
val timestampKey = timestamp?.signers.orEmpty()
val notaryKey = (tx.inputs.map { it.state.notary.owningKey } + timestampKey).toSet()
val notaryKey = tx.inputs.map { it.state.notary.owningKey }.toSet()
if (notaryKey.size > 1) throw TransactionVerificationException.MoreThanOneNotary(tx)
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 */
class General : TransactionType() {
/** 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.
* If any contract fails to verify, the whole transaction is considered to be invalid.
*/
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()
// 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]
* 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<*>) {
signers.addAll(stateAndRef.state.data.participants)
super.addInputState(stateAndRef)

View File

@ -17,7 +17,8 @@ data class TransactionForContract(val inputs: List<ContractState>,
val attachments: List<Attachment>,
val commands: List<AuthenticatedObject<CommandData>>,
val origHash: SecureHash,
val inputNotary: Party? = null) {
val inputNotary: Party? = null,
val timestamp: Timestamp? = null) {
override fun hashCode() = origHash.hashCode()
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.
*/
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() {
@ -108,4 +97,7 @@ sealed class TransactionVerificationException(val tx: LedgerTransaction, cause:
override fun toString() = "Signers missing: ${missing.map { it.toStringShort() }}"
}
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.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party
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.serialization.SerializedBytes
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 java.security.PublicKey
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
@ -48,8 +50,10 @@ data class WireTransaction(val inputs: List<StateRef>,
val attachments: List<SecureHash>,
val outputs: List<TransactionState<ContractState>>,
val commands: List<Command>,
val notary: Party?,
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.
@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.
val missing = getMissingSignatures()
if (missing.isNotEmpty() && throwIfSignaturesAreMissing)
throw SignatureException("Missing signatures on transaction ${id.prefixChars()} for: ${missing.map { it.toStringShort() }}")
if (missing.isNotEmpty() && throwIfSignaturesAreMissing) {
val missingElements = getMissingKeyDescriptions(missing)
throw SignatureException("Missing signatures for ${missingElements} on transaction ${id.prefixChars()} for ${missing.toStringsShort()}")
}
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 */
fun withAdditionalSignature(sig: DigitalSignature.WithKey): SignedTransaction {
// 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.
*/
private fun getMissingSignatures(): Set<PublicKey> {
val notaryKey = tx.notary?.owningKey
val requiredKeys = tx.signers.toSet()
val sigKeys = sigs.map { it.by }.toSet()
@ -161,10 +186,13 @@ data class LedgerTransaction(
val commands: List<AuthenticatedObject<CommandData>>,
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
val attachments: List<Attachment>,
/** The hash of the original serialised WireTransaction */
/** The hash of the original serialised WireTransaction. */
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. */
val signers: List<PublicKey>,
val timestamp: Timestamp?,
val type: TransactionType
) : NamedByHash {
@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 */
fun toTransactionForContract(): TransactionForContract {
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
import com.r3corda.core.contracts.TimestampCommand
import com.r3corda.core.contracts.Timestamp
import com.r3corda.core.seconds
import com.r3corda.core.until
import java.time.Clock
@ -11,7 +11,7 @@ import java.time.Duration
*/
class TimestampChecker(val clock: Clock = Clock.systemUTC(),
val tolerance: Duration = 30.seconds) {
fun isValid(timestampCommand: TimestampCommand): Boolean {
fun isValid(timestampCommand: Timestamp): Boolean {
val before = timestampCommand.before
val after = timestampCommand.after

View File

@ -230,8 +230,10 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
kryo.writeClassAndObject(output, obj.attachments)
kryo.writeClassAndObject(output, obj.outputs)
kryo.writeClassAndObject(output, obj.commands)
kryo.writeClassAndObject(output, obj.notary)
kryo.writeClassAndObject(output, obj.signers)
kryo.writeClassAndObject(output, obj.type)
kryo.writeClassAndObject(output, obj.timestamp)
}
@Suppress("UNCHECKED_CAST")
@ -260,10 +262,12 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
kryo.useClassLoader(classLoader) {
val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>>
val commands = kryo.readClassAndObject(input) as List<Command>
val notary = kryo.readClassAndObject(input) as Party?
val signers = kryo.readClassAndObject(input) as List<PublicKey>
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(
transactionLabel: String? = null,
transactionBuilder: TransactionBuilder = TransactionBuilder(),
transactionBuilder: TransactionBuilder = TransactionBuilder(notary = DUMMY_NOTARY),
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = ledger { this.transaction(transactionLabel, transactionBuilder, dsl) }

View File

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

View File

@ -130,10 +130,19 @@ data class TestTransactionDSLInterpreter private constructor(
}
override fun verifies(): EnforceVerifyOrFail {
toWireTransaction().toLedgerTransaction(services).verify()
// 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()
}
return EnforceVerifyOrFail.Token
}
override fun timestamp(data: Timestamp) {
transactionBuilder.setTime(data)
}
override fun tweak(
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = dsl(TransactionDSL(copy()))
@ -301,15 +310,18 @@ data class TestLedgerDSLInterpreter private constructor (
* @return List of [SignedTransaction]s.
*/
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: List<KeyPair>) = transactionsToSign.map { wtx ->
val allPubKeys = wtx.signers.toMutableSet()
check(wtx.signers.isNotEmpty())
val bits = wtx.serialize()
require(bits == wtx.serialized)
val signatures = ArrayList<DigitalSignature.WithKey>()
for (key in ALL_TEST_KEYS + extraKeys) {
if (key.public in allPubKeys) {
signatures += key.signWithECDSA(bits)
allPubKeys -= key.public
}
val keyLookup = HashMap<PublicKey, KeyPair>()
(ALL_TEST_KEYS + extraKeys).forEach {
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)
}

View File

@ -46,6 +46,12 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
*/
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.
* @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.
* @param time The [Instant] of the [TimestampCommand].
* @param tolerance The tolerance of the [TimestampCommand].
* @param notary The notary to sign the command.
*/
@JvmOverloads
fun timestamp(time: Instant, tolerance: Duration = 30.seconds, notary: PublicKey = DUMMY_NOTARY.owningKey) =
timestamp(TimestampCommand(time, tolerance), notary)
/**
* 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)
fun timestamp(time: Instant, tolerance: Duration = 30.seconds) =
timestamp(Timestamp(time, tolerance))
}

View File

@ -37,7 +37,7 @@ object NotaryChangeProtocol: AbstractStateReplacementProtocol<Party>() {
val state = originalState.state
val newState = state.withNotary(modification)
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)
val stx = tx.toSignedTransaction(false)

View File

@ -2,7 +2,8 @@ package com.r3corda.protocols
import co.paralleluniverse.fibers.Suspendable
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.crypto.DigitalSignature
import com.r3corda.core.crypto.Party
@ -51,7 +52,9 @@ object NotaryProtocol {
@Suspendable
override fun call(): DigitalSignature.LegallyIdentifiable {
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 receiveSessionID = random63BitValue()
@ -82,25 +85,6 @@ object NotaryProtocol {
check(sig.signer == notaryParty) { "Notary result not signed by the correct service" }
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) {
val timestampCmd = try {
tx.commands.noneOrSingle { it.value is TimestampCommand } ?: return
} 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))
if (tx.timestamp != null
&& !timestampChecker.isValid(tx.timestamp))
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"
}
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 */
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
// 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))
}
@ -375,7 +375,7 @@ object TwoPartyDealProtocol {
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) {
@Suspendable
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
// to have one.
ptx.setTime(serviceHub.clock.instant(), txState.notary, 30.seconds)
ptx.setTime(serviceHub.clock.instant(), 30.seconds)
}
}
subProtocol(addFixing)

View File

@ -3,6 +3,7 @@ package com.r3corda.core.contracts
import com.r3corda.core.crypto.newSecureRandom
import com.r3corda.core.node.services.testing.MockTransactionStorage
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import com.r3corda.core.testing.MEGA_CORP_KEY
import org.junit.Test
import java.security.KeyPair
@ -26,14 +27,16 @@ class TransactionGraphSearchTests {
* @param signer signer for the two transactions and their commands.
*/
fun buildTransactions(command: CommandData, signer: KeyPair): GraphTransactionStorage {
val originTx = TransactionType.General.Builder().apply {
addOutputState(DummyState(random31BitValue()), DUMMY_NOTARY)
val originTx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
addOutputState(DummyState(random31BitValue()))
addCommand(command, signer.public)
signWith(signer)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction(false)
val inputTx = TransactionType.General.Builder().apply {
val inputTx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
addInputState(originTx.tx.outRef<DummyState>(0))
signWith(signer)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction(false)
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
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))
)
}
@ -88,7 +88,7 @@ class TransactionSerializationTests {
// If the signature was replaced in transit, we don't like it.
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))
tx2.signWith(DUMMY_NOTARY_KEY)
tx2.signWith(DUMMY_KEY_2)
@ -99,10 +99,10 @@ class TransactionSerializationTests {
@Test
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_NOTARY_KEY)
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")
}
val ar = createARFromInvoice(invoice.state.data, discountRate, notary)
val tx = TransactionType.General.Builder()
val tx = TransactionType.General.Builder(notary)
tx.addInputState(invoice)
tx.addOutputState(invoice.state.data.copy(assigned = true))
tx.addCommand(Invoice.Commands.Assign(), invoice.state.data.owner.owningKey)
@ -108,7 +108,7 @@ class AccountReceivable : Contract {
override fun verify(tx: TransactionForContract) {
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")
when (command.value) {

View File

@ -74,7 +74,7 @@ class BillOfLadingAgreement : Contract {
override fun verify(tx: TransactionForContract) {
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")
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 {
val state = State(owner, beneficiary, props)
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))
}

View File

@ -113,7 +113,7 @@ class Invoice : Contract {
override fun verify(tx: TransactionForContract) {
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")
when (command.value) {

View File

@ -30,11 +30,6 @@ class LCApplication : Contract {
val inputs = tx.inputs.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) {
is Commands.ApplyForLC -> {
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 {
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)
return txBuilder
}

View File

@ -71,7 +71,7 @@ class LOC : Contract {
override fun verify(tx: TransactionForContract) {
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")
when (command.value) {
@ -146,7 +146,7 @@ class LOC : Contract {
fun generateIssue(beneficiaryPaid: Boolean, issued: Boolean, terminated: Boolean, props: LOCProperties, notary: Party): TransactionBuilder {
val state = State(beneficiaryPaid, issued, terminated, props)
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))
}

View File

@ -85,7 +85,7 @@ class AccountReceivableTests {
}
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(DUMMY_NOTARY_KEY)
}

View File

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

View File

@ -34,7 +34,7 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
val issuance = run {
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)
tx.setTime(Instant.now(), notary.info.identity, 30.seconds)
tx.setTime(Instant.now(), 30.seconds)
tx.signWith(notary.storage.myLegalIdentityKey)
tx.signWith(seller.storage.myLegalIdentityKey)
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
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
try {
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
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)
Cash().generateExit(builder, Amount(req.pennies, Issued(issuer, req.currency)),
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 kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
/**
@ -407,7 +408,14 @@ class TwoPartyTradeProtocolTests {
bobResult.get()
}
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(
@ -425,8 +433,8 @@ class TwoPartyTradeProtocolTests {
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
withError: Boolean,
owner: PublicKey = BOB_PUBKEY,
issuer: PartyAndReference = DUMMY_CASH_ISSUER): Pair<Wallet, List<WireTransaction>> {
owner: PublicKey = BOB_PUBKEY): 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
// wants to sell to Bob.
val eb1 = transaction {
@ -435,6 +443,9 @@ class TwoPartyTradeProtocolTests {
output("elbonian money 2") { 1000.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
if (!withError)
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)
if (withError) {
this.fails()
@ -475,7 +486,7 @@ class TwoPartyTradeProtocolTests {
}
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue(notary) }
if (!withError)
timestamp(time = TEST_TX_TIME, notary = notary.owningKey)
timestamp(time = TEST_TX_TIME)
if (attachmentID != null)
attachment(attachmentID)
if (withError) {

View File

@ -106,7 +106,7 @@ class NodeInterestRatesTest {
val (n1, n2) = net.createTwoNodes()
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 protocol = RatesFixProtocol(tx, n2.info.identity, fixOf, "0.675".bd, "0.1".bd)
LogHelper.setLevel("rates")
@ -122,5 +122,5 @@ class NodeInterestRatesTest {
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 {
val freshKey = services.keyManagementService.freshKey()
val state = TestState(factory.create(TestProtocolLogic::class.java, increment), instant)
val usefulTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
addOutputState(state)
val usefulTX = TransactionType.General.Builder(null).apply {
addOutputState(state, DUMMY_NOTARY)
addCommand(Command(), freshKey.public)
signWith(freshKey)
}.toSignedTransaction()

View File

@ -107,7 +107,7 @@ fun issueState(node: AbstractNode): StateAndRef<*> {
fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<DummyContract.MultiOwnerState> {
val state = TransactionState(DummyContract.MultiOwnerState(0,
listOf(nodeA.info.identity.owningKey, nodeB.info.identity.owningKey)), DUMMY_NOTARY)
val tx = TransactionType.NotaryChange.Builder().withItems(state)
val tx = TransactionType.NotaryChange.Builder(DUMMY_NOTARY).withItems(state)
tx.signWith(nodeA.storage.myLegalIdentityKey)
tx.signWith(nodeB.storage.myLegalIdentityKey)
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<*> {
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)
val stx = tx.toSignedTransaction(false)
node.services.recordTransactions(listOf(stx))

View File

@ -1,6 +1,6 @@
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.seconds
import com.r3corda.core.testing.DUMMY_NOTARY
@ -39,8 +39,8 @@ class NotaryServiceTests {
@Test fun `should sign a unique transaction with a valid timestamp`() {
val stx = run {
val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState)
tx.setTime(Instant.now(), DUMMY_NOTARY, 30.seconds)
val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
tx.setTime(Instant.now(), 30.seconds)
tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
}
@ -56,7 +56,7 @@ class NotaryServiceTests {
@Test fun `should sign a unique transaction without a timestamp`() {
val stx = run {
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.toSignedTransaction(false)
}
@ -72,8 +72,8 @@ class NotaryServiceTests {
@Test fun `should report error for transaction with an invalid timestamp`() {
val stx = run {
val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState)
tx.setTime(Instant.now().plusSeconds(3600), DUMMY_NOTARY, 30.seconds)
val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
tx.setTime(Instant.now().plusSeconds(3600), 30.seconds)
tx.signWith(clientNode.keyPair!!)
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`() {
val stx = run {
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.toSignedTransaction(false)
}

View File

@ -1,6 +1,6 @@
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.seconds
import org.junit.Test
@ -17,8 +17,8 @@ class TimestampCheckerTests {
@Test
fun `should return true for valid timestamp`() {
val now = clock.instant()
val timestampPast = TimestampCommand(now - 60.seconds, now - 29.seconds)
val timestampFuture = TimestampCommand(now + 29.seconds, now + 60.seconds)
val timestampPast = Timestamp(now - 60.seconds, now - 29.seconds)
val timestampFuture = Timestamp(now + 29.seconds, now + 60.seconds)
assertTrue { timestampChecker.isValid(timestampPast) }
assertTrue { timestampChecker.isValid(timestampFuture) }
}
@ -26,8 +26,8 @@ class TimestampCheckerTests {
@Test
fun `should return false for invalid timestamp`() {
val now = clock.instant()
val timestampPast = TimestampCommand(now - 60.seconds, now - 31.seconds)
val timestampFuture = TimestampCommand(now + 31.seconds, now + 60.seconds)
val timestampPast = Timestamp(now - 60.seconds, now - 31.seconds)
val timestampFuture = Timestamp(now + 31.seconds, now + 60.seconds)
assertFalse { timestampChecker.isValid(timestampPast) }
assertFalse { timestampChecker.isValid(timestampFuture) }
}

View File

@ -39,7 +39,7 @@ class ValidatingNotaryServiceTests {
@Test fun `should report error for invalid transaction dependency`() {
val stx = run {
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.toSignedTransaction(false)
}
@ -59,7 +59,7 @@ class ValidatingNotaryServiceTests {
val inputState = issueState(clientNode)
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.toSignedTransaction(false)
}

View File

@ -71,21 +71,21 @@ class WalletWithCashTest {
fun basics() {
// A tx that sends us money.
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)
signWith(MEGA_CORP_KEY)
}.toSignedTransaction()
val myOutput = usefulTX.toLedgerTransaction(services).outRef<Cash.State>(0)
// 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))
signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction()
// 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)
signWith(MEGA_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
@ -112,6 +112,7 @@ class WalletWithCashTest {
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction()
wallet.notify(dummyIssue.tx)
@ -121,6 +122,7 @@ class WalletWithCashTest {
val dummyIssue2 = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction()
assertThatThrownBy {
@ -139,6 +141,7 @@ class WalletWithCashTest {
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
addOutputState(DummyLinearState(thread = thread, participants = listOf(freshKey.public)))
signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction()
wallet.notify(dummyIssue.tx)

View File

@ -34,7 +34,7 @@ class DataVendingServiceTests {
network.runNetwork()
// 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)
// Complete the cash transaction, and then manually relay it
@ -66,7 +66,7 @@ class DataVendingServiceTests {
network.runNetwork()
// 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)
// 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,
advertisedServices, DemoClock()).setup().start() }
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.
val tx = TransactionType.General.Builder()
tx.addOutputState(TransactionState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.keyManagement.freshKey().public), notary.identity))
val tx = TransactionType.General.Builder(notaryNode.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)
node.smm.add("demo.ratefix", protocol).get()
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.
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)
// 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)
// 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.
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.
val move: SignedTransaction = run {
val builder = TransactionType.General.Builder()
val builder = TransactionType.General.Builder(notaryNode.identity)
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy)
builder.signWith(keyPair)
val notarySignature = subProtocol(NotaryProtocol.Client(builder.toSignedTransaction(false)))