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
56 changed files with 855 additions and 202 deletions

View File

@ -189,7 +189,8 @@ private class WireTransactionSerializer : JsonSerializer<WireTransaction>() {
value.timeWindow, value.timeWindow,
value.attachments, value.attachments,
value.references, value.references,
value.privacySalt value.privacySalt,
value.networkParametersHash
)) ))
} }
} }
@ -204,7 +205,8 @@ private class WireTransactionDeserializer : JsonDeserializer<WireTransaction>()
wrapper.attachments, wrapper.attachments,
wrapper.notary, wrapper.notary,
wrapper.timeWindow, wrapper.timeWindow,
wrapper.references wrapper.references,
wrapper.networkParametersHash
) )
return WireTransaction(componentGroups, wrapper.privacySalt) return WireTransaction(componentGroups, wrapper.privacySalt)
} }
@ -218,7 +220,8 @@ private class WireTransactionJson(val id: SecureHash,
val timeWindow: TimeWindow?, val timeWindow: TimeWindow?,
val attachments: List<SecureHash>, val attachments: List<SecureHash>,
val references: List<StateRef>, val references: List<StateRef>,
val privacySalt: PrivacySalt) val privacySalt: PrivacySalt,
val networkParametersHash: SecureHash?)
private interface TransactionStateMixin { private interface TransactionStateMixin {
@get:JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @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.NodeInfo
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.AttachmentStorage 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.CordaSerializable
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
@ -92,8 +93,13 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
} }
services = rigorousMock() services = rigorousMock()
cordappProvider = 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(cordappProvider).whenever(services).cordappProvider
doReturn(testNetworkParameters(minimumPlatformVersion = 4)).whenever(services).networkParameters doReturn(networkParameters).whenever(services).networkParameters
doReturn(attachments).whenever(services).attachments doReturn(attachments).whenever(services).attachments
} }
@ -254,7 +260,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
println(mapper.writeValueAsString(json)) println(mapper.writeValueAsString(json))
val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures") val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
assertThat(signaturesJson.childrenAs<TransactionSignature>(mapper)).isEqualTo(stx.sigs) 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[0].valueAs<SecureHash>(mapper)).isEqualTo(wtx.id)
assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary) assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary)
assertThat(wtxFields[2].childrenAs<StateRef>(mapper)).isEqualTo(wtx.inputs) assertThat(wtxFields[2].childrenAs<StateRef>(mapper)).isEqualTo(wtx.inputs)

View File

@ -71,7 +71,8 @@ object TransactionGenerator {
TransactionVerificationRequest( TransactionVerificationRequest(
wtx3.serialize(), wtx3.serialize(),
arrayOf(wtx1.serialize(), wtx2.serialize()), arrayOf(wtx1.serialize(), wtx2.serialize()),
arrayOf(contractAttachment.serialize().bytes)) arrayOf(contractAttachment.serialize().bytes),
ledgerServices.networkParameters.serialize())
.serialize() .serialize()
.writeTo(output) .writeTo(output)
} }
@ -104,7 +105,8 @@ object TransactionGenerator {
TransactionVerificationRequest( TransactionVerificationRequest(
wtx3.serialize(), wtx3.serialize(),
arrayOf(wtx1.serialize(), wtx2.serialize()), arrayOf(wtx1.serialize(), wtx2.serialize()),
arrayOf(contractAttachment.serialize().bytes)) arrayOf(contractAttachment.serialize().bytes),
ledgerServices.networkParameters.serialize())
.serialize() .serialize()
.writeTo(output) .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.ContractAttachment
import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.ContractClassName
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER 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.CordaSerializable
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
@ -14,7 +15,8 @@ import net.corda.core.transactions.WireTransaction
@CordaSerializable @CordaSerializable
class TransactionVerificationRequest(val wtxToVerify: SerializedBytes<WireTransaction>, class TransactionVerificationRequest(val wtxToVerify: SerializedBytes<WireTransaction>,
val dependencies: Array<SerializedBytes<WireTransaction>>, val dependencies: Array<SerializedBytes<WireTransaction>>,
val attachments: Array<ByteArray>) { val attachments: Array<ByteArray>,
val networkParameters: SerializedBytes<NetworkParameters>) {
fun toLedgerTransaction(): LedgerTransaction { fun toLedgerTransaction(): LedgerTransaction {
val deps = dependencies.map { it.deserialize() }.associateBy(WireTransaction::id) val deps = dependencies.map { it.deserialize() }.associateBy(WireTransaction::id)
val attachments = attachments.map { it.deserialize<Attachment>() } val attachments = attachments.map { it.deserialize<Attachment>() }
@ -27,7 +29,8 @@ class TransactionVerificationRequest(val wtxToVerify: SerializedBytes<WireTransa
resolveIdentity = { null }, resolveIdentity = { null },
resolveAttachment = { attachmentMap[it] }, resolveAttachment = { attachmentMap[it] },
resolveStateRef = { deps[it.txhash]?.outputs?.get(it.index) }, resolveStateRef = { deps[it.txhash]?.outputs?.get(it.index) },
resolveContractAttachment = { contractAttachmentMap[it.contract]?.id } resolveContractAttachment = { contractAttachmentMap[it.contract]?.id },
resolveParameters = { networkParameters.deserialize() }
) )
} }
} }

View File

@ -12,5 +12,6 @@ enum class ComponentGroupEnum {
NOTARY_GROUP, // ordinal = 4. NOTARY_GROUP, // ordinal = 4.
TIMEWINDOW_GROUP, // ordinal = 5. TIMEWINDOW_GROUP, // ordinal = 5.
SIGNERS_GROUP, // ordinal = 6. 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 progressTracker.currentStep = RECEIVING
// Receive transaction and resolve dependencies, check sufficient signatures is disabled as we don't have all signatures. // Receive transaction and resolve dependencies, check sufficient signatures is disabled as we don't have all signatures.
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false)) 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 // 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 // means we only have to check we own that one key, rather than matching all keys in the transaction against all
// keys we own. // keys we own.

View File

@ -33,7 +33,8 @@ class NotaryChangeFlow<out T : ContractState>(
val tx = NotaryChangeTransactionBuilder( val tx = NotaryChangeTransactionBuilder(
inputs.map { it.ref }, inputs.map { it.ref },
originalState.state.notary, originalState.state.notary,
modification modification,
serviceHub.networkParametersStorage.currentHash
).build() ).build()
val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() 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 co.paralleluniverse.fibers.Suspendable
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -12,10 +13,7 @@ import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.notary.generateSignature import net.corda.core.internal.notary.generateSignature
import net.corda.core.internal.notary.validateSignatures import net.corda.core.internal.notary.validateSignatures
import net.corda.core.internal.pushToLoggingContext import net.corda.core.internal.pushToLoggingContext
import net.corda.core.transactions.ContractUpgradeWireTransaction import net.corda.core.transactions.*
import net.corda.core.transactions.ReferenceStateRef
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap 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. * 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 * @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 @DoNotImplement
@InitiatingFlow @InitiatingFlow
@ -99,7 +98,9 @@ class NotaryFlow {
val ctx = stx.coreTransaction val ctx = stx.coreTransaction
val tx = when (ctx) { val tx = when (ctx) {
is ContractUpgradeWireTransaction -> ctx.buildFilteredTransaction() 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 else -> ctx
} }
session.send(NotarisationPayload(tx, signature)) session.send(NotarisationPayload(tx, signature))

View File

@ -21,6 +21,7 @@ object ContractUpgradeUtils {
else -> getContractAttachmentId(stateAndRef.state.contract, services) else -> getContractAttachmentId(stateAndRef.state.contract, services)
} }
val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services) val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services)
val networkParametersHash = services.networkParametersStorage.currentHash
val inputs = listOf(stateAndRef.ref) val inputs = listOf(stateAndRef.ref)
return ContractUpgradeTransactionBuilder( return ContractUpgradeTransactionBuilder(
@ -29,7 +30,8 @@ object ContractUpgradeUtils {
legacyContractAttachmentId, legacyContractAttachmentId,
upgradedContractClass.name, upgradedContractClass.name,
upgradedContractAttachmentId, upgradedContractAttachmentId,
privacySalt privacySalt,
networkParametersHash
).build() ).build()
} }

View File

@ -185,7 +185,7 @@ class FetchAttachmentsFlow(requests: Set<SecureHash>,
* [FetchDataFlow.DownloadedVsRequestedDataMismatch] being thrown. * [FetchDataFlow.DownloadedVsRequestedDataMismatch] being thrown.
* If the remote peer doesn't have an entry, it results in a [FetchDataFlow.HashNotFound] exception. * 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. * 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. * 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) : class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :

View File

@ -17,9 +17,10 @@ import kotlin.reflect.KClass
/** Constructs a [NotaryChangeWireTransaction]. */ /** Constructs a [NotaryChangeWireTransaction]. */
class NotaryChangeTransactionBuilder(val inputs: List<StateRef>, class NotaryChangeTransactionBuilder(val inputs: List<StateRef>,
val notary: Party, val notary: Party,
val newNotary: Party) { val newNotary: Party,
val networkParametersHash: SecureHash) {
fun build(): NotaryChangeWireTransaction { 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) return NotaryChangeWireTransaction(components)
} }
} }
@ -31,9 +32,10 @@ class ContractUpgradeTransactionBuilder(
val legacyContractAttachmentId: SecureHash, val legacyContractAttachmentId: SecureHash,
val upgradedContractClassName: ContractClassName, val upgradedContractClassName: ContractClassName,
val upgradedContractAttachmentId: SecureHash, val upgradedContractAttachmentId: SecureHash,
val privacySalt: PrivacySalt = PrivacySalt()) { val privacySalt: PrivacySalt = PrivacySalt(),
val networkParametersHash: SecureHash) {
fun build(): ContractUpgradeWireTransaction { 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) return ContractUpgradeWireTransaction(components, privacySalt)
} }
} }
@ -128,7 +130,8 @@ fun createComponentGroups(inputs: List<StateRef>,
attachments: List<SecureHash>, attachments: List<SecureHash>,
notary: Party?, notary: Party?,
timeWindow: TimeWindow?, timeWindow: TimeWindow?,
references: List<StateRef>): List<ComponentGroup> { references: List<StateRef>,
networkParametersHash: SecureHash?): List<ComponentGroup> {
val serialize = { value: Any, _: Int -> value.serialize() } val serialize = { value: Any, _: Int -> value.serialize() }
val componentGroupMap: MutableList<ComponentGroup> = mutableListOf() val componentGroupMap: MutableList<ComponentGroup> = mutableListOf()
if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.INPUTS_GROUP.ordinal, inputs.lazyMapped(serialize))) 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 // 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. // 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 (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 return componentGroupMap
} }

View File

@ -78,7 +78,6 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
requestPayload.requestSignature, requestPayload.requestSignature,
tx.timeWindow, tx.timeWindow,
tx.references) tx.references)
} catch (e: NotaryInternalException) { } catch (e: NotaryInternalException) {
logError(e.error) logError(e.error)
// Any exception that's not a NotaryInternalException is assumed to be an unexpected internal 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) val transaction = extractParts(requestPayload)
transactionId = transaction.id transactionId = transaction.id
checkNotary(transaction.notary) checkNotary(transaction.notary)
checkParametersHash(transaction.networkParametersHash)
checkInputs(transaction.inputs + transaction.references) checkInputs(transaction.inputs + transaction.references)
return transaction return transaction
} catch (e: Exception) { } 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. */ /** Verifies that the correct notarisation request was signed by the counterparty. */
private fun validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) { private fun validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) {
val requestingParty = otherSideSession.counterparty val requestingParty = otherSideSession.counterparty
@ -150,7 +165,8 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
val inputs: List<StateRef>, val inputs: List<StateRef>,
val timeWindow: TimeWindow?, val timeWindow: TimeWindow?,
val notary: Party?, val notary: Party?,
val references: List<StateRef> = emptyList() val references: List<StateRef> = emptyList(),
val networkParametersHash: SecureHash?
) )
private fun logError(error: NotaryError) { 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.contracts.*
import net.corda.core.cordapp.CordappContext import net.corda.core.cordapp.CordappContext
import net.corda.core.cordapp.CordappProvider import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.Crypto import net.corda.core.crypto.*
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.ContractUpgradeFlow import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.node.services.* import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken 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 */ /** Provides access to anything relating to cordapps including contract attachment resolution and app context */
val cordappProvider: CordappProvider 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. */ /** Returns the network parameters the node is operating under. */
val networkParameters: NetworkParameters 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.ContractState
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
/** /**
@ -16,13 +18,24 @@ abstract class CoreTransaction : BaseTransaction() {
abstract override val inputs: List<StateRef> abstract override val inputs: List<StateRef>
/** The reference inputs of this transaction, containing the state references only. **/ /** The reference inputs of this transaction, containing the state references only. **/
abstract override val references: List<StateRef> 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. */ /** A transaction with fully resolved components, such as input states. */
abstract class FullTransaction : BaseTransaction() { abstract class FullTransaction : BaseTransaction() {
abstract override val inputs: List<StateAndRef<ContractState>> abstract override val inputs: List<StateAndRef<ContractState>>
abstract override val references: 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() { override fun checkBaseInvariants() {
super.checkBaseInvariants() super.checkBaseInvariants()
checkInputsAndReferencesHaveSameNotary() checkInputsAndReferencesHaveSameNotary()

View File

@ -69,6 +69,11 @@ data class ContractUpgradeWireTransaction(
val legacyContractAttachmentId: SecureHash by lazy { serializedComponents[LEGACY_ATTACHMENT.ordinal].deserialize<SecureHash>() } val legacyContractAttachmentId: SecureHash by lazy { serializedComponents[LEGACY_ATTACHMENT.ordinal].deserialize<SecureHash>() }
val upgradedContractClassName: ContractClassName by lazy { serializedComponents[UPGRADED_CONTRACT.ordinal].deserialize<ContractClassName>() } val upgradedContractClassName: ContractClassName by lazy { serializedComponents[UPGRADED_CONTRACT.ordinal].deserialize<ContractClassName>() }
val upgradedContractAttachmentId: SecureHash by lazy { serializedComponents[UPGRADED_ATTACHMENT.ordinal].deserialize<SecureHash>() } 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 { init {
check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" } check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" }
@ -106,6 +111,8 @@ data class ContractUpgradeWireTransaction(
?: throw AttachmentResolutionException(legacyContractAttachmentId) ?: throw AttachmentResolutionException(legacyContractAttachmentId)
val upgradedContractAttachment = services.attachments.openAttachment(upgradedContractAttachmentId) val upgradedContractAttachment = services.attachments.openAttachment(upgradedContractAttachmentId)
?: throw AttachmentResolutionException(upgradedContractAttachmentId) ?: throw AttachmentResolutionException(upgradedContractAttachmentId)
val hashToResolve = networkParametersHash ?: services.networkParametersStorage.defaultHash
val resolvedNetworkParameters = services.networkParametersStorage.lookup(hashToResolve) ?: throw TransactionResolutionException(id)
return ContractUpgradeLedgerTransaction( return ContractUpgradeLedgerTransaction(
resolvedInputs, resolvedInputs,
notary, notary,
@ -115,7 +122,7 @@ data class ContractUpgradeWireTransaction(
id, id,
privacySalt, privacySalt,
sigs, 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 { fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction {
val totalComponents = (0 until serializedComponents.size).toSet() val totalComponents = (0 until serializedComponents.size).toSet()
val visibleComponents = mapOf( val visibleComponents = mapOf(
INPUTS.ordinal to FilteredComponent(serializedComponents[INPUTS.ordinal], nonces[INPUTS.ordinal]), 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 hiddenComponents = (totalComponents - visibleComponents.keys).map { index ->
val hash = componentHash(nonces[index], serializedComponents[index]) val hash = componentHash(nonces[index], serializedComponents[index])
@ -161,13 +169,13 @@ data class ContractUpgradeWireTransaction(
} }
enum class Component { 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 * 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. * rest of the transaction is always hidden. Its only purpose is to hide transaction data when using a non-validating notary.
*/ */
@KeepForDJVM @KeepForDJVM
@ -189,6 +197,9 @@ data class ContractUpgradeFilteredTransaction(
visibleComponents[NOTARY.ordinal]?.component?.deserialize<Party>() visibleComponents[NOTARY.ordinal]?.component?.deserialize<Party>()
?: throw IllegalArgumentException("Notary not specified") ?: throw IllegalArgumentException("Notary not specified")
} }
override val networkParametersHash: SecureHash? by lazy {
visibleComponents[PARAMETERS_HASH.ordinal]?.component?.deserialize<SecureHash>()
}
override val id: SecureHash by lazy { override val id: SecureHash by lazy {
val totalComponents = visibleComponents.size + hiddenComponents.size val totalComponents = visibleComponents.size + hiddenComponents.size
val hashList = (0 until totalComponents).map { i -> val hashList = (0 until totalComponents).map { i ->
@ -230,9 +241,9 @@ data class ContractUpgradeLedgerTransaction(
override val id: SecureHash, override val id: SecureHash,
val privacySalt: PrivacySalt, val privacySalt: PrivacySalt,
override val sigs: List<TransactionSignature>, override val sigs: List<TransactionSignature>,
private val networkParameters: NetworkParameters override val networkParameters: NetworkParameters
) : FullTransaction(), TransactionWithSignatures { ) : FullTransaction(), TransactionWithSignatures {
/** ContractUpgradeLEdgerTransactions do not contain reference input states. */ /** ContractUpgradeLedgerTransactions do not contain reference input states. */
override val references: List<StateAndRef<ContractState>> = emptyList() override val references: List<StateAndRef<ContractState>> = emptyList()
/** The legacy contract class name is determined by the first input state. */ /** The legacy contract class name is determined by the first input state. */
private val legacyContractClassName = inputs.first().state.contract private val legacyContractClassName = inputs.first().state.contract

View File

@ -52,7 +52,10 @@ private constructor(
override val notary: Party?, override val notary: Party?,
val timeWindow: TimeWindow?, val timeWindow: TimeWindow?,
val privacySalt: PrivacySalt, 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>> override val references: List<StateAndRef<ContractState>>
//DOCEND 1 //DOCEND 1
) : FullTransaction() { ) : FullTransaction() {
@ -81,7 +84,7 @@ private constructor(
notary: Party?, notary: Party?,
timeWindow: TimeWindow?, timeWindow: TimeWindow?,
privacySalt: PrivacySalt, privacySalt: PrivacySalt,
networkParameters: NetworkParameters?, networkParameters: NetworkParameters,
references: List<StateAndRef<ContractState>>, references: List<StateAndRef<ContractState>>,
componentGroups: List<ComponentGroup>? = null, componentGroups: List<ComponentGroup>? = null,
serializedInputs: List<SerializedStateAndRef>? = null, serializedInputs: List<SerializedStateAndRef>? = null,
@ -121,6 +124,11 @@ private constructor(
*/ */
@Throws(TransactionVerificationException::class) @Throws(TransactionVerificationException::class)
fun verify() { 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() val contractAttachmentsByContract: Map<ContractClassName, ContractAttachment> = getUniqueContractAttachmentsByContract()
AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments) { transactionClassLoader -> AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments) { transactionClassLoader ->
@ -169,14 +177,14 @@ private constructor(
/** /**
* Verify that for each contract the network wide package owner is respected. * 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>) { private fun validatePackageOwnership(contractAttachmentsByContract: Map<ContractClassName, ContractAttachment>) {
// This should never happen once we have network parameters in the transaction. // This should never happen once we have network parameters in the transaction.
if (networkParameters == null) { if (networkParameters == null) {
return return
} }
val contractsAndOwners = allStates.mapNotNull { transactionState -> val contractsAndOwners = allStates.mapNotNull { transactionState ->
val contractClassName = transactionState.contract val contractClassName = transactionState.contract
networkParameters.getOwnerOf(contractClassName)?.let { contractClassName to it } networkParameters.getOwnerOf(contractClassName)?.let { contractClassName to it }
@ -243,7 +251,6 @@ private constructor(
if (state.constraint is SignatureAttachmentConstraint) if (state.constraint is SignatureAttachmentConstraint)
checkMinimumPlatformVersion(networkParameters?.minimumPlatformVersion ?: 1, 4, "Signature constraints") checkMinimumPlatformVersion(networkParameters?.minimumPlatformVersion ?: 1, 4, "Signature constraints")
if (!state.constraint.isSatisfiedBy(constraintAttachment)) { if (!state.constraint.isSatisfiedBy(constraintAttachment)) {
throw TransactionVerificationException.ContractConstraintRejection(id, state.contract) throw TransactionVerificationException.ContractConstraintRejection(id, state.contract)
} }
@ -806,11 +813,8 @@ private constructor(
|)""".trimMargin() |)""".trimMargin()
} }
//
// Stuff that we can't remove and so is deprecated instead // Stuff that we can't remove and so is deprecated instead
// //
@Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.") @Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.")
constructor( constructor(
inputs: List<StateAndRef<ContractState>>, inputs: List<StateAndRef<ContractState>>,
@ -834,7 +838,7 @@ private constructor(
notary: Party?, notary: Party?,
timeWindow: TimeWindow?, timeWindow: TimeWindow?,
privacySalt: PrivacySalt, privacySalt: PrivacySalt,
networkParameters: NetworkParameters? networkParameters: NetworkParameters
) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, emptyList()) ) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, emptyList())
@Deprecated("LedgerTransactions should not be created directly, use WireTransaction.toLedgerTransaction instead.") @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() 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, * 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]: * 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 notary [Party], if present (list with one element)
* - The time-window of the transaction, 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 * - list of each reference input that is present
* - network parameters hash if present
*/ */
val availableComponentGroups: List<List<Any>> val availableComponentGroups: List<List<Any>>
get() { get() {
val result = mutableListOf(inputs, outputs, commands, attachments, references) val result = mutableListOf(inputs, outputs, commands, attachments, references)
notary?.let { result += listOf(it) } notary?.let { result += listOf(it) }
timeWindow?.let { result += listOf(it) } timeWindow?.let { result += listOf(it) }
networkParametersHash?.let { result += listOf(it) }
return result return result
} }
} }
@ -149,7 +157,9 @@ class FilteredTransaction internal constructor(
if (wtx.timeWindow != null) filter(wtx.timeWindow, TIMEWINDOW_GROUP.ordinal, 0) 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], // 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]. // 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.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, // It is highlighted that because there is no a signers property in TraversableTransaction,
// one cannot specifically filter them in or out. // 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 // 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 @KeepForDJVM
@CordaSerializable @CordaSerializable
data class ReferenceStateRef(val stateRef: StateRef) 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.TransactionSignature
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -37,9 +38,16 @@ data class NotaryChangeWireTransaction(
override val inputs: List<StateRef> = serializedComponents[INPUTS.ordinal].deserialize() override val inputs: List<StateRef> = serializedComponents[INPUTS.ordinal].deserialize()
override val references: List<StateRef> = emptyList() override val references: List<StateRef> = emptyList()
override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize() override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize()
/** Identity of the notary service to reassign the states to.*/ /** Identity of the notary service to reassign the states to.*/
val newNotary: Party = serializedComponents[NEW_NOTARY.ordinal].deserialize() 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 * This transaction does not contain any output states, outputs can be obtained by resolving a
* [NotaryChangeLedgerTransaction] and applying the notary modification to inputs. * [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 @DeleteForDJVM
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction { fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
val resolvedInputs = services.loadStates(inputs.toSet()).toList() 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]. */ /** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
@ -92,7 +103,7 @@ data class NotaryChangeWireTransaction(
} }
enum class Component { 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) @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. * needed for signature verification.
*/ */
@KeepForDJVM @KeepForDJVM
data class NotaryChangeLedgerTransaction( class NotaryChangeLedgerTransaction
private constructor(
override val inputs: List<StateAndRef<ContractState>>, override val inputs: List<StateAndRef<ContractState>>,
override val notary: Party, override val notary: Party,
val newNotary: Party, val newNotary: Party,
override val id: SecureHash, 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 { ) : 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 { init {
checkEncumbrances() 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 val references: List<StateRef> get() = coreTransaction.references
/** Helper to access the notary of the contained transaction. */ /** Helper to access the notary of the contained transaction. */
val notary: Party? get() = coreTransaction.notary 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 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. (allContractAttachments + attachments).toSortedSet().toList(), // Sort the attachments to ensure transaction builds are stable.
notary, notary,
window, window,
referenceStates referenceStates,
), services.networkParametersStorage.currentHash),
privacySalt privacySalt
) )
} }

View File

@ -66,7 +66,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
notary: Party?, notary: Party?,
timeWindow: TimeWindow?, timeWindow: TimeWindow?,
privacySalt: PrivacySalt = PrivacySalt() 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 { init {
check(componentGroups.all { it.components.isNotEmpty() }) { "Empty component groups are not allowed" } 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) }, resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) }, resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) }, 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") @Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead")
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
@JvmOverloads
fun toLedgerTransaction( fun toLedgerTransaction(
resolveIdentity: (PublicKey) -> Party?, resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?, resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?, 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 { ): LedgerTransaction {
// This reverts to serializing the resolved transaction state. // 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( private fun toLedgerTransactionInternal(
resolveIdentity: (PublicKey) -> Party?, resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?, resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?, resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
networkParameters: NetworkParameters? resolveParameters: (SecureHash?) -> NetworkParameters?
): LedgerTransaction { ): LedgerTransaction {
// Look up public keys to authenticated identities. // Look up public keys to authenticated identities.
val authenticatedCommands = commands.lazyMapped { cmd, _ -> 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 resolvedAttachments = attachments.lazyMapped { att, _ -> resolveAttachment(att) ?: throw AttachmentResolutionException(att) }
val resolvedNetworkParameters = resolveParameters(networkParametersHash) ?: throw TransactionResolutionException(id)
val ltx = LedgerTransaction.create( val ltx = LedgerTransaction.create(
resolvedInputs, resolvedInputs,
outputs, outputs,
@ -162,14 +170,14 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
notary, notary,
timeWindow, timeWindow,
privacySalt, privacySalt,
networkParameters, resolvedNetworkParameters,
resolvedReferences, resolvedReferences,
componentGroups, componentGroups,
serializedResolvedInputs, serializedResolvedInputs,
serializedResolvedReferences serializedResolvedReferences
) )
checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: DEFAULT_MAX_TX_SIZE, serializedResolvedInputs, serializedResolvedReferences) checkTransactionSize(ltx, resolvedNetworkParameters.maxTransactionSize, serializedResolvedInputs, serializedResolvedReferences)
return ltx return ltx
} }
@ -286,8 +294,6 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
} }
companion object { companion object {
private const val DEFAULT_MAX_TX_SIZE = 10485760
@CordaInternal @CordaInternal
@Deprecated("Do not use, this is internal API") @Deprecated("Do not use, this is internal API")
fun createComponentGroups(inputs: List<StateRef>, fun createComponentGroups(inputs: List<StateRef>,
@ -296,7 +302,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
attachments: List<SecureHash>, attachments: List<SecureHash>,
notary: Party?, notary: Party?,
timeWindow: TimeWindow?): List<ComponentGroup> { 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 val emoji = Emoji.paperclip
buf.appendln("${emoji}ATTACHMENT: $attachment") buf.appendln("${emoji}ATTACHMENT: $attachment")
} }
if (networkParametersHash != null) {
buf.appendln("PARAMETERS HASH: $networkParametersHash")
}
return buf.toString() return buf.toString()
} }

View File

@ -162,6 +162,7 @@ class PartialMerkleTreeTest {
assertEquals(1, ftx.commands.size) assertEquals(1, ftx.commands.size)
assertNull(ftx.notary) assertNull(ftx.notary)
assertNotNull(ftx.timeWindow) assertNotNull(ftx.timeWindow)
assertNull(ftx.networkParametersHash)
ftx.verify() ftx.verify()
} }
@ -186,6 +187,7 @@ class PartialMerkleTreeTest {
assertTrue(ftxNothing.outputs.isEmpty()) assertTrue(ftxNothing.outputs.isEmpty())
assertNull(ftxNothing.timeWindow) assertNull(ftxNothing.timeWindow)
assertTrue(ftxNothing.availableComponentGroups.flatten().isEmpty()) assertTrue(ftxNothing.availableComponentGroups.flatten().isEmpty())
assertNull(ftxNothing.networkParametersHash)
ftxNothing.verify() // We allow empty ftx transactions (eg from a timestamp authority that blindly signs). 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.serialization.serialize
import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import org.junit.Rule import org.junit.Rule
@ -31,6 +32,7 @@ class TopologicalSortTest {
override val outputs: List<TransactionState<ContractState>> = (1..numberOfOutputs).map { override val outputs: List<TransactionState<ContractState>> = (1..numberOfOutputs).map {
TransactionState(DummyState(), "", notary) TransactionState(DummyState(), "", notary)
} }
override val networkParametersHash: SecureHash? = testNetworkParameters().serialize().hash
} }
class DummyState : ContractState { class DummyState : ContractState {

View File

@ -6,6 +6,7 @@ import net.corda.core.crypto.*
import net.corda.core.internal.createComponentGroups import net.corda.core.internal.createComponentGroups
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState import net.corda.testing.contracts.DummyState
import net.corda.testing.core.* import net.corda.testing.core.*
@ -38,6 +39,7 @@ class CompatibleTransactionTests {
private val notary = DUMMY_NOTARY private val notary = DUMMY_NOTARY
private val timeWindow = TimeWindow.fromOnly(Instant.now()) private val timeWindow = TimeWindow.fromOnly(Instant.now())
private val privacySalt: PrivacySalt = PrivacySalt() 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 inputGroup by lazy { ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() }) }
private val outputGroup by lazy { ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.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 notaryGroup by lazy { ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize())) }
private val timeWindowGroup by lazy { ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.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 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 newUnknownComponentGroup = ComponentGroup(100, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8))))
private val newUnknownComponentEmptyGroup = ComponentGroup(101, emptyList()) private val newUnknownComponentEmptyGroup = ComponentGroup(101, emptyList())
@ -125,7 +128,7 @@ class CompatibleTransactionTests {
@Test @Test
fun `WireTransaction constructors and compatibility`() { 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) val wireTransactionOldConstructor = WireTransaction(groups, privacySalt)
assertEquals(wireTransactionA, wireTransactionOldConstructor) assertEquals(wireTransactionA, wireTransactionOldConstructor)
@ -211,6 +214,7 @@ class CompatibleTransactionTests {
ftxAll.checkAllComponentsVisible(NOTARY_GROUP) ftxAll.checkAllComponentsVisible(NOTARY_GROUP)
ftxAll.checkAllComponentsVisible(TIMEWINDOW_GROUP) ftxAll.checkAllComponentsVisible(TIMEWINDOW_GROUP)
ftxAll.checkAllComponentsVisible(SIGNERS_GROUP) ftxAll.checkAllComponentsVisible(SIGNERS_GROUP)
ftxAll.checkAllComponentsVisible(PARAMETERS_GROUP)
// Filter inputs only. // Filter inputs only.
fun filtering(elem: Any): Boolean { 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 }!!.components.size)
assertEquals(3, ftxCompatible.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.nonces.size) assertEquals(3, ftxCompatible.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.nonces.size)
assertNotNull(ftxCompatible.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.partialMerkleTree) 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. // 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. 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). // Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id).
assertFailsWith<ComponentVisibilityException> { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) } 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.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentStorage 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.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState import net.corda.testing.contracts.DummyState
@ -35,13 +37,17 @@ class TransactionBuilderTest {
private val services = rigorousMock<ServicesForResolution>() private val services = rigorousMock<ServicesForResolution>()
private val contractAttachmentId = SecureHash.randomSHA256() private val contractAttachmentId = SecureHash.randomSHA256()
private val attachments = rigorousMock<AttachmentStorage>() private val attachments = rigorousMock<AttachmentStorage>()
private val networkParametersStorage = rigorousMock<NetworkParametersStorage>()
@Before @Before
fun setup() { fun setup() {
val cordappProvider = rigorousMock<CordappProvider>() 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(cordappProvider).whenever(services).cordappProvider
doReturn(contractAttachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) 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>() val attachmentStorage = rigorousMock<AttachmentStorage>()
doReturn(attachmentStorage).whenever(services).attachments doReturn(attachmentStorage).whenever(services).attachments
@ -67,6 +73,7 @@ class TransactionBuilderTest {
val wtx = builder.toWireTransaction(services) val wtx = builder.toWireTransaction(services)
assertThat(wtx.outputs).containsOnly(outputState) assertThat(wtx.outputs).containsOnly(outputState)
assertThat(wtx.commands).containsOnly(Command(DummyCommandData, notary.owningKey)) assertThat(wtx.commands).containsOnly(Command(DummyCommandData, notary.owningKey))
assertThat(wtx.networkParametersHash).isEqualTo(networkParametersStorage.currentHash)
} }
@Test @Test

View File

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

View File

@ -15,7 +15,7 @@ import java.nio.file.Path
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import java.util.Collections.singleton 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. // relationships between those interfaces, therefore they have to be listed explicitly.
val coreContractClasses = setOf(Contract::class, UpgradedContractWithLegacyConstraint::class, UpgradedContract::class) 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.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AbstractAttachment
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.NetworkParametersStorage
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
@ -67,19 +66,18 @@ class AttachmentsClassLoaderStaticContractTests {
} }
} }
private val unsignedAttachment = object : AbstractAttachment({ byteArrayOf() }) { private val networkParameters = testNetworkParameters()
override val id: SecureHash get() = throw UnsupportedOperationException()
private val networkParametersStorage get() = rigorousMock<NetworkParametersStorage>().also {
doReturn(networkParameters.serialize().hash).whenever(it).currentHash
} }
private val attachments = rigorousMock<AttachmentStorage>().also { private val serviceHub get() = rigorousMock<ServicesForResolution>().also {
doReturn(unsignedAttachment).whenever(it).openAttachment(any())
}
private val serviceHub = rigorousMock<ServicesForResolution>().also {
val cordappProviderImpl = CordappProviderImpl(cordappLoaderForPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage()) val cordappProviderImpl = CordappProviderImpl(cordappLoaderForPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage())
cordappProviderImpl.start(testNetworkParameters().whitelistedContractImplementations) cordappProviderImpl.start(testNetworkParameters().whitelistedContractImplementations)
doReturn(cordappProviderImpl).whenever(it).cordappProvider doReturn(cordappProviderImpl).whenever(it).cordappProvider
doReturn(testNetworkParameters()).whenever(it).networkParameters doReturn(networkParametersStorage).whenever(it).networkParametersStorage
doReturn(networkParameters).whenever(it).networkParameters
val attachmentStorage = rigorousMock<AttachmentStorage>() val attachmentStorage = rigorousMock<AttachmentStorage>()
doReturn(attachmentStorage).whenever(it).attachments doReturn(attachmentStorage).whenever(it).attachments
val attachment = rigorousMock<ContractAttachment>() val attachment = rigorousMock<ContractAttachment>()

View File

@ -1,5 +1,6 @@
package net.corda.node.services package net.corda.node.services
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.CordaRuntimeException 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.ServicesForResolution
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.IdentityService 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.SerializationFactory
import net.corda.core.serialization.serialize
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
@ -63,6 +66,7 @@ class AttachmentLoadingTests {
} }
private val services = object : ServicesForResolution { private val services = object : ServicesForResolution {
private val testNetworkParameters = testNetworkParameters()
override fun loadState(stateRef: StateRef): TransactionState<*> = throw NotImplementedError() override fun loadState(stateRef: StateRef): TransactionState<*> = throw NotImplementedError()
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = throw NotImplementedError() override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = throw NotImplementedError()
override val identityService = rigorousMock<IdentityService>().apply { override val identityService = rigorousMock<IdentityService>().apply {
@ -70,7 +74,11 @@ class AttachmentLoadingTests {
} }
override val attachments: AttachmentStorage get() = this@AttachmentLoadingTests.attachments override val attachments: AttachmentStorage get() = this@AttachmentLoadingTests.attachments
override val cordappProvider: CordappProvider get() = this@AttachmentLoadingTests.provider 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 @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 networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize() val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize()
val cryptoService = configuration.makeCryptoService() val cryptoService = configuration.makeCryptoService()
val networkParametersStorage = DBNetworkParametersStorage(cacheFactory, database, networkMapClient).tokenize()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize() val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
@Suppress("LeakingThis") @Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize() 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 attachments.servicesForResolution = it
} }
@Suppress("LeakingThis") @Suppress("LeakingThis")
val vaultService = makeVaultService(keyManagementService, servicesForResolution, database).tokenize() val vaultService = makeVaultService(keyManagementService, servicesForResolution, database).tokenize()
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database, cacheFactory) val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database, cacheFactory)
val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader) 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( val networkMapUpdater = NetworkMapUpdater(
networkMapCache, networkMapCache,
NodeInfoWatcher( NodeInfoWatcher(
@ -188,7 +190,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
), ),
networkMapClient, networkMapClient,
configuration.baseDirectory, configuration.baseDirectory,
configuration.extraNetworkMapKeys configuration.extraNetworkMapKeys,
networkParametersStorage
).closeOnStop() ).closeOnStop()
@Suppress("LeakingThis") @Suppress("LeakingThis")
val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize() val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize()
@ -326,7 +329,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
check(netParams.minimumPlatformVersion <= versionInfo.platformVersion) { check(netParams.minimumPlatformVersion <= versionInfo.platformVersion) {
"Node's platform version is lower than network's required minimumPlatformVersion" "Node's platform version is lower than network's required minimumPlatformVersion"
} }
servicesForResolution.start(netParams)
networkMapCache.start(netParams.notaries) networkMapCache.start(netParams.notaries)
startDatabase() 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. // Do all of this in a database transaction so anything that might need a connection has one.
return database.transaction { return database.transaction {
networkParametersStorage.start(signedNetParams, trustRoot)
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts) identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
attachments.start() attachments.start()
cordappProvider.start(netParams.whitelistedContractImplementations) 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 configuration: NodeConfiguration get() = this@AbstractNode.configuration
override val networkMapUpdater: NetworkMapUpdater get() = this@AbstractNode.networkMapUpdater override val networkMapUpdater: NetworkMapUpdater get() = this@AbstractNode.networkMapUpdater
override val cacheFactory: NamedCacheFactory get() = this@AbstractNode.cacheFactory override val cacheFactory: NamedCacheFactory get() = this@AbstractNode.cacheFactory
override val networkParametersStorage: NetworkParametersStorage get() = this@AbstractNode.networkParametersStorage
private lateinit var _myInfo: NodeInfo private lateinit var _myInfo: NodeInfo
override val myInfo: NodeInfo get() = _myInfo 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.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.node.services.network.NetworkMapClient 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.Path
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import java.security.cert.X509Certificate import java.security.cert.X509Certificate

View File

@ -5,6 +5,7 @@ import net.corda.core.cordapp.CordappProvider
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.NetworkParametersStorage
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.TransactionStorage import net.corda.core.node.services.TransactionStorage
@ -12,14 +13,11 @@ data class ServicesForResolutionImpl(
override val identityService: IdentityService, override val identityService: IdentityService,
override val attachments: AttachmentStorage, override val attachments: AttachmentStorage,
override val cordappProvider: CordappProvider, override val cordappProvider: CordappProvider,
override val networkParametersStorage: NetworkParametersStorage,
private val validatedTransactions: TransactionStorage private val validatedTransactions: TransactionStorage
) : ServicesForResolution { ) : ServicesForResolution {
private lateinit var _networkParameters: NetworkParameters override val networkParameters: NetworkParameters get() = networkParametersStorage.lookup(networkParametersStorage.currentHash) ?:
override val networkParameters: NetworkParameters get() = _networkParameters throw IllegalArgumentException("No current parameters in network parameters storage")
fun start(networkParameters: NetworkParameters) {
_networkParameters = networkParameters
}
@Throws(TransactionResolutionException::class) @Throws(TransactionResolutionException::class)
override fun loadState(stateRef: StateRef): TransactionState<*> { 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.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.minutes import net.corda.core.utilities.minutes
import net.corda.node.internal.NetworkParametersStorageInternal
import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.config.NetworkParameterAcceptanceSettings import net.corda.node.services.config.NetworkParameterAcceptanceSettings
import net.corda.node.utilities.NamedThreadFactory import net.corda.node.utilities.NamedThreadFactory
@ -33,7 +34,8 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
private val nodeInfoWatcher: NodeInfoWatcher, private val nodeInfoWatcher: NodeInfoWatcher,
private val networkMapClient: NetworkMapClient?, private val networkMapClient: NetworkMapClient?,
private val baseDirectory: Path, private val baseDirectory: Path,
private val extraNetworkMapKeys: List<UUID> private val extraNetworkMapKeys: List<UUID>,
private val networkParametersStorage: NetworkParametersStorageInternal
) : AutoCloseable { ) : AutoCloseable {
companion object { companion object {
private val logger = contextLogger() private val logger = contextLogger()
@ -209,6 +211,7 @@ The node will shutdown now.""")
} }
val newSignedNetParams = networkMapClient.getNetworkParameters(update.newParametersHash) val newSignedNetParams = networkMapClient.getNetworkParameters(update.newParametersHash)
val newNetParams = newSignedNetParams.verifiedNetworkParametersCert(trustRoot) val newNetParams = newSignedNetParams.verifiedNetworkParametersCert(trustRoot)
networkParametersStorage.saveParameters(newSignedNetParams)
logger.info("Downloaded new network parameters: $newNetParams from the update: $update") logger.info("Downloaded new network parameters: $newNetParams from the update: $update")
newNetworkParameters = Pair(update, newSignedNetParams) newNetworkParameters = Pair(update, newSignedNetParams)
val updateInfo = ParametersUpdateInfo( 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.*
import net.corda.core.schemas.MappedSchemaValidator.crossReferencesToOtherMappedSchema import net.corda.core.schemas.MappedSchemaValidator.crossReferencesToOtherMappedSchema
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.node.internal.DBNetworkParametersStorage
import net.corda.node.internal.schemas.NodeInfoSchemaV1 import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.api.SchemaService import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.SchemaService.SchemaOptions 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.PersistentIdentity::class.java,
PersistentIdentityService.PersistentIdentityNames::class.java, PersistentIdentityService.PersistentIdentityNames::class.java,
ContractUpgradeServiceImpl.DBContractUpgrade::class.java, ContractUpgradeServiceImpl.DBContractUpgrade::class.java,
DBNetworkParametersStorage.PersistentNetworkParameters::class.java,
PersistentKeyManagementService.PublicKeyHashToExternalId::class.java PersistentKeyManagementService.PublicKeyHashToExternalId::class.java
)) { )) {
override val migrationResource = "node-core.changelog-master" 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.INPUTS_GROUP)
checkAllComponentsVisible(ComponentGroupEnum.TIMEWINDOW_GROUP) checkAllComponentsVisible(ComponentGroupEnum.TIMEWINDOW_GROUP)
checkAllComponentsVisible(ComponentGroupEnum.REFERENCES_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 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 -> { else -> {
throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," + throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," +
"expected either ${FilteredTransaction::class.java.simpleName} or ${NotaryChangeWireTransaction::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 { override fun extractParts(requestPayload: NotarisationPayload): TransactionParts {
val stx = requestPayload.signedTransaction val stx = requestPayload.signedTransaction
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null 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 == "BFTNonValidatingNotaryService_transactions" -> caffeine.maximumSize(defaultCacheSize)
name == "RaftUniquenessProvider_transactions" -> caffeine.maximumSize(defaultCacheSize) name == "RaftUniquenessProvider_transactions" -> caffeine.maximumSize(defaultCacheSize)
name == "BasicHSMKeyManagementService_keys" -> 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?") else -> throw IllegalArgumentException("Unexpected cache name $name. Did you add a new cache?")
} }
} }

View File

@ -1,8 +1,6 @@
package net.corda.node.messaging package net.corda.node.messaging
import co.paralleluniverse.fibers.Suspendable 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.concurrent.CordaFuture
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.* 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.contracts.asset.Cash
import net.corda.finance.flows.TwoPartyTradeFlow.Buyer import net.corda.finance.flows.TwoPartyTradeFlow.Buyer
import net.corda.finance.flows.TwoPartyTradeFlow.Seller 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.api.WritableTransactionStorage
import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.checkpoints 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.dsl.TestTransactionDSLInterpreter
import net.corda.testing.internal.LogHelper import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.TEST_TX_TIME 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.internal.vault.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.*
import net.corda.testing.node.ledger import net.corda.testing.node.ledger
import org.assertj.core.api.Assertions.assertThat 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. * 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) @RunWith(Parameterized::class)
class TwoPartyTradeFlowTests(private val anonymous: Boolean) { class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
companion object { companion object {
@ -80,7 +76,6 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
fun data(): Collection<Boolean> = listOf(true, false) fun data(): Collection<Boolean> = listOf(true, false)
private val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) 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 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 // we run in the unit test thread exclusively to speed things up, ensure deterministic results and
// allow interruption half way through. // allow interruption half way through.
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true) 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 aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME)
val bankNode = mockNet.createPartyNode(BOC_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME)
val alice = aliceNode.info.singleIdentity() val alice = aliceNode.info.singleIdentity()
val bank = bankNode.info.singleIdentity() val bank = bankNode.info.singleIdentity()
doReturn(null).whenever(ledgerIdentityService).partyFromKey(bank.owningKey)
val bob = bobNode.info.singleIdentity() val bob = bobNode.info.singleIdentity()
val notary = mockNet.defaultNotaryIdentity
val cashIssuer = bank.ref(1) val cashIssuer = bank.ref(1)
val cpIssuer = bank.ref(1, 2, 3) val cpIssuer = bank.ref(1, 2, 3)
@ -157,15 +150,13 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test(expected = InsufficientBalanceException::class) @Test(expected = InsufficientBalanceException::class)
fun `trade cash for commercial paper fails using soft locking`() { fun `trade cash for commercial paper fails using soft locking`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true) 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 aliceNode = mockNet.createPartyNode(ALICE_NAME)
val bobNode = mockNet.createPartyNode(BOB_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME)
val bankNode = mockNet.createPartyNode(BOC_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME)
val alice = aliceNode.info.singleIdentity() val alice = aliceNode.info.singleIdentity()
val bank = bankNode.info.singleIdentity() val bank = bankNode.info.singleIdentity()
doReturn(null).whenever(ledgerIdentityService).partyFromKey(bank.owningKey)
val bob = bobNode.info.singleIdentity() val bob = bobNode.info.singleIdentity()
val issuer = bank.ref(1) val issuer = bank.ref(1)
val notary = mockNet.defaultNotaryIdentity val notary = mockNet.defaultNotaryIdentity
@ -215,9 +206,9 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test @Test
fun `shutdown and restore`() { fun `shutdown and restore`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages)) 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) val aliceNode = mockNet.createPartyNode(ALICE_NAME)
var bobNode = mockNet.createPartyNode(BOB_NAME) var bobNode = mockNet.createPartyNode(BOB_NAME)
val bankNode = mockNet.createPartyNode(BOC_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME)
@ -227,10 +218,8 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
val bobAddr = bobNode.network.myAddress val bobAddr = bobNode.network.myAddress
mockNet.runNetwork() // Clear network map registration messages mockNet.runNetwork() // Clear network map registration messages
val notary = mockNet.defaultNotaryIdentity
val alice = aliceNode.info.singleIdentity() val alice = aliceNode.info.singleIdentity()
val bank = bankNode.info.singleIdentity() val bank = bankNode.info.singleIdentity()
doReturn(null).whenever(ledgerIdentityService).partyFromKey(bank.owningKey)
val bob = bobNode.info.singleIdentity() val bob = bobNode.info.singleIdentity()
val issuer = bank.ref(1, 2, 3) val issuer = bank.ref(1, 2, 3)
@ -336,7 +325,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
val bob = bobNode.info.singleIdentity() val bob = bobNode.info.singleIdentity()
val bank = bankNode.info.singleIdentity() val bank = bankNode.info.singleIdentity()
val issuer = bank.ref(1, 2, 3) 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. // Insert a prospectus type attachment into the commercial paper transaction.
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
JarOutputStream(stream).use { JarOutputStream(stream).use {
@ -440,7 +429,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
val bank: Party = bankNode.info.singleIdentity() val bank: Party = bankNode.info.singleIdentity()
val bob = bobNode.info.singleIdentity() val bob = bobNode.info.singleIdentity()
val issuer = bank.ref(1, 2, 3) 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. // Insert a prospectus type attachment into the commercial paper transaction.
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
JarOutputStream(stream).use { JarOutputStream(stream).use {
@ -508,18 +497,16 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test @Test
fun `dependency with error on buyer side`() { fun `dependency with error on buyer side`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages)) mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>() mockNet.defaultNotaryNode.services.ledger(DUMMY_NOTARY) {
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(true, false, "at least one cash input")
runWithError(ledgerIdentityService, true, false, "at least one cash input")
} }
} }
@Test @Test
fun `dependency with error on seller side`() { fun `dependency with error on seller side`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages)) mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>() mockNet.defaultNotaryNode.services.ledger(DUMMY_NOTARY) {
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(false, true, "Issuances have a time-window")
runWithError(ledgerIdentityService, 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) data class TestTx(val notaryIdentity: Party, val price: Amount<Currency>, val anonymous: Boolean)
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError( private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError(
ledgerIdentityService: IdentityServiceInternal,
bobError: Boolean, bobError: Boolean,
aliceError: Boolean, aliceError: Boolean,
expectedMessageSubstring: String expectedMessageSubstring: String
@ -595,7 +581,6 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
val alice = aliceNode.info.singleIdentity() val alice = aliceNode.info.singleIdentity()
val bob = bobNode.info.singleIdentity() val bob = bobNode.info.singleIdentity()
val bank = bankNode.info.singleIdentity() val bank = bankNode.info.singleIdentity()
doReturn(null).whenever(ledgerIdentityService).partyFromKey(bank.owningKey)
val issuer = bank.ref(1, 2, 3) val issuer = bank.ref(1, 2, 3)
val bobsBadCash = bobNode.database.transaction { val bobsBadCash = bobNode.database.transaction {
@ -624,7 +609,6 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
} }
} }
private fun insertFakeTransactions( private fun insertFakeTransactions(
wtxToSign: List<WireTransaction>, wtxToSign: List<WireTransaction>,
node: TestStartedNode, node: TestStartedNode,
@ -736,7 +720,6 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
return Pair(vault, listOf(ap)) return Pair(vault, listOf(ap))
} }
class RecordingTransactionStorage( class RecordingTransactionStorage(
private val database: CordaPersistence, private val database: CordaPersistence,
private val delegate: WritableTransactionStorage private val delegate: WritableTransactionStorage
@ -777,5 +760,4 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
data class Add(val transaction: SignedTransaction) : TxRecord data class Add(val transaction: SignedTransaction) : TxRecord
data class Get(val id: SecureHash) : 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.api.NetworkMapCacheInternal
import net.corda.node.services.config.NetworkParameterAcceptanceSettings import net.corda.node.services.config.NetworkParameterAcceptanceSettings
import net.corda.core.internal.NODE_INFO_DIRECTORY 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.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
@ -64,6 +65,7 @@ class NetworkMapUpdaterTest {
private val networkMapCache = createMockNetworkMapCache() private val networkMapCache = createMockNetworkMapCache()
private lateinit var ourKeyPair: KeyPair private lateinit var ourKeyPair: KeyPair
private lateinit var ourNodeInfo: SignedNodeInfo private lateinit var ourNodeInfo: SignedNodeInfo
private val networkParametersStorage: NetworkParametersStorageInternal = mock()
private lateinit var server: NetworkMapServer private lateinit var server: NetworkMapServer
private lateinit var networkMapClient: NetworkMapClient private lateinit var networkMapClient: NetworkMapClient
private lateinit var updater: NetworkMapUpdater private lateinit var updater: NetworkMapUpdater
@ -86,7 +88,7 @@ class NetworkMapUpdaterTest {
} }
private fun setUpdater(extraNetworkMapKeys: List<UUID> = emptyList(), netMapClient: NetworkMapClient? = networkMapClient) { 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, private fun startUpdater(ourNodeInfo: SignedNodeInfo = this.ourNodeInfo,
@ -236,6 +238,7 @@ class NetworkMapUpdaterTest {
val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME
assert(!updateFile.exists()) { "network parameters should not be auto accepted" } assert(!updateFile.exists()) { "network parameters should not be auto accepted" }
updater.acceptNewNetworkParameters(newHash) { it.serialize().sign(ourKeyPair) } updater.acceptNewNetworkParameters(newHash) { it.serialize().sign(ourKeyPair) }
verify(networkParametersStorage, times(1)).saveParameters(any())
val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>() val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>()
val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate) val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
assertEquals(newParameters, paramsFromFile) assertEquals(newParameters, paramsFromFile)

View File

@ -4,21 +4,18 @@ import co.paralleluniverse.fibers.Suspendable
import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.KryoException
import net.corda.core.contracts.UniqueIdentifier import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowLogic.Companion.sleep
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestIdentityService import net.corda.testing.node.makeTestIdentityService
import org.junit.BeforeClass import org.junit.After
import org.junit.ClassRule import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.Serializable import java.io.Serializable
import java.time.Duration
import javax.persistence.Column import javax.persistence.Column
import javax.persistence.Entity import javax.persistence.Entity
import javax.persistence.Id import javax.persistence.Id
@ -26,7 +23,6 @@ import javax.persistence.Table
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class ExposeJpaToFlowsTests { class ExposeJpaToFlowsTests {
object FooSchema object FooSchema
@ -39,15 +35,28 @@ class ExposeJpaToFlowsTests {
val myself = TestIdentity(CordaX500Name("Me", "London", "GB")) val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
val cordapps = listOf("net.corda.node.services.persistence") val cordapps = listOf("net.corda.node.services.persistence")
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices( lateinit var mockNet: MockNetwork
lateinit var services: MockServices
lateinit var database: CordaPersistence
@Before
fun setUp() {
mockNet = MockNetwork(cordapps)
val (db, mockServices) = MockServices.makeTestDatabaseAndMockServices(
cordappPackages = cordapps, cordappPackages = cordapps,
identityService = makeTestIdentityService(myself.identity), identityService = makeTestIdentityService(myself.identity),
initialIdentity = myself, initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4) networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
) )
val services: MockServices = databaseAndServices.second services = mockServices
val database: CordaPersistence = databaseAndServices.first database = db
}
@After
fun cleanUp() {
mockNet.stopNodes()
}
@Test @Test
fun `can persist and query custom entities`() { fun `can persist and query custom entities`() {
@ -71,7 +80,6 @@ class ExposeJpaToFlowsTests {
@Test @Test
fun `can't perform suspendable operations inside withEntityManager`() { fun `can't perform suspendable operations inside withEntityManager`() {
val mockNet = MockNetwork(cordapps)
val mockNode = mockNet.createNode() val mockNode = mockNet.createNode()
assertFailsWith(KryoException::class) { assertFailsWith(KryoException::class) {
mockNode.startFlow(object : FlowLogic<Unit>() { 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.codahale.metrics.MetricRegistry
import com.google.common.jimfs.Configuration import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs 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.contracts.ContractAttachment
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 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.node.services.transactions.PersistentUniquenessProvider
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.JarSignatureTestUtils.createJar import net.corda.testing.core.JarSignatureTestUtils.createJar
import net.corda.testing.core.JarSignatureTestUtils.generateKey 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.InternalMockNetwork
import net.corda.testing.node.internal.startFlow import net.corda.testing.node.internal.startFlow
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException 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.ByteArrayOutputStream
import java.io.Closeable import java.io.Closeable
import java.io.OutputStream import java.io.OutputStream
@ -52,7 +52,6 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
import kotlin.test.assertNull import kotlin.test.assertNull
class NodeAttachmentServiceTest { class NodeAttachmentServiceTest {
// Use an in memory file system for testing attachment storage. // Use an in memory file system for testing attachment storage.
@ -69,8 +68,6 @@ class NodeAttachmentServiceTest {
database = configureDatabase(dataSourceProperties, DatabaseConfig(), { null }, { null }) database = configureDatabase(dataSourceProperties, DatabaseConfig(), { null }, { null })
fs = Jimfs.newFileSystem(Configuration.unix()) fs = Jimfs.newFileSystem(Configuration.unix())
doReturn(testNetworkParameters()).whenever(services).networkParameters
storage = NodeAttachmentService(MetricRegistry(), TestingNamedCacheFactory(), database).also { storage = NodeAttachmentService(MetricRegistry(), TestingNamedCacheFactory(), database).also {
database.transaction { database.transaction {
it.start() it.start()
@ -427,5 +424,4 @@ class NodeAttachmentServiceTest {
return Paths.get(fileManager.list(StandardLocation.CLASS_OUTPUT, "", setOf(JavaFileObject.Kind.CLASS), true).single().name) 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)) mockNet = MockNetwork(listOf("net.corda.testing.contracts"), networkParameters = testNetworkParameters(maxTransactionSize = 3_000_000))
aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
bobNode.registerInitiatedFlow(ReceiveLargeTransactionFlow::class.java)
notaryNode = mockNet.defaultNotaryNode notaryNode = mockNet.defaultNotaryNode
notary = mockNet.defaultNotaryIdentity notary = mockNet.defaultNotaryIdentity
alice = aliceNode.info.singleIdentity() alice = aliceNode.info.singleIdentity()

View File

@ -3,18 +3,23 @@ package net.corda.node.services.transactions
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.NotaryChangeTransactionBuilder import net.corda.core.internal.NotaryChangeTransactionBuilder
import net.corda.core.node.ServiceHub 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.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow 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.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetworkNotarySpec import net.corda.testing.node.MockNetworkNotarySpec
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -31,7 +36,8 @@ class NotaryServiceTests {
fun setup() { fun setup() {
mockNet = InternalMockNetwork( mockNet = InternalMockNetwork(
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), 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)) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
notaryServices = mockNet.defaultNotaryNode.services //TODO get rid of that notaryServices = mockNet.defaultNotaryNode.services //TODO get rid of that
@ -49,6 +55,28 @@ class NotaryServiceTests {
notariseWithTooManyInputs(aliceNode, alice, notary, mockNet) 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 { internal companion object {
/** This is used by both [NotaryServiceTests] and [ValidatingNotaryServiceTests]. */ /** This is used by both [NotaryServiceTests] and [ValidatingNotaryServiceTests]. */
fun notariseWithTooManyInputs(node: TestStartedNode, party: Party, notary: Party, network: InternalMockNetwork) { fun notariseWithTooManyInputs(node: TestStartedNode, party: Party, notary: Party, network: InternalMockNetwork) {
@ -59,10 +87,17 @@ class NotaryServiceTests {
assertFailsWith<NotaryException> { future.getOrThrow() } 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 txHash = SecureHash.randomSHA256()
val inputs = (1..10_005).map { StateRef(txHash, it) } val inputs = (1..numberOfInputs).map { StateRef(txHash, it) }
val tx = NotaryChangeTransactionBuilder(inputs, notary, party).build() val tx = if (paramsHash != null) {
NotaryChangeTransactionBuilder(inputs, notary, party, paramsHash).build()
} else {
NotaryChangeWireTransaction(listOf(inputs, notary, party).map { it.serialize() })
}
return node.services.run { return node.services.run {
val myKey = myInfo.legalIdentities.first().owningKey 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.core.TestIdentity
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestIdentityService import net.corda.testing.node.makeTestIdentityService
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -25,14 +26,7 @@ class ResolveStatePointersTest {
private val myself = TestIdentity(CordaX500Name("Me", "London", "GB")) private val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
private val notary = TestIdentity(DUMMY_NOTARY_NAME, 20) private val notary = TestIdentity(DUMMY_NOTARY_NAME, 20)
private val cordapps = listOf("net.corda.testing.contracts") private val cordapps = listOf("net.corda.testing.contracts")
private val databaseAndServices = MockServices.makeTestDatabaseAndMockServices( private lateinit var services: MockServices
cordappPackages = cordapps,
identityService = makeTestIdentityService(notary.identity, myself.identity),
initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
)
private val services = databaseAndServices.second
private data class Bar( private data class Bar(
override val participants: List<AbstractParty> = listOf(), 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 @Test
fun `resolve state pointers and check reference state is added to transaction`() { fun `resolve state pointers and check reference state is added to transaction`() {
val stateAndRef = createPointedToState(barOne) val stateAndRef = createPointedToState(barOne)
@ -154,5 +159,4 @@ class ResolveStatePointersTest {
val foo = ltx.outputs.single().data as Foo<Bar> val foo = ltx.outputs.single().data as Foo<Bar>
assertEquals(stateAndRef, foo.baz.resolve(ltx)) 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.concurrent.CordaFuture
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.* 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.serialization.serialize
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.services.issueInvalidState import net.corda.node.services.issueInvalidState
import net.corda.node.services.messaging.Message import net.corda.node.services.messaging.Message
import net.corda.node.services.statemachine.InitialSessionMessage import net.corda.node.services.statemachine.InitialSessionMessage
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.dummyCommand import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.internal.createWireTransaction
import net.corda.testing.node.TestClock import net.corda.testing.node.TestClock
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.*
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
@ -46,7 +50,8 @@ class ValidatingNotaryServiceTests {
@Before @Before
fun setup() { 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)) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
notaryNode = mockNet.defaultNotaryNode notaryNode = mockNet.defaultNotaryNode
notary = mockNet.defaultNotaryIdentity notary = mockNet.defaultNotaryIdentity
@ -96,6 +101,27 @@ class ValidatingNotaryServiceTests {
assertEquals(setOf(expectedMissingKey), missingKeys) 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 @Test
fun `should sign a unique transaction with a valid time-window`() { fun `should sign a unique transaction with a valid time-window`() {
val stx = run { val stx = run {

View File

@ -19,6 +19,7 @@ import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.util.* import java.util.*
@ -37,7 +38,13 @@ class ExternalIdMappingTest {
private val myself = TestIdentity(CordaX500Name("Me", "London", "GB")) private val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
private val notary = TestIdentity(CordaX500Name("NotaryService", "London", "GB"), 1337L) private val notary = TestIdentity(CordaX500Name("NotaryService", "London", "GB"), 1337L)
private val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
lateinit var services: MockServices
lateinit var database: CordaPersistence
@Before
fun setUp() {
val (db, mockServices) = MockServices.makeTestDatabaseAndMockServices(
cordappPackages = cordapps, cordappPackages = cordapps,
identityService = rigorousMock<IdentityServiceInternal>().also { identityService = rigorousMock<IdentityServiceInternal>().also {
doReturn(notary.party).whenever(it).partyFromKey(notary.publicKey) doReturn(notary.party).whenever(it).partyFromKey(notary.publicKey)
@ -47,9 +54,9 @@ class ExternalIdMappingTest {
initialIdentity = myself, initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4) networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
) )
services = mockServices
private val services: MockServices = databaseAndServices.second database = db
private val database: CordaPersistence = databaseAndServices.first }
private fun freshKeyForExternalId(externalId: UUID): AnonymousParty { private fun freshKeyForExternalId(externalId: UUID): AnonymousParty {
val anonymousParty = freshKey() 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.IdentityServiceInternal
import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.api.WritableTransactionStorage
import net.corda.nodeapi.internal.persistence.CordaPersistence 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.DummyContract
import net.corda.testing.contracts.DummyState import net.corda.testing.contracts.DummyState
import net.corda.testing.core.* import net.corda.testing.core.*
@ -90,17 +91,19 @@ class NodeVaultServiceTest {
@Before @Before
fun setUp() { fun setUp() {
LogHelper.setLevel(NodeVaultService::class) LogHelper.setLevel(NodeVaultService::class)
val parameters = testNetworkParameters()
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices( val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
cordappPackages, cordappPackages,
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
megaCorp) megaCorp,
parameters)
database = databaseAndServices.first database = databaseAndServices.first
services = databaseAndServices.second services = databaseAndServices.second
vaultFiller = VaultFiller(services, dummyNotary) vaultFiller = VaultFiller(services, dummyNotary)
// This is safe because MockServices only ever have a single identity // This is safe because MockServices only ever have a single identity
identity = services.myInfo.singleIdentityAndCert() identity = services.myInfo.singleIdentityAndCert()
issuerServices = MockServices(cordappPackages, dummyCashIssuer, rigorousMock<IdentityService>()) issuerServices = MockServices(cordappPackages, dummyCashIssuer, rigorousMock<IdentityService>(), parameters)
bocServices = MockServices(cordappPackages, bankOfCorda, rigorousMock<IdentityService>()) bocServices = MockServices(cordappPackages, bankOfCorda, rigorousMock<IdentityService>(), parameters)
services.identityService.verifyAndRegisterIdentity(DUMMY_CASH_ISSUER_IDENTITY) services.identityService.verifyAndRegisterIdentity(DUMMY_CASH_ISSUER_IDENTITY)
services.identityService.verifyAndRegisterIdentity(BOC_IDENTITY) services.identityService.verifyAndRegisterIdentity(BOC_IDENTITY)
} }
@ -571,7 +574,6 @@ class NodeVaultServiceTest {
} }
// TODO: Unit test linear state relevancy checks // TODO: Unit test linear state relevancy checks
@Test @Test
fun `correct updates are generated for general transactions`() { fun `correct updates are generated for general transactions`() {
val notary = identity.party val notary = identity.party
@ -647,7 +649,7 @@ class NodeVaultServiceTest {
// Change notary // Change notary
services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY) services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY)
val newNotary = DUMMY_NOTARY 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)) val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0))
database.transaction { database.transaction {

View File

@ -16,6 +16,10 @@ import org.junit.rules.ExpectedException
class VaultQueryExceptionsTests : VaultQueryParties by rule { class VaultQueryExceptionsTests : VaultQueryParties by rule {
companion object { companion object {
@ClassRule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@ClassRule @ClassRule
@JvmField @JvmField
val rule = object : VaultQueryTestRule() { val rule = object : VaultQueryTestRule() {
@ -26,10 +30,6 @@ class VaultQueryExceptionsTests : VaultQueryParties by rule {
} }
} }
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Rule @Rule
@JvmField @JvmField
val expectedEx: ExpectedException = ExpectedException.none() val expectedEx: ExpectedException = ExpectedException.none()

View File

@ -99,7 +99,6 @@ interface VaultQueryParties {
} }
open class VaultQueryTestRule : ExternalResource(), VaultQueryParties { open class VaultQueryTestRule : ExternalResource(), VaultQueryParties {
override val alice = TestIdentity(ALICE_NAME, 70) override val alice = TestIdentity(ALICE_NAME, 70)
override val bankOfCorda = TestIdentity(BOC_NAME) override val bankOfCorda = TestIdentity(BOC_NAME)
override val bigCorp = TestIdentity(CordaX500Name("BigCorporation", "New York", "US")) 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.flows.StateMachineRunId
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowProgressHandle 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.*
import net.corda.core.node.services.* import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.NetworkParametersStorageInternal
import net.corda.node.internal.ServicesForResolutionImpl import net.corda.node.internal.ServicesForResolutionImpl
import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.api.* import net.corda.node.services.api.*
@ -40,9 +43,11 @@ import net.corda.testing.services.MockAttachmentStorage
import java.security.KeyPair import java.security.KeyPair
import java.sql.Connection import java.sql.Connection
import java.time.Clock import java.time.Clock
import java.time.Instant
import java.util.* import java.util.*
import java.util.function.Consumer import java.util.function.Consumer
import javax.persistence.EntityManager import javax.persistence.EntityManager
import kotlin.collections.HashMap
/** /**
* Returns a simple [InMemoryIdentityService] containing the supplied [identities]. * Returns a simple [InMemoryIdentityService] containing the supplied [identities].
@ -104,7 +109,7 @@ open class MockServices private constructor(
fun makeTestDatabaseAndMockServices(cordappPackages: List<String>, fun makeTestDatabaseAndMockServices(cordappPackages: List<String>,
identityService: IdentityService, identityService: IdentityService,
initialIdentity: TestIdentity, initialIdentity: TestIdentity,
networkParameters: NetworkParameters = testNetworkParameters(), networkParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN),
vararg moreKeys: KeyPair): Pair<CordaPersistence, MockServices> { vararg moreKeys: KeyPair): Pair<CordaPersistence, MockServices> {
val cordappLoader = cordappLoaderForPackages(cordappPackages) val cordappLoader = cordappLoaderForPackages(cordappPackages)
@ -114,7 +119,7 @@ open class MockServices private constructor(
val mockService = database.transaction { val mockService = database.transaction {
object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) { object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) {
override val vaultService: VaultService = makeVaultService(schemaService, database) override val vaultService: VaultService = makeVaultService(schemaService, database)
override val networkParametersStorage: NetworkParametersStorage get() = MockNetworkParametersStorage(networkParameters)
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) { override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
ServiceHubInternal.recordTransactions(statesToRecord, txs, ServiceHubInternal.recordTransactions(statesToRecord, txs,
validatedTransactions as WritableTransactionStorage, validatedTransactions as WritableTransactionStorage,
@ -164,7 +169,7 @@ open class MockServices private constructor(
initialIdentity: TestIdentity, initialIdentity: TestIdentity,
identityService: IdentityService = makeTestIdentityService(), identityService: IdentityService = makeTestIdentityService(),
vararg moreKeys: KeyPair) : vararg moreKeys: KeyPair) :
this(cordappLoaderForPackages(cordappPackages), identityService, testNetworkParameters(), initialIdentity, moreKeys) this(cordappLoaderForPackages(cordappPackages), identityService, testNetworkParameters(modifiedTime = Instant.MIN), initialIdentity, moreKeys)
constructor(cordappPackages: Iterable<String>, constructor(cordappPackages: Iterable<String>,
initialIdentity: TestIdentity, initialIdentity: TestIdentity,
@ -255,12 +260,11 @@ open class MockServices private constructor(
it.start(networkParameters.whitelistedContractImplementations) it.start(networkParameters.whitelistedContractImplementations)
} }
override val cordappProvider: CordappProvider get() = mockCordappProvider override val cordappProvider: CordappProvider get() = mockCordappProvider
override val networkParametersStorage: NetworkParametersStorage get() = MockNetworkParametersStorage(networkParameters)
protected val servicesForResolution: ServicesForResolution protected val servicesForResolution: ServicesForResolution
get() { get() {
return ServicesForResolutionImpl(identityService, attachments, cordappProvider, validatedTransactions).also { return ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, validatedTransactions)
it.start(networkParameters)
}
} }
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence): VaultServiceInternal { internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence): VaultServiceInternal {

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?, notary: Party?,
timeWindow: TimeWindow?, timeWindow: TimeWindow?,
privacySalt: PrivacySalt = PrivacySalt()): WireTransaction { 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) return WireTransaction(componentGroups, privacySalt)
} }