Merge pull request #13 from nicksavers/master
Fix Trie to generate the expected rootHash
This commit is contained in:
commit
7645262d55
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue