mirror of
https://github.com/corda/corda.git
synced 2024-12-19 13:08:04 +00:00
Merged in rnicoll-tx-timestamp (pull request #271)
Replace timestamp commands with a field on the transaction itself
This commit is contained in:
commit
316917f271
@ -3,6 +3,9 @@ package com.r3corda.contracts;
|
||||
import com.google.common.collect.*;
|
||||
import com.r3corda.contracts.asset.*;
|
||||
import com.r3corda.core.contracts.*;
|
||||
import static com.r3corda.core.contracts.ContractsDSL.requireThat;
|
||||
|
||||
import com.r3corda.core.contracts.Timestamp;
|
||||
import com.r3corda.core.contracts.TransactionForContract.*;
|
||||
import com.r3corda.core.contracts.clauses.*;
|
||||
import com.r3corda.core.crypto.*;
|
||||
@ -219,15 +222,14 @@ public class JavaCommercialPaper implements Contract {
|
||||
if (!cmd.getSigners().contains(input.getOwner()))
|
||||
throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP");
|
||||
|
||||
Party notary = cmd.getValue().notary;
|
||||
TimestampCommand timestampCommand = tx.getTimestampBy(notary);
|
||||
Instant time = null == timestampCommand
|
||||
Timestamp timestamp = tx.getTimestamp();
|
||||
Instant time = null == timestamp
|
||||
? null
|
||||
: timestampCommand.getBefore();
|
||||
: timestamp.getBefore();
|
||||
Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutputs(), input.getOwner());
|
||||
|
||||
requireThat(require -> {
|
||||
require.by("must be timestamped", timestampCommand != null);
|
||||
require.by("must be timestamped", timestamp != null);
|
||||
require.by("received amount equals the face value: "
|
||||
+ received + " vs " + input.getFaceValue(), received.equals(input.getFaceValue()));
|
||||
require.by("the paper must have matured", time != null && !time.isBefore(input.getMaturityDate()));
|
||||
@ -257,7 +259,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
AuthenticatedObject<Commands.Issue> cmd = requireSingleCommand(tx.getCommands(), Commands.Issue.class);
|
||||
State output = single(outputs);
|
||||
Party notary = cmd.getValue().notary;
|
||||
TimestampCommand timestampCommand = tx.getTimestampBy(notary);
|
||||
Timestamp timestampCommand = tx.getTimestamp();
|
||||
Instant time = null == timestampCommand
|
||||
? null
|
||||
: timestampCommand.getBefore();
|
||||
@ -338,7 +340,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
|
||||
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
|
||||
TransactionState output = new TransactionState<>(state, notary);
|
||||
return new TransactionType.General.Builder().withItems(output, new Command(new Commands.Issue(notary), issuance.getParty().getOwningKey()));
|
||||
return new TransactionType.General.Builder(notary).withItems(output, new Command(new Commands.Issue(notary), issuance.getParty().getOwningKey()));
|
||||
}
|
||||
|
||||
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
|
||||
|
@ -109,8 +109,7 @@ class CommercialPaper : Contract {
|
||||
token: Issued<Terms>): Set<CommandData> {
|
||||
val consumedCommands = super.verify(tx, inputs, outputs, commands, token)
|
||||
val command = commands.requireSingleCommand<Commands.Issue>()
|
||||
// If it's an issue, we can't take notary from inputs, so it must be specified in the command
|
||||
val timestamp: TimestampCommand? = tx.getTimestampBy(command.value.notary)
|
||||
val timestamp = tx.timestamp
|
||||
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
|
||||
|
||||
require(outputs.all { time < it.maturityDate }) { "maturity date is not in the past" }
|
||||
@ -151,8 +150,7 @@ class CommercialPaper : Contract {
|
||||
// TODO: This should filter commands down to those with compatible subjects (underlying product and maturity date)
|
||||
// before requiring a single command
|
||||
val command = commands.requireSingleCommand<Commands.Redeem>()
|
||||
// If it's an issue, we can't take notary from inputs, so it must be specified in the command
|
||||
val timestamp: TimestampCommand? = tx.getTimestampBy(command.value.notary)
|
||||
val timestamp = tx.timestamp
|
||||
|
||||
val input = inputs.single()
|
||||
val received = tx.outputs.sumCashBy(input.owner)
|
||||
|
@ -60,13 +60,7 @@ class CommercialPaperLegacy : Contract {
|
||||
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
|
||||
// it for cash on or after the maturity date.
|
||||
val command = tx.commands.requireSingleCommand<CommercialPaperLegacy.Commands>()
|
||||
// If it's an issue, we can't take notary from inputs, so it must be specified in the command
|
||||
val cmdVal = command.value
|
||||
val timestamp: TimestampCommand? = when (cmdVal) {
|
||||
is Commands.Issue -> tx.getTimestampBy(cmdVal.notary)
|
||||
is Commands.Redeem -> tx.getTimestampBy(cmdVal.notary)
|
||||
else -> null
|
||||
}
|
||||
val timestamp: Timestamp? = tx.timestamp
|
||||
|
||||
for ((inputs, outputs, key) in groups) {
|
||||
when (command.value) {
|
||||
|
@ -447,8 +447,8 @@ class InterestRateSwap() : Contract {
|
||||
fixingCalendar, index, indexSource, indexTenor)
|
||||
}
|
||||
|
||||
private fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
|
||||
= tx.commands.select<Commands>() + tx.commands.select<TimestampCommand>()
|
||||
fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
|
||||
= tx.commands.select<Commands>()
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClauses(tx, listOf(Clause.Timestamped(), Clause.Group()), extractCommands(tx))
|
||||
|
||||
@ -519,12 +519,9 @@ class InterestRateSwap() : Contract {
|
||||
override val requiredCommands = emptySet<Class<out CommandData>>()
|
||||
|
||||
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> {
|
||||
// TODO: This needs to either be the notary used for the inputs, or otherwise
|
||||
// derived as the correct notary
|
||||
@Suppress("DEPRECATION")
|
||||
val command = tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
|
||||
?: throw IllegalArgumentException("must be timestamped")
|
||||
return setOf(command)
|
||||
require(tx.timestamp?.midpoint != null) { "must be timestamped" }
|
||||
// We return an empty set because we don't process any commands
|
||||
return emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -403,7 +403,7 @@ class Obligation<P> : Contract {
|
||||
if (input is State<P>) {
|
||||
val actualOutput = outputs[stateIdx]
|
||||
val deadline = input.dueBefore
|
||||
val timestamp: TimestampCommand? = tx.timestamp
|
||||
val timestamp = tx.timestamp
|
||||
val expectedOutput = input.copy(lifecycle = expectedOutputLifecycle)
|
||||
|
||||
requireThat {
|
||||
@ -541,7 +541,7 @@ class Obligation<P> : Contract {
|
||||
}
|
||||
tx.addCommand(Commands.SetLifecycle(lifecycle), partiesUsed.distinct())
|
||||
}
|
||||
tx.setTime(issuanceDef.dueBefore, notary, issuanceDef.timeTolerance)
|
||||
tx.setTime(issuanceDef.dueBefore, issuanceDef.timeTolerance)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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>()
|
||||
@ -273,7 +273,7 @@ object TwoPartyTradeProtocol {
|
||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||
// to have one.
|
||||
val currentTime = serviceHub.clock.instant()
|
||||
ptx.setTime(currentTime, notary, 30.seconds)
|
||||
ptx.setTime(currentTime, 30.seconds)
|
||||
return Pair(ptx, cashSigningPubKeys)
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ class CommercialPaperTestsGeneric {
|
||||
}
|
||||
|
||||
fun cashOutputsToWallet(vararg outputs: TransactionState<Cash.State>): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> {
|
||||
val ltx = LedgerTransaction(emptyList(), listOf(*outputs), emptyList(), emptyList(), SecureHash.randomSHA256(), emptyList(), TransactionType.General())
|
||||
val ltx = LedgerTransaction(emptyList(), listOf(*outputs), emptyList(), emptyList(), SecureHash.randomSHA256(), null, emptyList(), null, TransactionType.General())
|
||||
return Pair(ltx, outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
|
||||
}
|
||||
|
||||
@ -205,14 +205,14 @@ class CommercialPaperTestsGeneric {
|
||||
val issuance = bigCorpServices.storageService.myLegalIdentity.ref(1)
|
||||
val issueTX: SignedTransaction =
|
||||
CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
|
||||
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
|
||||
setTime(TEST_TX_TIME, 30.seconds)
|
||||
signWith(bigCorpServices.key)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
|
||||
// Alice pays $9000 to BigCorp to own some of their debt.
|
||||
val moveTX: SignedTransaction = run {
|
||||
val ptx = TransactionType.General.Builder()
|
||||
val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
Cash().generateSpend(ptx, 9000.DOLLARS, bigCorpServices.key.public, alicesWallet.statesOfType<Cash.State>())
|
||||
CommercialPaper().generateMove(ptx, issueTX.tx.outRef(0), aliceServices.key.public)
|
||||
ptx.signWith(bigCorpServices.key)
|
||||
@ -222,8 +222,8 @@ class CommercialPaperTestsGeneric {
|
||||
}
|
||||
|
||||
fun makeRedeemTX(time: Instant): SignedTransaction {
|
||||
val ptx = TransactionType.General.Builder()
|
||||
ptx.setTime(time, DUMMY_NOTARY, 30.seconds)
|
||||
val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
ptx.setTime(time, 30.seconds)
|
||||
CommercialPaper().generateRedeem(ptx, moveTX.tx.outRef(1), bigCorpWallet.statesOfType<Cash.State>())
|
||||
ptx.signWith(aliceServices.key)
|
||||
ptx.signWith(bigCorpServices.key)
|
||||
|
@ -217,7 +217,7 @@ class IRSTests {
|
||||
calculation = dummyIRS.calculation,
|
||||
common = dummyIRS.common,
|
||||
notary = DUMMY_NOTARY).apply {
|
||||
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
|
||||
setTime(TEST_TX_TIME, 30.seconds)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
@ -299,11 +299,11 @@ class IRSTests {
|
||||
while (true) {
|
||||
val nextFix: FixOf = currentIRS().nextFixingOf() ?: break
|
||||
val fixTX: SignedTransaction = run {
|
||||
val tx = TransactionType.General.Builder()
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
val fixing = Fix(nextFix, "0.052".percent.value)
|
||||
InterestRateSwap().generateFix(tx, previousTXN.tx.outRef(0), fixing)
|
||||
with(tx) {
|
||||
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
|
||||
setTime(TEST_TX_TIME, 30.seconds)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
|
@ -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)
|
||||
|
@ -84,29 +84,6 @@ inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>
|
||||
fun <C : CommandData> Collection<AuthenticatedObject<CommandData>>.requireSingleCommand(klass: Class<C>) =
|
||||
mapNotNull { @Suppress("UNCHECKED_CAST") if (klass.isInstance(it.value)) it as AuthenticatedObject<C> else null }.single()
|
||||
|
||||
/** Returns a timestamp that was signed by the given authority, or returns null if missing. */
|
||||
fun List<AuthenticatedObject<CommandData>>.getTimestampBy(timestampingAuthority: Party): TimestampCommand? {
|
||||
val timestampCmds = filter { it.signers.contains(timestampingAuthority.owningKey) && it.value is TimestampCommand }
|
||||
return timestampCmds.singleOrNull()?.value as? TimestampCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timestamp that was signed by any of the the named authorities, or returns null if missing.
|
||||
* Note that matching here is done by (verified, legal) name, not by public key. Any signature by any
|
||||
* party with a name that matches (case insensitively) any of the given names will yield a match.
|
||||
*/
|
||||
@Deprecated(message = "Timestamping authority should always be notary for the transaction")
|
||||
fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: String): TimestampCommand? {
|
||||
val timestampCmd = filter { it.value is TimestampCommand }.singleOrNull() ?: return null
|
||||
val tsaNames = timestampCmd.signingParties.map { it.name.toLowerCase() }
|
||||
val acceptableNames = names.map(String::toLowerCase)
|
||||
val acceptableNameFound = tsaNames.intersect(acceptableNames).isNotEmpty()
|
||||
if (acceptableNameFound)
|
||||
return timestampCmd.value as TimestampCommand
|
||||
else
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
|
||||
*
|
||||
|
@ -338,13 +338,10 @@ data class AuthenticatedObject<out T : Any>(
|
||||
)
|
||||
|
||||
/**
|
||||
* If present in a transaction, contains a time that was verified by the timestamping authority/authorities whose
|
||||
* public keys are identified in the containing [Command] object. The true time must be between (after, before).
|
||||
* If present in a transaction, contains a time that was verified by the uniqueness service. The true time must be
|
||||
* between (after, before).
|
||||
*/
|
||||
// TODO: Timestamps are now always provided by the consensus service for the transaction, rather than potentially
|
||||
// having multiple timestamps on a transaction. As such, it likely makes more sense for time to be a field on the
|
||||
// transaction, rather than a command
|
||||
data class TimestampCommand(val after: Instant?, val before: Instant?) : CommandData {
|
||||
data class Timestamp(val after: Instant?, val before: Instant?) {
|
||||
init {
|
||||
if (after == null && before == null)
|
||||
throw IllegalArgumentException("At least one of before/after must be specified")
|
||||
|
@ -16,20 +16,26 @@ import java.util.*
|
||||
* The builder can be customised for specific transaction types, e.g. where additional processing is needed
|
||||
* before adding a state/command.
|
||||
*
|
||||
* @param notary The default notary that will be used for outputs that don't have a notary specified. When this is set,
|
||||
* an output state can be added by just passing in a [ContractState] – a [TransactionState] with the
|
||||
* default notary will be generated automatically.
|
||||
* @param notary Notary used for the transaction. If null, this indicates the transaction DOES NOT have a notary.
|
||||
* When this is set to a non-null value, an output state can be added by just passing in a [ContractState] – a
|
||||
* [TransactionState] with this notary specified will be generated automatically.
|
||||
*/
|
||||
open class TransactionBuilder(
|
||||
protected val type: TransactionType = TransactionType.General(),
|
||||
protected val notary: Party? = null,
|
||||
var notary: Party? = null,
|
||||
protected val inputs: MutableList<StateRef> = arrayListOf(),
|
||||
protected val attachments: MutableList<SecureHash> = arrayListOf(),
|
||||
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
|
||||
protected val commands: MutableList<Command> = arrayListOf(),
|
||||
protected val signers: MutableSet<PublicKey> = mutableSetOf()) {
|
||||
protected val signers: MutableSet<PublicKey> = mutableSetOf(),
|
||||
protected var timestamp: Timestamp? = null) {
|
||||
|
||||
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
|
||||
@Deprecated("use timestamp instead")
|
||||
val time: Timestamp? get() = timestamp
|
||||
|
||||
init {
|
||||
notary?.let { signers.add(it.owningKey) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the builder.
|
||||
@ -42,7 +48,8 @@ open class TransactionBuilder(
|
||||
attachments = ArrayList(attachments),
|
||||
outputs = ArrayList(outputs),
|
||||
commands = ArrayList(commands),
|
||||
signers = LinkedHashSet(signers)
|
||||
signers = LinkedHashSet(signers),
|
||||
timestamp = timestamp
|
||||
)
|
||||
|
||||
/**
|
||||
@ -57,10 +64,13 @@ open class TransactionBuilder(
|
||||
* collaborating parties may therefore require a higher time tolerance than a transaction being built by a single
|
||||
* node.
|
||||
*/
|
||||
fun setTime(time: Instant, authority: Party, timeTolerance: Duration) {
|
||||
fun setTime(time: Instant, timeTolerance: Duration)
|
||||
= setTime(Timestamp(time, timeTolerance))
|
||||
|
||||
fun setTime(newTimestamp: Timestamp) {
|
||||
check(notary != null) { "Only notarised transactions can have a timestamp" }
|
||||
check(currentSigs.isEmpty()) { "Cannot change timestamp after signing" }
|
||||
commands.removeAll { it.value is TimestampCommand }
|
||||
addCommand(TimestampCommand(time, timeTolerance), authority.owningKey)
|
||||
this.timestamp = newTimestamp
|
||||
}
|
||||
|
||||
/** A more convenient way to add items to this transaction that calls the add* methods for you based on type */
|
||||
@ -115,7 +125,7 @@ open class TransactionBuilder(
|
||||
}
|
||||
|
||||
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
|
||||
ArrayList(outputs), ArrayList(commands), signers.toList(), type)
|
||||
ArrayList(outputs), ArrayList(commands), notary, signers.toList(), type, timestamp)
|
||||
|
||||
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
|
||||
if (checkSufficientSignatures) {
|
||||
@ -131,6 +141,7 @@ open class TransactionBuilder(
|
||||
|
||||
fun addInputState(stateRef: StateRef, notary: Party) {
|
||||
check(currentSigs.isEmpty())
|
||||
require(notary == this.notary) { "Input state requires notary \"${notary}\" which does not match the transaction notary \"${this.notary}\"." }
|
||||
signers.add(notary.owningKey)
|
||||
inputs.add(stateRef)
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ fun WireTransaction.toLedgerTransaction(services: ServiceHub): LedgerTransaction
|
||||
services.storageService.attachments.openAttachment(it) ?: throw FileNotFoundException(it.toString())
|
||||
}
|
||||
val resolvedInputs = inputs.map { StateAndRef(services.loadState(it), it) }
|
||||
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, signers, type)
|
||||
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, signers, timestamp, type)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@ sealed class TransactionType {
|
||||
* Note: Presence of _signatures_ is not checked, only the public keys to be signed for.
|
||||
*/
|
||||
fun verify(tx: LedgerTransaction) {
|
||||
require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." }
|
||||
val missing = verifySigners(tx)
|
||||
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
|
||||
verifyTransaction(tx)
|
||||
@ -24,9 +25,7 @@ sealed class TransactionType {
|
||||
|
||||
/** Check that the list of signers includes all the necessary keys */
|
||||
fun verifySigners(tx: LedgerTransaction): Set<PublicKey> {
|
||||
val timestamp = tx.commands.noneOrSingle { it.value is TimestampCommand }
|
||||
val timestampKey = timestamp?.signers.orEmpty()
|
||||
val notaryKey = (tx.inputs.map { it.state.notary.owningKey } + timestampKey).toSet()
|
||||
val notaryKey = tx.inputs.map { it.state.notary.owningKey }.toSet()
|
||||
if (notaryKey.size > 1) throw TransactionVerificationException.MoreThanOneNotary(tx)
|
||||
|
||||
val requiredKeys = getRequiredSigners(tx) + notaryKey
|
||||
@ -47,14 +46,26 @@ sealed class TransactionType {
|
||||
/** A general transaction type where transaction validity is determined by custom contract code */
|
||||
class General : TransactionType() {
|
||||
/** Just uses the default [TransactionBuilder] with no special logic */
|
||||
class Builder(notary: Party? = null) : TransactionBuilder(General(), notary) {}
|
||||
class Builder(notary: Party?) : TransactionBuilder(General(), notary) {}
|
||||
|
||||
/**
|
||||
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
|
||||
* If any contract fails to verify, the whole transaction is considered to be invalid.
|
||||
*/
|
||||
override fun verifyTransaction(tx: LedgerTransaction) {
|
||||
// TODO: Check that notary is unchanged
|
||||
// Make sure the notary has stayed the same. As we can't tell how inputs and outputs connect, if there
|
||||
// are any inputs, all outputs must have the same notary.
|
||||
// TODO: Is that the correct set of restrictions? May need to come back to this, see if we can be more
|
||||
// flexible on output notaries.
|
||||
if (tx.notary != null
|
||||
&& tx.inputs.isNotEmpty()) {
|
||||
tx.outputs.forEach {
|
||||
if (it.notary != tx.notary) {
|
||||
throw TransactionVerificationException.NotaryChangeInWrongTransactionType(tx, it.notary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ctx = tx.toTransactionForContract()
|
||||
|
||||
// TODO: This will all be replaced in future once the sandbox and contract constraints work is done.
|
||||
@ -80,7 +91,7 @@ sealed class TransactionType {
|
||||
* A transaction builder that automatically sets the transaction type to [NotaryChange]
|
||||
* and adds the list of participants to the signers set for every input state.
|
||||
*/
|
||||
class Builder(notary: Party? = null) : TransactionBuilder(NotaryChange(), notary) {
|
||||
class Builder(notary: Party) : TransactionBuilder(NotaryChange(), notary) {
|
||||
override fun addInputState(stateAndRef: StateAndRef<*>) {
|
||||
signers.addAll(stateAndRef.state.data.participants)
|
||||
super.addInputState(stateAndRef)
|
||||
|
@ -17,7 +17,8 @@ data class TransactionForContract(val inputs: List<ContractState>,
|
||||
val attachments: List<Attachment>,
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
val origHash: SecureHash,
|
||||
val inputNotary: Party? = null) {
|
||||
val inputNotary: Party? = null,
|
||||
val timestamp: Timestamp? = null) {
|
||||
override fun hashCode() = origHash.hashCode()
|
||||
override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
|
||||
|
||||
@ -82,18 +83,6 @@ data class TransactionForContract(val inputs: List<ContractState>,
|
||||
* be used to simplify this logic.
|
||||
*/
|
||||
data class InOutGroup<out T : ContractState, out K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
|
||||
|
||||
/** Get the timestamp command for this transaction, using the notary from the input states. */
|
||||
val timestamp: TimestampCommand?
|
||||
get() = if (inputNotary == null) null else commands.getTimestampBy(inputNotary)
|
||||
|
||||
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
|
||||
fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority)
|
||||
|
||||
/** Simply calls [commands.getTimestampByName] as a shortcut to make code completion more intuitive. */
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated(message = "Timestamping authority should always be notary for the transaction")
|
||||
fun getTimestampByName(vararg authorityName: String): TimestampCommand? = commands.getTimestampByName(*authorityName)
|
||||
}
|
||||
|
||||
class TransactionResolutionException(val hash: SecureHash) : Exception() {
|
||||
@ -108,4 +97,7 @@ sealed class TransactionVerificationException(val tx: LedgerTransaction, cause:
|
||||
override fun toString() = "Signers missing: ${missing.map { it.toStringShort() }}"
|
||||
}
|
||||
class InvalidNotaryChange(tx: LedgerTransaction) : TransactionVerificationException(tx, null)
|
||||
class NotaryChangeInWrongTransactionType(tx: LedgerTransaction, val outputNotary: Party) : TransactionVerificationException(tx, null) {
|
||||
override fun toString(): String = "Found unexpected notary change in transaction. Tx notary: ${tx.notary}, found: ${outputNotary}"
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,9 @@ package com.r3corda.core.contracts
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.crypto.toStringShort
|
||||
import com.r3corda.core.crypto.toStringsShort
|
||||
import com.r3corda.core.indexOfOrThrow
|
||||
import com.r3corda.core.serialization.SerializedBytes
|
||||
import com.r3corda.core.serialization.THREAD_LOCAL_KRYO
|
||||
@ -12,6 +13,7 @@ import com.r3corda.core.serialization.serialize
|
||||
import com.r3corda.core.utilities.Emoji
|
||||
import java.security.PublicKey
|
||||
import java.security.SignatureException
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Views of a transaction as it progresses through the pipeline, from bytes loaded from disk/network to the object
|
||||
@ -48,8 +50,10 @@ data class WireTransaction(val inputs: List<StateRef>,
|
||||
val attachments: List<SecureHash>,
|
||||
val outputs: List<TransactionState<ContractState>>,
|
||||
val commands: List<Command>,
|
||||
val notary: Party?,
|
||||
val signers: List<PublicKey>,
|
||||
val type: TransactionType) : NamedByHash {
|
||||
val type: TransactionType,
|
||||
val timestamp: Timestamp?) : NamedByHash {
|
||||
|
||||
// Cache the serialised form of the transaction and its hash to give us fast access to it.
|
||||
@Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null
|
||||
@ -114,12 +118,32 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
||||
|
||||
// Now examine the contents and ensure the sigs we have line up with the advertised list of signers.
|
||||
val missing = getMissingSignatures()
|
||||
if (missing.isNotEmpty() && throwIfSignaturesAreMissing)
|
||||
throw SignatureException("Missing signatures on transaction ${id.prefixChars()} for: ${missing.map { it.toStringShort() }}")
|
||||
if (missing.isNotEmpty() && throwIfSignaturesAreMissing) {
|
||||
val missingElements = getMissingKeyDescriptions(missing)
|
||||
throw SignatureException("Missing signatures for ${missingElements} on transaction ${id.prefixChars()} for ${missing.toStringsShort()}")
|
||||
}
|
||||
|
||||
return missing
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human readable description of where signatures are required from, and are missing, to assist in debugging
|
||||
* the underlying cause.
|
||||
*/
|
||||
private fun getMissingKeyDescriptions(missing: Set<PublicKey>): ArrayList<String> {
|
||||
// TODO: We need a much better way of structuring this data
|
||||
val missingElements = ArrayList<String>()
|
||||
this.tx.commands.forEach { command ->
|
||||
if (command.signers.any { signer -> missing.contains(signer) })
|
||||
missingElements.add(command.toString())
|
||||
}
|
||||
this.tx.notary?.owningKey.apply {
|
||||
if (missing.contains(this))
|
||||
missingElements.add("notary")
|
||||
}
|
||||
return missingElements
|
||||
}
|
||||
|
||||
/** Returns the same transaction but with an additional (unchecked) signature */
|
||||
fun withAdditionalSignature(sig: DigitalSignature.WithKey): SignedTransaction {
|
||||
// TODO: need to make sure the Notary signs last
|
||||
@ -139,6 +163,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
||||
* Returns the set of missing signatures - a signature must be present for each signer public key.
|
||||
*/
|
||||
private fun getMissingSignatures(): Set<PublicKey> {
|
||||
val notaryKey = tx.notary?.owningKey
|
||||
val requiredKeys = tx.signers.toSet()
|
||||
val sigKeys = sigs.map { it.by }.toSet()
|
||||
|
||||
@ -161,10 +186,13 @@ data class LedgerTransaction(
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
||||
val attachments: List<Attachment>,
|
||||
/** The hash of the original serialised WireTransaction */
|
||||
/** The hash of the original serialised WireTransaction. */
|
||||
override val id: SecureHash,
|
||||
/** The notary for this party, may be null for transactions with no notary. */
|
||||
val notary: Party?,
|
||||
/** The notary key and the command keys together: a signed transaction must provide signatures for all of these. */
|
||||
val signers: List<PublicKey>,
|
||||
val timestamp: Timestamp?,
|
||||
val type: TransactionType
|
||||
) : NamedByHash {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -175,7 +203,7 @@ data class LedgerTransaction(
|
||||
/** Strips the transaction down to a form that is usable by the contract verify functions */
|
||||
fun toTransactionForContract(): TransactionForContract {
|
||||
return TransactionForContract(inputs.map { it.state.data }, outputs.map { it.data }, attachments, commands, id,
|
||||
inputs.map { it.state.notary }.singleOrNull())
|
||||
inputs.map { it.state.notary }.singleOrNull(), timestamp)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.r3corda.core.node.services
|
||||
|
||||
import com.r3corda.core.contracts.TimestampCommand
|
||||
import com.r3corda.core.contracts.Timestamp
|
||||
import com.r3corda.core.seconds
|
||||
import com.r3corda.core.until
|
||||
import java.time.Clock
|
||||
@ -11,7 +11,7 @@ import java.time.Duration
|
||||
*/
|
||||
class TimestampChecker(val clock: Clock = Clock.systemUTC(),
|
||||
val tolerance: Duration = 30.seconds) {
|
||||
fun isValid(timestampCommand: TimestampCommand): Boolean {
|
||||
fun isValid(timestampCommand: Timestamp): Boolean {
|
||||
val before = timestampCommand.before
|
||||
val after = timestampCommand.after
|
||||
|
||||
|
@ -230,8 +230,10 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
||||
kryo.writeClassAndObject(output, obj.attachments)
|
||||
kryo.writeClassAndObject(output, obj.outputs)
|
||||
kryo.writeClassAndObject(output, obj.commands)
|
||||
kryo.writeClassAndObject(output, obj.notary)
|
||||
kryo.writeClassAndObject(output, obj.signers)
|
||||
kryo.writeClassAndObject(output, obj.type)
|
||||
kryo.writeClassAndObject(output, obj.timestamp)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -260,10 +262,12 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
||||
kryo.useClassLoader(classLoader) {
|
||||
val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>>
|
||||
val commands = kryo.readClassAndObject(input) as List<Command>
|
||||
val notary = kryo.readClassAndObject(input) as Party?
|
||||
val signers = kryo.readClassAndObject(input) as List<PublicKey>
|
||||
val transactionType = kryo.readClassAndObject(input) as TransactionType
|
||||
val timestamp = kryo.readClassAndObject(input) as Timestamp?
|
||||
|
||||
return WireTransaction(inputs, attachmentHashes, outputs, commands, signers, transactionType)
|
||||
return WireTransaction(inputs, attachmentHashes, outputs, commands, notary, signers, transactionType, timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,10 +130,19 @@ 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
|
||||
}
|
||||
|
||||
override fun timestamp(data: Timestamp) {
|
||||
transactionBuilder.setTime(data)
|
||||
}
|
||||
|
||||
override fun tweak(
|
||||
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||
) = dsl(TransactionDSL(copy()))
|
||||
@ -301,15 +310,18 @@ data class TestLedgerDSLInterpreter private constructor (
|
||||
* @return List of [SignedTransaction]s.
|
||||
*/
|
||||
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: List<KeyPair>) = transactionsToSign.map { wtx ->
|
||||
val allPubKeys = wtx.signers.toMutableSet()
|
||||
check(wtx.signers.isNotEmpty())
|
||||
val bits = wtx.serialize()
|
||||
require(bits == wtx.serialized)
|
||||
val signatures = ArrayList<DigitalSignature.WithKey>()
|
||||
for (key in ALL_TEST_KEYS + extraKeys) {
|
||||
if (key.public in allPubKeys) {
|
||||
signatures += key.signWithECDSA(bits)
|
||||
allPubKeys -= key.public
|
||||
val keyLookup = HashMap<PublicKey, KeyPair>()
|
||||
|
||||
(ALL_TEST_KEYS + extraKeys).forEach {
|
||||
keyLookup[it.public] = it
|
||||
}
|
||||
wtx.signers.forEach {
|
||||
val key = keyLookup[it] ?: throw IllegalArgumentException("Missing required key for ${it.toStringShort()}")
|
||||
signatures += key.signWithECDSA(bits)
|
||||
}
|
||||
SignedTransaction(bits, signatures)
|
||||
}
|
||||
|
@ -46,6 +46,12 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
|
||||
*/
|
||||
fun _command(signers: List<PublicKey>, commandData: CommandData)
|
||||
|
||||
/**
|
||||
* Adds a timestamp to the transaction.
|
||||
* @param data The [TimestampCommand].
|
||||
*/
|
||||
fun timestamp(data: Timestamp)
|
||||
|
||||
/**
|
||||
* Creates a local scoped copy of the transaction.
|
||||
* @param dsl The transaction DSL to be interpreted using the copy.
|
||||
@ -102,16 +108,8 @@ class TransactionDSL<out T : TransactionDSLInterpreter>(val interpreter: T) : Tr
|
||||
* Adds a timestamp command to the transaction.
|
||||
* @param time The [Instant] of the [TimestampCommand].
|
||||
* @param tolerance The tolerance of the [TimestampCommand].
|
||||
* @param notary The notary to sign the command.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun timestamp(time: Instant, tolerance: Duration = 30.seconds, notary: PublicKey = DUMMY_NOTARY.owningKey) =
|
||||
timestamp(TimestampCommand(time, tolerance), notary)
|
||||
/**
|
||||
* Adds a timestamp command to the transaction.
|
||||
* @param data The [TimestampCommand].
|
||||
* @param notary The notary to sign the command.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun timestamp(data: TimestampCommand, notary: PublicKey = DUMMY_NOTARY.owningKey) = command(notary, data)
|
||||
fun timestamp(time: Instant, tolerance: Duration = 30.seconds) =
|
||||
timestamp(Timestamp(time, tolerance))
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -2,7 +2,8 @@ package com.r3corda.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.contracts.SignedTransaction
|
||||
import com.r3corda.core.contracts.TimestampCommand
|
||||
import com.r3corda.core.contracts.StateRef
|
||||
import com.r3corda.core.contracts.Timestamp
|
||||
import com.r3corda.core.contracts.WireTransaction
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.crypto.Party
|
||||
@ -51,7 +52,9 @@ object NotaryProtocol {
|
||||
@Suspendable
|
||||
override fun call(): DigitalSignature.LegallyIdentifiable {
|
||||
progressTracker.currentStep = REQUESTING
|
||||
notaryParty = findNotaryParty()
|
||||
val wtx = stx.tx
|
||||
notaryParty = wtx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
|
||||
check(wtx.inputs.all { stateRef -> serviceHub.loadState(stateRef).notary == notaryParty }) { "Input states must have the same Notary" }
|
||||
|
||||
val sendSessionID = random63BitValue()
|
||||
val receiveSessionID = random63BitValue()
|
||||
@ -82,25 +85,6 @@ object NotaryProtocol {
|
||||
check(sig.signer == notaryParty) { "Notary result not signed by the correct service" }
|
||||
sig.verifyWithECDSA(data)
|
||||
}
|
||||
|
||||
private fun findNotaryParty(): Party {
|
||||
var maybeNotaryKey: PublicKey? = null
|
||||
val wtx = stx.tx
|
||||
|
||||
val timestampCommand = wtx.commands.singleOrNull { it.value is TimestampCommand }
|
||||
if (timestampCommand != null) maybeNotaryKey = timestampCommand.signers.first()
|
||||
|
||||
for (stateRef in wtx.inputs) {
|
||||
val inputNotaryKey = serviceHub.loadState(stateRef).notary.owningKey
|
||||
if (maybeNotaryKey != null)
|
||||
check(maybeNotaryKey == inputNotaryKey) { "Input states and timestamp must have the same Notary" }
|
||||
else maybeNotaryKey = inputNotaryKey
|
||||
}
|
||||
|
||||
val notaryKey = maybeNotaryKey ?: throw IllegalStateException("Transaction does not specify a Notary")
|
||||
val notaryParty = serviceHub.networkMapCache.getNodeByPublicKey(notaryKey)?.identity
|
||||
return notaryParty ?: throw IllegalStateException("No Notary node can be found with the specified public key")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,15 +123,8 @@ object NotaryProtocol {
|
||||
}
|
||||
|
||||
private fun validateTimestamp(tx: WireTransaction) {
|
||||
val timestampCmd = try {
|
||||
tx.commands.noneOrSingle { it.value is TimestampCommand } ?: return
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw NotaryException(NotaryError.MoreThanOneTimestamp())
|
||||
}
|
||||
val myIdentity = serviceHub.storageService.myLegalIdentity
|
||||
if (!timestampCmd.signers.contains(myIdentity.owningKey))
|
||||
throw NotaryException(NotaryError.NotForMe())
|
||||
if (!timestampChecker.isValid(timestampCmd.value as TimestampCommand))
|
||||
if (tx.timestamp != null
|
||||
&& !timestampChecker.isValid(tx.timestamp))
|
||||
throw NotaryException(NotaryError.TimestampInvalid())
|
||||
}
|
||||
|
||||
@ -223,11 +200,6 @@ sealed class NotaryError {
|
||||
override fun toString() = "One or more input states for transaction ${tx.id} have been used in another transaction"
|
||||
}
|
||||
|
||||
class MoreThanOneTimestamp : NotaryError()
|
||||
|
||||
/** Thrown if the timestamp command in the transaction doesn't list this Notary as a signer */
|
||||
class NotForMe : NotaryError()
|
||||
|
||||
/** Thrown if the time specified in the timestamp command is outside the allowed tolerance */
|
||||
class TimestampInvalid : NotaryError()
|
||||
|
||||
|
@ -323,7 +323,7 @@ object TwoPartyDealProtocol {
|
||||
|
||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||
// to have one.
|
||||
ptx.setTime(serviceHub.clock.instant(), notary, 30.seconds)
|
||||
ptx.setTime(serviceHub.clock.instant(), 30.seconds)
|
||||
return Pair(ptx, arrayListOf(handshake.payload.parties.single { it.name == serviceHub.storageService.myLegalIdentity.name }.owningKey))
|
||||
}
|
||||
|
||||
@ -375,7 +375,7 @@ object TwoPartyDealProtocol {
|
||||
|
||||
val newDeal = deal
|
||||
|
||||
val ptx = TransactionType.General.Builder()
|
||||
val ptx = TransactionType.General.Builder(txState.notary)
|
||||
val addFixing = object : RatesFixProtocol(ptx, serviceHub.networkMapCache.ratesOracleNodes[0].identity, fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
|
||||
@Suspendable
|
||||
override fun beforeSigning(fix: Fix) {
|
||||
@ -383,7 +383,7 @@ object TwoPartyDealProtocol {
|
||||
|
||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||
// to have one.
|
||||
ptx.setTime(serviceHub.clock.instant(), txState.notary, 30.seconds)
|
||||
ptx.setTime(serviceHub.clock.instant(), 30.seconds)
|
||||
}
|
||||
}
|
||||
subProtocol(addFixing)
|
||||
|
@ -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)
|
||||
@ -99,10 +99,10 @@ class TransactionSerializationTests {
|
||||
|
||||
@Test
|
||||
fun timestamp() {
|
||||
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
|
||||
tx.setTime(TEST_TX_TIME, 30.seconds)
|
||||
tx.signWith(DUMMY_KEY_1)
|
||||
tx.signWith(DUMMY_NOTARY_KEY)
|
||||
val stx = tx.toSignedTransaction()
|
||||
assertEquals(TEST_TX_TIME, (stx.tx.commands[1].value as TimestampCommand).midpoint)
|
||||
assertEquals(TEST_TX_TIME, stx.tx.timestamp?.midpoint)
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ class AccountReceivable : Contract {
|
||||
throw IllegalArgumentException("Cannot build AR with an already assigned invoice")
|
||||
}
|
||||
val ar = createARFromInvoice(invoice.state.data, discountRate, notary)
|
||||
val tx = TransactionType.General.Builder()
|
||||
val tx = TransactionType.General.Builder(notary)
|
||||
tx.addInputState(invoice)
|
||||
tx.addOutputState(invoice.state.data.copy(assigned = true))
|
||||
tx.addCommand(Invoice.Commands.Assign(), invoice.state.data.owner.owningKey)
|
||||
@ -108,7 +108,7 @@ class AccountReceivable : Contract {
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
val command = tx.commands.requireSingleCommand<AccountReceivable.Commands>()
|
||||
|
||||
val time = tx.commands.getTimestampByName("Notary Service", "Seller")?.midpoint ?:
|
||||
val time = tx.timestamp?.midpoint ?:
|
||||
throw IllegalArgumentException("must be timestamped")
|
||||
|
||||
when (command.value) {
|
||||
|
@ -74,7 +74,7 @@ class BillOfLadingAgreement : Contract {
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
val command = tx.commands.requireSingleCommand<BillOfLadingAgreement.Commands>()
|
||||
|
||||
val time = tx.commands.getTimestampByName("Notary Service")?.midpoint
|
||||
val time = tx.timestamp?.midpoint
|
||||
if (time == null) throw IllegalArgumentException("must be timestamped")
|
||||
|
||||
val txOutputStates: List<BillOfLadingAgreement.State> = tx.outputs.filterIsInstance<BillOfLadingAgreement.State>()
|
||||
@ -114,7 +114,7 @@ class BillOfLadingAgreement : Contract {
|
||||
fun generateIssue(owner: PublicKey, beneficiary: Party, props: BillOfLadingProperties, notary: Party): TransactionBuilder {
|
||||
val state = State(owner, beneficiary, props)
|
||||
val builder = TransactionType.General.Builder(notary = notary)
|
||||
builder.setTime(Instant.now(), notary, 1.days)
|
||||
builder.setTime(Instant.now(), 1.days)
|
||||
return builder.withItems(state, Command(Commands.IssueBL(), props.carrierOwner.owningKey))
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ class Invoice : Contract {
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
val command = tx.commands.requireSingleCommand<Invoice.Commands>()
|
||||
|
||||
val time = tx.commands.getTimestampByName("Notary Service", "Seller")?.midpoint ?:
|
||||
val time = tx.timestamp?.midpoint ?:
|
||||
throw IllegalArgumentException("must be timestamped")
|
||||
|
||||
when (command.value) {
|
||||
|
@ -30,11 +30,6 @@ class LCApplication : Contract {
|
||||
val inputs = tx.inputs.filterIsInstance<State>()
|
||||
val outputs = tx.outputs.filterIsInstance<State>()
|
||||
|
||||
// Here, we match acceptable timestamp authorities by name. The list of acceptable TSAs (oracles) must be
|
||||
// hard coded into the contract because otherwise we could fail to gain consensus, if nodes disagree about
|
||||
// who or what is a trusted authority.
|
||||
tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
|
||||
|
||||
when (command.value) {
|
||||
is Commands.ApplyForLC -> {
|
||||
verifyApply(inputs, outputs, command as AuthenticatedObject<Commands.ApplyForLC>, tx)
|
||||
@ -134,7 +129,7 @@ class LCApplication : Contract {
|
||||
|
||||
fun generateApply(props: LCApplicationProperties, notary: Party, purchaseOrder: Attachment): TransactionBuilder {
|
||||
val state = State(props.issuer.owningKey, Status.PENDING_ISSUER_REVIEW, props)
|
||||
val txBuilder = TransactionType.General.Builder().withItems(state, Command(Commands.ApplyForLC(), props.applicant.owningKey))
|
||||
val txBuilder = TransactionType.General.Builder(notary).withItems(state, Command(Commands.ApplyForLC(), props.applicant.owningKey))
|
||||
txBuilder.addAttachment(purchaseOrder.id)
|
||||
return txBuilder
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ class LOC : Contract {
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
val command = tx.commands.requireSingleCommand<LOC.Commands>()
|
||||
|
||||
val time = tx.commands.getTimestampByName("Notary Service")?.midpoint
|
||||
val time = tx.timestamp?.midpoint
|
||||
if (time == null) throw IllegalArgumentException("must be timestamped")
|
||||
|
||||
when (command.value) {
|
||||
@ -146,7 +146,7 @@ class LOC : Contract {
|
||||
fun generateIssue(beneficiaryPaid: Boolean, issued: Boolean, terminated: Boolean, props: LOCProperties, notary: Party): TransactionBuilder {
|
||||
val state = State(beneficiaryPaid, issued, terminated, props)
|
||||
val builder = TransactionType.General.Builder(notary = notary)
|
||||
builder.setTime(Instant.now(), notary, 1.days)
|
||||
builder.setTime(Instant.now(), 1.days)
|
||||
return builder.withItems(state, Command(Commands.Issuance(), props.issuingbank.owningKey))
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ class AccountReceivableTests {
|
||||
}
|
||||
|
||||
val gtx = invoice.generateInvoice(DUMMY_NOTARY).apply {
|
||||
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
|
||||
setTime(TEST_TX_TIME, 30.seconds)
|
||||
signWith(MINI_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -34,7 +34,7 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
|
||||
val issuance = run {
|
||||
val tx = CommercialPaper().generateIssue(seller.info.identity.ref(1, 2, 3), 1100.DOLLARS `issued by` DUMMY_CASH_ISSUER,
|
||||
Instant.now() + 10.days, notary.info.identity)
|
||||
tx.setTime(Instant.now(), notary.info.identity, 30.seconds)
|
||||
tx.setTime(Instant.now(), 30.seconds)
|
||||
tx.signWith(notary.storage.myLegalIdentityKey)
|
||||
tx.signWith(seller.storage.myLegalIdentityKey)
|
||||
tx.toSignedTransaction(true)
|
||||
|
@ -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()
|
||||
@ -475,7 +486,7 @@ class TwoPartyTradeProtocolTests {
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue(notary) }
|
||||
if (!withError)
|
||||
timestamp(time = TEST_TX_TIME, notary = notary.owningKey)
|
||||
timestamp(time = TEST_TX_TIME)
|
||||
if (attachmentID != null)
|
||||
attachment(attachmentID)
|
||||
if (withError) {
|
||||
|
@ -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)
|
||||
@ -120,7 +120,7 @@ fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<
|
||||
|
||||
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
|
||||
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
|
||||
tx.setTime(Instant.now(), notary, 30.seconds)
|
||||
tx.setTime(Instant.now(), 30.seconds)
|
||||
tx.signWith(node.storage.myLegalIdentityKey)
|
||||
val stx = tx.toSignedTransaction(false)
|
||||
node.services.recordTransactions(listOf(stx))
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.r3corda.node.services
|
||||
|
||||
import com.r3corda.core.contracts.TimestampCommand
|
||||
import com.r3corda.core.contracts.Timestamp
|
||||
import com.r3corda.core.contracts.TransactionType
|
||||
import com.r3corda.core.seconds
|
||||
import com.r3corda.core.testing.DUMMY_NOTARY
|
||||
@ -39,8 +39,8 @@ class NotaryServiceTests {
|
||||
@Test fun `should sign a unique transaction with a valid timestamp`() {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder().withItems(inputState)
|
||||
tx.setTime(Instant.now(), DUMMY_NOTARY, 30.seconds)
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
|
||||
tx.setTime(Instant.now(), 30.seconds)
|
||||
tx.signWith(clientNode.keyPair!!)
|
||||
tx.toSignedTransaction(false)
|
||||
}
|
||||
@ -56,7 +56,7 @@ class NotaryServiceTests {
|
||||
@Test fun `should sign a unique transaction without a timestamp`() {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder().withItems(inputState)
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
|
||||
tx.signWith(clientNode.keyPair!!)
|
||||
tx.toSignedTransaction(false)
|
||||
}
|
||||
@ -72,8 +72,8 @@ class NotaryServiceTests {
|
||||
@Test fun `should report error for transaction with an invalid timestamp`() {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder().withItems(inputState)
|
||||
tx.setTime(Instant.now().plusSeconds(3600), DUMMY_NOTARY, 30.seconds)
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
|
||||
tx.setTime(Instant.now().plusSeconds(3600), 30.seconds)
|
||||
tx.signWith(clientNode.keyPair!!)
|
||||
tx.toSignedTransaction(false)
|
||||
}
|
||||
@ -88,31 +88,10 @@ class NotaryServiceTests {
|
||||
}
|
||||
|
||||
|
||||
@Test fun `should report error for transaction with more than one timestamp`() {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder().withItems(inputState)
|
||||
val timestamp = TimestampCommand(Instant.now(), 30.seconds)
|
||||
tx.addCommand(timestamp, DUMMY_NOTARY.owningKey)
|
||||
tx.addCommand(timestamp, DUMMY_NOTARY.owningKey)
|
||||
tx.signWith(clientNode.keyPair!!)
|
||||
tx.toSignedTransaction(false)
|
||||
}
|
||||
|
||||
val protocol = NotaryProtocol.Client(stx)
|
||||
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
|
||||
net.runNetwork()
|
||||
|
||||
val ex = assertFailsWith(ExecutionException::class) { future.get() }
|
||||
val error = (ex.cause as NotaryException).error
|
||||
assertTrue(error is NotaryError.MoreThanOneTimestamp)
|
||||
}
|
||||
|
||||
|
||||
@Test fun `should report conflict for a duplicate transaction`() {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder().withItems(inputState)
|
||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState)
|
||||
tx.signWith(clientNode.keyPair!!)
|
||||
tx.toSignedTransaction(false)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.r3corda.node.services
|
||||
|
||||
import com.r3corda.core.contracts.TimestampCommand
|
||||
import com.r3corda.core.contracts.Timestamp
|
||||
import com.r3corda.core.node.services.TimestampChecker
|
||||
import com.r3corda.core.seconds
|
||||
import org.junit.Test
|
||||
@ -17,8 +17,8 @@ class TimestampCheckerTests {
|
||||
@Test
|
||||
fun `should return true for valid timestamp`() {
|
||||
val now = clock.instant()
|
||||
val timestampPast = TimestampCommand(now - 60.seconds, now - 29.seconds)
|
||||
val timestampFuture = TimestampCommand(now + 29.seconds, now + 60.seconds)
|
||||
val timestampPast = Timestamp(now - 60.seconds, now - 29.seconds)
|
||||
val timestampFuture = Timestamp(now + 29.seconds, now + 60.seconds)
|
||||
assertTrue { timestampChecker.isValid(timestampPast) }
|
||||
assertTrue { timestampChecker.isValid(timestampFuture) }
|
||||
}
|
||||
@ -26,8 +26,8 @@ class TimestampCheckerTests {
|
||||
@Test
|
||||
fun `should return false for invalid timestamp`() {
|
||||
val now = clock.instant()
|
||||
val timestampPast = TimestampCommand(now - 60.seconds, now - 31.seconds)
|
||||
val timestampFuture = TimestampCommand(now + 31.seconds, now + 60.seconds)
|
||||
val timestampPast = Timestamp(now - 60.seconds, now - 31.seconds)
|
||||
val timestampFuture = Timestamp(now + 31.seconds, now + 60.seconds)
|
||||
assertFalse { timestampChecker.isValid(timestampPast) }
|
||||
assertFalse { timestampChecker.isValid(timestampFuture) }
|
||||
}
|
||||
|
@ -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
|
||||
@ -350,7 +350,7 @@ private class TraderDemoProtocolSeller(val otherSide: Party,
|
||||
tx.addAttachment(serviceHub.storageService.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
|
||||
|
||||
// Requesting timestamping, all CP must be timestamped.
|
||||
tx.setTime(Instant.now(), notaryNode.identity, 30.seconds)
|
||||
tx.setTime(Instant.now(), 30.seconds)
|
||||
|
||||
// Sign it as ourselves.
|
||||
tx.signWith(keyPair)
|
||||
@ -368,7 +368,7 @@ private class TraderDemoProtocolSeller(val otherSide: Party,
|
||||
|
||||
// Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works.
|
||||
val move: SignedTransaction = run {
|
||||
val builder = TransactionType.General.Builder()
|
||||
val builder = TransactionType.General.Builder(notaryNode.identity)
|
||||
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy)
|
||||
builder.signWith(keyPair)
|
||||
val notarySignature = subProtocol(NotaryProtocol.Client(builder.toSignedTransaction(false)))
|
||||
|
Loading…
Reference in New Issue
Block a user