Merge branch 'release/os/4.1' into tudor_merge_4.1_to_4.3

# Conflicts:
#	node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt
This commit is contained in:
tudor.malene@gmail.com 2019-09-09 16:04:39 +03:00
commit ea73dcfb22
4 changed files with 71 additions and 5 deletions

View File

@ -34,8 +34,8 @@ couple of resources.
.. code-block:: bash
wget https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda-finance-contracts-|corda_version|-corda/corda-finance-contracts-|corda_version|-corda.jar
wget https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda-finance-workflows-|corda_version|-corda/corda-finance-workflows-|corda_version|-corda.jar
wget https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda-finance-contracts/|corda_version|/corda-finance-contracts-|corda_version|.jar
wget https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda-finance-workflows/|corda_version|/corda-finance-workflows-|corda_version|.jar
This is required to run some flows to check your connections, and to issue/transfer cash to counterparties. Copy it to
the Corda installation location:

View File

@ -719,3 +719,29 @@ Although not strictly related to versioning, AMQP serialisation dictates that we
wildcard
* Any superclass must adhere to the same rules, but can be abstract
* Object graph cycles are not supported, so an object cannot refer to itself, directly or indirectly
Testing CorDapp upgrades
------------------------
At the time of this writing there is no platform support to test CorDapp upgrades. There are plans to add support in a future version.
This means that it is not possible to write automated tests using just the provided tooling.
To test an implicit upgrade, you must simulate a network that initially has only nodes with the old version of the CorDapp, and then nodes gradually transition to the new version.
Typically, in such a complex upgrade scenario, there must be a deadline by which time all nodes that want to continue to use the CorDapp must upgrade.
To achieve this, this deadline must be configured in the flow logic which must only use new features afterwards.
This can be simulated with a scenario like this:
1. Write and individually test the new version of the state and contract.
2. Setup a network of nodes with the previous version. In the simplest form, `deployNodes` can be used for this purpose.
3. Run some transactions between nodes.
4. Upgrade a couple of nodes to the new version of the CorDapp.
5. Continue running transactions between various combinations of versions. Also make sure transactions that were created between nodes with the new version
are being successfully read by nodes with the old CorDapp.
6. Upgrade all nodes and simulate the deadline expiration.
7. Make sure old transactions can be consumed, and new features are successfully used in new transactions.

View File

@ -28,6 +28,8 @@ import java.security.PublicKey
import java.time.Clock
import java.time.Instant
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import javax.persistence.Tuple
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.CriteriaUpdate
@ -91,7 +93,8 @@ class NodeVaultService(
* Maintain a list of contract state interfaces to concrete types stored in the vault
* for usage in generic queries of type queryBy<LinearState> or queryBy<FungibleState<*>>
*/
private val contractStateTypeMappings = mutableMapOf<String, MutableSet<String>>().toSynchronised()
@VisibleForTesting
internal val contractStateTypeMappings = ConcurrentHashMap<String, MutableSet<String>>()
override fun start() {
bootstrapContractStateTypes()
@ -103,7 +106,7 @@ class NodeVaultService(
if (!seen) {
val contractTypes = deriveContractTypes(concreteType)
contractTypes.map {
val contractStateType = contractStateTypeMappings.getOrPut(it.name) { mutableSetOf() }
val contractStateType = contractStateTypeMappings.getOrPut(it.name) { CopyOnWriteArraySet() }
contractStateType.add(concreteType.name)
}
}
@ -207,6 +210,9 @@ class NodeVaultService(
override val updates: Observable<Vault.Update<ContractState>>
get() = mutex.locked { _updatesInDbTx }
@VisibleForTesting
internal val publishUpdates get() = mutex.locked { updatesPublisher }
/** Groups adjacent transactions into batches to generate separate net updates per transaction type. */
override fun notifyAll(statesToRecord: StatesToRecord, txns: Iterable<CoreTransaction>, previouslySeenTxns: Iterable<CoreTransaction>) {
if (statesToRecord == StatesToRecord.NONE || (!txns.any() && !previouslySeenTxns.any())) return
@ -738,7 +744,7 @@ class NodeVaultService(
concreteType?.let {
val contractTypes = deriveContractTypes(it)
contractTypes.map {
val contractStateType = contractStateTypeMappings.getOrPut(it.name) { mutableSetOf() }
val contractStateType = contractStateTypeMappings.getOrPut(it.name) { CopyOnWriteArraySet() }
contractStateType.add(concreteType.name)
}
}

View File

@ -7,6 +7,7 @@ import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.NullKeys
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.*
import net.corda.core.internal.NotaryChangeTransactionBuilder
@ -22,6 +23,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.toNonEmptySet
import net.corda.finance.*
import net.corda.finance.contracts.asset.Cash
@ -951,4 +953,36 @@ class NodeVaultServiceTest {
assertTrue(it)
}
}
@Test
fun `test concurrent update of contract state type mappings`() {
// no registered contract state types at start-up.
assertEquals(0, vaultService.contractStateTypeMappings.size)
fun makeCash(amount: Amount<Currency>, issuer: AbstractParty, depositRef: Byte = 1) =
StateAndRef(
TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), identity.party), Cash.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
)
val cashIssued = setOf<StateAndRef<ContractState>>(makeCash(100.DOLLARS, dummyCashIssuer.party))
val cashUpdate = Vault.Update(emptySet(), cashIssued)
val service = Executors.newFixedThreadPool(10)
(1..100).map {
service.submit {
database.transaction {
vaultService.publishUpdates.onNext(cashUpdate)
}
}
}.forEach { it.getOrThrow() }
vaultService.contractStateTypeMappings.forEach {
println("${it.key} = ${it.value}")
}
// Cash.State and its superclasses and interfaces: FungibleAsset, FungibleState, OwnableState, QueryableState
assertEquals(4, vaultService.contractStateTypeMappings.size)
service.shutdown()
}
}