mirror of
https://github.com/corda/corda.git
synced 2025-06-19 07:38:22 +00:00
Add serialization workaround for HashMaps inside states. Fix tests.
This commit is contained in:
@ -42,7 +42,7 @@ class TransactionGraphSearch(val transactions: ReadOnlyTransactionStorage,
|
|||||||
results += tx
|
results += tx
|
||||||
|
|
||||||
val inputsLeadingToUnvisitedTx: Iterable<StateRef> = tx.inputs.filter { it.txhash !in alreadyVisited }
|
val inputsLeadingToUnvisitedTx: Iterable<StateRef> = tx.inputs.filter { it.txhash !in alreadyVisited }
|
||||||
val unvisitedInputTxs: Map<SecureHash, SignedTransaction> = inputsLeadingToUnvisitedTx.map { it.txhash }.toHashSet().map { transactions.getTransaction(it) }.filterNotNull().associateBy { it.txBits.hash }
|
val unvisitedInputTxs: Map<SecureHash, SignedTransaction> = inputsLeadingToUnvisitedTx.map { it.txhash }.toHashSet().map { transactions.getTransaction(it) }.filterNotNull().associateBy { it.id }
|
||||||
val unvisitedInputTxsWithInputIndex: Iterable<Pair<SignedTransaction, Int>> = inputsLeadingToUnvisitedTx.filter { it.txhash in unvisitedInputTxs.keys }.map { Pair(unvisitedInputTxs[it.txhash]!!, it.index) }
|
val unvisitedInputTxsWithInputIndex: Iterable<Pair<SignedTransaction, Int>> = inputsLeadingToUnvisitedTx.filter { it.txhash in unvisitedInputTxs.keys }.map { Pair(unvisitedInputTxs[it.txhash]!!, it.index) }
|
||||||
next += (unvisitedInputTxsWithInputIndex.filter { q.followInputsOfType == null || it.first.tx.outputs[it.second].data.javaClass == q.followInputsOfType }
|
next += (unvisitedInputTxsWithInputIndex.filter { q.followInputsOfType == null || it.first.tx.outputs[it.second].data.javaClass == q.followInputsOfType }
|
||||||
.map { it.first }.filter { alreadyVisited.add(it.txBits.hash) }.map { it.tx })
|
.map { it.first }.filter { alreadyVisited.add(it.txBits.hash) }.map { it.tx })
|
||||||
|
@ -9,6 +9,7 @@ import com.esotericsoftware.kryo.Serializer
|
|||||||
import com.esotericsoftware.kryo.io.Input
|
import com.esotericsoftware.kryo.io.Input
|
||||||
import com.esotericsoftware.kryo.io.Output
|
import com.esotericsoftware.kryo.io.Output
|
||||||
import com.esotericsoftware.kryo.serializers.JavaSerializer
|
import com.esotericsoftware.kryo.serializers.JavaSerializer
|
||||||
|
import com.esotericsoftware.kryo.serializers.MapSerializer
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.*
|
import com.r3corda.core.crypto.*
|
||||||
import com.r3corda.core.node.AttachmentsClassLoader
|
import com.r3corda.core.node.AttachmentsClassLoader
|
||||||
@ -437,3 +438,33 @@ var Kryo.attachmentStorage: AttachmentStorage?
|
|||||||
set(value) {
|
set(value) {
|
||||||
this.context.put(ATTACHMENT_STORAGE, value)
|
this.context.put(ATTACHMENT_STORAGE, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: It's a little workaround for serialization of HashMaps inside contract states.
|
||||||
|
//Used in Merkle tree calculation. It doesn't cover all the cases of unstable serialization format.
|
||||||
|
fun extendKryoHash(kryo: Kryo): Kryo {
|
||||||
|
return kryo.apply {
|
||||||
|
noReferencesWithin<TransactionState<ContractState>>()
|
||||||
|
register(LinkedHashMap::class.java, MapSerializer())
|
||||||
|
register(HashMap::class.java, OrderedSerializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object OrderedSerializer : Serializer<HashMap<Any, Any>>() {
|
||||||
|
override fun write(kryo: Kryo, output: Output, obj: HashMap<Any, Any>) {
|
||||||
|
//Change a HashMap to LinkedHashMap.
|
||||||
|
val linkedMap = LinkedHashMap<Any, Any>()
|
||||||
|
val sorted = obj.toList().sortedBy { it.first.hashCode() }
|
||||||
|
for ((k, v) in sorted) {
|
||||||
|
linkedMap.put(k,v)
|
||||||
|
}
|
||||||
|
kryo.writeClassAndObject(output, linkedMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
//It will be deserialized as a LinkedHashMap.
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun read(kryo: Kryo, input: Input, type: Class<HashMap<Any, Any>>): HashMap<Any, Any> {
|
||||||
|
val hm = kryo.readClassAndObject(input) as HashMap<Any, Any>
|
||||||
|
return hm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import com.r3corda.core.contracts.ContractState
|
|||||||
import com.r3corda.core.contracts.StateRef
|
import com.r3corda.core.contracts.StateRef
|
||||||
import com.r3corda.core.contracts.TransactionState
|
import com.r3corda.core.contracts.TransactionState
|
||||||
import com.r3corda.core.crypto.*
|
import com.r3corda.core.crypto.*
|
||||||
import com.r3corda.core.serialization.serialize
|
import com.r3corda.core.serialization.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,7 +26,11 @@ fun WireTransaction.calculateLeavesHashes(): List<SecureHash> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun SecureHash.hashConcat(other: SecureHash) = (this.bits + other.bits).sha256()
|
fun SecureHash.hashConcat(other: SecureHash) = (this.bits + other.bits).sha256()
|
||||||
fun <T: Any> serializedHash(x: T) = x.serialize().hash
|
|
||||||
|
fun <T: Any> serializedHash(x: T): SecureHash {
|
||||||
|
val kryo = extendKryoHash(createKryo()) //Dealing with HashMaps inside states.
|
||||||
|
return x.serialize(kryo).hash
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creation and verification of a Merkle Tree for a Wire Transaction.
|
* Creation and verification of a Merkle Tree for a Wire Transaction.
|
||||||
|
@ -45,7 +45,7 @@ class WireTransaction(
|
|||||||
|
|
||||||
@Volatile @Transient var cachedTree: MerkleTree? = null
|
@Volatile @Transient var cachedTree: MerkleTree? = null
|
||||||
val merkleTree: MerkleTree get() = cachedTree ?: MerkleTree.getMerkleTree(allLeavesHashes).apply { cachedTree = this }
|
val merkleTree: MerkleTree get() = cachedTree ?: MerkleTree.getMerkleTree(allLeavesHashes).apply { cachedTree = this }
|
||||||
//TODO There is a problem with that, it's failing 4 tests.
|
|
||||||
override val id: SecureHash get() = merkleTree.hash
|
override val id: SecureHash get() = merkleTree.hash
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
package com.r3corda.core.crypto
|
package com.r3corda.core.crypto
|
||||||
|
|
||||||
|
import com.esotericsoftware.kryo.serializers.MapSerializer
|
||||||
import com.r3corda.contracts.asset.*
|
import com.r3corda.contracts.asset.*
|
||||||
import com.r3corda.core.contracts.DOLLARS
|
import com.r3corda.core.contracts.DOLLARS
|
||||||
import com.r3corda.core.contracts.`issued by`
|
import com.r3corda.core.contracts.`issued by`
|
||||||
import com.r3corda.core.serialization.serialize
|
import com.r3corda.core.serialization.*
|
||||||
import com.r3corda.core.transactions.*
|
import com.r3corda.core.transactions.*
|
||||||
import com.r3corda.core.utilities.DUMMY_PUBKEY_1
|
import com.r3corda.core.utilities.DUMMY_PUBKEY_1
|
||||||
import com.r3corda.testing.*
|
import com.r3corda.testing.*
|
||||||
|
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class PartialMerkleTreeTest {
|
class PartialMerkleTreeTest {
|
||||||
val nodes = "abcdef"
|
val nodes = "abcdef"
|
||||||
@ -75,6 +78,8 @@ class PartialMerkleTreeTest {
|
|||||||
filterOutputs = { true },
|
filterOutputs = { true },
|
||||||
filterInputs = { true })
|
filterInputs = { true })
|
||||||
val mt = testTx.buildFilteredTransaction(filterFuns)
|
val mt = testTx.buildFilteredTransaction(filterFuns)
|
||||||
|
val d = WireTransaction.deserialize(testTx.serialized)
|
||||||
|
assertEquals(testTx.id, d.id)
|
||||||
assert(mt.verify(testTx.id))
|
assert(mt.verify(testTx.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,4 +156,15 @@ class PartialMerkleTreeTest {
|
|||||||
val wrongRoot = hashed[3].hashConcat(hashed[5])
|
val wrongRoot = hashed[3].hashConcat(hashed[5])
|
||||||
assertFalse(pmt.verify(wrongRoot, inclHashes))
|
assertFalse(pmt.verify(wrongRoot, inclHashes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `hash map serialization`(){
|
||||||
|
val hm1 = hashMapOf("a" to 1, "b" to 2, "c" to 3, "e" to 4)
|
||||||
|
assert(serializedHash(hm1) == serializedHash(hm1.serialize().deserialize())) //It internally uses the ordered HashMap extension.
|
||||||
|
val kryo = extendKryoHash(createKryo())
|
||||||
|
assertTrue(kryo.getSerializer(HashMap::class.java) is OrderedSerializer)
|
||||||
|
assertTrue(kryo.getSerializer(LinkedHashMap::class.java) is MapSerializer)
|
||||||
|
val hm2 = hm1.serialize(kryo).deserialize(kryo)
|
||||||
|
assert(hm1.hashCode() == hm2.hashCode())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,15 @@ package com.r3corda.node.services.persistence
|
|||||||
|
|
||||||
import com.google.common.primitives.Ints
|
import com.google.common.primitives.Ints
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
|
import com.r3corda.core.contracts.StateRef
|
||||||
|
import com.r3corda.core.contracts.TransactionType
|
||||||
import com.r3corda.core.crypto.DigitalSignature
|
import com.r3corda.core.crypto.DigitalSignature
|
||||||
import com.r3corda.core.crypto.NullPublicKey
|
import com.r3corda.core.crypto.NullPublicKey
|
||||||
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.serialization.SerializedBytes
|
import com.r3corda.core.serialization.SerializedBytes
|
||||||
import com.r3corda.core.transactions.SignedTransaction
|
import com.r3corda.core.transactions.SignedTransaction
|
||||||
|
import com.r3corda.core.transactions.WireTransaction
|
||||||
|
import com.r3corda.core.utilities.DUMMY_NOTARY
|
||||||
import com.r3corda.core.utilities.LogHelper
|
import com.r3corda.core.utilities.LogHelper
|
||||||
import com.r3corda.node.services.transactions.PersistentUniquenessProvider
|
import com.r3corda.node.services.transactions.PersistentUniquenessProvider
|
||||||
import com.r3corda.node.utilities.configureDatabase
|
import com.r3corda.node.utilities.configureDatabase
|
||||||
@ -128,8 +133,17 @@ class DBTransactionStorageTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var txCount = 0
|
private fun newTransaction(): SignedTransaction {
|
||||||
private fun newTransaction() = SignedTransaction(
|
val wtx = WireTransaction(
|
||||||
SerializedBytes(Ints.toByteArray(++txCount)),
|
inputs = listOf(StateRef(SecureHash.randomSHA256(), 0)),
|
||||||
listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1))))
|
attachments = emptyList(),
|
||||||
|
outputs = emptyList(),
|
||||||
|
commands = emptyList(),
|
||||||
|
notary = DUMMY_NOTARY,
|
||||||
|
signers = emptyList(),
|
||||||
|
type = TransactionType.General(),
|
||||||
|
timestamp = null
|
||||||
|
)
|
||||||
|
return SignedTransaction(wtx.serialized, listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1))))
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user