mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Require notary to be explicitely stated on each transaction
This commit is contained in:
parent
a2dff5488f
commit
a3d37a4d00
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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>()
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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])
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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}"
|
||||
}
|
||||
}
|
||||
|
@ -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,23 +119,31 @@ 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
|
||||
// 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")
|
||||
}
|
||||
throw SignatureException("Missing signatures for ${missingElements} on transaction ${id.prefixChars()} for ${missing.map { it.toStringShort() }}")
|
||||
val missingElements = getMissingKeyDescriptions(missing)
|
||||
throw SignatureException("Missing signatures for ${missingElements} on transaction ${id.prefixChars()} for ${missing.toStringsShort()}")
|
||||
}
|
||||
|
||||
return missing
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -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?,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) }
|
||||
|
@ -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)
|
||||
|
||||
|
@ -130,7 +130,12 @@ 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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
|
||||
*/
|
||||
fun timestamp(data: Timestamp)
|
||||
|
||||
/**
|
||||
/**
|
||||
* Creates a local scoped copy of the transaction.
|
||||
* @param dsl The transaction DSL to be interpreted using the copy.
|
||||
*/
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
&& !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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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>())
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)))
|
||||
|
Loading…
Reference in New Issue
Block a user