blob: 216d1a2bc45831e3c9901739c63e274837de85ea [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tuweni.trie
import org.apache.tuweni.bytes.Bytes
import org.apache.tuweni.bytes.Bytes32
import org.apache.tuweni.trie.CompactEncoding.bytesToPath
import org.apache.tuweni.trie.MerkleTrie.Companion.EMPTY_TRIE_ROOT_HASH
import java.util.function.Function
/**
* A [MerkleTrie] that persists trie nodes to a [MerkleStorage] key/value store.
*
* @param <V> The type of values stored by this trie.
*/
class StoredMerklePatriciaTrie<V> : MerkleTrie<Bytes, V> {
companion object {
/**
* Create a trie with value of type [Bytes].
*
* @param storage The storage to use for persistence.
*/
@JvmStatic
fun storingBytes(storage: MerkleStorage): StoredMerklePatriciaTrie<Bytes> =
StoredMerklePatriciaTrie(storage, ::bytesIdentity, ::bytesIdentity)
/**
* Create a trie with keys and values of type [Bytes].
*
* @param storage The storage to use for persistence.
* @param rootHash The initial root has for the trie, which should be already present in `storage`.
*/
@JvmStatic
fun storingBytes(storage: MerkleStorage, rootHash: Bytes32): StoredMerklePatriciaTrie<Bytes> =
StoredMerklePatriciaTrie(storage, rootHash, ::bytesIdentity, ::bytesIdentity)
/**
* Create a trie with value of type [String].
*
* Strings are stored in UTF-8 encoding.
*
* @param storage The storage to use for persistence.
*/
@JvmStatic
fun storingStrings(storage: MerkleStorage): StoredMerklePatriciaTrie<String> =
StoredMerklePatriciaTrie(storage, ::stringSerializer, ::stringDeserializer)
/**
* Create a trie with keys and values of type [String].
*
* Strings are stored in UTF-8 encoding.
*
* @param storage The storage to use for persistence.
* @param rootHash The initial root has for the trie, which should be already present in `storage`.
*/
@JvmStatic
fun storingStrings(storage: MerkleStorage, rootHash: Bytes32): StoredMerklePatriciaTrie<String> =
StoredMerklePatriciaTrie(storage, rootHash, ::stringSerializer, ::stringDeserializer)
/**
* Create a trie.
*
* @param storage The storage to use for persistence.
* @param valueSerializer A function for serializing values to bytes.
* @param valueDeserializer A function for deserializing values from bytes.
* @param <V> The serialized type.
* @return A new merkle trie.
*/
@JvmStatic
fun <V> create(
storage: MerkleStorage,
valueSerializer: Function<V, Bytes>,
valueDeserializer: Function<Bytes, V>
): StoredMerklePatriciaTrie<V> {
return StoredMerklePatriciaTrie(storage, valueSerializer::apply, valueDeserializer::apply)
}
/**
* Create a trie.
*
* @param storage The storage to use for persistence.
* @param rootHash The initial root has for the trie, which should be already present in `storage`.
* @param valueSerializer A function for serializing values to bytes.
* @param valueDeserializer A function for deserializing values from bytes.
* @param <V> The serialized type.
* @return A new merkle trie.
*/
@JvmStatic
fun <V> create(
storage: MerkleStorage,
rootHash: Bytes32,
valueSerializer: Function<V, Bytes>,
valueDeserializer: Function<Bytes, V>
): StoredMerklePatriciaTrie<V> {
return StoredMerklePatriciaTrie(storage, rootHash, valueSerializer::apply, valueDeserializer::apply)
}
}
private val getVisitor = GetVisitor<V>()
private val removeVisitor = RemoveVisitor<V>()
private val storage: MerkleStorage
private val nodeFactory: StoredNodeFactory<V>
private var root: Node<V>
/**
* Create a trie.
*
* @param storage The storage to use for persistence.
* @param valueSerializer A function for serializing values to bytes.
* @param valueDeserializer A function for deserializing values from bytes.
*/
constructor(
storage: MerkleStorage,
valueSerializer: (V) -> Bytes,
valueDeserializer: (Bytes) -> V
) : this(storage, EMPTY_TRIE_ROOT_HASH, valueSerializer, valueDeserializer)
/**
* Create a trie.
*
* @param storage The storage to use for persistence.
* @param rootHash The initial root has for the trie, which should be already present in `storage`.
* @param valueSerializer A function for serializing values to bytes.
* @param valueDeserializer A function for deserializing values from bytes.
*/
constructor(
storage: MerkleStorage,
rootHash: Bytes32,
valueSerializer: (V) -> Bytes,
valueDeserializer: (Bytes) -> V
) {
this.storage = storage
this.nodeFactory = StoredNodeFactory(storage, valueSerializer, valueDeserializer)
this.root = if (rootHash == EMPTY_TRIE_ROOT_HASH) {
NullNode.instance()
} else {
StoredNode(nodeFactory, rootHash)
}
}
override suspend fun get(key: Bytes): V? = root.accept(getVisitor, bytesToPath(key)).value()
override suspend fun put(key: Bytes, value: V?) {
if (value == null) {
return remove(key)
}
updateRoot(root.accept(PutVisitor(nodeFactory, value), bytesToPath(key)))
}
override suspend fun remove(key: Bytes) = updateRoot(root.accept(removeVisitor, bytesToPath(key)))
override fun rootHash(): Bytes32 = root.hash()
/**
* Forces any cached trie nodes to be released, so they can be garbage collected.
*
* Note: nodes are already stored using [java.lang.ref.SoftReference]'s, so they will be released automatically
* based on memory demands.
*/
fun clearCache() {
val currentRoot = root
if (currentRoot is StoredNode<*>) {
currentRoot.unload()
}
}
private suspend fun updateRoot(newRoot: Node<V>) {
this.root = if (newRoot is StoredNode<*>) {
newRoot
} else {
storage.put(newRoot.hash(), newRoot.rlp())
StoredNode(nodeFactory, newRoot)
}
}
/**
* @return A string representation of the object.
*/
override fun toString(): String {
return javaClass.simpleName + "[" + rootHash() + "]"
}
}