Require notary to be explicitely stated on each transaction

This commit is contained in:
Ross Nicoll 2016-08-04 19:00:17 +01:00
parent a2dff5488f
commit a3d37a4d00
38 changed files with 233 additions and 121 deletions

View File

@ -340,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

@ -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>()

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(), null, 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)) })
}
@ -212,7 +212,7 @@ class CommercialPaperTestsGeneric {
// 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,7 +222,7 @@ class CommercialPaperTestsGeneric {
}
fun makeRedeemTX(time: Instant): SignedTransaction {
val ptx = TransactionType.General.Builder()
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)

View File

@ -299,7 +299,7 @@ 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) {

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

@ -16,13 +16,13 @@ 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(),
@ -33,6 +33,10 @@ open class TransactionBuilder(
@Deprecated("use timestamp instead")
val time: Timestamp? get() = timestamp
init {
notary?.let { signers.add(it.owningKey) }
}
/**
* Creates a copy of the builder.
*/
@ -64,6 +68,7 @@ open class TransactionBuilder(
= 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" }
this.timestamp = newTimestamp
}
@ -120,7 +125,7 @@ open class TransactionBuilder(
}
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
ArrayList(outputs), ArrayList(commands), signers.toList(), type, timestamp)
ArrayList(outputs), ArrayList(commands), notary, signers.toList(), type, timestamp)
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
if (checkSufficientSignatures) {
@ -136,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, timestamp, 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)
@ -45,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.
@ -78,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

@ -97,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
@ -49,6 +50,7 @@ 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,
val timestamp: Timestamp?) : NamedByHash {
@ -117,7 +119,18 @@ 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) {
// Take a best guess at where the signatures are required from, for debugging
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 ->
@ -128,10 +141,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
if (missing.contains(this))
missingElements.add("notary")
}
throw SignatureException("Missing signatures for ${missingElements} on transaction ${id.prefixChars()} for ${missing.map { it.toStringShort() }}")
}
return missing
return missingElements
}
/** Returns the same transaction but with an additional (unchecked) signature */
@ -153,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()
@ -175,8 +186,10 @@ 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?,

View File

@ -230,6 +230,7 @@ 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)
@ -261,11 +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, timestamp)
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,7 +130,12 @@ data class TestTransactionDSLInterpreter private constructor(
}
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()
}
return EnforceVerifyOrFail.Token
}
@ -305,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

@ -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

@ -52,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()
@ -83,19 +85,6 @@ object NotaryProtocol {
check(sig.signer == notaryParty) { "Notary result not signed by the correct service" }
sig.verifyWithECDSA(data)
}
private fun findNotaryParty(): Party {
val wtx = stx.tx
val firstStateRef = wtx.inputs.firstOrNull()
var maybeNotaryParty: Party? = if (firstStateRef == null)
null
else
serviceHub.loadState(firstStateRef).notary
val notaryParty = maybeNotaryParty ?: 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" }
return notaryParty
}
}
/**
@ -133,12 +122,11 @@ object NotaryProtocol {
send(otherSide, sendSessionID, result)
}
private fun validateTimestamp(tx: WireTransaction) =
private fun validateTimestamp(tx: WireTransaction) {
if (tx.timestamp != null
&& !timestampChecker.isValid(tx.timestamp))
throw NotaryException(NotaryError.TimestampInvalid())
else
Unit
}
/**
* No pre-commit processing is done. Transaction is not checked for contract-validity, as that would require fully
@ -212,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

@ -101,11 +101,9 @@ object TwoPartyDealProtocol {
untrustedPartialTX.validate { stx ->
progressTracker.nextStep()
// TODO: Verify the notary on the transaction is set correctly
// Check that the tx proposed by the buyer is valid.
val missingSigs = stx.verifySignatures(throwIfSignaturesAreMissing = false)
if (missingSigs != setOf(myKeyPair.public))
if (missingSigs != setOf(myKeyPair.public, notaryNode.identity.owningKey))
throw SignatureException("The set of missing signatures is not as expected: $missingSigs")
val wtx: WireTransaction = stx.tx

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)

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)

View File

@ -135,7 +135,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

@ -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

@ -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()

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)

View File

@ -39,7 +39,7 @@ 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)
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,7 +72,7 @@ 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)
val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
tx.setTime(Instant.now().plusSeconds(3600), 30.seconds)
tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
@ -91,7 +91,7 @@ class NotaryServiceTests {
@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

@ -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
@ -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)))