From e357a881818aa27355a5144156baed3cd7dc7433 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Fri, 26 Jan 2018 09:32:11 +0000 Subject: [PATCH] Deprecate TimeWindowChecker, make TimeWindowInvalid report exact current time and transaction time window (#2280) * Make notary service return the current time and the transaction time window along with the TimeWindowInvalid error. Deprecate TimeWindowChecker. Add a static method for validating transaction time window to reduce code duplication. --- .ci/api-current.txt | 4 +- .../kotlin/net/corda/core/flows/NotaryFlow.kt | 10 ++++- .../corda/core/node/services/NotaryService.kt | 29 +++++++++++--- .../core/node/services/TimeWindowChecker.kt | 1 + .../node/services/TimeWindowCheckerTests.kt | 39 ------------------- .../BFTNonValidatingNotaryService.kt | 9 ++--- .../node/services/transactions/BFTSMaRt.kt | 12 +----- .../RaftNonValidatingNotaryService.kt | 3 -- .../RaftValidatingNotaryService.kt | 3 -- .../transactions/SimpleNotaryService.kt | 2 - .../transactions/ValidatingNotaryService.kt | 3 -- .../corda/notarydemo/MyCustomNotaryService.kt | 1 - 12 files changed, 40 insertions(+), 76 deletions(-) delete mode 100644 core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 493a31b91a..54472637a1 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1304,7 +1304,7 @@ public @interface net.corda.core.flows.InitiatingFlow @org.jetbrains.annotations.NotNull public String toString() ## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid extends net.corda.core.flows.NotaryError - public static final net.corda.core.flows.NotaryError$TimeWindowInvalid INSTANCE + @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.flows.NotaryError$TimeWindowInvalid INSTANCE ## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TransactionInvalid extends net.corda.core.flows.NotaryError public (Throwable) @@ -1885,7 +1885,7 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l public () public final void commitInputStates(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull protected org.slf4j.Logger getLog() - @org.jetbrains.annotations.NotNull protected abstract net.corda.core.node.services.TimeWindowChecker getTimeWindowChecker() + @org.jetbrains.annotations.NotNull protected net.corda.core.node.services.TimeWindowChecker getTimeWindowChecker() @org.jetbrains.annotations.NotNull protected abstract net.corda.core.node.services.UniquenessProvider getUniquenessProvider() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey sign(byte[]) diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index 52a802ed0a..395bc6635e 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -18,6 +18,7 @@ import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.unwrap import java.security.SignatureException +import java.time.Instant import java.util.function.Predicate class NotaryFlow { @@ -167,7 +168,14 @@ sealed class NotaryError { } /** Occurs when time specified in the [TimeWindow] command is outside the allowed tolerance. */ - object TimeWindowInvalid : NotaryError() + data class TimeWindowInvalid(val currentTime: Instant, val txTimeWindow: TimeWindow) : NotaryError() { + override fun toString() = "Current time $currentTime is outside the time bounds specified by the transaction: $txTimeWindow" + + companion object { + @JvmField @Deprecated("Here only for binary compatibility purposes, do not use.") + val INSTANCE = TimeWindowInvalid(Instant.EPOCH, TimeWindow.fromOnly(Instant.EPOCH)) + } + } data class TransactionInvalid(val cause: Throwable) : NotaryError() { override fun toString() = cause.toString() diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt index 5fff5d54ce..fc24174df4 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt @@ -12,6 +12,7 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import org.slf4j.Logger import java.security.PublicKey +import java.time.Clock abstract class NotaryService : SingletonSerializeAsToken() { companion object { @@ -27,6 +28,24 @@ abstract class NotaryService : SingletonSerializeAsToken() { if (custom) append(".custom") }.toString() } + + /** + * Checks if the current instant provided by the clock falls within the specified time window. + * + * @throws NotaryException if current time is outside the specified time window. The exception contains + * the [NotaryError.TimeWindowInvalid] error. + */ + @JvmStatic + @Throws(NotaryException::class) + fun validateTimeWindow(clock: Clock, timeWindow: TimeWindow?) { + if (timeWindow == null) return + val currentTime = clock.instant() + if (currentTime !in timeWindow) { + throw NotaryException( + NotaryError.TimeWindowInvalid(currentTime, timeWindow) + ) + } + } } abstract val services: ServiceHub @@ -52,14 +71,9 @@ abstract class TrustedAuthorityNotaryService : NotaryService() { } protected open val log: Logger get() = staticLog - // TODO: specify the valid time window in config, and convert TimeWindowChecker to a utility method - protected abstract val timeWindowChecker: TimeWindowChecker protected abstract val uniquenessProvider: UniquenessProvider - fun validateTimeWindow(t: TimeWindow?) { - if (t != null && !timeWindowChecker.isValid(t)) - throw NotaryException(NotaryError.TimeWindowInvalid) - } + fun validateTimeWindow(t: TimeWindow?) = NotaryService.validateTimeWindow(services.clock, t) /** * A NotaryException is thrown if any of the states have been consumed by a different transaction. Note that @@ -98,4 +112,7 @@ abstract class TrustedAuthorityNotaryService : NotaryService() { val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID)) return services.keyManagementService.sign(signableData, notaryIdentityKey) } + + @Deprecated("This property is no longer used") @Suppress("DEPRECATION") + protected open val timeWindowChecker: TimeWindowChecker get() = throw UnsupportedOperationException("No default implementation, need to override") } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt index fe1e95d96b..a4eeb0b487 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt @@ -6,6 +6,7 @@ import java.time.Clock /** * Checks if the current instant provided by the input clock falls within the provided time-window. */ +@Deprecated("This class is no longer used") class TimeWindowChecker(val clock: Clock = Clock.systemUTC()) { fun isValid(timeWindow: TimeWindow): Boolean = clock.instant() in timeWindow } diff --git a/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt b/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt deleted file mode 100644 index 61d37b5211..0000000000 --- a/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt +++ /dev/null @@ -1,39 +0,0 @@ -package net.corda.core.node.services - -import net.corda.core.contracts.TimeWindow -import net.corda.core.utilities.seconds -import org.junit.Test -import java.time.Clock -import java.time.Instant -import java.time.ZoneOffset -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class TimeWindowCheckerTests { - val clock: Clock = Clock.fixed(Instant.now(), ZoneOffset.UTC) - val timeWindowChecker = TimeWindowChecker(clock) - - @Test - fun `should return true for valid time-window`() { - val now = clock.instant() - val timeWindowBetween = TimeWindow.between(now - 10.seconds, now + 10.seconds) - val timeWindowFromOnly = TimeWindow.fromOnly(now - 10.seconds) - val timeWindowUntilOnly = TimeWindow.untilOnly(now + 10.seconds) - assertTrue { timeWindowChecker.isValid(timeWindowBetween) } - assertTrue { timeWindowChecker.isValid(timeWindowFromOnly) } - assertTrue { timeWindowChecker.isValid(timeWindowUntilOnly) } - } - - @Test - fun `should return false for invalid time-window`() { - val now = clock.instant() - val timeWindowBetweenPast = TimeWindow.between(now - 10.seconds, now - 2.seconds) - val timeWindowBetweenFuture = TimeWindow.between(now + 2.seconds, now + 10.seconds) - val timeWindowFromOnlyFuture = TimeWindow.fromOnly(now + 10.seconds) - val timeWindowUntilOnlyPast = TimeWindow.untilOnly(now - 10.seconds) - assertFalse { timeWindowChecker.isValid(timeWindowBetweenPast) } - assertFalse { timeWindowChecker.isValid(timeWindowBetweenFuture) } - assertFalse { timeWindowChecker.isValid(timeWindowFromOnlyFuture) } - assertFalse { timeWindowChecker.isValid(timeWindowUntilOnlyPast) } - } -} diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index 9f6ffe1d08..979e2d2ef0 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -13,7 +13,6 @@ import net.corda.core.flows.NotaryException import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.node.services.NotaryService -import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.UniquenessProvider import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.deserialize @@ -54,8 +53,7 @@ class BFTNonValidatingNotaryService( // Replica startup must be in parallel with other replicas, otherwise the constructor may not return: thread(name = "BFT SMaRt replica $replicaId init", isDaemon = true) { configHandle.use { - val timeWindowChecker = TimeWindowChecker(services.clock) - val replica = Replica(it, replicaId, { createMap() }, services, notaryIdentityKey, timeWindowChecker) + val replica = Replica(it, replicaId, { createMap() }, services, notaryIdentityKey) replicaHolder.set(replica) log.info("BFT SMaRt replica $replicaId is running.") } @@ -131,8 +129,7 @@ class BFTNonValidatingNotaryService( replicaId: Int, createMap: () -> AppendOnlyPersistentMap, services: ServiceHubInternal, - notaryIdentityKey: PublicKey, - timeWindowChecker: TimeWindowChecker) : BFTSMaRt.Replica(config, replicaId, createMap, services, notaryIdentityKey, timeWindowChecker) { + notaryIdentityKey: PublicKey) : BFTSMaRt.Replica(config, replicaId, createMap, services, notaryIdentityKey) { override fun executeCommand(command: ByteArray): ByteArray { val request = command.deserialize() @@ -146,7 +143,7 @@ class BFTNonValidatingNotaryService( val id = ftx.id val inputs = ftx.inputs val notary = ftx.notary - validateTimeWindow(ftx.timeWindow) + NotaryService.validateTimeWindow(services.clock, ftx.timeWindow) if (notary !in services.myInfo.legalIdentities) throw NotaryException(NotaryError.WrongNotary) commitInputStates(inputs, id, callerIdentity) log.debug { "Inputs committed successfully, signing $id" } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt index 67e49887ea..84ca283b48 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt @@ -13,14 +13,12 @@ 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.TimeWindow import net.corda.core.crypto.* import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryException import net.corda.core.identity.Party import net.corda.core.internal.declaredField import net.corda.core.internal.toTypedArray -import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.UniquenessProvider import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.CordaSerializable @@ -178,8 +176,7 @@ object BFTSMaRt { createMap: () -> AppendOnlyPersistentMap, protected val services: ServiceHubInternal, - protected val notaryIdentityKey: PublicKey, - private val timeWindowChecker: TimeWindowChecker) : DefaultRecoverable() { + protected val notaryIdentityKey: PublicKey) : DefaultRecoverable() { companion object { private val log = contextLogger() } @@ -218,7 +215,7 @@ object BFTSMaRt { /** * Implement logic to execute the command and commit the transaction to the log. - * Helper methods are provided for transaction processing: [commitInputStates], [validateTimeWindow], and [sign]. + * Helper methods are provided for transaction processing: [commitInputStates], and [sign]. */ abstract fun executeCommand(command: ByteArray): ByteArray? @@ -245,11 +242,6 @@ object BFTSMaRt { } } - protected fun validateTimeWindow(t: TimeWindow?) { - if (t != null && !timeWindowChecker.isValid(t)) - throw NotaryException(NotaryError.TimeWindowInvalid) - } - protected fun sign(bytes: ByteArray): DigitalSignature.WithKey { return services.database.transaction { services.keyManagementService.sign(bytes, notaryIdentityKey) } } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt index e672380398..63fef9fdfe 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt @@ -3,7 +3,6 @@ package net.corda.node.services.transactions import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow import net.corda.core.node.ServiceHub -import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.TrustedAuthorityNotaryService import java.security.PublicKey @@ -13,8 +12,6 @@ class RaftNonValidatingNotaryService( override val notaryIdentityKey: PublicKey, override val uniquenessProvider: RaftUniquenessProvider ) : TrustedAuthorityNotaryService() { - override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock) - override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service { return NonValidatingNotaryFlow(otherPartySession, this) } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt index 3e4899ae0a..0ddbb14ee0 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt @@ -3,7 +3,6 @@ package net.corda.node.services.transactions import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow import net.corda.core.node.ServiceHub -import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.TrustedAuthorityNotaryService import java.security.PublicKey @@ -13,8 +12,6 @@ class RaftValidatingNotaryService( override val notaryIdentityKey: PublicKey, override val uniquenessProvider: RaftUniquenessProvider ) : TrustedAuthorityNotaryService() { - override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock) - override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service { return ValidatingNotaryFlow(otherPartySession, this) } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt index cb4401cae5..f9ace1e95e 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt @@ -2,14 +2,12 @@ package net.corda.node.services.transactions import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow -import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal import java.security.PublicKey /** A simple Notary service that does not perform transaction validation */ class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { - override val timeWindowChecker = TimeWindowChecker(services.clock) override val uniquenessProvider = PersistentUniquenessProvider() override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = NonValidatingNotaryFlow(otherPartySession, this) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt index 5e687c3b6d..01da0911dd 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt @@ -2,15 +2,12 @@ package net.corda.node.services.transactions import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow -import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal import java.security.PublicKey /** A Notary service that validates the transaction chain of the submitted transaction before committing it */ class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { - override val timeWindowChecker = TimeWindowChecker(services.clock) - override val uniquenessProvider = PersistentUniquenessProvider() override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = ValidatingNotaryFlow(otherPartySession, this) diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt index 55adae9ee2..1dd9cda94b 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt @@ -22,7 +22,6 @@ import java.security.SignatureException // START 1 @CordaService class MyCustomValidatingNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { - override val timeWindowChecker = TimeWindowChecker(services.clock) override val uniquenessProvider = PersistentUniquenessProvider() override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = MyValidatingNotaryFlow(otherPartySession, this)