mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Rename Timestamp to TimeWindow (#706)
Rename Timestamp to TimeWindow + refactoring
This commit is contained in:
parent
1aae41214f
commit
9f2b44f8f7
@ -5,7 +5,6 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogicRef
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.serialization.*
|
||||
@ -406,33 +405,63 @@ data class AuthenticatedObject<out T : Any>(
|
||||
)
|
||||
|
||||
/**
|
||||
* A time-window is required for validation/notarization purposes.
|
||||
* If present in a transaction, contains a time that was verified by the uniqueness service. The true time must be
|
||||
* between (after, before).
|
||||
* between (fromTime, untilTime).
|
||||
* Usually, a time-window is required to have both sides set (fromTime, untilTime).
|
||||
* However, some apps may require that a time-window has a start [Instant] (fromTime), but no end [Instant] (untilTime) and vice versa.
|
||||
* TODO: Consider refactoring using TimeWindow abstraction like TimeWindow.From, TimeWindow.Until, TimeWindow.Between.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class Timestamp(
|
||||
/** The time at which this transaction is said to have occurred is after this moment */
|
||||
val after: Instant?,
|
||||
/** The time at which this transaction is said to have occurred is before this moment */
|
||||
val before: Instant?
|
||||
class TimeWindow private constructor(
|
||||
/** The time at which this transaction is said to have occurred is after this moment. */
|
||||
val fromTime: Instant?,
|
||||
/** The time at which this transaction is said to have occurred is before this moment. */
|
||||
val untilTime: Instant?
|
||||
) {
|
||||
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)
|
||||
companion object {
|
||||
/** Use when the left-side [fromTime] of a [TimeWindow] is only required and we don't need an end instant (untilTime). */
|
||||
@JvmStatic
|
||||
fun fromOnly(fromTime: Instant) = TimeWindow(fromTime, null)
|
||||
|
||||
/** Use when the right-side [untilTime] of a [TimeWindow] is only required and we don't need a start instant (fromTime). */
|
||||
@JvmStatic
|
||||
fun untilOnly(untilTime: Instant) = TimeWindow(null, untilTime)
|
||||
|
||||
/** Use when both sides of a [TimeWindow] must be set ([fromTime], [untilTime]). */
|
||||
@JvmStatic
|
||||
fun between(fromTime: Instant, untilTime: Instant): TimeWindow {
|
||||
require(fromTime < untilTime) { "fromTime should be earlier than untilTime" }
|
||||
return TimeWindow(fromTime, untilTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* When we need to create a [TimeWindow] based on a specific time [Instant] and some tolerance in both sides of this instant.
|
||||
* The result will be the following time-window: ([time] - [tolerance], [time] + [tolerance]).
|
||||
*/
|
||||
@JvmStatic
|
||||
fun withTolerance(time: Instant, tolerance: Duration) = TimeWindow(time - tolerance, time + tolerance)
|
||||
}
|
||||
|
||||
constructor(time: Instant, tolerance: Duration) : this(time - tolerance, time + tolerance)
|
||||
/** The midpoint is calculated as fromTime + (untilTime - fromTime)/2. Note that it can only be computed if both sides are set. */
|
||||
val midpoint: Instant get() = fromTime!! + Duration.between(fromTime, untilTime!!).dividedBy(2)
|
||||
|
||||
val midpoint: Instant get() = after!! + Duration.between(after, before!!).dividedBy(2)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is TimeWindow) return false
|
||||
return (fromTime == other.fromTime && untilTime == other.untilTime)
|
||||
}
|
||||
|
||||
override fun hashCode() = 31 * (fromTime?.hashCode() ?: 0) + (untilTime?.hashCode() ?: 0)
|
||||
|
||||
override fun toString() = "TimeWindow(fromTime=$fromTime, untilTime=$untilTime)"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* transaction for it to be accepted: failure of any aborts the entire thing. The time is taken from a trusted
|
||||
* timestamp attached to the transaction itself i.e. it is NOT necessarily the current time.
|
||||
* time-window attached to the transaction itself i.e. it is NOT necessarily the current time.
|
||||
*
|
||||
* TODO: Contract serialization is likely to change, so the annotation is likely temporary.
|
||||
*/
|
||||
|
@ -19,7 +19,7 @@ sealed class TransactionType {
|
||||
*/
|
||||
@Throws(TransactionVerificationException::class)
|
||||
fun verify(tx: LedgerTransaction) {
|
||||
require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." }
|
||||
require(tx.notary != null || tx.timeWindow == null) { "Transactions with time-windows must be notarised" }
|
||||
val duplicates = detectDuplicateInputs(tx)
|
||||
if (duplicates.isNotEmpty()) throw TransactionVerificationException.DuplicateInputStates(tx.id, duplicates)
|
||||
val missing = verifySigners(tx)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
@ -19,7 +19,7 @@ data class TransactionForContract(val inputs: List<ContractState>,
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
val origHash: SecureHash,
|
||||
val inputNotary: Party? = null,
|
||||
val timestamp: Timestamp? = null) {
|
||||
val timeWindow: TimeWindow? = null) {
|
||||
override fun hashCode() = origHash.hashCode()
|
||||
override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
|
@ -8,7 +8,7 @@ import java.util.*
|
||||
* See: https://en.wikipedia.org/wiki/Merkle_tree
|
||||
*
|
||||
* Transaction is split into following blocks: inputs, attachments' refs, outputs, commands, notary,
|
||||
* signers, tx type, timestamp. Merkle Tree is kept in a recursive data structure. Building is done bottom up,
|
||||
* signers, tx type, time-window. Merkle Tree is kept in a recursive data structure. Building is done bottom up,
|
||||
* from all leaves' hashes. If number of leaves is not a power of two, the tree is padded with zero hashes.
|
||||
*/
|
||||
sealed class MerkleTree {
|
||||
|
@ -0,0 +1,26 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.until
|
||||
import java.time.Clock
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* Checks if the given time-window falls within the allowed tolerance interval.
|
||||
*/
|
||||
class TimeWindowChecker(val clock: Clock = Clock.systemUTC(),
|
||||
val tolerance: Duration = 30.seconds) {
|
||||
fun isValid(timeWindow: TimeWindow): Boolean {
|
||||
val untilTime = timeWindow.untilTime
|
||||
val fromTime = timeWindow.fromTime
|
||||
|
||||
val now = clock.instant()
|
||||
|
||||
// We don't need to test for (fromTime == null && untilTime == null) or backwards bounds because the TimeWindow
|
||||
// constructor already checks that.
|
||||
if (untilTime != null && untilTime until now > tolerance) return false
|
||||
if (fromTime != null && now until fromTime > tolerance) return false
|
||||
return true
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.Timestamp
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.until
|
||||
import java.time.Clock
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* Checks if the given timestamp falls within the allowed tolerance interval.
|
||||
*/
|
||||
class TimestampChecker(val clock: Clock = Clock.systemUTC(),
|
||||
val tolerance: Duration = 30.seconds) {
|
||||
fun isValid(timestampCommand: Timestamp): Boolean {
|
||||
val before = timestampCommand.before
|
||||
val after = timestampCommand.after
|
||||
|
||||
val now = clock.instant()
|
||||
|
||||
// We don't need to test for (before == null && after == null) or backwards bounds because the TimestampCommand
|
||||
// constructor already checks that.
|
||||
if (before != null && before until now > tolerance) return false
|
||||
if (after != null && now until after > tolerance) return false
|
||||
return true
|
||||
}
|
||||
}
|
@ -325,7 +325,7 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
||||
kryo.writeClassAndObject(output, obj.notary)
|
||||
kryo.writeClassAndObject(output, obj.mustSign)
|
||||
kryo.writeClassAndObject(output, obj.type)
|
||||
kryo.writeClassAndObject(output, obj.timestamp)
|
||||
kryo.writeClassAndObject(output, obj.timeWindow)
|
||||
}
|
||||
|
||||
private fun attachmentsClassLoader(kryo: Kryo, attachmentHashes: List<SecureHash>): ClassLoader? {
|
||||
@ -353,8 +353,8 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
||||
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, notary, signers, transactionType, timestamp)
|
||||
val timeWindow = kryo.readClassAndObject(input) as TimeWindow?
|
||||
return WireTransaction(inputs, attachmentHashes, outputs, commands, notary, signers, transactionType, timeWindow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ abstract class BaseTransaction(
|
||||
* If specified, a time window in which this transaction may have been notarised. Contracts can check this
|
||||
* time window to find out when a transaction is deemed to have occurred, from the ledger's perspective.
|
||||
*/
|
||||
val timestamp: Timestamp?
|
||||
val timeWindow: TimeWindow?
|
||||
) : NamedByHash {
|
||||
|
||||
protected fun checkInvariants() {
|
||||
if (notary == null) check(inputs.isEmpty()) { "The notary must be specified explicitly for any transaction that has inputs." }
|
||||
if (timestamp != null) check(notary != null) { "If a timestamp is provided, there must be a notary." }
|
||||
if (notary == null) check(inputs.isEmpty()) { "The notary must be specified explicitly for any transaction that has inputs" }
|
||||
if (timeWindow != null) check(notary != null) { "If a time-window is provided, there must be a notary" }
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@ -50,10 +50,10 @@ abstract class BaseTransaction(
|
||||
notary == other.notary &&
|
||||
mustSign == other.mustSign &&
|
||||
type == other.type &&
|
||||
timestamp == other.timestamp
|
||||
timeWindow == other.timeWindow
|
||||
}
|
||||
|
||||
override fun hashCode() = Objects.hash(notary, mustSign, type, timestamp)
|
||||
override fun hashCode() = Objects.hash(notary, mustSign, type, timeWindow)
|
||||
|
||||
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
|
||||
}
|
||||
|
@ -32,9 +32,9 @@ class LedgerTransaction(
|
||||
override val id: SecureHash,
|
||||
notary: Party?,
|
||||
signers: List<PublicKey>,
|
||||
timestamp: Timestamp?,
|
||||
timeWindow: TimeWindow?,
|
||||
type: TransactionType
|
||||
) : BaseTransaction(inputs, outputs, notary, signers, type, timestamp) {
|
||||
) : BaseTransaction(inputs, outputs, notary, signers, type, timeWindow) {
|
||||
init {
|
||||
checkInvariants()
|
||||
}
|
||||
@ -47,7 +47,7 @@ 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(), timestamp)
|
||||
inputs.map { it.state.notary }.singleOrNull(), timeWindow)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,7 +33,7 @@ interface TraversableTransaction {
|
||||
val notary: Party?
|
||||
val mustSign: List<PublicKey>
|
||||
val type: TransactionType?
|
||||
val timestamp: Timestamp?
|
||||
val timeWindow: TimeWindow?
|
||||
|
||||
/**
|
||||
* Returns a flattened list of all the components that are present in the transaction, in the following order:
|
||||
@ -45,7 +45,7 @@ interface TraversableTransaction {
|
||||
* - The notary [Party], if present
|
||||
* - Each required signer ([mustSign]) that is present
|
||||
* - The type of the transaction, if present
|
||||
* - The timestamp of the transaction, if present
|
||||
* - The time-window of the transaction, if present
|
||||
*/
|
||||
val availableComponents: List<Any>
|
||||
get() {
|
||||
@ -56,7 +56,7 @@ interface TraversableTransaction {
|
||||
notary?.let { result += it }
|
||||
result.addAll(mustSign)
|
||||
type?.let { result += it }
|
||||
timestamp?.let { result += it }
|
||||
timeWindow?.let { result += it }
|
||||
return result
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ class FilteredLeaves(
|
||||
override val notary: Party?,
|
||||
override val mustSign: List<PublicKey>,
|
||||
override val type: TransactionType?,
|
||||
override val timestamp: Timestamp?
|
||||
override val timeWindow: TimeWindow?
|
||||
) : TraversableTransaction {
|
||||
/**
|
||||
* Function that checks the whole filtered structure.
|
||||
|
@ -37,9 +37,9 @@ open class TransactionBuilder(
|
||||
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
|
||||
protected val commands: MutableList<Command> = arrayListOf(),
|
||||
protected val signers: MutableSet<PublicKey> = mutableSetOf(),
|
||||
protected var timestamp: Timestamp? = null) {
|
||||
protected var timeWindow: TimeWindow? = null) {
|
||||
|
||||
val time: Timestamp? get() = timestamp
|
||||
val time: TimeWindow? get() = timeWindow // TODO: rename using a more descriptive name (i.e. timeWindowGetter) or remove if unused.
|
||||
|
||||
/**
|
||||
* Creates a copy of the builder.
|
||||
@ -53,28 +53,28 @@ open class TransactionBuilder(
|
||||
outputs = ArrayList(outputs),
|
||||
commands = ArrayList(commands),
|
||||
signers = LinkedHashSet(signers),
|
||||
timestamp = timestamp
|
||||
timeWindow = timeWindow
|
||||
)
|
||||
|
||||
/**
|
||||
* Places a [TimestampCommand] in this transaction, removing any existing command if there is one.
|
||||
* Places a [TimeWindow] 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 [NotaryFlow].
|
||||
*
|
||||
* The window of time in which the final timestamp may lie is defined as [time] +/- [timeTolerance].
|
||||
* The window of time in which the final time-window 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) = setTime(Timestamp(time, timeTolerance))
|
||||
fun addTimeWindow(time: Instant, timeTolerance: Duration) = addTimeWindow(TimeWindow.withTolerance(time, timeTolerance))
|
||||
|
||||
fun setTime(newTimestamp: Timestamp) {
|
||||
check(notary != null) { "Only notarised transactions can have a timestamp" }
|
||||
fun addTimeWindow(timeWindow: TimeWindow) {
|
||||
check(notary != null) { "Only notarised transactions can have a time-window" }
|
||||
signers.add(notary!!.owningKey)
|
||||
check(currentSigs.isEmpty()) { "Cannot change timestamp after signing" }
|
||||
this.timestamp = newTimestamp
|
||||
check(currentSigs.isEmpty()) { "Cannot change time-window after signing" }
|
||||
this.timeWindow = timeWindow
|
||||
}
|
||||
|
||||
/** A more convenient way to add items to this transaction that calls the add* methods for you based on type */
|
||||
@ -132,7 +132,7 @@ open class TransactionBuilder(
|
||||
}
|
||||
|
||||
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
|
||||
ArrayList(outputs), ArrayList(commands), notary, signers.toList(), type, timestamp)
|
||||
ArrayList(outputs), ArrayList(commands), notary, signers.toList(), type, timeWindow)
|
||||
|
||||
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
|
||||
if (checkSufficientSignatures) {
|
||||
|
@ -31,8 +31,8 @@ class WireTransaction(
|
||||
notary: Party?,
|
||||
signers: List<PublicKey>,
|
||||
type: TransactionType,
|
||||
timestamp: Timestamp?
|
||||
) : BaseTransaction(inputs, outputs, notary, signers, type, timestamp), TraversableTransaction {
|
||||
timeWindow: TimeWindow?
|
||||
) : BaseTransaction(inputs, outputs, notary, signers, type, timeWindow), TraversableTransaction {
|
||||
init {
|
||||
checkInvariants()
|
||||
}
|
||||
@ -100,7 +100,7 @@ class WireTransaction(
|
||||
val resolvedInputs = inputs.map { ref ->
|
||||
resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash)
|
||||
}
|
||||
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, mustSign, timestamp, type)
|
||||
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, mustSign, timeWindow, type)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,7 +130,7 @@ class WireTransaction(
|
||||
notNullFalse(notary) as Party?,
|
||||
mustSign.filter { filtering(it) },
|
||||
notNullFalse(type) as TransactionType?,
|
||||
notNullFalse(timestamp) as Timestamp?
|
||||
notNullFalse(timeWindow) as TimeWindow?
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ class FinalityFlow(val transactions: Iterable<SignedTransaction>,
|
||||
|
||||
private fun needsNotarySignature(stx: SignedTransaction): Boolean {
|
||||
val wtx = stx.tx
|
||||
val needsNotarisation = wtx.inputs.isNotEmpty() || wtx.timestamp != null
|
||||
val needsNotarisation = wtx.inputs.isNotEmpty() || wtx.timeWindow != null
|
||||
return needsNotarisation && hasNoNotarySignature(stx)
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.Timestamp
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
@ -11,7 +11,7 @@ import net.corda.core.flows.FlowException
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.TimeWindowChecker
|
||||
import net.corda.core.node.services.UniquenessException
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -23,13 +23,13 @@ import net.corda.core.utilities.unwrap
|
||||
object NotaryFlow {
|
||||
/**
|
||||
* A flow to be used by a party for obtaining signature(s) from a [NotaryService] ascertaining the transaction
|
||||
* timestamp is correct and none of its inputs have been used in another completed transaction.
|
||||
* time-window is correct and none of its inputs have been used in another completed transaction.
|
||||
*
|
||||
* In case of a single-node or Raft notary, the flow will return a single signature. For the BFT notary multiple
|
||||
* signatures will be returned – one from each replica that accepted the input state commit.
|
||||
*
|
||||
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
|
||||
* by another transaction or the timestamp is invalid.
|
||||
* by another transaction or the time-window is invalid.
|
||||
*/
|
||||
@InitiatingFlow
|
||||
open class Client(private val stx: SignedTransaction,
|
||||
@ -63,7 +63,7 @@ object NotaryFlow {
|
||||
val payload: Any = if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
|
||||
stx
|
||||
} else {
|
||||
wtx.buildFilteredTransaction { it is StateRef || it is Timestamp }
|
||||
wtx.buildFilteredTransaction { it is StateRef || it is TimeWindow }
|
||||
}
|
||||
|
||||
val response = try {
|
||||
@ -90,19 +90,19 @@ object NotaryFlow {
|
||||
/**
|
||||
* A flow run by a notary service that handles notarisation requests.
|
||||
*
|
||||
* It checks that the timestamp command is valid (if present) and commits the input state, or returns a conflict
|
||||
* It checks that the time-window command is valid (if present) and commits the input state, or returns a conflict
|
||||
* if any of the input states have been previously committed.
|
||||
*
|
||||
* Additional transaction validation logic can be added when implementing [receiveAndVerifyTx].
|
||||
*/
|
||||
// See AbstractStateReplacementFlow.Acceptor for why it's Void?
|
||||
abstract class Service(val otherSide: Party,
|
||||
val timestampChecker: TimestampChecker,
|
||||
val timeWindowChecker: TimeWindowChecker,
|
||||
val uniquenessProvider: UniquenessProvider) : FlowLogic<Void?>() {
|
||||
@Suspendable
|
||||
override fun call(): Void? {
|
||||
val (id, inputs, timestamp) = receiveAndVerifyTx()
|
||||
validateTimestamp(timestamp)
|
||||
val (id, inputs, timeWindow) = receiveAndVerifyTx()
|
||||
validateTimeWindow(timeWindow)
|
||||
commitInputStates(inputs, id)
|
||||
signAndSendResponse(id)
|
||||
return null
|
||||
@ -121,9 +121,9 @@ object NotaryFlow {
|
||||
send(otherSide, listOf(signature))
|
||||
}
|
||||
|
||||
private fun validateTimestamp(t: Timestamp?) {
|
||||
if (t != null && !timestampChecker.isValid(t))
|
||||
throw NotaryException(NotaryError.TimestampInvalid)
|
||||
private fun validateTimeWindow(t: TimeWindow?) {
|
||||
if (t != null && !timeWindowChecker.isValid(t))
|
||||
throw NotaryException(NotaryError.TimeWindowInvalid)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,7 +162,7 @@ object NotaryFlow {
|
||||
* The minimum amount of information needed to notarise a transaction. Note that this does not include
|
||||
* any sensitive transaction details.
|
||||
*/
|
||||
data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val timestamp: Timestamp?)
|
||||
data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val timestamp: TimeWindow?)
|
||||
|
||||
class NotaryException(val error: NotaryError) : FlowException("Error response from Notary - $error")
|
||||
|
||||
@ -172,8 +172,8 @@ sealed class NotaryError {
|
||||
override fun toString() = "One or more input states for transaction $txId have been used in another transaction"
|
||||
}
|
||||
|
||||
/** Thrown if the time specified in the timestamp command is outside the allowed tolerance */
|
||||
object TimestampInvalid : NotaryError()
|
||||
/** Thrown if the time specified in the [TimeWindow] command is outside the allowed tolerance. */
|
||||
object TimeWindowInvalid : NotaryError()
|
||||
|
||||
data class TransactionInvalid(val msg: String) : NotaryError()
|
||||
data class SignaturesInvalid(val msg: String) : NotaryError()
|
||||
|
@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.expandedCompositeKeys
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
@ -181,9 +180,9 @@ object TwoPartyDealFlow {
|
||||
val deal = handshake.payload.dealBeingOffered
|
||||
val ptx = deal.generateAgreement(handshake.payload.notary)
|
||||
|
||||
// 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(), 30.seconds)
|
||||
// And add a request for a time-window: it may be that none of the contracts need this!
|
||||
// But it can't hurt to have one.
|
||||
ptx.addTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
||||
return Pair(ptx, arrayListOf(deal.parties.single { it == serviceHub.myInfo.legalIdentity as AbstractParty }.owningKey))
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ class TransactionEncumbranceTests {
|
||||
override val legalContractReference = SecureHash.sha256("DummyTimeLock")
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
val timeLockInput = tx.inputs.filterIsInstance<State>().singleOrNull() ?: return
|
||||
val time = tx.timestamp?.before ?: throw IllegalArgumentException("Transactions containing time-locks must be timestamped")
|
||||
val time = tx.timeWindow?.untilTime ?: throw IllegalArgumentException("Transactions containing time-locks must have a time-window")
|
||||
requireThat {
|
||||
"the time specified in the time-lock has passed" using (time >= timeLockInput.validFrom)
|
||||
}
|
||||
@ -70,7 +70,7 @@ class TransactionEncumbranceTests {
|
||||
input("5pm time-lock")
|
||||
output { stateWithNewOwner }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
timestamp(FIVE_PM)
|
||||
timeWindow(FIVE_PM)
|
||||
verifies()
|
||||
}
|
||||
}
|
||||
@ -89,7 +89,7 @@ class TransactionEncumbranceTests {
|
||||
input("5pm time-lock")
|
||||
output { state }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
timestamp(FOUR_PM)
|
||||
timeWindow(FOUR_PM)
|
||||
this `fails with` "the time specified in the time-lock has passed"
|
||||
}
|
||||
}
|
||||
@ -106,7 +106,7 @@ class TransactionEncumbranceTests {
|
||||
input("state encumbered by 5pm time-lock")
|
||||
output { stateWithNewOwner }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
timestamp(FIVE_PM)
|
||||
timeWindow(FIVE_PM)
|
||||
this `fails with` "Missing required encumbrance 1 in INPUT"
|
||||
}
|
||||
}
|
||||
@ -146,7 +146,7 @@ class TransactionEncumbranceTests {
|
||||
input("5pm time-lock")
|
||||
output { stateWithNewOwner }
|
||||
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
|
||||
timestamp(FIVE_PM)
|
||||
timeWindow(FIVE_PM)
|
||||
this `fails with` "Missing required encumbrance 1 in INPUT"
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ class TransactionTests {
|
||||
notary = DUMMY_NOTARY,
|
||||
signers = listOf(compKey, DUMMY_KEY_1.public, DUMMY_KEY_2.public),
|
||||
type = TransactionType.General,
|
||||
timestamp = null
|
||||
timeWindow = null
|
||||
)
|
||||
assertEquals(
|
||||
setOf(compKey, DUMMY_KEY_2.public),
|
||||
@ -69,7 +69,7 @@ class TransactionTests {
|
||||
notary = DUMMY_NOTARY,
|
||||
signers = listOf(DUMMY_KEY_1.public, DUMMY_KEY_2.public),
|
||||
type = TransactionType.General,
|
||||
timestamp = null
|
||||
timeWindow = null
|
||||
)
|
||||
assertFailsWith<IllegalArgumentException> { makeSigned(wtx).verifySignatures() }
|
||||
|
||||
@ -101,7 +101,7 @@ class TransactionTests {
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public)
|
||||
val timestamp: Timestamp? = null
|
||||
val timeWindow: TimeWindow? = null
|
||||
val transaction: LedgerTransaction = LedgerTransaction(
|
||||
inputs,
|
||||
outputs,
|
||||
@ -110,7 +110,7 @@ class TransactionTests {
|
||||
id,
|
||||
null,
|
||||
signers,
|
||||
timestamp,
|
||||
timeWindow,
|
||||
TransactionType.General
|
||||
)
|
||||
|
||||
@ -128,7 +128,7 @@ class TransactionTests {
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public)
|
||||
val timestamp: Timestamp? = null
|
||||
val timeWindow: TimeWindow? = null
|
||||
val transaction: LedgerTransaction = LedgerTransaction(
|
||||
inputs,
|
||||
outputs,
|
||||
@ -137,7 +137,7 @@ class TransactionTests {
|
||||
id,
|
||||
DUMMY_NOTARY,
|
||||
signers,
|
||||
timestamp,
|
||||
timeWindow,
|
||||
TransactionType.General
|
||||
)
|
||||
|
||||
@ -155,7 +155,7 @@ class TransactionTests {
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public)
|
||||
val timestamp: Timestamp? = null
|
||||
val timeWindow: TimeWindow? = null
|
||||
val transaction: LedgerTransaction = LedgerTransaction(
|
||||
inputs,
|
||||
outputs,
|
||||
@ -164,7 +164,7 @@ class TransactionTests {
|
||||
id,
|
||||
notary,
|
||||
signers,
|
||||
timestamp,
|
||||
timeWindow,
|
||||
TransactionType.General
|
||||
)
|
||||
|
||||
|
@ -43,7 +43,7 @@ class PartialMerkleTreeTest {
|
||||
input("MEGA_CORP cash")
|
||||
output("MEGA_CORP cash".output<Cash.State>().copy(owner = MINI_CORP))
|
||||
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -98,7 +98,7 @@ class PartialMerkleTreeTest {
|
||||
is StateRef -> true
|
||||
is TransactionState<*> -> elem.data.participants[0].owningKey.keys == MINI_CORP_PUBKEY.keys
|
||||
is Command -> MEGA_CORP_PUBKEY in elem.signers
|
||||
is Timestamp -> true
|
||||
is TimeWindow -> true
|
||||
is PublicKey -> elem == MEGA_CORP_PUBKEY
|
||||
else -> false
|
||||
}
|
||||
@ -113,7 +113,7 @@ class PartialMerkleTreeTest {
|
||||
assertEquals(1, leaves.inputs.size)
|
||||
assertEquals(1, leaves.mustSign.size)
|
||||
assertEquals(0, leaves.attachments.size)
|
||||
assertTrue(mt.filteredLeaves.timestamp != null)
|
||||
assertTrue(mt.filteredLeaves.timeWindow != null)
|
||||
assertEquals(null, mt.filteredLeaves.type)
|
||||
assertEquals(null, mt.filteredLeaves.notary)
|
||||
assertTrue(mt.verify())
|
||||
@ -133,7 +133,7 @@ class PartialMerkleTreeTest {
|
||||
assertTrue(mt.filteredLeaves.commands.isEmpty())
|
||||
assertTrue(mt.filteredLeaves.inputs.isEmpty())
|
||||
assertTrue(mt.filteredLeaves.outputs.isEmpty())
|
||||
assertTrue(mt.filteredLeaves.timestamp == null)
|
||||
assertTrue(mt.filteredLeaves.timeWindow == null)
|
||||
assertFailsWith<MerkleTreeException> { mt.verify() }
|
||||
}
|
||||
|
||||
@ -219,7 +219,7 @@ class PartialMerkleTreeTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeSimpleCashWtx(notary: Party, timestamp: Timestamp? = null, attachments: List<SecureHash> = emptyList()): WireTransaction {
|
||||
private fun makeSimpleCashWtx(notary: Party, timeWindow: TimeWindow? = null, attachments: List<SecureHash> = emptyList()): WireTransaction {
|
||||
return WireTransaction(
|
||||
inputs = testTx.inputs,
|
||||
attachments = attachments,
|
||||
@ -228,7 +228,7 @@ class PartialMerkleTreeTest {
|
||||
notary = notary,
|
||||
signers = listOf(MEGA_CORP_PUBKEY, DUMMY_PUBKEY_1),
|
||||
type = TransactionType.General,
|
||||
timestamp = timestamp
|
||||
timeWindow = timeWindow
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.seconds
|
||||
import org.junit.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class TimeWindowCheckerTests {
|
||||
val clock = Clock.fixed(Instant.now(), ZoneId.systemDefault())
|
||||
val timeWindowChecker = TimeWindowChecker(clock, tolerance = 30.seconds)
|
||||
|
||||
@Test
|
||||
fun `should return true for valid time-window`() {
|
||||
val now = clock.instant()
|
||||
val timeWindowPast = TimeWindow.between(now - 60.seconds, now - 29.seconds)
|
||||
val timeWindowFuture = TimeWindow.between(now + 29.seconds, now + 60.seconds)
|
||||
assertTrue { timeWindowChecker.isValid(timeWindowPast) }
|
||||
assertTrue { timeWindowChecker.isValid(timeWindowFuture) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return false for invalid time-window`() {
|
||||
val now = clock.instant()
|
||||
val timeWindowPast = TimeWindow.between(now - 60.seconds, now - 31.seconds)
|
||||
val timeWindowFuture = TimeWindow.between(now + 31.seconds, now + 60.seconds)
|
||||
assertFalse { timeWindowChecker.isValid(timeWindowPast) }
|
||||
assertFalse { timeWindowChecker.isValid(timeWindowFuture) }
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.Timestamp
|
||||
import net.corda.core.seconds
|
||||
import org.junit.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class TimestampCheckerTests {
|
||||
val clock = Clock.fixed(Instant.now(), ZoneId.systemDefault())
|
||||
val timestampChecker = TimestampChecker(clock, tolerance = 30.seconds)
|
||||
|
||||
@Test
|
||||
fun `should return true for valid timestamp`() {
|
||||
val now = clock.instant()
|
||||
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) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return false for invalid timestamp`() {
|
||||
val now = clock.instant()
|
||||
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) }
|
||||
}
|
||||
}
|
@ -107,11 +107,11 @@ class TransactionSerializationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun timestamp() {
|
||||
tx.setTime(TEST_TX_TIME, 30.seconds)
|
||||
fun timeWindow() {
|
||||
tx.addTimeWindow(TEST_TX_TIME, 30.seconds)
|
||||
tx.signWith(MEGA_CORP_KEY)
|
||||
tx.signWith(DUMMY_NOTARY_KEY)
|
||||
val stx = tx.toSignedTransaction()
|
||||
assertEquals(TEST_TX_TIME, stx.tx.timestamp?.midpoint)
|
||||
assertEquals(TEST_TX_TIME, stx.tx.timeWindow?.midpoint)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ import com.pholser.junit.quickcheck.generator.Generator
|
||||
import com.pholser.junit.quickcheck.generator.java.util.ArrayListGenerator
|
||||
import com.pholser.junit.quickcheck.random.SourceOfRandomness
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
@ -118,9 +119,9 @@ class DurationGenerator : Generator<Duration>(Duration::class.java) {
|
||||
}
|
||||
}
|
||||
|
||||
class TimestampGenerator : Generator<Timestamp>(Timestamp::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Timestamp {
|
||||
return Timestamp(InstantGenerator().generate(random, status), DurationGenerator().generate(random, status))
|
||||
class TimeWindowGenerator : Generator<TimeWindow>(TimeWindow::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): TimeWindow {
|
||||
return TimeWindow.withTolerance(InstantGenerator().generate(random, status), DurationGenerator().generate(random, status))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,13 @@ UNRELEASED
|
||||
----------
|
||||
|
||||
* API changes:
|
||||
* ``Timestamp`` used for validation/notarization time-range has been renamed to ``TimeWindow``.
|
||||
There are now 4 factory methods ``TimeWindow.fromOnly(fromTime: Instant)``,
|
||||
``TimeWindow.untilOnly(untilTime: Instant)``, ``TimeWindow.between(fromTime: Instant, untilTime: Instant)`` and
|
||||
``TimeWindow.withTolerance(time: Instant, tolerance: Duration)``.
|
||||
Previous constructors ``TimeWindow(fromTime: Instant, untilTime: Instant)`` and
|
||||
``TimeWindow(time: Instant, tolerance: Duration)`` have been removed.
|
||||
|
||||
* ``CordaPluginRegistry.requiredFlows`` is no longer needed. Instead annotate any flows you wish to start via RPC with
|
||||
``@StartableByRPC`` and any scheduled flows with ``@SchedulableFlow``.
|
||||
|
||||
|
@ -80,7 +80,7 @@ data class TradeApprovalContract(override val legalContractReference: SecureHash
|
||||
*/
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
val command = tx.commands.requireSingleCommand<TradeApprovalContract.Commands>()
|
||||
require(tx.timestamp?.midpoint != null) { "must be timestamped" }
|
||||
require(tx.timeWindow?.midpoint != null) { "must have a time-window" }
|
||||
when (command.value) {
|
||||
is Commands.Issue -> {
|
||||
requireThat {
|
||||
@ -132,7 +132,7 @@ class SubmitTradeApprovalFlow(val tradeId: String,
|
||||
// Create the TransactionBuilder and populate with the new state.
|
||||
val tx = TransactionType.General.Builder(notary)
|
||||
.withItems(tradeProposal, Command(TradeApprovalContract.Commands.Issue(), listOf(tradeProposal.source.owningKey)))
|
||||
tx.setTime(serviceHub.clock.instant(), Duration.ofSeconds(60))
|
||||
tx.addTimeWindow(serviceHub.clock.instant(), Duration.ofSeconds(60))
|
||||
// We can automatically sign as there is no untrusted data.
|
||||
val signedTx = serviceHub.signInitialTransaction(tx)
|
||||
// Notarise and distribute.
|
||||
@ -193,7 +193,7 @@ class SubmitCompletionFlow(val ref: StateRef, val verdict: WorkflowState) : Flow
|
||||
newState,
|
||||
Command(TradeApprovalContract.Commands.Completed(),
|
||||
listOf(serviceHub.myInfo.legalIdentity.owningKey, latestRecord.state.data.source.owningKey)))
|
||||
tx.setTime(serviceHub.clock.instant(), Duration.ofSeconds(60))
|
||||
tx.addTimeWindow(serviceHub.clock.instant(), Duration.ofSeconds(60))
|
||||
// We can sign this transaction immediately as we have already checked all the fields and the decision
|
||||
// is ultimately a manual one from the caller.
|
||||
// As a SignedTransaction we can pass the data around certain that it cannot be modified,
|
||||
|
@ -47,8 +47,8 @@ class UniversalContract : Contract {
|
||||
is PerceivableOr -> eval(tx, expr.left) || eval(tx, expr.right)
|
||||
is Const<Boolean> -> expr.value
|
||||
is TimePerceivable -> when (expr.cmp) {
|
||||
Comparison.LTE -> tx.timestamp!!.after!! <= eval(tx, expr.instant)
|
||||
Comparison.GTE -> tx.timestamp!!.before!! >= eval(tx, expr.instant)
|
||||
Comparison.LTE -> tx.timeWindow!!.fromTime!! <= eval(tx, expr.instant)
|
||||
Comparison.GTE -> tx.timeWindow!!.untilTime!! >= eval(tx, expr.instant)
|
||||
else -> throw NotImplementedError("eval special")
|
||||
}
|
||||
is ActorPerceivable -> tx.commands.single().signers.contains(expr.actor.owningKey)
|
||||
@ -207,7 +207,7 @@ class UniversalContract : Contract {
|
||||
assert(rest is Zero)
|
||||
|
||||
requireThat {
|
||||
"action must be timestamped" using (tx.timestamp != null)
|
||||
"action must have a time-window" using (tx.timeWindow != null)
|
||||
// "action must be authorized" by (cmd.signers.any { action.actors.any { party -> party.owningKey == it } })
|
||||
// todo perhaps merge these two requirements?
|
||||
"condition must be met" using (eval(tx, action.condition))
|
||||
|
@ -167,7 +167,7 @@ class Cap {
|
||||
fun issue() {
|
||||
transaction {
|
||||
output { stateInitial }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
this `fails with` "transaction has a single command"
|
||||
|
||||
@ -187,7 +187,7 @@ class Cap {
|
||||
transaction {
|
||||
input { stateInitial }
|
||||
output { stateAfterFixingFirst }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
@ -234,7 +234,7 @@ class Cap {
|
||||
output { stateAfterExecutionFirst }
|
||||
output { statePaymentFirst }
|
||||
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
@ -253,7 +253,7 @@ class Cap {
|
||||
input { stateAfterFixingFinal }
|
||||
output { statePaymentFinal }
|
||||
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
@ -271,7 +271,7 @@ class Cap {
|
||||
transaction {
|
||||
input { stateAfterExecutionFirst }
|
||||
output { stateAfterFixingFinal }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
|
@ -54,7 +54,7 @@ class Caplet {
|
||||
fun issue() {
|
||||
transaction {
|
||||
output { stateStart }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
this `fails with` "transaction has a single command"
|
||||
|
||||
@ -74,7 +74,7 @@ class Caplet {
|
||||
transaction {
|
||||
input { stateFixed }
|
||||
output { stateFinal }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
@ -92,7 +92,7 @@ class Caplet {
|
||||
transaction {
|
||||
input { stateStart }
|
||||
output { stateFixed }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
|
@ -51,7 +51,7 @@ class FXFwdTimeOption
|
||||
fun `issue - signature`() {
|
||||
transaction {
|
||||
output { inState }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
this `fails with` "transaction has a single command"
|
||||
|
||||
@ -77,7 +77,7 @@ class FXFwdTimeOption
|
||||
output { outState1 }
|
||||
output { outState2 }
|
||||
|
||||
timestamp(TEST_TX_TIME_AFTER_MATURITY)
|
||||
timeWindow(TEST_TX_TIME_AFTER_MATURITY)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
@ -109,7 +109,7 @@ class FXFwdTimeOption
|
||||
output { outState1 }
|
||||
output { outState2 }
|
||||
|
||||
timestamp(TEST_TX_TIME_BEFORE_MATURITY)
|
||||
timeWindow(TEST_TX_TIME_BEFORE_MATURITY)
|
||||
|
||||
tweak {
|
||||
command(acmeCorp.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
|
@ -43,7 +43,7 @@ class FXSwap {
|
||||
|
||||
transaction {
|
||||
output { inState }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
this `fails with` "transaction has a single command"
|
||||
|
||||
@ -68,7 +68,7 @@ class FXSwap {
|
||||
input { inState }
|
||||
output { outState1 }
|
||||
output { outState2 }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
@ -87,7 +87,7 @@ class FXSwap {
|
||||
input { inState }
|
||||
output { outState2 }
|
||||
output { outState1 }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
@ -106,7 +106,7 @@ class FXSwap {
|
||||
input { inState }
|
||||
output { outState1 }
|
||||
output { outState2 }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
command(momAndPop.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "condition must be met"
|
||||
@ -119,7 +119,7 @@ class FXSwap {
|
||||
input { inState }
|
||||
output { outState1 }
|
||||
output { outState2 }
|
||||
timestamp(TEST_TX_TIME_TOO_EARLY)
|
||||
timeWindow(TEST_TX_TIME_TOO_EARLY)
|
||||
|
||||
command(acmeCorp.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "condition must be met"
|
||||
@ -131,7 +131,7 @@ class FXSwap {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState1 }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
command(acmeCorp.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "output state must match action result state"
|
||||
@ -144,7 +144,7 @@ class FXSwap {
|
||||
input { inState }
|
||||
output { outState1 }
|
||||
output { outStateBad2 }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
command(acmeCorp.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "output states must match action result state"
|
||||
@ -157,7 +157,7 @@ class FXSwap {
|
||||
input { inState }
|
||||
output { outStateBad1 }
|
||||
output { outState2 }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
command(acmeCorp.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "output states must match action result state"
|
||||
@ -170,7 +170,7 @@ class FXSwap {
|
||||
input { inState }
|
||||
output { outState1 }
|
||||
output { outStateBad3 }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
command(acmeCorp.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "output states must match action result state"
|
||||
|
@ -134,7 +134,7 @@ class IRS {
|
||||
fun issue() {
|
||||
transaction {
|
||||
output { stateInitial }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
this `fails with` "transaction has a single command"
|
||||
|
||||
@ -154,7 +154,7 @@ class IRS {
|
||||
transaction {
|
||||
input { stateInitial }
|
||||
output { stateAfterFixingFirst }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
@ -201,7 +201,7 @@ class IRS {
|
||||
output { stateAfterExecutionFirst }
|
||||
output { statePaymentFirst }
|
||||
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
|
@ -143,7 +143,7 @@ class RollOutTests {
|
||||
fun issue() {
|
||||
transaction {
|
||||
output { stateStart }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
this `fails with` "transaction has a single command"
|
||||
|
||||
@ -164,7 +164,7 @@ class RollOutTests {
|
||||
input { stateStart }
|
||||
output { stateStep1a }
|
||||
output { stateStep1b }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
/* tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
|
@ -60,7 +60,7 @@ class Swaption {
|
||||
fun issue() {
|
||||
transaction {
|
||||
output { stateInitial }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
this `fails with` "transaction has a single command"
|
||||
|
||||
|
@ -70,7 +70,7 @@ class ZeroCouponBond {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
@ -88,7 +88,7 @@ class ZeroCouponBond {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
command(momAndPop.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "condition must be met"
|
||||
@ -100,7 +100,7 @@ class ZeroCouponBond {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outStateWrong }
|
||||
timestamp(TEST_TX_TIME_1)
|
||||
timeWindow(TEST_TX_TIME_1)
|
||||
|
||||
command(acmeCorp.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "output state must match action result state"
|
||||
|
@ -10,7 +10,6 @@ import net.corda.core.contracts.TransactionForContract.*;
|
||||
import net.corda.core.contracts.clauses.*;
|
||||
import net.corda.core.crypto.*;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.identity.AnonymousParty;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.node.services.*;
|
||||
@ -20,7 +19,6 @@ import org.jetbrains.annotations.*;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
import java.security.PublicKey;
|
||||
|
||||
import static kotlin.collections.CollectionsKt.*;
|
||||
import static net.corda.core.contracts.ContractsDSL.*;
|
||||
@ -206,14 +204,14 @@ public class JavaCommercialPaper implements Contract {
|
||||
if (!cmd.getSigners().contains(input.getOwner().getOwningKey()))
|
||||
throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP");
|
||||
|
||||
Timestamp timestamp = tx.getTimestamp();
|
||||
Instant time = null == timestamp
|
||||
TimeWindow timeWindow = tx.getTimeWindow();
|
||||
Instant time = null == timeWindow
|
||||
? null
|
||||
: timestamp.getBefore();
|
||||
: timeWindow.getUntilTime();
|
||||
Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutputs(), input.getOwner());
|
||||
|
||||
requireThat(require -> {
|
||||
require.using("must be timestamped", timestamp != null);
|
||||
require.using("must be timestamped", timeWindow != null);
|
||||
require.using("received amount equals the face value: "
|
||||
+ received + " vs " + input.getFaceValue(), received.equals(input.getFaceValue()));
|
||||
require.using("the paper must have matured", time != null && !time.isBefore(input.getMaturityDate()));
|
||||
@ -243,15 +241,15 @@ public class JavaCommercialPaper implements Contract {
|
||||
State groupingKey) {
|
||||
AuthenticatedObject<Commands.Issue> cmd = requireSingleCommand(tx.getCommands(), Commands.Issue.class);
|
||||
State output = single(outputs);
|
||||
Timestamp timestampCommand = tx.getTimestamp();
|
||||
Instant time = null == timestampCommand
|
||||
TimeWindow timeWindowCommand = tx.getTimeWindow();
|
||||
Instant time = null == timeWindowCommand
|
||||
? null
|
||||
: timestampCommand.getBefore();
|
||||
: timeWindowCommand.getUntilTime();
|
||||
|
||||
requireThat(require -> {
|
||||
require.using("output values sum to more than the inputs", inputs.isEmpty());
|
||||
require.using("output values sum to more than the inputs", output.faceValue.getQuantity() > 0);
|
||||
require.using("must be timestamped", timestampCommand != null);
|
||||
require.using("must be timestamped", timeWindowCommand != null);
|
||||
require.using("the maturity date is not in the past", time != null && time.isBefore(output.getMaturityDate()));
|
||||
require.using("output states are issued by a command signer", cmd.getSigners().contains(output.issuance.getParty().getOwningKey()));
|
||||
return Unit.INSTANCE;
|
||||
|
@ -126,8 +126,8 @@ class CommercialPaper : Contract {
|
||||
groupingKey: Issued<Terms>?): Set<Commands> {
|
||||
val consumedCommands = super.verify(tx, inputs, outputs, commands, groupingKey)
|
||||
commands.requireSingleCommand<Commands.Issue>()
|
||||
val timestamp = tx.timestamp
|
||||
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
|
||||
val timeWindow = tx.timeWindow
|
||||
val time = timeWindow?.untilTime ?: throw IllegalArgumentException("Issuances must have a time-window")
|
||||
|
||||
require(outputs.all { time < it.maturityDate }) { "maturity date is not in the past" }
|
||||
|
||||
@ -166,11 +166,11 @@ 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>()
|
||||
val timestamp = tx.timestamp
|
||||
val timeWindow = tx.timeWindow
|
||||
|
||||
val input = inputs.single()
|
||||
val received = tx.outputs.sumCashBy(input.owner)
|
||||
val time = timestamp?.after ?: throw IllegalArgumentException("Redemptions must be timestamped")
|
||||
val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must have a time-window")
|
||||
requireThat {
|
||||
"the paper must have matured" using (time >= input.maturityDate)
|
||||
"the received amount equals the face value" using (received == input.faceValue)
|
||||
|
@ -61,7 +61,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>()
|
||||
val timestamp: Timestamp? = tx.timestamp
|
||||
val timeWindow: TimeWindow? = tx.timeWindow
|
||||
|
||||
// Suppress compiler warning as 'key' is an unused variable when destructuring 'groups'.
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
@ -81,7 +81,7 @@ class CommercialPaperLegacy : Contract {
|
||||
// Redemption of the paper requires movement of on-ledger cash.
|
||||
val input = inputs.single()
|
||||
val received = tx.outputs.sumCashBy(input.owner)
|
||||
val time = timestamp?.after ?: throw IllegalArgumentException("Redemptions must be timestamped")
|
||||
val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must have a time-window")
|
||||
requireThat {
|
||||
"the paper must have matured" using (time >= input.maturityDate)
|
||||
"the received amount equals the face value" using (received == input.faceValue)
|
||||
@ -92,7 +92,7 @@ class CommercialPaperLegacy : Contract {
|
||||
|
||||
is Commands.Issue -> {
|
||||
val output = outputs.single()
|
||||
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
|
||||
val time = timeWindow?.untilTime ?: throw IllegalArgumentException("Issuances have a time-window")
|
||||
requireThat {
|
||||
// Don't allow people to issue commercial paper under other entities identities.
|
||||
"output states are issued by a command signer" using
|
||||
|
@ -24,36 +24,9 @@ import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import kotlin.collections.Collection
|
||||
import kotlin.collections.Iterable
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.Map
|
||||
import kotlin.collections.Set
|
||||
import kotlin.collections.all
|
||||
import kotlin.collections.asIterable
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.contains
|
||||
import kotlin.collections.distinct
|
||||
import kotlin.collections.emptySet
|
||||
import kotlin.collections.filter
|
||||
import kotlin.collections.filterIsInstance
|
||||
import kotlin.collections.first
|
||||
import kotlin.collections.firstOrNull
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.groupBy
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.collections.iterator
|
||||
import kotlin.collections.listOf
|
||||
import kotlin.collections.map
|
||||
import kotlin.collections.none
|
||||
import kotlin.collections.reduce
|
||||
import kotlin.collections.set
|
||||
import kotlin.collections.setOf
|
||||
import kotlin.collections.single
|
||||
import kotlin.collections.toSet
|
||||
import kotlin.collections.union
|
||||
import kotlin.collections.withIndex
|
||||
|
||||
// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
|
||||
val OBLIGATION_PROGRAM_ID = Obligation<Currency>()
|
||||
@ -432,12 +405,12 @@ class Obligation<P : Any> : Contract {
|
||||
if (input is State<P>) {
|
||||
val actualOutput = outputs[stateIdx]
|
||||
val deadline = input.dueBefore
|
||||
val timestamp = tx.timestamp
|
||||
val timeWindow = tx.timeWindow
|
||||
val expectedOutput = input.copy(lifecycle = expectedOutputLifecycle)
|
||||
|
||||
requireThat {
|
||||
"there is a timestamp from the authority" using (timestamp != null)
|
||||
"the due date has passed" using (timestamp!!.after?.isAfter(deadline) ?: false)
|
||||
"there is a time-window from the authority" using (timeWindow != null)
|
||||
"the due date has passed" using (timeWindow!!.fromTime?.isAfter(deadline) ?: false)
|
||||
"input state lifecycle is correct" using (input.lifecycle == expectedInputLifecycle)
|
||||
"output state corresponds exactly to input state, with lifecycle changed" using (expectedOutput == actualOutput)
|
||||
}
|
||||
@ -567,7 +540,7 @@ class Obligation<P : Any> : Contract {
|
||||
}
|
||||
tx.addCommand(Commands.SetLifecycle(lifecycle), partiesUsed.map { it.owningKey }.distinct())
|
||||
}
|
||||
tx.setTime(issuanceDef.dueBefore, issuanceDef.timeTolerance)
|
||||
tx.addTimeWindow(issuanceDef.dueBefore, issuanceDef.timeTolerance)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -225,10 +225,10 @@ object TwoPartyTradeFlow {
|
||||
tx.addOutputState(state, tradeRequest.assetForSale.state.notary)
|
||||
tx.addCommand(command, tradeRequest.assetForSale.state.data.owner.owningKey)
|
||||
|
||||
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
|
||||
// to have one.
|
||||
// And add a request for a time-window: it may be that none of the contracts need this!
|
||||
// But it can't hurt to have one.
|
||||
val currentTime = serviceHub.clock.instant()
|
||||
tx.setTime(currentTime, 30.seconds)
|
||||
tx.addTimeWindow(currentTime, 30.seconds)
|
||||
return Pair(tx, cashSigningPubKeys)
|
||||
}
|
||||
// DOCEND 1
|
||||
|
@ -97,7 +97,7 @@ class CommercialPaperTestsGeneric {
|
||||
transaction("Issuance") {
|
||||
output("paper") { thisTest.getPaper() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
@ -129,17 +129,17 @@ class CommercialPaperTestsGeneric {
|
||||
|
||||
tweak {
|
||||
outputs(700.DOLLARS `issued by` issuer)
|
||||
timestamp(TEST_TX_TIME + 8.days)
|
||||
timeWindow(TEST_TX_TIME + 8.days)
|
||||
this `fails with` "received amount equals the face value"
|
||||
}
|
||||
outputs(1000.DOLLARS `issued by` issuer)
|
||||
|
||||
|
||||
tweak {
|
||||
timestamp(TEST_TX_TIME + 2.days)
|
||||
timeWindow(TEST_TX_TIME + 2.days)
|
||||
this `fails with` "must have matured"
|
||||
}
|
||||
timestamp(TEST_TX_TIME + 8.days)
|
||||
timeWindow(TEST_TX_TIME + 8.days)
|
||||
|
||||
tweak {
|
||||
output { "paper".output<ICommercialPaperState>() }
|
||||
@ -156,7 +156,7 @@ class CommercialPaperTestsGeneric {
|
||||
transaction {
|
||||
output { thisTest.getPaper() }
|
||||
command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "output states are issued by a command signer"
|
||||
}
|
||||
}
|
||||
@ -166,7 +166,7 @@ class CommercialPaperTestsGeneric {
|
||||
transaction {
|
||||
output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "output values sum to more than the inputs"
|
||||
}
|
||||
}
|
||||
@ -176,7 +176,7 @@ class CommercialPaperTestsGeneric {
|
||||
transaction {
|
||||
output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "maturity date is not in the past"
|
||||
}
|
||||
}
|
||||
@ -187,7 +187,7 @@ class CommercialPaperTestsGeneric {
|
||||
input(thisTest.getPaper())
|
||||
output { thisTest.getPaper() }
|
||||
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "output values sum to more than the inputs"
|
||||
}
|
||||
}
|
||||
@ -259,7 +259,7 @@ class CommercialPaperTestsGeneric {
|
||||
val issuance = bigCorpServices.myInfo.legalIdentity.ref(1)
|
||||
val issueTX: SignedTransaction =
|
||||
CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
|
||||
setTime(TEST_TX_TIME, 30.seconds)
|
||||
addTimeWindow(TEST_TX_TIME, 30.seconds)
|
||||
signWith(bigCorpServices.key)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
@ -289,7 +289,7 @@ class CommercialPaperTestsGeneric {
|
||||
databaseBigCorp.transaction {
|
||||
fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> {
|
||||
val ptx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||
ptx.setTime(time, 30.seconds)
|
||||
ptx.addTimeWindow(time, 30.seconds)
|
||||
CommercialPaper().generateRedeem(ptx, moveTX.tx.outRef(1), bigCorpVaultService)
|
||||
ptx.signWith(aliceServices.key)
|
||||
ptx.signWith(bigCorpServices.key)
|
||||
|
@ -341,7 +341,7 @@ class ObligationTests {
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
// Note we can sign with either key here
|
||||
command(ALICE_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
this.verifies()
|
||||
@ -357,7 +357,7 @@ class ObligationTests {
|
||||
input("MegaCorp's $1,000,000 obligation to Bob")
|
||||
output("change") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB) }
|
||||
command(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
this.verifies()
|
||||
@ -371,7 +371,7 @@ class ObligationTests {
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
output("change") { (oneMillionDollars.splitEvenly(2).first()).OBLIGATION between Pair(ALICE, BOB) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "amounts owed on input and output must match"
|
||||
}
|
||||
}
|
||||
@ -383,7 +383,7 @@ class ObligationTests {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "any involved party has signed"
|
||||
}
|
||||
}
|
||||
@ -398,7 +398,7 @@ class ObligationTests {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
this.verifies()
|
||||
@ -412,7 +412,7 @@ class ObligationTests {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
input("Bob's $1,000,000 obligation to Alice")
|
||||
command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "all involved parties have signed"
|
||||
}
|
||||
}
|
||||
@ -425,7 +425,7 @@ class ObligationTests {
|
||||
input("MegaCorp's $1,000,000 obligation to Bob")
|
||||
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE) }
|
||||
command(ALICE_PUBKEY, BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
this.verifies()
|
||||
@ -439,7 +439,7 @@ class ObligationTests {
|
||||
input("MegaCorp's $1,000,000 obligation to Bob")
|
||||
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE) }
|
||||
command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "all involved parties have signed"
|
||||
}
|
||||
}
|
||||
@ -527,14 +527,14 @@ class ObligationTests {
|
||||
|
||||
@Test
|
||||
fun `payment default`() {
|
||||
// Try defaulting an obligation without a timestamp
|
||||
// Try defaulting an obligation without a time-window.
|
||||
ledger {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Settlement") {
|
||||
input("Alice's $1,000,000 obligation to Bob")
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Lifecycle.DEFAULTED) }
|
||||
this `fails with` "there is a timestamp from the authority"
|
||||
this `fails with` "there is a time-window from the authority"
|
||||
}
|
||||
}
|
||||
|
||||
@ -545,7 +545,7 @@ class ObligationTests {
|
||||
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` futureTestTime)
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Lifecycle.DEFAULTED) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "the due date has passed"
|
||||
}
|
||||
|
||||
@ -555,7 +555,7 @@ class ObligationTests {
|
||||
input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` pastTestTime)
|
||||
output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
|
||||
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Lifecycle.DEFAULTED) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
this.verifies()
|
||||
|
@ -75,7 +75,7 @@ class WiredTransactionGenerator : Generator<WireTransaction>(WireTransaction::cl
|
||||
notary = PartyGenerator().generate(random, status),
|
||||
signers = commands.flatMap { it.signers },
|
||||
type = TransactionType.General,
|
||||
timestamp = TimestampGenerator().generate(random, status)
|
||||
timeWindow = TimeWindowGenerator().generate(random, status)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ class VaultSchemaTest {
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public)
|
||||
val timestamp: Timestamp? = null
|
||||
val timeWindow: TimeWindow? = null
|
||||
transaction = LedgerTransaction(
|
||||
inputs,
|
||||
outputs,
|
||||
@ -128,7 +128,7 @@ class VaultSchemaTest {
|
||||
id,
|
||||
notary,
|
||||
signers,
|
||||
timestamp,
|
||||
timeWindow,
|
||||
TransactionType.General
|
||||
)
|
||||
}
|
||||
@ -151,7 +151,7 @@ class VaultSchemaTest {
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public)
|
||||
val timestamp: Timestamp? = null
|
||||
val timeWindow: TimeWindow? = null
|
||||
return LedgerTransaction(
|
||||
inputs,
|
||||
outputs,
|
||||
@ -160,7 +160,7 @@ class VaultSchemaTest {
|
||||
id,
|
||||
notary,
|
||||
signers,
|
||||
timestamp,
|
||||
timeWindow,
|
||||
TransactionType.General
|
||||
)
|
||||
}
|
||||
|
@ -509,20 +509,20 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
}
|
||||
|
||||
open protected fun makeNotaryService(type: ServiceType, tokenizableServices: MutableList<Any>) {
|
||||
val timestampChecker = TimestampChecker(platformClock, 30.seconds)
|
||||
val timeWindowChecker = TimeWindowChecker(platformClock, 30.seconds)
|
||||
val uniquenessProvider = makeUniquenessProvider(type)
|
||||
tokenizableServices.add(uniquenessProvider)
|
||||
|
||||
val notaryService = when (type) {
|
||||
SimpleNotaryService.type -> SimpleNotaryService(timestampChecker, uniquenessProvider)
|
||||
ValidatingNotaryService.type -> ValidatingNotaryService(timestampChecker, uniquenessProvider)
|
||||
RaftNonValidatingNotaryService.type -> RaftNonValidatingNotaryService(timestampChecker, uniquenessProvider as RaftUniquenessProvider)
|
||||
RaftValidatingNotaryService.type -> RaftValidatingNotaryService(timestampChecker, uniquenessProvider as RaftUniquenessProvider)
|
||||
SimpleNotaryService.type -> SimpleNotaryService(timeWindowChecker, uniquenessProvider)
|
||||
ValidatingNotaryService.type -> ValidatingNotaryService(timeWindowChecker, uniquenessProvider)
|
||||
RaftNonValidatingNotaryService.type -> RaftNonValidatingNotaryService(timeWindowChecker, uniquenessProvider as RaftUniquenessProvider)
|
||||
RaftValidatingNotaryService.type -> RaftValidatingNotaryService(timeWindowChecker, uniquenessProvider as RaftUniquenessProvider)
|
||||
BFTNonValidatingNotaryService.type -> with(configuration as FullNodeConfiguration) {
|
||||
val replicaId = bftReplicaId ?: throw IllegalArgumentException("bftReplicaId value must be specified in the configuration")
|
||||
BFTSMaRtConfig(notaryClusterAddresses).use { config ->
|
||||
val client = BFTSMaRt.Client(config, replicaId).also { tokenizableServices += it } // (Ab)use replicaId for clientId.
|
||||
BFTNonValidatingNotaryService(config, services, timestampChecker, replicaId, database, client)
|
||||
BFTNonValidatingNotaryService(config, services, timeWindowChecker, replicaId, database, client)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
@ -2,9 +2,9 @@ package net.corda.node.services.transactions
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.TimeWindowChecker
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.FilteredTransaction
|
||||
@ -20,11 +20,11 @@ import kotlin.concurrent.thread
|
||||
/**
|
||||
* A non-validating notary service operated by a group of parties that don't necessarily trust each other.
|
||||
*
|
||||
* A transaction is notarised when the consensus is reached by the cluster on its uniqueness, and timestamp validity.
|
||||
* A transaction is notarised when the consensus is reached by the cluster on its uniqueness, and time-window validity.
|
||||
*/
|
||||
class BFTNonValidatingNotaryService(config: BFTSMaRtConfig,
|
||||
services: ServiceHubInternal,
|
||||
timestampChecker: TimestampChecker,
|
||||
timeWindowChecker: TimeWindowChecker,
|
||||
serverId: Int,
|
||||
db: Database,
|
||||
private val client: BFTSMaRt.Client) : NotaryService {
|
||||
@ -32,7 +32,7 @@ class BFTNonValidatingNotaryService(config: BFTSMaRtConfig,
|
||||
val configHandle = config.handle()
|
||||
thread(name = "BFTSmartServer-$serverId", isDaemon = true) {
|
||||
configHandle.use {
|
||||
Server(configHandle.path, serverId, db, "bft_smart_notary_committed_states", services, timestampChecker)
|
||||
Server(configHandle.path, serverId, db, "bft_smart_notary_committed_states", services, timeWindowChecker)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,7 +72,7 @@ class BFTNonValidatingNotaryService(config: BFTSMaRtConfig,
|
||||
db: Database,
|
||||
tableName: String,
|
||||
services: ServiceHubInternal,
|
||||
timestampChecker: TimestampChecker) : BFTSMaRt.Server(configHome, id, db, tableName, services, timestampChecker) {
|
||||
timeWindowChecker: TimeWindowChecker) : BFTSMaRt.Server(configHome, id, db, tableName, services, timeWindowChecker) {
|
||||
|
||||
override fun executeCommand(command: ByteArray): ByteArray {
|
||||
val request = command.deserialize<BFTSMaRt.CommitRequest>()
|
||||
@ -86,7 +86,7 @@ class BFTNonValidatingNotaryService(config: BFTSMaRtConfig,
|
||||
val id = ftx.rootHash
|
||||
val inputs = ftx.filteredLeaves.inputs
|
||||
|
||||
validateTimestamp(ftx.filteredLeaves.timestamp)
|
||||
validateTimeWindow(ftx.filteredLeaves.timeWindow)
|
||||
commitInputStates(inputs, id, callerIdentity)
|
||||
|
||||
log.debug { "Inputs committed successfully, signing $id" }
|
||||
|
@ -8,13 +8,13 @@ import bftsmart.tom.server.defaultservices.DefaultRecoverable
|
||||
import bftsmart.tom.server.defaultservices.DefaultReplier
|
||||
import bftsmart.tom.util.Extractor
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.Timestamp
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.TimeWindowChecker
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
@ -147,7 +147,7 @@ object BFTSMaRt {
|
||||
val db: Database,
|
||||
tableName: String,
|
||||
val services: ServiceHubInternal,
|
||||
val timestampChecker: TimestampChecker) : DefaultRecoverable() {
|
||||
val timeWindowChecker: TimeWindowChecker) : DefaultRecoverable() {
|
||||
companion object {
|
||||
private val log = loggerFor<Server>()
|
||||
}
|
||||
@ -174,7 +174,7 @@ object BFTSMaRt {
|
||||
|
||||
/**
|
||||
* Implement logic to execute the command and commit the transaction to the log.
|
||||
* Helper methods are provided for transaction processing: [commitInputStates], [validateTimestamp], and [sign].
|
||||
* Helper methods are provided for transaction processing: [commitInputStates], [validateTimeWindow], and [sign].
|
||||
*/
|
||||
abstract fun executeCommand(command: ByteArray): ByteArray?
|
||||
|
||||
@ -201,9 +201,9 @@ object BFTSMaRt {
|
||||
}
|
||||
}
|
||||
|
||||
protected fun validateTimestamp(t: Timestamp?) {
|
||||
if (t != null && !timestampChecker.isValid(t))
|
||||
throw NotaryException(NotaryError.TimestampInvalid)
|
||||
protected fun validateTimeWindow(t: TimeWindow?) {
|
||||
if (t != null && !timeWindowChecker.isValid(t))
|
||||
throw NotaryException(NotaryError.TimeWindowInvalid)
|
||||
}
|
||||
|
||||
protected fun sign(bytes: ByteArray): DigitalSignature.WithKey {
|
||||
|
@ -2,7 +2,7 @@ package net.corda.node.services.transactions
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.TimeWindowChecker
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.core.transactions.FilteredTransaction
|
||||
import net.corda.core.utilities.unwrap
|
||||
@ -10,8 +10,8 @@ import net.corda.flows.NotaryFlow
|
||||
import net.corda.flows.TransactionParts
|
||||
|
||||
class NonValidatingNotaryFlow(otherSide: Party,
|
||||
timestampChecker: TimestampChecker,
|
||||
uniquenessProvider: UniquenessProvider) : NotaryFlow.Service(otherSide, timestampChecker, uniquenessProvider) {
|
||||
timeWindowChecker: TimeWindowChecker,
|
||||
uniquenessProvider: UniquenessProvider) : NotaryFlow.Service(otherSide, timeWindowChecker, uniquenessProvider) {
|
||||
/**
|
||||
* The received transaction is not checked for contract-validity, as that would require fully
|
||||
* resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction
|
||||
@ -26,6 +26,6 @@ class NonValidatingNotaryFlow(otherSide: Party,
|
||||
it.verify()
|
||||
it
|
||||
}
|
||||
return TransactionParts(ftx.rootHash, ftx.filteredLeaves.inputs, ftx.filteredLeaves.timestamp)
|
||||
return TransactionParts(ftx.rootHash, ftx.filteredLeaves.inputs, ftx.filteredLeaves.timeWindow)
|
||||
}
|
||||
}
|
@ -2,16 +2,16 @@ package net.corda.node.services.transactions
|
||||
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.TimeWindowChecker
|
||||
|
||||
/** A non-validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
|
||||
class RaftNonValidatingNotaryService(val timestampChecker: TimestampChecker,
|
||||
class RaftNonValidatingNotaryService(val timeWindowChecker: TimeWindowChecker,
|
||||
val uniquenessProvider: RaftUniquenessProvider) : NotaryService {
|
||||
companion object {
|
||||
val type = SimpleNotaryService.type.getSubType("raft")
|
||||
}
|
||||
|
||||
override val serviceFlowFactory: (Party, Int) -> FlowLogic<Void?> = { otherParty, _ ->
|
||||
NonValidatingNotaryFlow(otherParty, timestampChecker, uniquenessProvider)
|
||||
NonValidatingNotaryFlow(otherParty, timeWindowChecker, uniquenessProvider)
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,16 @@ package net.corda.node.services.transactions
|
||||
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.TimeWindowChecker
|
||||
|
||||
/** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
|
||||
class RaftValidatingNotaryService(val timestampChecker: TimestampChecker,
|
||||
class RaftValidatingNotaryService(val timeWindowChecker: TimeWindowChecker,
|
||||
val uniquenessProvider: RaftUniquenessProvider) : NotaryService {
|
||||
companion object {
|
||||
val type = ValidatingNotaryService.type.getSubType("raft")
|
||||
}
|
||||
|
||||
override val serviceFlowFactory: (Party, Int) -> FlowLogic<Void?> = { otherParty, _ ->
|
||||
ValidatingNotaryFlow(otherParty, timestampChecker, uniquenessProvider)
|
||||
ValidatingNotaryFlow(otherParty, timeWindowChecker, uniquenessProvider)
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,17 @@ package net.corda.node.services.transactions
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.TimeWindowChecker
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
|
||||
/** A simple Notary service that does not perform transaction validation */
|
||||
class SimpleNotaryService(val timestampChecker: TimestampChecker,
|
||||
class SimpleNotaryService(val timeWindowChecker: TimeWindowChecker,
|
||||
val uniquenessProvider: UniquenessProvider) : NotaryService {
|
||||
companion object {
|
||||
val type = ServiceType.notary.getSubType("simple")
|
||||
}
|
||||
|
||||
override val serviceFlowFactory: (Party, Int) -> FlowLogic<Void?> = { otherParty, _ ->
|
||||
NonValidatingNotaryFlow(otherParty, timestampChecker, uniquenessProvider)
|
||||
NonValidatingNotaryFlow(otherParty, timeWindowChecker, uniquenessProvider)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package net.corda.node.services.transactions
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.TimeWindowChecker
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
@ -18,9 +18,9 @@ import java.security.SignatureException
|
||||
* indeed valid.
|
||||
*/
|
||||
class ValidatingNotaryFlow(otherSide: Party,
|
||||
timestampChecker: TimestampChecker,
|
||||
timeWindowChecker: TimeWindowChecker,
|
||||
uniquenessProvider: UniquenessProvider) :
|
||||
NotaryFlow.Service(otherSide, timestampChecker, uniquenessProvider) {
|
||||
NotaryFlow.Service(otherSide, timeWindowChecker, uniquenessProvider) {
|
||||
/**
|
||||
* The received transaction is checked for contract-validity, which requires fully resolving it into a
|
||||
* [TransactionForVerification], for which the caller also has to to reveal the whole transaction
|
||||
@ -32,7 +32,7 @@ class ValidatingNotaryFlow(otherSide: Party,
|
||||
checkSignatures(stx)
|
||||
val wtx = stx.tx
|
||||
validateTransaction(wtx)
|
||||
return TransactionParts(wtx.id, wtx.inputs, wtx.timestamp)
|
||||
return TransactionParts(wtx.id, wtx.inputs, wtx.timeWindow)
|
||||
}
|
||||
|
||||
private fun checkSignatures(stx: SignedTransaction) {
|
||||
|
@ -3,17 +3,17 @@ package net.corda.node.services.transactions
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.TimeWindowChecker
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
|
||||
/** A Notary service that validates the transaction chain of the submitted transaction before committing it */
|
||||
class ValidatingNotaryService(val timestampChecker: TimestampChecker,
|
||||
class ValidatingNotaryService(val timeWindowChecker: TimeWindowChecker,
|
||||
val uniquenessProvider: UniquenessProvider) : NotaryService {
|
||||
companion object {
|
||||
val type = ServiceType.notary.getSubType("validating")
|
||||
}
|
||||
|
||||
override val serviceFlowFactory: (Party, Int) -> FlowLogic<Void?> = { otherParty, _ ->
|
||||
ValidatingNotaryFlow(otherParty, timestampChecker, uniquenessProvider)
|
||||
ValidatingNotaryFlow(otherParty, timeWindowChecker, uniquenessProvider)
|
||||
}
|
||||
}
|
||||
|
@ -474,7 +474,7 @@ class TwoPartyTradeFlowTests {
|
||||
@Test
|
||||
fun `dependency with error on seller side`() {
|
||||
ledger {
|
||||
runWithError(false, true, "must be timestamped")
|
||||
runWithError(false, true, "Issuances must have a time-window")
|
||||
}
|
||||
}
|
||||
|
||||
@ -602,7 +602,7 @@ class TwoPartyTradeFlowTests {
|
||||
// Put a broken command on so at least a signature is created
|
||||
command(issuer.owningKey) { Cash.Commands.Move() }
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
if (withError) {
|
||||
this.fails()
|
||||
} else {
|
||||
@ -642,7 +642,7 @@ class TwoPartyTradeFlowTests {
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
if (!withError)
|
||||
timestamp(time = TEST_TX_TIME)
|
||||
timeWindow(time = TEST_TX_TIME)
|
||||
if (attachmentID != null)
|
||||
attachment(attachmentID)
|
||||
if (withError) {
|
||||
|
@ -170,7 +170,7 @@ fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode, notaryNode: A
|
||||
|
||||
fun issueInvalidState(node: AbstractNode, notary: Party): StateAndRef<*> {
|
||||
val tx = DummyContract.generateInitial(Random().nextInt(), notary, node.info.legalIdentity.ref(0))
|
||||
tx.setTime(Instant.now(), 30.seconds)
|
||||
tx.addTimeWindow(Instant.now(), 30.seconds)
|
||||
val stx = node.services.signInitialTransaction(tx)
|
||||
node.services.recordTransactions(listOf(stx))
|
||||
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
||||
|
@ -167,7 +167,7 @@ class RequeryConfigurationTest {
|
||||
notary = DUMMY_NOTARY,
|
||||
signers = emptyList(),
|
||||
type = TransactionType.General,
|
||||
timestamp = null
|
||||
timeWindow = null
|
||||
)
|
||||
return SignedTransaction(wtx.serialized, listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1))))
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ class DBTransactionStorageTests {
|
||||
notary = DUMMY_NOTARY,
|
||||
signers = emptyList(),
|
||||
type = TransactionType.General,
|
||||
timestamp = null
|
||||
timeWindow = null
|
||||
)
|
||||
return SignedTransaction(wtx.serialized, listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1))))
|
||||
}
|
||||
|
@ -39,11 +39,11 @@ class NotaryServiceTests {
|
||||
net.runNetwork() // Clear network map registration messages
|
||||
}
|
||||
|
||||
@Test fun `should sign a unique transaction with a valid timestamp`() {
|
||||
@Test fun `should sign a unique transaction with a valid time-window`() {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState)
|
||||
tx.setTime(Instant.now(), 30.seconds)
|
||||
tx.addTimeWindow(Instant.now(), 30.seconds)
|
||||
clientNode.services.signInitialTransaction(tx)
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ class NotaryServiceTests {
|
||||
signatures.forEach { it.verify(stx.id) }
|
||||
}
|
||||
|
||||
@Test fun `should sign a unique transaction without a timestamp`() {
|
||||
@Test fun `should sign a unique transaction without a time-window`() {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState)
|
||||
@ -64,18 +64,18 @@ class NotaryServiceTests {
|
||||
signatures.forEach { it.verify(stx.id) }
|
||||
}
|
||||
|
||||
@Test fun `should report error for transaction with an invalid timestamp`() {
|
||||
@Test fun `should report error for transaction with an invalid time-window`() {
|
||||
val stx = run {
|
||||
val inputState = issueState(clientNode)
|
||||
val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState)
|
||||
tx.setTime(Instant.now().plusSeconds(3600), 30.seconds)
|
||||
tx.addTimeWindow(Instant.now().plusSeconds(3600), 30.seconds)
|
||||
clientNode.services.signInitialTransaction(tx)
|
||||
}
|
||||
|
||||
val future = runNotaryClient(stx)
|
||||
|
||||
val ex = assertFailsWith(NotaryException::class) { future.getOrThrow() }
|
||||
assertThat(ex.error).isInstanceOf(NotaryError.TimestampInvalid::class.java)
|
||||
assertThat(ex.error).isInstanceOf(NotaryError.TimeWindowInvalid::class.java)
|
||||
}
|
||||
|
||||
@Test fun `should sign identical transaction multiple times (signing is idempotent)`() {
|
||||
|
@ -701,7 +701,7 @@ class VaultQueryTests {
|
||||
val issuance = MEGA_CORP.ref(1)
|
||||
val commercialPaper =
|
||||
CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
|
||||
setTime(TEST_TX_TIME, 30.seconds)
|
||||
addTimeWindow(TEST_TX_TIME, 30.seconds)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction()
|
||||
|
@ -24,7 +24,6 @@ import net.corda.node.driver.poll
|
||||
import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.security.PublicKey
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.jar.JarInputStream
|
||||
import javax.servlet.http.HttpServletResponse.SC_OK
|
||||
|
@ -8,8 +8,6 @@ import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.node.driver.driver
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.testing.BOC
|
||||
import net.corda.testing.http.HttpUtils
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
@ -2,7 +2,6 @@ package net.corda.irs.api
|
||||
|
||||
import net.corda.client.rpc.notUsed
|
||||
import net.corda.core.contracts.filterStatesOfType
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.startFlow
|
||||
|
@ -6,7 +6,6 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.containsAny
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -459,7 +458,7 @@ class InterestRateSwap : Contract {
|
||||
fixingCalendar, index, indexSource, indexTenor)
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.Timestamped(), Clauses.Group()), tx.commands.select<Commands>())
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.TimeWindow(), Clauses.Group()), tx.commands.select<Commands>())
|
||||
|
||||
interface Clauses {
|
||||
/**
|
||||
@ -515,13 +514,13 @@ class InterestRateSwap : Contract {
|
||||
}
|
||||
}
|
||||
|
||||
class Timestamped : Clause<ContractState, Commands, Unit>() {
|
||||
class TimeWindow : Clause<ContractState, Commands, Unit>() {
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<ContractState>,
|
||||
outputs: List<ContractState>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
require(tx.timestamp?.midpoint != null) { "must be timestamped" }
|
||||
require(tx.timeWindow?.midpoint != null) { "must be have a time-window)" }
|
||||
// We return an empty set because we don't process any commands
|
||||
return emptySet()
|
||||
}
|
||||
|
@ -77,9 +77,9 @@ object FixingFlow {
|
||||
override fun beforeSigning(fix: Fix) {
|
||||
newDeal.generateFix(ptx, StateAndRef(txState, handshake.payload.ref), fix)
|
||||
|
||||
// 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(), 30.seconds)
|
||||
// And add a request for a time-window: it may be that none of the contracts need this!
|
||||
// But it can't hurt to have one.
|
||||
ptx.addTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
|
@ -1,10 +1,10 @@
|
||||
package net.corda.irs.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
@ -66,7 +66,7 @@ object UpdateBusinessDayFlow {
|
||||
/**
|
||||
* Returns recipients ordered by legal name, with notary nodes taking priority over party nodes.
|
||||
* Ordering is required so that we avoid situations where on clock update a party starts a scheduled flow, but
|
||||
* the notary or counterparty still use the old clock, so the timestamp on the transaction does not validate.
|
||||
* the notary or counterparty still use the old clock, so the time-window on the transaction does not validate.
|
||||
*/
|
||||
private fun getRecipients(): Iterable<NodeInfo> {
|
||||
val notaryNodes = serviceHub.networkMapCache.notaryNodes
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.irs.plugin
|
||||
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.irs.api.InterestRateSwapAPI
|
||||
import net.corda.irs.flows.FixingFlow
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.irs.testing
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
@ -224,7 +223,7 @@ class IRSTests {
|
||||
calculation = dummyIRS.calculation,
|
||||
common = dummyIRS.common,
|
||||
notary = DUMMY_NOTARY).apply {
|
||||
setTime(TEST_TX_TIME, 30.seconds)
|
||||
addTimeWindow(TEST_TX_TIME, 30.seconds)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
@ -310,7 +309,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, 30.seconds)
|
||||
addTimeWindow(TEST_TX_TIME, 30.seconds)
|
||||
signWith(MEGA_CORP_KEY)
|
||||
signWith(MINI_CORP_KEY)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
@ -375,7 +374,7 @@ class IRSTests {
|
||||
transaction("Agreement") {
|
||||
output("irs post agreement") { singleIRS() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
@ -393,7 +392,7 @@ class IRSTests {
|
||||
command(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -406,7 +405,7 @@ class IRSTests {
|
||||
input { irs }
|
||||
output("irs post agreement") { irs }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "There are no in states for an agreement"
|
||||
}
|
||||
}
|
||||
@ -420,7 +419,7 @@ class IRSTests {
|
||||
irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule))
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "There are events in the fix schedule"
|
||||
}
|
||||
}
|
||||
@ -434,7 +433,7 @@ class IRSTests {
|
||||
irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule))
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "There are events in the float schedule"
|
||||
}
|
||||
}
|
||||
@ -447,7 +446,7 @@ class IRSTests {
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(quantity = 0)))
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "All notionals must be non zero"
|
||||
}
|
||||
|
||||
@ -456,7 +455,7 @@ class IRSTests {
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0)))
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "All notionals must be non zero"
|
||||
}
|
||||
}
|
||||
@ -470,7 +469,7 @@ class IRSTests {
|
||||
modifiedIRS
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "The fixed leg rate must be positive"
|
||||
}
|
||||
}
|
||||
@ -487,7 +486,7 @@ class IRSTests {
|
||||
modifiedIRS
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "The currency of the notionals must be the same"
|
||||
}
|
||||
}
|
||||
@ -501,7 +500,7 @@ class IRSTests {
|
||||
modifiedIRS
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "All leg notionals must be the same"
|
||||
}
|
||||
}
|
||||
@ -515,7 +514,7 @@ class IRSTests {
|
||||
modifiedIRS1
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "The effective date is before the termination date for the fixed leg"
|
||||
}
|
||||
|
||||
@ -525,7 +524,7 @@ class IRSTests {
|
||||
modifiedIRS2
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "The effective date is before the termination date for the floating leg"
|
||||
}
|
||||
}
|
||||
@ -540,7 +539,7 @@ class IRSTests {
|
||||
modifiedIRS3
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "The termination dates are aligned"
|
||||
}
|
||||
|
||||
@ -551,7 +550,7 @@ class IRSTests {
|
||||
modifiedIRS4
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "The effective dates are aligned"
|
||||
}
|
||||
}
|
||||
@ -565,7 +564,7 @@ class IRSTests {
|
||||
transaction {
|
||||
output("irs post agreement") { singleIRS() }
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
@ -586,7 +585,7 @@ class IRSTests {
|
||||
command(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
output { newIRS }
|
||||
this.verifies()
|
||||
}
|
||||
@ -594,7 +593,7 @@ class IRSTests {
|
||||
// This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new
|
||||
tweak {
|
||||
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
output { oldIRS }
|
||||
this `fails with` "There is at least one difference in the IRS floating leg payment schedules"
|
||||
}
|
||||
@ -602,7 +601,7 @@ class IRSTests {
|
||||
// This tests tries to sneak in a change to another fixing (which may or may not be the latest one)
|
||||
tweak {
|
||||
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
|
||||
val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.toList()[1]
|
||||
val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey]
|
||||
@ -623,7 +622,7 @@ class IRSTests {
|
||||
// This tests modifies the payment currency for the fixing
|
||||
tweak {
|
||||
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)) }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
|
||||
val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key }
|
||||
val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY")))
|
||||
@ -666,7 +665,7 @@ class IRSTests {
|
||||
)
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
@ -681,7 +680,7 @@ class IRSTests {
|
||||
)
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
@ -710,7 +709,7 @@ class IRSTests {
|
||||
command(ORACLE_PUBKEY) {
|
||||
InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1))
|
||||
}
|
||||
timestamp(TEST_TX_TIME)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
@ -9,20 +9,20 @@ import java.math.BigDecimal
|
||||
* Specifies the contract between two parties that trade an OpenGamma IRS. Currently can only agree to trade.
|
||||
*/
|
||||
data class OGTrade(override val legalContractReference: SecureHash = SecureHash.sha256("OGTRADE.KT")) : Contract {
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.Timestamped(), Clauses.Group()), tx.commands.select<Commands>())
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select<Commands>())
|
||||
|
||||
interface Commands : CommandData {
|
||||
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to trade
|
||||
}
|
||||
|
||||
interface Clauses {
|
||||
class Timestamped : Clause<ContractState, Commands, Unit>() {
|
||||
class TimeWindowed : Clause<ContractState, Commands, Unit>() {
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<ContractState>,
|
||||
outputs: List<ContractState>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
require(tx.timestamp?.midpoint != null) { "must be timestamped" }
|
||||
require(tx.timeWindow?.midpoint != null) { "must have a time-window" }
|
||||
// We return an empty set because we don't process any commands
|
||||
return emptySet()
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import net.corda.core.crypto.SecureHash
|
||||
* of the portfolio arbitrarily.
|
||||
*/
|
||||
data class PortfolioSwap(override val legalContractReference: SecureHash = SecureHash.sha256("swordfish")) : Contract {
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.Timestamped(), Clauses.Group()), tx.commands.select<Commands>())
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select<Commands>())
|
||||
|
||||
interface Commands : CommandData {
|
||||
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to portfolio
|
||||
@ -18,13 +18,13 @@ data class PortfolioSwap(override val legalContractReference: SecureHash = Secur
|
||||
}
|
||||
|
||||
interface Clauses {
|
||||
class Timestamped : Clause<ContractState, Commands, Unit>() {
|
||||
class TimeWindowed : Clause<ContractState, Commands, Unit>() {
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<ContractState>,
|
||||
outputs: List<ContractState>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Unit?): Set<Commands> {
|
||||
require(tx.timestamp?.midpoint != null) { "must be timestamped" }
|
||||
require(tx.timeWindow?.midpoint != null) { "must have a time-window)" }
|
||||
// We return an empty set because we don't process any commands
|
||||
return emptySet()
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.services.dealsWith
|
||||
|
@ -8,7 +8,6 @@ import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.flows.AbstractStateReplacementFlow
|
||||
import net.corda.flows.StateReplacementException
|
||||
import net.corda.vega.contracts.RevisionedState
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* Flow that generates an update on a mutable deal state and commits the resulting transaction reaching consensus
|
||||
@ -20,7 +19,7 @@ object StateRevisionFlow {
|
||||
override fun assembleTx(): Pair<SignedTransaction, List<AbstractParty>> {
|
||||
val state = originalState.state.data
|
||||
val tx = state.generateRevision(originalState.state.notary, originalState, modification)
|
||||
tx.setTime(serviceHub.clock.instant(), 30.seconds)
|
||||
tx.addTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
||||
|
||||
val stx = serviceHub.signInitialTransaction(tx)
|
||||
return Pair(stx, state.participants)
|
||||
|
@ -10,7 +10,6 @@ import com.opengamma.strata.market.curve.CurveName
|
||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivities
|
||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivity
|
||||
import com.opengamma.strata.market.param.TenorDateParameterMetadata
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.serialization.SerializationCustomization
|
||||
import net.corda.vega.analytics.CordaMarketData
|
||||
|
@ -52,7 +52,7 @@ fun main(args: Array<String>) {
|
||||
|
||||
class SwapExample {
|
||||
|
||||
val VALUATION_DATE = LocalDate.of(2016, 6, 6)
|
||||
val VALUATION_DATE = LocalDate.of(2016, 6, 6)!!
|
||||
|
||||
fun main(@Suppress("UNUSED_PARAMETER") args: Array<String>) {
|
||||
val curveGroupDefinition = loadCurveGroup()
|
||||
|
@ -10,8 +10,8 @@ import net.corda.core.days
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -19,7 +19,6 @@ import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.flows.NotaryFlow
|
||||
import net.corda.flows.TwoPartyTradeFlow
|
||||
import net.corda.testing.BOC
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
@ -82,13 +81,13 @@ class SellerFlow(val otherParty: Party,
|
||||
// Attach the prospectus.
|
||||
tx.addAttachment(serviceHub.storageService.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
|
||||
|
||||
// Requesting timestamping, all CP must be timestamped.
|
||||
tx.setTime(Instant.now(), 30.seconds)
|
||||
// Requesting a time-window to be set, all CP must have a validation window.
|
||||
tx.addTimeWindow(Instant.now(), 30.seconds)
|
||||
|
||||
// Sign it as ourselves.
|
||||
tx.signWith(keyPair)
|
||||
|
||||
// Get the notary to sign the timestamp
|
||||
// Get the notary to sign the time-window.
|
||||
val notarySigs = subFlow(NotaryFlow.Client(tx.toSignedTransaction(false)))
|
||||
notarySigs.forEach { tx.addSignatureUnchecked(it) }
|
||||
|
||||
|
@ -145,8 +145,8 @@ data class TestTransactionDSLInterpreter private constructor(
|
||||
return EnforceVerifyOrFail.Token
|
||||
}
|
||||
|
||||
override fun timestamp(data: Timestamp) {
|
||||
transactionBuilder.setTime(data)
|
||||
override fun timeWindow(data: TimeWindow) {
|
||||
transactionBuilder.addTimeWindow(data)
|
||||
}
|
||||
|
||||
override fun tweak(
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.testing
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
@ -51,10 +51,10 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
|
||||
fun _command(signers: List<PublicKey>, commandData: CommandData)
|
||||
|
||||
/**
|
||||
* Adds a timestamp to the transaction.
|
||||
* @param data The [TimestampCommand].
|
||||
* Adds a time-window to the transaction.
|
||||
* @param data the [TimeWindow] (validation window).
|
||||
*/
|
||||
fun timestamp(data: Timestamp)
|
||||
fun timeWindow(data: TimeWindow)
|
||||
|
||||
/**
|
||||
* Creates a local scoped copy of the transaction.
|
||||
@ -115,11 +115,11 @@ class TransactionDSL<out T : TransactionDSLInterpreter>(val interpreter: T) : Tr
|
||||
fun command(signer: PublicKey, commandData: CommandData) = _command(listOf(signer), commandData)
|
||||
|
||||
/**
|
||||
* Adds a timestamp command to the transaction.
|
||||
* @param time The [Instant] of the [TimestampCommand].
|
||||
* @param tolerance The tolerance of the [TimestampCommand].
|
||||
* Adds a [TimeWindow] command to the transaction.
|
||||
* @param time The [Instant] of the [TimeWindow].
|
||||
* @param tolerance The tolerance of the [TimeWindow].
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun timestamp(time: Instant, tolerance: Duration = 30.seconds) =
|
||||
timestamp(Timestamp(time, tolerance))
|
||||
fun timeWindow(time: Instant, tolerance: Duration = 30.seconds) =
|
||||
timeWindow(TimeWindow.withTolerance(time, tolerance))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user