mirror of
https://github.com/corda/corda.git
synced 2025-04-07 11:27:01 +00:00
CORDA-2089 - network parameters tags - part (#4228)
* CORDA-2089 - network parameters tags - part Data structures changes, storage and notarisation. Tag transactions with network parameters hash that was in force when tx was created. Add component group on all core transactions and resolved parameters on full transactions. The hash should be always visible on the filtered versions of transactions. Add notarisation check that the parameters are current. Implement network parameters storage on services for resolution. This is only part of the work, next PR will include changes to ResolveTransactionsFlow to make sure that parameters in the transaction graph are ordered (this is to prevent the downgrade attack, when the malicious notary and participants sign transaction that shouldn't be notarised otherwise). Probably on network services side we need the default parameters endpoint for the transactions that were created before this change - for now it's default to the current ones. * Make parameters storage agnostic to cert hierarchy Test fixes * Address most PR comments * Fixes after rebase * Fixes. Add epoch column to parameters storage. * Address part of review comments * Some more comments * Hopefully fixing what I broke doing rebse * Address Kostas comments * Further fixes * Save all parameters from updates to storage * Fix integration test * Address comments * Fixes after rebase * Fix test * Fixes * Add wrapper for filtering around parameters hash * API stability fixes * Add NetworkParametersStorageInternal * Rename
This commit is contained in:
parent
e4c1db4db6
commit
5d2ad46553
@ -189,7 +189,8 @@ private class WireTransactionSerializer : JsonSerializer<WireTransaction>() {
|
||||
value.timeWindow,
|
||||
value.attachments,
|
||||
value.references,
|
||||
value.privacySalt
|
||||
value.privacySalt,
|
||||
value.networkParametersHash
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -204,7 +205,8 @@ private class WireTransactionDeserializer : JsonDeserializer<WireTransaction>()
|
||||
wrapper.attachments,
|
||||
wrapper.notary,
|
||||
wrapper.timeWindow,
|
||||
wrapper.references
|
||||
wrapper.references,
|
||||
wrapper.networkParametersHash
|
||||
)
|
||||
return WireTransaction(componentGroups, wrapper.privacySalt)
|
||||
}
|
||||
@ -218,7 +220,8 @@ private class WireTransactionJson(val id: SecureHash,
|
||||
val timeWindow: TimeWindow?,
|
||||
val attachments: List<SecureHash>,
|
||||
val references: List<StateRef>,
|
||||
val privacySalt: PrivacySalt)
|
||||
val privacySalt: PrivacySalt,
|
||||
val networkParametersHash: SecureHash?)
|
||||
|
||||
private interface TransactionStateMixin {
|
||||
@get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
|
@ -24,6 +24,7 @@ import net.corda.core.internal.DigitalSignatureWithCert
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.NetworkParametersStorage
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.serialize
|
||||
@ -92,8 +93,13 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
||||
}
|
||||
services = rigorousMock()
|
||||
cordappProvider = rigorousMock()
|
||||
val networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
||||
val networkParametersStorage = rigorousMock<NetworkParametersStorage>().also {
|
||||
doReturn(networkParameters.serialize().hash).whenever(it).currentHash
|
||||
}
|
||||
doReturn(networkParametersStorage).whenever(services).networkParametersStorage
|
||||
doReturn(cordappProvider).whenever(services).cordappProvider
|
||||
doReturn(testNetworkParameters(minimumPlatformVersion = 4)).whenever(services).networkParameters
|
||||
doReturn(networkParameters).whenever(services).networkParameters
|
||||
doReturn(attachments).whenever(services).attachments
|
||||
}
|
||||
|
||||
@ -254,7 +260,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
||||
println(mapper.writeValueAsString(json))
|
||||
val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
|
||||
assertThat(signaturesJson.childrenAs<TransactionSignature>(mapper)).isEqualTo(stx.sigs)
|
||||
val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "references", "privacySalt")
|
||||
val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "references", "privacySalt", "networkParametersHash")
|
||||
assertThat(wtxFields[0].valueAs<SecureHash>(mapper)).isEqualTo(wtx.id)
|
||||
assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary)
|
||||
assertThat(wtxFields[2].childrenAs<StateRef>(mapper)).isEqualTo(wtx.inputs)
|
||||
|
@ -71,7 +71,8 @@ object TransactionGenerator {
|
||||
TransactionVerificationRequest(
|
||||
wtx3.serialize(),
|
||||
arrayOf(wtx1.serialize(), wtx2.serialize()),
|
||||
arrayOf(contractAttachment.serialize().bytes))
|
||||
arrayOf(contractAttachment.serialize().bytes),
|
||||
ledgerServices.networkParameters.serialize())
|
||||
.serialize()
|
||||
.writeTo(output)
|
||||
}
|
||||
@ -104,7 +105,8 @@ object TransactionGenerator {
|
||||
TransactionVerificationRequest(
|
||||
wtx3.serialize(),
|
||||
arrayOf(wtx1.serialize(), wtx2.serialize()),
|
||||
arrayOf(contractAttachment.serialize().bytes))
|
||||
arrayOf(contractAttachment.serialize().bytes),
|
||||
ledgerServices.networkParameters.serialize())
|
||||
.serialize()
|
||||
.writeTo(output)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
@ -14,7 +15,8 @@ import net.corda.core.transactions.WireTransaction
|
||||
@CordaSerializable
|
||||
class TransactionVerificationRequest(val wtxToVerify: SerializedBytes<WireTransaction>,
|
||||
val dependencies: Array<SerializedBytes<WireTransaction>>,
|
||||
val attachments: Array<ByteArray>) {
|
||||
val attachments: Array<ByteArray>,
|
||||
val networkParameters: SerializedBytes<NetworkParameters>) {
|
||||
fun toLedgerTransaction(): LedgerTransaction {
|
||||
val deps = dependencies.map { it.deserialize() }.associateBy(WireTransaction::id)
|
||||
val attachments = attachments.map { it.deserialize<Attachment>() }
|
||||
@ -24,10 +26,11 @@ class TransactionVerificationRequest(val wtxToVerify: SerializedBytes<WireTransa
|
||||
val contractAttachmentMap = emptyMap<ContractClassName, ContractAttachment>()
|
||||
@Suppress("DEPRECATION")
|
||||
return wtxToVerify.deserialize().toLedgerTransaction(
|
||||
resolveIdentity = { null },
|
||||
resolveAttachment = { attachmentMap[it] },
|
||||
resolveStateRef = { deps[it.txhash]?.outputs?.get(it.index) },
|
||||
resolveContractAttachment = { contractAttachmentMap[it.contract]?.id }
|
||||
resolveIdentity = { null },
|
||||
resolveAttachment = { attachmentMap[it] },
|
||||
resolveStateRef = { deps[it.txhash]?.outputs?.get(it.index) },
|
||||
resolveContractAttachment = { contractAttachmentMap[it.contract]?.id },
|
||||
resolveParameters = { networkParameters.deserialize() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -12,5 +12,6 @@ enum class ComponentGroupEnum {
|
||||
NOTARY_GROUP, // ordinal = 4.
|
||||
TIMEWINDOW_GROUP, // ordinal = 5.
|
||||
SIGNERS_GROUP, // ordinal = 6.
|
||||
REFERENCES_GROUP // ordinal = 7.
|
||||
REFERENCES_GROUP, // ordinal = 7.
|
||||
PARAMETERS_GROUP // ordinal = 8.
|
||||
}
|
||||
|
@ -212,6 +212,11 @@ abstract class SignTransactionFlow @JvmOverloads constructor(val otherSideSessio
|
||||
progressTracker.currentStep = RECEIVING
|
||||
// Receive transaction and resolve dependencies, check sufficient signatures is disabled as we don't have all signatures.
|
||||
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false))
|
||||
// TODO ENT-2666: Have time period for checking the parameters (because of the delay of propagation of new network data).
|
||||
check(stx.networkParametersHash == serviceHub.networkParametersStorage.currentHash) {
|
||||
"Received transaction for signing with invalid network parameters hash: ${stx.networkParametersHash}." +
|
||||
"Node's parameters hash: ${serviceHub.networkParametersStorage.currentHash}"
|
||||
}
|
||||
// Receive the signing key that the party requesting the signature expects us to sign with. Having this provided
|
||||
// means we only have to check we own that one key, rather than matching all keys in the transaction against all
|
||||
// keys we own.
|
||||
|
@ -33,7 +33,8 @@ class NotaryChangeFlow<out T : ContractState>(
|
||||
val tx = NotaryChangeTransactionBuilder(
|
||||
inputs.map { it.ref },
|
||||
originalState.state.notary,
|
||||
modification
|
||||
modification,
|
||||
serviceHub.networkParametersStorage.currentHash
|
||||
).build()
|
||||
|
||||
val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet()
|
||||
|
@ -2,6 +2,7 @@ package net.corda.core.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.contracts.ComponentGroupEnum
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.crypto.SecureHash
|
||||
@ -12,10 +13,7 @@ import net.corda.core.internal.FetchDataFlow
|
||||
import net.corda.core.internal.notary.generateSignature
|
||||
import net.corda.core.internal.notary.validateSignatures
|
||||
import net.corda.core.internal.pushToLoggingContext
|
||||
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||
import net.corda.core.transactions.ReferenceStateRef
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.transactions.*
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
import net.corda.core.utilities.unwrap
|
||||
@ -30,7 +28,8 @@ class NotaryFlow {
|
||||
* signatures will be returned – one from each replica that accepted the input state commit.
|
||||
*
|
||||
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
|
||||
* by another transaction or the time-window is invalid.
|
||||
* by another transaction or the time-window is invalid or
|
||||
* the parameters used for this transaction are no longer in force in the network.
|
||||
*/
|
||||
@DoNotImplement
|
||||
@InitiatingFlow
|
||||
@ -99,7 +98,9 @@ class NotaryFlow {
|
||||
val ctx = stx.coreTransaction
|
||||
val tx = when (ctx) {
|
||||
is ContractUpgradeWireTransaction -> ctx.buildFilteredTransaction()
|
||||
is WireTransaction -> ctx.buildFilteredTransaction(Predicate { it is StateRef || it is ReferenceStateRef || it is TimeWindow || it == notaryParty })
|
||||
is WireTransaction -> ctx.buildFilteredTransaction(Predicate {
|
||||
it is StateRef || it is ReferenceStateRef || it is TimeWindow || it == notaryParty || it is NetworkParametersHash
|
||||
})
|
||||
else -> ctx
|
||||
}
|
||||
session.send(NotarisationPayload(tx, signature))
|
||||
|
@ -21,6 +21,7 @@ object ContractUpgradeUtils {
|
||||
else -> getContractAttachmentId(stateAndRef.state.contract, services)
|
||||
}
|
||||
val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services)
|
||||
val networkParametersHash = services.networkParametersStorage.currentHash
|
||||
|
||||
val inputs = listOf(stateAndRef.ref)
|
||||
return ContractUpgradeTransactionBuilder(
|
||||
@ -29,7 +30,8 @@ object ContractUpgradeUtils {
|
||||
legacyContractAttachmentId,
|
||||
upgradedContractClass.name,
|
||||
upgradedContractAttachmentId,
|
||||
privacySalt
|
||||
privacySalt,
|
||||
networkParametersHash
|
||||
).build()
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ class FetchAttachmentsFlow(requests: Set<SecureHash>,
|
||||
* [FetchDataFlow.DownloadedVsRequestedDataMismatch] being thrown.
|
||||
* If the remote peer doesn't have an entry, it results in a [FetchDataFlow.HashNotFound] exception.
|
||||
* If the remote peer is not authorized to request this transaction, it results in a [FetchDataFlow.IllegalTransactionRequest] exception.
|
||||
* Authorisation is accorded only on valid ancestors of the root transation.
|
||||
* Authorisation is accorded only on valid ancestors of the root transaction.
|
||||
* Note that returned transactions are not inserted into the database, because it's up to the caller to actually verify the transactions are valid.
|
||||
*/
|
||||
class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
|
||||
|
@ -17,9 +17,10 @@ import kotlin.reflect.KClass
|
||||
/** Constructs a [NotaryChangeWireTransaction]. */
|
||||
class NotaryChangeTransactionBuilder(val inputs: List<StateRef>,
|
||||
val notary: Party,
|
||||
val newNotary: Party) {
|
||||
val newNotary: Party,
|
||||
val networkParametersHash: SecureHash) {
|
||||
fun build(): NotaryChangeWireTransaction {
|
||||
val components = listOf(inputs, notary, newNotary).map { it.serialize() }
|
||||
val components = listOf(inputs, notary, newNotary, networkParametersHash).map { it.serialize() }
|
||||
return NotaryChangeWireTransaction(components)
|
||||
}
|
||||
}
|
||||
@ -31,9 +32,10 @@ class ContractUpgradeTransactionBuilder(
|
||||
val legacyContractAttachmentId: SecureHash,
|
||||
val upgradedContractClassName: ContractClassName,
|
||||
val upgradedContractAttachmentId: SecureHash,
|
||||
val privacySalt: PrivacySalt = PrivacySalt()) {
|
||||
val privacySalt: PrivacySalt = PrivacySalt(),
|
||||
val networkParametersHash: SecureHash) {
|
||||
fun build(): ContractUpgradeWireTransaction {
|
||||
val components = listOf(inputs, notary, legacyContractAttachmentId, upgradedContractClassName, upgradedContractAttachmentId).map { it.serialize() }
|
||||
val components = listOf(inputs, notary, legacyContractAttachmentId, upgradedContractClassName, upgradedContractAttachmentId, networkParametersHash).map { it.serialize() }
|
||||
return ContractUpgradeWireTransaction(components, privacySalt)
|
||||
}
|
||||
}
|
||||
@ -128,7 +130,8 @@ fun createComponentGroups(inputs: List<StateRef>,
|
||||
attachments: List<SecureHash>,
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?,
|
||||
references: List<StateRef>): List<ComponentGroup> {
|
||||
references: List<StateRef>,
|
||||
networkParametersHash: SecureHash?): List<ComponentGroup> {
|
||||
val serialize = { value: Any, _: Int -> value.serialize() }
|
||||
val componentGroupMap: MutableList<ComponentGroup> = mutableListOf()
|
||||
if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.INPUTS_GROUP.ordinal, inputs.lazyMapped(serialize)))
|
||||
@ -142,6 +145,7 @@ fun createComponentGroups(inputs: List<StateRef>,
|
||||
// Adding signers to their own group. This is required for command visibility purposes: a party receiving
|
||||
// a FilteredTransaction can now verify it sees all the commands it should sign.
|
||||
if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.SIGNERS_GROUP.ordinal, commands.map { it.signers }.lazyMapped(serialize)))
|
||||
if (networkParametersHash != null) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.PARAMETERS_GROUP.ordinal, listOf(networkParametersHash.serialize())))
|
||||
return componentGroupMap
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,6 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
|
||||
requestPayload.requestSignature,
|
||||
tx.timeWindow,
|
||||
tx.references)
|
||||
|
||||
} catch (e: NotaryInternalException) {
|
||||
logError(e.error)
|
||||
// Any exception that's not a NotaryInternalException is assumed to be an unexpected internal error
|
||||
@ -95,6 +94,7 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
|
||||
val transaction = extractParts(requestPayload)
|
||||
transactionId = transaction.id
|
||||
checkNotary(transaction.notary)
|
||||
checkParametersHash(transaction.networkParametersHash)
|
||||
checkInputs(transaction.inputs + transaction.references)
|
||||
return transaction
|
||||
} catch (e: Exception) {
|
||||
@ -122,6 +122,21 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that network parameters hash on this transaction is the current hash for the network.
|
||||
*/
|
||||
// TODO ENT-2666 Implement network parameters fuzzy checking. By design in Corda network we have propagation time delay.
|
||||
// We will never end up in perfect synchronization with all the nodes. However, network parameters update process
|
||||
// lets us predict what is the reasonable time window for changing parameters on most of the nodes.
|
||||
@Suspendable
|
||||
protected fun checkParametersHash(networkParametersHash: SecureHash?) {
|
||||
if (networkParametersHash == null && serviceHub.networkParameters.minimumPlatformVersion < 4) return
|
||||
val notaryParametersHash = serviceHub.networkParametersStorage.currentHash
|
||||
require (notaryParametersHash == networkParametersHash) {
|
||||
"Transaction for notarisation was tagged with parameters with hash: $networkParametersHash, but current network parameters are: $notaryParametersHash"
|
||||
}
|
||||
}
|
||||
|
||||
/** Verifies that the correct notarisation request was signed by the counterparty. */
|
||||
private fun validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) {
|
||||
val requestingParty = otherSideSession.counterparty
|
||||
@ -150,7 +165,8 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
|
||||
val inputs: List<StateRef>,
|
||||
val timeWindow: TimeWindow?,
|
||||
val notary: Party?,
|
||||
val references: List<StateRef> = emptyList()
|
||||
val references: List<StateRef> = emptyList(),
|
||||
val networkParametersHash: SecureHash?
|
||||
)
|
||||
|
||||
private fun logError(error: NotaryError) {
|
||||
|
@ -5,10 +5,7 @@ import net.corda.core.DoNotImplement
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.cordapp.CordappContext
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignableData
|
||||
import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.ContractUpgradeFlow
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
@ -41,6 +38,9 @@ interface ServicesForResolution {
|
||||
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */
|
||||
val cordappProvider: CordappProvider
|
||||
|
||||
/** Provides access to storage of historical network parameters that are used in transaction resolution */
|
||||
val networkParametersStorage: NetworkParametersStorage
|
||||
|
||||
/** Returns the network parameters the node is operating under. */
|
||||
val networkParameters: NetworkParameters
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.CordaInternal
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.SignedDataWithCert
|
||||
import net.corda.core.node.NetworkParameters
|
||||
|
||||
/**
|
||||
* Interface for handling network parameters storage used for resolving transactions according to parameters that were
|
||||
* historically in force in the network.
|
||||
*/
|
||||
@DoNotImplement
|
||||
interface NetworkParametersStorage {
|
||||
/**
|
||||
* Hash of the current parameters for the network.
|
||||
*/
|
||||
val currentHash: SecureHash
|
||||
|
||||
/**
|
||||
* For backwards compatibility, this parameters hash will be used for resolving historical transactions in the chain.
|
||||
*/
|
||||
val defaultHash: SecureHash
|
||||
|
||||
/**
|
||||
* Return network parameters for the given hash. Null if there are no parameters for this hash in the storage and we are unable to
|
||||
* get them from network map.
|
||||
*/
|
||||
fun lookup(hash: SecureHash): NetworkParameters?
|
||||
}
|
@ -3,6 +3,8 @@ package net.corda.core.transactions
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
|
||||
/**
|
||||
@ -16,13 +18,24 @@ abstract class CoreTransaction : BaseTransaction() {
|
||||
abstract override val inputs: List<StateRef>
|
||||
/** The reference inputs of this transaction, containing the state references only. **/
|
||||
abstract override val references: List<StateRef>
|
||||
/**
|
||||
* Hash of the network parameters that were in force when the transaction was notarised. Null means, that the transaction
|
||||
* was created on older version of Corda (before 4), resolution will default to initial parameters.
|
||||
*/
|
||||
abstract val networkParametersHash: SecureHash?
|
||||
}
|
||||
|
||||
/** A transaction with fully resolved components, such as input states. */
|
||||
abstract class FullTransaction : BaseTransaction() {
|
||||
abstract override val inputs: List<StateAndRef<ContractState>>
|
||||
abstract override val references: List<StateAndRef<ContractState>>
|
||||
|
||||
// TODO NetworkParameters field is nullable only because of the API stability and the fact that our ledger transactions exposed constructors
|
||||
// (they were data classes). This should be revisited.
|
||||
/**
|
||||
* Network parameters that were in force when this transaction was created. Resolved from the hash of network parameters on the corresponding
|
||||
* wire transaction.
|
||||
*/
|
||||
abstract val networkParameters: NetworkParameters?
|
||||
override fun checkBaseInvariants() {
|
||||
super.checkBaseInvariants()
|
||||
checkInputsAndReferencesHaveSameNotary()
|
||||
|
@ -69,6 +69,11 @@ data class ContractUpgradeWireTransaction(
|
||||
val legacyContractAttachmentId: SecureHash by lazy { serializedComponents[LEGACY_ATTACHMENT.ordinal].deserialize<SecureHash>() }
|
||||
val upgradedContractClassName: ContractClassName by lazy { serializedComponents[UPGRADED_CONTRACT.ordinal].deserialize<ContractClassName>() }
|
||||
val upgradedContractAttachmentId: SecureHash by lazy { serializedComponents[UPGRADED_ATTACHMENT.ordinal].deserialize<SecureHash>() }
|
||||
override val networkParametersHash: SecureHash? by lazy {
|
||||
if (serializedComponents.size >= PARAMETERS_HASH.ordinal + 1) {
|
||||
serializedComponents[PARAMETERS_HASH.ordinal].deserialize<SecureHash>()
|
||||
} else null
|
||||
}
|
||||
|
||||
init {
|
||||
check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" }
|
||||
@ -106,6 +111,8 @@ data class ContractUpgradeWireTransaction(
|
||||
?: throw AttachmentResolutionException(legacyContractAttachmentId)
|
||||
val upgradedContractAttachment = services.attachments.openAttachment(upgradedContractAttachmentId)
|
||||
?: throw AttachmentResolutionException(upgradedContractAttachmentId)
|
||||
val hashToResolve = networkParametersHash ?: services.networkParametersStorage.defaultHash
|
||||
val resolvedNetworkParameters = services.networkParametersStorage.lookup(hashToResolve) ?: throw TransactionResolutionException(id)
|
||||
return ContractUpgradeLedgerTransaction(
|
||||
resolvedInputs,
|
||||
notary,
|
||||
@ -115,7 +122,7 @@ data class ContractUpgradeWireTransaction(
|
||||
id,
|
||||
privacySalt,
|
||||
sigs,
|
||||
services.networkParameters
|
||||
resolvedNetworkParameters
|
||||
)
|
||||
}
|
||||
|
||||
@ -145,12 +152,13 @@ data class ContractUpgradeWireTransaction(
|
||||
}
|
||||
}
|
||||
|
||||
/** Constructs a filtered transaction: the inputs and the notary party are always visible, while the rest are hidden. */
|
||||
/** Constructs a filtered transaction: the inputs, the notary party and network parameters hash are always visible, while the rest are hidden. */
|
||||
fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction {
|
||||
val totalComponents = (0 until serializedComponents.size).toSet()
|
||||
val visibleComponents = mapOf(
|
||||
INPUTS.ordinal to FilteredComponent(serializedComponents[INPUTS.ordinal], nonces[INPUTS.ordinal]),
|
||||
NOTARY.ordinal to FilteredComponent(serializedComponents[NOTARY.ordinal], nonces[NOTARY.ordinal])
|
||||
NOTARY.ordinal to FilteredComponent(serializedComponents[NOTARY.ordinal], nonces[NOTARY.ordinal]),
|
||||
PARAMETERS_HASH.ordinal to FilteredComponent(serializedComponents[PARAMETERS_HASH.ordinal], nonces[PARAMETERS_HASH.ordinal])
|
||||
)
|
||||
val hiddenComponents = (totalComponents - visibleComponents.keys).map { index ->
|
||||
val hash = componentHash(nonces[index], serializedComponents[index])
|
||||
@ -161,13 +169,13 @@ data class ContractUpgradeWireTransaction(
|
||||
}
|
||||
|
||||
enum class Component {
|
||||
INPUTS, NOTARY, LEGACY_ATTACHMENT, UPGRADED_CONTRACT, UPGRADED_ATTACHMENT
|
||||
INPUTS, NOTARY, LEGACY_ATTACHMENT, UPGRADED_CONTRACT, UPGRADED_ATTACHMENT, PARAMETERS_HASH
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A filtered version of the [ContractUpgradeWireTransaction]. In comparison with a regular [FilteredTransaction], there
|
||||
* is no flexibility on what parts of the transaction to reveal – the inputs and notary field are always visible and the
|
||||
* is no flexibility on what parts of the transaction to reveal – the inputs, notary and network parameters hash fields are always visible and the
|
||||
* rest of the transaction is always hidden. Its only purpose is to hide transaction data when using a non-validating notary.
|
||||
*/
|
||||
@KeepForDJVM
|
||||
@ -189,6 +197,9 @@ data class ContractUpgradeFilteredTransaction(
|
||||
visibleComponents[NOTARY.ordinal]?.component?.deserialize<Party>()
|
||||
?: throw IllegalArgumentException("Notary not specified")
|
||||
}
|
||||
override val networkParametersHash: SecureHash? by lazy {
|
||||
visibleComponents[PARAMETERS_HASH.ordinal]?.component?.deserialize<SecureHash>()
|
||||
}
|
||||
override val id: SecureHash by lazy {
|
||||
val totalComponents = visibleComponents.size + hiddenComponents.size
|
||||
val hashList = (0 until totalComponents).map { i ->
|
||||
@ -230,9 +241,9 @@ data class ContractUpgradeLedgerTransaction(
|
||||
override val id: SecureHash,
|
||||
val privacySalt: PrivacySalt,
|
||||
override val sigs: List<TransactionSignature>,
|
||||
private val networkParameters: NetworkParameters
|
||||
override val networkParameters: NetworkParameters
|
||||
) : FullTransaction(), TransactionWithSignatures {
|
||||
/** ContractUpgradeLEdgerTransactions do not contain reference input states. */
|
||||
/** ContractUpgradeLedgerTransactions do not contain reference input states. */
|
||||
override val references: List<StateAndRef<ContractState>> = emptyList()
|
||||
/** The legacy contract class name is determined by the first input state. */
|
||||
private val legacyContractClassName = inputs.first().state.contract
|
||||
|
@ -52,7 +52,10 @@ private constructor(
|
||||
override val notary: Party?,
|
||||
val timeWindow: TimeWindow?,
|
||||
val privacySalt: PrivacySalt,
|
||||
val networkParameters: NetworkParameters?,
|
||||
/** Network parameters that were in force when the transaction was notarised. */
|
||||
// TODO Network parameters should never be null on LedgerTransaction, this is left only because of deprecated constructors. We should decide to
|
||||
// get rid of them.
|
||||
override val networkParameters: NetworkParameters?,
|
||||
override val references: List<StateAndRef<ContractState>>
|
||||
//DOCEND 1
|
||||
) : FullTransaction() {
|
||||
@ -81,7 +84,7 @@ private constructor(
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?,
|
||||
privacySalt: PrivacySalt,
|
||||
networkParameters: NetworkParameters?,
|
||||
networkParameters: NetworkParameters,
|
||||
references: List<StateAndRef<ContractState>>,
|
||||
componentGroups: List<ComponentGroup>? = null,
|
||||
serializedInputs: List<SerializedStateAndRef>? = null,
|
||||
@ -121,6 +124,11 @@ private constructor(
|
||||
*/
|
||||
@Throws(TransactionVerificationException::class)
|
||||
fun verify() {
|
||||
if (networkParameters == null) {
|
||||
// For backwards compatibility only.
|
||||
logger.warn("Network parameters on the LedgerTransaction with id: $id are null. Please don't use deprecated constructors of the LedgerTransaction. " +
|
||||
"Use WireTransaction.toLedgerTransaction instead. The result of the verify method might not be accurate.")
|
||||
}
|
||||
val contractAttachmentsByContract: Map<ContractClassName, ContractAttachment> = getUniqueContractAttachmentsByContract()
|
||||
|
||||
AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments) { transactionClassLoader ->
|
||||
@ -169,14 +177,14 @@ private constructor(
|
||||
/**
|
||||
* Verify that for each contract the network wide package owner is respected.
|
||||
*
|
||||
* TODO - revisit once transaction contains network parameters.
|
||||
* TODO - revisit once transaction contains network parameters. - UPDATE: It contains them, but because of the API stability and the fact that
|
||||
* LedgerTransaction was data class i.e. exposed constructors that shouldn't had been exposed, we still need to keep them nullable :/
|
||||
*/
|
||||
private fun validatePackageOwnership(contractAttachmentsByContract: Map<ContractClassName, ContractAttachment>) {
|
||||
// This should never happen once we have network parameters in the transaction.
|
||||
if (networkParameters == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val contractsAndOwners = allStates.mapNotNull { transactionState ->
|
||||
val contractClassName = transactionState.contract
|
||||
networkParameters.getOwnerOf(contractClassName)?.let { contractClassName to it }
|
||||
@ -243,7 +251,6 @@ private constructor(
|
||||
|
||||
if (state.constraint is SignatureAttachmentConstraint)
|
||||
checkMinimumPlatformVersion(networkParameters?.minimumPlatformVersion ?: 1, 4, "Signature constraints")
|
||||
|
||||
if (!state.constraint.isSatisfiedBy(constraintAttachment)) {
|
||||
throw TransactionVerificationException.ContractConstraintRejection(id, state.contract)
|
||||
}
|
||||
@ -806,11 +813,8 @@ private constructor(
|
||||
|)""".trimMargin()
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Stuff that we can't remove and so is deprecated instead
|
||||
//
|
||||
|
||||
@Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.")
|
||||
constructor(
|
||||
inputs: List<StateAndRef<ContractState>>,
|
||||
@ -834,7 +838,7 @@ private constructor(
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?,
|
||||
privacySalt: PrivacySalt,
|
||||
networkParameters: NetworkParameters?
|
||||
networkParameters: NetworkParameters
|
||||
) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, emptyList())
|
||||
|
||||
@Deprecated("LedgerTransactions should not be created directly, use WireTransaction.toLedgerTransaction instead.")
|
||||
|
@ -48,6 +48,12 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
||||
timeWindows.firstOrNull()
|
||||
}
|
||||
|
||||
override val networkParametersHash: SecureHash? = let {
|
||||
val parametersHashes = deserialiseComponentGroup(componentGroups, SecureHash::class, PARAMETERS_GROUP)
|
||||
check(parametersHashes.size <= 1) { "Invalid Transaction. More than 1 network parameters hash detected." }
|
||||
parametersHashes.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the component groups that are present in the transaction, excluding the privacySalt,
|
||||
* in the following order (which is the same with the order in [ComponentGroupEnum]:
|
||||
@ -58,12 +64,14 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
||||
* - The notary [Party], if present (list with one element)
|
||||
* - The time-window of the transaction, if present (list with one element)
|
||||
* - list of each reference input that is present
|
||||
* - network parameters hash if present
|
||||
*/
|
||||
val availableComponentGroups: List<List<Any>>
|
||||
get() {
|
||||
val result = mutableListOf(inputs, outputs, commands, attachments, references)
|
||||
notary?.let { result += listOf(it) }
|
||||
timeWindow?.let { result += listOf(it) }
|
||||
networkParametersHash?.let { result += listOf(it) }
|
||||
return result
|
||||
}
|
||||
}
|
||||
@ -149,7 +157,9 @@ class FilteredTransaction internal constructor(
|
||||
if (wtx.timeWindow != null) filter(wtx.timeWindow, TIMEWINDOW_GROUP.ordinal, 0)
|
||||
// Note that because [inputs] and [references] share the same type [StateRef], we use a wrapper for references [ReferenceStateRef],
|
||||
// when filtering. Thus, to filter-in all [references] based on type, one should use the wrapper type [ReferenceStateRef] and not [StateRef].
|
||||
// Similar situation is for network parameters hash and attachments, one should use wrapper [NetworkParametersHash] and not [SecureHash].
|
||||
wtx.references.forEachIndexed { internalIndex, it -> filter(ReferenceStateRef(it), REFERENCES_GROUP.ordinal, internalIndex) }
|
||||
wtx.networkParametersHash?.let { filter(NetworkParametersHash(it), PARAMETERS_GROUP.ordinal, 0) }
|
||||
// It is highlighted that because there is no a signers property in TraversableTransaction,
|
||||
// one cannot specifically filter them in or out.
|
||||
// The above is very important to ensure someone won't filter out the signers component group if at least one
|
||||
@ -351,3 +361,8 @@ class FilteredTransactionVerificationException(val id: SecureHash, val reason: S
|
||||
@KeepForDJVM
|
||||
@CordaSerializable
|
||||
data class ReferenceStateRef(val stateRef: StateRef)
|
||||
|
||||
/** Wrapper over [SecureHash] to be used when filtering network parameters hash. */
|
||||
@KeepForDJVM
|
||||
@CordaSerializable
|
||||
data class NetworkParametersHash(val hash: SecureHash)
|
@ -8,6 +8,7 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -37,9 +38,16 @@ data class NotaryChangeWireTransaction(
|
||||
override val inputs: List<StateRef> = serializedComponents[INPUTS.ordinal].deserialize()
|
||||
override val references: List<StateRef> = emptyList()
|
||||
override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize()
|
||||
|
||||
/** Identity of the notary service to reassign the states to.*/
|
||||
val newNotary: Party = serializedComponents[NEW_NOTARY.ordinal].deserialize()
|
||||
|
||||
override val networkParametersHash: SecureHash? by lazy {
|
||||
if (serializedComponents.size >= PARAMETERS_HASH.ordinal + 1) {
|
||||
serializedComponents[PARAMETERS_HASH.ordinal].deserialize<SecureHash>()
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* This transaction does not contain any output states, outputs can be obtained by resolving a
|
||||
* [NotaryChangeLedgerTransaction] and applying the notary modification to inputs.
|
||||
@ -66,11 +74,14 @@ data class NotaryChangeWireTransaction(
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
|
||||
/** Resolves input states and network parameters and builds a [NotaryChangeLedgerTransaction]. */
|
||||
@DeleteForDJVM
|
||||
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
|
||||
val resolvedInputs = services.loadStates(inputs.toSet()).toList()
|
||||
return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs)
|
||||
val hashToResolve = networkParametersHash ?: services.networkParametersStorage.defaultHash
|
||||
val resolvedNetworkParameters = services.networkParametersStorage.lookup(hashToResolve)
|
||||
?: throw TransactionResolutionException(id)
|
||||
return NotaryChangeLedgerTransaction.create(resolvedInputs, notary, newNotary, id, sigs, resolvedNetworkParameters)
|
||||
}
|
||||
|
||||
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
|
||||
@ -92,7 +103,7 @@ data class NotaryChangeWireTransaction(
|
||||
}
|
||||
|
||||
enum class Component {
|
||||
INPUTS, NOTARY, NEW_NOTARY
|
||||
INPUTS, NOTARY, NEW_NOTARY, PARAMETERS_HASH
|
||||
}
|
||||
|
||||
@Deprecated("Required only for backwards compatibility purposes. This type of transaction should not be constructed outside Corda code.", ReplaceWith("NotaryChangeTransactionBuilder"), DeprecationLevel.WARNING)
|
||||
@ -105,13 +116,29 @@ data class NotaryChangeWireTransaction(
|
||||
* needed for signature verification.
|
||||
*/
|
||||
@KeepForDJVM
|
||||
data class NotaryChangeLedgerTransaction(
|
||||
class NotaryChangeLedgerTransaction
|
||||
private constructor(
|
||||
override val inputs: List<StateAndRef<ContractState>>,
|
||||
override val notary: Party,
|
||||
val newNotary: Party,
|
||||
override val id: SecureHash,
|
||||
override val sigs: List<TransactionSignature>
|
||||
override val sigs: List<TransactionSignature>,
|
||||
// TODO Network parameters should never be null on NotaryChangeLedgerTransaction, this is left only because of deprecated constructors. We should decide to
|
||||
// get rid of them.
|
||||
override val networkParameters: NetworkParameters?
|
||||
) : FullTransaction(), TransactionWithSignatures {
|
||||
companion object {
|
||||
@CordaInternal
|
||||
internal fun create(inputs: List<StateAndRef<ContractState>>,
|
||||
notary: Party,
|
||||
newNotary: Party,
|
||||
id: SecureHash,
|
||||
sigs: List<TransactionSignature>,
|
||||
networkParameters: NetworkParameters): NotaryChangeLedgerTransaction {
|
||||
return NotaryChangeLedgerTransaction(inputs, notary, newNotary, id, sigs, networkParameters)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
checkEncumbrances()
|
||||
}
|
||||
@ -157,4 +184,54 @@ data class NotaryChangeLedgerTransaction(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun component1(): List<StateAndRef<ContractState>> = inputs
|
||||
operator fun component2(): Party = notary
|
||||
operator fun component3(): Party = newNotary
|
||||
operator fun component4(): SecureHash = id
|
||||
operator fun component5(): List<TransactionSignature> = sigs
|
||||
operator fun component6(): NetworkParameters? = networkParameters
|
||||
|
||||
override fun equals(other: Any?): Boolean = this === other || other is NotaryChangeLedgerTransaction && this.id == other.id
|
||||
|
||||
override fun hashCode(): Int = id.hashCode()
|
||||
|
||||
override fun toString(): String {
|
||||
return """NotaryChangeLedgerTransaction(
|
||||
| id=$id
|
||||
| inputs=$inputs
|
||||
| notary=$notary
|
||||
| newNotary=$newNotary
|
||||
| sigs=$sigs
|
||||
| networkParameters=$networkParameters
|
||||
|)""".trimMargin()
|
||||
}
|
||||
|
||||
// Things that we can't remove after `data class` removal from this class, so it is deprecated instead.
|
||||
//
|
||||
@Deprecated("NotaryChangeLedgerTransaction should not be created directly, use NotaryChangeWireTransaction.resolve instead.")
|
||||
constructor(
|
||||
inputs: List<StateAndRef<ContractState>>,
|
||||
notary: Party,
|
||||
newNotary: Party,
|
||||
id: SecureHash,
|
||||
sigs: List<TransactionSignature>
|
||||
) : this(inputs, notary, newNotary, id, sigs, null)
|
||||
|
||||
@Deprecated("NotaryChangeLedgerTransaction should not be created directly, use NotaryChangeWireTransaction.resolve instead.")
|
||||
fun copy(inputs: List<StateAndRef<ContractState>> = this.inputs,
|
||||
notary: Party = this.notary,
|
||||
newNotary: Party = this.newNotary,
|
||||
id: SecureHash = this.id,
|
||||
sigs: List<TransactionSignature> = this.sigs
|
||||
): NotaryChangeLedgerTransaction {
|
||||
return NotaryChangeLedgerTransaction(
|
||||
inputs,
|
||||
notary,
|
||||
newNotary,
|
||||
id,
|
||||
sigs,
|
||||
this.networkParameters
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,8 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
val references: List<StateRef> get() = coreTransaction.references
|
||||
/** Helper to access the notary of the contained transaction. */
|
||||
val notary: Party? get() = coreTransaction.notary
|
||||
/** Helper to access the network parameters hash for the contained transaction. */
|
||||
val networkParametersHash: SecureHash? get() = coreTransaction.networkParametersHash
|
||||
|
||||
override val requiredSigningKeys: Set<PublicKey> get() = tx.requiredSigningKeys
|
||||
|
||||
|
@ -130,8 +130,8 @@ open class TransactionBuilder @JvmOverloads constructor(
|
||||
(allContractAttachments + attachments).toSortedSet().toList(), // Sort the attachments to ensure transaction builds are stable.
|
||||
notary,
|
||||
window,
|
||||
referenceStates
|
||||
),
|
||||
referenceStates,
|
||||
services.networkParametersStorage.currentHash),
|
||||
privacySalt
|
||||
)
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?,
|
||||
privacySalt: PrivacySalt = PrivacySalt()
|
||||
) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList()), privacySalt)
|
||||
) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null), privacySalt)
|
||||
|
||||
init {
|
||||
check(componentGroups.all { it.components.isNotEmpty() }) { "Empty component groups are not allowed" }
|
||||
@ -106,7 +106,10 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
resolveIdentity = { services.identityService.partyFromKey(it) },
|
||||
resolveAttachment = { services.attachments.openAttachment(it) },
|
||||
resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) },
|
||||
networkParameters = services.networkParameters
|
||||
resolveParameters = {
|
||||
val hashToResolve = it ?: services.networkParametersStorage.defaultHash
|
||||
services.networkParametersStorage.lookup(hashToResolve)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -119,21 +122,24 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
*/
|
||||
@Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead")
|
||||
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||
@JvmOverloads
|
||||
fun toLedgerTransaction(
|
||||
resolveIdentity: (PublicKey) -> Party?,
|
||||
resolveAttachment: (SecureHash) -> Attachment?,
|
||||
resolveStateRef: (StateRef) -> TransactionState<*>?,
|
||||
@Suppress("UNUSED_PARAMETER") resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
|
||||
@Suppress("UNUSED_PARAMETER") resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?,
|
||||
resolveParameters: (SecureHash?) -> NetworkParameters? = { null } // TODO This { null } is left here only because of API stability. It doesn't make much sense anymore as it will fail on transaction verification.
|
||||
): LedgerTransaction {
|
||||
// This reverts to serializing the resolved transaction state.
|
||||
return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, { stateRef -> resolveStateRef(stateRef)?.serialize() }, null)
|
||||
return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, { stateRef -> resolveStateRef(stateRef)?.serialize() }, resolveParameters)
|
||||
}
|
||||
|
||||
|
||||
private fun toLedgerTransactionInternal(
|
||||
resolveIdentity: (PublicKey) -> Party?,
|
||||
resolveAttachment: (SecureHash) -> Attachment?,
|
||||
resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
|
||||
networkParameters: NetworkParameters?
|
||||
resolveParameters: (SecureHash?) -> NetworkParameters?
|
||||
): LedgerTransaction {
|
||||
// Look up public keys to authenticated identities.
|
||||
val authenticatedCommands = commands.lazyMapped { cmd, _ ->
|
||||
@ -153,6 +159,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
|
||||
val resolvedAttachments = attachments.lazyMapped { att, _ -> resolveAttachment(att) ?: throw AttachmentResolutionException(att) }
|
||||
|
||||
val resolvedNetworkParameters = resolveParameters(networkParametersHash) ?: throw TransactionResolutionException(id)
|
||||
|
||||
val ltx = LedgerTransaction.create(
|
||||
resolvedInputs,
|
||||
outputs,
|
||||
@ -162,14 +170,14 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
notary,
|
||||
timeWindow,
|
||||
privacySalt,
|
||||
networkParameters,
|
||||
resolvedNetworkParameters,
|
||||
resolvedReferences,
|
||||
componentGroups,
|
||||
serializedResolvedInputs,
|
||||
serializedResolvedReferences
|
||||
)
|
||||
|
||||
checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: DEFAULT_MAX_TX_SIZE, serializedResolvedInputs, serializedResolvedReferences)
|
||||
checkTransactionSize(ltx, resolvedNetworkParameters.maxTransactionSize, serializedResolvedInputs, serializedResolvedReferences)
|
||||
|
||||
return ltx
|
||||
}
|
||||
@ -286,8 +294,6 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_MAX_TX_SIZE = 10485760
|
||||
|
||||
@CordaInternal
|
||||
@Deprecated("Do not use, this is internal API")
|
||||
fun createComponentGroups(inputs: List<StateRef>,
|
||||
@ -296,7 +302,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
attachments: List<SecureHash>,
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?): List<ComponentGroup> {
|
||||
return createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList())
|
||||
return createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -346,6 +352,9 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
val emoji = Emoji.paperclip
|
||||
buf.appendln("${emoji}ATTACHMENT: $attachment")
|
||||
}
|
||||
if (networkParametersHash != null) {
|
||||
buf.appendln("PARAMETERS HASH: $networkParametersHash")
|
||||
}
|
||||
return buf.toString()
|
||||
}
|
||||
|
||||
|
@ -162,6 +162,7 @@ class PartialMerkleTreeTest {
|
||||
assertEquals(1, ftx.commands.size)
|
||||
assertNull(ftx.notary)
|
||||
assertNotNull(ftx.timeWindow)
|
||||
assertNull(ftx.networkParametersHash)
|
||||
ftx.verify()
|
||||
}
|
||||
|
||||
@ -186,6 +187,7 @@ class PartialMerkleTreeTest {
|
||||
assertTrue(ftxNothing.outputs.isEmpty())
|
||||
assertNull(ftxNothing.timeWindow)
|
||||
assertTrue(ftxNothing.availableComponentGroups.flatten().isEmpty())
|
||||
assertNull(ftxNothing.networkParametersHash)
|
||||
ftxNothing.verify() // We allow empty ftx transactions (eg from a timestamp authority that blindly signs).
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import org.junit.Rule
|
||||
@ -31,6 +32,7 @@ class TopologicalSortTest {
|
||||
override val outputs: List<TransactionState<ContractState>> = (1..numberOfOutputs).map {
|
||||
TransactionState(DummyState(), "", notary)
|
||||
}
|
||||
override val networkParametersHash: SecureHash? = testNetworkParameters().serialize().hash
|
||||
}
|
||||
|
||||
class DummyState : ContractState {
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.crypto.*
|
||||
import net.corda.core.internal.createComponentGroups
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.core.*
|
||||
@ -38,6 +39,7 @@ class CompatibleTransactionTests {
|
||||
private val notary = DUMMY_NOTARY
|
||||
private val timeWindow = TimeWindow.fromOnly(Instant.now())
|
||||
private val privacySalt: PrivacySalt = PrivacySalt()
|
||||
private val paramsHash = SecureHash.randomSHA256()
|
||||
|
||||
private val inputGroup by lazy { ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() }) }
|
||||
private val outputGroup by lazy { ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() }) }
|
||||
@ -46,6 +48,7 @@ class CompatibleTransactionTests {
|
||||
private val notaryGroup by lazy { ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize())) }
|
||||
private val timeWindowGroup by lazy { ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize())) }
|
||||
private val signersGroup by lazy { ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers.serialize() }) }
|
||||
private val networkParamsGroup by lazy { ComponentGroup(PARAMETERS_GROUP.ordinal, listOf(paramsHash.serialize())) }
|
||||
|
||||
private val newUnknownComponentGroup = ComponentGroup(100, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8))))
|
||||
private val newUnknownComponentEmptyGroup = ComponentGroup(101, emptyList())
|
||||
@ -125,7 +128,7 @@ class CompatibleTransactionTests {
|
||||
|
||||
@Test
|
||||
fun `WireTransaction constructors and compatibility`() {
|
||||
val groups = createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList())
|
||||
val groups = createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null)
|
||||
val wireTransactionOldConstructor = WireTransaction(groups, privacySalt)
|
||||
assertEquals(wireTransactionA, wireTransactionOldConstructor)
|
||||
|
||||
@ -211,6 +214,7 @@ class CompatibleTransactionTests {
|
||||
ftxAll.checkAllComponentsVisible(NOTARY_GROUP)
|
||||
ftxAll.checkAllComponentsVisible(TIMEWINDOW_GROUP)
|
||||
ftxAll.checkAllComponentsVisible(SIGNERS_GROUP)
|
||||
ftxAll.checkAllComponentsVisible(PARAMETERS_GROUP)
|
||||
|
||||
// Filter inputs only.
|
||||
fun filtering(elem: Any): Boolean {
|
||||
@ -261,6 +265,8 @@ class CompatibleTransactionTests {
|
||||
assertEquals(3, ftxCompatible.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.components.size)
|
||||
assertEquals(3, ftxCompatible.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.nonces.size)
|
||||
assertNotNull(ftxCompatible.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.partialMerkleTree)
|
||||
assertNull(wireTransactionCompatibleA.networkParametersHash)
|
||||
assertNull(ftxCompatible.networkParametersHash)
|
||||
|
||||
// Now, let's allow everything, including the new component type that we cannot process.
|
||||
val ftxCompatibleAll = wireTransactionCompatibleA.buildFilteredTransaction(Predicate { true }) // All filtered, including the unknown component.
|
||||
@ -547,5 +553,34 @@ class CompatibleTransactionTests {
|
||||
// Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id).
|
||||
assertFailsWith<ComponentVisibilityException> { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parameters hash visibility`() {
|
||||
fun paramsFilter(elem: Any): Boolean = elem is NetworkParametersHash && elem.hash == paramsHash
|
||||
fun attachmentFilter(elem: Any): Boolean = elem is SecureHash && elem == paramsHash
|
||||
val attachments = ComponentGroup(ATTACHMENTS_GROUP.ordinal, listOf(paramsHash.serialize())) // Same hash as network parameters
|
||||
val componentGroups = listOf(
|
||||
inputGroup,
|
||||
outputGroup,
|
||||
attachments,
|
||||
commandGroup,
|
||||
notaryGroup,
|
||||
timeWindowGroup,
|
||||
signersGroup,
|
||||
networkParamsGroup
|
||||
)
|
||||
val wtx = WireTransaction(componentGroups, privacySalt)
|
||||
val ftx1 = wtx.buildFilteredTransaction(Predicate(::paramsFilter)) // Filter only network parameters hash.
|
||||
ftx1.verify()
|
||||
assertEquals(wtx.id, ftx1.id)
|
||||
ftx1.checkAllComponentsVisible(PARAMETERS_GROUP)
|
||||
assertFailsWith<ComponentVisibilityException> { ftx1.checkAllComponentsVisible(ATTACHMENTS_GROUP) }
|
||||
// Filter only attachment.
|
||||
val ftx2 = wtx.buildFilteredTransaction(Predicate(::attachmentFilter))
|
||||
ftx2.verify()
|
||||
assertEquals(wtx.id, ftx2.id)
|
||||
ftx2.checkAllComponentsVisible(ATTACHMENTS_GROUP)
|
||||
assertFailsWith<ComponentVisibilityException> { ftx2.checkAllComponentsVisible(PARAMETERS_GROUP) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.ZoneVersionTooLowException
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.NetworkParametersStorage
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
@ -35,13 +37,17 @@ class TransactionBuilderTest {
|
||||
private val services = rigorousMock<ServicesForResolution>()
|
||||
private val contractAttachmentId = SecureHash.randomSHA256()
|
||||
private val attachments = rigorousMock<AttachmentStorage>()
|
||||
private val networkParametersStorage = rigorousMock<NetworkParametersStorage>()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val cordappProvider = rigorousMock<CordappProvider>()
|
||||
val networkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION)
|
||||
doReturn(networkParametersStorage).whenever(services).networkParametersStorage
|
||||
doReturn(networkParameters.serialize().hash).whenever(networkParametersStorage).currentHash
|
||||
doReturn(cordappProvider).whenever(services).cordappProvider
|
||||
doReturn(contractAttachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
|
||||
doReturn(testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION)).whenever(services).networkParameters
|
||||
doReturn(networkParameters).whenever(services).networkParameters
|
||||
|
||||
val attachmentStorage = rigorousMock<AttachmentStorage>()
|
||||
doReturn(attachmentStorage).whenever(services).attachments
|
||||
@ -67,6 +73,7 @@ class TransactionBuilderTest {
|
||||
val wtx = builder.toWireTransaction(services)
|
||||
assertThat(wtx.outputs).containsOnly(outputState)
|
||||
assertThat(wtx.commands).containsOnly(Command(DummyCommandData, notary.owningKey))
|
||||
assertThat(wtx.networkParametersHash).isEqualTo(networkParametersStorage.currentHash)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -176,7 +176,7 @@ class TransactionTests {
|
||||
notary,
|
||||
timeWindow,
|
||||
privacySalt,
|
||||
null,
|
||||
testNetworkParameters(),
|
||||
emptyList()
|
||||
)
|
||||
|
||||
|
@ -15,7 +15,7 @@ import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.util.Collections.singleton
|
||||
|
||||
// When scanning of the CorDapp Jar is performed without "corda-core.jar" being the in the classpath, there is no way to appreciate
|
||||
// When scanning of the CorDapp Jar is performed without "corda-core.jar" being in the classpath, there is no way to appreciate
|
||||
// relationships between those interfaces, therefore they have to be listed explicitly.
|
||||
val coreContractClasses = setOf(Contract::class, UpgradedContractWithLegacyConstraint::class, UpgradedContract::class)
|
||||
|
||||
|
@ -4,13 +4,12 @@ import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.AbstractAttachment
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.NetworkParametersStorage
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
@ -67,19 +66,18 @@ class AttachmentsClassLoaderStaticContractTests {
|
||||
}
|
||||
}
|
||||
|
||||
private val unsignedAttachment = object : AbstractAttachment({ byteArrayOf() }) {
|
||||
override val id: SecureHash get() = throw UnsupportedOperationException()
|
||||
private val networkParameters = testNetworkParameters()
|
||||
|
||||
private val networkParametersStorage get() = rigorousMock<NetworkParametersStorage>().also {
|
||||
doReturn(networkParameters.serialize().hash).whenever(it).currentHash
|
||||
}
|
||||
|
||||
private val attachments = rigorousMock<AttachmentStorage>().also {
|
||||
doReturn(unsignedAttachment).whenever(it).openAttachment(any())
|
||||
}
|
||||
|
||||
private val serviceHub = rigorousMock<ServicesForResolution>().also {
|
||||
private val serviceHub get() = rigorousMock<ServicesForResolution>().also {
|
||||
val cordappProviderImpl = CordappProviderImpl(cordappLoaderForPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage())
|
||||
cordappProviderImpl.start(testNetworkParameters().whitelistedContractImplementations)
|
||||
doReturn(cordappProviderImpl).whenever(it).cordappProvider
|
||||
doReturn(testNetworkParameters()).whenever(it).networkParameters
|
||||
doReturn(networkParametersStorage).whenever(it).networkParametersStorage
|
||||
doReturn(networkParameters).whenever(it).networkParameters
|
||||
val attachmentStorage = rigorousMock<AttachmentStorage>()
|
||||
doReturn(attachmentStorage).whenever(it).attachments
|
||||
val attachment = rigorousMock<ContractAttachment>()
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.CordaRuntimeException
|
||||
@ -13,7 +14,9 @@ import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.NetworkParametersStorage
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.VersionInfo
|
||||
@ -63,6 +66,7 @@ class AttachmentLoadingTests {
|
||||
}
|
||||
|
||||
private val services = object : ServicesForResolution {
|
||||
private val testNetworkParameters = testNetworkParameters()
|
||||
override fun loadState(stateRef: StateRef): TransactionState<*> = throw NotImplementedError()
|
||||
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = throw NotImplementedError()
|
||||
override val identityService = rigorousMock<IdentityService>().apply {
|
||||
@ -70,7 +74,11 @@ class AttachmentLoadingTests {
|
||||
}
|
||||
override val attachments: AttachmentStorage get() = this@AttachmentLoadingTests.attachments
|
||||
override val cordappProvider: CordappProvider get() = this@AttachmentLoadingTests.provider
|
||||
override val networkParameters: NetworkParameters = testNetworkParameters()
|
||||
override val networkParameters: NetworkParameters = testNetworkParameters
|
||||
override val networkParametersStorage: NetworkParametersStorage get() = rigorousMock<NetworkParametersStorage>().apply {
|
||||
doReturn(testNetworkParameters.serialize().hash).whenever(this).currentHash
|
||||
doReturn(testNetworkParameters).whenever(this).lookup(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -168,16 +168,18 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
|
||||
val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize()
|
||||
val cryptoService = configuration.makeCryptoService()
|
||||
val networkParametersStorage = DBNetworkParametersStorage(cacheFactory, database, networkMapClient).tokenize()
|
||||
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
|
||||
@Suppress("LeakingThis")
|
||||
val keyManagementService = makeKeyManagementService(identityService).tokenize()
|
||||
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, transactionStorage).also {
|
||||
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage).also {
|
||||
attachments.servicesForResolution = it
|
||||
}
|
||||
@Suppress("LeakingThis")
|
||||
val vaultService = makeVaultService(keyManagementService, servicesForResolution, database).tokenize()
|
||||
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database, cacheFactory)
|
||||
val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader)
|
||||
// TODO Cancelling parameters updates - if we do that, how we ensure that no one uses cancelled parameters in the transactions?
|
||||
val networkMapUpdater = NetworkMapUpdater(
|
||||
networkMapCache,
|
||||
NodeInfoWatcher(
|
||||
@ -188,7 +190,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
),
|
||||
networkMapClient,
|
||||
configuration.baseDirectory,
|
||||
configuration.extraNetworkMapKeys
|
||||
configuration.extraNetworkMapKeys,
|
||||
networkParametersStorage
|
||||
).closeOnStop()
|
||||
@Suppress("LeakingThis")
|
||||
val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize()
|
||||
@ -326,7 +329,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
check(netParams.minimumPlatformVersion <= versionInfo.platformVersion) {
|
||||
"Node's platform version is lower than network's required minimumPlatformVersion"
|
||||
}
|
||||
servicesForResolution.start(netParams)
|
||||
networkMapCache.start(netParams.notaries)
|
||||
|
||||
startDatabase()
|
||||
@ -354,6 +356,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
|
||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||
return database.transaction {
|
||||
networkParametersStorage.start(signedNetParams, trustRoot)
|
||||
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
||||
attachments.start()
|
||||
cordappProvider.start(netParams.whitelistedContractImplementations)
|
||||
@ -984,6 +987,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
override val configuration: NodeConfiguration get() = this@AbstractNode.configuration
|
||||
override val networkMapUpdater: NetworkMapUpdater get() = this@AbstractNode.networkMapUpdater
|
||||
override val cacheFactory: NamedCacheFactory get() = this@AbstractNode.cacheFactory
|
||||
override val networkParametersStorage: NetworkParametersStorage get() = this@AbstractNode.networkParametersStorage
|
||||
|
||||
private lateinit var _myInfo: NodeInfo
|
||||
override val myInfo: NodeInfo get() = _myInfo
|
||||
|
@ -0,0 +1,159 @@
|
||||
package net.corda.node.internal
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.DigitalSignatureWithCert
|
||||
import net.corda.core.internal.NamedCacheFactory
|
||||
import net.corda.core.internal.SignedDataWithCert
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.services.NetworkParametersStorage
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.node.services.network.NetworkMapClient
|
||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.network.SignedNetworkParameters
|
||||
import net.corda.nodeapi.internal.network.verifiedNetworkParametersCert
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import org.apache.commons.lang.ArrayUtils
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.persistence.*
|
||||
|
||||
interface NetworkParametersStorageInternal : NetworkParametersStorage {
|
||||
/**
|
||||
* Return parameters epoch for the given parameters hash. Null if there are no parameters for this hash in the storage and we are unable to
|
||||
* get them from network map.
|
||||
*/
|
||||
fun getEpochFromHash(hash: SecureHash): Int?
|
||||
|
||||
/**
|
||||
* Save signed network parameters data. Internally network parameters bytes should be stored with the signature.
|
||||
* It's because of ability of older nodes to function in network where parameters were extended with new fields.
|
||||
* Hash should always be calculated over the serialized bytes.
|
||||
*/
|
||||
fun saveParameters(signedNetworkParameters: SignedDataWithCert<NetworkParameters>)
|
||||
}
|
||||
|
||||
class DBNetworkParametersStorage(
|
||||
cacheFactory: NamedCacheFactory,
|
||||
private val database: CordaPersistence,
|
||||
// TODO It's very inefficient solution (at least at the beginning when node joins without historical data)
|
||||
// We could have historic parameters endpoint or always add parameters as an attachment to the transaction.
|
||||
private val networkMapClient: NetworkMapClient?
|
||||
) : NetworkParametersStorageInternal, SingletonSerializeAsToken() {
|
||||
private lateinit var trustRoot: X509Certificate
|
||||
|
||||
companion object {
|
||||
private val log = contextLogger()
|
||||
|
||||
fun createParametersMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<SecureHash, SignedDataWithCert<NetworkParameters>, PersistentNetworkParameters, String> {
|
||||
return AppendOnlyPersistentMap(
|
||||
cacheFactory = cacheFactory,
|
||||
name = "NodeParametersStorage_networkParametersByHash",
|
||||
toPersistentEntityKey = { it.toString() },
|
||||
fromPersistentEntity = {
|
||||
Pair(
|
||||
SecureHash.parse(it.hash),
|
||||
it.signedNetworkParameters
|
||||
)
|
||||
},
|
||||
toPersistentEntity = { key: SecureHash, value: SignedDataWithCert<NetworkParameters> ->
|
||||
PersistentNetworkParameters(key.toString(), value.verified().epoch, value.raw.bytes, value.sig.bytes, value.sig.by.encoded,
|
||||
X509Utilities.buildCertPath(value.sig.parentCertsChain).encoded)
|
||||
},
|
||||
persistentEntityClass = PersistentNetworkParameters::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun start(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoot: X509Certificate) {
|
||||
this.trustRoot = trustRoot
|
||||
saveParameters(currentSignedParameters)
|
||||
_currentHash = currentSignedParameters.raw.hash
|
||||
}
|
||||
|
||||
private lateinit var _currentHash: SecureHash
|
||||
override val currentHash: SecureHash get() = _currentHash
|
||||
// TODO Have network map serve special "starting" parameters as parameters for resolution for older transactions?
|
||||
override val defaultHash: SecureHash get() = currentHash
|
||||
|
||||
private val hashToParameters = createParametersMap(cacheFactory)
|
||||
|
||||
override fun lookup(hash: SecureHash): NetworkParameters? {
|
||||
return database.transaction { hashToParameters[hash]?.raw?.deserialize() } ?: tryDownloadUnknownParameters(hash)
|
||||
}
|
||||
|
||||
override fun getEpochFromHash(hash: SecureHash): Int? = lookup(hash)?.epoch
|
||||
|
||||
override fun saveParameters(signedNetworkParameters: SignedNetworkParameters) {
|
||||
log.trace { "Saving new network parameters to network parameters storage." }
|
||||
val networkParameters = signedNetworkParameters.verified()
|
||||
val hash = signedNetworkParameters.raw.hash
|
||||
log.trace { "Parameters to save $networkParameters with hash $hash" }
|
||||
database.transaction {
|
||||
hashToParameters.addWithDuplicatesAllowed(hash, signedNetworkParameters)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO For the future we could get them also as signed (by network operator) attachments on transactions.
|
||||
private fun tryDownloadUnknownParameters(parametersHash: SecureHash): NetworkParameters? {
|
||||
return if (networkMapClient != null) {
|
||||
try {
|
||||
val signedParams = networkMapClient.getNetworkParameters(parametersHash)
|
||||
val networkParameters = signedParams.verifiedNetworkParametersCert(trustRoot)
|
||||
saveParameters(signedParams)
|
||||
networkParameters
|
||||
} catch (e: Exception) {
|
||||
log.warn("Failed to download historical network parameters with hash $parametersHash", e)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
log.warn("Tried to download historical network parameters with hash $parametersHash, but network map url isn't configured")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Entity
|
||||
@Table(name = "${NODE_DATABASE_PREFIX}network_parameters")
|
||||
class PersistentNetworkParameters(
|
||||
@Id
|
||||
@Column(name = "hash", length = MAX_HASH_HEX_SIZE, nullable = false)
|
||||
val hash: String = "",
|
||||
|
||||
@Column(name = "epoch", nullable = false)
|
||||
val epoch: Int = 0,
|
||||
|
||||
// Stored as serialized bytes because network parameters structure evolves over time.
|
||||
@Lob
|
||||
@Column(name = "parameters_bytes", nullable = false)
|
||||
val networkParametersBytes: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY,
|
||||
|
||||
@Lob
|
||||
@Column(name = "signature_bytes", nullable = false)
|
||||
private val signature: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY,
|
||||
|
||||
// First certificate in the certificate chain.
|
||||
@Lob
|
||||
@Column(name = "cert", nullable = false)
|
||||
private val certificate: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY,
|
||||
|
||||
// Parent certificate path (the first one is stored separately), so node is agnostic to certificate hierarchy.
|
||||
@Lob
|
||||
@Column(name = "parent_cert_path", nullable = false)
|
||||
private val certPath: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY
|
||||
) {
|
||||
val networkParameters: NetworkParameters get() = networkParametersBytes.deserialize()
|
||||
val signedNetworkParameters: SignedDataWithCert<NetworkParameters>
|
||||
get() {
|
||||
val certChain = X509CertificateFactory().delegate.generateCertPath(certPath.inputStream())
|
||||
.certificates.map { it as X509Certificate }
|
||||
val signWithCert = DigitalSignatureWithCert(X509CertificateFactory().generateCertificate(certificate.inputStream()), certChain, signature)
|
||||
return SignedDataWithCert(SerializedBytes(networkParametersBytes), signWithCert)
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,10 @@ import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.node.services.network.NetworkMapClient
|
||||
import net.corda.nodeapi.internal.network.*
|
||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
|
||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
|
||||
import net.corda.nodeapi.internal.network.SignedNetworkParameters
|
||||
import net.corda.nodeapi.internal.network.verifiedNetworkParametersCert
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.security.cert.X509Certificate
|
||||
@ -23,7 +26,7 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
|
||||
class NetworkMapNotConfigured : Error("Node hasn't been configured to connect to a network map from which to get the network parameters.")
|
||||
class OldParamsAndUpdate : Error(
|
||||
"Both network parameters and network parameters update files don't match" +
|
||||
"parameters advertised by network map. Please update node to use correct network parameters file."
|
||||
"parameters advertised by network map. Please update node to use correct network parameters file."
|
||||
)
|
||||
class OldParams(previousParametersHash: SecureHash, advertisedParametersHash: SecureHash) : Error(
|
||||
"Node uses parameters with hash: $previousParametersHash but network map is advertising: " +
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.NetworkParametersStorage
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.TransactionStorage
|
||||
|
||||
@ -12,14 +13,11 @@ data class ServicesForResolutionImpl(
|
||||
override val identityService: IdentityService,
|
||||
override val attachments: AttachmentStorage,
|
||||
override val cordappProvider: CordappProvider,
|
||||
override val networkParametersStorage: NetworkParametersStorage,
|
||||
private val validatedTransactions: TransactionStorage
|
||||
) : ServicesForResolution {
|
||||
private lateinit var _networkParameters: NetworkParameters
|
||||
override val networkParameters: NetworkParameters get() = _networkParameters
|
||||
|
||||
fun start(networkParameters: NetworkParameters) {
|
||||
_networkParameters = networkParameters
|
||||
}
|
||||
override val networkParameters: NetworkParameters get() = networkParametersStorage.lookup(networkParametersStorage.currentHash) ?:
|
||||
throw IllegalArgumentException("No current parameters in network parameters storage")
|
||||
|
||||
@Throws(TransactionResolutionException::class)
|
||||
override fun loadState(stateRef: StateRef): TransactionState<*> {
|
||||
|
@ -12,6 +12,7 @@ import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.node.internal.NetworkParametersStorageInternal
|
||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||
import net.corda.node.services.config.NetworkParameterAcceptanceSettings
|
||||
import net.corda.node.utilities.NamedThreadFactory
|
||||
@ -33,7 +34,8 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
private val nodeInfoWatcher: NodeInfoWatcher,
|
||||
private val networkMapClient: NetworkMapClient?,
|
||||
private val baseDirectory: Path,
|
||||
private val extraNetworkMapKeys: List<UUID>
|
||||
private val extraNetworkMapKeys: List<UUID>,
|
||||
private val networkParametersStorage: NetworkParametersStorageInternal
|
||||
) : AutoCloseable {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
@ -209,6 +211,7 @@ The node will shutdown now.""")
|
||||
}
|
||||
val newSignedNetParams = networkMapClient.getNetworkParameters(update.newParametersHash)
|
||||
val newNetParams = newSignedNetParams.verifiedNetworkParametersCert(trustRoot)
|
||||
networkParametersStorage.saveParameters(newSignedNetParams)
|
||||
logger.info("Downloaded new network parameters: $newNetParams from the update: $update")
|
||||
newNetworkParameters = Pair(update, newSignedNetParams)
|
||||
val updateInfo = ParametersUpdateInfo(
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.contracts.LinearState
|
||||
import net.corda.core.schemas.*
|
||||
import net.corda.core.schemas.MappedSchemaValidator.crossReferencesToOtherMappedSchema
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.node.internal.DBNetworkParametersStorage
|
||||
import net.corda.node.internal.schemas.NodeInfoSchemaV1
|
||||
import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.services.api.SchemaService.SchemaOptions
|
||||
@ -43,6 +44,7 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
|
||||
PersistentIdentityService.PersistentIdentity::class.java,
|
||||
PersistentIdentityService.PersistentIdentityNames::class.java,
|
||||
ContractUpgradeServiceImpl.DBContractUpgrade::class.java,
|
||||
DBNetworkParametersStorage.PersistentNetworkParameters::class.java,
|
||||
PersistentKeyManagementService.PublicKeyHashToExternalId::class.java
|
||||
)) {
|
||||
override val migrationResource = "node-core.changelog-master"
|
||||
|
@ -28,11 +28,12 @@ class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePart
|
||||
checkAllComponentsVisible(ComponentGroupEnum.INPUTS_GROUP)
|
||||
checkAllComponentsVisible(ComponentGroupEnum.TIMEWINDOW_GROUP)
|
||||
checkAllComponentsVisible(ComponentGroupEnum.REFERENCES_GROUP)
|
||||
if(serviceHub.networkParameters.minimumPlatformVersion >= 4) checkAllComponentsVisible(ComponentGroupEnum.PARAMETERS_GROUP)
|
||||
}
|
||||
TransactionParts(tx.id, tx.inputs, tx.timeWindow, tx.notary, tx.references)
|
||||
TransactionParts(tx.id, tx.inputs, tx.timeWindow, tx.notary, tx.references, networkParametersHash = tx.networkParametersHash)
|
||||
}
|
||||
is ContractUpgradeFilteredTransaction,
|
||||
is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary)
|
||||
is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary, networkParametersHash = tx.networkParametersHash)
|
||||
else -> {
|
||||
throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," +
|
||||
"expected either ${FilteredTransaction::class.java.simpleName} or ${NotaryChangeWireTransaction::class.java.simpleName}")
|
||||
|
@ -24,7 +24,7 @@ open class ValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePa
|
||||
override fun extractParts(requestPayload: NotarisationPayload): TransactionParts {
|
||||
val stx = requestPayload.signedTransaction
|
||||
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
|
||||
return TransactionParts(stx.id, stx.inputs, timeWindow, stx.notary, stx.references)
|
||||
return TransactionParts(stx.id, stx.inputs, timeWindow, stx.notary, stx.references, stx.networkParametersHash)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,6 +57,7 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi
|
||||
name == "BFTNonValidatingNotaryService_transactions" -> caffeine.maximumSize(defaultCacheSize)
|
||||
name == "RaftUniquenessProvider_transactions" -> caffeine.maximumSize(defaultCacheSize)
|
||||
name == "BasicHSMKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)
|
||||
name == "NodeParametersStorage_networkParametersByHash" -> caffeine.maximumSize(defaultCacheSize)
|
||||
else -> throw IllegalArgumentException("Unexpected cache name $name. Did you add a new cache?")
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package net.corda.node.messaging
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
@ -34,7 +32,6 @@ import net.corda.finance.contracts.asset.CASH
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.flows.TwoPartyTradeFlow.Buyer
|
||||
import net.corda.finance.flows.TwoPartyTradeFlow.Seller
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.node.services.api.WritableTransactionStorage
|
||||
import net.corda.node.services.persistence.DBTransactionStorage
|
||||
import net.corda.node.services.persistence.checkpoints
|
||||
@ -45,9 +42,7 @@ import net.corda.testing.dsl.TestLedgerDSLInterpreter
|
||||
import net.corda.testing.dsl.TestTransactionDSLInterpreter
|
||||
import net.corda.testing.internal.LogHelper
|
||||
import net.corda.testing.internal.TEST_TX_TIME
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.internal.vault.VaultFiller
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.internal.*
|
||||
import net.corda.testing.node.ledger
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@ -71,6 +66,7 @@ import kotlin.test.assertTrue
|
||||
*
|
||||
* We assume that Alice and Bob already found each other via some market, and have agreed the details already.
|
||||
*/
|
||||
// TODO These tests need serious cleanup.
|
||||
@RunWith(Parameterized::class)
|
||||
class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
companion object {
|
||||
@ -80,7 +76,6 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
fun data(): Collection<Boolean> = listOf(true, false)
|
||||
|
||||
private val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
|
||||
private val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
|
||||
private val DUMMY_NOTARY get() = dummyNotary.party
|
||||
}
|
||||
|
||||
@ -105,17 +100,15 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
// we run in the unit test thread exclusively to speed things up, ensure deterministic results and
|
||||
// allow interruption half way through.
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true)
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val notary = mockNet.defaultNotaryIdentity
|
||||
notaryNode.services.ledger(notary) {
|
||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
val bankNode = mockNet.createPartyNode(BOC_NAME)
|
||||
val alice = aliceNode.info.singleIdentity()
|
||||
val bank = bankNode.info.singleIdentity()
|
||||
doReturn(null).whenever(ledgerIdentityService).partyFromKey(bank.owningKey)
|
||||
val bob = bobNode.info.singleIdentity()
|
||||
val notary = mockNet.defaultNotaryIdentity
|
||||
val cashIssuer = bank.ref(1)
|
||||
val cpIssuer = bank.ref(1, 2, 3)
|
||||
|
||||
@ -157,15 +150,13 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
@Test(expected = InsufficientBalanceException::class)
|
||||
fun `trade cash for commercial paper fails using soft locking`() {
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true)
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
notaryNode.services.ledger(notaryNode.info.singleIdentity()) {
|
||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
val bankNode = mockNet.createPartyNode(BOC_NAME)
|
||||
val alice = aliceNode.info.singleIdentity()
|
||||
val bank = bankNode.info.singleIdentity()
|
||||
doReturn(null).whenever(ledgerIdentityService).partyFromKey(bank.owningKey)
|
||||
val bob = bobNode.info.singleIdentity()
|
||||
val issuer = bank.ref(1)
|
||||
val notary = mockNet.defaultNotaryIdentity
|
||||
@ -215,9 +206,9 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
@Test
|
||||
fun `shutdown and restore`() {
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val notary = mockNet.defaultNotaryIdentity
|
||||
notaryNode.services.ledger(notary) {
|
||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
var bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
val bankNode = mockNet.createPartyNode(BOC_NAME)
|
||||
@ -227,10 +218,8 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
val bobAddr = bobNode.network.myAddress
|
||||
mockNet.runNetwork() // Clear network map registration messages
|
||||
|
||||
val notary = mockNet.defaultNotaryIdentity
|
||||
val alice = aliceNode.info.singleIdentity()
|
||||
val bank = bankNode.info.singleIdentity()
|
||||
doReturn(null).whenever(ledgerIdentityService).partyFromKey(bank.owningKey)
|
||||
val bob = bobNode.info.singleIdentity()
|
||||
val issuer = bank.ref(1, 2, 3)
|
||||
|
||||
@ -336,7 +325,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
val bob = bobNode.info.singleIdentity()
|
||||
val bank = bankNode.info.singleIdentity()
|
||||
val issuer = bank.ref(1, 2, 3)
|
||||
aliceNode.services.ledger(DUMMY_NOTARY) {
|
||||
aliceNode.services.ledger(notary) {
|
||||
// Insert a prospectus type attachment into the commercial paper transaction.
|
||||
val stream = ByteArrayOutputStream()
|
||||
JarOutputStream(stream).use {
|
||||
@ -440,7 +429,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
val bank: Party = bankNode.info.singleIdentity()
|
||||
val bob = bobNode.info.singleIdentity()
|
||||
val issuer = bank.ref(1, 2, 3)
|
||||
aliceNode.services.ledger(DUMMY_NOTARY) {
|
||||
aliceNode.services.ledger(notary) {
|
||||
// Insert a prospectus type attachment into the commercial paper transaction.
|
||||
val stream = ByteArrayOutputStream()
|
||||
JarOutputStream(stream).use {
|
||||
@ -508,18 +497,16 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
@Test
|
||||
fun `dependency with error on buyer side`() {
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
runWithError(ledgerIdentityService, true, false, "at least one cash input")
|
||||
mockNet.defaultNotaryNode.services.ledger(DUMMY_NOTARY) {
|
||||
runWithError(true, false, "at least one cash input")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dependency with error on seller side`() {
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
runWithError(ledgerIdentityService, false, true, "Issuances have a time-window")
|
||||
mockNet.defaultNotaryNode.services.ledger(DUMMY_NOTARY) {
|
||||
runWithError(false, true, "Issuances have a time-window")
|
||||
}
|
||||
}
|
||||
|
||||
@ -581,7 +568,6 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
data class TestTx(val notaryIdentity: Party, val price: Amount<Currency>, val anonymous: Boolean)
|
||||
|
||||
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError(
|
||||
ledgerIdentityService: IdentityServiceInternal,
|
||||
bobError: Boolean,
|
||||
aliceError: Boolean,
|
||||
expectedMessageSubstring: String
|
||||
@ -595,7 +581,6 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
val alice = aliceNode.info.singleIdentity()
|
||||
val bob = bobNode.info.singleIdentity()
|
||||
val bank = bankNode.info.singleIdentity()
|
||||
doReturn(null).whenever(ledgerIdentityService).partyFromKey(bank.owningKey)
|
||||
val issuer = bank.ref(1, 2, 3)
|
||||
|
||||
val bobsBadCash = bobNode.database.transaction {
|
||||
@ -624,7 +609,6 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun insertFakeTransactions(
|
||||
wtxToSign: List<WireTransaction>,
|
||||
node: TestStartedNode,
|
||||
@ -719,7 +703,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
notary: Party): Pair<Vault<ContractState>, List<WireTransaction>> {
|
||||
val ap = transaction(transactionBuilder = TransactionBuilder(notary = notary)) {
|
||||
output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", notary = notary,
|
||||
contractState = CommercialPaper.State(issuer, owner, amount, TEST_TX_TIME + 7.days))
|
||||
contractState = CommercialPaper.State(issuer, owner, amount, TEST_TX_TIME + 7.days))
|
||||
command(issuer.party.owningKey, CommercialPaper.Commands.Issue())
|
||||
if (!withError)
|
||||
timeWindow(time = TEST_TX_TIME)
|
||||
@ -736,7 +720,6 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
return Pair(vault, listOf(ap))
|
||||
}
|
||||
|
||||
|
||||
class RecordingTransactionStorage(
|
||||
private val database: CordaPersistence,
|
||||
private val delegate: WritableTransactionStorage
|
||||
@ -777,5 +760,4 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
data class Add(val transaction: SignedTransaction) : TxRecord
|
||||
data class Get(val id: SecureHash) : TxRecord
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.times
|
||||
import com.nhaarman.mockito_kotlin.verify
|
||||
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.SignedDataWithCert
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.services.NetworkParametersStorage
|
||||
import net.corda.node.internal.DBNetworkParametersStorage
|
||||
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
|
||||
import net.corda.testing.internal.DEV_ROOT_CA
|
||||
import net.corda.testing.internal.TestingNamedCacheFactory
|
||||
import net.corda.testing.internal.configureDatabase
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.PrintStream
|
||||
import kotlin.streams.toList
|
||||
|
||||
class DBNetworkParametersStorageTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule(true)
|
||||
|
||||
private lateinit var networkMapClient: NetworkMapClient
|
||||
private lateinit var nodeParametersStorage: NetworkParametersStorage
|
||||
private lateinit var database: CordaPersistence
|
||||
|
||||
private val certKeyPair: CertificateAndKeyPair = createDevNetworkMapCa()
|
||||
private lateinit var netParams1: SignedDataWithCert<NetworkParameters>
|
||||
private lateinit var netParams2: SignedDataWithCert<NetworkParameters>
|
||||
private lateinit var incorrectParams: SignedDataWithCert<NetworkParameters>
|
||||
private lateinit var hash1: SecureHash
|
||||
private lateinit var hash2: SecureHash
|
||||
private lateinit var hash3: SecureHash
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
netParams1 = certKeyPair.sign(testNetworkParameters(minimumPlatformVersion = 1))
|
||||
netParams2 = certKeyPair.sign(testNetworkParameters(minimumPlatformVersion = 2))
|
||||
incorrectParams = createDevNetworkMapCa(DEV_INTERMEDIATE_CA).sign(testNetworkParameters(minimumPlatformVersion = 3))
|
||||
hash1 = netParams1.raw.hash
|
||||
hash2 = netParams2.raw.hash
|
||||
hash3 = incorrectParams.raw.hash
|
||||
database = configureDatabase(
|
||||
MockServices.makeTestDataSourceProperties(),
|
||||
DatabaseConfig(),
|
||||
{ null },
|
||||
{ null }
|
||||
)
|
||||
networkMapClient = createMockNetworkMapClient()
|
||||
nodeParametersStorage = DBNetworkParametersStorage(TestingNamedCacheFactory(), database, networkMapClient).apply {
|
||||
database.transaction {
|
||||
start(netParams1, DEV_ROOT_CA.certificate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
database.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `set current parameters`() {
|
||||
assertThat(nodeParametersStorage.currentHash).isEqualTo(hash1)
|
||||
assertThat(nodeParametersStorage.lookup(hash1)).isEqualTo(netParams1.verified())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get default parameters`() {
|
||||
// TODO After implementing default endpoint on network map check it is correct, for now we set it to current.
|
||||
assertThat(nodeParametersStorage.defaultHash).isEqualTo(hash1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `download parameters from network map server`() {
|
||||
database.transaction {
|
||||
val netParams = nodeParametersStorage.lookup(hash2)
|
||||
assertThat(nodeParametersStorage.lookup(hash2)).isEqualTo(netParams)
|
||||
verify(networkMapClient, times(1)).getNetworkParameters(hash2)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `try save parameters with incorrect signature`() {
|
||||
database.transaction {
|
||||
val consoleOutput = interceptConsoleOutput {
|
||||
nodeParametersStorage.lookup(hash3)
|
||||
}
|
||||
assertThat(consoleOutput).anySatisfy {
|
||||
it.contains("Caused by: java.security.cert.CertPathValidatorException: subject/issuer name chaining check failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun interceptConsoleOutput(block: () -> Unit): List<String> {
|
||||
val oldOut = System.out
|
||||
val out = ByteOutputStream()
|
||||
System.setOut(PrintStream(out))
|
||||
block()
|
||||
System.setOut(oldOut)
|
||||
return out.bytes.inputStream().bufferedReader().lines().toList()
|
||||
}
|
||||
|
||||
private fun createMockNetworkMapClient(): NetworkMapClient {
|
||||
return mock {
|
||||
on { getNetworkParameters(any()) }.then {
|
||||
val hash = it.getArguments()[0]
|
||||
when (hash) {
|
||||
hash1 -> netParams1
|
||||
hash2 -> netParams2
|
||||
hash3 -> incorrectParams
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import net.corda.node.VersionInfo
|
||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||
import net.corda.node.services.config.NetworkParameterAcceptanceSettings
|
||||
import net.corda.core.internal.NODE_INFO_DIRECTORY
|
||||
import net.corda.node.internal.NetworkParametersStorageInternal
|
||||
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
@ -64,6 +65,7 @@ class NetworkMapUpdaterTest {
|
||||
private val networkMapCache = createMockNetworkMapCache()
|
||||
private lateinit var ourKeyPair: KeyPair
|
||||
private lateinit var ourNodeInfo: SignedNodeInfo
|
||||
private val networkParametersStorage: NetworkParametersStorageInternal = mock()
|
||||
private lateinit var server: NetworkMapServer
|
||||
private lateinit var networkMapClient: NetworkMapClient
|
||||
private lateinit var updater: NetworkMapUpdater
|
||||
@ -86,7 +88,7 @@ class NetworkMapUpdaterTest {
|
||||
}
|
||||
|
||||
private fun setUpdater(extraNetworkMapKeys: List<UUID> = emptyList(), netMapClient: NetworkMapClient? = networkMapClient) {
|
||||
updater = NetworkMapUpdater(networkMapCache, fileWatcher, netMapClient, baseDir, extraNetworkMapKeys)
|
||||
updater = NetworkMapUpdater(networkMapCache, fileWatcher, netMapClient, baseDir, extraNetworkMapKeys, networkParametersStorage)
|
||||
}
|
||||
|
||||
private fun startUpdater(ourNodeInfo: SignedNodeInfo = this.ourNodeInfo,
|
||||
@ -236,6 +238,7 @@ class NetworkMapUpdaterTest {
|
||||
val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME
|
||||
assert(!updateFile.exists()) { "network parameters should not be auto accepted" }
|
||||
updater.acceptNewNetworkParameters(newHash) { it.serialize().sign(ourKeyPair) }
|
||||
verify(networkParametersStorage, times(1)).saveParameters(any())
|
||||
val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>()
|
||||
val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
assertEquals(newParameters, paramsFromFile)
|
||||
|
@ -4,21 +4,18 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import com.esotericsoftware.kryo.KryoException
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogic.Companion.sleep
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.makeTestIdentityService
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.ClassRule
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.Serializable
|
||||
import java.time.Duration
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.Id
|
||||
@ -26,7 +23,6 @@ import javax.persistence.Table
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
|
||||
class ExposeJpaToFlowsTests {
|
||||
|
||||
object FooSchema
|
||||
@ -39,15 +35,28 @@ class ExposeJpaToFlowsTests {
|
||||
|
||||
val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
|
||||
val cordapps = listOf("net.corda.node.services.persistence")
|
||||
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
|
||||
cordappPackages = cordapps,
|
||||
identityService = makeTestIdentityService(myself.identity),
|
||||
initialIdentity = myself,
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
||||
)
|
||||
lateinit var mockNet: MockNetwork
|
||||
lateinit var services: MockServices
|
||||
lateinit var database: CordaPersistence
|
||||
|
||||
val services: MockServices = databaseAndServices.second
|
||||
val database: CordaPersistence = databaseAndServices.first
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockNet = MockNetwork(cordapps)
|
||||
val (db, mockServices) = MockServices.makeTestDatabaseAndMockServices(
|
||||
cordappPackages = cordapps,
|
||||
identityService = makeTestIdentityService(myself.identity),
|
||||
initialIdentity = myself,
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
||||
)
|
||||
|
||||
services = mockServices
|
||||
database = db
|
||||
}
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can persist and query custom entities`() {
|
||||
@ -71,7 +80,6 @@ class ExposeJpaToFlowsTests {
|
||||
|
||||
@Test
|
||||
fun `can't perform suspendable operations inside withEntityManager`() {
|
||||
val mockNet = MockNetwork(cordapps)
|
||||
val mockNode = mockNet.createNode()
|
||||
assertFailsWith(KryoException::class) {
|
||||
mockNode.startFlow(object : FlowLogic<Unit>() {
|
||||
@ -84,6 +92,5 @@ class ExposeJpaToFlowsTests {
|
||||
}
|
||||
})
|
||||
}
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.google.common.jimfs.Configuration
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
@ -20,7 +18,6 @@ import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.JarSignatureTestUtils.createJar
|
||||
import net.corda.testing.core.JarSignatureTestUtils.generateKey
|
||||
@ -33,7 +30,10 @@ import net.corda.testing.node.MockServices.Companion.makeTestDataSourcePropertie
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
import org.junit.*
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.Closeable
|
||||
import java.io.OutputStream
|
||||
@ -52,7 +52,6 @@ import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
|
||||
class NodeAttachmentServiceTest {
|
||||
|
||||
// Use an in memory file system for testing attachment storage.
|
||||
@ -69,8 +68,6 @@ class NodeAttachmentServiceTest {
|
||||
database = configureDatabase(dataSourceProperties, DatabaseConfig(), { null }, { null })
|
||||
fs = Jimfs.newFileSystem(Configuration.unix())
|
||||
|
||||
doReturn(testNetworkParameters()).whenever(services).networkParameters
|
||||
|
||||
storage = NodeAttachmentService(MetricRegistry(), TestingNamedCacheFactory(), database).also {
|
||||
database.transaction {
|
||||
it.start()
|
||||
@ -427,5 +424,4 @@ class NodeAttachmentServiceTest {
|
||||
return Paths.get(fileManager.list(StandardLocation.CLASS_OUTPUT, "", setOf(JavaFileObject.Kind.CLASS), true).single().name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ class MaxTransactionSizeTests {
|
||||
mockNet = MockNetwork(listOf("net.corda.testing.contracts"), networkParameters = testNetworkParameters(maxTransactionSize = 3_000_000))
|
||||
aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
|
||||
bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
|
||||
bobNode.registerInitiatedFlow(ReceiveLargeTransactionFlow::class.java)
|
||||
notaryNode = mockNet.defaultNotaryNode
|
||||
notary = mockNet.defaultNotaryIdentity
|
||||
alice = aliceNode.info.singleIdentity()
|
||||
|
@ -3,18 +3,23 @@ package net.corda.node.services.transactions
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.NotaryError
|
||||
import net.corda.core.flows.NotaryException
|
||||
import net.corda.core.flows.NotaryFlow
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.NotaryChangeTransactionBuilder
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.MockNetworkNotarySpec
|
||||
import net.corda.testing.node.internal.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -31,7 +36,8 @@ class NotaryServiceTests {
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(
|
||||
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"),
|
||||
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME, validating = false))
|
||||
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME, validating = false)),
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
||||
)
|
||||
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||
notaryServices = mockNet.defaultNotaryNode.services //TODO get rid of that
|
||||
@ -49,6 +55,28 @@ class NotaryServiceTests {
|
||||
notariseWithTooManyInputs(aliceNode, alice, notary, mockNet)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should reject when network parameters component is not visible`() {
|
||||
val stx = generateTransaction(aliceNode, alice, notary, null, 13)
|
||||
val future = aliceNode.services.startFlow(DummyClientFlow(stx, notary)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
val ex = assertFailsWith<NotaryException> { future.getOrThrow() }
|
||||
val notaryError = ex.error as NotaryError.TransactionInvalid
|
||||
assertThat(notaryError.cause).hasMessageContaining("Transaction for notarisation was tagged with parameters with hash: null")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should reject when parameters not current`() {
|
||||
val hash = SecureHash.randomSHA256()
|
||||
val stx = generateTransaction(aliceNode, alice, notary, hash, 13)
|
||||
val future = aliceNode.services.startFlow(DummyClientFlow(stx, notary)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
val ex = assertFailsWith<NotaryException> { future.getOrThrow() }
|
||||
val notaryError = ex.error as NotaryError.TransactionInvalid
|
||||
assertThat(notaryError.cause).hasMessageContaining("Transaction for notarisation was tagged with parameters with hash: $hash, " +
|
||||
"but current network parameters are: ${notaryServices.networkParametersStorage.currentHash}")
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
/** This is used by both [NotaryServiceTests] and [ValidatingNotaryServiceTests]. */
|
||||
fun notariseWithTooManyInputs(node: TestStartedNode, party: Party, notary: Party, network: InternalMockNetwork) {
|
||||
@ -59,10 +87,17 @@ class NotaryServiceTests {
|
||||
assertFailsWith<NotaryException> { future.getOrThrow() }
|
||||
}
|
||||
|
||||
private fun generateTransaction(node: TestStartedNode, party: Party, notary: Party): SignedTransaction {
|
||||
private fun generateTransaction(node: TestStartedNode,
|
||||
party: Party, notary: Party,
|
||||
paramsHash: SecureHash? = node.services.networkParametersStorage.currentHash,
|
||||
numberOfInputs: Int = 10_005): SignedTransaction {
|
||||
val txHash = SecureHash.randomSHA256()
|
||||
val inputs = (1..10_005).map { StateRef(txHash, it) }
|
||||
val tx = NotaryChangeTransactionBuilder(inputs, notary, party).build()
|
||||
val inputs = (1..numberOfInputs).map { StateRef(txHash, it) }
|
||||
val tx = if (paramsHash != null) {
|
||||
NotaryChangeTransactionBuilder(inputs, notary, party, paramsHash).build()
|
||||
} else {
|
||||
NotaryChangeWireTransaction(listOf(inputs, notary, party).map { it.serialize() })
|
||||
}
|
||||
|
||||
return node.services.run {
|
||||
val myKey = myInfo.legalIdentities.first().owningKey
|
||||
|
@ -11,6 +11,7 @@ import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.makeTestIdentityService
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
@ -25,14 +26,7 @@ class ResolveStatePointersTest {
|
||||
private val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
|
||||
private val notary = TestIdentity(DUMMY_NOTARY_NAME, 20)
|
||||
private val cordapps = listOf("net.corda.testing.contracts")
|
||||
private val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
|
||||
cordappPackages = cordapps,
|
||||
identityService = makeTestIdentityService(notary.identity, myself.identity),
|
||||
initialIdentity = myself,
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
||||
)
|
||||
|
||||
private val services = databaseAndServices.second
|
||||
private lateinit var services: MockServices
|
||||
|
||||
private data class Bar(
|
||||
override val participants: List<AbstractParty> = listOf(),
|
||||
@ -58,6 +52,17 @@ class ResolveStatePointersTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
|
||||
cordappPackages = cordapps,
|
||||
identityService = makeTestIdentityService(notary.identity, myself.identity),
|
||||
initialIdentity = myself,
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
||||
)
|
||||
services = databaseAndServices.second
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve state pointers and check reference state is added to transaction`() {
|
||||
val stateAndRef = createPointedToState(barOne)
|
||||
@ -154,5 +159,4 @@ class ResolveStatePointersTest {
|
||||
val foo = ltx.outputs.single().data as Foo<Bar>
|
||||
assertEquals(stateAndRef, foo.baz.resolve(ltx))
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package net.corda.node.services.transactions
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.*
|
||||
@ -14,16 +15,19 @@ import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.issueInvalidState
|
||||
import net.corda.node.services.messaging.Message
|
||||
import net.corda.node.services.statemachine.InitialSessionMessage
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.dummyCommand
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.internal.createWireTransaction
|
||||
import net.corda.testing.node.TestClock
|
||||
import net.corda.testing.node.internal.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@ -46,7 +50,8 @@ class ValidatingNotaryServiceTests {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"))
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"),
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4))
|
||||
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||
notaryNode = mockNet.defaultNotaryNode
|
||||
notary = mockNet.defaultNotaryIdentity
|
||||
@ -96,6 +101,27 @@ class ValidatingNotaryServiceTests {
|
||||
assertEquals(setOf(expectedMissingKey), missingKeys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should reject transaction without network parameters`() {
|
||||
val inputState = issueState(aliceNode.services, alice).ref
|
||||
val wtx = createWireTransaction(inputs = listOf(inputState),
|
||||
attachments = emptyList(),
|
||||
outputs = emptyList(),
|
||||
commands = listOf(dummyCommand(alice.owningKey)),
|
||||
notary = notary,
|
||||
timeWindow = null)
|
||||
assertThat(wtx.networkParametersHash).isNull()
|
||||
val sig = aliceNode.services.keyManagementService.sign(
|
||||
SignableData(wtx.id, SignatureMetadata(1, Crypto.findSignatureScheme(alice.owningKey).schemeNumberID)), alice.owningKey
|
||||
)
|
||||
val stx = SignedTransaction(wtx, listOf(sig))
|
||||
assertThat(stx.networkParametersHash).isNull()
|
||||
val future = runNotaryClient(stx)
|
||||
val ex = assertFailsWith(NotaryException::class) { future.getOrThrow() }
|
||||
val notaryError = ex.error as NotaryError.TransactionInvalid
|
||||
assertThat(notaryError.cause).hasMessageContaining("Transaction for notarisation was tagged with parameters with hash: null")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should sign a unique transaction with a valid time-window`() {
|
||||
val stx = run {
|
||||
@ -173,7 +199,7 @@ class ValidatingNotaryServiceTests {
|
||||
fun `notarise issue tx with time-window`() {
|
||||
val stx = run {
|
||||
val tx = DummyContract.generateInitial(Random().nextInt(), notary, alice.ref(0))
|
||||
.setTimeWindow(Instant.now(), 30.seconds)
|
||||
.setTimeWindow(Instant.now(), 30.seconds)
|
||||
aliceNode.services.signInitialTransaction(tx)
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
@ -37,19 +38,25 @@ class ExternalIdMappingTest {
|
||||
|
||||
private val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
|
||||
private val notary = TestIdentity(CordaX500Name("NotaryService", "London", "GB"), 1337L)
|
||||
private val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
|
||||
cordappPackages = cordapps,
|
||||
identityService = rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(notary.party).whenever(it).partyFromKey(notary.publicKey)
|
||||
doReturn(notary.party).whenever(it).wellKnownPartyFromAnonymous(notary.party)
|
||||
doReturn(notary.party).whenever(it).wellKnownPartyFromX500Name(notary.name)
|
||||
},
|
||||
initialIdentity = myself,
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
||||
)
|
||||
|
||||
private val services: MockServices = databaseAndServices.second
|
||||
private val database: CordaPersistence = databaseAndServices.first
|
||||
lateinit var services: MockServices
|
||||
lateinit var database: CordaPersistence
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val (db, mockServices) = MockServices.makeTestDatabaseAndMockServices(
|
||||
cordappPackages = cordapps,
|
||||
identityService = rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(notary.party).whenever(it).partyFromKey(notary.publicKey)
|
||||
doReturn(notary.party).whenever(it).wellKnownPartyFromAnonymous(notary.party)
|
||||
doReturn(notary.party).whenever(it).wellKnownPartyFromX500Name(notary.name)
|
||||
},
|
||||
initialIdentity = myself,
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
|
||||
)
|
||||
services = mockServices
|
||||
database = db
|
||||
}
|
||||
|
||||
private fun freshKeyForExternalId(externalId: UUID): AnonymousParty {
|
||||
val anonymousParty = freshKey()
|
||||
|
@ -29,6 +29,7 @@ import net.corda.finance.utils.sumCash
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.node.services.api.WritableTransactionStorage
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.core.*
|
||||
@ -90,17 +91,19 @@ class NodeVaultServiceTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
LogHelper.setLevel(NodeVaultService::class)
|
||||
val parameters = testNetworkParameters()
|
||||
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
|
||||
cordappPackages,
|
||||
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
|
||||
megaCorp)
|
||||
megaCorp,
|
||||
parameters)
|
||||
database = databaseAndServices.first
|
||||
services = databaseAndServices.second
|
||||
vaultFiller = VaultFiller(services, dummyNotary)
|
||||
// This is safe because MockServices only ever have a single identity
|
||||
identity = services.myInfo.singleIdentityAndCert()
|
||||
issuerServices = MockServices(cordappPackages, dummyCashIssuer, rigorousMock<IdentityService>())
|
||||
bocServices = MockServices(cordappPackages, bankOfCorda, rigorousMock<IdentityService>())
|
||||
issuerServices = MockServices(cordappPackages, dummyCashIssuer, rigorousMock<IdentityService>(), parameters)
|
||||
bocServices = MockServices(cordappPackages, bankOfCorda, rigorousMock<IdentityService>(), parameters)
|
||||
services.identityService.verifyAndRegisterIdentity(DUMMY_CASH_ISSUER_IDENTITY)
|
||||
services.identityService.verifyAndRegisterIdentity(BOC_IDENTITY)
|
||||
}
|
||||
@ -571,7 +574,6 @@ class NodeVaultServiceTest {
|
||||
}
|
||||
|
||||
// TODO: Unit test linear state relevancy checks
|
||||
|
||||
@Test
|
||||
fun `correct updates are generated for general transactions`() {
|
||||
val notary = identity.party
|
||||
@ -647,7 +649,7 @@ class NodeVaultServiceTest {
|
||||
// Change notary
|
||||
services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY)
|
||||
val newNotary = DUMMY_NOTARY
|
||||
val changeNotaryTx = NotaryChangeTransactionBuilder(listOf(initialCashState.ref), issueStx.notary!!, newNotary).build()
|
||||
val changeNotaryTx = NotaryChangeTransactionBuilder(listOf(initialCashState.ref), issueStx.notary!!, newNotary, services.networkParametersStorage.currentHash).build()
|
||||
val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0))
|
||||
|
||||
database.transaction {
|
||||
|
@ -16,6 +16,10 @@ import org.junit.rules.ExpectedException
|
||||
class VaultQueryExceptionsTests : VaultQueryParties by rule {
|
||||
|
||||
companion object {
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rule = object : VaultQueryTestRule() {
|
||||
@ -26,10 +30,6 @@ class VaultQueryExceptionsTests : VaultQueryParties by rule {
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val expectedEx: ExpectedException = ExpectedException.none()
|
||||
|
@ -99,7 +99,6 @@ interface VaultQueryParties {
|
||||
}
|
||||
|
||||
open class VaultQueryTestRule : ExternalResource(), VaultQueryParties {
|
||||
|
||||
override val alice = TestIdentity(ALICE_NAME, 70)
|
||||
override val bankOfCorda = TestIdentity(BOC_NAME)
|
||||
override val bigCorp = TestIdentity(CordaX500Name("BigCorporation", "New York", "US"))
|
||||
|
@ -9,6 +9,7 @@ import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.SignedDataWithCert
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.messaging.FlowHandle
|
||||
import net.corda.core.messaging.FlowProgressHandle
|
||||
@ -16,10 +17,12 @@ import net.corda.core.messaging.StateMachineTransactionMapping
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.cordapp.CordappLoader
|
||||
import net.corda.node.internal.NetworkParametersStorageInternal
|
||||
import net.corda.node.internal.ServicesForResolutionImpl
|
||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
||||
import net.corda.node.services.api.*
|
||||
@ -40,9 +43,11 @@ import net.corda.testing.services.MockAttachmentStorage
|
||||
import java.security.KeyPair
|
||||
import java.sql.Connection
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import javax.persistence.EntityManager
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* Returns a simple [InMemoryIdentityService] containing the supplied [identities].
|
||||
@ -104,7 +109,7 @@ open class MockServices private constructor(
|
||||
fun makeTestDatabaseAndMockServices(cordappPackages: List<String>,
|
||||
identityService: IdentityService,
|
||||
initialIdentity: TestIdentity,
|
||||
networkParameters: NetworkParameters = testNetworkParameters(),
|
||||
networkParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN),
|
||||
vararg moreKeys: KeyPair): Pair<CordaPersistence, MockServices> {
|
||||
|
||||
val cordappLoader = cordappLoaderForPackages(cordappPackages)
|
||||
@ -114,7 +119,7 @@ open class MockServices private constructor(
|
||||
val mockService = database.transaction {
|
||||
object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) {
|
||||
override val vaultService: VaultService = makeVaultService(schemaService, database)
|
||||
|
||||
override val networkParametersStorage: NetworkParametersStorage get() = MockNetworkParametersStorage(networkParameters)
|
||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||
ServiceHubInternal.recordTransactions(statesToRecord, txs,
|
||||
validatedTransactions as WritableTransactionStorage,
|
||||
@ -164,7 +169,7 @@ open class MockServices private constructor(
|
||||
initialIdentity: TestIdentity,
|
||||
identityService: IdentityService = makeTestIdentityService(),
|
||||
vararg moreKeys: KeyPair) :
|
||||
this(cordappLoaderForPackages(cordappPackages), identityService, testNetworkParameters(), initialIdentity, moreKeys)
|
||||
this(cordappLoaderForPackages(cordappPackages), identityService, testNetworkParameters(modifiedTime = Instant.MIN), initialIdentity, moreKeys)
|
||||
|
||||
constructor(cordappPackages: Iterable<String>,
|
||||
initialIdentity: TestIdentity,
|
||||
@ -252,15 +257,14 @@ open class MockServices private constructor(
|
||||
}
|
||||
override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2)
|
||||
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments).also {
|
||||
it.start( networkParameters.whitelistedContractImplementations)
|
||||
it.start(networkParameters.whitelistedContractImplementations)
|
||||
}
|
||||
override val cordappProvider: CordappProvider get() = mockCordappProvider
|
||||
override val networkParametersStorage: NetworkParametersStorage get() = MockNetworkParametersStorage(networkParameters)
|
||||
|
||||
protected val servicesForResolution: ServicesForResolution
|
||||
get() {
|
||||
return ServicesForResolutionImpl(identityService, attachments, cordappProvider, validatedTransactions).also {
|
||||
it.start(networkParameters)
|
||||
}
|
||||
return ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, validatedTransactions)
|
||||
}
|
||||
|
||||
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence): VaultServiceInternal {
|
||||
@ -318,4 +322,4 @@ fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serv
|
||||
}
|
||||
}
|
||||
return MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.SignedDataWithCert
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.internal.NetworkParametersStorageInternal
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import java.time.Instant
|
||||
|
||||
class MockNetworkParametersStorage(val currentParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN)) : NetworkParametersStorageInternal {
|
||||
private val hashToParametersMap: HashMap<SecureHash, NetworkParameters> = HashMap()
|
||||
|
||||
init {
|
||||
hashToParametersMap[currentHash] = currentParameters
|
||||
}
|
||||
|
||||
override val currentHash: SecureHash get() = currentParameters.serialize().hash
|
||||
override val defaultHash: SecureHash get() = currentHash
|
||||
override fun getEpochFromHash(hash: SecureHash): Int? = lookup(hash)?.epoch
|
||||
override fun lookup(hash: SecureHash): NetworkParameters? = hashToParametersMap[hash]
|
||||
override fun saveParameters(signedNetworkParameters: SignedDataWithCert<NetworkParameters>) {
|
||||
val networkParameters = signedNetworkParameters.verified()
|
||||
val hash = signedNetworkParameters.raw.hash
|
||||
hashToParametersMap[hash] = networkParameters
|
||||
}
|
||||
}
|
@ -155,7 +155,7 @@ fun createWireTransaction(inputs: List<StateRef>,
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?,
|
||||
privacySalt: PrivacySalt = PrivacySalt()): WireTransaction {
|
||||
val componentGroups = createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList())
|
||||
val componentGroups = createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null)
|
||||
return WireTransaction(componentGroups, privacySalt)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user