CORDA-2115: Notary whitelist verification changes (#4293)

* CORDA-2115: Notary whitelist verification changes

- For regular and contract upgrade transactions: check that the notary is in the network parameter whitelist
- For notary change transactions: check the the new notary is in the network parameter whitelist. This enabled support for network merging: the old notary doesn't have to be in the current network's notary whitelist for re-pointing old states to another notary.

These checks are done during transaction construction/verification and also by the non-validating notary.

* Address comments

* Remove stale todo

* Use notary whitelist of current network parameters for platform versoin 3

* Cleanup test

* Move `getHistoricNotary` to `HistoricNetworkParameterStorage` in `core.internal`

* Require `newNotary` to be notary on the network map during notary change
This commit is contained in:
Andrius Dagys 2018-12-04 13:54:24 +00:00 committed by Thomas Schroeter
parent 36c5b0cbae
commit 838c99c6e4
48 changed files with 810 additions and 215 deletions

View File

@ -2278,8 +2278,6 @@ public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.cor
@NotNull
public java.util.List<net.corda.core.crypto.TransactionSignature> call()
@NotNull
protected final net.corda.core.identity.Party checkTransaction()
@NotNull
public net.corda.core.utilities.ProgressTracker getProgressTracker()
@Suspendable
@NotNull
@ -6383,7 +6381,7 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
@NotNull
public net.corda.core.node.services.NetworkMapCache getNetworkMapCache()
@NotNull
public final net.corda.core.node.NetworkParameters getNetworkParameters()
public net.corda.core.node.NetworkParameters getNetworkParameters()
@NotNull
protected final net.corda.core.node.ServicesForResolution getServicesForResolution()
@NotNull

View File

@ -1,6 +1,7 @@
package net.corda.client.rpc;
import net.corda.core.contracts.Amount;
import net.corda.core.identity.Party;
import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.messaging.FlowHandle;
import net.corda.core.utilities.OpaqueBytes;
@ -28,10 +29,11 @@ import static net.corda.finance.contracts.GetBalances.getCashBalance;
import static net.corda.node.services.Permissions.invokeRpc;
import static net.corda.node.services.Permissions.startFlow;
import static net.corda.testing.core.TestConstants.ALICE_NAME;
import static net.corda.testing.core.TestConstants.DUMMY_NOTARY_NAME;
public class CordaRPCJavaClientTest extends NodeBasedTest {
public CordaRPCJavaClientTest() {
super(Arrays.asList("net.corda.finance.contracts", CashSchemaV1.class.getPackage().getName()));
super(Arrays.asList("net.corda.finance.contracts", CashSchemaV1.class.getPackage().getName()), Collections.singletonList(DUMMY_NOTARY_NAME));
}
private List<String> perms = Arrays.asList(
@ -73,9 +75,10 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
public void testCashBalances() throws ExecutionException, InterruptedException {
login(rpcUser.getUsername(), rpcUser.getPassword());
Party notaryIdentity = InternalTestUtilsKt.chooseIdentity(getNotaryNodes().get(0).getInfo());
FlowHandle<AbstractCashFlow.Result> flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class,
DOLLARS(123), OpaqueBytes.of((byte)0),
InternalTestUtilsKt.chooseIdentity(node.getInfo()));
notaryIdentity);
System.out.println("Started issuing cash, waiting on result");
flowHandle.getReturnValue().get();

View File

@ -9,6 +9,7 @@ import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.location
import net.corda.core.internal.toPath
import net.corda.core.messaging.*
import net.corda.core.node.NotaryInfo
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
@ -45,7 +46,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance"), notaries = listOf(DUMMY_NOTARY_NAME)) {
companion object {
val rpcUser = User("user1", "test", permissions = setOf(all()))
}
@ -65,7 +66,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
client = CordaRPCClient(node.node.configuration.rpcOptions.address, CordaRPCClientConfiguration.DEFAULT.copy(
maxReconnectAttempts = 5
))
identity = node.info.identityFromX500Name(ALICE_NAME)
identity = notaryNodes.first().info.identityFromX500Name(DUMMY_NOTARY_NAME)
}
@After

View File

@ -102,7 +102,8 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
transaction.pushToLoggingContext()
logCommandData()
val externalParticipants = extractExternalParticipants(verifyTx())
val ledgerTransaction = verifyTx()
val externalParticipants = extractExternalParticipants(ledgerTransaction)
if (sessions != null) {
val missingRecipients = externalParticipants - sessions.map { it.counterparty }

View File

@ -2,7 +2,6 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.DoNotImplement
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
@ -10,6 +9,7 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party
import net.corda.core.internal.BackpressureAwareTimedFlow
import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.notary.HistoricNetworkParameterStorage
import net.corda.core.internal.notary.generateSignature
import net.corda.core.internal.notary.validateSignatures
import net.corda.core.internal.pushToLoggingContext
@ -27,6 +27,8 @@ class NotaryFlow {
* In case of a single-node or Raft notary, the flow will return a single signature. For the BFT notary multiple
* signatures will be returned one from each replica that accepted the input state commit.
*
* The transaction to be notarised, [stx], should be fully verified before calling this flow.
*
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
* by another transaction or the time-window is invalid or
* the parameters used for this transaction are no longer in force in the network.
@ -51,8 +53,8 @@ class NotaryFlow {
override fun call(): List<TransactionSignature> {
stx.pushToLoggingContext()
val notaryParty = checkTransaction()
progressTracker.currentStep = REQUESTING
logger.info("Sending transaction to notary: ${notaryParty.name}.")
progressTracker.currentStep = REQUESTING
val response = notarise(notaryParty)
logger.info("Notary responded.")
progressTracker.currentStep = VALIDATING
@ -63,6 +65,7 @@ class NotaryFlow {
* Checks that the transaction specifies a valid notary, and verifies that it contains all required signatures
* apart from the notary's.
*/
// TODO: [CORDA-2274] Perform full transaction verification once verification caching is enabled.
protected fun checkTransaction(): Party {
val notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
check(serviceHub.networkMapCache.isNotary(notaryParty)) { "$notaryParty is not a notary on the network" }
@ -72,20 +75,37 @@ class NotaryFlow {
stx.resolveTransactionWithSignatures(serviceHub).verifySignaturesExcept(notaryParty.owningKey)
return notaryParty
}
/** Notarises the transaction with the [notaryParty], obtains the notary's signature(s). */
@Throws(NotaryException::class)
@Suspendable
protected fun notarise(notaryParty: Party): UntrustworthyData<NotarisationResponse> {
val session = initiateFlow(notaryParty)
val requestSignature = generateRequestSignature()
return if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
return if (isValidating(notaryParty)) {
sendAndReceiveValidating(session, requestSignature)
} else {
sendAndReceiveNonValidating(notaryParty, session, requestSignature)
}
}
private fun isValidating(notaryParty: Party): Boolean {
val onTheCurrentWhitelist = serviceHub.networkMapCache.isNotary(notaryParty)
return if (!onTheCurrentWhitelist) {
/*
Note that the only scenario where it's acceptable to use a notary not in the current network parameter whitelist is
when performing a notary change transaction after a network merge the old notary won't be on the whitelist of the new network,
and can't be used for regular transactions.
*/
check(stx.coreTransaction is NotaryChangeWireTransaction) {
"Notary $notaryParty is not on the network parameter whitelist. A non-whitelisted notary can only be used for notary change transactions"
}
val historicNotary = (serviceHub.networkParametersStorage as HistoricNetworkParameterStorage).getHistoricNotary(notaryParty)
?: throw IllegalStateException("The notary party $notaryParty specified by transaction ${stx.id}, is not recognised as a current or historic notary.")
historicNotary.validating
} else serviceHub.networkMapCache.isValidatingNotary(notaryParty)
}
@Suspendable
private fun sendAndReceiveValidating(session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<NotarisationResponse> {
val payload = NotarisationPayload(stx, signature)

View File

@ -0,0 +1,11 @@
package net.corda.core.internal.notary
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
interface HistoricNetworkParameterStorage {
/**
* Returns the [NotaryInfo] for a notary [party] in the current or any historic network parameter whitelist, or null if not found.
*/
fun getHistoricNotary(party: Party): NotaryInfo?
}

View File

@ -4,17 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotarisationRequest
import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.flows.NotarisationResponse
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
import net.corda.core.flows.WaitTimeUpdate
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.MIN_PLATFORM_VERSION_FOR_BACKPRESSURE_MESSAGE
import net.corda.core.internal.checkParameterHash
@ -55,9 +45,6 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
@Suspendable
override fun call(): Void? {
check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) {
"We are not a notary on the network"
}
val requestPayload = otherSideSession.receive<NotarisationPayload>().unwrap { it }
try {
@ -123,6 +110,21 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
}
}
/**
* Check that network parameters hash on this transaction is the current hash for the network.
*/
// TODO: ENT-2666 Implement network parameters fuzzy checking. By design in Corda network we have propagation time delay.
// We will never end up in perfect synchronization with all the nodes. However, network parameters update process
// lets us predict what is the reasonable time window for changing parameters on most of the nodes.
@Suspendable
protected fun checkParametersHash(networkParametersHash: SecureHash?) {
if (networkParametersHash == null && serviceHub.networkParameters.minimumPlatformVersion < 4) return
val notaryParametersHash = serviceHub.networkParametersStorage.currentHash
require(notaryParametersHash == networkParametersHash) {
"Transaction for notarisation was tagged with parameters with hash: $networkParametersHash, but current network parameters are: $notaryParametersHash"
}
}
/** Verifies that the correct notarisation request was signed by the counterparty. */
private fun validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) {
val requestingParty = otherSideSession.counterparty
@ -133,8 +135,7 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
* Override to implement custom logic to perform transaction verification based on validity and privacy requirements.
*/
@Suspendable
protected open fun verifyTransaction(requestPayload: NotarisationPayload) {
}
abstract fun verifyTransaction(requestPayload: NotarisationPayload)
@Suspendable
private fun signTransactionAndSendResponse(txId: SecureHash) {
@ -158,7 +159,7 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
private fun logError(error: NotaryError) {
val errorCause = when (error) {
is NotaryError.RequestSignatureInvalid -> error.cause
is NotaryError.TransactionInvalid -> error.cause
is NotaryError.TransactionInvalid -> error.cause
is NotaryError.General -> error.cause
else -> null
}

View File

@ -1,10 +1,10 @@
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.identity.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
/**
* Interface for handling network parameters storage used for resolving transactions according to parameters that were

View File

@ -29,13 +29,12 @@ abstract class CoreTransaction : BaseTransaction() {
abstract class FullTransaction : BaseTransaction() {
abstract override val inputs: List<StateAndRef<ContractState>>
abstract override val references: List<StateAndRef<ContractState>>
// TODO NetworkParameters field is nullable only because of the API stability and the fact that our ledger transactions exposed constructors
// (they were data classes). This should be revisited.
/**
* Network parameters that were in force when this transaction was created. Resolved from the hash of network parameters on the corresponding
* wire transaction.
*/
abstract val networkParameters: NetworkParameters?
override fun checkBaseInvariants() {
super.checkBaseInvariants()
checkInputsAndReferencesHaveSameNotary()
@ -47,4 +46,17 @@ abstract class FullTransaction : BaseTransaction() {
check(notaries.size == 1) { "All inputs and reference inputs must point to the same notary" }
check(notaries.single() == notary) { "The specified notary must be the one specified by all inputs and input references" }
}
/** Make sure the assigned notary is part of the network parameter whitelist. */
protected fun checkNotaryWhitelisted() {
notary?.let { notaryParty ->
// Network parameters will never be null if the transaction is resolved from a CoreTransaction rather than constructed directly.
networkParameters?.let { parameters ->
val notaryWhitelist = parameters.notaries.map { it.identity }
check(notaryParty in notaryWhitelist) {
"Notary ($notaryParty) specified by the transaction is not on the network parameter whitelist: [${notaryWhitelist.joinToString()}]"
}
}
}
}
}

View File

@ -250,6 +250,7 @@ data class ContractUpgradeLedgerTransaction(
private val upgradedContract: UpgradedContract<ContractState, *> = loadUpgradedContract()
init {
checkNotaryWhitelisted()
// TODO: relax this constraint once upgrading encumbered states is supported.
check(inputs.all { it.state.contract == legacyContractClassName }) {
"All input states must point to the legacy contract"

View File

@ -53,8 +53,6 @@ private constructor(
val timeWindow: TimeWindow?,
val privacySalt: PrivacySalt,
/** Network parameters that were in force when the transaction was notarised. */
// TODO Network parameters should never be null on LedgerTransaction, this is left only because of deprecated constructors. We should decide to
// get rid of them.
override val networkParameters: NetworkParameters?,
override val references: List<StateAndRef<ContractState>>
//DOCEND 1
@ -67,6 +65,7 @@ private constructor(
init {
checkBaseInvariants()
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
checkNotaryWhitelisted()
checkNoNotaryChange()
checkEncumbrancesValid()
}

View File

@ -123,8 +123,6 @@ private constructor(
val newNotary: Party,
override val id: SecureHash,
override val sigs: List<TransactionSignature>,
// TODO Network parameters should never be null on NotaryChangeLedgerTransaction, this is left only because of deprecated constructors. We should decide to
// get rid of them.
override val networkParameters: NetworkParameters?
) : FullTransaction(), TransactionWithSignatures {
companion object {
@ -141,6 +139,22 @@ private constructor(
init {
checkEncumbrances()
checkNewNotaryWhitelisted()
}
/**
* Check that the output notary is whitelisted.
*
* Note that for this transaction type we do not require the input notary to be whitelisted to support network merging.
* For all other transaction types this is enforced.
*/
private fun checkNewNotaryWhitelisted() {
networkParameters?.let { parameters ->
val notaryWhitelist = parameters.notaries.map { it.identity }
check(newNotary in notaryWhitelist) {
"The output notary $newNotary is not whitelisted in the attached network parameters."
}
}
}
override val references: List<StateAndRef<ContractState>> = emptyList()

View File

@ -6,6 +6,7 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.NotaryInfo
import net.corda.core.transactions.LedgerTransaction
import net.corda.finance.POUNDS
import net.corda.finance.`issued by`
@ -48,10 +49,14 @@ class ConstraintsPropagationTests {
doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY)
doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY)
},
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
.copy(whitelistedContractImplementations = mapOf(
networkParameters = testNetworkParameters(
minimumPlatformVersion = 4,
whitelistedContractImplementations = mapOf(
Cash.PROGRAM_ID to listOf(SecureHash.zeroHash, SecureHash.allOnesHash),
noPropagationContractClassName to listOf(SecureHash.zeroHash)))
noPropagationContractClassName to listOf(SecureHash.zeroHash)
),
notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))
)
)
@Test

View File

@ -6,6 +6,7 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.NotaryInfo
import net.corda.core.transactions.LedgerTransaction
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.common.internal.testNetworkParameters
@ -43,8 +44,10 @@ class PackageOwnershipVerificationTests {
doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY)
doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY)
},
networkParameters = testNetworkParameters()
.copy(packageOwnership = mapOf("net.corda.core.contracts" to OWNER_KEY_PAIR.public))
networkParameters = testNetworkParameters(
packageOwnership = mapOf("net.corda.core.contracts" to OWNER_KEY_PAIR.public),
notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))
)
)
@Test
@ -70,7 +73,6 @@ class PackageOwnershipVerificationTests {
}
}
}
}
@BelongsToContract(DummyContract::class)

View File

@ -6,6 +6,7 @@ import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.ReferenceStateRef
@ -49,6 +50,7 @@ class PartialMerkleTreeTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val nodes = "abcdef"
private lateinit var hashed: List<SecureHash.SHA256>
private lateinit var expectedRoot: SecureHash
@ -56,6 +58,7 @@ class PartialMerkleTreeTest {
private lateinit var testLedger: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
private lateinit var txs: List<WireTransaction>
private lateinit var testTx: WireTransaction
@Before
fun init() {
hashed = nodes.map { it.serialize().sha256() }
@ -66,8 +69,9 @@ class PartialMerkleTreeTest {
cordappPackages = emptyList(),
initialIdentity = TestIdentity(MEGA_CORP.name),
identityService = rigorousMock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) },
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
},
networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
).ledger(DUMMY_NOTARY) {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)

View File

@ -120,7 +120,7 @@ class WithReferencedStatesFlowTests {
private val mockNet = InternalMockNetwork(
cordappsForAllNodes = cordappsForPackages("net.corda.core.flows", "net.corda.testing.contracts"),
threadPerNode = true,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = 4)
)
}

View File

@ -7,7 +7,9 @@ import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.*
import net.corda.testing.internal.rigorousMock
@ -29,10 +31,15 @@ class LedgerTransactionQueryTests {
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val keyPair = generateKeyPair()
private val services = MockServices(listOf("net.corda.testing.contracts"), CordaX500Name("MegaCorp", "London", "GB"),
private val services = MockServices(
listOf("net.corda.testing.contracts"),
TestIdentity(CordaX500Name("MegaCorp", "London", "GB"), keyPair),
rigorousMock<IdentityServiceInternal>().also {
doReturn(null).whenever(it).partyFromKey(keyPair.public)
}, keyPair)
},
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))),
keyPair
)
private val identity: Party = services.myInfo.singleIdentity()
@Before

View File

@ -8,6 +8,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
@ -49,8 +50,7 @@ class ReferenceStateTests {
doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY)
doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY)
},
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
)
// This state has only been created to serve reference data so it cannot ever be used as an input or

View File

@ -5,10 +5,12 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.NotaryInfo
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
@ -51,11 +53,14 @@ class TransactionEncumbranceTests {
val FIVE_PM: Instant = FOUR_PM.plus(1, ChronoUnit.HOURS)
val timeLock = DummyTimeLock.State(FIVE_PM)
val ledgerServices = MockServices(listOf("net.corda.core.transactions", "net.corda.finance.contracts.asset"), MEGA_CORP.name,
val ledgerServices = MockServices(
listOf("net.corda.core.transactions", "net.corda.finance.contracts.asset"),
MEGA_CORP.name,
rigorousMock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
})
},
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
)
}
class DummyTimeLock : Contract {
@ -255,7 +260,7 @@ class TransactionEncumbranceTests {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", encumbrance = 1, contractState = state)
output(TEST_TIMELOCK_ID, "5pm time-lock",0, timeLock)
output(TEST_TIMELOCK_ID, "5pm time-lock", 0, timeLock)
}
transaction {
attachments(Cash.PROGRAM_ID)
@ -330,27 +335,31 @@ class TransactionEncumbranceTests {
// More complex encumbrance (full cycle of size 4) where one of the encumbered states is assigned to a different notary.
// 0 -> 1, 1 -> 3, 3 -> 2, 2 -> 0
// We expect that state at index 3 cannot be encumbered with the state at index 2, due to mismatched notaries.
AssertionsForClassTypes.assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java).isThrownBy {
TransactionBuilder()
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 1, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 3, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY2, 0, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 2, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
}.withMessageContaining("index 3 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with index 2 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
AssertionsForClassTypes.assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java)
.isThrownBy {
TransactionBuilder()
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 1, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 3, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY2, 0, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 2, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
}
.withMessageContaining("index 3 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with index 2 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
// Two different encumbrance chains, where only one fails due to mismatched notary.
// 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2 where encumbered states with indices 2 and 3, respectively, are assigned
// to different notaries.
AssertionsForClassTypes.assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java).isThrownBy {
TransactionBuilder()
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 1, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 0, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 3, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY2, 2, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
}.withMessageContaining("index 2 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with index 3 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
AssertionsForClassTypes.assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java)
.isThrownBy {
TransactionBuilder()
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 1, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 0, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 3, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY2, 2, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
}
.withMessageContaining("index 2 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with index 3 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
}
}

View File

@ -6,6 +6,7 @@ import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.*
@ -176,7 +177,7 @@ class TransactionTests {
notary,
timeWindow,
privacySalt,
testNetworkParameters(),
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))),
emptyList()
)

View File

@ -4,6 +4,7 @@ import net.corda.core.contracts.*
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.Vault
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
@ -14,6 +15,7 @@ import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.CASH
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.STATE
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.*
import net.corda.testing.dsl.EnforceVerifyOrFail
import net.corda.testing.dsl.TransactionDSL
@ -22,6 +24,7 @@ import net.corda.testing.internal.TEST_TX_TIME
import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.internal.MockNetworkParametersStorage
import net.corda.testing.node.ledger
import net.corda.testing.node.makeTestIdentityService
import net.corda.testing.node.transaction
@ -244,13 +247,23 @@ class CommercialPaperTestsGeneric {
// MegaCorp will issue some commercial paper and Alice will buy it, using cash issued to her in the name
// of the dummy cash issuer.
val networkParameters = testNetworkParameters(notaries = listOf(NotaryInfo(dummyNotary.party, true)))
val allIdentities = arrayOf(megaCorp.identity, alice.identity, dummyCashIssuer.identity, dummyNotary.identity)
val notaryServices = MockServices(listOf("net.corda.finance.contracts", "net.corda.finance.contracts.asset", "net.corda.finance.schemas"), dummyNotary)
val issuerServices = MockServices(listOf("net.corda.finance.contracts", "net.corda.finance.contracts.asset", "net.corda.finance.schemas"), dummyCashIssuer, dummyNotary)
val notaryServices = MockServices(
listOf("net.corda.finance.contracts", "net.corda.finance.contracts.asset", "net.corda.finance.schemas"),
dummyNotary
)
val issuerServices = MockServices(
listOf("net.corda.finance.contracts", "net.corda.finance.contracts.asset", "net.corda.finance.schemas"),
dummyCashIssuer,
networkParameters,
dummyNotary
)
val (aliceDatabase, aliceServices) = makeTestDatabaseAndMockServices(
listOf("net.corda.finance.contracts", "net.corda.finance.schemas"),
makeTestIdentityService(*allIdentities),
alice
alice,
networkParameters
)
val aliceCash: Vault<Cash.State> = aliceDatabase.transaction {
VaultFiller(aliceServices, dummyNotary).fillWithSomeTestCash(9000.DOLLARS, issuerServices, 1, dummyCashIssuer.ref(1))
@ -259,10 +272,12 @@ class CommercialPaperTestsGeneric {
val (megaCorpDatabase, megaCorpServices) = makeTestDatabaseAndMockServices(
listOf("net.corda.finance.contracts", "net.corda.finance.schemas"),
makeTestIdentityService(*allIdentities),
megaCorp
megaCorp,
networkParameters
)
val bigCorpCash: Vault<Cash.State> = megaCorpDatabase.transaction {
VaultFiller(megaCorpServices, dummyNotary).fillWithSomeTestCash(13000.DOLLARS, issuerServices, 1, dummyCashIssuer.ref(1))
VaultFiller(megaCorpServices, dummyNotary).fillWithSomeTestCash(13000.DOLLARS, issuerServices, 1, dummyCashIssuer.ref(1))
}
// Propagate the cash transactions to each side.

View File

@ -23,6 +23,7 @@ import net.corda.node.VersionInfo
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.common.internal.addNotary
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
@ -67,7 +68,7 @@ class AttachmentLoadingTests {
}
private val services = object : ServicesForResolution {
private val testNetworkParameters = testNetworkParameters()
private val testNetworkParameters = testNetworkParameters().addNotary(DUMMY_NOTARY)
override fun loadState(stateRef: StateRef): TransactionState<*> = throw NotImplementedError()
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = throw NotImplementedError()
override val identityService = rigorousMock<IdentityService>().apply {

View File

@ -13,7 +13,10 @@ import net.corda.node.services.config.MB
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
import net.corda.testing.core.*
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.core.dummyCommand
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
@ -27,7 +30,6 @@ import kotlin.test.assertEquals
class LargeTransactionsTest {
private companion object {
val BOB = TestIdentity(BOB_NAME, 80).party
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
}
@StartableByRPC
@ -38,7 +40,8 @@ class LargeTransactionsTest {
private val hash4: SecureHash) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val tx = TransactionBuilder(notary = DUMMY_NOTARY)
val notary = serviceHub.networkParameters.notaries.first().identity
val tx = TransactionBuilder(notary)
.addOutputState(DummyState(), DummyContract.PROGRAM_ID)
.addCommand(dummyCommand(ourIdentity.owningKey))
.addAttachment(hash1)

View File

@ -168,7 +168,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize()
val cryptoService = configuration.makeCryptoService()
val networkParametersStorage = DBNetworkParametersStorage(cacheFactory, database, networkMapClient).tokenize()
@Suppress("LeakingThis")
val networkParametersStorage = makeParametersStorage()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
@ -362,7 +363,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// Do all of this in a database transaction so anything that might need a connection has one.
return database.transaction {
networkParametersStorage.start(signedNetParams, trustRoot)
networkParametersStorage.setCurrentParameters(signedNetParams, trustRoot)
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
attachments.start()
cordappProvider.start(netParams.whitelistedContractImplementations)
@ -701,6 +702,10 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
return DBTransactionStorage(database, cacheFactory)
}
protected open fun makeParametersStorage(): NetworkParametersStorageInternal {
return DBNetworkParametersStorage(cacheFactory, database, networkMapClient).tokenize()
}
@VisibleForTesting
protected open fun acceptableLiveFiberCountOnStop(): Int = 0
@ -778,7 +783,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
/** Some notary implementations only work with Java serialization. */
maybeInstallSerializationFilter(serviceClass)
val constructor = serviceClass.getDeclaredConstructor(ServiceHubInternal::class.java, PublicKey::class.java).apply { isAccessible = true }
val constructor = serviceClass.getDeclaredConstructor(ServiceHubInternal::class.java, PublicKey::class.java)
.apply { isAccessible = true }
val service = constructor.newInstance(services, notaryKey) as NotaryService
service.run {

View File

@ -1,10 +1,13 @@
package net.corda.node.internal
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.internal.notary.HistoricNetworkParameterStorage
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.NetworkParametersStorage
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.SingletonSerializeAsToken
@ -37,6 +40,8 @@ interface NetworkParametersStorageInternal : NetworkParametersStorage {
* Hash should always be calculated over the serialized bytes.
*/
fun saveParameters(signedNetworkParameters: SignedDataWithCert<NetworkParameters>)
fun setCurrentParameters(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoot: X509Certificate)
}
class DBNetworkParametersStorage(
@ -45,7 +50,7 @@ class DBNetworkParametersStorage(
// 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() {
) : NetworkParametersStorageInternal, HistoricNetworkParameterStorage, SingletonSerializeAsToken() {
private lateinit var trustRoot: X509Certificate
companion object {
@ -71,7 +76,7 @@ class DBNetworkParametersStorage(
}
}
fun start(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoot: X509Certificate) {
override fun setCurrentParameters(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoot: X509Certificate) {
this.trustRoot = trustRoot
saveParameters(currentSignedParameters)
_currentHash = currentSignedParameters.raw.hash
@ -118,6 +123,22 @@ class DBNetworkParametersStorage(
}
}
/**
* Try to obtain notary info from the current network parameters. If not found, look through historical ones.
*/
override fun getHistoricNotary(party: Party): NotaryInfo? {
val currentParameters = lookup(currentHash)
?: throw IllegalStateException("Unable to obtain NotaryInfo current network parameters not set.")
val inCurrentParams = currentParameters.notaries.singleOrNull { it.identity == party }
if (inCurrentParams == null) {
val inOldParams = hashToParameters.allPersisted().flatMap { (_, signedParams) ->
val parameters = signedParams.raw.deserialize()
parameters.notaries.asSequence()
}.firstOrNull { it.identity == party }
return inOldParams
} else return inCurrentParams
}
@Entity
@Table(name = "${NODE_DATABASE_PREFIX}network_parameters")
class PersistentNetworkParameters(

View File

@ -29,13 +29,13 @@ class NotaryChangeHandler(otherSideSession: FlowSession) : AbstractStateReplacem
override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal<Party>) {
val state = proposal.stateRef
val proposedTx = stx.resolveNotaryChangeTransaction(serviceHub)
val newNotary = proposal.modification
// TODO: Right now all nodes will automatically approve the notary change. We need to figure out if stricter controls are necessary.
if (state !in proposedTx.inputs.map { it.ref }) {
throw StateReplacementException("The proposed state $state is not in the proposed transaction inputs")
}
// TODO: load and compare against notary whitelist from config. Remove the check below
val newNotary = proposal.modification
val isNotary = serviceHub.networkMapCache.isNotary(newNotary)
if (!isNotary) {
throw StateReplacementException("The proposed node $newNotary does not run a Notary service")

View File

@ -1,11 +1,17 @@
package net.corda.node.services.transactions
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotaryError
import net.corda.core.identity.Party
import net.corda.core.internal.notary.NotaryInternalException
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.node.NetworkParameters
import net.corda.core.transactions.ContractUpgradeFilteredTransaction
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import java.time.Duration
@ -19,25 +25,77 @@ import java.time.Duration
* undo the commit of the input states (the exact mechanism still needs to be worked out).
*/
class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService, etaThreshold: Duration) : NotaryServiceFlow(otherSideSession, service, etaThreshold) {
private val minPlatformVersion get() = serviceHub.networkParameters.minimumPlatformVersion
override fun extractParts(requestPayload: NotarisationPayload): TransactionParts {
val tx = requestPayload.coreTransaction
return when (tx) {
is FilteredTransaction -> {
tx.apply {
verify()
checkAllComponentsVisible(ComponentGroupEnum.INPUTS_GROUP)
checkAllComponentsVisible(ComponentGroupEnum.TIMEWINDOW_GROUP)
checkAllComponentsVisible(ComponentGroupEnum.REFERENCES_GROUP)
if(serviceHub.networkParameters.minimumPlatformVersion >= 4) checkAllComponentsVisible(ComponentGroupEnum.PARAMETERS_GROUP)
}
TransactionParts(tx.id, tx.inputs, tx.timeWindow, tx.notary, tx.references, networkParametersHash = tx.networkParametersHash)
}
is FilteredTransaction -> TransactionParts(tx.id, tx.inputs, tx.timeWindow, tx.notary, tx.references, networkParametersHash = tx.networkParametersHash)
is ContractUpgradeFilteredTransaction,
is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary, networkParametersHash = tx.networkParametersHash)
else -> {
throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," +
"expected either ${FilteredTransaction::class.java.simpleName} or ${NotaryChangeWireTransaction::class.java.simpleName}")
}
else -> throw unexpectedTransactionType(tx)
}
}
}
override fun verifyTransaction(requestPayload: NotarisationPayload) {
val tx = requestPayload.coreTransaction
try {
when (tx) {
is FilteredTransaction -> {
tx.apply {
verify()
checkAllComponentsVisible(ComponentGroupEnum.INPUTS_GROUP)
checkAllComponentsVisible(ComponentGroupEnum.TIMEWINDOW_GROUP)
checkAllComponentsVisible(ComponentGroupEnum.REFERENCES_GROUP)
if (minPlatformVersion >= 4) checkAllComponentsVisible(ComponentGroupEnum.PARAMETERS_GROUP)
}
val notary = tx.notary ?: throw IllegalArgumentException("Transaction does not specify a notary.")
checkNotaryWhitelisted(notary, tx.networkParametersHash)
}
is ContractUpgradeFilteredTransaction -> {
checkNotaryWhitelisted(tx.notary, tx.networkParametersHash)
}
is NotaryChangeWireTransaction -> {
checkNotaryWhitelisted(tx.newNotary, tx.networkParametersHash)
}
else -> throw unexpectedTransactionType(tx)
}
} catch (e: Exception) {
throw NotaryInternalException(NotaryError.TransactionInvalid(e))
}
}
/** Make sure the transaction notary is part of the network parameter whitelist. */
private fun checkNotaryWhitelisted(notary: Party, attachedParameterHash: SecureHash?) {
if (minPlatformVersion >= 4) {
// Expecting network parameters to be attached for platform version 4 or later.
if (attachedParameterHash == null) {
throw IllegalArgumentException("Transaction must contain network parameters.")
}
val attachedParameters = serviceHub.networkParametersStorage.lookup(attachedParameterHash)
?: throw IllegalStateException("Unable to resolve network parameters from hash: $attachedParameterHash")
checkInWhitelist(attachedParameters, notary)
} else {
// Using current network parameters for platform versions 3 or earlier.
val defaultParams = with(serviceHub.networkParametersStorage) {
lookup(currentHash)
?: throw IllegalStateException("Unable to verify whether the notary $notary is whitelisted: current network parameters not set.")
}
checkInWhitelist(defaultParams, notary)
}
}
private fun checkInWhitelist(networkParameters: NetworkParameters, notary: Party) {
val notaryWhitelist = networkParameters.notaries.map { it.identity }
check(notary in notaryWhitelist) {
"Notary specified by the transaction ($notary) is not on the network parameter whitelist: ${notaryWhitelist.joinToString()}"
}
}
private fun unexpectedTransactionType(tx: CoreTransaction): IllegalArgumentException {
return IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," +
"expected either ${FilteredTransaction::class.java.simpleName} or ${NotaryChangeWireTransaction::class.java.simpleName}")
}
}

View File

@ -38,7 +38,7 @@ open class ValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePa
resolveAndContractVerify(stx)
verifySignatures(stx)
} catch (e: Exception) {
throw NotaryInternalException(NotaryError.TransactionInvalid(e))
throw NotaryInternalException(NotaryError.TransactionInvalid(e))
}
}

View File

@ -497,7 +497,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `dependency with error on buyer side`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
mockNet.defaultNotaryNode.services.ledger(DUMMY_NOTARY) {
mockNet.defaultNotaryNode.services.ledger(mockNet.defaultNotaryIdentity) {
runWithError(true, false, "at least one cash input")
}
}
@ -505,7 +505,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `dependency with error on seller side`() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
mockNet.defaultNotaryNode.services.ledger(DUMMY_NOTARY) {
mockNet.defaultNotaryNode.services.ledger(mockNet.defaultNotaryIdentity) {
runWithError(false, true, "Issuances have a time-window")
}
}

View File

@ -1,7 +1,9 @@
package net.corda.node.services
import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.flows.NotaryChangeFlow
import net.corda.core.flows.NotaryFlow
import net.corda.core.flows.StateReplacementException
@ -13,11 +15,18 @@ import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.*
import net.corda.testing.node.*
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetworkNotarySpec
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.StartedMockNode
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.time.Instant
import java.util.*
@ -25,6 +34,9 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue
class NotaryChangeTests {
private val oldNotaryName = CordaX500Name("Old Notary", "Zurich", "CH")
private val newNotaryName = CordaX500Name("New Notary", "Zurich", "CH")
private lateinit var mockNet: MockNetwork
private lateinit var oldNotaryNode: StartedMockNode
private lateinit var clientNodeA: StartedMockNode
@ -35,17 +47,16 @@ class NotaryChangeTests {
@Before
fun setUp() {
val oldNotaryName = CordaX500Name("Regulator A", "Paris", "FR")
mockNet = MockNetwork(
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME), MockNetworkNotarySpec(oldNotaryName)),
notarySpecs = listOf(MockNetworkNotarySpec(oldNotaryName), MockNetworkNotarySpec(newNotaryName)),
cordappPackages = listOf("net.corda.testing.contracts")
)
clientNodeA = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
clientNodeB = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
clientA = clientNodeA.info.singleIdentity()
oldNotaryNode = mockNet.notaryNodes[1]
newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_NAME)!!
oldNotaryNode = mockNet.notaryNodes[0]
oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(oldNotaryName)!!
newNotaryParty = clientNodeA.services.networkMapCache.getNotary(newNotaryName)!!
}
@After
@ -77,11 +88,13 @@ class NotaryChangeTests {
assertEquals(loadedStateA, loadedStateB)
}
// TODO: Re-enable the test when parameter currentness checks are in place, ENT-2666.
@Test
@Ignore
fun `should throw when a participant refuses to change Notary`() {
val state = issueMultiPartyState(clientNodeA, clientNodeB, oldNotaryNode, oldNotaryParty)
val newEvilNotary = getTestPartyAndCertificate(CordaX500Name(organisation = "Evil R3", locality = "London", country = "GB"), generateKeyPair().public)
val flow = NotaryChangeFlow(state, newEvilNotary.party)
val flow = NotaryChangeFlow(state, newNotaryParty)
val future = clientNodeA.startFlow(flow)
mockNet.runNetwork()
@ -112,8 +125,10 @@ class NotaryChangeTests {
assertTrue(originalOutputs.size == newOutputs.size && originalOutputs.containsAll(newOutputs))
// Check if encumbrance linking between states has not changed.
val originalLinkedStates = issueTx.outputs.asSequence().filter { it.encumbrance != null }.map { Pair(it.data, issueTx.outputs[it.encumbrance!!].data) }.toSet()
val notaryChangeLinkedStates = notaryChangeTx.outputs.asSequence().filter { it.encumbrance != null }.map { Pair(it.data, notaryChangeTx.outputs[it.encumbrance!!].data) }.toSet()
val originalLinkedStates = issueTx.outputs.asSequence().filter { it.encumbrance != null }
.map { Pair(it.data, issueTx.outputs[it.encumbrance!!].data) }.toSet()
val notaryChangeLinkedStates = notaryChangeTx.outputs.asSequence().filter { it.encumbrance != null }
.map { Pair(it.data, notaryChangeTx.outputs[it.encumbrance!!].data) }.toSet()
assertTrue { originalLinkedStates.size == notaryChangeLinkedStates.size && originalLinkedStates.containsAll(notaryChangeLinkedStates) }
}

View File

@ -63,7 +63,7 @@ class DBNetworkParametersStorageTest {
networkMapClient = createMockNetworkMapClient()
nodeParametersStorage = DBNetworkParametersStorage(TestingNamedCacheFactory(), database, networkMapClient).apply {
database.transaction {
start(netParams1, DEV_ROOT_CA.certificate)
setCurrentParameters(netParams1, DEV_ROOT_CA.certificate)
}
}
}

View File

@ -1,12 +1,21 @@
package net.corda.node.services.persistence
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.AbstractCashFlow
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.issuedBy
import net.corda.node.internal.NetworkParametersStorageInternal
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.testing.internal.TestingNamedCacheFactory
@ -17,6 +26,7 @@ import net.corda.testing.node.StartedMockNode
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
class HibernateColumnConverterTests {
@ -25,6 +35,20 @@ class HibernateColumnConverterTests {
private lateinit var bankOfCorda: Party
private lateinit var notary: Party
class TestCashIssueFlow(private val amount: Amount<Currency>,
private val issuerBankPartyRef: OpaqueBytes,
private val notary: Party) : AbstractCashFlow<AbstractCashFlow.Result>(tracker()) {
@Suspendable
override fun call(): AbstractCashFlow.Result {
val builder = TransactionBuilder(notary)
val issuer = ourIdentity.ref(issuerBankPartyRef)
val signers = Cash().generateIssue(builder, amount.issuedBy(issuer), ourIdentity, notary)
val tx = serviceHub.signInitialTransaction(builder, signers)
serviceHub.recordTransactions(tx)
return Result(tx, ourIdentity)
}
}
@Before
fun start() {
mockNet = MockNetwork(
@ -59,10 +83,10 @@ class HibernateColumnConverterTests {
val newKeyAndCert = keyService.freshKeyAndCert(bankOfCordaNode.info.legalIdentitiesAndCerts[0], false)
val randomNotary = Party(BOC_NAME, newKeyAndCert.owningKey)
val future = bankOfCordaNode.startFlow(CashIssueFlow(expected, ref, randomNotary))
val future = bankOfCordaNode.startFlow(TestCashIssueFlow(expected, ref, randomNotary))
mockNet.runNetwork()
val issueTx = future.getOrThrow().stx
val output = issueTx.tx.outputsOfType<Cash.State>().single()
assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), output.amount)
}
}
}

View File

@ -38,7 +38,7 @@ class NotaryServiceTests {
mockNet = InternalMockNetwork(
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"),
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME, validating = false)),
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = 4)
)
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
notaryServices = mockNet.defaultNotaryNode.services //TODO get rid of that

View File

@ -0,0 +1,257 @@
package net.corda.node.services.transactions
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.StateAndRef
import net.corda.core.crypto.*
import net.corda.core.flows.NotaryChangeFlow
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.NotaryChangeTransactionBuilder
import net.corda.core.node.NetworkParameters
import net.corda.core.transactions.NotaryChangeLedgerTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetworkNotarySpec
import net.corda.testing.node.internal.*
import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.security.KeyPair
import java.time.Instant
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@RunWith(Parameterized::class)
class NotaryWhitelistTests(
/** Specified whether validating notaries should be used. */
private val isValidating: Boolean
) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "Validating = {0}")
fun data(): Collection<Boolean> = listOf(true, false)
}
private val oldNotaryName = CordaX500Name("Old Notary", "Zurich", "CH")
private val newNotaryName = CordaX500Name("New Notary", "Zurich", "CH")
private lateinit var mockNet: InternalMockNetwork
private lateinit var aliceNode: TestStartedNode
private lateinit var oldNotary: Party
private lateinit var newNotary: Party
private lateinit var alice: Party
@Before
fun setup() {
mockNet = InternalMockNetwork(
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"),
notarySpecs = listOf(MockNetworkNotarySpec(oldNotaryName, validating = isValidating), MockNetworkNotarySpec(newNotaryName, validating = isValidating)),
initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = 4)
)
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
oldNotary = mockNet.notaryNodes[0].info.legalIdentities.last()
newNotary = mockNet.notaryNodes[1].info.legalIdentities.last()
alice = aliceNode.services.myInfo.singleIdentity()
}
@After
fun cleanUp() {
mockNet.stopNodes()
}
/**
* This test verifies network merging support: when a sub-zone merges into another zone, and _its states are deemed to be low-risk_ by
* the network operator, the old notary service can temporarily operate on the new zone to facilitate notary change requests (even though
* it's not whitelisted for regular use).
*/
@Test
fun `can perform notary change on a de-listed notary`() {
// Issue a state using the old notary. It is currently whitelisted.
val stateFakeNotary = issueStateOnOldNotary(oldNotary)
// Remove old notary from the whitelist
val parameters = aliceNode.services.networkParameters
val newParameters = removeOldNotary(parameters)
mockNet.nodes.forEach {
(it.networkParametersStorage as MockNetworkParametersStorage).setCurrentParametersUnverified(newParameters)
}
// Re-point the state to the remaining whitelisted notary. The transaction itself should be considered valid, even though the old notary is not whitelisted.
val futureChange = aliceNode.services.startFlow(NotaryChangeFlow(stateFakeNotary, newNotary)).resultFuture
mockNet.runNetwork()
val newSTate = futureChange.getOrThrow()
// Create a valid transaction consuming the re-pointed state.
val validTxBuilder = TransactionBuilder(newNotary)
.addInputState(newSTate)
.addCommand(dummyCommand(alice.owningKey))
val validStx = aliceNode.services.signInitialTransaction(validTxBuilder)
// The transaction verifies.
validStx.verify(aliceNode.services, false)
// Notarisation should succeed.
val future = runNotaryClient(validStx)
future.getOrThrow()
}
/**
* Following on from the previous one, this test verifies that a non-whitelisted notary cannot be used for regular transactions.
*/
@Test
fun `can't perform a regular transaction on a de-listed notary`() {
// Issue a state using the old notary. It is currently whitelisted.
val state = issueStateOnOldNotary(oldNotary)
// Remove old notary from the whitelist
val parameters = aliceNode.services.networkParameters
val newParameters = removeOldNotary(parameters)
mockNet.nodes.forEach {
(it.networkParametersStorage as MockNetworkParametersStorage).setCurrentParametersUnverified(newParameters)
}
// Create a valid transaction consuming the state.
val validTxBuilder = TransactionBuilder(oldNotary)
.addInputState(state)
.addOutputState(state.state.copy())
.addCommand(dummyCommand(alice.owningKey))
val validStx = aliceNode.services.signInitialTransaction(validTxBuilder)
// The transaction does not verify as the notary is no longer whitelisted.
assertFailsWith<IllegalStateException> {
validStx.verify(aliceNode.services, false)
}
// Notarisation should fail (assuming the unlisted notary is not malicious).
val future = runNotaryClient(validStx)
val ex = assertFailsWith(NotaryException::class) {
future.getOrThrow()
}
assert(ex.error is NotaryError.TransactionInvalid)
assertEquals(validStx.id, ex.txId)
}
private fun removeOldNotary(parameters: NetworkParameters): NetworkParameters {
val newParameters = parameters.copy(notaries = parameters.notaries.drop(1))
assert(newParameters.notaries.none { it.identity == oldNotary })
assert(newParameters.notaries.any { it.identity == newNotary })
return newParameters
}
private fun issueStateOnOldNotary(oldNotaryParty: Party): StateAndRef<DummyContract.State> {
val fakeTxBuilder = DummyContract
.generateInitial(Random().nextInt(), oldNotaryParty, alice.ref(0))
.setTimeWindow(Instant.now(), 30.seconds)
val fakeStx = aliceNode.services.signInitialTransaction(fakeTxBuilder)
val sigs = runNotaryClient(fakeStx).getOrThrow()
aliceNode.services.validatedTransactions.addTransaction(fakeStx + sigs)
return fakeStx.tx.outRef(0)
}
private fun runNotaryClient(stx: SignedTransaction): CordaFuture<List<TransactionSignature>> {
val flow = NotaryFlow.Client(stx)
val future = aliceNode.services.startFlow(flow).resultFuture
mockNet.runNetwork()
return future
}
@Test
fun `should reject transaction when a dependency does not contain notary in whitelist`() {
Assume.assumeTrue(isValidating) // Skip the test for non-validating notaries
val fakeNotaryKeyPair = generateKeyPair()
val fakeNotaryParty = Party(DUMMY_NOTARY_NAME.copy(organisation = "Fake notary"), fakeNotaryKeyPair.public)
// Issue a state using an unlisted notary. This transaction should not verify when checked by counterparties.
val stateFakeNotary = issueStateWithFakeNotary(fakeNotaryParty, fakeNotaryKeyPair)
// Re-point the state to the whitelisted notary. The transaction itself should be considered valid, even though the old notary is not whitelisted.
val notaryChangeLtx = changeNotary(stateFakeNotary, fakeNotaryParty, fakeNotaryKeyPair)
// Create a valid transaction consuming the re-pointed state.
val inputStateValidNotary = notaryChangeLtx.outRef<DummyContract.State>(0)
val validTxBuilder = TransactionBuilder(oldNotary)
.addInputState(inputStateValidNotary)
.addCommand(dummyCommand(alice.owningKey))
val validStx = aliceNode.services.signInitialTransaction(validTxBuilder)
// The transaction itself verifies, as no resolution is done here.
validStx.verify(aliceNode.services, false)
val future = runNotaryClient(validStx)
// The notary should reject this transaction the issue transaction in the dependencies should not verify.
val ex = assertFailsWith(NotaryException::class) {
future.getOrThrow()
}
assert(ex.error is NotaryError.TransactionInvalid)
assertEquals(validStx.id, ex.txId)
}
private fun issueStateWithFakeNotary(fakeNotaryParty: Party, fakeNotaryKeyPair: KeyPair): StateAndRef<DummyContract.State> {
val fakeTxBuilder = DummyContract
.generateInitial(Random().nextInt(), fakeNotaryParty, alice.ref(0))
.setTimeWindow(Instant.now(), 30.seconds)
val fakeStx = aliceNode.services.signInitialTransaction(fakeTxBuilder)
val notarySig = getNotarySig(fakeStx, fakeNotaryKeyPair)
aliceNode.services.validatedTransactions.addTransaction(fakeStx + notarySig)
return fakeStx.tx.outRef(0)
}
/** Changes the notary service to [notary]. Does not actually communicate with a notary. */
private fun changeNotary(inputState: StateAndRef<DummyContract.State>, fakeNotaryParty: Party, fakeNotaryKeyPair: KeyPair): NotaryChangeLedgerTransaction {
val notaryChangeTx = NotaryChangeTransactionBuilder(
listOf(inputState.ref),
fakeNotaryParty,
oldNotary,
aliceNode.services.networkParametersStorage.currentHash
).build()
val notaryChangeAliceSig = getAliceSig(notaryChangeTx)
val notaryChangeNotarySig = run {
val metadata = SignatureMetadata(4, Crypto.findSignatureScheme(fakeNotaryParty.owningKey).schemeNumberID)
val data = SignableData(notaryChangeTx.id, metadata)
fakeNotaryKeyPair.sign(data)
}
val notaryChangeStx = SignedTransaction(notaryChangeTx, listOf(notaryChangeAliceSig, notaryChangeNotarySig))
aliceNode.services.validatedTransactions.addTransaction(notaryChangeStx)
// Resolving the ledger transaction verifies the whitelist checking logic for notary change transactions the old notary
// does not need to be whitelisted.
val notaryChangeLtx = notaryChangeStx.resolveNotaryChangeTransaction(aliceNode.services)
notaryChangeLtx.verifyRequiredSignatures()
return notaryChangeLtx
}
private fun getAliceSig(notaryChangeTx: NotaryChangeWireTransaction): TransactionSignature {
val metadata = SignatureMetadata(4, Crypto.findSignatureScheme(alice.owningKey).schemeNumberID)
val data = SignableData(notaryChangeTx.id, metadata)
return aliceNode.services.keyManagementService.sign(data, alice.owningKey)
}
private fun getNotarySig(fakeStx: SignedTransaction, fakeNotaryKeyPair: KeyPair): TransactionSignature {
val metadata = SignatureMetadata(4, Crypto.findSignatureScheme(fakeNotaryKeyPair.public).schemeNumberID)
val data = SignableData(fakeStx.id, metadata)
return fakeNotaryKeyPair.sign(data)
}
}

View File

@ -3,6 +3,7 @@ package net.corda.node.services.transactions
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.NotaryInfo
import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
@ -58,7 +59,7 @@ class ResolveStatePointersTest {
cordappPackages = cordapps,
identityService = makeTestIdentityService(notary.identity, myself.identity),
initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(notary.party, true)))
)
services = databaseAndServices.second
}

View File

@ -2,7 +2,6 @@ package net.corda.node.services.transactions
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.Command
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
@ -15,7 +14,6 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
@ -51,7 +49,7 @@ class ValidatingNotaryServiceTests {
@Before
fun setup() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"),
networkParameters = testNetworkParameters(minimumPlatformVersion = 4))
initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = 4))
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
notaryNode = mockNet.defaultNotaryNode
notary = mockNet.defaultNotaryIdentity
@ -116,6 +114,7 @@ class ValidatingNotaryServiceTests {
)
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

View File

@ -11,6 +11,7 @@ import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.*
import net.corda.core.internal.NotaryChangeTransactionBuilder
import net.corda.core.internal.packageName
import net.corda.core.node.NotaryInfo
import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.*
import net.corda.core.node.services.vault.PageSpecification
@ -91,7 +92,7 @@ class NodeVaultServiceTest {
@Before
fun setUp() {
LogHelper.setLevel(NodeVaultService::class)
val parameters = testNetworkParameters()
val parameters = testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
cordappPackages,
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),

View File

@ -102,7 +102,8 @@ class VaultSoftLockManagerTest {
class ClientLogic(nodePair: NodePair, val state: ContractState) : NodePair.AbstractClientLogic<List<ContractState>>(nodePair) {
override fun callImpl(): List<ContractState> {
val stx = serviceHub.signInitialTransaction(TransactionBuilder(notary = ourIdentity).apply {
val notary = serviceHub.networkParameters.notaries.first().identity
val stx = serviceHub.signInitialTransaction(TransactionBuilder(notary).apply {
addOutputState(state, ContractImpl::class.jvmName)
addCommand(CommandDataImpl, ourIdentity.owningKey)
})

View File

@ -11,7 +11,6 @@ import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.packageName
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.queryBy
@ -24,6 +23,8 @@ import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.schemas.CashSchemaV1
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.common.internal.addNotary
import net.corda.testing.core.*
import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.rigorousMock
@ -74,16 +75,21 @@ class VaultWithCashTest {
@Before
fun setUp() {
LogHelper.setLevel(VaultWithCashTest::class)
val networkParameters = testNetworkParameters().addNotary(DUMMY_NOTARY)
val databaseAndServices = makeTestDatabaseAndMockServices(
cordappPackages,
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity),
TestIdentity(MEGA_CORP.name, servicesKey),
networkParameters,
moreKeys = *arrayOf(dummyNotary.keyPair))
database = databaseAndServices.first
services = databaseAndServices.second
vaultFiller = VaultFiller(services, dummyNotary)
issuerServices = MockServices(cordappPackages, dummyCashIssuer, rigorousMock(), MEGA_CORP_KEY)
notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock<IdentityService>())
issuerServices = MockServices(cordappPackages, dummyCashIssuer, rigorousMock(), networkParameters, MEGA_CORP_KEY)
notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock(), networkParameters)
notary = notaryServices.myInfo.legalIdentitiesAndCerts.single().party
}

View File

@ -12,18 +12,10 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.seconds
import net.corda.finance.DOLLARS
import net.corda.finance.EUR
import net.corda.finance.contracts.AccrualAdjustment
import net.corda.finance.contracts.BusinessCalendar
import net.corda.finance.contracts.DateRollConvention
import net.corda.finance.contracts.DayCountBasisDay
import net.corda.finance.contracts.DayCountBasisYear
import net.corda.finance.contracts.Expression
import net.corda.finance.contracts.Fix
import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.Frequency
import net.corda.finance.contracts.PaymentRule
import net.corda.finance.contracts.Tenor
import net.corda.finance.contracts.*
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.common.internal.addNotary
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
@ -47,12 +39,9 @@ private val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
private val ORACLE_PUBKEY = TestIdentity(CordaX500Name("Oracle", "London", "GB")).publicKey
private val DUMMY_NOTARY get() = dummyNotary.party
private val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
private val MEGA_CORP get() = megaCorp.party
private val MEGA_CORP_KEY get() = megaCorp.keyPair
private val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
private val MINI_CORP get() = miniCorp.party
private val MINI_CORP_KEY get() = miniCorp.keyPair
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
return when (irsSelect) {
1 -> {
@ -228,7 +217,6 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
)
return InterestRateSwap.State(fixedLeg = fixedLeg, floatingLeg = floatingLeg, calculation = calculation, common = common, oracle = DUMMY_PARTY)
}
else -> TODO("IRS number $irsSelect not defined")
}
@ -238,14 +226,20 @@ class IRSTests {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val megaCorpServices = MockServices(listOf("net.corda.irs.contract"), MEGA_CORP.name, rigorousMock(), MEGA_CORP_KEY)
private val miniCorpServices = MockServices(listOf("net.corda.irs.contract"), MINI_CORP.name, rigorousMock(), MINI_CORP_KEY)
private val notaryServices = MockServices(listOf("net.corda.irs.contract"), DUMMY_NOTARY.name, rigorousMock(), DUMMY_NOTARY_KEY)
private val ledgerServices
get() = MockServices(emptyList(), MEGA_CORP.name, rigorousMock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(ORACLE_PUBKEY)
})
private val cordappPackages = listOf("net.corda.irs.contract")
private val networkParameters = testNetworkParameters().addNotary(dummyNotary.party)
private val megaCorpServices = MockServices(cordappPackages, megaCorp, rigorousMock(), networkParameters, megaCorp.keyPair)
private val miniCorpServices = MockServices(cordappPackages, miniCorp, rigorousMock(), networkParameters, miniCorp.keyPair)
private val notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock(), networkParameters, dummyNotary.keyPair)
private val ledgerServices = MockServices(
emptyList(),
megaCorp,
rigorousMock<IdentityServiceInternal>().also {
doReturn(megaCorp.party).whenever(it).partyFromKey(megaCorp.publicKey)
doReturn(null).whenever(it).partyFromKey(ORACLE_PUBKEY)
},
networkParameters
)
@Test
fun ok() {
@ -346,7 +340,8 @@ class IRSTests {
listOf(MEGA_CORP, MINI_CORP).forEach { party ->
doReturn(party).whenever(it).partyFromKey(party.owningKey)
}
})
},
networkParameters = ledgerServices.networkParameters)
var previousTXN = generateIRSTxn(1)
previousTXN.toLedgerTransaction(services).verify()
services.recordTransactions(previousTXN)
@ -408,7 +403,6 @@ class IRSTests {
// This does not throw an exception in the test itself; it evaluates the above and they will throw if they do not pass.
}
/**
* Generates a typical transactional history for an IRS.
*/
@ -583,7 +577,6 @@ class IRSTests {
this `fails with` "The termination dates are aligned"
}
val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1)))
transaction {
attachments(IRS_PROGRAM_ID)
@ -594,7 +587,6 @@ class IRSTests {
}
}
@Test
fun `various fixing tests`() {
val ld = LocalDate.of(2016, 3, 8)
@ -672,7 +664,6 @@ class IRSTests {
}
}
/**
* This returns an example of transactions that are grouped by TradeId and then a fixing applied.
* It's important to make the tradeID different for two reasons, the hashes will be the same and all sorts of confusion will
@ -737,4 +728,4 @@ class IRSTests {
}
}
}
}
}

View File

@ -308,7 +308,7 @@ open class MockNetwork(
networkParameters: NetworkParameters = defaultParameters.networkParameters
) : this(emptyList(), defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters, cordappsForAllNodes = cordappsForPackages(cordappPackages))
private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters = networkParameters, cordappsForAllNodes = cordappsForAllNodes)
private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, initialNetworkParameters = networkParameters, cordappsForAllNodes = cordappsForAllNodes)
/** In a mock network, nodes have an incrementing integer ID. Real networks do not have this. Returns the next ID that will be used. */
val nextNodeId get(): Int = internalMockNetwork.nextNodeId

View File

@ -8,8 +8,8 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowProgressHandle
@ -17,12 +17,10 @@ import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.node.*
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.NetworkParametersStorageInternal
import net.corda.node.internal.ServicesForResolutionImpl
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.api.*
@ -34,6 +32,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.contextTransaction
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.common.internal.addNotary
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.DEV_ROOT_CA
import net.corda.testing.internal.MockCordappProvider
@ -47,7 +46,6 @@ import java.time.Instant
import java.util.*
import java.util.function.Consumer
import javax.persistence.EntityManager
import kotlin.collections.HashMap
/**
* Returns a simple [InMemoryIdentityService] containing the supplied [identities].
@ -67,7 +65,7 @@ open class MockServices private constructor(
cordappLoader: CordappLoader,
override val validatedTransactions: TransactionStorage,
override val identityService: IdentityService,
final override val networkParameters: NetworkParameters,
private val initialNetworkParameters: NetworkParameters,
private val initialIdentity: TestIdentity,
private val moreKeys: Array<out KeyPair>
) : ServiceHub {
@ -118,8 +116,8 @@ open class MockServices private constructor(
val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, schemaService.internalSchemas())
val mockService = database.transaction {
object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) {
override val networkParametersStorage: NetworkParametersStorage = MockNetworkParametersStorage(networkParameters)
override val vaultService: VaultService = makeVaultService(schemaService, database)
override val networkParametersStorage: NetworkParametersStorage get() = MockNetworkParametersStorage(networkParameters)
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
ServiceHubInternal.recordTransactions(statesToRecord, txs,
validatedTransactions as WritableTransactionStorage,
@ -185,6 +183,7 @@ open class MockServices private constructor(
@JvmOverloads
constructor(cordappPackages: Iterable<String>, initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService(), key: KeyPair, vararg moreKeys: KeyPair) :
this(cordappPackages, TestIdentity(initialIdentityName, key), identityService, *moreKeys)
/**
* Create a mock [ServiceHub] that can't load CorDapp code, which uses the provided identity service
* (you can get one from [makeTestIdentityService]) and which represents the given identity.
@ -214,6 +213,10 @@ open class MockServices private constructor(
constructor(initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService())
: this(listOf(getCallerPackage(MockServices::class)!!), TestIdentity(initialIdentityName), identityService)
@JvmOverloads
constructor(cordappPackages: List<String>, initialIdentityName: CordaX500Name, identityService: IdentityService, networkParameters: NetworkParameters)
: this(cordappPackages, TestIdentity(initialIdentityName), identityService, networkParameters)
/**
* A helper constructor that requires at least one test identity to be registered, and which takes the package of
* the caller as the package in which to find app code. This is the most convenient constructor and the one that
@ -226,6 +229,13 @@ open class MockServices private constructor(
*moreIdentities
)
constructor(firstIdentity: TestIdentity, networkParameters: NetworkParameters, vararg moreIdentities: TestIdentity) : this(
listOf(getCallerPackage(MockServices::class)!!),
firstIdentity,
networkParameters,
*moreIdentities
)
constructor(cordappPackages: List<String>, firstIdentity: TestIdentity, vararg moreIdentities: TestIdentity) : this(
cordappPackages,
firstIdentity,
@ -233,6 +243,14 @@ open class MockServices private constructor(
firstIdentity.keyPair
)
constructor(cordappPackages: List<String>, firstIdentity: TestIdentity, networkParameters: NetworkParameters, vararg moreIdentities: TestIdentity) : this(
cordappPackages,
firstIdentity,
makeTestIdentityService(*listOf(firstIdentity, *moreIdentities).map { it.identity }.toTypedArray()),
networkParameters,
firstIdentity.keyPair
)
/**
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses a default service
* identity.
@ -245,6 +263,9 @@ open class MockServices private constructor(
}
}
override val networkParameters: NetworkParameters
get() = networkParametersStorage.run { lookup(currentHash)!! }
final override val attachments = MockAttachmentStorage()
override val keyManagementService: KeyManagementService by lazy { MockKeyManagementService(identityService, *arrayOf(initialIdentity.keyPair) + moreKeys) }
override val vaultService: VaultService get() = throw UnsupportedOperationException()
@ -257,10 +278,10 @@ open class MockServices private constructor(
}
override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2)
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments).also {
it.start(networkParameters.whitelistedContractImplementations)
it.start(initialNetworkParameters.whitelistedContractImplementations)
}
override val cordappProvider: CordappProvider get() = mockCordappProvider
override val networkParametersStorage: NetworkParametersStorage get() = MockNetworkParametersStorage(networkParameters)
override val networkParametersStorage: NetworkParametersStorage = MockNetworkParametersStorage(initialNetworkParameters)
protected val servicesForResolution: ServicesForResolution
get() {
@ -322,4 +343,4 @@ fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serv
}
}
return MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance
}
}

View File

@ -8,17 +8,12 @@ import net.corda.core.context.InvocationContext
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.common.internal.addNotary
import net.corda.testing.core.TestIdentity
import net.corda.testing.dsl.EnforceVerifyOrFail
import net.corda.testing.dsl.LedgerDSL
import net.corda.testing.dsl.LedgerDSLInterpreter
import net.corda.testing.dsl.TestLedgerDSLInterpreter
import net.corda.testing.dsl.TestTransactionDSLInterpreter
import net.corda.testing.dsl.TransactionDSL
import net.corda.testing.dsl.TransactionDSLInterpreter
import net.corda.testing.dsl.*
import net.corda.testing.internal.withTestSerializationEnvIfNotSet
import net.corda.testing.node.internal.MockNetworkParametersStorage
/**
* Creates and tests a ledger built by the passed in dsl.
@ -28,17 +23,20 @@ fun ServiceHub.ledger(
notary: Party = TestIdentity.fresh("ledger notary").party,
script: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
val serializationExists = try {
effectiveSerializationEnv
true
} catch (e: IllegalStateException) {
false
val currentParameters = networkParametersStorage.run {
lookup(currentHash) ?: throw IllegalStateException("Current network parameters not found, $currentHash")
}
return LedgerDSL(TestLedgerDSLInterpreter(this), notary).apply {
if (serializationExists) {
if (currentParameters.notaries.none { it.identity == notary }) {
// Add the notary to the whitelist. Otherwise no constructed transactions will verify.
val newParameters = currentParameters.addNotary(notary)
(networkParametersStorage as MockNetworkParametersStorage).setCurrentParametersUnverified(newParameters)
}
return withTestSerializationEnvIfNotSet("ledgerDSL") {
val interpreter = TestLedgerDSLInterpreter(this)
LedgerDSL(interpreter, notary).apply {
script()
} else {
SerializationEnvironmentRule.run("ledger") { script() }
}
}
}

View File

@ -30,6 +30,7 @@ import net.corda.core.utilities.seconds
import net.corda.node.VersionInfo
import net.corda.node.internal.AbstractNode
import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.NetworkParametersStorageInternal
import net.corda.node.internal.NodeFlowManager
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
@ -138,7 +139,6 @@ interface TestStartedNode {
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<T>, track: Boolean = false): Observable<T>
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatingFlowClass: Class<out FlowLogic<*>>, initiatedFlowClass: Class<T>, track: Boolean = false): Observable<T>
}
open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParameters(),
@ -147,16 +147,20 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
val networkParameters: NetworkParameters = testNetworkParameters(),
initialNetworkParameters: NetworkParameters = testNetworkParameters(),
val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) },
val cordappsForAllNodes: Collection<TestCordapp> = emptySet(),
val autoVisibleNodes: Boolean = true) : AutoCloseable {
var networkParameters: NetworkParameters = initialNetworkParameters
private set
init {
// Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS.
// This SFTP support loads BouncyCastle, which we want to avoid.
// Please see https://issues.apache.org/jira/browse/SSHD-736 - it's easier then to create our own fork of SSHD
SecurityUtils.setAPrioriDisabledProvider("BC", true) // XXX: Why isn't this static?
require(networkParameters.notaries.isEmpty()) { "Define notaries using notarySpecs" }
require(initialNetworkParameters.notaries.isEmpty()) { "Define notaries using notarySpecs" }
}
var nextNodeId = 0
@ -229,8 +233,9 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
try {
filesystem.getPath("/nodes").createDirectory()
val notaryInfos = generateNotaryIdentities()
networkParameters = initialNetworkParameters.copy(notaries = notaryInfos)
// The network parameters must be serialised before starting any of the nodes
networkParametersCopier = NetworkParametersCopier(networkParameters.copy(notaries = notaryInfos))
networkParametersCopier = NetworkParametersCopier(networkParameters)
@Suppress("LeakingThis")
// Notary nodes need a platform version >= network min platform version.
notaryNodes = createNotaries()
@ -302,8 +307,6 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
internals.flowManager.registerInitiatedFlow(initiatingFlowClass, initiatedFlowClass)
return smm.changes.filter { it is StateMachineManager.Change.Add }.map { it.logic }.ofType(initiatedFlowClass)
}
}
val mockNet = args.network
@ -430,8 +433,11 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
Observable.empty<T>()
}
}
}
override fun makeParametersStorage(): NetworkParametersStorageInternal {
return MockNetworkParametersStorage()
}
}
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters()): MockNode {
return createUnstartedNode(parameters, defaultFactory)

View File

@ -1,21 +1,41 @@
package net.corda.testing.node.internal
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.internal.notary.HistoricNetworkParameterStorage
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.serialize
import net.corda.node.internal.NetworkParametersStorageInternal
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.withTestSerializationEnvIfNotSet
import java.security.cert.X509Certificate
import java.time.Instant
class MockNetworkParametersStorage(val currentParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN)) : NetworkParametersStorageInternal {
class MockNetworkParametersStorage(private var currentParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN)) : NetworkParametersStorageInternal, HistoricNetworkParameterStorage {
private val hashToParametersMap: HashMap<SecureHash, NetworkParameters> = HashMap()
init {
hashToParametersMap[currentHash] = currentParameters
storeCurrentParameters()
}
override val currentHash: SecureHash get() = currentParameters.serialize().hash
fun setCurrentParametersUnverified(networkParameters: NetworkParameters) {
currentParameters = networkParameters
storeCurrentParameters()
}
override fun setCurrentParameters(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoot: X509Certificate) {
setCurrentParametersUnverified(currentSignedParameters.verifiedNetworkMapCert(trustRoot))
}
override val currentHash: SecureHash
get() {
return withTestSerializationEnvIfNotSet("networkParameters") {
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]
@ -24,4 +44,18 @@ class MockNetworkParametersStorage(val currentParameters: NetworkParameters = te
val hash = signedNetworkParameters.raw.hash
hashToParametersMap[hash] = networkParameters
}
}
override fun getHistoricNotary(party: Party): NotaryInfo? {
val inCurrentParams = currentParameters.notaries.singleOrNull { it.identity == party }
if (inCurrentParams == null) {
val inOldParams = hashToParametersMap.flatMap { (_, parameters) ->
parameters.notaries
}.firstOrNull { it.identity == party }
return inOldParams
} else return inCurrentParams
}
private fun storeCurrentParameters() {
hashToParametersMap[currentHash] = currentParameters
}
}

View File

@ -2,12 +2,14 @@ package net.corda.testing.node.internal
import com.typesafe.config.ConfigValueFactory
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
import net.corda.core.utilities.getOrThrow
import net.corda.node.VersionInfo
import net.corda.node.internal.FlowManager
@ -15,6 +17,7 @@ import net.corda.node.internal.Node
import net.corda.node.internal.NodeFlowManager
import net.corda.node.internal.NodeWithInfo
import net.corda.node.services.config.*
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
@ -37,7 +40,9 @@ import kotlin.test.assertFalse
// TODO Some of the logic here duplicates what's in the driver - the reason why it's not straightforward to replace it by
// using DriverDSLImpl in `init()` and `stopAllNodes()` is because of the platform version passed to nodes (driver doesn't
// support this, and it's a property of the Corda JAR)
abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyList()) {
abstract class NodeBasedTest
@JvmOverloads
constructor(private val cordappPackages: List<String> = emptyList(), private val notaries: List<CordaX500Name> = emptyList()) {
companion object {
private val WHITESPACE = "\\s++".toRegex()
}
@ -50,8 +55,8 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
val tempFolder = TemporaryFolder()
private lateinit var defaultNetworkParameters: NetworkParametersCopier
protected val notaryNodes = mutableListOf<NodeWithInfo>()
private val nodes = mutableListOf<NodeWithInfo>()
private val nodeInfos = mutableListOf<NodeInfo>()
private val portAllocation = incrementalPortAllocation(10000)
init {
@ -60,7 +65,14 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
@Before
fun init() {
defaultNetworkParameters = NetworkParametersCopier(testNetworkParameters())
val notaryInfos = notaries.map { NotaryInfo(installNotary(it), true) } // todo only validating ones
defaultNetworkParameters = NetworkParametersCopier(testNetworkParameters(notaries = notaryInfos))
notaries.mapTo(notaryNodes) { startNode(it) }
}
private fun installNotary(legalName: CordaX500Name): Party {
val baseDirectory = baseDirectory(legalName).createDirectories()
return DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory, legalName)
}
/**
@ -69,17 +81,19 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
*/
@After
fun stopAllNodes() {
val shutdownExecutor = Executors.newScheduledThreadPool(nodes.size)
val nodesToShut = nodes + notaryNodes
val shutdownExecutor = Executors.newScheduledThreadPool(nodesToShut.size)
try {
nodes.map { shutdownExecutor.fork(it::dispose) }.transpose().getOrThrow()
nodesToShut.map { shutdownExecutor.fork(it::dispose) }.transpose().getOrThrow()
// Wait until ports are released
val portNotBoundChecks = nodes.flatMap {
val portNotBoundChecks = nodesToShut.flatMap {
listOf(
addressMustNotBeBoundFuture(shutdownExecutor, it.node.configuration.p2pAddress),
addressMustNotBeBoundFuture(shutdownExecutor, it.node.configuration.rpcOptions.address)
)
}
nodes.clear()
notaryNodes.clear()
portNotBoundChecks.transpose().getOrThrow()
} finally {
shutdownExecutor.shutdown()
@ -146,8 +160,8 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
}
class InProcessNode(configuration: NodeConfiguration, versionInfo: VersionInfo, flowManager: FlowManager = NodeFlowManager(configuration.flowOverrides)) : Node(configuration, versionInfo, false, flowManager = flowManager) {
override fun start() : NodeInfo {
assertFalse(isInvalidJavaVersion(), "You are using a version of Java that is not supported (${SystemUtils.JAVA_VERSION}). Please upgrade to the latest version of Java 8." )
override fun start(): NodeInfo {
assertFalse(isInvalidJavaVersion(), "You are using a version of Java that is not supported (${SystemUtils.JAVA_VERSION}). Please upgrade to the latest version of Java 8.")
return super.start()
}

View File

@ -1,9 +1,11 @@
package net.corda.testing.common.internal
import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.utilities.days
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
@ -16,16 +18,31 @@ fun testNetworkParameters(
maxTransactionSize: Int = maxMessageSize * 50,
whitelistedContractImplementations: Map<String, List<AttachmentId>> = emptyMap(),
epoch: Int = 1,
eventHorizon: Duration = 30.days
eventHorizon: Duration = 30.days,
packageOwnership: Map<String, PublicKey> = emptyMap()
): NetworkParameters {
return NetworkParameters(
minimumPlatformVersion = minimumPlatformVersion,
notaries = notaries,
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
whitelistedContractImplementations = whitelistedContractImplementations,
modifiedTime = modifiedTime,
epoch = epoch,
eventHorizon = eventHorizon
whitelistedContractImplementations = whitelistedContractImplementations,
eventHorizon = eventHorizon,
packageOwnership = packageOwnership
)
}
}
/**
* Includes the specified notary [party] in the network parameter whitelist to ensure transactions verify.
* If used when actual notarisation flows are involved, the right notary type (validating/non-validating) should be specified.
*
* Note that it returns new network parameters with a different hash.
*/
fun NetworkParameters.addNotary(party: Party, validating: Boolean = true): NetworkParameters {
val notaryInfo = NotaryInfo(party, validating)
val notaryList = notaries + notaryInfo
return copy(notaries = notaryList)
}

View File

@ -13,6 +13,7 @@ import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.createComponentGroups
import net.corda.core.node.NodeInfo
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.loggerFor
import net.corda.node.internal.cordapp.set
@ -34,6 +35,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.registerDevP2pCertificates
import net.corda.serialization.internal.amqp.AMQP_ENABLED
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.stubs.CertificateStoreStubs
import java.io.ByteArrayOutputStream
import java.nio.file.Files
@ -41,9 +43,9 @@ import java.nio.file.Path
import java.security.KeyPair
import java.util.*
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.zip.ZipEntry
import javax.security.auth.x500.X500Principal
import java.util.jar.Manifest
@Suppress("unused")
inline fun <reified T : Any> T.kryoSpecific(reason: String, function: () -> Unit) = if (!AMQP_ENABLED) {
@ -183,14 +185,29 @@ fun configureDatabase(hikariProperties: Properties,
/**
* Convenience method for creating a fake attachment containing a file with some content.
*/
fun fakeAttachment(filePath: String, content: String, manifestAttributes: Map<String,String> = emptyMap()): ByteArray {
fun fakeAttachment(filePath: String, content: String, manifestAttributes: Map<String, String> = emptyMap()): ByteArray {
val bs = ByteArrayOutputStream()
val manifest = Manifest()
manifestAttributes.forEach{ manifest[it.key] = it.value} //adding manually instead of putAll, as it requires typed keys, not strings
manifestAttributes.forEach { manifest[it.key] = it.value } //adding manually instead of putAll, as it requires typed keys, not strings
JarOutputStream(bs, manifest).use { js ->
js.putNextEntry(ZipEntry(filePath))
js.writer().apply { append(content); flush() }
js.closeEntry()
}
return bs.toByteArray()
}
/** If [effectiveSerializationEnv] is not set, runs the block with a new [SerializationEnvironmentRule]. */
fun <R> withTestSerializationEnvIfNotSet(taskName: String, block: () -> R): R {
val serializationExists = try {
effectiveSerializationEnv
true
} catch (e: IllegalStateException) {
false
}
return if (serializationExists) {
block()
} else SerializationEnvironmentRule.run(taskName) {
block()
}
}