mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
ENT-4610 Move tx signing to the Uniqueness provider (#5773)
* ENT-4610 Move tx signing to the Uniqueness provider * Make detekt happy * Remove unused imports * Address review comment
This commit is contained in:
parent
81a60377fa
commit
06f97cfed5
@ -4,12 +4,15 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.internal.IdempotentFlow
|
||||||
import net.corda.core.internal.MIN_PLATFORM_VERSION_FOR_BACKPRESSURE_MESSAGE
|
import net.corda.core.internal.MIN_PLATFORM_VERSION_FOR_BACKPRESSURE_MESSAGE
|
||||||
import net.corda.core.internal.checkParameterHash
|
import net.corda.core.internal.checkParameterHash
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
|
import java.lang.IllegalStateException
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,7 +28,7 @@ import java.time.Duration
|
|||||||
* @param etaThreshold If the ETA for processing the request, according to the service, is greater than this, notify the client.
|
* @param etaThreshold If the ETA for processing the request, according to the service, is greater than this, notify the client.
|
||||||
*/
|
*/
|
||||||
// See AbstractStateReplacementFlow.Acceptor for why it's Void?
|
// See AbstractStateReplacementFlow.Acceptor for why it's Void?
|
||||||
abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service: SinglePartyNotaryService, private val etaThreshold: Duration) : FlowLogic<Void?>() {
|
abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service: SinglePartyNotaryService, private val etaThreshold: Duration) : FlowLogic<Void?>(), IdempotentFlow {
|
||||||
companion object {
|
companion object {
|
||||||
// TODO: Determine an appropriate limit and also enforce in the network parameters and the transaction builder.
|
// TODO: Determine an appropriate limit and also enforce in the network parameters and the transaction builder.
|
||||||
private const val maxAllowedInputsAndReferences = 10_000
|
private const val maxAllowedInputsAndReferences = 10_000
|
||||||
@ -47,7 +50,7 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
|
|||||||
override fun call(): Void? {
|
override fun call(): Void? {
|
||||||
val requestPayload = otherSideSession.receive<NotarisationPayload>().unwrap { it }
|
val requestPayload = otherSideSession.receive<NotarisationPayload>().unwrap { it }
|
||||||
|
|
||||||
try {
|
val commitStatus = try {
|
||||||
val tx: TransactionParts = validateRequest(requestPayload)
|
val tx: TransactionParts = validateRequest(requestPayload)
|
||||||
val request = NotarisationRequest(tx.inputs, tx.id)
|
val request = NotarisationRequest(tx.inputs, tx.id)
|
||||||
validateRequestSignature(request, requestPayload.requestSignature)
|
validateRequestSignature(request, requestPayload.requestSignature)
|
||||||
@ -73,7 +76,13 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
|
|||||||
throw NotaryException(e.error, transactionId)
|
throw NotaryException(e.error, transactionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
signTransactionAndSendResponse(transactionId!!)
|
if (commitStatus is UniquenessProvider.Result.Success) {
|
||||||
|
sendSignedResponse(transactionId!!, commitStatus.signature)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val error = IllegalStateException("Request that failed uniqueness reached signing code! Ignoring.")
|
||||||
|
throw NotaryException(NotaryError.General(error))
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,8 +133,7 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
|
|||||||
abstract fun verifyTransaction(requestPayload: NotarisationPayload)
|
abstract fun verifyTransaction(requestPayload: NotarisationPayload)
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun signTransactionAndSendResponse(txId: SecureHash) {
|
private fun sendSignedResponse(txId: SecureHash, signature: TransactionSignature) {
|
||||||
val signature = service.signTransaction(txId)
|
|
||||||
logger.info("Transaction [$txId] successfully notarised, sending signature back to [${otherSideSession.counterparty.name}]")
|
logger.info("Transaction [$txId] successfully notarised, sending signature back to [${otherSideSession.counterparty.name}]")
|
||||||
otherSideSession.send(NotarisationResponse(listOf(signature)))
|
otherSideSession.send(NotarisationResponse(listOf(signature)))
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,11 @@ import net.corda.core.contracts.StateRef
|
|||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.isFulfilledBy
|
import net.corda.core.crypto.isFulfilledBy
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.NotarisationRequest
|
||||||
|
import net.corda.core.flows.NotarisationRequestSignature
|
||||||
|
import net.corda.core.flows.NotarisationResponse
|
||||||
|
import net.corda.core.flows.NotaryError
|
||||||
|
import net.corda.core.flows.StateConsumptionDetails
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
|
@ -40,7 +40,7 @@ abstract class SinglePartyNotaryService : NotaryService() {
|
|||||||
requestSignature: NotarisationRequestSignature,
|
requestSignature: NotarisationRequestSignature,
|
||||||
timeWindow: TimeWindow?,
|
timeWindow: TimeWindow?,
|
||||||
references: List<StateRef>
|
references: List<StateRef>
|
||||||
) {
|
): Result {
|
||||||
// TODO: Log the request here. Benchmarking shows that logging is expensive and we might get better performance
|
// TODO: Log the request here. Benchmarking shows that logging is expensive and we might get better performance
|
||||||
// when we concurrently log requests here as part of the flows, instead of logging sequentially in the
|
// when we concurrently log requests here as part of the flows, instead of logging sequentially in the
|
||||||
// `UniquenessProvider`.
|
// `UniquenessProvider`.
|
||||||
@ -60,9 +60,11 @@ abstract class SinglePartyNotaryService : NotaryService() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (result is UniquenessProvider.Result.Failure) {
|
if (result is Result.Failure) {
|
||||||
throw NotaryInternalException(result.error)
|
throw NotaryInternalException(result.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,4 +99,5 @@ abstract class SinglePartyNotaryService : 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,14 @@ import net.corda.core.concurrent.CordaFuture
|
|||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.flows.NotarisationRequestSignature
|
import net.corda.core.flows.NotarisationRequestSignature
|
||||||
import net.corda.core.flows.NotaryError
|
import net.corda.core.flows.NotaryError
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
|
typealias SigningFunction = (SecureHash) -> TransactionSignature
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service that records input states of the given transaction and provides conflict information
|
* A service that records input states of the given transaction and provides conflict information
|
||||||
* if any of the inputs have already been used in another transaction.
|
* if any of the inputs have already been used in another transaction.
|
||||||
@ -36,10 +39,11 @@ interface UniquenessProvider {
|
|||||||
return NotaryServiceFlow.defaultEstimatedWaitTime
|
return NotaryServiceFlow.defaultEstimatedWaitTime
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The outcome of committing a transaction. */
|
/** The outcome of committing and signing a transaction. */
|
||||||
sealed class Result {
|
sealed class Result {
|
||||||
/** Indicates that all input states have been committed successfully. */
|
/** Indicates that all input states have been committed successfully. */
|
||||||
object Success : Result()
|
data class Success(val signature: TransactionSignature) : Result()
|
||||||
|
|
||||||
/** Indicates that the transaction has not been committed. */
|
/** Indicates that the transaction has not been committed. */
|
||||||
data class Failure(val error: NotaryError) : Result()
|
data class Failure(val error: NotaryError) : Result()
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,12 @@ import net.corda.core.internal.NamedCacheFactory
|
|||||||
import net.corda.core.internal.concurrent.OpenFuture
|
import net.corda.core.internal.concurrent.OpenFuture
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.elapsedTime
|
import net.corda.core.internal.elapsedTime
|
||||||
import net.corda.core.internal.notary.*
|
import net.corda.core.internal.notary.NotaryInternalException
|
||||||
|
import net.corda.core.internal.notary.NotaryServiceFlow
|
||||||
|
import net.corda.core.internal.notary.SigningFunction
|
||||||
|
import net.corda.core.internal.notary.UniquenessProvider
|
||||||
|
import net.corda.core.internal.notary.isConsumedByTheSameTx
|
||||||
|
import net.corda.core.internal.notary.validateTimeWindow
|
||||||
import net.corda.core.schemas.PersistentStateRef
|
import net.corda.core.schemas.PersistentStateRef
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
@ -33,12 +38,18 @@ import java.util.concurrent.LinkedBlockingQueue
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
import javax.persistence.*
|
import javax.persistence.Column
|
||||||
|
import javax.persistence.EmbeddedId
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.GeneratedValue
|
||||||
|
import javax.persistence.Id
|
||||||
|
import javax.persistence.Lob
|
||||||
|
import javax.persistence.MappedSuperclass
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
/** A RDBMS backed Uniqueness provider */
|
/** A RDBMS backed Uniqueness provider */
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersistence, cacheFactory: NamedCacheFactory) : UniquenessProvider, SingletonSerializeAsToken() {
|
class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersistence, cacheFactory: NamedCacheFactory, val signTransaction : SigningFunction) : UniquenessProvider, SingletonSerializeAsToken() {
|
||||||
|
|
||||||
@MappedSuperclass
|
@MappedSuperclass
|
||||||
class BaseComittedState(
|
class BaseComittedState(
|
||||||
@ -315,6 +326,7 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun respondWithSuccess(request: CommitRequest) {
|
private fun respondWithSuccess(request: CommitRequest) {
|
||||||
request.future.set(UniquenessProvider.Result.Success)
|
val signedTx = signTransaction(request.txId)
|
||||||
|
request.future.set(UniquenessProvider.Result.Success(signedTx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,11 @@ class SimpleNotaryService(override val services: ServiceHubInternal, override va
|
|||||||
log.info("Starting notary in $mode mode")
|
log.info("Starting notary in $mode mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
|
override val uniquenessProvider = PersistentUniquenessProvider(
|
||||||
|
services.clock,
|
||||||
|
services.database,
|
||||||
|
services.cacheFactory,
|
||||||
|
::signTransaction)
|
||||||
|
|
||||||
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
|
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
|
||||||
return if (notaryConfig.validating) {
|
return if (notaryConfig.validating) {
|
||||||
|
@ -28,7 +28,8 @@ class RaftNotaryService(
|
|||||||
clock,
|
clock,
|
||||||
monitoringService.metrics,
|
monitoringService.metrics,
|
||||||
services.cacheFactory,
|
services.cacheFactory,
|
||||||
raftConfig
|
raftConfig,
|
||||||
|
::signTransaction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import net.corda.core.flows.NotarisationRequestSignature
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.NamedCacheFactory
|
import net.corda.core.internal.NamedCacheFactory
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
|
import net.corda.core.internal.notary.SigningFunction
|
||||||
import net.corda.core.internal.notary.UniquenessProvider
|
import net.corda.core.internal.notary.UniquenessProvider
|
||||||
import net.corda.core.serialization.SerializationDefaults
|
import net.corda.core.serialization.SerializationDefaults
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
@ -37,7 +38,11 @@ import java.nio.file.Path
|
|||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
import javax.persistence.*
|
import javax.persistence.Column
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.Id
|
||||||
|
import javax.persistence.Lob
|
||||||
|
import javax.persistence.Table
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A uniqueness provider that records committed input states in a distributed collection replicated and
|
* A uniqueness provider that records committed input states in a distributed collection replicated and
|
||||||
@ -56,7 +61,8 @@ class RaftUniquenessProvider(
|
|||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val metrics: MetricRegistry,
|
private val metrics: MetricRegistry,
|
||||||
private val cacheFactory: NamedCacheFactory,
|
private val cacheFactory: NamedCacheFactory,
|
||||||
private val raftConfig: RaftConfig
|
private val raftConfig: RaftConfig,
|
||||||
|
private val signTransaction: SigningFunction
|
||||||
) : UniquenessProvider, SingletonSerializeAsToken() {
|
) : UniquenessProvider, SingletonSerializeAsToken() {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
@ -228,7 +234,7 @@ class RaftUniquenessProvider(
|
|||||||
UniquenessProvider.Result.Failure(commitError)
|
UniquenessProvider.Result.Failure(commitError)
|
||||||
} else {
|
} else {
|
||||||
log.info("All input states of transaction $txId have been committed")
|
log.info("All input states of transaction $txId have been committed")
|
||||||
UniquenessProvider.Result.Success
|
UniquenessProvider.Result.Success(signTransaction(txId))
|
||||||
}
|
}
|
||||||
future.set(result)
|
future.set(result)
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,10 @@ class TimedFlowTests {
|
|||||||
/** A dummy commit method that immediately returns a success message. */
|
/** A dummy commit method that immediately returns a success message. */
|
||||||
override fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef>): CordaFuture<UniquenessProvider.Result> {
|
override fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef>): CordaFuture<UniquenessProvider.Result> {
|
||||||
return openFuture<UniquenessProvider.Result>().apply {
|
return openFuture<UniquenessProvider.Result>().apply {
|
||||||
set(UniquenessProvider.Result.Success)
|
val signature = services.database.transaction {
|
||||||
|
signTransaction(txId)
|
||||||
|
}
|
||||||
|
set(UniquenessProvider.Result.Success(signature))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +283,14 @@ class TimedFlowTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun commitInputStates(inputs: List<StateRef>, txId: SecureHash, caller: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef>) {
|
override fun commitInputStates(
|
||||||
|
inputs: List<StateRef>,
|
||||||
|
txId: SecureHash,
|
||||||
|
caller: Party,
|
||||||
|
requestSignature: NotarisationRequestSignature,
|
||||||
|
timeWindow: TimeWindow?,
|
||||||
|
references: List<StateRef>
|
||||||
|
) : UniquenessProvider.Result {
|
||||||
val callingFlow = FlowLogic.currentTopLevel
|
val callingFlow = FlowLogic.currentTopLevel
|
||||||
?: throw IllegalStateException("This method should be invoked in a flow context.")
|
?: throw IllegalStateException("This method should be invoked in a flow context.")
|
||||||
|
|
||||||
@ -290,8 +300,9 @@ class TimedFlowTests {
|
|||||||
callingFlow.stateMachine.suspend(FlowIORequest.WaitForLedgerCommit(SecureHash.randomSHA256()), false)
|
callingFlow.stateMachine.suspend(FlowIORequest.WaitForLedgerCommit(SecureHash.randomSHA256()), false)
|
||||||
} else {
|
} else {
|
||||||
log.info("Processing")
|
log.info("Processing")
|
||||||
super.commitInputStates(inputs, txId, caller, requestSignature, timeWindow, references)
|
return super.commitInputStates(inputs, txId, caller, requestSignature, timeWindow, references)
|
||||||
}
|
}
|
||||||
|
return UniquenessProvider.Result.Failure(NotaryError.General(Throwable("leave me alone")))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = NonValidatingNotaryFlow(otherPartySession, this, waitEtaThreshold)
|
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = NonValidatingNotaryFlow(otherPartySession, this, waitEtaThreshold)
|
||||||
|
@ -2,9 +2,12 @@ package net.corda.node.services.transactions
|
|||||||
|
|
||||||
import com.codahale.metrics.MetricRegistry
|
import com.codahale.metrics.MetricRegistry
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.DigitalSignature
|
import net.corda.core.crypto.DigitalSignature
|
||||||
import net.corda.core.crypto.NullKeys
|
import net.corda.core.crypto.NullKeys
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.SignableData
|
||||||
|
import net.corda.core.crypto.SignatureMetadata
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.flows.NotarisationRequestSignature
|
import net.corda.core.flows.NotarisationRequestSignature
|
||||||
import net.corda.core.flows.NotaryError
|
import net.corda.core.flows.NotaryError
|
||||||
@ -14,6 +17,7 @@ import net.corda.core.internal.notary.UniquenessProvider
|
|||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.minutes
|
import net.corda.core.utilities.minutes
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
import net.corda.node.services.schema.NodeSchemaService
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.notary.experimental.raft.RaftConfig
|
import net.corda.notary.experimental.raft.RaftConfig
|
||||||
@ -28,12 +32,15 @@ import net.corda.testing.internal.configureDatabase
|
|||||||
import net.corda.testing.internal.configureTestSSL
|
import net.corda.testing.internal.configureTestSSL
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import net.corda.testing.node.TestClock
|
import net.corda.testing.node.TestClock
|
||||||
|
import net.corda.testing.node.internal.MockKeyManagementService
|
||||||
|
import net.corda.testing.node.makeTestIdentityService
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.Parameterized
|
import org.junit.runners.Parameterized
|
||||||
|
import java.security.KeyPair
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ -93,12 +100,12 @@ class UniquenessProviderTests(
|
|||||||
val firstTxId = SecureHash.randomSHA256()
|
val firstTxId = SecureHash.randomSHA256()
|
||||||
val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes))
|
val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes))
|
||||||
val result = uniquenessProvider.commit(listOf(inputState1), firstTxId, identity, requestSignature, timeWindow).get()
|
val result = uniquenessProvider.commit(listOf(inputState1), firstTxId, identity, requestSignature, timeWindow).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Idempotency: can re-notarise successfully later.
|
// Idempotency: can re-notarise successfully later.
|
||||||
testClock.advanceBy(90.minutes)
|
testClock.advanceBy(90.minutes)
|
||||||
val result2 = uniquenessProvider.commit(listOf(inputState1), firstTxId, identity, requestSignature, timeWindow).get()
|
val result2 = uniquenessProvider.commit(listOf(inputState1), firstTxId, identity, requestSignature, timeWindow).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result2)
|
assert(result2 is UniquenessProvider.Result.Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -120,12 +127,12 @@ class UniquenessProviderTests(
|
|||||||
|
|
||||||
val result = uniquenessProvider.commit(emptyList(), firstTxId, identity, requestSignature, references = listOf(referenceState))
|
val result = uniquenessProvider.commit(emptyList(), firstTxId, identity, requestSignature, references = listOf(referenceState))
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Idempotency: can re-notarise successfully.
|
// Idempotency: can re-notarise successfully.
|
||||||
val result2 = uniquenessProvider.commit(emptyList(), firstTxId, identity, requestSignature, references = listOf(referenceState))
|
val result2 = uniquenessProvider.commit(emptyList(), firstTxId, identity, requestSignature, references = listOf(referenceState))
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result2)
|
assert(result2 is UniquenessProvider.Result.Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -135,7 +142,7 @@ class UniquenessProviderTests(
|
|||||||
|
|
||||||
val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList())
|
val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList())
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Transaction referencing the spent sate fails.
|
// Transaction referencing the spent sate fails.
|
||||||
val secondTxId = SecureHash.randomSHA256()
|
val secondTxId = SecureHash.randomSHA256()
|
||||||
@ -157,18 +164,18 @@ class UniquenessProviderTests(
|
|||||||
|
|
||||||
val result = uniquenessProvider.commit(emptyList(), firstTxId, identity, requestSignature, timeWindow, references = listOf(referenceState))
|
val result = uniquenessProvider.commit(emptyList(), firstTxId, identity, requestSignature, timeWindow, references = listOf(referenceState))
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// The reference state gets consumed.
|
// The reference state gets consumed.
|
||||||
val result2 = uniquenessProvider.commit(listOf(referenceState), SecureHash.randomSHA256(), identity, requestSignature, timeWindow)
|
val result2 = uniquenessProvider.commit(listOf(referenceState), SecureHash.randomSHA256(), identity, requestSignature, timeWindow)
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result2)
|
assert(result2 is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Idempotency: can re-notarise successfully.
|
// Idempotency: can re-notarise successfully.
|
||||||
testClock.advanceBy(90.minutes)
|
testClock.advanceBy(90.minutes)
|
||||||
val result3 = uniquenessProvider.commit(emptyList(), firstTxId, identity, requestSignature, timeWindow, references = listOf(referenceState))
|
val result3 = uniquenessProvider.commit(emptyList(), firstTxId, identity, requestSignature, timeWindow, references = listOf(referenceState))
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result3)
|
assert(result3 is UniquenessProvider.Result.Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -190,7 +197,7 @@ class UniquenessProviderTests(
|
|||||||
|
|
||||||
val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList())
|
val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList())
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Transaction referencing the spent sate fails.
|
// Transaction referencing the spent sate fails.
|
||||||
val secondTxId = SecureHash.randomSHA256()
|
val secondTxId = SecureHash.randomSHA256()
|
||||||
@ -210,7 +217,7 @@ class UniquenessProviderTests(
|
|||||||
|
|
||||||
val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList())
|
val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList())
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Transaction referencing the spent sate fails.
|
// Transaction referencing the spent sate fails.
|
||||||
val secondTxId = SecureHash.randomSHA256()
|
val secondTxId = SecureHash.randomSHA256()
|
||||||
@ -230,11 +237,11 @@ class UniquenessProviderTests(
|
|||||||
val inputState = generateStateRef()
|
val inputState = generateStateRef()
|
||||||
|
|
||||||
val result = uniquenessProvider.commit(listOf(inputState), txID, identity, requestSignature).get()
|
val result = uniquenessProvider.commit(listOf(inputState), txID, identity, requestSignature).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Idempotency: can re-notarise successfully.
|
// Idempotency: can re-notarise successfully.
|
||||||
val result2 = uniquenessProvider.commit(listOf(inputState), txID, identity, requestSignature).get()
|
val result2 = uniquenessProvider.commit(listOf(inputState), txID, identity, requestSignature).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result2)
|
assert(result2 is UniquenessProvider.Result.Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -244,7 +251,7 @@ class UniquenessProviderTests(
|
|||||||
val inputs = listOf(inputState)
|
val inputs = listOf(inputState)
|
||||||
val firstTxId = txID
|
val firstTxId = txID
|
||||||
val result = uniquenessProvider.commit(inputs, firstTxId, identity, requestSignature).get()
|
val result = uniquenessProvider.commit(inputs, firstTxId, identity, requestSignature).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
val secondTxId = SecureHash.randomSHA256()
|
val secondTxId = SecureHash.randomSHA256()
|
||||||
|
|
||||||
@ -263,12 +270,12 @@ class UniquenessProviderTests(
|
|||||||
val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes))
|
val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes))
|
||||||
|
|
||||||
val result = uniquenessProvider.commit(listOf(inputState), txID, identity, requestSignature, timeWindow).get()
|
val result = uniquenessProvider.commit(listOf(inputState), txID, identity, requestSignature, timeWindow).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Idempotency: can re-notarise successfully later.
|
// Idempotency: can re-notarise successfully later.
|
||||||
testClock.advanceBy(90.minutes)
|
testClock.advanceBy(90.minutes)
|
||||||
val result2 = uniquenessProvider.commit(listOf(inputState), txID, identity, requestSignature, timeWindow).get()
|
val result2 = uniquenessProvider.commit(listOf(inputState), txID, identity, requestSignature, timeWindow).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result2)
|
assert(result2 is UniquenessProvider.Result.Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -287,7 +294,7 @@ class UniquenessProviderTests(
|
|||||||
val inputs = listOf(inputState)
|
val inputs = listOf(inputState)
|
||||||
val firstTxId = txID
|
val firstTxId = txID
|
||||||
val result = uniquenessProvider.commit(inputs, firstTxId, identity, requestSignature).get()
|
val result = uniquenessProvider.commit(inputs, firstTxId, identity, requestSignature).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
val secondTxId = SecureHash.randomSHA256()
|
val secondTxId = SecureHash.randomSHA256()
|
||||||
|
|
||||||
@ -306,7 +313,7 @@ class UniquenessProviderTests(
|
|||||||
val inputs = listOf(inputState)
|
val inputs = listOf(inputState)
|
||||||
val firstTxId = txID
|
val firstTxId = txID
|
||||||
val result = uniquenessProvider.commit(inputs, firstTxId, identity, requestSignature).get()
|
val result = uniquenessProvider.commit(inputs, firstTxId, identity, requestSignature).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
val secondTxId = SecureHash.randomSHA256()
|
val secondTxId = SecureHash.randomSHA256()
|
||||||
|
|
||||||
@ -330,13 +337,13 @@ class UniquenessProviderTests(
|
|||||||
|
|
||||||
val result = uniquenessProvider.commit(listOf(inputState), firstTxId, identity, requestSignature, timeWindow, references = listOf(referenceState))
|
val result = uniquenessProvider.commit(listOf(inputState), firstTxId, identity, requestSignature, timeWindow, references = listOf(referenceState))
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Idempotency: can re-notarise successfully.
|
// Idempotency: can re-notarise successfully.
|
||||||
testClock.advanceBy(90.minutes)
|
testClock.advanceBy(90.minutes)
|
||||||
val result2 = uniquenessProvider.commit(listOf(inputState), firstTxId, identity, requestSignature, timeWindow, references = listOf(referenceState))
|
val result2 = uniquenessProvider.commit(listOf(inputState), firstTxId, identity, requestSignature, timeWindow, references = listOf(referenceState))
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result2)
|
assert(result2 is UniquenessProvider.Result.Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -346,7 +353,7 @@ class UniquenessProviderTests(
|
|||||||
val referenceState = generateStateRef()
|
val referenceState = generateStateRef()
|
||||||
|
|
||||||
val result = uniquenessProvider.commit(listOf(inputState), firstTxId, identity, requestSignature, references = emptyList()).get()
|
val result = uniquenessProvider.commit(listOf(inputState), firstTxId, identity, requestSignature, references = emptyList()).get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Transaction referencing the spent sate fails.
|
// Transaction referencing the spent sate fails.
|
||||||
val secondTxId = SecureHash.randomSHA256()
|
val secondTxId = SecureHash.randomSHA256()
|
||||||
@ -367,7 +374,7 @@ class UniquenessProviderTests(
|
|||||||
|
|
||||||
val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList())
|
val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList())
|
||||||
.get()
|
.get()
|
||||||
assertEquals(UniquenessProvider.Result.Success, result)
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
|
||||||
// Transaction referencing the spent sate fails.
|
// Transaction referencing the spent sate fails.
|
||||||
val secondTxId = SecureHash.randomSHA256()
|
val secondTxId = SecureHash.randomSHA256()
|
||||||
@ -381,6 +388,22 @@ class UniquenessProviderTests(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Group G: input, reference states and time window – covered by previous tests. */
|
/* Group G: input, reference states and time window – covered by previous tests. */
|
||||||
|
|
||||||
|
/* Transaction signing tests. */
|
||||||
|
@Test
|
||||||
|
fun `signs transactions correctly`() {
|
||||||
|
(1..10).map {
|
||||||
|
val inputState1 = generateStateRef()
|
||||||
|
val firstTxId = SecureHash.randomSHA256()
|
||||||
|
val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes))
|
||||||
|
Pair(firstTxId, uniquenessProvider.commit(listOf(inputState1), firstTxId, identity, requestSignature, timeWindow))
|
||||||
|
}.forEach {
|
||||||
|
val result = it.second.get()
|
||||||
|
assert(result is UniquenessProvider.Result.Success)
|
||||||
|
val signature = (result as UniquenessProvider.Result.Success).signature
|
||||||
|
assert(signature.verify(it.first))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UniquenessProviderFactory {
|
interface UniquenessProviderFactory {
|
||||||
@ -394,7 +417,7 @@ class PersistentUniquenessProviderFactory : UniquenessProviderFactory {
|
|||||||
override fun create(clock: Clock): UniquenessProvider {
|
override fun create(clock: Clock): UniquenessProvider {
|
||||||
database?.close()
|
database?.close()
|
||||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }, NodeSchemaService(extraSchemas = setOf(NodeNotarySchemaV1)))
|
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }, NodeSchemaService(extraSchemas = setOf(NodeNotarySchemaV1)))
|
||||||
return PersistentUniquenessProvider(clock, database!!, TestingNamedCacheFactory())
|
return PersistentUniquenessProvider(clock, database!!, TestingNamedCacheFactory(), ::signSingle)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cleanUp() {
|
override fun cleanUp() {
|
||||||
@ -420,7 +443,8 @@ class RaftUniquenessProviderFactory : UniquenessProviderFactory {
|
|||||||
clock,
|
clock,
|
||||||
MetricRegistry(),
|
MetricRegistry(),
|
||||||
TestingNamedCacheFactory(),
|
TestingNamedCacheFactory(),
|
||||||
RaftConfig(NetworkHostAndPort("localhost", raftNodePort), emptyList())
|
RaftConfig(NetworkHostAndPort("localhost", raftNodePort), emptyList()),
|
||||||
|
::signSingle
|
||||||
).apply {
|
).apply {
|
||||||
start()
|
start()
|
||||||
provider = this
|
provider = this
|
||||||
@ -432,3 +456,17 @@ class RaftUniquenessProviderFactory : UniquenessProviderFactory {
|
|||||||
database?.close()
|
database?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ourKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
|
val keyService = MockKeyManagementService(makeTestIdentityService(), ourKeyPair)
|
||||||
|
val pubKey = keyService.freshKey()
|
||||||
|
|
||||||
|
fun signSingle(it: SecureHash) = keyService.sign(
|
||||||
|
SignableData(
|
||||||
|
txId = it,
|
||||||
|
signatureMetadata = SignatureMetadata(
|
||||||
|
4,
|
||||||
|
Crypto.findSignatureScheme(pubKey).schemeNumberID
|
||||||
|
)
|
||||||
|
), pubKey
|
||||||
|
)
|
||||||
|
@ -22,8 +22,15 @@ import java.security.PublicKey
|
|||||||
* The notary-related APIs might change in the future.
|
* The notary-related APIs might change in the future.
|
||||||
*/
|
*/
|
||||||
// START 1
|
// START 1
|
||||||
class MyCustomValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : SinglePartyNotaryService() {
|
class MyCustomValidatingNotaryService(
|
||||||
override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
|
override val services: ServiceHubInternal,
|
||||||
|
override val notaryIdentityKey: PublicKey)
|
||||||
|
: SinglePartyNotaryService() {
|
||||||
|
override val uniquenessProvider = PersistentUniquenessProvider(
|
||||||
|
services.clock,
|
||||||
|
services.database,
|
||||||
|
services.cacheFactory,
|
||||||
|
::signTransaction)
|
||||||
|
|
||||||
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = MyValidatingNotaryFlow(otherPartySession, this)
|
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = MyValidatingNotaryFlow(otherPartySession, this)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user