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

View File

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

View File

@ -2,12 +2,13 @@ package org.ethereum.trie;
import org.ethereum.util.Value; import org.ethereum.util.Value;
/** /**
* A Node in a Merkle Patricia Tree is one of the following: * A Node in a Merkle Patricia Tree is one of the following:
* *
* - NULL (represented as the empty string) * - NULL (represented as the empty string)
* - A two-item array [ key, value ] * - A two-item array [ key, value ] (1 key for 2-item array)
* - A 17-item array [ v0 ... v15, vt ] * - 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 * 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 * each with only one element, we shortcut the descent by setting up
@ -21,41 +22,40 @@ import org.ethereum.util.Value;
* N N * N N
* / \ / \ * / \ / \
* L L L L * L L L L
* * *
*
* Also, we add another conceptual change: internal nodes can no longer * Also, we add another conceptual change: internal nodes can no longer
* have values, only leaves with no children of their own can; however, * 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 * 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 * 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 * 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, * "en-route" to another value.
* 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. * 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) * 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, * 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 * 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. * when length < 32 for the obvious reason that the function f(x) = x is reversible.
*
*/ */
public class Node { 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 Value value;
private boolean dirty; private boolean dirty;
public Node(byte[] key, Value val) { public Node(Value val) {
this(key, val, false); this(val, false);
} }
public Node(byte[] key, Value val, boolean dirty) { public Node(Value val, boolean dirty) {
this.key = key;
this.value = val; this.value = val;
this.dirty = dirty; this.dirty = dirty;
} }
public Node copy() { public Node copy() {
return new Node(this.key.clone(), this.value, this.dirty); return new Node(this.value, this.dirty);
} }
public boolean isDirty() { public boolean isDirty() {
@ -66,15 +66,7 @@ public class Node {
this.dirty = ditry; this.dirty = ditry;
} }
public byte[] getKey() {
return key;
}
public Value getValue() { public Value getValue() {
return value; 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 java.util.Arrays.copyOfRange;
import static org.spongycastle.util.Arrays.concatenate; import static org.spongycastle.util.Arrays.concatenate;
import static org.ethereum.util.CompactEncoder.*;
import java.util.Arrays; import java.util.Arrays;
import org.ethereum.util.CompactEncoder; import org.ethereum.crypto.HashUtil;
import org.ethereum.util.Value; import org.ethereum.util.Value;
import org.iq80.leveldb.DB; 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 { public class Trie {
private static byte PAIR_SIZE = 2; private static byte PAIR_SIZE = 2;
private static byte LIST_SIZE = 17; 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 prevRoot;
private Object root; private Object root;
private Cache cache; private Cache cache;
@ -73,7 +84,7 @@ public class Trie {
public void update(String key, String value) { public void update(String key, String value) {
if (key == null) if (key == null)
throw new NullPointerException("Key should not be blank"); 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()); this.root = this.insertOrDelete(this.root, k, value.getBytes());
} }
@ -84,7 +95,7 @@ public class Trie {
* @return value * @return value
*/ */
public String get(String key) { 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) ); Value c = new Value( this.get(this.root, k) );
return c.asString(); return c.asString();
} }
@ -110,11 +121,10 @@ public class Trie {
} }
Value currentNode = this.getNode(node); Value currentNode = this.getNode(node);
int length = currentNode.length();
if (length == PAIR_SIZE) { if (currentNode.length() == PAIR_SIZE) {
// Decode the key // Decode the key
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes()); byte[] k = unpackToNibbles(currentNode.get(0).asBytes());
Object v = currentNode.get(1).asObj(); Object v = currentNode.get(1).asObj();
if (key.length >= k.length && Arrays.equals(k, copyOfRange(key, 0, k.length))) { if (key.length >= k.length && Arrays.equals(k, copyOfRange(key, 0, k.length))) {
@ -122,12 +132,9 @@ public class Trie {
} else { } else {
return ""; return "";
} }
} else if (length == LIST_SIZE) { } else {
return this.get(currentNode.get(key[0]).asObj(), copyOfRange(key, 1, key.length)); 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) { private Object insertOrDelete(Object node, byte[] key, byte[] value) {
@ -149,7 +156,7 @@ public class Trie {
} }
if (isEmptyNode(node)) { if (isEmptyNode(node)) {
Object[] newNode = new Object[] { CompactEncoder.encode(key), value }; Object[] newNode = new Object[] { packNibbles(key), value };
return this.put(newNode); return this.put(newNode);
} }
@ -158,13 +165,12 @@ public class Trie {
// Check for "special" 2 slice type node // Check for "special" 2 slice type node
if (currentNode.length() == PAIR_SIZE) { if (currentNode.length() == PAIR_SIZE) {
// Decode the key // Decode the key
byte[] k = unpackToNibbles(currentNode.get(0).asBytes());
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes());
Object v = currentNode.get(1).asObj(); Object v = currentNode.get(1).asObj();
// Matching key pair (ie. there's already an object with this key) // Matching key pair (ie. there's already an object with this key)
if (Arrays.equals(k, 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); return this.put(newNode);
} }
@ -172,9 +178,10 @@ public class Trie {
int matchingLength = matchingNibbleLength(key, k); int matchingLength = matchingNibbleLength(key, k);
if (matchingLength == k.length) { if (matchingLength == k.length) {
// Insert the hash, creating a new node // Insert the hash, creating a new node
newHash = this.insert(v, copyOfRange(key, matchingLength, key.length), value); byte[] remainingKeypart = copyOfRange(key, matchingLength, key.length);
} else { newHash = this.insert(v, remainingKeypart, value);
// Expand the 2 length slice to a 17 length slice } 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 oldNode = this.insert("", copyOfRange(k, matchingLength+1, k.length), v);
Object newNode = this.insert("", copyOfRange(key, matchingLength+1, key.length), value); Object newNode = this.insert("", copyOfRange(key, matchingLength+1, key.length), value);
// Create an expanded slice // Create an expanded slice
@ -189,7 +196,7 @@ public class Trie {
// End of the chain, return // End of the chain, return
return newHash; return newHash;
} else { } 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); return this.put(newNode);
} }
} else { } else {
@ -213,7 +220,7 @@ public class Trie {
// Check for "special" 2 slice type node // Check for "special" 2 slice type node
if (currentNode.length() == PAIR_SIZE) { if (currentNode.length() == PAIR_SIZE) {
// Decode the key // Decode the key
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes()); byte[] k = unpackToNibbles(currentNode.get(0).asBytes());
Object v = currentNode.get(1).asObj(); Object v = currentNode.get(1).asObj();
// Matching key pair (ie. there's already an object with this key) // Matching key pair (ie. there's already an object with this key)
@ -225,8 +232,8 @@ public class Trie {
Object newNode; Object newNode;
if (child.length() == PAIR_SIZE) { if (child.length() == PAIR_SIZE) {
byte[] newKey = concatenate(k, CompactEncoder.decode(child.get(0).asBytes())); byte[] newKey = concatenate(k, unpackToNibbles(child.get(0).asBytes()));
newNode = new Object[] {CompactEncoder.encode(newKey), child.get(1).asObj()}; newNode = new Object[] {packNibbles(newKey), child.get(1).asObj()};
} else { } else {
newNode = new Object[] {currentNode.get(0).asString(), hash}; newNode = new Object[] {currentNode.get(0).asString(), hash};
} }
@ -240,6 +247,7 @@ public class Trie {
// Replace the first nibble in the key // Replace the first nibble in the key
itemList[key[0]] = this.delete(itemList[key[0]], copyOfRange(key, 1, key.length)); itemList[key[0]] = this.delete(itemList[key[0]], copyOfRange(key, 1, key.length));
byte amount = -1; byte amount = -1;
for (byte i = 0; i < LIST_SIZE; i++) { for (byte i = 0; i < LIST_SIZE; i++) {
if (itemList[i] != "") { if (itemList[i] != "") {
@ -253,14 +261,14 @@ public class Trie {
Object[] newNode = null; Object[] newNode = null;
if (amount == 16) { 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) { } else if (amount >= 0) {
Value child = this.getNode(itemList[amount]); Value child = this.getNode(itemList[amount]);
if (child.length() == PAIR_SIZE) { if (child.length() == PAIR_SIZE) {
key = concatenate(new byte[]{amount}, CompactEncoder.decode(child.get(0).asBytes())); key = concatenate(new byte[]{amount}, unpackToNibbles(child.get(0).asBytes()));
newNode = new Object[] {CompactEncoder.encode(key), child.get(1).asObj()}; newNode = new Object[] {packNibbles(key), child.get(1).asObj()};
} else if (child.length() == LIST_SIZE) { } 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 { } else {
newNode = itemList; newNode = itemList;
@ -283,23 +291,16 @@ public class Trie {
return n; return n;
} }
String str = n.asString(); byte[] str = n.asBytes();
if (str.length() == 0) { if (str.length == 0) {
return n; return n;
} else if (str.length() < 32) { } else if (str.length < 32) {
return new Value(str.getBytes()); return new Value(str);
} }
return this.cache.get(n.asBytes()); return this.cache.get(str);
} }
private Object put(Object node) { 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); return this.cache.put(node);
} }
@ -319,9 +320,9 @@ public class Trie {
return itemList; 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) { 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. // Save the cached value to the database.
@ -358,6 +359,7 @@ public class Trie {
return i; return i;
} }
// Created an array of empty elements of requred length
private Object[] emptyStringSlice(int l) { private Object[] emptyStringSlice(int l) {
Object[] slice = new Object[l]; Object[] slice = new Object[l];
for (int i = 0; i < l; i++) { for (int i = 0; i < l; i++) {
@ -365,4 +367,20 @@ public class Trie {
} }
return slice; 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 java.util.List;
import org.ethereum.util.CompactEncoder;
import org.ethereum.util.Value; import org.ethereum.util.Value;
import static org.ethereum.util.CompactEncoder.unpackToNibbles;
public class TrieIterator { public class TrieIterator {
@ -22,7 +22,7 @@ public class TrieIterator {
// XXX Note to self, IsSlice == inline node. Str == sha3 to node // XXX Note to self, IsSlice == inline node. Str == sha3 to node
private void workNode(Value currentNode) { private void workNode(Value currentNode) {
if (currentNode.length() == 2) { if (currentNode.length() == 2) {
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes()); byte[] k = unpackToNibbles(currentNode.get(0).asBytes());
if (currentNode.get(1).asString() == "") { if (currentNode.get(1).asString() == "") {
this.workNode(currentNode.get(1)); this.workNode(currentNode.get(1));

View File

@ -10,7 +10,6 @@ import static org.spongycastle.util.Arrays.concatenate;
import static org.spongycastle.util.encoders.Hex.toHexString; import static org.spongycastle.util.encoders.Hex.toHexString;
/** /**
*
* Compact encoding of hex sequence with optional terminator * Compact encoding of hex sequence with optional terminator
* *
* The traditional compact way of encoding a hex string is to convert it into binary * 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 byte TERMINATOR = 16;
private final static String hexBase = "0123456789abcdef"; 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; int terminator = 0;
if (hexSlice[hexSlice.length-1] == TERMINATOR) { if (nibbles[nibbles.length-1] == TERMINATOR) {
terminator = 1; terminator = 1;
hexSlice = copyOf(hexSlice, hexSlice.length-1); nibbles = copyOf(nibbles, nibbles.length-1);
} }
int oddlen = nibbles.length % 2;
int oddlen = hexSlice.length % 2;
int flag = 2*terminator + oddlen; int flag = 2*terminator + oddlen;
if (oddlen != 0) { if (oddlen != 0) {
byte[] flags = new byte[] { (byte) flag}; byte[] flags = new byte[] { (byte) flag};
hexSlice = concatenate(flags, hexSlice); nibbles = concatenate(flags, nibbles);
} else { } else {
byte[] flags = new byte[] { (byte) flag, 0}; byte[] flags = new byte[] { (byte) flag, 0};
hexSlice = concatenate(flags, hexSlice); nibbles = concatenate(flags, nibbles);
} }
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (int i = 0; i < hexSlice.length; i += 2) { for (int i = 0; i < nibbles.length; i += 2) {
buffer.write( 16*hexSlice[i] + hexSlice[i+1] ); buffer.write( 16*nibbles[i] + nibbles[i+1] );
} }
return buffer.toByteArray(); 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) { public static byte[] unpackToNibbles(byte[] str) {
byte[] base = hexDecode(str); byte[] base = binToNibbles(str);
base = copyOf(base, base.length-1); base = copyOf(base, base.length-1);
if (base[0] >= 2) { if (base[0] >= 2) {
base = appendByte(base, TERMINATOR); base = appendByte(base, TERMINATOR);
@ -92,10 +98,11 @@ public class CompactEncoder {
} }
/** /**
* Transforms a binary array to hexadecimal format, * Transforms a binary array to hexadecimal format + terminator
* returns array with each individual nibble adding a terminator at the end *
* @return array with each individual nibble adding a terminator at the end
*/ */
public static byte[] hexDecode(byte[] str) { public static byte[] binToNibbles(byte[] str) {
byte[] hexSlice = new byte[0]; byte[] hexSlice = new byte[0];
String hexEncoded = toHexString(str); String hexEncoded = toHexString(str);
for (char value : hexEncoded.toCharArray()) { for (char value : hexEncoded.toCharArray()) {

View File

@ -5,11 +5,10 @@ import static org.junit.Assert.*;
import org.ethereum.trie.Trie; import org.ethereum.trie.Trie;
import org.junit.Test; import org.junit.Test;
import com.cedarsoftware.util.DeepEquals;
public class TrieTest { public class TrieTest {
private static String LONG_STRING = "1234567890abcdefghijklmnopqrstuvwxxzABCEFGHIJKLMNOPQRSTUVWXYZ"; private static String LONG_STRING = "1234567890abcdefghijklmnopqrstuvwxxzABCEFGHIJKLMNOPQRSTUVWXYZ";
private static String ROOT_HASH_EMPTY = "";
private static String c = "c"; private static String c = "c";
private static String ca = "ca"; private static String ca = "ca";
@ -33,215 +32,305 @@ public class TrieTest {
@Test @Test
public void testEmptyKey() { public void testEmptyKey() {
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update("", dog); trie.update("", dog);
String result = trie.get(""); assertEquals(dog, trie.get(""));
assertEquals(dog, result);
} }
@Test @Test
public void testInsertShortString() { public void testInsertShortString() {
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
String result = trie.get(cat); assertEquals(dog, trie.get(cat));
assertEquals(dog, result);
} }
@Test @Test
public void testInsertLongString() { public void testInsertLongString() {
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
String result = trie.get(cat); assertEquals(LONG_STRING, trie.get(cat));
assertEquals(LONG_STRING, result);
} }
@Test @Test
public void testInsertMultipleItems1() { public void testInsertMultipleItems1() {
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(ca, dude); trie.update(ca, dude);
assertEquals(dude, trie.get(ca));
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(dog, test); trie.update(dog, test);
assertEquals(test, trie.get(dog));
trie.update(doge, LONG_STRING); trie.update(doge, LONG_STRING);
assertEquals(LONG_STRING, trie.get(doge));
trie.update(test, LONG_STRING); trie.update(test, LONG_STRING);
String result1 = trie.get(ca); assertEquals(LONG_STRING, trie.get(test));
String result2 = trie.get(cat);
String result3 = trie.get(dog); // Test if everything is still there
String result4 = trie.get(doge); assertEquals(dude, trie.get(ca));
String result5 = trie.get(test); assertEquals(dog, trie.get(cat));
assertEquals(dude, result1); assertEquals(test, trie.get(dog));
assertEquals(dog, result2); assertEquals(LONG_STRING, trie.get(doge));
assertEquals(test, result3); assertEquals(LONG_STRING, trie.get(test));
assertEquals(LONG_STRING, result4);
assertEquals(LONG_STRING, result5);
} }
@Test @Test
public void testInsertMultipleItems2() { public void testInsertMultipleItems2() {
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(ca, dude); trie.update(ca, dude);
assertEquals(dude, trie.get(ca));
trie.update(doge, LONG_STRING); trie.update(doge, LONG_STRING);
assertEquals(LONG_STRING, trie.get(doge));
trie.update(dog, test); trie.update(dog, test);
assertEquals(test, trie.get(dog));
trie.update(test, LONG_STRING); trie.update(test, LONG_STRING);
String result1 = trie.get(cat); assertEquals(LONG_STRING, trie.get(test));
String result2 = trie.get(ca);
String result3 = trie.get(doge); // Test if everything is still there
String result4 = trie.get(dog); assertEquals(dog, trie.get(cat));
String result5 = trie.get(test); assertEquals(dude, trie.get(ca));
assertEquals(dog, result1); assertEquals(LONG_STRING, trie.get(doge));
assertEquals(dude, result2); assertEquals(test, trie.get(dog));
assertEquals(LONG_STRING, result3); assertEquals(LONG_STRING, trie.get(test));
assertEquals(test, result4);
assertEquals(LONG_STRING, result5);
} }
@Test @Test
public void testUpdateShortToShortString() { public void testUpdateShortToShortString() {
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(cat, dog+"1"); trie.update(cat, dog+"1");
String result = trie.get(cat); assertEquals(dog+"1", trie.get(cat));
assertEquals(dog+"1", result);
} }
@Test @Test
public void testUpdateLongToLongString() { public void testUpdateLongToLongString() {
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, trie.get(cat));
trie.update(cat, LONG_STRING+"1"); trie.update(cat, LONG_STRING+"1");
String result = trie.get(cat); assertEquals(LONG_STRING+"1", trie.get(cat));
assertEquals(LONG_STRING+"1", result);
} }
@Test @Test
public void testUpdateShortToLongString() { public void testUpdateShortToLongString() {
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(cat, LONG_STRING+"1"); trie.update(cat, LONG_STRING+"1");
String result = trie.get(cat); assertEquals(LONG_STRING+"1", trie.get(cat));
assertEquals(LONG_STRING+"1", result);
} }
@Test @Test
public void testUpdateLongToShortString() { public void testUpdateLongToShortString() {
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, trie.get(cat));
trie.update(cat, dog+"1"); trie.update(cat, dog+"1");
String result = trie.get(cat); assertEquals(dog+"1", trie.get(cat));
assertEquals(dog+"1", result);
} }
@Test @Test
public void testDeleteShortString1() { public void testDeleteShortString1() {
String ROOT_HASH_BEFORE = "a9539c810cc2e8fa20785bdd78ec36cc1dab4b41f0d531e80a5e5fd25c3037ee";
String ROOT_HASH_AFTER = "fc5120b4a711bca1f5bb54769525b11b3fb9a8d6ac0b8bf08cbb248770521758";
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
Object expected = trie.getRoot(); assertEquals(dog, trie.get(cat));
trie.update(ca, dude); trie.update(ca, dude);
assertEquals(dude, trie.get(ca));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(ca); trie.delete(ca);
Object result = trie.getRoot(); assertEquals("", trie.get(ca));
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result)); assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
} }
@Test @Test
public void testDeleteShortString2() { public void testDeleteShortString2() {
String ROOT_HASH_BEFORE = "a9539c810cc2e8fa20785bdd78ec36cc1dab4b41f0d531e80a5e5fd25c3037ee";
String ROOT_HASH_AFTER = "b25e1b5be78dbadf6c4e817c6d170bbb47e9916f8f6cc4607c5f3819ce98497b";
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(ca, dude); trie.update(ca, dude);
Object expected = trie.getRoot(); assertEquals(dude, trie.get(ca));
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(cat); trie.delete(cat);
Object result = trie.getRoot(); assertEquals("", trie.get(cat));
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result)); assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
} }
@Test @Test
public void testDeleteShortString3() { public void testDeleteShortString3() {
String ROOT_HASH_BEFORE = "778ab82a7e8236ea2ff7bb9cfa46688e7241c1fd445bf2941416881a6ee192eb";
String ROOT_HASH_AFTER = "05875807b8f3e735188d2479add82f96dee4db5aff00dc63f07a7e27d0deab65";
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, dude); trie.update(cat, dude);
Object expected = trie.getRoot(); assertEquals(dude, trie.get(cat));
trie.update(dog, test); trie.update(dog, test);
assertEquals(test, trie.get(dog));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(dog); trie.delete(dog);
Object result = trie.getRoot(); assertEquals("", trie.get(dog));
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result)); assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
} }
@Test @Test
public void testDeleteLongString1() { public void testDeleteLongString1() {
String ROOT_HASH_BEFORE = "318961a1c8f3724286e8e80d312352f01450bc4892c165cc7614e1c2e5a0012a";
String ROOT_HASH_AFTER = "63356ecf33b083e244122fca7a9b128cc7620d438d5d62e4f8b5168f1fb0527b";
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
Object expected = trie.getRoot(); assertEquals(LONG_STRING, trie.get(cat));
trie.update(dog, LONG_STRING); trie.update(dog, LONG_STRING);
assertEquals(LONG_STRING, trie.get(dog));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(dog); trie.delete(dog);
Object result = trie.getRoot(); assertEquals("", trie.get(dog));
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result)); assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
} }
@Test @Test
public void testDeleteLongString2() { public void testDeleteLongString2() {
String ROOT_HASH_BEFORE = "e020de34ca26f8d373ff2c0a8ac3a4cb9032bfa7a194c68330b7ac3584a1d388";
String ROOT_HASH_AFTER = "334511f0c4897677b782d13a6fa1e58e18de6b24879d57ced430bad5ac831cb2";
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(ca, LONG_STRING); trie.update(ca, LONG_STRING);
Object expected = trie.getRoot(); assertEquals(LONG_STRING, trie.get(ca));
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, trie.get(cat));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(cat); trie.delete(cat);
Object result = trie.getRoot(); assertEquals("", trie.get(cat));
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result)); assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
} }
@Test @Test
public void testDeleteLongString3() { public void testDeleteLongString3() {
String ROOT_HASH_BEFORE = "e020de34ca26f8d373ff2c0a8ac3a4cb9032bfa7a194c68330b7ac3584a1d388";
String ROOT_HASH_AFTER = "63356ecf33b083e244122fca7a9b128cc7620d438d5d62e4f8b5168f1fb0527b";
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
Object expected = trie.getRoot(); assertEquals(LONG_STRING, trie.get(cat));
trie.update(ca, LONG_STRING); trie.update(ca, LONG_STRING);
assertEquals(LONG_STRING, trie.get(ca));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(ca); trie.delete(ca);
Object result = trie.getRoot(); assertEquals("", trie.get(ca));
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result)); assertEquals(ROOT_HASH_AFTER, trie.getRootHash());
} }
@Test @Test
public void testDeleteMultipleItems1() { public void testDeleteMultipleItems1() {
String ROOT_HASH_BEFORE = "3a784eddf1936515f0313b073f99e3bd65c38689021d24855f62a9601ea41717";
String ROOT_HASH_AFTER1 = "60a2e75cfa153c4af2783bd6cb48fd6bed84c6381bc2c8f02792c046b46c0653";
String ROOT_HASH_AFTER2 = "a84739b4762ddf15e3acc4e6957e5ab2bbfaaef00fe9d436a7369c6f058ec90d";
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, trie.get(cat));
trie.update(ca, dude); trie.update(ca, dude);
assertEquals(dude, trie.get(ca));
trie.update(doge, LONG_STRING); trie.update(doge, LONG_STRING);
Object expected = trie.getRoot(); assertEquals(LONG_STRING, trie.get(doge));
trie.update(dog, test); trie.update(dog, test);
assertEquals(test, trie.get(dog));
trie.update(test, LONG_STRING); trie.update(test, LONG_STRING);
assertEquals(LONG_STRING, trie.get(test));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(dog); trie.delete(dog);
assertEquals("", trie.get(dog));
assertEquals(ROOT_HASH_AFTER1, trie.getRootHash());
trie.delete(test); trie.delete(test);
Object result = trie.getRoot(); assertEquals("", trie.get(test));
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result)); assertEquals(ROOT_HASH_AFTER2, trie.getRootHash());
} }
@Test @Test
public void testDeleteMultipleItems2() { public void testDeleteMultipleItems2() {
String ROOT_HASH_BEFORE = "cf1ed2b6c4b6558f70ef0ecf76bfbee96af785cb5d5e7bfc37f9804ad8d0fb56";
String ROOT_HASH_AFTER1 = "f586af4a476ba853fca8cea1fbde27cd17d537d18f64269fe09b02aa7fe55a9e";
String ROOT_HASH_AFTER2 = "c59fdc16a80b11cc2f7a8b107bb0c954c0d8059e49c760ec3660eea64053ac91";
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
trie.update(c, LONG_STRING); trie.update(c, LONG_STRING);
Object expected = trie.getRoot(); assertEquals(LONG_STRING, trie.get(c));
trie.update(ca, LONG_STRING); trie.update(ca, LONG_STRING);
assertEquals(LONG_STRING, trie.get(ca));
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, trie.get(cat));
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(ca); trie.delete(ca);
trie.delete(cat); assertEquals("", trie.get(ca));
Object result = trie.getRoot(); assertEquals(ROOT_HASH_AFTER1, trie.getRootHash());
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
trie.delete(cat);
assertEquals("", trie.get(cat));
assertEquals(ROOT_HASH_AFTER2, trie.getRootHash());
} }
@Test @Test
public void testDeleteAll() { public void testDeleteAll() {
String ROOT_HASH_BEFORE = "a84739b4762ddf15e3acc4e6957e5ab2bbfaaef00fe9d436a7369c6f058ec90d";
Trie trie = new Trie(mockDb); Trie trie = new Trie(mockDb);
Object expected = trie.getRoot(); assertEquals(ROOT_HASH_EMPTY, trie.getRootHash());
trie.update(ca, dude); trie.update(ca, dude);
trie.update(cat, dog); trie.update(cat, dog);
trie.update(doge, LONG_STRING); trie.update(doge, LONG_STRING);
assertEquals(ROOT_HASH_BEFORE, trie.getRootHash());
trie.delete(ca); trie.delete(ca);
trie.delete(cat); trie.delete(cat);
trie.delete(doge); trie.delete(doge);
Object result = trie.getRoot(); assertEquals(ROOT_HASH_EMPTY, trie.getRootHash());
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
} }
@Test @Test
public void testTrieCmp() { public void testTrieCmp() {
Trie trie1 = new Trie(mockDb); Trie trie1 = new Trie(mockDb);
@ -250,10 +339,12 @@ public class TrieTest {
trie1.update(doge, LONG_STRING); trie1.update(doge, LONG_STRING);
trie2.update(doge, LONG_STRING); trie2.update(doge, LONG_STRING);
assertTrue("Expected tries to be equal", trie1.cmp(trie2)); assertTrue("Expected tries to be equal", trie1.cmp(trie2));
assertEquals(trie1.getRootHash(), trie2.getRootHash());
trie1.update(dog, LONG_STRING); trie1.update(dog, LONG_STRING);
trie2.update(cat, LONG_STRING); trie2.update(cat, LONG_STRING);
assertFalse("Expected tries not to be equal", trie1.cmp(trie2)); assertFalse("Expected tries not to be equal", trie1.cmp(trie2));
assertNotEquals(trie1.getRootHash(), trie2.getRootHash());
} }
@Test @Test
@ -302,4 +393,84 @@ public class TrieTest {
public void testTrieUndo() { public void testTrieUndo() {
fail("To be implemented"); 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() { public void testCompactEncodeOddCompact() {
byte[] test = new byte[] { 1, 2, 3, 4, 5 }; byte[] test = new byte[] { 1, 2, 3, 4, 5 };
byte[] expectedData = new byte[] { 0x11, 0x23, 0x45 }; 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 @Test
public void testCompactEncodeEvenCompact() { public void testCompactEncodeEvenCompact() {
byte[] test = new byte[] { 0, 1, 2, 3, 4, 5 }; byte[] test = new byte[] { 0, 1, 2, 3, 4, 5 };
byte[] expectedData = new byte[] { 0x00, 0x01, 0x23, 0x45 }; 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 @Test
public void testCompactEncodeEvenTerminated() { public void testCompactEncodeEvenTerminated() {
byte[] test = new byte[] { 0, 15, 1, 12, 11, 8, T }; byte[] test = new byte[] { 0, 15, 1, 12, 11, 8, T };
byte[] expectedData = new byte[] { 0x20, 0x0f, 0x1c, (byte) 0xb8 }; 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 @Test
public void testCompactEncodeOddTerminated() { public void testCompactEncodeOddTerminated() {
byte[] test = new byte[] { 15, 1, 12, 11, 8, T }; byte[] test = new byte[] { 15, 1, 12, 11, 8, T };
byte[] expectedData = new byte[] { 0x3f, 0x1c, (byte) 0xb8 }; 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 @Test
public void testCompactDecodeOddCompact() { public void testCompactDecodeOddCompact() {
byte[] test = new byte[] { 0x11, 0x23, 0x45 }; byte[] test = new byte[] { 0x11, 0x23, 0x45 };
byte[] expected = new byte[] {1, 2, 3, 4, 5}; 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 @Test
public void testCompactDecodeEvenCompact() { public void testCompactDecodeEvenCompact() {
byte[] test = new byte[] { 0x00, 0x01, 0x23, 0x45 }; byte[] test = new byte[] { 0x00, 0x01, 0x23, 0x45 };
byte[] expected = new byte[] {0, 1, 2, 3, 4, 5}; 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 @Test
public void testCompactDecodeEvenTerminated() { public void testCompactDecodeEvenTerminated() {
byte[] test = new byte[] { 0x20, 0x0f, 0x1c, (byte) 0xb8 }; byte[] test = new byte[] { 0x20, 0x0f, 0x1c, (byte) 0xb8 };
byte[] expected = new byte[] {0, 15, 1, 12, 11, 8, T}; 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 @Test
public void testCompactDecodeOddTerminated() { public void testCompactDecodeOddTerminated() {
byte[] test = new byte[] { 0x3f, 0x1c, (byte) 0xb8 }; byte[] test = new byte[] { 0x3f, 0x1c, (byte) 0xb8 };
byte[] expected = new byte[] {15, 1, 12, 11, 8, T}; 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 @Test
public void testCompactHexDecode() { public void testCompactHexDecode() {
byte[] test = "stallion".getBytes(); 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 }; 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));
} }
} }