mirror of
https://github.com/corda/corda.git
synced 2025-02-20 17:33:15 +00:00
Add serialization workaround for HashMaps inside states. Fix tests.
This commit is contained in:
parent
023ba380a0
commit
71965b0792
@ -42,7 +42,7 @@ class TransactionGraphSearch(val transactions: ReadOnlyTransactionStorage,
|
||||
results += tx
|
||||
|
||||
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) }
|
||||
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 })
|
||||
|
@ -9,6 +9,7 @@ import com.esotericsoftware.kryo.Serializer
|
||||
import com.esotericsoftware.kryo.io.Input
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import com.esotericsoftware.kryo.serializers.JavaSerializer
|
||||
import com.esotericsoftware.kryo.serializers.MapSerializer
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.*
|
||||
import com.r3corda.core.node.AttachmentsClassLoader
|
||||
@ -437,3 +438,33 @@ var Kryo.attachmentStorage: AttachmentStorage?
|
||||
set(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.TransactionState
|
||||
import com.r3corda.core.crypto.*
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import com.r3corda.core.serialization.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -26,7 +26,11 @@ fun WireTransaction.calculateLeavesHashes(): List<SecureHash> {
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -45,7 +45,7 @@ class WireTransaction(
|
||||
|
||||
@Volatile @Transient var cachedTree: MerkleTree? = null
|
||||
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
|
||||
|
||||
companion object {
|
||||
|
@ -1,18 +1,21 @@
|
||||
package com.r3corda.core.crypto
|
||||
|
||||
import com.esotericsoftware.kryo.serializers.MapSerializer
|
||||
import com.r3corda.contracts.asset.*
|
||||
import com.r3corda.core.contracts.DOLLARS
|
||||
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.utilities.DUMMY_PUBKEY_1
|
||||
import com.r3corda.testing.*
|
||||
|
||||
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class PartialMerkleTreeTest {
|
||||
val nodes = "abcdef"
|
||||
@ -75,6 +78,8 @@ class PartialMerkleTreeTest {
|
||||
filterOutputs = { true },
|
||||
filterInputs = { true })
|
||||
val mt = testTx.buildFilteredTransaction(filterFuns)
|
||||
val d = WireTransaction.deserialize(testTx.serialized)
|
||||
assertEquals(testTx.id, d.id)
|
||||
assert(mt.verify(testTx.id))
|
||||
}
|
||||
|
||||
@ -151,4 +156,15 @@ class PartialMerkleTreeTest {
|
||||
val wrongRoot = hashed[3].hashConcat(hashed[5])
|
||||
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.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.NullPublicKey
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.serialization.SerializedBytes
|
||||
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.node.services.transactions.PersistentUniquenessProvider
|
||||
import com.r3corda.node.utilities.configureDatabase
|
||||
@ -128,8 +133,17 @@ class DBTransactionStorageTests {
|
||||
}
|
||||
}
|
||||
|
||||
private var txCount = 0
|
||||
private fun newTransaction() = SignedTransaction(
|
||||
SerializedBytes(Ints.toByteArray(++txCount)),
|
||||
listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1))))
|
||||
private fun newTransaction(): SignedTransaction {
|
||||
val wtx = WireTransaction(
|
||||
inputs = listOf(StateRef(SecureHash.randomSHA256(), 0)),
|
||||
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))))
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user