diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java b/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java index fd948f45..8798a98b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java @@ -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); - - } } diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java b/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java index 7ead2c11..c8d1ac80 100644 --- a/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java +++ b/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java @@ -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; } diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Node.java b/ethereumj-core/src/main/java/org/ethereum/trie/Node.java index f37c68d6..141c7336 100644 --- a/ethereumj-core/src/main/java/org/ethereum/trie/Node.java +++ b/ethereumj-core/src/main/java/org/ethereum/trie/Node.java @@ -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 }; - } } diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java b/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java index e0e83afc..debad971 100644 --- a/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java +++ b/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java @@ -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); + } + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java b/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java index 9b43817e..57c5c943 100644 --- a/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java +++ b/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java @@ -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)); diff --git a/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java b/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java index c88305f0..6a6dee82 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java @@ -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()) { diff --git a/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java b/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java index 4b26fabd..ac062d07 100644 --- a/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java @@ -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()); + } } diff --git a/ethereumj-core/src/test/java/org/ethereum/util/CompactEncoderTest.java b/ethereumj-core/src/test/java/org/ethereum/util/CompactEncoderTest.java index 35df3377..cf8a53d3 100644 --- a/ethereumj-core/src/test/java/org/ethereum/util/CompactEncoderTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/CompactEncoderTest.java @@ -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)); } }