mirror of
https://github.com/corda/corda.git
synced 2024-12-20 13:33:12 +00:00
Example of encrypted backchain - WIP for investigation purposes
This commit is contained in:
parent
0a21fda87c
commit
a165c69d3a
@ -21,7 +21,7 @@ jdkClassifier11=jdk11
|
||||
dockerJavaVersion=3.2.5
|
||||
proguardVersion=6.1.1
|
||||
bouncycastleVersion=1.68
|
||||
classgraphVersion=4.8.90
|
||||
classgraphVersion=4.8.135
|
||||
disruptorVersion=3.4.2
|
||||
typesafeConfigVersion=1.3.4
|
||||
jsr305Version=3.0.2
|
||||
|
@ -180,7 +180,8 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
|
||||
oldV3Broadcast(notarised, oldParticipants.toSet())
|
||||
for (session in sessions) {
|
||||
try {
|
||||
subFlow(SendTransactionFlow(session, notarised))
|
||||
// PoC send encrypted
|
||||
subFlow(SendTransactionFlow(session, notarised, true))
|
||||
logger.info("Party ${session.counterparty} received the transaction.")
|
||||
} catch (e: UnexpectedFlowEndException) {
|
||||
throw UnexpectedFlowEndException(
|
||||
@ -282,7 +283,7 @@ class ReceiveFinalityFlow @JvmOverloads constructor(private val otherSideSession
|
||||
private val statesToRecord: StatesToRecord = ONLY_RELEVANT) : FlowLogic<SignedTransaction>() {
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
return subFlow(object : ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = true, statesToRecord = statesToRecord) {
|
||||
return subFlow(object : ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = true, statesToRecord = statesToRecord, encrypted = true) {
|
||||
override fun checkBeforeRecording(stx: SignedTransaction) {
|
||||
require(expectedTxId == null || expectedTxId == stx.id) {
|
||||
"We expected to receive transaction with ID $expectedTxId but instead got ${stx.id}. Transaction was" +
|
||||
|
@ -4,8 +4,10 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.internal.ResolveTransactionsFlow
|
||||
import net.corda.core.internal.checkParameterHash
|
||||
import net.corda.core.internal.dependencies
|
||||
import net.corda.core.internal.pushToLoggingContext
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.transactions.RawDependency
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.core.utilities.unwrap
|
||||
@ -30,7 +32,8 @@ import java.security.SignatureException
|
||||
*/
|
||||
open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSideSession: FlowSession,
|
||||
private val checkSufficientSignatures: Boolean = true,
|
||||
private val statesToRecord: StatesToRecord = StatesToRecord.NONE) : FlowLogic<SignedTransaction>() {
|
||||
private val statesToRecord: StatesToRecord = StatesToRecord.NONE,
|
||||
private val encrypted : Boolean = false) : FlowLogic<SignedTransaction>() {
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
@Suspendable
|
||||
@Throws(SignatureException::class,
|
||||
@ -47,11 +50,59 @@ open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSid
|
||||
it.pushToLoggingContext()
|
||||
logger.info("Received transaction acknowledgement request from party ${otherSideSession.counterparty}.")
|
||||
checkParameterHash(it.networkParametersHash)
|
||||
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord))
|
||||
|
||||
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord, encrypted = encrypted))
|
||||
|
||||
logger.info("Transaction dependencies resolution completed.")
|
||||
try {
|
||||
if (encrypted) {
|
||||
|
||||
val validatedTxSvc = serviceHub.validatedTransactions
|
||||
|
||||
val encryptedTxs = it.dependencies.mapNotNull {
|
||||
validatedTxId ->
|
||||
validatedTxSvc.getEncryptedTransaction(validatedTxId)?.let { etx ->
|
||||
etx.id to etx
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
val signedTxs = it.dependencies.mapNotNull {
|
||||
validatedTxId ->
|
||||
validatedTxSvc.getTransaction(validatedTxId)?.let { stx ->
|
||||
stx.id to stx
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
val networkParameters = it.dependencies.mapNotNull { depTxId ->
|
||||
val npHash = when {
|
||||
encryptedTxs[depTxId] != null -> serviceHub.encryptedTransactionService.getNetworkParameterHash(encryptedTxs[depTxId]!!)
|
||||
?: serviceHub.networkParametersService.defaultHash
|
||||
signedTxs[depTxId] != null -> signedTxs[depTxId]!!.networkParametersHash
|
||||
?: serviceHub.networkParametersService.defaultHash
|
||||
else -> null
|
||||
}
|
||||
|
||||
npHash?.let { depTxId to npHash }
|
||||
}.associate {
|
||||
netParams ->
|
||||
netParams.first to serviceHub.networkParametersService.lookup(netParams.second)
|
||||
}
|
||||
|
||||
val rawDependencies = it.dependencies.associate {
|
||||
txId ->
|
||||
txId to RawDependency(
|
||||
encryptedTxs[txId],
|
||||
signedTxs[txId],
|
||||
networkParameters[txId]
|
||||
)
|
||||
}
|
||||
|
||||
serviceHub.encryptedTransactionService.verifyTransaction(it, serviceHub, checkSufficientSignatures, rawDependencies)
|
||||
it
|
||||
} else {
|
||||
it.verify(serviceHub, checkSufficientSignatures)
|
||||
it
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Transaction verification failed.")
|
||||
throw e
|
||||
|
@ -67,7 +67,7 @@ class MaybeSerializedSignedTransaction(override val id: SecureHash, val serializ
|
||||
* @param otherSide the target party.
|
||||
* @param stx the [SignedTransaction] being sent to the [otherSideSession].
|
||||
*/
|
||||
open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) : DataVendingFlow(otherSide, stx)
|
||||
open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction, override val encrypted: Boolean = false) : DataVendingFlow(otherSide, stx, encrypted)
|
||||
|
||||
/**
|
||||
* The [SendStateAndRefFlow] should be used to send a list of input [StateAndRef] to another peer that wishes to verify
|
||||
@ -80,7 +80,7 @@ open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) :
|
||||
*/
|
||||
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
|
||||
|
||||
open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
|
||||
open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any, open val encrypted: Boolean = false) : FlowLogic<Void?>() {
|
||||
@Suspendable
|
||||
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)
|
||||
|
||||
@ -91,6 +91,9 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any)
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Void? {
|
||||
|
||||
val encryptSvc = serviceHub.encryptedTransactionService
|
||||
|
||||
val networkMaxMessageSize = serviceHub.networkParameters.maxMessageSize
|
||||
val maxPayloadSize = networkMaxMessageSize / 2
|
||||
|
||||
@ -146,10 +149,31 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any)
|
||||
var numSent = 0
|
||||
payload = when (dataRequest.dataType) {
|
||||
FetchDataFlow.DataType.TRANSACTION -> dataRequest.hashes.map { txId ->
|
||||
logger.trace { "Sending: TRANSACTION (dataRequest.hashes.size=${dataRequest.hashes.size})" }
|
||||
|
||||
logger.trace { "Sending: TRANSACTION (dataRequest.hashes.size=${dataRequest.hashes.size}), encrypted: $encrypted" }
|
||||
|
||||
if (!authorisedTransactions.isAuthorised(txId)) {
|
||||
throw FetchDataFlow.IllegalTransactionRequest(txId)
|
||||
}
|
||||
|
||||
if (encrypted) {
|
||||
var encryptedTx = serviceHub.validatedTransactions.getEncryptedTransaction(txId)
|
||||
|
||||
if (encryptedTx == null) {
|
||||
val tx = serviceHub.validatedTransactions.getTransaction(txId)
|
||||
?: throw FetchDataFlow.HashNotFound(txId)
|
||||
|
||||
encryptedTx = encryptSvc.encryptTransaction(tx)
|
||||
}
|
||||
|
||||
authorisedTransactions.removeAuthorised(encryptedTx.id)
|
||||
authorisedTransactions.addAuthorised(encryptSvc.getDependencies(encryptedTx))
|
||||
|
||||
totalByteCount += encryptedTx.bytes.size
|
||||
numSent++
|
||||
encryptedTx
|
||||
} else {
|
||||
|
||||
val tx = serviceHub.validatedTransactions.getTransaction(txId)
|
||||
?: throw FetchDataFlow.HashNotFound(txId)
|
||||
authorisedTransactions.removeAuthorised(tx.id)
|
||||
@ -158,8 +182,15 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any)
|
||||
numSent++
|
||||
tx
|
||||
}
|
||||
}
|
||||
// Loop on all items returned using dataRequest.hashes.map:
|
||||
FetchDataFlow.DataType.BATCH_TRANSACTION -> dataRequest.hashes.map { txId ->
|
||||
|
||||
// TODO: adding this for PoC to ensure that we don't fall into this
|
||||
if (encrypted) {
|
||||
throw FlowException("Batch mode disabled for encryption PoC")
|
||||
}
|
||||
|
||||
if (!authorisedTransactions.isAuthorised(txId)) {
|
||||
throw FetchDataFlow.IllegalTransactionRequest(txId)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||
import net.corda.core.serialization.SerializationToken
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SerializeAsTokenContext
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
@ -273,6 +274,12 @@ class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
|
||||
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid)
|
||||
}
|
||||
|
||||
class FetchEncryptedTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
|
||||
FetchDataFlow<EncryptedTransaction, EncryptedTransaction>(requests, otherSide, DataType.TRANSACTION) {
|
||||
|
||||
override fun load(txid: SecureHash): EncryptedTransaction? = serviceHub.validatedTransactions.getEncryptedTransaction(txid)
|
||||
}
|
||||
|
||||
class FetchBatchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
|
||||
FetchDataFlow<MaybeSerializedSignedTransaction, MaybeSerializedSignedTransaction>(requests, otherSide, DataType.BATCH_TRANSACTION) {
|
||||
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.debug
|
||||
@ -21,7 +22,8 @@ class ResolveTransactionsFlow private constructor(
|
||||
val initialTx: SignedTransaction?,
|
||||
val txHashes: Set<SecureHash>,
|
||||
val otherSide: FlowSession,
|
||||
val statesToRecord: StatesToRecord
|
||||
val statesToRecord: StatesToRecord,
|
||||
val encrypted: Boolean = false
|
||||
) : FlowLogic<Unit>() {
|
||||
|
||||
constructor(txHashes: Set<SecureHash>, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE)
|
||||
@ -36,6 +38,10 @@ class ResolveTransactionsFlow private constructor(
|
||||
constructor(transaction: SignedTransaction, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE)
|
||||
: this(transaction, transaction.dependencies, otherSide, statesToRecord)
|
||||
|
||||
// TODO: PoC constructor
|
||||
constructor(transaction: SignedTransaction, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE, encrypted: Boolean)
|
||||
: this(transaction, transaction.dependencies, otherSide, statesToRecord, encrypted)
|
||||
|
||||
private var fetchNetParamsFromCounterpart = false
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ -49,16 +55,25 @@ class ResolveTransactionsFlow private constructor(
|
||||
// Fetch missing parameters flow was added in version 4. This check is needed so we don't end up with node V4 sending parameters
|
||||
// request to node V3 that doesn't know about this protocol.
|
||||
fetchNetParamsFromCounterpart = counterpartyPlatformVersion >= PlatformVersionSwitches.FETCH_MISSING_NETWORK_PARAMETERS
|
||||
val batchMode = counterpartyPlatformVersion >= PlatformVersionSwitches.BATCH_DOWNLOAD_COUNTERPARTY_BACKCHAIN
|
||||
|
||||
// disable batch mode for encrypted
|
||||
val batchMode = counterpartyPlatformVersion >= PlatformVersionSwitches.BATCH_DOWNLOAD_COUNTERPARTY_BACKCHAIN && !encrypted
|
||||
|
||||
logger.debug { "ResolveTransactionsFlow.call(): Otherside Platform Version = '$counterpartyPlatformVersion': Batch mode = $batchMode" }
|
||||
|
||||
// TODO: attachments and net params are not encrypted (yet)
|
||||
if (initialTx != null) {
|
||||
fetchMissingAttachments(initialTx)
|
||||
fetchMissingNetworkParameters(initialTx)
|
||||
}
|
||||
|
||||
val resolver = (serviceHub as ServiceHubCoreInternal).createTransactionsResolver(this)
|
||||
|
||||
if (encrypted) {
|
||||
resolver.downloadEncryptedDependencies()
|
||||
} else {
|
||||
resolver.downloadDependencies(batchMode)
|
||||
}
|
||||
|
||||
logger.trace { "ResolveTransactionsFlow: Sending END." }
|
||||
otherSide.send(FetchDataFlow.Request.End) // Finish fetching data.
|
||||
@ -66,8 +81,13 @@ class ResolveTransactionsFlow private constructor(
|
||||
// If transaction resolution is performed for a transaction where some states are relevant, then those should be
|
||||
// recorded if this has not already occurred.
|
||||
val usedStatesToRecord = if (statesToRecord == StatesToRecord.NONE) StatesToRecord.ONLY_RELEVANT else statesToRecord
|
||||
|
||||
if (encrypted) {
|
||||
resolver.recordEncryptedDependencies(usedStatesToRecord)
|
||||
} else {
|
||||
resolver.recordDependencies(usedStatesToRecord)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the set of attachments required to verify the given transaction. If these are not already present, they will be fetched from
|
||||
@ -108,4 +128,25 @@ class ResolveTransactionsFlow private constructor(
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// PoC variants.
|
||||
// TODO: no support for contract upgrade!
|
||||
@Suspendable
|
||||
fun fetchMissingAttachments(transaction: EncryptedTransaction): Boolean {
|
||||
val attachmentIds = serviceHub.encryptedTransactionService.getAttachmentIds(transaction)
|
||||
val downloads = subFlow(FetchAttachmentsFlow(attachmentIds, otherSide)).downloaded
|
||||
return (downloads.isNotEmpty())
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
fun fetchMissingNetworkParameters(transaction: EncryptedTransaction): Boolean {
|
||||
return if (fetchNetParamsFromCounterpart) {
|
||||
serviceHub.encryptedTransactionService.getNetworkParameterHash(transaction)?.let {
|
||||
val downloads = subFlow(FetchNetworkParametersFlow(setOf(it), otherSide)).downloaded
|
||||
downloads.isNotEmpty()
|
||||
} ?: false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,4 +32,12 @@ interface TransactionsResolver {
|
||||
|
||||
@Suspendable
|
||||
fun recordDependencies(usedStatesToRecord: StatesToRecord)
|
||||
|
||||
// for Poc we will create a completely parallel set of functions, perhaps a different implementation of TransactionsResolver is
|
||||
// preferable long term
|
||||
@Suspendable
|
||||
fun downloadEncryptedDependencies()
|
||||
|
||||
@Suspendable
|
||||
fun recordEncryptedDependencies(usedStatesToRecord: StatesToRecord)
|
||||
}
|
@ -438,3 +438,7 @@ class ContractVerifier(private val transactionClassLoader: ClassLoader) : Functi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BOB
|
||||
// E32DC1E8E08D41FE635B27DED128193B75833DC2053BD6A8BB52FB21448EF045 -> 68BB0EA190E6CCD4A5E39CCCEE18A27
|
||||
// E32DC1E8E08D41FE635B27DED128193B75833DC2053BD6A8BB52FB21448EF045 -> 68BB0EA190E6CCD4A5E39CCCEE18A27
|
||||
|
@ -49,6 +49,8 @@ interface ServicesForResolution {
|
||||
/** Returns the network parameters the node is operating under. */
|
||||
val networkParameters: NetworkParameters
|
||||
|
||||
val encryptedTransactionService: EncryptedTransactionService
|
||||
|
||||
/**
|
||||
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
|
||||
*
|
||||
@ -120,6 +122,8 @@ interface ServiceHub : ServicesForResolution {
|
||||
// NOTE: Any services exposed to flows (public view) need to implement [SerializeAsToken] or similar to avoid
|
||||
// their internal state from being serialized in checkpoints.
|
||||
|
||||
override val encryptedTransactionService: EncryptedTransactionService
|
||||
|
||||
/**
|
||||
* The vault service lets you observe, soft lock and add notes to states that involve you or are relevant to your
|
||||
* node in some way. Additionally you may query and track states that correspond to various criteria.
|
||||
|
@ -0,0 +1,149 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.dependencies
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.RawDependency
|
||||
import net.corda.core.transactions.RawDependencyMap
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.SignedTransactionDependencies
|
||||
import net.corda.core.transactions.SignedTransactionDependencyMap
|
||||
import net.corda.core.utilities.toHexString
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
||||
// TODO: this should be an interface
|
||||
class EncryptedTransactionService() : SingletonSerializeAsToken() {
|
||||
companion object {
|
||||
|
||||
private const val CRYPTO_TRANSFORMATION = "AES/CBC/PKCS5PADDING"
|
||||
private const val CRYPTO_ALGORITHM = "AES"
|
||||
|
||||
private const val IV_BYTE_LENGTH = 16
|
||||
|
||||
// we will use this when we want to sign over an encrypted transaction to attest that we've verified it
|
||||
private val enclaveKeyPair = Crypto.generateKeyPair()
|
||||
|
||||
// this key we will use internally for encryption/decryption n.b. this is JUST for a quick PoC, as we will lose this key on restart
|
||||
private val encryptionKey = KeyGenerator
|
||||
.getInstance(CRYPTO_ALGORITHM)
|
||||
.also { it.init(256) }
|
||||
.generateKey()
|
||||
|
||||
private fun generateRandomBytes(length: Int): ByteArray {
|
||||
val arr = ByteArray(length)
|
||||
SecureRandom().nextBytes(arr)
|
||||
return arr
|
||||
}
|
||||
|
||||
fun getPublicEnclaveKey() = enclaveKeyPair.public
|
||||
}
|
||||
|
||||
fun getDependencies(encryptedTransaction: EncryptedTransaction): Set<SecureHash> {
|
||||
|
||||
val stx = decryptTransaction(encryptedTransaction)
|
||||
return stx.dependencies
|
||||
}
|
||||
|
||||
fun getAttachmentIds(encryptedTransaction: EncryptedTransaction): Set<SecureHash> {
|
||||
|
||||
val stx = decryptTransaction(encryptedTransaction)
|
||||
return stx.tx.attachments.toSet()
|
||||
}
|
||||
|
||||
fun getNetworkParameterHash(encryptedTransaction: EncryptedTransaction): SecureHash? {
|
||||
|
||||
val stx = decryptTransaction(encryptedTransaction)
|
||||
return stx.tx.networkParametersHash
|
||||
}
|
||||
|
||||
// TODO: this is glossing over a lot of difficulty here. toLedgerTransaction resolves a lot of stuff via services which wont be available within an enclave
|
||||
fun verifyTransaction(encryptedTransaction: EncryptedTransaction, serviceHub : ServiceHub, checkSufficientSignatures: Boolean, rawDependencies: RawDependencyMap): ByteArray {
|
||||
|
||||
println("Verifying encrypted ${encryptedTransaction.id} ${serviceHub.myInfo.legalIdentities.single()}")
|
||||
|
||||
val stx = decryptTransaction(encryptedTransaction)
|
||||
|
||||
val dependencies = extractDependencies(stx.inputs + stx.references, rawDependencies)
|
||||
|
||||
// will throw if cannot verify
|
||||
stx.toLedgerTransaction(serviceHub, checkSufficientSignatures, dependencies).verify()
|
||||
|
||||
return Crypto.doSign(enclaveKeyPair.private, stx.txBits.bytes)
|
||||
}
|
||||
|
||||
fun verifyTransaction(signedTransaction: SignedTransaction, serviceHub: ServiceHub, checkSufficientSignatures: Boolean, rawDependencies: RawDependencyMap) {
|
||||
|
||||
println("Verifying ${signedTransaction.id} ${serviceHub.myInfo.legalIdentities.single()}")
|
||||
|
||||
val dependencies = extractDependencies(signedTransaction.inputs + signedTransaction.references, rawDependencies)
|
||||
|
||||
// will throw if cannot verify
|
||||
signedTransaction.toLedgerTransaction(serviceHub, checkSufficientSignatures, dependencies).verify()
|
||||
}
|
||||
|
||||
fun encryptTransaction(signedTransaction: SignedTransaction): EncryptedTransaction {
|
||||
|
||||
println("Encrypting ${signedTransaction.id} with key: $encryptionKey ${encryptionKey.encoded.toHexString()}")
|
||||
|
||||
val ivBytes = generateRandomBytes(IV_BYTE_LENGTH)
|
||||
val initialisationVector = IvParameterSpec(ivBytes)
|
||||
val encryptionCipher = Cipher
|
||||
.getInstance(CRYPTO_TRANSFORMATION)
|
||||
.also { it.init(Cipher.ENCRYPT_MODE, encryptionKey, initialisationVector) }
|
||||
|
||||
val encryptedTxBytes = encryptionCipher.doFinal(ivBytes + signedTransaction.serialize().bytes)
|
||||
return EncryptedTransaction(signedTransaction.id, encryptedTxBytes)
|
||||
}
|
||||
|
||||
private fun decryptTransaction(encryptedTransaction: EncryptedTransaction): SignedTransaction {
|
||||
|
||||
val encryptedBytes = encryptedTransaction.bytes
|
||||
|
||||
// first IV_BYTE_LENGTH bytes are the IV
|
||||
val initialisationVector = IvParameterSpec(encryptedTransaction.bytes.copyOf(IV_BYTE_LENGTH))
|
||||
|
||||
// remainder is the transaction
|
||||
val encryptedTransactionBytes = encryptedTransaction.bytes.copyOfRange(IV_BYTE_LENGTH, encryptedBytes.size)
|
||||
|
||||
val decryptionCipher = Cipher
|
||||
.getInstance(CRYPTO_TRANSFORMATION)
|
||||
.also { it.init(Cipher.DECRYPT_MODE, encryptionKey, initialisationVector) }
|
||||
|
||||
println("Decrypting ${encryptedTransaction.id} with key: $encryptionKey ${encryptionKey.encoded.toHexString()}")
|
||||
|
||||
return decryptionCipher.doFinal(encryptedTransactionBytes).deserialize()
|
||||
}
|
||||
|
||||
private fun extractDependencies(requiredStateRefs: List<StateRef>, rawDependencies: RawDependencyMap) : SignedTransactionDependencyMap {
|
||||
|
||||
val requiredStates = requiredStateRefs.map { it.txhash }.distinct()
|
||||
|
||||
val dependencies = requiredStateRefs.map {
|
||||
val rawDependency = rawDependencies[it.txhash] ?: throw IllegalArgumentException("Missing raw dependency for ${it.txhash}")
|
||||
val tx = rawDependency.getTransaction() ?: throw IllegalArgumentException("Missing raw dependency data for ${it.txhash} $rawDependency")
|
||||
it to tx.coreTransaction.outputs[it.index]
|
||||
}.groupBy { it.first.txhash }
|
||||
|
||||
|
||||
return requiredStates.map {
|
||||
val resolvedDependencies = dependencies[it] ?: throw IllegalArgumentException("Missing encrypted transaction resolved reference for $it")
|
||||
it to SignedTransactionDependencies(
|
||||
inputsAndRefs = resolvedDependencies.toMap(),
|
||||
networkParameters = rawDependencies[it]?.networkParameters
|
||||
)
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
private fun RawDependency.getTransaction() : SignedTransaction? {
|
||||
return signedTransaction ?: encryptedTransaction?.let { decryptTransaction(it) }
|
||||
}
|
||||
}
|
@ -5,7 +5,9 @@ import net.corda.core.DoNotImplement
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.debug
|
||||
import rx.Observable
|
||||
|
||||
/**
|
||||
@ -19,6 +21,11 @@ interface TransactionStorage {
|
||||
*/
|
||||
fun getTransaction(id: SecureHash): SignedTransaction?
|
||||
|
||||
/**
|
||||
* Return the encrypted transaction with the given [id], or null if no such transaction exists.
|
||||
*/
|
||||
fun getEncryptedTransaction(id: SecureHash): EncryptedTransaction?
|
||||
|
||||
/**
|
||||
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
|
||||
* incorporate the update.
|
||||
|
@ -0,0 +1,29 @@
|
||||
package net.corda.core.transactions
|
||||
|
||||
import net.corda.core.contracts.NamedByHash
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
|
||||
@CordaSerializable
|
||||
data class EncryptedTransaction (
|
||||
override val id : SecureHash,
|
||||
val bytes : ByteArray
|
||||
// TODO: will need to also store the signature of who verified this tx
|
||||
) : NamedByHash{
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as EncryptedTransaction
|
||||
|
||||
if (id != other.id) return false
|
||||
if (!bytes.contentEquals(other.bytes)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return 31 * (id.hashCode() + bytes.contentHashCode())
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.TransactionDeserialisationException
|
||||
import net.corda.core.internal.TransactionVerifierServiceInternal
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -363,6 +364,47 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
class SignaturesMissingException(val missing: Set<PublicKey>, val descriptions: List<String>, override val id: SecureHash)
|
||||
: NamedByHash, SignatureException(missingSignatureMsg(missing, descriptions, id)), CordaThrowable by CordaException(missingSignatureMsg(missing, descriptions, id))
|
||||
|
||||
|
||||
//region Added for encryption PoC
|
||||
@JvmOverloads
|
||||
@DeleteForDJVM
|
||||
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||
fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true, dependencyMap: SignedTransactionDependencyMap): LedgerTransaction {
|
||||
if (checkSufficientSignatures) {
|
||||
verifyRequiredSignatures() // It internally invokes checkSignaturesAreValid().
|
||||
} else {
|
||||
checkSignaturesAreValid()
|
||||
}
|
||||
// We need parameters check here, because finality flow calls stx.toLedgerTransaction() and then verify.
|
||||
resolveAndCheckNetworkParameters(services, dependencyMap)
|
||||
return tx.toLedgerTransaction(services, dependencyMap)
|
||||
}
|
||||
|
||||
@DeleteForDJVM
|
||||
private fun resolveAndCheckNetworkParameters(services: ServiceHub, dependencyMap: SignedTransactionDependencyMap) {
|
||||
|
||||
val defaultNetworkParameters = services.networkParametersService.lookup(services.networkParametersService.defaultHash)
|
||||
val txNetworkParameters = if (networkParametersHash != null) {
|
||||
services.networkParametersService.lookup(networkParametersHash!!)
|
||||
} else {
|
||||
defaultNetworkParameters
|
||||
} ?: throw TransactionResolutionException(id)
|
||||
|
||||
val groupedInputsAndRefs = (inputs + references).groupBy { it.txhash }
|
||||
groupedInputsAndRefs.map { entry ->
|
||||
|
||||
val dependencies = dependencyMap[entry.key] ?: throw TransactionResolutionException(id)
|
||||
|
||||
val params = (dependencies.networkParameters ?: defaultNetworkParameters) ?: throw TransactionResolutionException(id)
|
||||
|
||||
if (txNetworkParameters.epoch < params.epoch) {
|
||||
throw TransactionVerificationException.TransactionNetworkParameterOrderingException(id, entry.value.first(), txNetworkParameters, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
//region Deprecated
|
||||
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
|
||||
@Deprecated("No replacement, this should not be used outside of Corda core")
|
||||
@ -373,3 +415,25 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
fun isNotaryChangeTransaction() = this.coreTransaction is NotaryChangeWireTransaction
|
||||
//endregion
|
||||
}
|
||||
|
||||
typealias SignedTransactionDependencyMap = Map<SecureHash, SignedTransactionDependencies>
|
||||
|
||||
data class SignedTransactionDependencies(
|
||||
val inputsAndRefs : Map<StateRef, TransactionState<ContractState>>,
|
||||
val networkParameters : NetworkParameters?
|
||||
)
|
||||
|
||||
//data class RawDependencies(
|
||||
// val encryptedTransactions: Map<SecureHash, EncryptedTransaction>,
|
||||
// val signedTransactions: Map<SecureHash, SignedTransaction>
|
||||
// val networkParameters: NetworkParameters
|
||||
//)
|
||||
|
||||
|
||||
typealias RawDependencyMap = Map<SecureHash, RawDependency>
|
||||
|
||||
data class RawDependency(
|
||||
val encryptedTransaction: EncryptedTransaction?,
|
||||
val signedTransaction: SignedTransaction?,
|
||||
val networkParameters: NetworkParameters?
|
||||
)
|
@ -123,6 +123,31 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
)
|
||||
}
|
||||
|
||||
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||
@DeleteForDJVM
|
||||
fun toLedgerTransaction(services: ServicesForResolution, dependencyMap: SignedTransactionDependencyMap): LedgerTransaction {
|
||||
return services.specialise(
|
||||
toLedgerTransactionInternal(
|
||||
resolveIdentity = { services.identityService.partyFromKey(it) },
|
||||
resolveAttachment = { services.attachments.openAttachment(it) },
|
||||
resolveStateRefAsSerialized = {
|
||||
|
||||
val dependencies = dependencyMap[it.txhash] ?: throw TransactionResolutionException(it.txhash)
|
||||
val stateRef = dependencies.inputsAndRefs[it] ?: throw TransactionResolutionException(it.txhash)
|
||||
|
||||
stateRef.serialize()
|
||||
},
|
||||
resolveParameters = {
|
||||
val hashToResolve = it ?: services.networkParametersService.defaultHash
|
||||
services.networkParametersService.lookup(hashToResolve)
|
||||
},
|
||||
// `as?` is used due to [MockServices] not implementing [ServiceHubCoreInternal]
|
||||
isAttachmentTrusted = { (services as? ServiceHubCoreInternal)?.attachmentTrustCalculator?.calculate(it) ?: true },
|
||||
attachmentsClassLoaderCache = (services as? ServiceHubCoreInternal)?.attachmentsClassLoaderCache
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Helper for deprecated toLedgerTransaction
|
||||
// TODO: revisit once Deterministic JVM code updated
|
||||
@Suppress("UNUSED") // not sure if this field can be removed safely??
|
||||
|
@ -4,8 +4,10 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.trackBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.toHexString
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
@ -29,6 +31,9 @@ class CashPaymentFlowTests {
|
||||
private lateinit var bankOfCordaNode: StartedMockNode
|
||||
private lateinit var bankOfCorda: Party
|
||||
private lateinit var aliceNode: StartedMockNode
|
||||
private lateinit var bobNode: StartedMockNode
|
||||
|
||||
private lateinit var issuanceTx: SignedTransaction
|
||||
|
||||
@Before
|
||||
fun start() {
|
||||
@ -36,8 +41,9 @@ class CashPaymentFlowTests {
|
||||
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
|
||||
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
|
||||
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
val future = bankOfCordaNode.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity))
|
||||
future.getOrThrow()
|
||||
issuanceTx = future.getOrThrow().stx
|
||||
}
|
||||
|
||||
@After
|
||||
@ -58,7 +64,12 @@ class CashPaymentFlowTests {
|
||||
|
||||
val future = bankOfCordaNode.startFlow(CashPaymentFlow(expectedPayment, payTo))
|
||||
mockNet.runNetwork()
|
||||
future.getOrThrow()
|
||||
val payTx = future.getOrThrow().stx
|
||||
|
||||
// pay Bob (not anonymously as we want to check that Bob owns it)
|
||||
val futureBob = aliceNode.startFlow(CashPaymentFlow(expectedPayment, bobNode.info.singleIdentity(), false))
|
||||
mockNet.runNetwork()
|
||||
val bobTx = futureBob.getOrThrow().stx
|
||||
|
||||
// Check Bank of Corda vault updates - we take in some issued cash and split it into $500 to the notary
|
||||
// and $1,500 back to us, so we expect to consume one state, produce one state for our own vault
|
||||
@ -80,6 +91,18 @@ class CashPaymentFlowTests {
|
||||
assertEquals(expectedPayment.`issued by`(bankOfCorda.ref(ref)), paymentState.amount)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
listOf(bobNode, aliceNode, bankOfCordaNode).forEach { node ->
|
||||
listOf(issuanceTx, payTx, bobTx).forEach { stx ->
|
||||
println("${node.info.singleIdentity()} UNENCRYPTED: ${node.services.validatedTransactions.getTransaction(stx.id)}")
|
||||
println("${node.info.singleIdentity()} ENCRYPTED: ${node.services.validatedTransactions.getEncryptedTransaction(stx.id)?.let { "${stx.id} -> ${it.bytes.toHexString()}"}}")
|
||||
}
|
||||
}
|
||||
|
||||
bobNode.services.vaultService.queryBy(Cash.State::class.java).states.forEach {
|
||||
println("BOB: ${it.state.data}")
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
|
@ -53,6 +53,7 @@ import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.ContractUpgradeService
|
||||
import net.corda.core.node.services.CordaService
|
||||
import net.corda.core.node.services.EncryptedTransactionService
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.node.services.TransactionVerifierService
|
||||
@ -290,7 +291,10 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
|
||||
@Suppress("LeakingThis")
|
||||
val keyManagementService = makeKeyManagementService(identityService).tokenize()
|
||||
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage).also {
|
||||
|
||||
val encryptedTransactionService = EncryptedTransactionService().tokenize()
|
||||
|
||||
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage, encryptedTransactionService).also {
|
||||
attachments.servicesForResolution = it
|
||||
}
|
||||
@Suppress("LeakingThis")
|
||||
@ -1134,6 +1138,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
override val diagnosticsService: DiagnosticsService get() = this@AbstractNode.diagnosticsService
|
||||
override val externalOperationExecutor: ExecutorService get() = this@AbstractNode.externalOperationExecutor
|
||||
override val notaryService: NotaryService? get() = this@AbstractNode.notaryService
|
||||
override val encryptedTransactionService: EncryptedTransactionService = this@AbstractNode.encryptedTransactionService
|
||||
|
||||
private lateinit var _myInfo: NodeInfo
|
||||
override val myInfo: NodeInfo get() = _myInfo
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.internal.SerializedStateAndRef
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.EncryptedTransactionService
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.NetworkParametersService
|
||||
import net.corda.core.node.services.TransactionStorage
|
||||
@ -19,7 +20,8 @@ data class ServicesForResolutionImpl(
|
||||
override val attachments: AttachmentStorage,
|
||||
override val cordappProvider: CordappProvider,
|
||||
override val networkParametersService: NetworkParametersService,
|
||||
private val validatedTransactions: TransactionStorage
|
||||
private val validatedTransactions: TransactionStorage,
|
||||
override val encryptedTransactionService: EncryptedTransactionService
|
||||
) : ServicesForResolution {
|
||||
override val networkParameters: NetworkParameters get() = networkParametersService.lookup(networkParametersService.currentHash) ?:
|
||||
throw IllegalArgumentException("No current parameters in network parameters storage")
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.internal.readObject
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.EncryptedTransactionService
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.NetworkParametersService
|
||||
import net.corda.core.node.services.TransactionStorage
|
||||
@ -38,7 +39,8 @@ class MigrationServicesForResolution(
|
||||
override val attachments: AttachmentStorageInternal,
|
||||
private val transactions: TransactionStorage,
|
||||
private val cordaDB: CordaPersistence,
|
||||
cacheFactory: MigrationNamedCacheFactory
|
||||
cacheFactory: MigrationNamedCacheFactory,
|
||||
override val encryptedTransactionService: EncryptedTransactionService = EncryptedTransactionService()
|
||||
): ServicesForResolution {
|
||||
|
||||
companion object {
|
||||
|
@ -3,11 +3,14 @@ package net.corda.node.services
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.internal.FetchEncryptedTransactionsFlow
|
||||
import net.corda.core.internal.FetchTransactionsFlow
|
||||
import net.corda.core.internal.ResolveTransactionsFlow
|
||||
import net.corda.core.internal.TransactionsResolver
|
||||
import net.corda.core.internal.dependencies
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.RawDependency
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.trace
|
||||
@ -113,6 +116,131 @@ class DbTransactionsResolver(private val flow: ResolveTransactionsFlow) : Transa
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun downloadEncryptedDependencies() {
|
||||
logger.debug { "Downloading encrypted dependencies for transactions ${flow.txHashes}" }
|
||||
val transactionStorage = flow.serviceHub.validatedTransactions as WritableTransactionStorage
|
||||
val encryptSvc = flow.serviceHub.encryptedTransactionService
|
||||
|
||||
val nextRequests = LinkedHashSet<SecureHash>(flow.txHashes) // Keep things unique but ordered, for unit test stability.
|
||||
val topologicalSort = TopologicalSort()
|
||||
|
||||
while (nextRequests.isNotEmpty()) {
|
||||
logger.debug { "Main fetch loop: size_remaining=${nextRequests.size}" }
|
||||
// Don't re-download the same tx when we haven't verified it yet but it's referenced multiple times in the
|
||||
// graph we're traversing.
|
||||
nextRequests.removeAll(topologicalSort.transactionIds)
|
||||
if (nextRequests.isEmpty()) {
|
||||
// Done early.
|
||||
break
|
||||
}
|
||||
|
||||
// Request the standalone transaction data (which may refer to things we don't yet have).
|
||||
val (existingTxIds, downloadedTxs) = fetchEncryptedRequiredTransactions(Collections.singleton(nextRequests.first())) // Fetch first item only
|
||||
for (tx in downloadedTxs) {
|
||||
val dependencies = encryptSvc.getDependencies(tx)
|
||||
topologicalSort.add(tx.id, dependencies)
|
||||
}
|
||||
|
||||
var suspended = true
|
||||
for (downloaded in downloadedTxs) {
|
||||
suspended = false
|
||||
val dependencies = encryptSvc.getDependencies(downloaded)
|
||||
// Do not keep in memory as this bloats the checkpoint. Write each item to the database.
|
||||
transactionStorage.addUnverifiedEncryptedTransaction(downloaded)
|
||||
|
||||
// The write locks are only released over a suspend, so need to keep track of whether the flow has been suspended to ensure
|
||||
// that locks are not held beyond each while loop iteration (as doing this would result in a deadlock due to claiming locks
|
||||
// in the wrong order)
|
||||
val suspendedViaAttachments = flow.fetchMissingAttachments(downloaded)
|
||||
val suspendedViaParams = flow.fetchMissingNetworkParameters(downloaded)
|
||||
suspended = suspended || suspendedViaAttachments || suspendedViaParams
|
||||
|
||||
// Add all input states and reference input states to the work queue.
|
||||
nextRequests.addAll(dependencies)
|
||||
}
|
||||
|
||||
// If the flow did not suspend on the last iteration of the downloaded loop above, perform a suspend here to ensure that
|
||||
// all data is flushed to the database.
|
||||
if (!suspended) {
|
||||
FlowLogic.sleep(0.seconds)
|
||||
}
|
||||
|
||||
// It's possible that the node has a transaction in storage already. Dependencies should also be present for this transaction,
|
||||
// so just remove these IDs from the set of next requests.
|
||||
nextRequests.removeAll(existingTxIds)
|
||||
}
|
||||
|
||||
sortedDependencies = topologicalSort.complete()
|
||||
logger.debug { "Downloaded ${sortedDependencies?.size} dependencies from remote peer for transactions ${flow.txHashes}" }
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun recordEncryptedDependencies(usedStatesToRecord: StatesToRecord) {
|
||||
val sortedDependencies = checkNotNull(this.sortedDependencies)
|
||||
val encryptSvc = flow.serviceHub.encryptedTransactionService
|
||||
logger.trace { "Recording ${sortedDependencies.size} dependencies for ${flow.txHashes.size} transactions" }
|
||||
val transactionStorage = flow.serviceHub.validatedTransactions as WritableTransactionStorage
|
||||
for (txId in sortedDependencies) {
|
||||
// Retrieve and delete the transaction from the unverified store.
|
||||
val (tx, isVerified) = checkNotNull(transactionStorage.getEncryptedTransactionInternal(txId)) {
|
||||
"Somehow the unverified transaction ($txId) that we stored previously is no longer there."
|
||||
}
|
||||
if (!isVerified) {
|
||||
|
||||
val dependencies = encryptSvc.getDependencies(tx)
|
||||
|
||||
val encryptedTxs = dependencies.mapNotNull {
|
||||
depTxId ->
|
||||
transactionStorage.getEncryptedTransaction(depTxId)?.let { etx ->
|
||||
etx.id to etx
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
val signedTxs = dependencies.mapNotNull {
|
||||
depTxId ->
|
||||
transactionStorage.getTransaction(depTxId)?.let { stx ->
|
||||
stx.id to stx
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
val services = flow.serviceHub
|
||||
val networkParameters = dependencies.mapNotNull { depTxId ->
|
||||
val npHash = when {
|
||||
encryptedTxs[depTxId] != null -> encryptSvc.getNetworkParameterHash(encryptedTxs[depTxId]!!)
|
||||
?: services.networkParametersService.defaultHash
|
||||
signedTxs[depTxId] != null -> signedTxs[depTxId]!!.networkParametersHash
|
||||
?: services.networkParametersService.defaultHash
|
||||
else -> null
|
||||
}
|
||||
|
||||
npHash?.let { depTxId to npHash }
|
||||
}.associate {
|
||||
it.first to services.networkParametersService.lookup(it.second)
|
||||
}
|
||||
|
||||
val rawDependencies = dependencies.associate {
|
||||
it to RawDependency(
|
||||
encryptedTxs[it],
|
||||
signedTxs[it],
|
||||
networkParameters[it]
|
||||
)
|
||||
}
|
||||
|
||||
encryptSvc.verifyTransaction(tx, flow.serviceHub, true, rawDependencies)
|
||||
|
||||
// TODO: why does this usually go through the serviceHub's recordTransactions function and not
|
||||
// direct to the validatedTransactions service??
|
||||
// flow.serviceHub.recordTransactions(usedStatesToRecord, listOf(tx))
|
||||
|
||||
val transactionStorage = flow.serviceHub.validatedTransactions as WritableTransactionStorage
|
||||
transactionStorage.addEncryptedTransaction(tx)
|
||||
} else {
|
||||
logger.debug { "No need to record $txId as it's already been verified" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The transactions already present in the database do not need to be checkpointed on every iteration of downloading
|
||||
// dependencies for other transactions, so strip these down to just the IDs here.
|
||||
@Suspendable
|
||||
@ -121,6 +249,12 @@ class DbTransactionsResolver(private val flow: ResolveTransactionsFlow) : Transa
|
||||
return Pair(requestedTxs.fromDisk.map { it.id }, requestedTxs.downloaded)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun fetchEncryptedRequiredTransactions(requests: Set<SecureHash>): Pair<List<SecureHash>, List<EncryptedTransaction>> {
|
||||
val requestedTxs = flow.subFlow(FetchEncryptedTransactionsFlow(requests, flow.otherSide))
|
||||
return Pair(requestedTxs.fromDisk.map { it.id }, requestedTxs.downloaded)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a way to topologically sort SignedTransactions represented just their [SecureHash] IDs. This means that given any two transactions
|
||||
* T1 and T2 in the list returned by [complete] if T1 is a dependency of T2 then T1 will occur earlier than T2.
|
||||
|
@ -14,8 +14,10 @@ import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.NetworkMapCacheBase
|
||||
import net.corda.core.node.services.TransactionStorage
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.node.internal.InitiatedFlowFactory
|
||||
import net.corda.node.internal.cordapp.CordappProviderInternal
|
||||
import net.corda.node.services.DbTransactionsResolver
|
||||
@ -23,6 +25,7 @@ import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
import net.corda.node.services.network.NetworkMapUpdater
|
||||
import net.corda.node.services.persistence.AttachmentStorageInternal
|
||||
import net.corda.node.services.persistence.DBTransactionStorage
|
||||
import net.corda.node.services.statemachine.ExternalEvent
|
||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
@ -253,17 +256,33 @@ interface WritableTransactionStorage : TransactionStorage {
|
||||
// TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry.
|
||||
fun addTransaction(transaction: SignedTransaction): Boolean
|
||||
|
||||
/**
|
||||
* Add a new encrypted transaction to the store
|
||||
*/
|
||||
fun addEncryptedTransaction(encryptedTransaction: EncryptedTransaction): Boolean
|
||||
|
||||
/**
|
||||
* Add a new *unverified* transaction to the store.
|
||||
*/
|
||||
fun addUnverifiedTransaction(transaction: SignedTransaction)
|
||||
|
||||
/**
|
||||
* Add a new *unverified* encrypted transaction to the store.
|
||||
*/
|
||||
fun addUnverifiedEncryptedTransaction(encryptedTransaction: EncryptedTransaction)
|
||||
|
||||
/**
|
||||
* Return the transaction with the given ID from the store, and a flag of whether it's verified. Returns null if no transaction with the
|
||||
* ID exists.
|
||||
*/
|
||||
fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>?
|
||||
|
||||
/**
|
||||
* Return the transaction with the given ID from the store, and a flag of whether it's verified. Returns null if no transaction with the
|
||||
* ID exists.
|
||||
*/
|
||||
fun getEncryptedTransactionInternal(id: SecureHash): Pair<EncryptedTransaction, Boolean>?
|
||||
|
||||
/**
|
||||
* Returns a future that completes with the transaction corresponding to [id] once it has been committed. Do not warn when run inside
|
||||
* a DB transaction.
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.serialization.*
|
||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
@ -53,7 +54,12 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
||||
val status: TransactionStatus,
|
||||
|
||||
@Column(name = "timestamp", nullable = false)
|
||||
val timestamp: Instant
|
||||
val timestamp: Instant,
|
||||
|
||||
@Column(name = "encrypted", nullable = false)
|
||||
val encrypted: Boolean = false
|
||||
|
||||
// TODO: will need to also store the signature of who verified this tx
|
||||
)
|
||||
|
||||
enum class TransactionStatus {
|
||||
@ -120,17 +126,33 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
||||
name = "DBTransactionStorage_transactions",
|
||||
toPersistentEntityKey = SecureHash::toString,
|
||||
fromPersistentEntity = {
|
||||
if (it.encrypted) {
|
||||
SecureHash.create(it.txId) to TxCacheValue(
|
||||
it.transaction.deserialize(context = contextToUse()),
|
||||
it.status)
|
||||
EncryptedTransaction(
|
||||
SecureHash.parse(it.txId),
|
||||
it.transaction
|
||||
),
|
||||
it.status
|
||||
)
|
||||
} else {
|
||||
SecureHash.create(it.txId) to TxCacheValue(
|
||||
it.transaction.deserialize<SignedTransaction>(context = contextToUse()),
|
||||
it.status
|
||||
)
|
||||
}
|
||||
},
|
||||
toPersistentEntity = { key: SecureHash, value: TxCacheValue ->
|
||||
DBTransaction(
|
||||
txId = key.toString(),
|
||||
stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id?.uuid?.toString(),
|
||||
transaction = value.toSignedTx().serialize(context = contextToUse().withEncoding(SNAPPY)).bytes,
|
||||
transaction = if( value.encrypted ) {
|
||||
value.txBits
|
||||
} else {
|
||||
value.toSignedTx().serialize(context = contextToUse().withEncoding(SNAPPY)).bytes
|
||||
},
|
||||
status = value.status,
|
||||
timestamp = clock.instant()
|
||||
timestamp = clock.instant(),
|
||||
encrypted = value.encrypted
|
||||
)
|
||||
},
|
||||
persistentEntityClass = DBTransaction::class.java,
|
||||
@ -138,6 +160,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: weight of transactions will be wrong at this stage for encrypted transactions
|
||||
private fun weighTx(tx: AppendOnlyPersistentMapBase.Transactional<TxCacheValue>): Int {
|
||||
val actTx = tx.peekableValue ?: return 0
|
||||
return actTx.sigs.sumBy { it.size + transactionSignatureOverheadEstimate } + actTx.txBits.size
|
||||
@ -187,7 +210,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
||||
|
||||
override fun getTransaction(id: SecureHash): SignedTransaction? {
|
||||
return database.transaction {
|
||||
txStorage.content[id]?.let { if (it.status.isVerified()) it.toSignedTx() else null }
|
||||
txStorage.content[id]?.let { if (it.status.isVerified() && !it.encrypted ) it.toSignedTx() else null }
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,7 +230,58 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
||||
|
||||
override fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>? {
|
||||
return database.transaction {
|
||||
txStorage.content[id]?.let { it.toSignedTx() to it.status.isVerified() }
|
||||
txStorage.content[id]?.let {
|
||||
if (!it.encrypted) {
|
||||
it.toSignedTx() to it.status.isVerified()
|
||||
} else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun addEncryptedTransaction(encryptedTransaction: EncryptedTransaction): Boolean {
|
||||
val transactionId = encryptedTransaction.id
|
||||
return database.transaction {
|
||||
txStorage.locked {
|
||||
val cachedValue = TxCacheValue(encryptedTransaction, TransactionStatus.VERIFIED)
|
||||
val addedOrUpdated = addOrUpdate(transactionId, cachedValue) { k, _ -> updateTransaction(k) }
|
||||
if (addedOrUpdated) {
|
||||
logger.debug { "Transaction $transactionId has been recorded as verified" }
|
||||
} else {
|
||||
logger.debug { "Transaction $transactionId is already recorded as verified, so no need to re-record" }
|
||||
}
|
||||
addedOrUpdated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEncryptedTransaction(id: SecureHash): EncryptedTransaction? {
|
||||
return database.transaction {
|
||||
txStorage.content[id]?.let { if (it.status.isVerified() && it.encrypted ) it.toEncryptedTx() else null }
|
||||
}
|
||||
}
|
||||
|
||||
override fun addUnverifiedEncryptedTransaction(encryptedTransaction: EncryptedTransaction) {
|
||||
val transactionId = encryptedTransaction.id
|
||||
database.transaction {
|
||||
txStorage.locked {
|
||||
val cacheValue = TxCacheValue(encryptedTransaction, status = TransactionStatus.UNVERIFIED)
|
||||
val added = addWithDuplicatesAllowed(transactionId, cacheValue)
|
||||
if (added) {
|
||||
logger.debug { "Encrypted Transaction $transactionId recorded as unverified." }
|
||||
} else {
|
||||
logger.info("Encrypted Transaction $transactionId already exists so no need to record.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEncryptedTransactionInternal(id: SecureHash): Pair<EncryptedTransaction, Boolean>? {
|
||||
return database.transaction {
|
||||
txStorage.content[id]?.let {
|
||||
if (it.encrypted) {
|
||||
it.toEncryptedTx() to it.status.isVerified()
|
||||
} else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,21 +336,70 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
||||
|
||||
private fun snapshot(): List<SignedTransaction> {
|
||||
return txStorage.content.allPersisted.use {
|
||||
it.filter { it.second.status.isVerified() }.map { it.second.toSignedTx() }.toList()
|
||||
it.filter { it.second.status.isVerified() && !it.second.encrypted }.map { it.second.toSignedTx() }.toList()
|
||||
}
|
||||
}
|
||||
|
||||
// Cache value type to just store the immutable bits of a signed transaction plus conversion helpers
|
||||
private data class TxCacheValue(
|
||||
val txBits: SerializedBytes<CoreTransaction>,
|
||||
val id: SecureHash,
|
||||
val txBits: ByteArray,
|
||||
val sigs: List<TransactionSignature>,
|
||||
val status: TransactionStatus
|
||||
val status: TransactionStatus,
|
||||
val encrypted: Boolean
|
||||
) {
|
||||
constructor(stx: SignedTransaction, status: TransactionStatus) : this(
|
||||
stx.txBits,
|
||||
Collections.unmodifiableList(stx.sigs),
|
||||
status)
|
||||
stx.id,
|
||||
stx.txBits.bytes,
|
||||
stx.sigs,
|
||||
status,
|
||||
false)
|
||||
|
||||
fun toSignedTx() = SignedTransaction(txBits, sigs)
|
||||
constructor(encryptedTransaction: EncryptedTransaction, status: TransactionStatus) : this(
|
||||
encryptedTransaction.id,
|
||||
encryptedTransaction.bytes,
|
||||
emptyList(),
|
||||
status,
|
||||
true)
|
||||
|
||||
fun toSignedTx() : SignedTransaction {
|
||||
return if (!encrypted) {
|
||||
val txBitsAsSerialized = SerializedBytes<CoreTransaction>(txBits)
|
||||
SignedTransaction(txBitsAsSerialized, sigs)
|
||||
} else {
|
||||
throw IllegalArgumentException("Cannot get signed transaction for encrypted tx")
|
||||
}
|
||||
}
|
||||
|
||||
fun toEncryptedTx() : EncryptedTransaction {
|
||||
return if (encrypted) {
|
||||
// TODO: EncryptedTransaction will be extended to include verification signature
|
||||
EncryptedTransaction(id, txBits)
|
||||
} else {
|
||||
throw IllegalArgumentException("Cannot get encrypted transaction for signed tx")
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as TxCacheValue
|
||||
|
||||
if (!txBits.contentEquals(other.txBits)) return false
|
||||
if (sigs != other.sigs) return false
|
||||
if (status != other.status) return false
|
||||
if (encrypted != other.encrypted) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = txBits.contentHashCode()
|
||||
result = 31 * result + sigs.hashCode()
|
||||
result = 31 * result + status.hashCode()
|
||||
result = 31 * result + encrypted.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
|
||||
logicalFilePath="migration/node-services.changelog-init.xml">
|
||||
|
||||
<changeSet author="R3.Corda" id="add_encrypted_column_to_node_transactions">
|
||||
<addColumn tableName="node_transactions">
|
||||
<column name="encrypted" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
@ -18,6 +18,7 @@ import net.corda.core.node.services.Vault
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
@ -786,6 +787,31 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
delegate.getTransactionInternal(id)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: these Encrypted transactions may need an overhaul is probably indicative that overloading the current storage was a bad idea
|
||||
override fun addEncryptedTransaction(encryptedTransaction: EncryptedTransaction): Boolean {
|
||||
return database.transaction {
|
||||
delegate.addEncryptedTransaction(encryptedTransaction)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addUnverifiedEncryptedTransaction(encryptedTransaction: EncryptedTransaction) {
|
||||
return database.transaction {
|
||||
delegate.addUnverifiedEncryptedTransaction(encryptedTransaction)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEncryptedTransaction(id: SecureHash): EncryptedTransaction? {
|
||||
return database.transaction {
|
||||
delegate.getEncryptedTransaction(id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEncryptedTransactionInternal(id: SecureHash): Pair<EncryptedTransaction, Boolean>? {
|
||||
return database.transaction {
|
||||
delegate.getEncryptedTransactionInternal(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface TxRecord {
|
||||
|
@ -7,7 +7,13 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationDefaults
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.node.CordaClock
|
||||
import net.corda.node.MutableClock
|
||||
@ -15,6 +21,7 @@ import net.corda.node.SimpleClock
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.serialization.internal.CordaSerializationEncoding
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
@ -32,12 +39,17 @@ import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import rx.plugins.RxJavaHooks
|
||||
import java.security.SecureRandom
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.Semaphore
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class DBTransactionStorageTests {
|
||||
private companion object {
|
||||
@ -390,6 +402,47 @@ class DBTransactionStorageTests {
|
||||
assertThat(warning).isEqualTo(DBTransactionStorage.TRANSACTION_ALREADY_IN_PROGRESS_WARNING)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `encrypted transaction`() {
|
||||
val now = Instant.ofEpochSecond(111222333L)
|
||||
val transactionClock = TransactionClock(now)
|
||||
newTransactionStorage(clock = transactionClock)
|
||||
val transaction = newTransaction()
|
||||
|
||||
val keygen = KeyGenerator.getInstance("AES")
|
||||
keygen.init(256)
|
||||
val key = keygen.generateKey()
|
||||
|
||||
val cipherTransformation = "AES/CBC/PKCS5PADDING"
|
||||
val encryptionCipher = Cipher.getInstance(cipherTransformation)
|
||||
val iv = generateIv()
|
||||
encryptionCipher.init(Cipher.ENCRYPT_MODE, key, iv)
|
||||
|
||||
val encryptedTxBytes = encryptionCipher.doFinal(transaction.serialize(context = contextToUse().withEncoding(CordaSerializationEncoding.SNAPPY)).bytes)
|
||||
val encryptedTx = EncryptedTransaction(transaction.id, encryptedTxBytes)
|
||||
|
||||
transactionStorage.addEncryptedTransaction(encryptedTx)
|
||||
|
||||
val storedTx = transactionStorage.getEncryptedTransaction(transaction.id)
|
||||
|
||||
val decryptionCipher = Cipher.getInstance(cipherTransformation)
|
||||
decryptionCipher.init(Cipher.DECRYPT_MODE, key, iv)
|
||||
|
||||
assertNotNull(storedTx, "Could not find stored encrypted message")
|
||||
|
||||
val decryptedTx = decryptionCipher.doFinal(storedTx!!.bytes).deserialize<SignedTransaction>(context = contextToUse())
|
||||
|
||||
assertEquals(decryptedTx, transaction)
|
||||
|
||||
assertEquals(now, readTransactionTimestampFromDB(transaction.id))
|
||||
}
|
||||
|
||||
fun generateIv(): IvParameterSpec? {
|
||||
val iv = ByteArray(16)
|
||||
SecureRandom().nextBytes(iv)
|
||||
return IvParameterSpec(iv)
|
||||
}
|
||||
|
||||
private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null, clock: CordaClock = SimpleClock(Clock.systemUTC())) {
|
||||
transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride
|
||||
?: 1024), clock)
|
||||
@ -413,4 +466,12 @@ class DBTransactionStorageTests {
|
||||
listOf(TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID)))
|
||||
)
|
||||
}
|
||||
|
||||
private fun contextToUse(): SerializationContext {
|
||||
return if (effectiveSerializationEnv.serializationFactory.currentContext?.useCase == SerializationContext.UseCase.Storage) {
|
||||
effectiveSerializationEnv.serializationFactory.currentContext!!
|
||||
} else {
|
||||
SerializationDefaults.STORAGE_CONTEXT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,8 @@ open class MockServices private constructor(
|
||||
override val keyManagementService: KeyManagementService = MockKeyManagementService(
|
||||
identityService,
|
||||
*arrayOf(initialIdentity.keyPair) + moreKeys
|
||||
)
|
||||
),
|
||||
override val encryptedTransactionService : EncryptedTransactionService = EncryptedTransactionService()
|
||||
) : ServiceHub {
|
||||
|
||||
companion object {
|
||||
@ -457,7 +458,7 @@ open class MockServices private constructor(
|
||||
override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService()
|
||||
|
||||
protected val servicesForResolution: ServicesForResolution
|
||||
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions)
|
||||
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions, encryptedTransactionService)
|
||||
|
||||
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
|
||||
return NodeVaultService(clock, keyManagementService, servicesForResolution, database, schemaService, cordappLoader.appClassLoader).apply { start() }
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.internal.concurrent.doneFuture
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.EncryptedTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.node.services.api.WritableTransactionStorage
|
||||
import net.corda.testing.node.MockServices
|
||||
@ -30,6 +31,7 @@ open class MockTransactionStorage : WritableTransactionStorage, SingletonSeriali
|
||||
}
|
||||
|
||||
private val txns = HashMap<SecureHash, TxHolder>()
|
||||
private val encryptedTxns = HashMap<SecureHash, EncryptedTxHolder>()
|
||||
|
||||
private val _updatesPublisher = PublishSubject.create<SignedTransaction>()
|
||||
|
||||
@ -61,5 +63,27 @@ open class MockTransactionStorage : WritableTransactionStorage, SingletonSeriali
|
||||
|
||||
override fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>? = txns[id]?.let { Pair(it.stx, it.isVerified) }
|
||||
|
||||
override fun addEncryptedTransaction(encryptedTransaction: EncryptedTransaction): Boolean {
|
||||
val current = encryptedTxns.putIfAbsent(encryptedTransaction.id, EncryptedTxHolder(encryptedTransaction, isVerified = true))
|
||||
return if (current == null) {
|
||||
true
|
||||
} else if (!current.isVerified) {
|
||||
current.isVerified = true
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun addUnverifiedEncryptedTransaction(encryptedTransaction: EncryptedTransaction) {
|
||||
encryptedTxns.putIfAbsent(encryptedTransaction.id, EncryptedTxHolder(encryptedTransaction, isVerified = false))
|
||||
}
|
||||
|
||||
override fun getEncryptedTransaction(id: SecureHash): EncryptedTransaction? = encryptedTxns[id]?.let { if (it.isVerified) it.etx else null }
|
||||
|
||||
override fun getEncryptedTransactionInternal(id: SecureHash): Pair<EncryptedTransaction, Boolean>? =
|
||||
encryptedTxns[id]?.let { Pair(it.etx, it.isVerified) }
|
||||
|
||||
private class TxHolder(val stx: SignedTransaction, var isVerified: Boolean)
|
||||
private class EncryptedTxHolder(val etx: EncryptedTransaction, var isVerified: Boolean)
|
||||
}
|
Loading…
Reference in New Issue
Block a user