ENT-6548: Ensure LazyMappedList is realised with correct SerializationContext. (#7028)

This commit is contained in:
Chris Rankin 2022-01-17 09:57:33 +00:00 committed by GitHub
parent 265a293666
commit 4f1a07cbcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 19 deletions

View File

@ -56,7 +56,9 @@ import java.security.cert.TrustAnchor
import java.security.cert.X509Certificate
import java.time.Duration
import java.time.temporal.Temporal
import java.util.*
import java.util.Collections
import java.util.PrimitiveIterator
import java.util.Spliterator
import java.util.Spliterator.DISTINCT
import java.util.Spliterator.IMMUTABLE
import java.util.Spliterator.NONNULL
@ -64,6 +66,7 @@ import java.util.Spliterator.ORDERED
import java.util.Spliterator.SIZED
import java.util.Spliterator.SORTED
import java.util.Spliterator.SUBSIZED
import java.util.Spliterators
import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit
import java.util.stream.Collectors

View File

@ -114,14 +114,14 @@ fun deserialiseCommands(
componentGroups: List<ComponentGroup>,
forceDeserialize: Boolean = false,
factory: SerializationFactory = SerializationFactory.defaultFactory,
@Suppress("UNUSED_PARAMETER") context: SerializationContext = factory.defaultContext,
context: SerializationContext = factory.defaultContext,
digestService: DigestService = DigestService.sha2_256
): List<Command<*>> {
// TODO: we could avoid deserialising unrelated signers.
// However, current approach ensures the transaction is not malformed
// and it will throw if any of the signers objects is not List of public keys).
val signersList: List<List<PublicKey>> = uncheckedCast(deserialiseComponentGroup(componentGroups, List::class, ComponentGroupEnum.SIGNERS_GROUP, forceDeserialize))
val commandDataList: List<CommandData> = deserialiseComponentGroup(componentGroups, CommandData::class, ComponentGroupEnum.COMMANDS_GROUP, forceDeserialize)
val signersList: List<List<PublicKey>> = uncheckedCast(deserialiseComponentGroup(componentGroups, List::class, ComponentGroupEnum.SIGNERS_GROUP, forceDeserialize, factory, context))
val commandDataList: List<CommandData> = deserialiseComponentGroup(componentGroups, CommandData::class, ComponentGroupEnum.COMMANDS_GROUP, forceDeserialize, factory, context)
val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal }
return if (group is FilteredComponentGroup) {
check(commandDataList.size <= signersList.size) {
@ -154,8 +154,9 @@ fun createComponentGroups(inputs: List<StateRef>,
timeWindow: TimeWindow?,
references: List<StateRef>,
networkParametersHash: SecureHash?): List<ComponentGroup> {
val serializationContext = SerializationFactory.defaultFactory.defaultContext
val serialize = { value: Any, _: Int -> value.serialize(context = serializationContext) }
val serializationFactory = SerializationFactory.defaultFactory
val serializationContext = serializationFactory.defaultContext
val serialize = { value: Any, _: Int -> value.serialize(serializationFactory, serializationContext) }
val componentGroupMap: MutableList<ComponentGroup> = mutableListOf()
if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.INPUTS_GROUP.ordinal, inputs.lazyMapped(serialize)))
if (references.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.REFERENCES_GROUP.ordinal, references.lazyMapped(serialize)))
@ -178,7 +179,11 @@ fun createComponentGroups(inputs: List<StateRef>,
*/
@KeepForDJVM
data class SerializedStateAndRef(val serializedState: SerializedBytes<TransactionState<ContractState>>, val ref: StateRef) {
fun toStateAndRef(): StateAndRef<ContractState> = StateAndRef(serializedState.deserialize(), ref)
fun toStateAndRef(factory: SerializationFactory, context: SerializationContext) = StateAndRef(serializedState.deserialize(factory, context), ref)
fun toStateAndRef(): StateAndRef<ContractState> {
val factory = SerializationFactory.defaultFactory
return toStateAndRef(factory, factory.defaultContext)
}
}
/** Check that network parameters hash on this transaction is the current hash for the network. */

View File

@ -15,6 +15,7 @@ import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.serialize
@ -187,19 +188,26 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
): LedgerTransaction {
// Look up public keys to authenticated identities.
val authenticatedCommands = commands.lazyMapped { cmd, _ ->
val parties = cmd.signers.mapNotNull { pk -> resolveIdentity(pk) }
val parties = cmd.signers.mapNotNull(resolveIdentity)
CommandWithParties(cmd.signers, parties, cmd.value)
}
// Ensure that the lazy mappings will use the correct SerializationContext.
val serializationFactory = SerializationFactory.defaultFactory
val serializationContext = serializationFactory.defaultContext
val toStateAndRef = { ssar: SerializedStateAndRef, _: Int ->
ssar.toStateAndRef(serializationFactory, serializationContext)
}
val serializedResolvedInputs = inputs.map { ref ->
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
}
val resolvedInputs = serializedResolvedInputs.lazyMapped { star, _ -> star.toStateAndRef() }
val resolvedInputs = serializedResolvedInputs.lazyMapped(toStateAndRef)
val serializedResolvedReferences = references.map { ref ->
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
}
val resolvedReferences = serializedResolvedReferences.lazyMapped { star, _ -> star.toStateAndRef() }
val resolvedReferences = serializedResolvedReferences.lazyMapped(toStateAndRef)
val resolvedAttachments = attachments.lazyMapped { att, _ -> resolveAttachment(att) ?: throw AttachmentResolutionException(att) }

View File

@ -30,7 +30,7 @@ import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.*
import java.util.Collections
import javax.annotation.concurrent.ThreadSafe
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty
@ -509,6 +509,7 @@ class ThrowableSerializer<T>(kryo: Kryo, type: Class<T>) : Serializer<Throwable>
@ThreadSafe
@SuppressWarnings("ALL")
object LazyMappedListSerializer : Serializer<List<*>>() {
override fun write(kryo: Kryo, output: Output, obj: List<*>) = kryo.writeClassAndObject(output, obj.toList())
override fun read(kryo: Kryo, input: Input, type: Class<List<*>>) = kryo.readClassAndObject(input) as List<*>
// Using a MutableList so that Kryo will always write an instance of java.util.ArrayList.
override fun write(kryo: Kryo, output: Output, obj: List<*>) = kryo.writeClassAndObject(output, obj.toMutableList())
override fun read(kryo: Kryo, input: Input, type: Class<List<*>>) = kryo.readClassAndObject(input) as? List<*>
}

View File

@ -0,0 +1,68 @@
package net.corda.node
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.node.services.config.NodeConfiguration
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.findCordapp
import org.junit.Test
import org.junit.jupiter.api.assertDoesNotThrow
/**
* Execute a flow with sub-flows, including the finality flow.
* This operation should checkpoint, and have its checkpoint restored.
*/
@Suppress("FunctionName")
class CashIssueAndPaymentTest {
companion object {
private val logger = loggerFor<CashIssueAndPaymentTest>()
private val configOverrides = mapOf(NodeConfiguration::reloadCheckpointAfterSuspend.name to true)
private val CASH_AMOUNT = 500.DOLLARS
fun parametersFor(): DriverParameters {
return DriverParameters(
systemProperties = mapOf("co.paralleluniverse.fibers.verifyInstrumentation" to "false"),
portAllocation = incrementalPortAllocation(),
startNodesInProcess = false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = false, validating = true)),
notaryCustomOverrides = configOverrides,
cordappsForAllNodes = listOf(
findCordapp("net.corda.finance.contracts"),
findCordapp("net.corda.finance.workflows")
)
)
}
}
@Test(timeout = 300_000)
fun `test can issue cash`() {
driver(parametersFor()) {
val alice = startNode(providedName = ALICE_NAME, customOverrides = configOverrides).getOrThrow()
val aliceParty = alice.nodeInfo.singleIdentity()
val notaryParty = notaryHandles.single().identity
val result = assertDoesNotThrow {
alice.rpc.startFlow(::CashIssueAndPaymentFlow,
CASH_AMOUNT,
OpaqueBytes.of(0x01),
aliceParty,
false,
notaryParty
).use { flowHandle ->
flowHandle.returnValue.getOrThrow()
}
}
logger.info("TXN={}, recipient={}", result.stx, result.recipient)
}
}
}

View File

@ -7,6 +7,7 @@ import net.corda.core.utilities.loggerFor
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.node.DeterministicSourcesRule
import net.corda.node.services.config.NodeConfiguration
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.singleIdentity
@ -22,20 +23,21 @@ import org.junit.jupiter.api.assertDoesNotThrow
@Suppress("FunctionName")
class DeterministicCashIssueAndPaymentTest {
companion object {
val logger = loggerFor<DeterministicCashIssueAndPaymentTest>()
private val logger = loggerFor<DeterministicCashIssueAndPaymentTest>()
private val configOverrides = mapOf(NodeConfiguration::reloadCheckpointAfterSuspend.name to true)
private val CASH_AMOUNT = 500.DOLLARS
@ClassRule
@JvmField
val djvmSources = DeterministicSourcesRule()
@JvmField
val CASH_AMOUNT = 500.DOLLARS
fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
notaryCustomOverrides = configOverrides,
cordappsForAllNodes = listOf(
findCordapp("net.corda.finance.contracts"),
findCordapp("net.corda.finance.workflows")
@ -50,7 +52,7 @@ class DeterministicCashIssueAndPaymentTest {
fun `test DJVM can issue cash`() {
val reference = OpaqueBytes.of(0x01)
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val alice = startNode(providedName = ALICE_NAME, customOverrides = configOverrides).getOrThrow()
val aliceParty = alice.nodeInfo.singleIdentity()
val notaryParty = notaryHandles.single().identity
val txId = assertDoesNotThrow {
@ -60,7 +62,9 @@ class DeterministicCashIssueAndPaymentTest {
aliceParty,
false,
notaryParty
).returnValue.getOrThrow()
).use { flowHandle ->
flowHandle.returnValue.getOrThrow()
}
}
logger.info("TX-ID: {}", txId)
}