Rename Timestamp to TimeWindow (#706)

Rename Timestamp to TimeWindow + refactoring
This commit is contained in:
Konstantinos Chalkias 2017-05-25 13:18:49 +01:00 committed by GitHub
parent 1aae41214f
commit 9f2b44f8f7
76 changed files with 382 additions and 387 deletions

View File

@ -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.
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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