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

View File

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

View File

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

View File

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

View File

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

View File

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