Add serialization workaround for HashMaps inside states. Fix tests.

This commit is contained in:
Katarzyna Streich 2016-11-04 17:21:11 +00:00
parent 023ba380a0
commit 71965b0792
6 changed files with 74 additions and 9 deletions

View File

@ -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 })

View File

@ -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
}
}

View File

@ -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.

View File

@ -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 {

View File

@ -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())
}
}

View File

@ -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))))
}
}