blob: 5e3daf69d11033499aea8121c76f55a8be59584a [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 static org.junit.jupiter.api.Assertions.*;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.concurrent.AsyncCompletion;
import org.apache.tuweni.concurrent.AsyncResult;
import org.apache.tuweni.junit.BouncyCastleExtension;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(BouncyCastleExtension.class)
class StoredMerklePatriciaTrieJavaTest {
private MerkleStorage merkleStorage;
private StoredMerklePatriciaTrie<String> trie;
@BeforeEach
void setup() {
Map<Bytes32, Bytes> storage = new HashMap<>();
merkleStorage = new AsyncMerkleStorage() {
@Override
public @NotNull AsyncResult<Bytes> getAsync(@NotNull Bytes32 hash) {
return AsyncResult.completed(storage.get(hash));
}
@Override
public @NotNull AsyncCompletion putAsync(@NotNull Bytes32 hash, @NotNull Bytes content) {
storage.put(hash, content);
return AsyncCompletion.completed();
}
};
trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage);
}
@Test
void testEmptyTreeReturnsEmpty() throws Exception {
assertNull(trie.getAsync(Bytes.EMPTY).get());
}
@Test
void testEmptyTreeHasKnownRootHash() {
assertEquals("0x56E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421", trie.rootHash().toString());
}
@Test
void testDeletesEntryUpdateWithNull() throws Exception {
final Bytes key = Bytes.of(1);
trie.putAsync(key, "value1").join();
trie.putAsync(key, null).join();
assertNull(trie.getAsync(key).get());
}
@Test
void testReplaceSingleValue() throws Exception {
final Bytes key = Bytes.of(1);
trie.putAsync(key, "value1").join();
assertEquals("value1", trie.getAsync(key).get());
trie.putAsync(key, "value2").join();
assertEquals("value2", trie.getAsync(key).get());
}
@Test
void testHashChangesWhenSingleValueReplaced() throws Exception {
final Bytes key = Bytes.of(1);
trie.putAsync(key, "value1").join();
final Bytes32 hash1 = trie.rootHash();
trie.putAsync(key, "value2").join();
final Bytes32 hash2 = trie.rootHash();
assertNotEquals(hash2, hash1);
trie.putAsync(key, "value1").join();
assertEquals(hash1, trie.rootHash());
}
@Test
void testReadPastLeaf() throws Exception {
final Bytes key1 = Bytes.of(1);
final Bytes key2 = Bytes.of(1, 3);
trie.putAsync(key1, "value").join();
assertNull(trie.getAsync(key2).get());
}
@Test
void testBranchValue() throws Exception {
final Bytes key1 = Bytes.of(1);
final Bytes key2 = Bytes.of(16);
trie.putAsync(key1, "value1").join();
trie.putAsync(key2, "value2").join();
assertEquals("value1", trie.getAsync(key1).get());
assertEquals("value2", trie.getAsync(key2).get());
}
@Test
void testReadPastBranch() throws Exception {
final Bytes key1 = Bytes.of(12);
final Bytes key2 = Bytes.of(12, 54);
final Bytes key3 = Bytes.of(3);
trie.putAsync(key1, "value1").join();
trie.putAsync(key2, "value2").join();
assertNull(trie.getAsync(key3).get());
}
@Test
void testBranchWithValue() throws Exception {
final Bytes key1 = Bytes.of(5);
final Bytes key2 = Bytes.EMPTY;
trie.putAsync(key1, "value1").join();
trie.putAsync(key2, "value2").join();
assertEquals("value1", trie.getAsync(key1).get());
assertEquals("value2", trie.getAsync(key2).get());
}
@Test
void testExtendAndBranch() throws Exception {
final Bytes key1 = Bytes.of(1, 5, 9);
final Bytes key2 = Bytes.of(1, 5, 2);
trie.putAsync(key1, "value1").join();
trie.putAsync(key2, "value2").join();
assertEquals("value1", trie.getAsync(key1).get());
assertEquals("value2", trie.getAsync(key2).get());
assertNull(trie.getAsync(Bytes.of(1, 4)).get());
}
@Test
void testBranchFromTopOfExtend() throws Exception {
final Bytes key1 = Bytes.of(0xFE, 1);
final Bytes key2 = Bytes.of(0xFE, 2);
final Bytes key3 = Bytes.of(0xE1, 1);
trie.putAsync(key1, "value1").join();
trie.putAsync(key2, "value2").join();
trie.putAsync(key3, "value3").join();
assertEquals("value1", trie.getAsync(key1).get());
assertEquals("value2", trie.getAsync(key2).get());
assertEquals("value3", trie.getAsync(key3).get());
assertNull(trie.getAsync(Bytes.of(1, 4)).get());
assertNull(trie.getAsync(Bytes.of(2, 4)).get());
assertNull(trie.getAsync(Bytes.of(3)).get());
}
@Test
void testSplitBranchExtension() throws Exception {
final Bytes key1 = Bytes.of(1, 5, 9);
final Bytes key2 = Bytes.of(1, 5, 2);
final Bytes key3 = Bytes.of(1, 9, 1);
trie.putAsync(key1, "value1").join();
trie.putAsync(key2, "value2").join();
trie.putAsync(key3, "value3").join();
assertEquals("value1", trie.getAsync(key1).get());
assertEquals("value2", trie.getAsync(key2).get());
assertEquals("value3", trie.getAsync(key3).get());
}
@Test
void testReplaceBranchChild() throws Exception {
final Bytes key1 = Bytes.of(0);
final Bytes key2 = Bytes.of(1);
trie.putAsync(key1, "value1").join();
trie.putAsync(key2, "value2").join();
assertEquals("value1", trie.getAsync(key1).get());
assertEquals("value2", trie.getAsync(key2).get());
trie.putAsync(key1, "value3").join();
assertEquals("value3", trie.getAsync(key1).get());
assertEquals("value2", trie.getAsync(key2).get());
}
@Test
void testInlineBranchInBranch() throws Exception {
final Bytes key1 = Bytes.of(0);
final Bytes key2 = Bytes.of(1);
final Bytes key3 = Bytes.of(2);
final Bytes key4 = Bytes.of(0, 0);
final Bytes key5 = Bytes.of(0, 1);
trie.putAsync(key1, "value1").join();
trie.putAsync(key2, "value2").join();
trie.putAsync(key3, "value3").join();
trie.putAsync(key4, "value4").join();
trie.putAsync(key5, "value5").join();
trie.removeAsync(key2).join();
trie.removeAsync(key3).join();
assertEquals("value1", trie.getAsync(key1).get());
assertNull(trie.getAsync(key2).get());
assertNull(trie.getAsync(key3).get());
assertEquals("value4", trie.getAsync(key4).get());
assertEquals("value5", trie.getAsync(key5).get());
}
@Test
void testRemoveNodeInBranchExtensionHasNoEffect() throws Exception {
final Bytes key1 = Bytes.of(1, 5, 9);
final Bytes key2 = Bytes.of(1, 5, 2);
trie.putAsync(key1, "value1").join();
trie.putAsync(key2, "value2").join();
final Bytes hash = trie.rootHash();
trie.removeAsync(Bytes.of(1, 4)).join();
assertEquals(hash, trie.rootHash());
}
@Test
void testHashChangesWhenValueChanged() throws Exception {
final Bytes key1 = Bytes.of(1, 5, 8, 9);
final Bytes key2 = Bytes.of(1, 6, 1, 2);
final Bytes key3 = Bytes.of(1, 6, 1, 3);
trie.putAsync(key1, "value1").join();
final Bytes32 hash1 = trie.rootHash();
trie.putAsync(key2, "value2").join();
trie.putAsync(key3, "value3").join();
final Bytes32 hash2 = trie.rootHash();
assertNotEquals(hash2, hash1);
trie.putAsync(key1, "value4").join();
final Bytes32 hash3 = trie.rootHash();
assertNotEquals(hash3, hash1);
assertNotEquals(hash3, hash2);
trie.clearCache();
trie.putAsync(key1, "value1").join();
assertEquals(hash2, trie.rootHash());
trie.removeAsync(key2).join();
trie.removeAsync(key3).join();
assertEquals(hash1, trie.rootHash());
}
@Test
void testCanReloadTrieFromHash() throws Exception {
final Bytes key1 = Bytes.of(1, 5, 8, 9);
final Bytes key2 = Bytes.of(1, 6, 1, 2);
final Bytes key3 = Bytes.of(1, 6, 1, 3);
trie.putAsync(key1, "value1").join();
final Bytes32 hash1 = trie.rootHash();
trie.putAsync(key2, "value2").join();
trie.putAsync(key3, "value3").join();
final Bytes32 hash2 = trie.rootHash();
assertNotEquals(hash2, hash1);
trie.putAsync(key1, "value4").join();
final Bytes32 hash3 = trie.rootHash();
assertNotEquals(hash3, hash1);
assertNotEquals(hash3, hash2);
assertEquals("value4", trie.getAsync(key1).get());
trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage, hash1);
assertEquals("value1", trie.getAsync(key1).get());
assertNull(trie.getAsync(key2).get());
assertNull(trie.getAsync(key3).get());
trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage, hash2);
assertEquals("value1", trie.getAsync(key1).get());
assertEquals("value2", trie.getAsync(key2).get());
assertEquals("value3", trie.getAsync(key3).get());
trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage, hash3);
assertEquals("value4", trie.getAsync(key1).get());
assertEquals("value2", trie.getAsync(key2).get());
assertEquals("value3", trie.getAsync(key3).get());
}
}