mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
Remove support for timestamp commands
This commit is contained in:
parent
f0aa5a30d4
commit
17ae349f4d
@ -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();
|
||||
|
@ -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,10 +403,7 @@ class Obligation<P> : Contract {
|
||||
if (input is State<P>) {
|
||||
val actualOutput = outputs[stateIdx]
|
||||
val deadline = input.dueBefore
|
||||
val timestamp: TimestampCommand? = if (tx.inputNotary == null)
|
||||
null
|
||||
else
|
||||
tx.getTimestampBy(tx.inputNotary!!)
|
||||
val timestamp = tx.timestamp
|
||||
val expectedOutput = input.copy(lifecycle = expectedOutputLifecycle)
|
||||
|
||||
requireThat {
|
||||
@ -544,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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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(), emptyList(), null, TransactionType.General())
|
||||
return Pair(ltx, outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(ltx.id, index)) })
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@ 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()
|
||||
@ -223,7 +223,7 @@ class CommercialPaperTestsGeneric {
|
||||
|
||||
fun makeRedeemTX(time: Instant): SignedTransaction {
|
||||
val ptx = TransactionType.General.Builder()
|
||||
ptx.setTime(time, DUMMY_NOTARY, 30.seconds)
|
||||
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)
|
||||
@ -303,7 +303,7 @@ class IRSTests {
|
||||
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)
|
||||
|
@ -84,30 +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. */
|
||||
@Deprecated("Get timestamp from the transaction")
|
||||
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.
|
||||
*
|
||||
|
@ -354,26 +354,6 @@ data class Timestamp(val after: Instant?, val before: Instant?) {
|
||||
val midpoint: Instant get() = after!! + Duration.between(after, before!!).dividedBy(2)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @deprecated timestamps are now a field on a transaction, and this exists just for legacy reasons.
|
||||
*/
|
||||
@Deprecated("timestamps are now a field on a transaction, and this exists just for legacy reasons.")
|
||||
data class TimestampCommand(val after: Instant?, val before: Instant?) : CommandData {
|
||||
init {
|
||||
if (after == null && before == null)
|
||||
throw IllegalArgumentException("At least one of before/after must be specified")
|
||||
if (after != null && before != null)
|
||||
check(after <= before)
|
||||
}
|
||||
|
||||
constructor(time: Instant, tolerance: Duration) : this(time - tolerance, time + tolerance)
|
||||
|
||||
val midpoint: Instant get() = after!! + Duration.between(after, before!!).dividedBy(2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented by a program that implements business logic on the shared ledger. All participants run this code for
|
||||
* every [LedgerTransaction] they see on the network, for every input and output state. All contracts must accept the
|
||||
|
@ -31,7 +31,7 @@ open class TransactionBuilder(
|
||||
protected var timestamp: Timestamp? = null) {
|
||||
|
||||
@Deprecated("use timestamp instead")
|
||||
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
|
||||
val time: Timestamp? get() = timestamp
|
||||
|
||||
/**
|
||||
* Creates a copy of the builder.
|
||||
@ -44,7 +44,8 @@ open class TransactionBuilder(
|
||||
attachments = ArrayList(attachments),
|
||||
outputs = ArrayList(outputs),
|
||||
commands = ArrayList(commands),
|
||||
signers = LinkedHashSet(signers)
|
||||
signers = LinkedHashSet(signers),
|
||||
timestamp = timestamp
|
||||
)
|
||||
|
||||
/**
|
||||
@ -59,28 +60,12 @@ open class TransactionBuilder(
|
||||
* collaborating parties may therefore require a higher time tolerance than a transaction being built by a single
|
||||
* node.
|
||||
*/
|
||||
@Deprecated("use setTime(Instant, Duration) instead")
|
||||
fun setTime(time: Instant, authority: Party, timeTolerance: Duration) {
|
||||
check(currentSigs.isEmpty()) { "Cannot change timestamp after signing" }
|
||||
commands.removeAll { it.value is TimestampCommand }
|
||||
addCommand(TimestampCommand(time, timeTolerance), authority.owningKey)
|
||||
}
|
||||
fun setTime(time: Instant, timeTolerance: Duration)
|
||||
= setTime(Timestamp(time, timeTolerance))
|
||||
|
||||
/**
|
||||
* Places a [TimestampCommand] in this transaction, removing any existing command if there is one.
|
||||
* The command requires a signature from the Notary service, which acts as a Timestamp Authority.
|
||||
* The signature can be obtained using [NotaryProtocol].
|
||||
*
|
||||
* The window of time in which the final timestamp may lie is defined as [time] +/- [timeTolerance].
|
||||
* If you want a non-symmetrical time window you must add the command via [addCommand] yourself. The tolerance
|
||||
* should be chosen such that your code can finish building the transaction and sending it to the TSA within that
|
||||
* window of time, taking into account factors such as network latency. Transactions being built by a group of
|
||||
* collaborating parties may therefore require a higher time tolerance than a transaction being built by a single
|
||||
* node.
|
||||
*/
|
||||
fun setTime(time: Instant, timeTolerance: Duration) {
|
||||
fun setTime(newTimestamp: Timestamp) {
|
||||
check(currentSigs.isEmpty()) { "Cannot change timestamp after signing" }
|
||||
timestamp = Timestamp(time, timeTolerance)
|
||||
this.timestamp = newTimestamp
|
||||
}
|
||||
|
||||
/** A more convenient way to add items to this transaction that calls the add* methods for you based on type */
|
||||
|
@ -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, timestamp)
|
||||
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, signers, timestamp, type)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,9 +24,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
|
||||
|
@ -83,15 +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)
|
||||
|
||||
/** Simply calls [commands.getTimestampBy] as a shortcut to make code completion more intuitive. */
|
||||
@Deprecated("use timestamp property instead")
|
||||
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() {
|
||||
|
@ -50,7 +50,7 @@ data class WireTransaction(val inputs: List<StateRef>,
|
||||
val commands: List<Command>,
|
||||
val signers: List<PublicKey>,
|
||||
val type: TransactionType,
|
||||
val timestamp: Timestamp? = null) : NamedByHash {
|
||||
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
|
||||
@ -166,8 +166,8 @@ data class LedgerTransaction(
|
||||
override val id: SecureHash,
|
||||
/** The notary key and the command keys together: a signed transaction must provide signatures for all of these. */
|
||||
val signers: List<PublicKey>,
|
||||
val type: TransactionType,
|
||||
val timestamp: Timestamp? = null
|
||||
val timestamp: Timestamp?,
|
||||
val type: TransactionType
|
||||
) : NamedByHash {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
|
||||
@ -177,7 +177,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
|
||||
|
||||
|
@ -232,6 +232,7 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
||||
kryo.writeClassAndObject(output, obj.commands)
|
||||
kryo.writeClassAndObject(output, obj.signers)
|
||||
kryo.writeClassAndObject(output, obj.type)
|
||||
kryo.writeClassAndObject(output, obj.timestamp)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -262,8 +263,9 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
||||
val commands = kryo.readClassAndObject(input) as List<Command>
|
||||
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, signers, transactionType, timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,10 @@ data class TestTransactionDSLInterpreter private constructor(
|
||||
return EnforceVerifyOrFail.Token
|
||||
}
|
||||
|
||||
override fun timestamp(data: Timestamp) {
|
||||
transactionBuilder.setTime(data)
|
||||
}
|
||||
|
||||
override fun tweak(
|
||||
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||
) = dsl(TransactionDSL(copy()))
|
||||
|
@ -47,6 +47,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))
|
||||
}
|
||||
|
@ -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
|
||||
@ -84,22 +85,16 @@ object NotaryProtocol {
|
||||
}
|
||||
|
||||
private fun findNotaryParty(): Party {
|
||||
var maybeNotaryKey: PublicKey? = null
|
||||
val wtx = stx.tx
|
||||
val firstStateRef = wtx.inputs.firstOrNull()
|
||||
var maybeNotaryParty: Party? = if (firstStateRef == null)
|
||||
null
|
||||
else
|
||||
serviceHub.loadState(firstStateRef).notary
|
||||
|
||||
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")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,18 +133,12 @@ object NotaryProtocol {
|
||||
send(otherSide, sendSessionID, result)
|
||||
}
|
||||
|
||||
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))
|
||||
private fun validateTimestamp(tx: WireTransaction) =
|
||||
if (tx.timestamp != null
|
||||
&& !timestampChecker.isValid(tx.timestamp))
|
||||
throw NotaryException(NotaryError.TimestampInvalid())
|
||||
}
|
||||
else
|
||||
Unit
|
||||
|
||||
/**
|
||||
* No pre-commit processing is done. Transaction is not checked for contract-validity, as that would require fully
|
||||
|
@ -101,9 +101,11 @@ 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, notaryNode.identity.owningKey))
|
||||
if (missingSigs != setOf(myKeyPair.public))
|
||||
throw SignatureException("The set of missing signatures is not as expected: $missingSigs")
|
||||
|
||||
val wtx: WireTransaction = stx.tx
|
||||
@ -323,7 +325,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 +377,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 +385,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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -33,7 +33,8 @@ class LCApplication : Contract {
|
||||
// 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")
|
||||
// FIXME: This isn't used anywhere
|
||||
tx.timestamp
|
||||
|
||||
when (command.value) {
|
||||
is Commands.ApplyForLC -> {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -475,7 +475,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) {
|
||||
|
@ -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
|
||||
@ -40,7 +40,7 @@ class NotaryServiceTests {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder().withItems(inputState)
|
||||
tx.setTime(Instant.now(), DUMMY_NOTARY, 30.seconds)
|
||||
tx.setTime(Instant.now(), 30.seconds)
|
||||
tx.signWith(clientNode.keyPair!!)
|
||||
tx.toSignedTransaction(false)
|
||||
}
|
||||
@ -73,7 +73,7 @@ class NotaryServiceTests {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder().withItems(inputState)
|
||||
tx.setTime(Instant.now().plusSeconds(3600), DUMMY_NOTARY, 30.seconds)
|
||||
tx.setTime(Instant.now().plusSeconds(3600), 30.seconds)
|
||||
tx.signWith(clientNode.keyPair!!)
|
||||
tx.toSignedTransaction(false)
|
||||
}
|
||||
@ -88,27 +88,6 @@ 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)
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user