mirror of
https://github.com/corda/corda.git
synced 2024-12-29 17:28:56 +00:00
Example of encrypted backchain - WIP for investigation purposes
This commit is contained in:
parent
0a21fda87c
commit
a165c69d3a
@ -9,4 +9,4 @@ package net.corda.common.logging
|
|||||||
* (originally added to source control for ease of use)
|
* (originally added to source control for ease of use)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
internal const val CURRENT_MAJOR_RELEASE = "4.8.5"
|
internal const val CURRENT_MAJOR_RELEASE = "4.8.5"
|
@ -21,7 +21,7 @@ jdkClassifier11=jdk11
|
|||||||
dockerJavaVersion=3.2.5
|
dockerJavaVersion=3.2.5
|
||||||
proguardVersion=6.1.1
|
proguardVersion=6.1.1
|
||||||
bouncycastleVersion=1.68
|
bouncycastleVersion=1.68
|
||||||
classgraphVersion=4.8.90
|
classgraphVersion=4.8.135
|
||||||
disruptorVersion=3.4.2
|
disruptorVersion=3.4.2
|
||||||
typesafeConfigVersion=1.3.4
|
typesafeConfigVersion=1.3.4
|
||||||
jsr305Version=3.0.2
|
jsr305Version=3.0.2
|
||||||
|
@ -180,7 +180,8 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
|
|||||||
oldV3Broadcast(notarised, oldParticipants.toSet())
|
oldV3Broadcast(notarised, oldParticipants.toSet())
|
||||||
for (session in sessions) {
|
for (session in sessions) {
|
||||||
try {
|
try {
|
||||||
subFlow(SendTransactionFlow(session, notarised))
|
// PoC send encrypted
|
||||||
|
subFlow(SendTransactionFlow(session, notarised, true))
|
||||||
logger.info("Party ${session.counterparty} received the transaction.")
|
logger.info("Party ${session.counterparty} received the transaction.")
|
||||||
} catch (e: UnexpectedFlowEndException) {
|
} catch (e: UnexpectedFlowEndException) {
|
||||||
throw UnexpectedFlowEndException(
|
throw UnexpectedFlowEndException(
|
||||||
@ -282,7 +283,7 @@ class ReceiveFinalityFlow @JvmOverloads constructor(private val otherSideSession
|
|||||||
private val statesToRecord: StatesToRecord = ONLY_RELEVANT) : FlowLogic<SignedTransaction>() {
|
private val statesToRecord: StatesToRecord = ONLY_RELEVANT) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
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) {
|
override fun checkBeforeRecording(stx: SignedTransaction) {
|
||||||
require(expectedTxId == null || expectedTxId == stx.id) {
|
require(expectedTxId == null || expectedTxId == stx.id) {
|
||||||
"We expected to receive transaction with ID $expectedTxId but instead got ${stx.id}. Transaction was" +
|
"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.contracts.*
|
||||||
import net.corda.core.internal.ResolveTransactionsFlow
|
import net.corda.core.internal.ResolveTransactionsFlow
|
||||||
import net.corda.core.internal.checkParameterHash
|
import net.corda.core.internal.checkParameterHash
|
||||||
|
import net.corda.core.internal.dependencies
|
||||||
import net.corda.core.internal.pushToLoggingContext
|
import net.corda.core.internal.pushToLoggingContext
|
||||||
import net.corda.core.node.StatesToRecord
|
import net.corda.core.node.StatesToRecord
|
||||||
|
import net.corda.core.transactions.RawDependency
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.trace
|
import net.corda.core.utilities.trace
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
@ -30,7 +32,8 @@ import java.security.SignatureException
|
|||||||
*/
|
*/
|
||||||
open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSideSession: FlowSession,
|
open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSideSession: FlowSession,
|
||||||
private val checkSufficientSignatures: Boolean = true,
|
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")
|
@Suppress("KDocMissingDocumentation")
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Throws(SignatureException::class,
|
@Throws(SignatureException::class,
|
||||||
@ -47,11 +50,59 @@ open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSid
|
|||||||
it.pushToLoggingContext()
|
it.pushToLoggingContext()
|
||||||
logger.info("Received transaction acknowledgement request from party ${otherSideSession.counterparty}.")
|
logger.info("Received transaction acknowledgement request from party ${otherSideSession.counterparty}.")
|
||||||
checkParameterHash(it.networkParametersHash)
|
checkParameterHash(it.networkParametersHash)
|
||||||
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord))
|
|
||||||
|
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord, encrypted = encrypted))
|
||||||
|
|
||||||
logger.info("Transaction dependencies resolution completed.")
|
logger.info("Transaction dependencies resolution completed.")
|
||||||
try {
|
try {
|
||||||
it.verify(serviceHub, checkSufficientSignatures)
|
if (encrypted) {
|
||||||
it
|
|
||||||
|
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) {
|
} catch (e: Exception) {
|
||||||
logger.warn("Transaction verification failed.")
|
logger.warn("Transaction verification failed.")
|
||||||
throw e
|
throw e
|
||||||
|
@ -67,7 +67,7 @@ class MaybeSerializedSignedTransaction(override val id: SecureHash, val serializ
|
|||||||
* @param otherSide the target party.
|
* @param otherSide the target party.
|
||||||
* @param stx the [SignedTransaction] being sent to the [otherSideSession].
|
* @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
|
* 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 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
|
@Suspendable
|
||||||
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)
|
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
|
@Suspendable
|
||||||
override fun call(): Void? {
|
override fun call(): Void? {
|
||||||
|
|
||||||
|
val encryptSvc = serviceHub.encryptedTransactionService
|
||||||
|
|
||||||
val networkMaxMessageSize = serviceHub.networkParameters.maxMessageSize
|
val networkMaxMessageSize = serviceHub.networkParameters.maxMessageSize
|
||||||
val maxPayloadSize = networkMaxMessageSize / 2
|
val maxPayloadSize = networkMaxMessageSize / 2
|
||||||
|
|
||||||
@ -146,20 +149,48 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any)
|
|||||||
var numSent = 0
|
var numSent = 0
|
||||||
payload = when (dataRequest.dataType) {
|
payload = when (dataRequest.dataType) {
|
||||||
FetchDataFlow.DataType.TRANSACTION -> dataRequest.hashes.map { txId ->
|
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)) {
|
if (!authorisedTransactions.isAuthorised(txId)) {
|
||||||
throw FetchDataFlow.IllegalTransactionRequest(txId)
|
throw FetchDataFlow.IllegalTransactionRequest(txId)
|
||||||
}
|
}
|
||||||
val tx = serviceHub.validatedTransactions.getTransaction(txId)
|
|
||||||
?: throw FetchDataFlow.HashNotFound(txId)
|
if (encrypted) {
|
||||||
authorisedTransactions.removeAuthorised(tx.id)
|
var encryptedTx = serviceHub.validatedTransactions.getEncryptedTransaction(txId)
|
||||||
authorisedTransactions.addAuthorised(getInputTransactions(tx))
|
|
||||||
totalByteCount += tx.txBits.size
|
if (encryptedTx == null) {
|
||||||
numSent++
|
val tx = serviceHub.validatedTransactions.getTransaction(txId)
|
||||||
tx
|
?: 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)
|
||||||
|
authorisedTransactions.addAuthorised(getInputTransactions(tx))
|
||||||
|
totalByteCount += tx.txBits.size
|
||||||
|
numSent++
|
||||||
|
tx
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Loop on all items returned using dataRequest.hashes.map:
|
// Loop on all items returned using dataRequest.hashes.map:
|
||||||
FetchDataFlow.DataType.BATCH_TRANSACTION -> dataRequest.hashes.map { txId ->
|
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)) {
|
if (!authorisedTransactions.isAuthorised(txId)) {
|
||||||
throw FetchDataFlow.IllegalTransactionRequest(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.SerializationToken
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import net.corda.core.serialization.SerializeAsTokenContext
|
import net.corda.core.serialization.SerializeAsTokenContext
|
||||||
|
import net.corda.core.transactions.EncryptedTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.NonEmptySet
|
import net.corda.core.utilities.NonEmptySet
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
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)
|
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) :
|
class FetchBatchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
|
||||||
FetchDataFlow<MaybeSerializedSignedTransaction, MaybeSerializedSignedTransaction>(requests, otherSide, DataType.BATCH_TRANSACTION) {
|
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.flows.FlowSession
|
||||||
import net.corda.core.node.StatesToRecord
|
import net.corda.core.node.StatesToRecord
|
||||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||||
|
import net.corda.core.transactions.EncryptedTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
@ -21,7 +22,8 @@ class ResolveTransactionsFlow private constructor(
|
|||||||
val initialTx: SignedTransaction?,
|
val initialTx: SignedTransaction?,
|
||||||
val txHashes: Set<SecureHash>,
|
val txHashes: Set<SecureHash>,
|
||||||
val otherSide: FlowSession,
|
val otherSide: FlowSession,
|
||||||
val statesToRecord: StatesToRecord
|
val statesToRecord: StatesToRecord,
|
||||||
|
val encrypted: Boolean = false
|
||||||
) : FlowLogic<Unit>() {
|
) : FlowLogic<Unit>() {
|
||||||
|
|
||||||
constructor(txHashes: Set<SecureHash>, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE)
|
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)
|
constructor(transaction: SignedTransaction, otherSide: FlowSession, statesToRecord: StatesToRecord = StatesToRecord.NONE)
|
||||||
: this(transaction, transaction.dependencies, otherSide, statesToRecord)
|
: 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
|
private var fetchNetParamsFromCounterpart = false
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@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
|
// 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.
|
// request to node V3 that doesn't know about this protocol.
|
||||||
fetchNetParamsFromCounterpart = counterpartyPlatformVersion >= PlatformVersionSwitches.FETCH_MISSING_NETWORK_PARAMETERS
|
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" }
|
logger.debug { "ResolveTransactionsFlow.call(): Otherside Platform Version = '$counterpartyPlatformVersion': Batch mode = $batchMode" }
|
||||||
|
|
||||||
|
// TODO: attachments and net params are not encrypted (yet)
|
||||||
if (initialTx != null) {
|
if (initialTx != null) {
|
||||||
fetchMissingAttachments(initialTx)
|
fetchMissingAttachments(initialTx)
|
||||||
fetchMissingNetworkParameters(initialTx)
|
fetchMissingNetworkParameters(initialTx)
|
||||||
}
|
}
|
||||||
|
|
||||||
val resolver = (serviceHub as ServiceHubCoreInternal).createTransactionsResolver(this)
|
val resolver = (serviceHub as ServiceHubCoreInternal).createTransactionsResolver(this)
|
||||||
resolver.downloadDependencies(batchMode)
|
|
||||||
|
if (encrypted) {
|
||||||
|
resolver.downloadEncryptedDependencies()
|
||||||
|
} else {
|
||||||
|
resolver.downloadDependencies(batchMode)
|
||||||
|
}
|
||||||
|
|
||||||
logger.trace { "ResolveTransactionsFlow: Sending END." }
|
logger.trace { "ResolveTransactionsFlow: Sending END." }
|
||||||
otherSide.send(FetchDataFlow.Request.End) // Finish fetching data.
|
otherSide.send(FetchDataFlow.Request.End) // Finish fetching data.
|
||||||
@ -66,7 +81,12 @@ class ResolveTransactionsFlow private constructor(
|
|||||||
// If transaction resolution is performed for a transaction where some states are relevant, then those should be
|
// If transaction resolution is performed for a transaction where some states are relevant, then those should be
|
||||||
// recorded if this has not already occurred.
|
// recorded if this has not already occurred.
|
||||||
val usedStatesToRecord = if (statesToRecord == StatesToRecord.NONE) StatesToRecord.ONLY_RELEVANT else statesToRecord
|
val usedStatesToRecord = if (statesToRecord == StatesToRecord.NONE) StatesToRecord.ONLY_RELEVANT else statesToRecord
|
||||||
resolver.recordDependencies(usedStatesToRecord)
|
|
||||||
|
if (encrypted) {
|
||||||
|
resolver.recordEncryptedDependencies(usedStatesToRecord)
|
||||||
|
} else {
|
||||||
|
resolver.recordDependencies(usedStatesToRecord)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,4 +128,25 @@ class ResolveTransactionsFlow private constructor(
|
|||||||
false
|
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
|
@Suspendable
|
||||||
fun recordDependencies(usedStatesToRecord: StatesToRecord)
|
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. */
|
/** Returns the network parameters the node is operating under. */
|
||||||
val networkParameters: NetworkParameters
|
val networkParameters: NetworkParameters
|
||||||
|
|
||||||
|
val encryptedTransactionService: EncryptedTransactionService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
|
* 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
|
// NOTE: Any services exposed to flows (public view) need to implement [SerializeAsToken] or similar to avoid
|
||||||
// their internal state from being serialized in checkpoints.
|
// 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
|
* 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.
|
* 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.concurrent.CordaFuture
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
|
import net.corda.core.transactions.EncryptedTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,6 +21,11 @@ interface TransactionStorage {
|
|||||||
*/
|
*/
|
||||||
fun getTransaction(id: SecureHash): SignedTransaction?
|
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
|
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
|
||||||
* incorporate the update.
|
* 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.TransactionDeserialisationException
|
||||||
import net.corda.core.internal.TransactionVerifierServiceInternal
|
import net.corda.core.internal.TransactionVerifierServiceInternal
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.serialization.CordaSerializable
|
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)
|
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))
|
: 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
|
//region Deprecated
|
||||||
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
|
/** Returns the contained [NotaryChangeWireTransaction], or throws if this is a normal transaction. */
|
||||||
@Deprecated("No replacement, this should not be used outside of Corda core")
|
@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
|
fun isNotaryChangeTransaction() = this.coreTransaction is NotaryChangeWireTransaction
|
||||||
//endregion
|
//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
|
// Helper for deprecated toLedgerTransaction
|
||||||
// TODO: revisit once Deterministic JVM code updated
|
// TODO: revisit once Deterministic JVM code updated
|
||||||
@Suppress("UNUSED") // not sure if this field can be removed safely??
|
@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.Vault
|
||||||
import net.corda.core.node.services.trackBy
|
import net.corda.core.node.services.trackBy
|
||||||
import net.corda.core.node.services.vault.QueryCriteria
|
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.OpaqueBytes
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.core.utilities.toHexString
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.`issued by`
|
import net.corda.finance.`issued by`
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
@ -29,6 +31,9 @@ class CashPaymentFlowTests {
|
|||||||
private lateinit var bankOfCordaNode: StartedMockNode
|
private lateinit var bankOfCordaNode: StartedMockNode
|
||||||
private lateinit var bankOfCorda: Party
|
private lateinit var bankOfCorda: Party
|
||||||
private lateinit var aliceNode: StartedMockNode
|
private lateinit var aliceNode: StartedMockNode
|
||||||
|
private lateinit var bobNode: StartedMockNode
|
||||||
|
|
||||||
|
private lateinit var issuanceTx: SignedTransaction
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun start() {
|
fun start() {
|
||||||
@ -36,8 +41,9 @@ class CashPaymentFlowTests {
|
|||||||
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
|
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
|
||||||
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
|
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
|
||||||
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||||
|
bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||||
val future = bankOfCordaNode.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity))
|
val future = bankOfCordaNode.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity))
|
||||||
future.getOrThrow()
|
issuanceTx = future.getOrThrow().stx
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -58,7 +64,12 @@ class CashPaymentFlowTests {
|
|||||||
|
|
||||||
val future = bankOfCordaNode.startFlow(CashPaymentFlow(expectedPayment, payTo))
|
val future = bankOfCordaNode.startFlow(CashPaymentFlow(expectedPayment, payTo))
|
||||||
mockNet.runNetwork()
|
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
|
// 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
|
// 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)
|
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)
|
@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.ServicesForResolution
|
||||||
import net.corda.core.node.services.ContractUpgradeService
|
import net.corda.core.node.services.ContractUpgradeService
|
||||||
import net.corda.core.node.services.CordaService
|
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.IdentityService
|
||||||
import net.corda.core.node.services.KeyManagementService
|
import net.corda.core.node.services.KeyManagementService
|
||||||
import net.corda.core.node.services.TransactionVerifierService
|
import net.corda.core.node.services.TransactionVerifierService
|
||||||
@ -290,7 +291,10 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
|
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
val keyManagementService = makeKeyManagementService(identityService).tokenize()
|
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
|
attachments.servicesForResolution = it
|
||||||
}
|
}
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
@ -1134,6 +1138,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
override val diagnosticsService: DiagnosticsService get() = this@AbstractNode.diagnosticsService
|
override val diagnosticsService: DiagnosticsService get() = this@AbstractNode.diagnosticsService
|
||||||
override val externalOperationExecutor: ExecutorService get() = this@AbstractNode.externalOperationExecutor
|
override val externalOperationExecutor: ExecutorService get() = this@AbstractNode.externalOperationExecutor
|
||||||
override val notaryService: NotaryService? get() = this@AbstractNode.notaryService
|
override val notaryService: NotaryService? get() = this@AbstractNode.notaryService
|
||||||
|
override val encryptedTransactionService: EncryptedTransactionService = this@AbstractNode.encryptedTransactionService
|
||||||
|
|
||||||
private lateinit var _myInfo: NodeInfo
|
private lateinit var _myInfo: NodeInfo
|
||||||
override val myInfo: NodeInfo get() = _myInfo
|
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.NetworkParameters
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.node.services.AttachmentStorage
|
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.IdentityService
|
||||||
import net.corda.core.node.services.NetworkParametersService
|
import net.corda.core.node.services.NetworkParametersService
|
||||||
import net.corda.core.node.services.TransactionStorage
|
import net.corda.core.node.services.TransactionStorage
|
||||||
@ -19,7 +20,8 @@ data class ServicesForResolutionImpl(
|
|||||||
override val attachments: AttachmentStorage,
|
override val attachments: AttachmentStorage,
|
||||||
override val cordappProvider: CordappProvider,
|
override val cordappProvider: CordappProvider,
|
||||||
override val networkParametersService: NetworkParametersService,
|
override val networkParametersService: NetworkParametersService,
|
||||||
private val validatedTransactions: TransactionStorage
|
private val validatedTransactions: TransactionStorage,
|
||||||
|
override val encryptedTransactionService: EncryptedTransactionService
|
||||||
) : ServicesForResolution {
|
) : ServicesForResolution {
|
||||||
override val networkParameters: NetworkParameters get() = networkParametersService.lookup(networkParametersService.currentHash) ?:
|
override val networkParameters: NetworkParameters get() = networkParametersService.lookup(networkParametersService.currentHash) ?:
|
||||||
throw IllegalArgumentException("No current parameters in network parameters storage")
|
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.NetworkParameters
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.node.services.AttachmentId
|
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.IdentityService
|
||||||
import net.corda.core.node.services.NetworkParametersService
|
import net.corda.core.node.services.NetworkParametersService
|
||||||
import net.corda.core.node.services.TransactionStorage
|
import net.corda.core.node.services.TransactionStorage
|
||||||
@ -38,7 +39,8 @@ class MigrationServicesForResolution(
|
|||||||
override val attachments: AttachmentStorageInternal,
|
override val attachments: AttachmentStorageInternal,
|
||||||
private val transactions: TransactionStorage,
|
private val transactions: TransactionStorage,
|
||||||
private val cordaDB: CordaPersistence,
|
private val cordaDB: CordaPersistence,
|
||||||
cacheFactory: MigrationNamedCacheFactory
|
cacheFactory: MigrationNamedCacheFactory,
|
||||||
|
override val encryptedTransactionService: EncryptedTransactionService = EncryptedTransactionService()
|
||||||
): ServicesForResolution {
|
): ServicesForResolution {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -3,11 +3,14 @@ package net.corda.node.services
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.internal.FetchEncryptedTransactionsFlow
|
||||||
import net.corda.core.internal.FetchTransactionsFlow
|
import net.corda.core.internal.FetchTransactionsFlow
|
||||||
import net.corda.core.internal.ResolveTransactionsFlow
|
import net.corda.core.internal.ResolveTransactionsFlow
|
||||||
import net.corda.core.internal.TransactionsResolver
|
import net.corda.core.internal.TransactionsResolver
|
||||||
import net.corda.core.internal.dependencies
|
import net.corda.core.internal.dependencies
|
||||||
import net.corda.core.node.StatesToRecord
|
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.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.trace
|
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
|
// 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.
|
// dependencies for other transactions, so strip these down to just the IDs here.
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -121,6 +249,12 @@ class DbTransactionsResolver(private val flow: ResolveTransactionsFlow) : Transa
|
|||||||
return Pair(requestedTxs.fromDisk.map { it.id }, requestedTxs.downloaded)
|
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
|
* 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.
|
* 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.NetworkMapCache
|
||||||
import net.corda.core.node.services.NetworkMapCacheBase
|
import net.corda.core.node.services.NetworkMapCacheBase
|
||||||
import net.corda.core.node.services.TransactionStorage
|
import net.corda.core.node.services.TransactionStorage
|
||||||
|
import net.corda.core.transactions.EncryptedTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.node.internal.InitiatedFlowFactory
|
import net.corda.node.internal.InitiatedFlowFactory
|
||||||
import net.corda.node.internal.cordapp.CordappProviderInternal
|
import net.corda.node.internal.cordapp.CordappProviderInternal
|
||||||
import net.corda.node.services.DbTransactionsResolver
|
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.messaging.MessagingService
|
||||||
import net.corda.node.services.network.NetworkMapUpdater
|
import net.corda.node.services.network.NetworkMapUpdater
|
||||||
import net.corda.node.services.persistence.AttachmentStorageInternal
|
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.ExternalEvent
|
||||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
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.
|
// TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry.
|
||||||
fun addTransaction(transaction: SignedTransaction): Boolean
|
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.
|
* Add a new *unverified* transaction to the store.
|
||||||
*/
|
*/
|
||||||
fun addUnverifiedTransaction(transaction: SignedTransaction)
|
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
|
* 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.
|
* ID exists.
|
||||||
*/
|
*/
|
||||||
fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>?
|
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
|
* 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.
|
* a DB transaction.
|
||||||
|
@ -13,6 +13,7 @@ import net.corda.core.serialization.*
|
|||||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
import net.corda.core.transactions.CoreTransaction
|
import net.corda.core.transactions.CoreTransaction
|
||||||
|
import net.corda.core.transactions.EncryptedTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
@ -53,8 +54,13 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
|||||||
val status: TransactionStatus,
|
val status: TransactionStatus,
|
||||||
|
|
||||||
@Column(name = "timestamp", nullable = false)
|
@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 {
|
enum class TransactionStatus {
|
||||||
UNVERIFIED,
|
UNVERIFIED,
|
||||||
@ -120,17 +126,33 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
|||||||
name = "DBTransactionStorage_transactions",
|
name = "DBTransactionStorage_transactions",
|
||||||
toPersistentEntityKey = SecureHash::toString,
|
toPersistentEntityKey = SecureHash::toString,
|
||||||
fromPersistentEntity = {
|
fromPersistentEntity = {
|
||||||
SecureHash.create(it.txId) to TxCacheValue(
|
if (it.encrypted) {
|
||||||
it.transaction.deserialize(context = contextToUse()),
|
SecureHash.create(it.txId) to TxCacheValue(
|
||||||
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 ->
|
toPersistentEntity = { key: SecureHash, value: TxCacheValue ->
|
||||||
DBTransaction(
|
DBTransaction(
|
||||||
txId = key.toString(),
|
txId = key.toString(),
|
||||||
stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id?.uuid?.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,
|
status = value.status,
|
||||||
timestamp = clock.instant()
|
timestamp = clock.instant(),
|
||||||
|
encrypted = value.encrypted
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
persistentEntityClass = DBTransaction::class.java,
|
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 {
|
private fun weighTx(tx: AppendOnlyPersistentMapBase.Transactional<TxCacheValue>): Int {
|
||||||
val actTx = tx.peekableValue ?: return 0
|
val actTx = tx.peekableValue ?: return 0
|
||||||
return actTx.sigs.sumBy { it.size + transactionSignatureOverheadEstimate } + actTx.txBits.size
|
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? {
|
override fun getTransaction(id: SecureHash): SignedTransaction? {
|
||||||
return database.transaction {
|
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>? {
|
override fun getTransactionInternal(id: SecureHash): Pair<SignedTransaction, Boolean>? {
|
||||||
return database.transaction {
|
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> {
|
private fun snapshot(): List<SignedTransaction> {
|
||||||
return txStorage.content.allPersisted.use {
|
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
|
// Cache value type to just store the immutable bits of a signed transaction plus conversion helpers
|
||||||
private data class TxCacheValue(
|
private data class TxCacheValue(
|
||||||
val txBits: SerializedBytes<CoreTransaction>,
|
val id: SecureHash,
|
||||||
|
val txBits: ByteArray,
|
||||||
val sigs: List<TransactionSignature>,
|
val sigs: List<TransactionSignature>,
|
||||||
val status: TransactionStatus
|
val status: TransactionStatus,
|
||||||
|
val encrypted: Boolean
|
||||||
) {
|
) {
|
||||||
constructor(stx: SignedTransaction, status: TransactionStatus) : this(
|
constructor(stx: SignedTransaction, status: TransactionStatus) : this(
|
||||||
stx.txBits,
|
stx.id,
|
||||||
Collections.unmodifiableList(stx.sigs),
|
stx.txBits.bytes,
|
||||||
status)
|
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.CordaSerializable
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
|
import net.corda.core.transactions.EncryptedTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
@ -786,6 +787,31 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
|||||||
delegate.getTransactionInternal(id)
|
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 {
|
interface TxRecord {
|
||||||
|
@ -7,7 +7,13 @@ import net.corda.core.crypto.Crypto
|
|||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.SignatureMetadata
|
import net.corda.core.crypto.SignatureMetadata
|
||||||
import net.corda.core.crypto.TransactionSignature
|
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.toFuture
|
||||||
|
import net.corda.core.transactions.EncryptedTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.node.CordaClock
|
import net.corda.node.CordaClock
|
||||||
import net.corda.node.MutableClock
|
import net.corda.node.MutableClock
|
||||||
@ -15,6 +21,7 @@ import net.corda.node.SimpleClock
|
|||||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||||
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.serialization.internal.CordaSerializationEncoding
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
@ -32,12 +39,17 @@ import org.junit.Before
|
|||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import rx.plugins.RxJavaHooks
|
import rx.plugins.RxJavaHooks
|
||||||
|
import java.security.SecureRandom
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.concurrent.Semaphore
|
import java.util.concurrent.Semaphore
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.KeyGenerator
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
class DBTransactionStorageTests {
|
class DBTransactionStorageTests {
|
||||||
private companion object {
|
private companion object {
|
||||||
@ -390,6 +402,47 @@ class DBTransactionStorageTests {
|
|||||||
assertThat(warning).isEqualTo(DBTransactionStorage.TRANSACTION_ALREADY_IN_PROGRESS_WARNING)
|
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())) {
|
private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null, clock: CordaClock = SimpleClock(Clock.systemUTC())) {
|
||||||
transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride
|
transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride
|
||||||
?: 1024), clock)
|
?: 1024), clock)
|
||||||
@ -413,4 +466,12 @@ class DBTransactionStorageTests {
|
|||||||
listOf(TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID)))
|
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(
|
override val keyManagementService: KeyManagementService = MockKeyManagementService(
|
||||||
identityService,
|
identityService,
|
||||||
*arrayOf(initialIdentity.keyPair) + moreKeys
|
*arrayOf(initialIdentity.keyPair) + moreKeys
|
||||||
)
|
),
|
||||||
|
override val encryptedTransactionService : EncryptedTransactionService = EncryptedTransactionService()
|
||||||
) : ServiceHub {
|
) : ServiceHub {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -457,7 +458,7 @@ open class MockServices private constructor(
|
|||||||
override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService()
|
override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService()
|
||||||
|
|
||||||
protected val servicesForResolution: ServicesForResolution
|
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 {
|
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
|
||||||
return NodeVaultService(clock, keyManagementService, servicesForResolution, database, schemaService, cordappLoader.appClassLoader).apply { start() }
|
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.messaging.DataFeed
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
|
import net.corda.core.transactions.EncryptedTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.node.services.api.WritableTransactionStorage
|
import net.corda.node.services.api.WritableTransactionStorage
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
@ -30,6 +31,7 @@ open class MockTransactionStorage : WritableTransactionStorage, SingletonSeriali
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val txns = HashMap<SecureHash, TxHolder>()
|
private val txns = HashMap<SecureHash, TxHolder>()
|
||||||
|
private val encryptedTxns = HashMap<SecureHash, EncryptedTxHolder>()
|
||||||
|
|
||||||
private val _updatesPublisher = PublishSubject.create<SignedTransaction>()
|
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 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 TxHolder(val stx: SignedTransaction, var isVerified: Boolean)
|
||||||
|
private class EncryptedTxHolder(val etx: EncryptedTransaction, var isVerified: Boolean)
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user