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:
Katarzyna Streich 2018-11-27 15:48:32 +00:00 committed by GitHub
parent e4c1db4db6
commit 5d2ad46553
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 855 additions and 202 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -176,7 +176,7 @@ class TransactionTests {
notary,
timeWindow,
privacySalt,
null,
testNetworkParameters(),
emptyList()
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: " +

View File

@ -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<*> {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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