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.
This commit is contained in:
Andrius Dagys 2018-01-26 09:32:11 +00:00 committed by GitHub
parent 0ff9c9e2e3
commit e357a88181
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 40 additions and 76 deletions

View File

@ -1304,7 +1304,7 @@ public @interface net.corda.core.flows.InitiatingFlow
@org.jetbrains.annotations.NotNull public String toString() @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 @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 @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TransactionInvalid extends net.corda.core.flows.NotaryError
public <init>(Throwable) public <init>(Throwable)
@ -1885,7 +1885,7 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l
public <init>() public <init>()
public final void commitInputStates(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party) 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 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 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.TransactionSignature sign(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey sign(byte[]) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey sign(byte[])

View File

@ -18,6 +18,7 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import java.security.SignatureException import java.security.SignatureException
import java.time.Instant
import java.util.function.Predicate import java.util.function.Predicate
class NotaryFlow { class NotaryFlow {
@ -167,7 +168,14 @@ sealed class NotaryError {
} }
/** Occurs when time specified in the [TimeWindow] command is outside the allowed tolerance. */ /** 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() { data class TransactionInvalid(val cause: Throwable) : NotaryError() {
override fun toString() = cause.toString() override fun toString() = cause.toString()

View File

@ -12,6 +12,7 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import org.slf4j.Logger import org.slf4j.Logger
import java.security.PublicKey import java.security.PublicKey
import java.time.Clock
abstract class NotaryService : SingletonSerializeAsToken() { abstract class NotaryService : SingletonSerializeAsToken() {
companion object { companion object {
@ -27,6 +28,24 @@ abstract class NotaryService : SingletonSerializeAsToken() {
if (custom) append(".custom") if (custom) append(".custom")
}.toString() }.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 abstract val services: ServiceHub
@ -52,14 +71,9 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
} }
protected open val log: Logger get() = staticLog 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 protected abstract val uniquenessProvider: UniquenessProvider
fun validateTimeWindow(t: TimeWindow?) { fun validateTimeWindow(t: TimeWindow?) = NotaryService.validateTimeWindow(services.clock, t)
if (t != null && !timeWindowChecker.isValid(t))
throw NotaryException(NotaryError.TimeWindowInvalid)
}
/** /**
* A NotaryException is thrown if any of the states have been consumed by a different transaction. Note that * 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)) val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID))
return services.keyManagementService.sign(signableData, notaryIdentityKey) 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")
} }

View File

@ -6,6 +6,7 @@ import java.time.Clock
/** /**
* Checks if the current instant provided by the input clock falls within the provided time-window. * 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()) { class TimeWindowChecker(val clock: Clock = Clock.systemUTC()) {
fun isValid(timeWindow: TimeWindow): Boolean = clock.instant() in timeWindow fun isValid(timeWindow: TimeWindow): Boolean = clock.instant() in timeWindow
} }

View File

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

View File

@ -13,7 +13,6 @@ import net.corda.core.flows.NotaryException
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.NotaryService 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.node.services.UniquenessProvider
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.deserialize 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: // Replica startup must be in parallel with other replicas, otherwise the constructor may not return:
thread(name = "BFT SMaRt replica $replicaId init", isDaemon = true) { thread(name = "BFT SMaRt replica $replicaId init", isDaemon = true) {
configHandle.use { configHandle.use {
val timeWindowChecker = TimeWindowChecker(services.clock) val replica = Replica(it, replicaId, { createMap() }, services, notaryIdentityKey)
val replica = Replica(it, replicaId, { createMap() }, services, notaryIdentityKey, timeWindowChecker)
replicaHolder.set(replica) replicaHolder.set(replica)
log.info("BFT SMaRt replica $replicaId is running.") log.info("BFT SMaRt replica $replicaId is running.")
} }
@ -131,8 +129,7 @@ class BFTNonValidatingNotaryService(
replicaId: Int, replicaId: Int,
createMap: () -> AppendOnlyPersistentMap<StateRef, UniquenessProvider.ConsumingTx, PersistedCommittedState, PersistentStateRef>, createMap: () -> AppendOnlyPersistentMap<StateRef, UniquenessProvider.ConsumingTx, PersistedCommittedState, PersistentStateRef>,
services: ServiceHubInternal, services: ServiceHubInternal,
notaryIdentityKey: PublicKey, notaryIdentityKey: PublicKey) : BFTSMaRt.Replica(config, replicaId, createMap, services, notaryIdentityKey) {
timeWindowChecker: TimeWindowChecker) : BFTSMaRt.Replica(config, replicaId, createMap, services, notaryIdentityKey, timeWindowChecker) {
override fun executeCommand(command: ByteArray): ByteArray { override fun executeCommand(command: ByteArray): ByteArray {
val request = command.deserialize<BFTSMaRt.CommitRequest>() val request = command.deserialize<BFTSMaRt.CommitRequest>()
@ -146,7 +143,7 @@ class BFTNonValidatingNotaryService(
val id = ftx.id val id = ftx.id
val inputs = ftx.inputs val inputs = ftx.inputs
val notary = ftx.notary val notary = ftx.notary
validateTimeWindow(ftx.timeWindow) NotaryService.validateTimeWindow(services.clock, ftx.timeWindow)
if (notary !in services.myInfo.legalIdentities) throw NotaryException(NotaryError.WrongNotary) if (notary !in services.myInfo.legalIdentities) throw NotaryException(NotaryError.WrongNotary)
commitInputStates(inputs, id, callerIdentity) commitInputStates(inputs, id, callerIdentity)
log.debug { "Inputs committed successfully, signing $id" } log.debug { "Inputs committed successfully, signing $id" }

View File

@ -13,14 +13,12 @@ import bftsmart.tom.server.defaultservices.DefaultRecoverable
import bftsmart.tom.server.defaultservices.DefaultReplier import bftsmart.tom.server.defaultservices.DefaultReplier
import bftsmart.tom.util.Extractor import bftsmart.tom.util.Extractor
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException import net.corda.core.flows.NotaryException
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.declaredField import net.corda.core.internal.declaredField
import net.corda.core.internal.toTypedArray import net.corda.core.internal.toTypedArray
import net.corda.core.node.services.TimeWindowChecker
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -178,8 +176,7 @@ object BFTSMaRt {
createMap: () -> AppendOnlyPersistentMap<StateRef, UniquenessProvider.ConsumingTx, createMap: () -> AppendOnlyPersistentMap<StateRef, UniquenessProvider.ConsumingTx,
BFTNonValidatingNotaryService.PersistedCommittedState, PersistentStateRef>, BFTNonValidatingNotaryService.PersistedCommittedState, PersistentStateRef>,
protected val services: ServiceHubInternal, protected val services: ServiceHubInternal,
protected val notaryIdentityKey: PublicKey, protected val notaryIdentityKey: PublicKey) : DefaultRecoverable() {
private val timeWindowChecker: TimeWindowChecker) : DefaultRecoverable() {
companion object { companion object {
private val log = contextLogger() private val log = contextLogger()
} }
@ -218,7 +215,7 @@ object BFTSMaRt {
/** /**
* Implement logic to execute the command and commit the transaction to the log. * 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? 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 { protected fun sign(bytes: ByteArray): DigitalSignature.WithKey {
return services.database.transaction { services.keyManagementService.sign(bytes, notaryIdentityKey) } return services.database.transaction { services.keyManagementService.sign(bytes, notaryIdentityKey) }
} }

View File

@ -3,7 +3,6 @@ package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotaryFlow import net.corda.core.flows.NotaryFlow
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.TimeWindowChecker
import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TrustedAuthorityNotaryService
import java.security.PublicKey import java.security.PublicKey
@ -13,8 +12,6 @@ class RaftNonValidatingNotaryService(
override val notaryIdentityKey: PublicKey, override val notaryIdentityKey: PublicKey,
override val uniquenessProvider: RaftUniquenessProvider override val uniquenessProvider: RaftUniquenessProvider
) : TrustedAuthorityNotaryService() { ) : TrustedAuthorityNotaryService() {
override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service { override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service {
return NonValidatingNotaryFlow(otherPartySession, this) return NonValidatingNotaryFlow(otherPartySession, this)
} }

View File

@ -3,7 +3,6 @@ package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotaryFlow import net.corda.core.flows.NotaryFlow
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.TimeWindowChecker
import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TrustedAuthorityNotaryService
import java.security.PublicKey import java.security.PublicKey
@ -13,8 +12,6 @@ class RaftValidatingNotaryService(
override val notaryIdentityKey: PublicKey, override val notaryIdentityKey: PublicKey,
override val uniquenessProvider: RaftUniquenessProvider override val uniquenessProvider: RaftUniquenessProvider
) : TrustedAuthorityNotaryService() { ) : TrustedAuthorityNotaryService() {
override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service { override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service {
return ValidatingNotaryFlow(otherPartySession, this) return ValidatingNotaryFlow(otherPartySession, this)
} }

View File

@ -2,14 +2,12 @@ package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotaryFlow import net.corda.core.flows.NotaryFlow
import net.corda.core.node.services.TimeWindowChecker
import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TrustedAuthorityNotaryService
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import java.security.PublicKey import java.security.PublicKey
/** A simple Notary service that does not perform transaction validation */ /** A simple Notary service that does not perform transaction validation */
class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val timeWindowChecker = TimeWindowChecker(services.clock)
override val uniquenessProvider = PersistentUniquenessProvider() override val uniquenessProvider = PersistentUniquenessProvider()
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = NonValidatingNotaryFlow(otherPartySession, this) override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = NonValidatingNotaryFlow(otherPartySession, this)

View File

@ -2,15 +2,12 @@ package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotaryFlow import net.corda.core.flows.NotaryFlow
import net.corda.core.node.services.TimeWindowChecker
import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TrustedAuthorityNotaryService
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import java.security.PublicKey import java.security.PublicKey
/** A Notary service that validates the transaction chain of the submitted transaction before committing it */ /** 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() { class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val timeWindowChecker = TimeWindowChecker(services.clock)
override val uniquenessProvider = PersistentUniquenessProvider() override val uniquenessProvider = PersistentUniquenessProvider()
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = ValidatingNotaryFlow(otherPartySession, this) override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = ValidatingNotaryFlow(otherPartySession, this)

View File

@ -22,7 +22,6 @@ import java.security.SignatureException
// START 1 // START 1
@CordaService @CordaService
class MyCustomValidatingNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { class MyCustomValidatingNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val timeWindowChecker = TimeWindowChecker(services.clock)
override val uniquenessProvider = PersistentUniquenessProvider() override val uniquenessProvider = PersistentUniquenessProvider()
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = MyValidatingNotaryFlow(otherPartySession, this) override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = MyValidatingNotaryFlow(otherPartySession, this)