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()
##
@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 <init>(Throwable)
@ -1885,7 +1885,7 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l
public <init>()
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[])

View File

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

View File

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

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.
*/
@Deprecated("This class is no longer used")
class TimeWindowChecker(val clock: Clock = Clock.systemUTC()) {
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.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<StateRef, UniquenessProvider.ConsumingTx, PersistedCommittedState, PersistentStateRef>,
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<BFTSMaRt.CommitRequest>()
@ -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" }

View File

@ -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<StateRef, UniquenessProvider.ConsumingTx,
BFTNonValidatingNotaryService.PersistedCommittedState, PersistentStateRef>,
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) }
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Void?> = MyValidatingNotaryFlow(otherPartySession, this)