Merge pull request #13 from nicksavers/master

Fix Trie to generate the expected rootHash
This commit is contained in:
romanman 2014-05-26 08:28:45 +03:00
commit 7645262d55
8 changed files with 403 additions and 241 deletions

View File

@ -1,6 +1,5 @@
package org.ethereum.gui;
import org.ethereum.config.SystemProperties;
import org.ethereum.core.Transaction;
import org.ethereum.manager.MainData;
import org.ethereum.net.client.ClientPeer;
@ -9,26 +8,19 @@ import org.ethereum.net.submit.TransactionTask;
import org.ethereum.wallet.AddressState;
import org.spongycastle.util.BigIntegers;
import org.spongycastle.util.encoders.Hex;
import samples.Main;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.math.BigInteger;
import java.net.URL;
import java.util.*;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import static org.ethereum.config.SystemProperties.CONFIG;
@ -44,11 +36,11 @@ class PayOutDialog extends JDialog {
AddressState addressState = null;
JLabel statusMsg = null;
public PayOutDialog(Frame parent, final AddressState addressState) {
super(parent, "Payout details: ", false);
dialog = this;
public PayOutDialog(Frame parent, final AddressState addressState) {
super(parent, "Payout details: ", false);
dialog = this;
this.addressState = addressState;
this.addressState = addressState;
final JTextField receiverInput = new JTextField(18);
GUIUtils.addStyle(receiverInput, "Pay to:");
@ -86,14 +78,12 @@ class PayOutDialog extends JDialog {
rejectLabel.setBounds(260, 145, 45, 45);
this.getContentPane().add(rejectLabel);
rejectLabel.setVisible(true);
rejectLabel.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
dialog.dispose();
}}
);
rejectLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
dialog.dispose();
}
});
URL approveIconURL = ClassLoader.getSystemResource("buttons/approve.png");
ImageIcon approveIcon = new ImageIcon(approveIconURL);
@ -105,53 +95,45 @@ class PayOutDialog extends JDialog {
this.getContentPane().add(approveLabel);
approveLabel.setVisible(true);
approveLabel.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
approveLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
BigInteger fee = new BigInteger(feeInput.getText());
BigInteger value = new BigInteger(amountInput.getText());
byte[] address = Hex.decode(receiverInput.getText());
BigInteger fee = new BigInteger(feeInput.getText());
BigInteger value = new BigInteger(amountInput.getText());
byte[] address = Hex.decode( receiverInput.getText());
// Client
ClientPeer peer = MainData.instance.getActivePeer();
if (peer == null) {
dialog.alertStatusMsg("Not connected to any peer");
return;
}
// Client
ClientPeer peer = MainData.instance.getActivePeer();
byte[] senderPrivKey = addressState.getEcKey().getPrivKeyBytes();
if (peer == null){
dialog.alertStatusMsg("Not connected to any peer");
return;
}
byte[] nonce = addressState.getNonce() == BigInteger.ZERO ? null : addressState.getNonce().toByteArray();
byte[] senderPrivKey = addressState.getEcKey().getPrivKeyBytes();
// todo: in the future it should be retrieved from the block
byte[] gasPrice = new BigInteger("10000000000000").toByteArray();
byte[] nonce = addressState.getNonce() == BigInteger.ZERO ?
null : addressState.getNonce().toByteArray();
Transaction tx = new Transaction(nonce, gasPrice, BigIntegers
.asUnsignedByteArray(fee), address, BigIntegers
.asUnsignedByteArray(value), null);
// todo: in the future it should be retrieved from the block
byte[] gasPrice = new BigInteger("10000000000000").toByteArray();
try {
tx.sign(senderPrivKey);
} catch (Exception e1) {
Transaction tx = new Transaction(nonce, gasPrice,
BigIntegers.asUnsignedByteArray(fee),
address,
BigIntegers.asUnsignedByteArray(value), null);
try {
tx.sign(senderPrivKey);
} catch (Exception e1) {
dialog.alertStatusMsg("Failed to sign the transaction");
return;
}
// SwingWorker
DialogWorker worker = new DialogWorker(tx);
worker.execute();
}
}
);
dialog.alertStatusMsg("Failed to sign the transaction");
return;
}
// SwingWorker
DialogWorker worker = new DialogWorker(tx);
worker.execute();
}
});
feeInput.setText("1000");
amountInput.setText("0");
@ -185,7 +167,6 @@ class PayOutDialog extends JDialog {
this.setSize(500, 255);
this.setVisible(true);
return rootPane;
}
@ -217,8 +198,7 @@ class PayOutDialog extends JDialog {
});
}
class DialogWorker extends SwingWorker{
class DialogWorker extends SwingWorker {
Transaction tx;
@ -252,17 +232,12 @@ class PayOutDialog extends JDialog {
MainData.instance.getWallet().applyTransaction(tx);
return null;
}
}
}
public static void main(String args[]) {
AddressState as = new AddressState();
PayOutDialog pod = new PayOutDialog(null, as);
pod.setVisible(true);
}
}

View File

@ -12,7 +12,6 @@ import org.ethereum.crypto.HashUtil;
import org.ethereum.util.Value;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.Options;
import org.spongycastle.util.encoders.Hex;
public class Cache {
@ -40,8 +39,8 @@ public class Cache {
Value value = new Value(o);
byte[] enc = value.encode();
if (enc.length >= 32) {
byte[] sha = Hex.encode(HashUtil.sha3(enc));
this.nodes.put(sha, new Node(sha, value, true));
byte[] sha = HashUtil.sha3(enc);
this.nodes.put(sha, new Node(value, true));
this.isDirty = true;
return sha;
}
@ -57,7 +56,7 @@ public class Cache {
byte[] data = this.db.get(key);
Value value = new Value(data);
// Create caching node
this.nodes.put(key, new Node(key, value, false));
this.nodes.put(key, new Node(value, false));
return value;
}

View File

@ -2,12 +2,13 @@ package org.ethereum.trie;
import org.ethereum.util.Value;
/**
* A Node in a Merkle Patricia Tree is one of the following:
*
* - NULL (represented as the empty string)
* - A two-item array [ key, value ]
* - A 17-item array [ v0 ... v15, vt ]
* - A two-item array [ key, value ] (1 key for 2-item array)
* - A 17-item array [ v0 ... v15, vt ] (16 keys for 17-item array)
*
* The idea is that in the event that there is a long path of nodes
* each with only one element, we shortcut the descent by setting up
@ -21,41 +22,40 @@ import org.ethereum.util.Value;
* N N
* / \ / \
* L L L L
*
*
* *
* Also, we add another conceptual change: internal nodes can no longer
* have values, only leaves with no children of their own can; however,
* since to be fully generic we want the key/value store to be able to
* store keys like 'dog' and 'doge' at the same time, we simply add
* a terminator symbol (16) to the alphabet so there is never a value
* "en-route" to another value. Where a node is referenced inside a node,
* what is included is H(rlp.encode(x)) where H(x) = sha3(x) if len(x) >= 32 else x
* and rlp.encode is the RLP encoding function.
* "en-route" to another value.
*
* Where a node is referenced inside a node, what is included is:
*
* H(rlp.encode(x)) where H(x) = sha3(x) if len(x) >= 32 else x
*
* Note that when updating a trie, you will need to store the key/value pair (sha3(x), x)
* in a persistent lookup table when you create a node with length >= 32,
* but if the node is shorter than that then you do not need to store anything
* when length < 32 for the obvious reason that the function f(x) = x is reversible.
*
*/
public class Node {
private byte[] key; // 1 key for 2-item array, 16 keys for 17-item array
/* RLP encoded value of the Trie-node */
private Value value;
private boolean dirty;
public Node(byte[] key, Value val) {
this(key, val, false);
public Node(Value val) {
this(val, false);
}
public Node(byte[] key, Value val, boolean dirty) {
this.key = key;
public Node(Value val, boolean dirty) {
this.value = val;
this.dirty = dirty;
}
public Node copy() {
return new Node(this.key.clone(), this.value, this.dirty);
return new Node(this.value, this.dirty);
}
public boolean isDirty() {
@ -66,15 +66,7 @@ public class Node {
this.dirty = ditry;
}
public byte[] getKey() {
return key;
}
public Value getValue() {
return value;
}
public Object[] getItems() {
return new Object[] { key, value };
}
}

View File

@ -2,26 +2,37 @@ package org.ethereum.trie;
import static java.util.Arrays.copyOfRange;
import static org.spongycastle.util.Arrays.concatenate;
import static org.ethereum.util.CompactEncoder.*;
import java.util.Arrays;
import org.ethereum.util.CompactEncoder;
import org.ethereum.crypto.HashUtil;
import org.ethereum.util.Value;
import org.iq80.leveldb.DB;
import org.spongycastle.util.encoders.Hex;
import com.cedarsoftware.util.DeepEquals;
/**
* The modified Merkle Patricia tree (trie) provides a persistent data structure
* to map between arbitrary-length binary data (byte arrays). It is defined in terms of
* a mutable data structure to map between 256-bit binary fragments and arbitrary-length
* binary data, typically implemented as a database. The core of the trie, and its sole
* requirement in terms of the protocol specification is to provide a single value that
* identifies a given set of key-value pairs, which may either a 32 byte sequence or
* the empty byte sequence. It is left as an implementation consideration to store and
* maintain the structure of the trie in a manner the allows effective and efficient
* realisation of the protocol.
*
* The trie implements a caching mechanism and will use cached values if they are present.
* If a node is not present in the cache it will try to fetch it from the database and
* store the cached value.
*
* Please note that the data isn't persisted unless `sync` is explicitly called.
*/
public class Trie {
private static byte PAIR_SIZE = 2;
private static byte LIST_SIZE = 17;
// A (modified) Radix Trie implementation. The Trie implements
// a caching mechanism and will used cached values if they are
// present. If a node is not present in the cache it will try to
// fetch it from the database and store the cached value.
// Please note that the data isn't persisted unless `Sync` is
// explicitly called.
private Object prevRoot;
private Object root;
private Cache cache;
@ -73,7 +84,7 @@ public class Trie {
public void update(String key, String value) {
if (key == null)
throw new NullPointerException("Key should not be blank");
byte[] k = CompactEncoder.hexDecode(key.getBytes());
byte[] k = binToNibbles(key.getBytes());
this.root = this.insertOrDelete(this.root, k, value.getBytes());
}
@ -84,7 +95,7 @@ public class Trie {
* @return value
*/
public String get(String key) {
byte[] k = CompactEncoder.hexDecode(key.getBytes());
byte[] k = binToNibbles(key.getBytes());
Value c = new Value( this.get(this.root, k) );
return c.asString();
}
@ -110,11 +121,10 @@ public class Trie {
}
Value currentNode = this.getNode(node);
int length = currentNode.length();
if (length == PAIR_SIZE) {
if (currentNode.length() == PAIR_SIZE) {
// Decode the key
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes());
byte[] k = unpackToNibbles(currentNode.get(0).asBytes());
Object v = currentNode.get(1).asObj();
if (key.length >= k.length && Arrays.equals(k, copyOfRange(key, 0, k.length))) {
@ -122,12 +132,9 @@ public class Trie {
} else {
return "";
}
} else if (length == LIST_SIZE) {
return this.get(currentNode.get(key[0]).asObj(), copyOfRange(key, 1, key.length));
} else {
return this.get(currentNode.get(key[0]).asObj(), copyOfRange(key, 1, key.length));
}
// It shouldn't come this far
throw new RuntimeException("Unexpected Node length: " + length);
}
private Object insertOrDelete(Object node, byte[] key, byte[] value) {
@ -149,7 +156,7 @@ public class Trie {
}
if (isEmptyNode(node)) {
Object[] newNode = new Object[] { CompactEncoder.encode(key), value };
Object[] newNode = new Object[] { packNibbles(key), value };
return this.put(newNode);
}
@ -158,13 +165,12 @@ public class Trie {
// Check for "special" 2 slice type node
if (currentNode.length() == PAIR_SIZE) {
// Decode the key
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes());
byte[] k = unpackToNibbles(currentNode.get(0).asBytes());
Object v = currentNode.get(1).asObj();
// Matching key pair (ie. there's already an object with this key)
if (Arrays.equals(k, key)) {
Object[] newNode = new Object[] {CompactEncoder.encode(key), value};
Object[] newNode = new Object[] {packNibbles(key), value};
return this.put(newNode);
}
@ -172,9 +178,10 @@ public class Trie {
int matchingLength = matchingNibbleLength(key, k);
if (matchingLength == k.length) {
// Insert the hash, creating a new node
newHash = this.insert(v, copyOfRange(key, matchingLength, key.length), value);
} else {
// Expand the 2 length slice to a 17 length slice
byte[] remainingKeypart = copyOfRange(key, matchingLength, key.length);
newHash = this.insert(v, remainingKeypart, value);
} else { // Expand the 2 length slice to a 17 length slice
// Create two nodes to put into the new 17 length node
Object oldNode = this.insert("", copyOfRange(k, matchingLength+1, k.length), v);
Object newNode = this.insert("", copyOfRange(key, matchingLength+1, key.length), value);
// Create an expanded slice
@ -189,7 +196,7 @@ public class Trie {
// End of the chain, return
return newHash;
} else {
Object[] newNode = new Object[] {CompactEncoder.encode(copyOfRange(key, 0, matchingLength)), newHash};
Object[] newNode = new Object[] { packNibbles(copyOfRange(key, 0, matchingLength)), newHash};
return this.put(newNode);
}
} else {
@ -213,7 +220,7 @@ public class Trie {
// Check for "special" 2 slice type node
if (currentNode.length() == PAIR_SIZE) {
// Decode the key
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes());
byte[] k = unpackToNibbles(currentNode.get(0).asBytes());
Object v = currentNode.get(1).asObj();
// Matching key pair (ie. there's already an object with this key)
@ -225,8 +232,8 @@ public class Trie {
Object newNode;
if (child.length() == PAIR_SIZE) {
byte[] newKey = concatenate(k, CompactEncoder.decode(child.get(0).asBytes()));
newNode = new Object[] {CompactEncoder.encode(newKey), child.get(1).asObj()};
byte[] newKey = concatenate(k, unpackToNibbles(child.get(0).asBytes()));
newNode = new Object[] {packNibbles(newKey), child.get(1).asObj()};
} else {
newNode = new Object[] {currentNode.get(0).asString(), hash};
}
@ -240,6 +247,7 @@ public class Trie {
// Replace the first nibble in the key
itemList[key[0]] = this.delete(itemList[key[0]], copyOfRange(key, 1, key.length));
byte amount = -1;
for (byte i = 0; i < LIST_SIZE; i++) {
if (itemList[i] != "") {
@ -253,14 +261,14 @@ public class Trie {
Object[] newNode = null;
if (amount == 16) {
newNode = new Object[] { CompactEncoder.encode(new byte[] {16} ), itemList[amount]};
newNode = new Object[] { packNibbles(new byte[] {16} ), itemList[amount]};
} else if (amount >= 0) {
Value child = this.getNode(itemList[amount]);
if (child.length() == PAIR_SIZE) {
key = concatenate(new byte[]{amount}, CompactEncoder.decode(child.get(0).asBytes()));
newNode = new Object[] {CompactEncoder.encode(key), child.get(1).asObj()};
key = concatenate(new byte[]{amount}, unpackToNibbles(child.get(0).asBytes()));
newNode = new Object[] {packNibbles(key), child.get(1).asObj()};
} else if (child.length() == LIST_SIZE) {
newNode = new Object[] { CompactEncoder.encode(new byte[]{amount}), itemList[amount]};
newNode = new Object[] { packNibbles(new byte[]{amount}), itemList[amount]};
}
} else {
newNode = itemList;
@ -276,30 +284,23 @@ public class Trie {
* @param node
* @return
*/
private Value getNode(Object node) {
private Value getNode(Object node) {
Value n = new Value(node);
if (!n.get(0).isNull()) {
return n;
}
String str = n.asString();
if (str.length() == 0) {
byte[] str = n.asBytes();
if (str.length == 0) {
return n;
} else if (str.length() < 32) {
return new Value(str.getBytes());
} else if (str.length < 32) {
return new Value(str);
}
return this.cache.get(n.asBytes());
return this.cache.get(str);
}
private Object put(Object node) {
/* TODO?
c := Conv(t.Root)
fmt.Println(c.Type(), c.Length())
if c.Type() == reflect.String && c.AsString() == "" {
return enc
}
*/
return this.cache.put(node);
}
@ -319,9 +320,9 @@ public class Trie {
return itemList;
}
// Simple compare function which creates a rlp value out of the evaluated objects
// Simple compare function which compared the tries based on their stateRoot
public boolean cmp(Trie trie) {
return DeepEquals.deepEquals(this.root, trie.getRoot());
return this.getRootHash().equals(trie.getRootHash());
}
// Save the cached value to the database.
@ -358,6 +359,7 @@ public class Trie {
return i;
}
// Created an array of empty elements of requred length
private Object[] emptyStringSlice(int l) {
Object[] slice = new Object[l];
for (int i = 0; i < l; i++) {
@ -365,4 +367,20 @@ public class Trie {
}
return slice;
}
public String getRootHash() {
Object root = this.getRoot();
if (root == null
|| (root instanceof byte[] && ((byte[]) root).length == 0)
|| (root instanceof String && "".equals((String) root))) {
return "";
} else if (root instanceof byte[]) {
return Hex.toHexString((byte[])this.getRoot());
} else {
Value rootValue = new Value(this.getRoot());
byte[] val = rootValue.encode();
byte[] key = HashUtil.sha3(val);
return Hex.toHexString(key);
}
}
}

View File

@ -2,8 +2,8 @@ package org.ethereum.trie;
import java.util.List;
import org.ethereum.util.CompactEncoder;
import org.ethereum.util.Value;
import static org.ethereum.util.CompactEncoder.unpackToNibbles;
public class TrieIterator {
@ -22,7 +22,7 @@ public class TrieIterator {
// XXX Note to self, IsSlice == inline node. Str == sha3 to node
private void workNode(Value currentNode) {
if (currentNode.length() == 2) {
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes());
byte[] k = unpackToNibbles(currentNode.get(0).asBytes());
if (currentNode.get(1).asString() == "") {
this.workNode(currentNode.get(1));

View File

@ -9,8 +9,7 @@ import static org.ethereum.util.ByteUtil.appendByte;
import static org.spongycastle.util.Arrays.concatenate;
import static org.spongycastle.util.encoders.Hex.toHexString;
/**
*
/**
* Compact encoding of hex sequence with optional terminator
*
* The traditional compact way of encoding a hex string is to convert it into binary
@ -49,36 +48,43 @@ public class CompactEncoder {
private final static byte TERMINATOR = 16;
private final static String hexBase = "0123456789abcdef";
public static byte[] encode(byte[] hexSlice) {
/**
* Pack nibbles to binary
*
* @param nibbles sequence. may have a terminator
* @return hex-encoded byte array
*/
public static byte[] packNibbles(byte[] nibbles) {
int terminator = 0;
if (hexSlice[hexSlice.length-1] == TERMINATOR) {
if (nibbles[nibbles.length-1] == TERMINATOR) {
terminator = 1;
hexSlice = copyOf(hexSlice, hexSlice.length-1);
nibbles = copyOf(nibbles, nibbles.length-1);
}
int oddlen = hexSlice.length % 2;
int oddlen = nibbles.length % 2;
int flag = 2*terminator + oddlen;
if (oddlen != 0) {
byte[] flags = new byte[] { (byte) flag};
hexSlice = concatenate(flags, hexSlice);
nibbles = concatenate(flags, nibbles);
} else {
byte[] flags = new byte[] { (byte) flag, 0};
hexSlice = concatenate(flags, hexSlice);
nibbles = concatenate(flags, nibbles);
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (int i = 0; i < hexSlice.length; i += 2) {
buffer.write( 16*hexSlice[i] + hexSlice[i+1] );
for (int i = 0; i < nibbles.length; i += 2) {
buffer.write( 16*nibbles[i] + nibbles[i+1] );
}
return buffer.toByteArray();
}
/**
* Strips hex slices
* Unpack a binary string to its nibbles equivalent
*
* @param string of binary data
* @return array of nibbles in byte-format
*/
public static byte[] decode(byte[] str) {
byte[] base = hexDecode(str);
public static byte[] unpackToNibbles(byte[] str) {
byte[] base = binToNibbles(str);
base = copyOf(base, base.length-1);
if (base[0] >= 2) {
base = appendByte(base, TERMINATOR);
@ -92,10 +98,11 @@ public class CompactEncoder {
}
/**
* Transforms a binary array to hexadecimal format,
* returns array with each individual nibble adding a terminator at the end
*/
public static byte[] hexDecode(byte[] str) {
* Transforms a binary array to hexadecimal format + terminator
*
* @return array with each individual nibble adding a terminator at the end
*/
public static byte[] binToNibbles(byte[] str) {
byte[] hexSlice = new byte[0];
String hexEncoded = toHexString(str);
for (char value : hexEncoded.toCharArray()) {

View File

@ -5,11 +5,10 @@ import static org.junit.Assert.*;
import org.ethereum.trie.Trie;
import org.junit.Test;
import com.cedarsoftware.util.DeepEquals;
public class TrieTest {
private static String LONG_STRING = "1234567890abcdefghijklmnopqrstuvwxxzABCEFGHIJKLMNOPQRSTUVWXYZ";
private static String ROOT_HASH_EMPTY = "";
private static String c = "c";
private static String ca = "ca";
@ -33,215 +32,305 @@ public class TrieTest {
@Test
public void testEmptyKey() {
Trie trie = new Trie(mockDb);
trie.update("", dog);
String result = trie.get("");
assertEquals(dog, result);
assertEquals(dog, trie.get(""));
}
@Test
public void testInsertShortString() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
String result = trie.get(cat);
assertEquals(dog, result);
assertEquals(dog, trie.get(cat));
}
@Test
public void testInsertLongString() {
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
String result = trie.get(cat);
assertEquals(LONG_STRING, result);
assertEquals(LONG_STRING, trie.get(cat));
}
@Test
public void testInsertMultipleItems1() {
Trie trie = new Trie(mockDb);
trie.update(ca, dude);
assertEquals(dude, trie.get(ca));
trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(dog, test);
assertEquals(test, trie.get(dog));
trie.update(doge, LONG_STRING);
assertEquals(LONG_STRING, trie.get(doge));
trie.update(test, LONG_STRING);
String result1 = trie.get(ca);
String result2 = trie.get(cat);
String result3 = trie.get(dog);
String result4 = trie.get(doge);
String result5 = trie.get(test);
assertEquals(dude, result1);
assertEquals(dog, result2);
assertEquals(test, result3);
assertEquals(LONG_STRING, result4);
assertEquals(LONG_STRING, result5);
assertEquals(LONG_STRING, trie.get(test));
// Test if everything is still there
assertEquals(dude, trie.get(ca));
assertEquals(dog, trie.get(cat));
assertEquals(test, trie.get(dog));
assertEquals(LONG_STRING, trie.get(doge));
assertEquals(LONG_STRING, trie.get(test));
}
@Test
public void testInsertMultipleItems2() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(ca, dude);
assertEquals(dude, trie.get(ca));
trie.update(doge, LONG_STRING);
assertEquals(LONG_STRING, trie.get(doge));
trie.update(dog, test);
assertEquals(test, trie.get(dog));
trie.update(test, LONG_STRING);
String result1 = trie.get(cat);
String result2 = trie.get(ca);
String result3 = trie.get(doge);
String result4 = trie.get(dog);
String result5 = trie.get(test);
assertEquals(dog, result1);
assertEquals(dude, result2);
assertEquals(LONG_STRING, result3);
assertEquals(test, result4);
assertEquals(LONG_STRING, result5);
assertEquals(LONG_STRING, trie.get(test));
// Test if everything is still there
assertEquals(dog, trie.get(cat));
assertEquals(dude, trie.get(ca));
assertEquals(LONG_STRING, trie.get(doge));
assertEquals(test, trie.get(dog));
assertEquals(LONG_STRING, trie.get(test));
}
@Test
public void testUpdateShortToShortString() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(cat, dog+"1");
String result = trie.get(cat);
assertEquals(dog+"1", result);
assertEquals(dog+"1", trie.get(cat));
}
@Test
public void testUpdateLongToLongString() {
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, trie.get(cat));
trie.update(cat, LONG_STRING+"1");
String result = trie.get(cat);
assertEquals(LONG_STRING+"1", result);
assertEquals(LONG_STRING+"1", trie.get(cat));
}
@Test
public void testUpdateShortToLongString() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(cat, LONG_STRING+"1");
String result = trie.get(cat);
assertEquals(LONG_STRING+"1", result);
assertEquals(LONG_STRING+"1", trie.get(cat));
}
@Test
public void testUpdateLongToShortString() {
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, trie.get(cat));
trie.update(cat, dog+"1");
String result = trie.get(cat);
assertEquals(dog+"1", result);
assertEquals(dog+"1", trie.get(cat));
}
@Test
public void testDeleteShortString1() {
String ROOT_HASH_BEFORE = "a9539c810cc2e8fa20785bdd78ec36cc1dab4b41f0d531e80a5e5fd25c3037ee";
String ROOT_HASH_AFTER = "fc5120b4a711bca1f5bb54769525b11b3fb9a8d6ac0b8bf08cbb248770521758";
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
Object expected = trie.getRoot();
assertEquals(dog, trie.get(cat));
trie.update(ca, dude);
assertEquals(dude, trie.get(ca));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(ca);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
assertEquals("", trie.get(ca));
assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
}
@Test
public void testDeleteShortString2() {
String ROOT_HASH_BEFORE = "a9539c810cc2e8fa20785bdd78ec36cc1dab4b41f0d531e80a5e5fd25c3037ee";
String ROOT_HASH_AFTER = "b25e1b5be78dbadf6c4e817c6d170bbb47e9916f8f6cc4607c5f3819ce98497b";
Trie trie = new Trie(mockDb);
trie.update(ca, dude);
Object expected = trie.getRoot();
assertEquals(dude, trie.get(ca));
trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(cat);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
assertEquals("", trie.get(cat));
assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
}
@Test
public void testDeleteShortString3() {
String ROOT_HASH_BEFORE = "778ab82a7e8236ea2ff7bb9cfa46688e7241c1fd445bf2941416881a6ee192eb";
String ROOT_HASH_AFTER = "05875807b8f3e735188d2479add82f96dee4db5aff00dc63f07a7e27d0deab65";
Trie trie = new Trie(mockDb);
trie.update(cat, dude);
Object expected = trie.getRoot();
assertEquals(dude, trie.get(cat));
trie.update(dog, test);
assertEquals(test, trie.get(dog));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(dog);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
assertEquals("", trie.get(dog));
assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
}
@Test
public void testDeleteLongString1() {
String ROOT_HASH_BEFORE = "318961a1c8f3724286e8e80d312352f01450bc4892c165cc7614e1c2e5a0012a";
String ROOT_HASH_AFTER = "63356ecf33b083e244122fca7a9b128cc7620d438d5d62e4f8b5168f1fb0527b";
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
Object expected = trie.getRoot();
assertEquals(LONG_STRING, trie.get(cat));
trie.update(dog, LONG_STRING);
assertEquals(LONG_STRING, trie.get(dog));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(dog);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
assertEquals("", trie.get(dog));
assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
}
@Test
public void testDeleteLongString2() {
String ROOT_HASH_BEFORE = "e020de34ca26f8d373ff2c0a8ac3a4cb9032bfa7a194c68330b7ac3584a1d388";
String ROOT_HASH_AFTER = "334511f0c4897677b782d13a6fa1e58e18de6b24879d57ced430bad5ac831cb2";
Trie trie = new Trie(mockDb);
trie.update(ca, LONG_STRING);
Object expected = trie.getRoot();
assertEquals(LONG_STRING, trie.get(ca));
trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, trie.get(cat));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(cat);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
assertEquals("", trie.get(cat));
assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
}
@Test
public void testDeleteLongString3() {
String ROOT_HASH_BEFORE = "e020de34ca26f8d373ff2c0a8ac3a4cb9032bfa7a194c68330b7ac3584a1d388";
String ROOT_HASH_AFTER = "63356ecf33b083e244122fca7a9b128cc7620d438d5d62e4f8b5168f1fb0527b";
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
Object expected = trie.getRoot();
assertEquals(LONG_STRING, trie.get(cat));
trie.update(ca, LONG_STRING);
assertEquals(LONG_STRING, trie.get(ca));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(ca);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
assertEquals("", trie.get(ca));
assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
}
@Test
public void testDeleteMultipleItems1() {
String ROOT_HASH_BEFORE = "3a784eddf1936515f0313b073f99e3bd65c38689021d24855f62a9601ea41717";
String ROOT_HASH_AFTER1 = "60a2e75cfa153c4af2783bd6cb48fd6bed84c6381bc2c8f02792c046b46c0653";
String ROOT_HASH_AFTER2 = "a84739b4762ddf15e3acc4e6957e5ab2bbfaaef00fe9d436a7369c6f058ec90d";
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(ca, dude);
assertEquals(dude, trie.get(ca));
trie.update(doge, LONG_STRING);
Object expected = trie.getRoot();
assertEquals(LONG_STRING, trie.get(doge));
trie.update(dog, test);
assertEquals(test, trie.get(dog));
trie.update(test, LONG_STRING);
assertEquals(LONG_STRING, trie.get(test));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(dog);
assertEquals("", trie.get(dog));
assertEquals(ROOT_HASH_AFTER1, trie.getRootHash());
trie.delete(test);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
assertEquals("", trie.get(test));
assertEquals(ROOT_HASH_AFTER2, trie.getRootHash());
}
@Test
public void testDeleteMultipleItems2() {
String ROOT_HASH_BEFORE = "cf1ed2b6c4b6558f70ef0ecf76bfbee96af785cb5d5e7bfc37f9804ad8d0fb56";
String ROOT_HASH_AFTER1 = "f586af4a476ba853fca8cea1fbde27cd17d537d18f64269fe09b02aa7fe55a9e";
String ROOT_HASH_AFTER2 = "c59fdc16a80b11cc2f7a8b107bb0c954c0d8059e49c760ec3660eea64053ac91";
Trie trie = new Trie(mockDb);
trie.update(c, LONG_STRING);
Object expected = trie.getRoot();
assertEquals(LONG_STRING, trie.get(c));
trie.update(ca, LONG_STRING);
assertEquals(LONG_STRING, trie.get(ca));
trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, trie.get(cat));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(ca);
assertEquals("", trie.get(ca));
assertEquals(ROOT_HASH_AFTER1, trie.getRootHash());
trie.delete(cat);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
assertEquals("", trie.get(cat));
assertEquals(ROOT_HASH_AFTER2, trie.getRootHash());
}
@Test
public void testDeleteAll() {
String ROOT_HASH_BEFORE = "a84739b4762ddf15e3acc4e6957e5ab2bbfaaef00fe9d436a7369c6f058ec90d";
Trie trie = new Trie(mockDb);
Object expected = trie.getRoot();
assertEquals(ROOT_HASH_EMPTY, trie.getRootHash());
trie.update(ca, dude);
trie.update(cat, dog);
trie.update(doge, LONG_STRING);
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(ca);
trie.delete(cat);
trie.delete(doge);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
assertEquals(ROOT_HASH_EMPTY, trie.getRootHash());
}
@Test
public void testTrieCmp() {
Trie trie1 = new Trie(mockDb);
@ -250,10 +339,12 @@ public class TrieTest {
trie1.update(doge, LONG_STRING);
trie2.update(doge, LONG_STRING);
assertTrue("Expected tries to be equal", trie1.cmp(trie2));
assertEquals(trie1.getRootHash(), trie2.getRootHash());
trie1.update(dog, LONG_STRING);
trie2.update(cat, LONG_STRING);
assertFalse("Expected tries not to be equal", trie1.cmp(trie2));
assertNotEquals(trie1.getRootHash(), trie2.getRootHash());
}
@Test
@ -302,4 +393,84 @@ public class TrieTest {
public void testTrieUndo() {
fail("To be implemented");
}
// Using tests from: https://github.com/ethereum/tests/blob/master/trietest.json
@Test
public void testSingleItem() {
Trie trie = new Trie(mockDb);
trie.update("A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
assertEquals("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab", trie.getRootHash());
}
@Test
public void testDogs() {
Trie trie = new Trie(mockDb);
trie.update("doe", "reindeer");
assertEquals("11a0327cfcc5b7689b6b6d727e1f5f8846c1137caaa9fc871ba31b7cce1b703e", trie.getRootHash());
trie.update("dog", "puppy");
assertEquals("05ae693aac2107336a79309e0c60b24a7aac6aa3edecaef593921500d33c63c4", trie.getRootHash());
trie.update("dogglesworth", "cat");
assertEquals("8aad789dff2f538bca5d8ea56e8abe10f4c7ba3a5dea95fea4cd6e7c3a1168d3", trie.getRootHash());
}
@Test
public void testPuppy() {
Trie trie = new Trie(mockDb);
trie.update("do", "verb");
trie.update("horse", "stallion");
trie.update("doge", "coin");
trie.update("dog", "puppy");
assertEquals("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84", trie.getRootHash());
}
@Test
public void testEmptyValues() {
Trie trie = new Trie(mockDb);
trie.update("do", "verb");
trie.update("ether", "wookiedoo");
trie.update("horse", "stallion");
trie.update("shaman", "horse");
trie.update("doge", "coin");
trie.update("ether", "");
trie.update("dog", "puppy");
trie.update("shaman", "");
assertEquals("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84", trie.getRootHash());
}
@Test
public void testFoo() {
Trie trie = new Trie(mockDb);
trie.update("foo", "bar");
trie.update("food", "bat");
trie.update("food", "bass");
assertEquals("17beaa1648bafa633cda809c90c04af50fc8aed3cb40d16efbddee6fdf63c4c3", trie.getRootHash());
}
@Test
public void testSmallValues() {
Trie trie = new Trie(mockDb);
trie.update("be", "e");
trie.update("dog", "puppy");
trie.update("bed", "d");
assertEquals("3f67c7a47520f79faa29255d2d3c084a7a6df0453116ed7232ff10277a8be68b", trie.getRootHash());
}
@Test
public void testTesty() {
Trie trie = new Trie(mockDb);
trie.update("test", "test");
assertEquals("85d106d4edff3b7a4889e91251d0a87d7c17a1dda648ebdba8c6060825be23b8", trie.getRootHash());
trie.update("te", "testy");
assertEquals("8452568af70d8d140f58d941338542f645fcca50094b20f3c3d8c3df49337928", trie.getRootHash());
}
}

View File

@ -13,62 +13,62 @@ public class CompactEncoderTest {
public void testCompactEncodeOddCompact() {
byte[] test = new byte[] { 1, 2, 3, 4, 5 };
byte[] expectedData = new byte[] { 0x11, 0x23, 0x45 };
assertArrayEquals("odd compact encode fail", expectedData, CompactEncoder.encode(test));
assertArrayEquals("odd compact encode fail", expectedData, CompactEncoder.packNibbles(test));
}
@Test
public void testCompactEncodeEvenCompact() {
byte[] test = new byte[] { 0, 1, 2, 3, 4, 5 };
byte[] expectedData = new byte[] { 0x00, 0x01, 0x23, 0x45 };
assertArrayEquals("even compact encode fail", expectedData, CompactEncoder.encode(test));
assertArrayEquals("even compact encode fail", expectedData, CompactEncoder.packNibbles(test));
}
@Test
public void testCompactEncodeEvenTerminated() {
byte[] test = new byte[] { 0, 15, 1, 12, 11, 8, T };
byte[] expectedData = new byte[] { 0x20, 0x0f, 0x1c, (byte) 0xb8 };
assertArrayEquals("even terminated compact encode fail", expectedData, CompactEncoder.encode(test));
assertArrayEquals("even terminated compact encode fail", expectedData, CompactEncoder.packNibbles(test));
}
@Test
public void testCompactEncodeOddTerminated() {
byte[] test = new byte[] { 15, 1, 12, 11, 8, T };
byte[] expectedData = new byte[] { 0x3f, 0x1c, (byte) 0xb8 };
assertArrayEquals("odd terminated compact encode fail", expectedData, CompactEncoder.encode(test));
assertArrayEquals("odd terminated compact encode fail", expectedData, CompactEncoder.packNibbles(test));
}
@Test
public void testCompactDecodeOddCompact() {
byte[] test = new byte[] { 0x11, 0x23, 0x45 };
byte[] expected = new byte[] {1, 2, 3, 4, 5};
assertArrayEquals("odd compact decode fail", expected, CompactEncoder.decode(test));
assertArrayEquals("odd compact decode fail", expected, CompactEncoder.unpackToNibbles(test));
}
@Test
public void testCompactDecodeEvenCompact() {
byte[] test = new byte[] { 0x00, 0x01, 0x23, 0x45 };
byte[] expected = new byte[] {0, 1, 2, 3, 4, 5};
assertArrayEquals("even compact decode fail", expected, CompactEncoder.decode(test));
assertArrayEquals("even compact decode fail", expected, CompactEncoder.unpackToNibbles(test));
}
@Test
public void testCompactDecodeEvenTerminated() {
byte[] test = new byte[] { 0x20, 0x0f, 0x1c, (byte) 0xb8 };
byte[] expected = new byte[] {0, 15, 1, 12, 11, 8, T};
assertArrayEquals("even terminated compact decode fail", expected, CompactEncoder.decode(test));
assertArrayEquals("even terminated compact decode fail", expected, CompactEncoder.unpackToNibbles(test));
}
@Test
public void testCompactDecodeOddTerminated() {
byte[] test = new byte[] { 0x3f, 0x1c, (byte) 0xb8 };
byte[] expected = new byte[] {15, 1, 12, 11, 8, T};
assertArrayEquals("odd terminated compact decode fail", expected, CompactEncoder.decode(test));
assertArrayEquals("odd terminated compact decode fail", expected, CompactEncoder.unpackToNibbles(test));
}
@Test
public void testCompactHexDecode() {
byte[] test = "stallion".getBytes();
byte[] result = new byte[] { 7, 3, 7, 4, 6, 1, 6, 12, 6, 12, 6, 9, 6, 15, 6, 14, T };
assertArrayEquals(result, CompactEncoder.hexDecode(test));
assertArrayEquals(result, CompactEncoder.binToNibbles(test));
}
}