Add Trie datastructure, utility classes and leveldb dependency
This commit is contained in:
parent
6fca92b62f
commit
01a9fcc7e2
|
@ -35,6 +35,18 @@
|
|||
<artifactId>core</artifactId>
|
||||
<version>${spongycastle.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.iq80.leveldb</groupId>
|
||||
<artifactId>leveldb</artifactId>
|
||||
<version>${leveldb.version}</version>
|
||||
</dependency>
|
||||
<!-- Added Cedarsoft for DeepEqual function -->
|
||||
<dependency>
|
||||
<groupId>com.cedarsoftware</groupId>
|
||||
<artifactId>java-util</artifactId>
|
||||
<version>1.8.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fifesoft</groupId>
|
||||
<artifactId>rsyntaxtextarea</artifactId>
|
||||
|
|
|
@ -24,7 +24,6 @@ import java.util.TimerTask;
|
|||
import org.ethereum.gui.PeerListener;
|
||||
import org.ethereum.manager.MainData;
|
||||
import org.ethereum.net.Command;
|
||||
import org.ethereum.net.RLP;
|
||||
import org.ethereum.net.message.BlocksMessage;
|
||||
import org.ethereum.net.message.DisconnectMessage;
|
||||
import org.ethereum.net.message.GetChainMessage;
|
||||
|
@ -34,6 +33,7 @@ import org.ethereum.net.message.NotInChainMessage;
|
|||
import org.ethereum.net.message.PeersMessage;
|
||||
import org.ethereum.net.message.StaticMessages;
|
||||
import org.ethereum.net.message.TransactionsMessage;
|
||||
import org.ethereum.net.rlp.RLP;
|
||||
import org.ethereum.net.rlp.RLPList;
|
||||
import org.ethereum.net.vo.Block;
|
||||
import org.ethereum.util.Utils;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package org.ethereum.net.message;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import static org.ethereum.net.Command.HELLO;
|
||||
import org.ethereum.net.RLP;
|
||||
|
||||
import org.ethereum.net.rlp.RLP;
|
||||
import org.ethereum.net.rlp.RLPItem;
|
||||
import org.ethereum.net.rlp.RLPList;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.ethereum.net;
|
||||
package org.ethereum.net.rlp;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
|
@ -8,9 +8,6 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import org.ethereum.net.rlp.RLPItem;
|
||||
import org.ethereum.net.rlp.RLPList;
|
||||
|
||||
public class RLP {
|
||||
|
||||
public static byte decodeOneByteItem(byte[] data, int index) {
|
|
@ -0,0 +1,117 @@
|
|||
package org.ethereum.trie;
|
||||
|
||||
import static org.iq80.leveldb.impl.Iq80DBFactory.factory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
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 {
|
||||
|
||||
private Map<byte[], Node> nodes;
|
||||
private DB db;
|
||||
private boolean isDirty;
|
||||
|
||||
public Cache(DB db) {
|
||||
if(db == null) {
|
||||
try {
|
||||
/* **** Experimental LevelDB Code **** */
|
||||
Options options = new Options();
|
||||
options.createIfMissing(true);
|
||||
this.db = factory.open(new File("ethereumdb"), options);
|
||||
/* **** Experimental LevelDB Code **** */
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.db = db;
|
||||
nodes = new HashMap<byte[], Node>();
|
||||
}
|
||||
|
||||
public Object put(Object o) {
|
||||
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));
|
||||
this.isDirty = true;
|
||||
return sha;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public Value get(byte[] key) {
|
||||
// First check if the key is the cache
|
||||
if (this.nodes.get(key) != null) {
|
||||
return this.nodes.get(key).getValue();
|
||||
}
|
||||
// Get the key of the database instead and cache it
|
||||
byte[] data = this.db.get(key);
|
||||
Value value = new Value(data);
|
||||
// Create caching node
|
||||
this.nodes.put(key, new Node(key, value, false));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void delete(byte[] key) {
|
||||
this.nodes.remove(key);
|
||||
this.db.delete(key);
|
||||
}
|
||||
|
||||
public void commit() {
|
||||
// Don't try to commit if it isn't dirty
|
||||
if (!this.isDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (byte[] key : this.nodes.keySet()) {
|
||||
Node node = this.nodes.get(key);
|
||||
if (node.isDirty()) {
|
||||
this.db.put(key, node.getValue().encode());
|
||||
node.setDirty(false);
|
||||
}
|
||||
}
|
||||
this.isDirty = false;
|
||||
|
||||
// If the nodes grows beyond the 200 entries we simple empty it
|
||||
// FIXME come up with something better
|
||||
if (this.nodes.size() > 200) {
|
||||
this.nodes = new HashMap<byte[], Node>();
|
||||
}
|
||||
}
|
||||
|
||||
public void undo() {
|
||||
Iterator<Map.Entry<byte[], Node>> iter = this.nodes.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
if(iter.next().getValue().isDirty()){
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
this.isDirty = false;
|
||||
}
|
||||
|
||||
public boolean isDirty() {
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
public void setDirty(boolean isDirty) {
|
||||
this.isDirty = isDirty;
|
||||
}
|
||||
|
||||
public Map<byte[], Node> getNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public DB getDb() {
|
||||
return db;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
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 ]
|
||||
*
|
||||
* 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
|
||||
* a [ key, value ] node, where the key gives the hexadecimal path
|
||||
* to descend, in the compact encoding described above, and the value
|
||||
* is just the hash of the node like in the standard radix tree.
|
||||
*
|
||||
* R
|
||||
* / \
|
||||
* / \
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
private Value value;
|
||||
private boolean dirty;
|
||||
|
||||
public Node(byte[] key, Value val) {
|
||||
this(key, val, false);
|
||||
}
|
||||
|
||||
public Node(byte[] key, Value val, boolean dirty) {
|
||||
this.key = key;
|
||||
this.value = val;
|
||||
this.dirty = dirty;
|
||||
}
|
||||
|
||||
public Node copy() {
|
||||
return new Node(this.key.clone(), this.value, this.dirty);
|
||||
}
|
||||
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
public void setDirty(boolean ditry) {
|
||||
this.dirty = ditry;
|
||||
}
|
||||
|
||||
public byte[] getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Value getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Object[] getItems() {
|
||||
return new Object[] { key, value };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,368 @@
|
|||
package org.ethereum.trie;
|
||||
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
import static org.spongycastle.util.Arrays.concatenate;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.ethereum.util.CompactEncoder;
|
||||
import org.ethereum.util.Value;
|
||||
import org.iq80.leveldb.DB;
|
||||
|
||||
import com.cedarsoftware.util.DeepEquals;
|
||||
|
||||
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;
|
||||
|
||||
public Trie(DB db) {
|
||||
this(db, "");
|
||||
}
|
||||
|
||||
public Trie(DB db, Object root) {
|
||||
this.cache = new Cache(db);
|
||||
this.root = root;
|
||||
this.prevRoot = root;
|
||||
}
|
||||
|
||||
public TrieIterator getIterator() {
|
||||
return new TrieIterator(this);
|
||||
}
|
||||
|
||||
public Cache getCache() {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
public Object getPrevRoot() {
|
||||
return prevRoot;
|
||||
}
|
||||
|
||||
public Object getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public void setRoot(Node root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public void setCache(Cache cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
/**************************************
|
||||
* Public (query) interface functions *
|
||||
**************************************/
|
||||
|
||||
/**
|
||||
* Insert key/value pair into trie
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
public void update(String key, String value) {
|
||||
if (key == null)
|
||||
throw new NullPointerException("Key should not be blank");
|
||||
byte[] k = CompactEncoder.hexDecode(key.getBytes());
|
||||
this.root = this.insertOrDelete(this.root, k, value.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value from a node
|
||||
*
|
||||
* @param key
|
||||
* @return value
|
||||
*/
|
||||
public String get(String key) {
|
||||
byte[] k = CompactEncoder.hexDecode(key.getBytes());
|
||||
Value c = new Value( this.get(this.root, k) );
|
||||
return c.asString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a key/value pair from the trie
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
public void delete(String key) {
|
||||
this.update(key, "");
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* Private functions *
|
||||
****************************************/
|
||||
|
||||
private Object get(Object node, byte[] key) {
|
||||
|
||||
// Return the node if key is empty (= found)
|
||||
if (key.length == 0 || isEmptyNode(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
Value currentNode = this.getNode(node);
|
||||
int length = currentNode.length();
|
||||
|
||||
if (length == PAIR_SIZE) {
|
||||
// Decode the key
|
||||
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes());
|
||||
Object v = currentNode.get(1).asObj();
|
||||
|
||||
if (key.length >= k.length && Arrays.equals(k, copyOfRange(key, 0, k.length))) {
|
||||
return this.get(v, copyOfRange(key, k.length, key.length));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} else if (length == LIST_SIZE) {
|
||||
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) {
|
||||
if (value.length != 0) {
|
||||
return this.insert(node, key, value);
|
||||
} else {
|
||||
return this.delete(node, key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update or add the item inside a node
|
||||
* return the updated node with rlp encoded
|
||||
*/
|
||||
private Object insert(Object node, byte[] key, Object value) {
|
||||
|
||||
if (key.length == 0) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (isEmptyNode(node)) {
|
||||
Object[] newNode = new Object[] { CompactEncoder.encode(key), value };
|
||||
return this.put(newNode);
|
||||
}
|
||||
|
||||
Value currentNode = this.getNode(node);
|
||||
|
||||
// Check for "special" 2 slice type node
|
||||
if (currentNode.length() == PAIR_SIZE) {
|
||||
// Decode the key
|
||||
|
||||
byte[] k = CompactEncoder.decode(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};
|
||||
return this.put(newNode);
|
||||
}
|
||||
|
||||
Object newHash;
|
||||
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
|
||||
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
|
||||
Object[] scaledSlice = emptyStringSlice(17);
|
||||
// Set the copied and new node
|
||||
scaledSlice[k[matchingLength]] = oldNode;
|
||||
scaledSlice[key[matchingLength]] = newNode;
|
||||
newHash = this.put(scaledSlice);
|
||||
}
|
||||
|
||||
if (matchingLength == 0) {
|
||||
// End of the chain, return
|
||||
return newHash;
|
||||
} else {
|
||||
Object[] newNode = new Object[] {CompactEncoder.encode(copyOfRange(key, 0, matchingLength)), newHash};
|
||||
return this.put(newNode);
|
||||
}
|
||||
} else {
|
||||
// Copy the current node over to the new node
|
||||
Object[] newNode = copyNode(currentNode);
|
||||
// Replace the first nibble in the key
|
||||
newNode[key[0]] = this.insert(currentNode.get(key[0]).asObj(), copyOfRange(key, 1, key.length), value);
|
||||
|
||||
return this.put(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
private Object delete(Object node, byte[] key) {
|
||||
|
||||
if (key.length == 0 || isEmptyNode(node)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// New node
|
||||
Value currentNode = this.getNode(node);
|
||||
// Check for "special" 2 slice type node
|
||||
if (currentNode.length() == PAIR_SIZE) {
|
||||
// Decode the key
|
||||
byte[] k = CompactEncoder.decode(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)) {
|
||||
return "";
|
||||
} else if (Arrays.equals(copyOfRange(key, 0, k.length), k)) {
|
||||
Object hash = this.delete(v, copyOfRange(key, k.length, key.length));
|
||||
Value child = this.getNode(hash);
|
||||
|
||||
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()};
|
||||
} else {
|
||||
newNode = new Object[] {currentNode.get(0).asString(), hash};
|
||||
}
|
||||
return this.put(newNode);
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
} else {
|
||||
// Copy the current node over to a new node
|
||||
Object[] itemList = copyNode(currentNode);
|
||||
|
||||
// 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] != "") {
|
||||
if (amount == -1) {
|
||||
amount = i;
|
||||
} else {
|
||||
amount = -2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object[] newNode = null;
|
||||
if (amount == 16) {
|
||||
newNode = new Object[] { CompactEncoder.encode(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()};
|
||||
} else if (child.length() == LIST_SIZE) {
|
||||
newNode = new Object[] { CompactEncoder.encode(new byte[]{amount}), itemList[amount]};
|
||||
}
|
||||
} else {
|
||||
newNode = itemList;
|
||||
}
|
||||
return this.put(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to retrieve the actual node
|
||||
* If the node is not a list and length is > 32 bytes get the actual node from the db
|
||||
*
|
||||
* @param node
|
||||
* @return
|
||||
*/
|
||||
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) {
|
||||
return n;
|
||||
} else if (str.length() < 32) {
|
||||
return new Value(str.getBytes());
|
||||
}
|
||||
return this.cache.get(n.asBytes());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private boolean isEmptyNode(Object node) {
|
||||
Value n = new Value(node);
|
||||
return (node == null || (n.isString() && (n.asString() == "" || n.get(0).isNull())) || n.length() == 0);
|
||||
}
|
||||
|
||||
private Object[] copyNode(Value currentNode) {
|
||||
Object[] itemList = emptyStringSlice(LIST_SIZE);
|
||||
for (int i = 0; i < LIST_SIZE; i++) {
|
||||
Object cpy = currentNode.get(i).asObj();
|
||||
if (cpy != null) {
|
||||
itemList[i] = cpy;
|
||||
}
|
||||
}
|
||||
return itemList;
|
||||
}
|
||||
|
||||
// Simple compare function which creates a rlp value out of the evaluated objects
|
||||
public boolean cmp(Trie trie) {
|
||||
return DeepEquals.deepEquals(this.root, trie.getRoot());
|
||||
}
|
||||
|
||||
// Save the cached value to the database.
|
||||
public void sync() {
|
||||
this.cache.commit();
|
||||
this.prevRoot = this.root;
|
||||
}
|
||||
|
||||
public void undo() {
|
||||
this.cache.undo();
|
||||
this.root = this.prevRoot;
|
||||
}
|
||||
|
||||
// Returns a copy of this trie
|
||||
public Trie copy() {
|
||||
Trie trie = new Trie(this.cache.getDb(), this.root);
|
||||
for (byte[] key : this.cache.getNodes().keySet()) {
|
||||
Node node = this.cache.getNodes().get(key);
|
||||
trie.cache.getNodes().put(key, node.copy());
|
||||
}
|
||||
return trie;
|
||||
}
|
||||
|
||||
/********************************
|
||||
* Utility functions *
|
||||
*******************************/
|
||||
|
||||
// Returns the amount of nibbles that match each other from 0 ...
|
||||
private int matchingNibbleLength(byte[] a, byte[] b) {
|
||||
int i = 0;
|
||||
while (Arrays.equals(copyOfRange(a, 0, i+1), copyOfRange(b, 0, i+1)) && i < b.length) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private Object[] emptyStringSlice(int l) {
|
||||
Object[] slice = new Object[l];
|
||||
for (int i = 0; i < l; i++) {
|
||||
slice[i] = "";
|
||||
}
|
||||
return slice;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package org.ethereum.trie;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.ethereum.util.CompactEncoder;
|
||||
import org.ethereum.util.Value;
|
||||
|
||||
public class TrieIterator {
|
||||
|
||||
Trie trie;
|
||||
String key;
|
||||
String value;
|
||||
|
||||
List<byte[]> shas;
|
||||
List<String> values;
|
||||
|
||||
public TrieIterator(Trie t) {
|
||||
this.trie = t;
|
||||
}
|
||||
|
||||
// Some time in the near future this will need refactoring :-)
|
||||
// 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());
|
||||
|
||||
if (currentNode.get(1).asString() == "") {
|
||||
this.workNode(currentNode.get(1));
|
||||
} else {
|
||||
if (k[k.length-1] == 16) {
|
||||
this.values.add( currentNode.get(1).asString() );
|
||||
} else {
|
||||
this.shas.add(currentNode.get(1).asBytes());
|
||||
this.getNode(currentNode.get(1).asBytes());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < currentNode.length(); i++) {
|
||||
if (i == 16 && currentNode.get(i).length() != 0) {
|
||||
this.values.add( currentNode.get(i).asString() );
|
||||
} else {
|
||||
if (currentNode.get(i).asString() == "") {
|
||||
this.workNode(currentNode.get(i));
|
||||
} else {
|
||||
String val = currentNode.get(i).asString();
|
||||
if (val != "") {
|
||||
this.shas.add(currentNode.get(1).asBytes());
|
||||
this.getNode(val.getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getNode(byte[] node) {
|
||||
Value currentNode = this.trie.getCache().get(node);
|
||||
this.workNode(currentNode);
|
||||
}
|
||||
|
||||
private List<byte[]> collect() {
|
||||
if (this.trie.getRoot() == "") {
|
||||
return null;
|
||||
}
|
||||
this.getNode(new Value(this.trie.getRoot()).asBytes());
|
||||
return this.shas;
|
||||
}
|
||||
|
||||
public int purge() {
|
||||
List<byte[]> shas = this.collect();
|
||||
|
||||
for (byte[] sha : shas) {
|
||||
this.trie.getCache().delete(sha);
|
||||
}
|
||||
return this.values.size();
|
||||
}
|
||||
|
||||
private String getKey() {
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getValue() {
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package org.ethereum.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
import static java.util.Arrays.copyOf;
|
||||
|
||||
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
|
||||
* - that is, a string like 0f1248 would become three bytes 15, 18, 72. However,
|
||||
* this approach has one slight problem: what if the length of the hex string is odd?
|
||||
* In that case, there is no way to distinguish between, say, 0f1248 and f1248.
|
||||
*
|
||||
* Additionally, our application in the Merkle Patricia tree requires the additional feature
|
||||
* that a hex string can also have a special "terminator symbol" at the end (denoted by the 'T').
|
||||
* A terminator symbol can occur only once, and only at the end.
|
||||
*
|
||||
* An alternative way of thinking about this to not think of there being a terminator symbol,
|
||||
* but instead treat bit specifying the existence of the terminator symbol as a bit specifying
|
||||
* that the given node encodes a final node, where the value is an actual value, rather than
|
||||
* the hash of yet another node.
|
||||
*
|
||||
* To solve both of these issues, we force the first nibble of the final byte-stream to encode
|
||||
* two flags, specifying oddness of length (ignoring the 'T' symbol) and terminator status;
|
||||
* these are placed, respectively, into the two lowest significant bits of the first nibble.
|
||||
* In the case of an even-length hex string, we must introduce a second nibble (of value zero)
|
||||
* to ensure the hex-string is even in length and thus is representable by a whole number of bytes.
|
||||
*
|
||||
* Examples:
|
||||
* > [ 1, 2, 3, 4, 5 ]
|
||||
* '\x11\x23\x45'
|
||||
* > [ 0, 1, 2, 3, 4, 5 ]
|
||||
* '\x00\x01\x23\x45'
|
||||
* > [ 0, 15, 1, 12, 11, 8, T ]
|
||||
* '\x20\x0f\x1c\xb8'
|
||||
* > [ 15, 1, 12, 11, 8, T ]
|
||||
* '\x3f\x1c\xb8'
|
||||
*
|
||||
*/
|
||||
public class CompactEncoder {
|
||||
|
||||
private final static byte TERMINATOR = 16;
|
||||
private final static String hexBase = "0123456789abcdef";
|
||||
|
||||
public static byte[] encode(byte[] hexSlice) {
|
||||
int terminator = 0;
|
||||
|
||||
if (hexSlice[hexSlice.length-1] == TERMINATOR) {
|
||||
terminator = 1;
|
||||
hexSlice = copyOf(hexSlice, hexSlice.length-1);
|
||||
}
|
||||
|
||||
int oddlen = hexSlice.length % 2;
|
||||
int flag = 2*terminator + oddlen;
|
||||
if (oddlen != 0) {
|
||||
byte[] flags = new byte[] { (byte) flag};
|
||||
hexSlice = concatenate(flags, hexSlice);
|
||||
} else {
|
||||
byte[] flags = new byte[] { (byte) flag, 0};
|
||||
hexSlice = concatenate(flags, hexSlice);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
for (int i = 0; i < hexSlice.length; i += 2) {
|
||||
buffer.write( 16*hexSlice[i] + hexSlice[i+1] );
|
||||
}
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips hex slices
|
||||
*/
|
||||
public static byte[] decode(byte[] str) {
|
||||
byte[] base = hexDecode(str);
|
||||
base = copyOf(base, base.length-1);
|
||||
if (base[0] >= 2) {
|
||||
base = appendByte(base, TERMINATOR);
|
||||
}
|
||||
if (base[0]%2 == 1) {
|
||||
base = copyOfRange(base, 1, base.length);
|
||||
} else {
|
||||
base = copyOfRange(base, 2, base.length);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
byte[] hexSlice = new byte[0];
|
||||
String hexEncoded = toHexString(str);
|
||||
for (char value : hexEncoded.toCharArray()) {
|
||||
hexSlice = appendByte(hexSlice, (byte) hexBase.indexOf(value));
|
||||
}
|
||||
hexSlice = appendByte(hexSlice, TERMINATOR);
|
||||
|
||||
return hexSlice;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.ethereum.util;
|
||||
|
||||
public class DecodeResult {
|
||||
|
||||
private int pos;
|
||||
private Object decoded;
|
||||
|
||||
public DecodeResult(int pos, Object decoded) {
|
||||
this.pos = pos;
|
||||
this.decoded = decoded;
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
public Object getDecoded() {
|
||||
return decoded;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package org.ethereum.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
import static org.spongycastle.util.Arrays.concatenate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Recursive Length Prefix (RLP) encoding.
|
||||
*
|
||||
* The purpose of RLP is to encode arbitrarily nested arrays of binary data, and
|
||||
* RLP is the main encoding method used to serialize objects in Ethereum. The
|
||||
* only purpose of RLP is to encode structure; encoding specific atomic data
|
||||
* types (eg. strings, ints, floats) is left up to higher-order protocols; in
|
||||
* Ethereum the standard is that integers are represented in big endian binary
|
||||
* form. If one wishes to use RLP to encode a dictionary, the two suggested
|
||||
* canonical forms are to either use [[k1,v1],[k2,v2]...] with keys in
|
||||
* lexicographic order or to use the higher-level Patricia Tree encoding as
|
||||
* Ethereum does.
|
||||
*
|
||||
* The RLP encoding function takes in an item. An item is defined as follows:
|
||||
*
|
||||
* - A string (ie. byte array) is an item - A list of items is an item
|
||||
*
|
||||
* For example, an empty string is an item, as is the string containing the word
|
||||
* "cat", a list containing any number of strings, as well as more complex data
|
||||
* structures like ["cat",["puppy","cow"],"horse",[[]],"pig",[""],"sheep"]. Note
|
||||
* that in the context of the rest of this article, "string" will be used as a
|
||||
* synonym for "a certain number of bytes of binary data"; no special encodings
|
||||
* are used and no knowledge about the content of the strings is implied.
|
||||
*
|
||||
* See: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP
|
||||
*/
|
||||
public class RlpEncoder extends CompactEncoder {
|
||||
|
||||
/** Allow for content up to size of 2^64 bytes **/
|
||||
private static double MAX_ITEM_LENGTH = Math.pow(256, 8);
|
||||
|
||||
/**
|
||||
[5:30:35 PM] Vitalik Buterin: 56 bytes maximizes the benefit of both options
|
||||
[5:30:41 PM] Vitalik Buterin: if we went with 60
|
||||
[5:31:03 PM] Vitalik Buterin: then we would have only had 4 slots for long strings so RLP would not have been able to store objects above 4gb
|
||||
[5:31:08 PM] Vitalik Buterin: if we went with 48
|
||||
[5:31:18 PM] Vitalik Buterin: then RLP would be fine for 2^128 space, but that's way too much
|
||||
[5:31:32 PM] Vitalik Buterin: so 56 and 2^64 space seems like the right place to put the cutoff
|
||||
[5:31:44 PM] Vitalik Buterin: also, that's where Bitcoin's varint does the cutof
|
||||
**/
|
||||
private static int SIZE_THRESHOLD = 56;
|
||||
|
||||
/** RLP encoding rules are defined as follows: */
|
||||
|
||||
/*
|
||||
* For a single byte whose value is in the [0x00, 0x7f] range, that byte is
|
||||
* its own RLP encoding.
|
||||
*/
|
||||
|
||||
/*
|
||||
* If a string is 0-55 bytes long, the RLP encoding consists of a single
|
||||
* byte with value 0x80 plus the length of the string followed by the
|
||||
* string. The range of the first byte is thus [0x80, 0xb7].
|
||||
*/
|
||||
private static int offsetShortItem = 0x80;
|
||||
|
||||
/*
|
||||
* If a string is more than 55 bytes long, the RLP encoding consists of a
|
||||
* single byte with value 0xb7 plus the length of the length of the string
|
||||
* in binary form, followed by the length of the string, followed by the
|
||||
* string. For example, a length-1024 string would be encoded as
|
||||
* \xb9\x04\x00 followed by the string. The range of the first byte is thus
|
||||
* [0xb8, 0xbf].
|
||||
*/
|
||||
private static int offsetLongItem = 0xb8;
|
||||
|
||||
/*
|
||||
* If the total payload of a list (i.e. the combined length of all its
|
||||
* items) is 0-55 bytes long, the RLP encoding consists of a single byte
|
||||
* with value 0xc0 plus the length of the list followed by the concatenation
|
||||
* of the RLP encodings of the items. The range of the first byte is thus
|
||||
* [0xc0, 0xf7].
|
||||
*/
|
||||
private static int offsetShortList = 0xc0;
|
||||
|
||||
/*
|
||||
* If the total payload of a list is more than 55 bytes long, the RLP
|
||||
* encoding consists of a single byte with value 0xf7 plus the length of the
|
||||
* length of the list in binary form, followed by the length of the list,
|
||||
* followed by the concatenation of the RLP encodings of the items. The
|
||||
* range of the first byte is thus [0xf8, 0xff].
|
||||
*/
|
||||
private static int offsetLongList = 0xf8;
|
||||
private static int maxPrefix = 0xff;
|
||||
|
||||
public static byte[] encode(Object input) {
|
||||
Value val = new Value(input);
|
||||
if (val.isList()) {
|
||||
List<Object> inputArray = val.asList();
|
||||
if (inputArray.size() == 0) {
|
||||
return encodeLength(inputArray.size(), offsetShortList);
|
||||
}
|
||||
byte[] output = new byte[0];
|
||||
for (Object object : inputArray) {
|
||||
output = concatenate(output, encode(object));
|
||||
}
|
||||
byte[] prefix = encodeLength(output.length, offsetShortList);
|
||||
return concatenate(prefix, output);
|
||||
} else {
|
||||
byte[] inputAsHex = asHex(input);
|
||||
if(inputAsHex.length == 1) {
|
||||
return inputAsHex;
|
||||
} else {
|
||||
byte[] firstByte = encodeLength(inputAsHex.length, offsetShortItem);
|
||||
return concatenate(firstByte, inputAsHex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static DecodeResult decode(byte[] data, int pos) {
|
||||
if (data == null || data.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int prefix = data[pos] & maxPrefix;
|
||||
if (prefix == offsetShortItem) {
|
||||
return new DecodeResult(pos+1, new byte[0]); // means no length or 0
|
||||
} else if (prefix < offsetShortItem) {
|
||||
return new DecodeResult(pos+1, new byte[] { data[pos] }); // byte is its own RLP encoding
|
||||
} else if (prefix < offsetLongItem){
|
||||
int len = prefix - offsetShortItem; // length of the encoded bytes
|
||||
return new DecodeResult(pos+1+len, copyOfRange(data, pos+1, pos+1+len));
|
||||
} else if (prefix < offsetShortList) {
|
||||
int lenlen = prefix - offsetLongItem + 1; // length of length the encoded bytes
|
||||
int lenbytes = toInt(copyOfRange(data, pos+1, pos+1+lenlen)); // length of encoded bytes
|
||||
return new DecodeResult(pos+1+lenlen+lenbytes, copyOfRange(data, pos+1+lenlen, pos+1+lenlen+lenbytes));
|
||||
} else if (prefix < offsetLongList) {
|
||||
int len = prefix - offsetShortList; // length of the encoded list
|
||||
int prevPos = pos; pos++;
|
||||
return decodeList(data, pos, prevPos, len);
|
||||
} else if (prefix < maxPrefix) {
|
||||
int lenlen = prefix - offsetLongList + 1; // length of length the encoded list
|
||||
int lenlist = toInt(copyOfRange(data, pos+1, pos+1+lenlen)); // length of encoded bytes
|
||||
pos = pos + lenlen + 1;
|
||||
int prevPos = lenlist;
|
||||
return decodeList(data, pos, prevPos, lenlist);
|
||||
} else {
|
||||
throw new RuntimeException("Only byte values between 0x00 and 0xFF are supported, but got: " + prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/** Integer limitation goes up to 2^31-1 so length can never be bigger than MAX_ITEM_LENGTH */
|
||||
public static byte[] encodeLength(int length, int offset) {
|
||||
if (length < SIZE_THRESHOLD) {
|
||||
byte firstByte = (byte) (length + offset);
|
||||
return new byte[] { firstByte };
|
||||
} else if (length < MAX_ITEM_LENGTH) {
|
||||
byte[] binaryLength = BigInteger.valueOf(length).toByteArray();
|
||||
byte firstByte = (byte) (binaryLength.length + offset + SIZE_THRESHOLD - 1 );
|
||||
return concatenate(new byte[] { firstByte }, binaryLength);
|
||||
} else {
|
||||
throw new RuntimeException("Input too long");
|
||||
}
|
||||
}
|
||||
|
||||
private static DecodeResult decodeList(byte[] data, int pos, int prevPos, int len) {
|
||||
List<Object> slice = new ArrayList<Object>();
|
||||
for (int i = 0; i < len;) {
|
||||
// Get the next item in the data list and append it
|
||||
DecodeResult result = decode(data, pos);
|
||||
slice.add(result.getDecoded());
|
||||
// Increment pos by the amount bytes in the previous read
|
||||
prevPos = result.getPos();
|
||||
i += (prevPos - pos);
|
||||
pos = prevPos;
|
||||
}
|
||||
return new DecodeResult(pos, slice.toArray());
|
||||
}
|
||||
|
||||
public static byte[] asHex(Object input) {
|
||||
if (input instanceof byte[]) {
|
||||
return (byte[]) input;
|
||||
} else if (input instanceof String) {
|
||||
String inputString = (String) input;
|
||||
return inputString.getBytes();
|
||||
} else if(input instanceof Integer) {
|
||||
Integer inputInt = (Integer) input;
|
||||
return (inputInt == 0) ? new byte[0] : BigInteger.valueOf(inputInt.longValue()).toByteArray();
|
||||
} else if(input instanceof BigInteger) {
|
||||
BigInteger inputBigInt = (BigInteger) input;
|
||||
return (inputBigInt == BigInteger.ZERO) ? new byte[0] : inputBigInt.toByteArray();
|
||||
} else if (input instanceof Value) {
|
||||
Value val = (Value) input;
|
||||
return asHex(val.asObj());
|
||||
}
|
||||
throw new RuntimeException("Unsupported type: Only accepting String, Integer and BigInteger for now");
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast hex encoded value from byte[] to int
|
||||
*
|
||||
* Limited to Integer.MAX_VALUE: 2^32-1
|
||||
*
|
||||
* @param b array contains the hex values
|
||||
* @return int value of all hex values together.
|
||||
*/
|
||||
public static int toInt(byte[] b) {
|
||||
if (b == null || b.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
return new BigInteger(b).intValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
package org.ethereum.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.cedarsoftware.util.DeepEquals;
|
||||
|
||||
/**
|
||||
* Class to encapsulate an object and provide utilities for conversion
|
||||
*/
|
||||
public class Value {
|
||||
|
||||
private Object value;
|
||||
|
||||
public void fromRlpEncoded(byte[] data) {
|
||||
if (data.length != 0) {
|
||||
this.value = RlpEncoder.decode(data, 0).getDecoded();
|
||||
}
|
||||
}
|
||||
|
||||
public Value(Object obj) {
|
||||
if (obj instanceof Value) {
|
||||
this.value = ((Value) obj).asObj();
|
||||
} else {
|
||||
this.value = obj;
|
||||
}
|
||||
}
|
||||
|
||||
/* *****************
|
||||
* Convert
|
||||
* *****************/
|
||||
|
||||
public Object asObj() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public List<Object> asList() {
|
||||
Object[] valueArray = (Object[]) value;
|
||||
return Arrays.asList(valueArray);
|
||||
}
|
||||
|
||||
public int asInt() {
|
||||
if (isInt()) {
|
||||
return (Integer) value;
|
||||
} else if (isBytes()) {
|
||||
return new BigInteger(asBytes()).intValue();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long asLong() {
|
||||
if (isLong()) {
|
||||
return (Long) value;
|
||||
} else if (isBytes()) {
|
||||
return new BigInteger(asBytes()).longValue();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public BigInteger asBigInt() {
|
||||
return (BigInteger) value;
|
||||
}
|
||||
|
||||
public String asString() {
|
||||
if (isBytes()) {
|
||||
return new String((byte[]) value);
|
||||
} else if (isString()) {
|
||||
return (String) value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public byte[] asBytes() {
|
||||
if(isBytes()) {
|
||||
return (byte[]) value;
|
||||
} else if(isString()) {
|
||||
return asString().getBytes();
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public int[] asSlice() {
|
||||
return (int[]) value;
|
||||
}
|
||||
|
||||
public Value get(int index) {
|
||||
if(isList()) {
|
||||
// Guard for OutOfBounds
|
||||
if (asList().size() <= index) {
|
||||
return new Value(null);
|
||||
}
|
||||
if (index < 0) {
|
||||
throw new RuntimeException("Negative index not allowed");
|
||||
}
|
||||
return new Value(asList().get(index));
|
||||
}
|
||||
// If this wasn't a slice you probably shouldn't be using this function
|
||||
return new Value(null);
|
||||
}
|
||||
|
||||
/* *****************
|
||||
* Utility
|
||||
* *****************/
|
||||
|
||||
public byte[] encode() {
|
||||
return RlpEncoder.encode(value);
|
||||
}
|
||||
|
||||
public boolean cmp(Value o) {
|
||||
return DeepEquals.deepEquals(this, o);
|
||||
}
|
||||
|
||||
/* *****************
|
||||
* Checks
|
||||
* *****************/
|
||||
|
||||
public boolean isList() {
|
||||
return value != null && value.getClass().isArray() && !value.getClass().getComponentType().isPrimitive();
|
||||
}
|
||||
|
||||
public boolean isString() {
|
||||
return value instanceof String;
|
||||
}
|
||||
|
||||
public boolean isInt() {
|
||||
return value instanceof Integer;
|
||||
}
|
||||
|
||||
public boolean isLong() {
|
||||
return value instanceof Long;
|
||||
}
|
||||
|
||||
public boolean isBigInt() {
|
||||
return value instanceof BigInteger;
|
||||
}
|
||||
|
||||
public boolean isBytes() {
|
||||
return value instanceof byte[];
|
||||
}
|
||||
|
||||
public boolean isNull() {
|
||||
return value == null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return !isNull() && isList() && asList().size() == 0;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
if (isList()) {
|
||||
return asList().size();
|
||||
} else if (isBytes()) {
|
||||
return asBytes().length;
|
||||
} else if (isString()) {
|
||||
return asString().length();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package org.ethereum.block;
|
|||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.ethereum.crypto.HashUtil;
|
||||
import org.ethereum.net.RLP;
|
||||
import org.ethereum.net.rlp.RLP;
|
||||
import org.ethereum.net.rlp.RLPList;
|
||||
import org.ethereum.net.vo.Block;
|
||||
import org.junit.Test;
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.ethereum.net.message.HelloMessage;
|
|||
import org.ethereum.net.message.NotInChainMessage;
|
||||
import org.ethereum.net.message.PeersMessage;
|
||||
import org.ethereum.net.message.TransactionsMessage;
|
||||
import org.ethereum.net.rlp.RLP;
|
||||
import org.ethereum.net.rlp.RLPList;
|
||||
import org.ethereum.net.vo.Block;
|
||||
import org.ethereum.net.vo.PeerData;
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package org.ethereum.trie;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.iq80.leveldb.DB;
|
||||
import org.iq80.leveldb.DBException;
|
||||
import org.iq80.leveldb.DBIterator;
|
||||
import org.iq80.leveldb.Range;
|
||||
import org.iq80.leveldb.ReadOptions;
|
||||
import org.iq80.leveldb.Snapshot;
|
||||
import org.iq80.leveldb.WriteBatch;
|
||||
import org.iq80.leveldb.WriteOptions;
|
||||
|
||||
public class MockDB implements DB {
|
||||
|
||||
private int addedItems;
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compactRange(byte[] arg0, byte[] arg1) throws DBException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public WriteBatch createWriteBatch() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(byte[] arg0) throws DBException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public Snapshot delete(byte[] arg0, WriteOptions arg1)
|
||||
throws DBException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] get(byte[] arg0) throws DBException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] get(byte[] arg0, ReadOptions arg1) throws DBException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getApproximateSizes(Range... arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Snapshot getSnapshot() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBIterator iterator() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBIterator iterator(ReadOptions arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(byte[] arg0, byte[] arg1) throws DBException {
|
||||
// TODO Auto-generated method stub
|
||||
addedItems++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Snapshot put(byte[] arg0, byte[] arg1, WriteOptions arg2)
|
||||
throws DBException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeCompactions() {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suspendCompactions() throws InterruptedException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(WriteBatch arg0) throws DBException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public Snapshot write(WriteBatch arg0, WriteOptions arg1)
|
||||
throws DBException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of items added to this Mock DB
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public int getAddedItems() {
|
||||
return addedItems;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
package org.ethereum.trie;
|
||||
|
||||
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 c = "c";
|
||||
private static String ca = "ca";
|
||||
private static String cat = "cat";
|
||||
private static String dog = "dog";
|
||||
private static String doge = "doge";
|
||||
private static String test = "test";
|
||||
private static String dude = "dude";
|
||||
|
||||
private MockDB mockDb = new MockDB();
|
||||
|
||||
// ROOT: [ '\x16', A ]
|
||||
// A: [ '', '', '', '', B, '', '', '', C, '', '', '', '', '', '', '', '' ]
|
||||
// B: [ '\x00\x6f', D ]
|
||||
// D: [ '', '', '', '', '', '', E, '', '', '', '', '', '', '', '', '', 'verb' ]
|
||||
// E: [ '\x17', F ]
|
||||
// F: [ '', '', '', '', '', '', G, '', '', '', '', '', '', '', '', '', 'puppy' ]
|
||||
// G: [ '\x35', 'coin' ]
|
||||
// C: [ '\x20\x6f\x72\x73\x65', 'stallion' ]
|
||||
|
||||
@Test
|
||||
public void testEmptyKey() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update("", dog);
|
||||
String result = trie.get("");
|
||||
assertEquals(dog, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertShortString() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, dog);
|
||||
String result = trie.get(cat);
|
||||
assertEquals(dog, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertLongString() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, LONG_STRING);
|
||||
String result = trie.get(cat);
|
||||
assertEquals(LONG_STRING, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertMultipleItems1() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(ca, dude);
|
||||
trie.update(cat, dog);
|
||||
trie.update(dog, test);
|
||||
trie.update(doge, LONG_STRING);
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertMultipleItems2() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, dog);
|
||||
trie.update(ca, dude);
|
||||
trie.update(doge, LONG_STRING);
|
||||
trie.update(dog, test);
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateShortToShortString() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, dog);
|
||||
trie.update(cat, dog+"1");
|
||||
String result = trie.get(cat);
|
||||
assertEquals(dog+"1", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLongToLongString() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, LONG_STRING);
|
||||
trie.update(cat, LONG_STRING+"1");
|
||||
String result = trie.get(cat);
|
||||
assertEquals(LONG_STRING+"1", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateShortToLongString() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, dog);
|
||||
trie.update(cat, LONG_STRING+"1");
|
||||
String result = trie.get(cat);
|
||||
assertEquals(LONG_STRING+"1", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLongToShortString() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, LONG_STRING);
|
||||
trie.update(cat, dog+"1");
|
||||
String result = trie.get(cat);
|
||||
assertEquals(dog+"1", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShortString1() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, dog);
|
||||
Object expected = trie.getRoot();
|
||||
trie.update(ca, dude);
|
||||
trie.delete(ca);
|
||||
Object result = trie.getRoot();
|
||||
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShortString2() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(ca, dude);
|
||||
Object expected = trie.getRoot();
|
||||
trie.update(cat, dog);
|
||||
trie.delete(cat);
|
||||
Object result = trie.getRoot();
|
||||
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteShortString3() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, dude);
|
||||
Object expected = trie.getRoot();
|
||||
trie.update(dog, test);
|
||||
trie.delete(dog);
|
||||
Object result = trie.getRoot();
|
||||
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteLongString1() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, LONG_STRING);
|
||||
Object expected = trie.getRoot();
|
||||
trie.update(dog, LONG_STRING);
|
||||
trie.delete(dog);
|
||||
Object result = trie.getRoot();
|
||||
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteLongString2() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(ca, LONG_STRING);
|
||||
Object expected = trie.getRoot();
|
||||
trie.update(cat, LONG_STRING);
|
||||
trie.delete(cat);
|
||||
Object result = trie.getRoot();
|
||||
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteLongString3() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, LONG_STRING);
|
||||
Object expected = trie.getRoot();
|
||||
trie.update(ca, LONG_STRING);
|
||||
trie.delete(ca);
|
||||
Object result = trie.getRoot();
|
||||
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMultipleItems1() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(cat, dog);
|
||||
trie.update(ca, dude);
|
||||
trie.update(doge, LONG_STRING);
|
||||
Object expected = trie.getRoot();
|
||||
trie.update(dog, test);
|
||||
trie.update(test, LONG_STRING);
|
||||
|
||||
trie.delete(dog);
|
||||
trie.delete(test);
|
||||
Object result = trie.getRoot();
|
||||
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMultipleItems2() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(c, LONG_STRING);
|
||||
Object expected = trie.getRoot();
|
||||
trie.update(ca, LONG_STRING);
|
||||
trie.update(cat, LONG_STRING);
|
||||
|
||||
trie.delete(ca);
|
||||
trie.delete(cat);
|
||||
Object result = trie.getRoot();
|
||||
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAll() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
Object expected = trie.getRoot();
|
||||
trie.update(ca, dude);
|
||||
trie.update(cat, dog);
|
||||
trie.update(doge, LONG_STRING);
|
||||
trie.delete(ca);
|
||||
trie.delete(cat);
|
||||
trie.delete(doge);
|
||||
Object result = trie.getRoot();
|
||||
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTrieCmp() {
|
||||
Trie trie1 = new Trie(mockDb);
|
||||
Trie trie2 = new Trie(mockDb);
|
||||
|
||||
trie1.update(doge, LONG_STRING);
|
||||
trie2.update(doge, LONG_STRING);
|
||||
assertTrue("Expected tries to be equal", trie1.cmp(trie2));
|
||||
|
||||
trie1.update(dog, LONG_STRING);
|
||||
trie2.update(cat, LONG_STRING);
|
||||
assertFalse("Expected tries not to be equal", trie1.cmp(trie2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrieSync() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
|
||||
trie.update(dog, LONG_STRING);
|
||||
assertEquals("Expected no data in database", mockDb.getAddedItems(), 0);
|
||||
|
||||
trie.sync();
|
||||
assertNotEquals("Expected data to be persisted", mockDb.getAddedItems(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestTrieDirtyTracking() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
trie.update(dog, LONG_STRING);
|
||||
assertTrue("Expected trie to be dirty", trie.getCache().isDirty());
|
||||
|
||||
trie.sync();
|
||||
assertFalse("Expected trie not to be dirty", trie.getCache().isDirty());
|
||||
|
||||
trie.update(test, LONG_STRING);
|
||||
trie.getCache().undo();
|
||||
assertFalse("Expected trie not to be dirty", trie.getCache().isDirty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestTrieReset() {
|
||||
Trie trie = new Trie(mockDb);
|
||||
|
||||
trie.update(cat, LONG_STRING);
|
||||
assertNotEquals("Expected cached nodes", 0, trie.getCache().getNodes().size());
|
||||
|
||||
trie.getCache().undo();
|
||||
|
||||
assertEquals("Expected no nodes after undo", 0, trie.getCache().getNodes().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrieCopy() {
|
||||
fail("To be implemented");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrieUndo() {
|
||||
fail("To be implemented");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package org.ethereum.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.ethereum.util.CompactEncoder;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CompactEncoderTest {
|
||||
|
||||
private final static byte T = 16; // terminator
|
||||
|
||||
@Test
|
||||
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));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package org.ethereum.net;
|
||||
package org.ethereum.util;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.ethereum.crypto.HashUtil;
|
||||
import org.ethereum.net.rlp.RLP;
|
||||
import org.ethereum.net.rlp.RLPList;
|
||||
import org.ethereum.util.Utils;
|
||||
import org.junit.Test;
|
|
@ -0,0 +1,425 @@
|
|||
package org.ethereum.util;
|
||||
|
||||
import static org.ethereum.util.RlpEncoder.toInt;
|
||||
import static org.ethereum.util.RlpTestData.expected14;
|
||||
import static org.ethereum.util.RlpTestData.expected16;
|
||||
import static org.ethereum.util.RlpTestData.result01;
|
||||
import static org.ethereum.util.RlpTestData.result02;
|
||||
import static org.ethereum.util.RlpTestData.result03;
|
||||
import static org.ethereum.util.RlpTestData.result04;
|
||||
import static org.ethereum.util.RlpTestData.result05;
|
||||
import static org.ethereum.util.RlpTestData.result06;
|
||||
import static org.ethereum.util.RlpTestData.result07;
|
||||
import static org.ethereum.util.RlpTestData.result08;
|
||||
import static org.ethereum.util.RlpTestData.result09;
|
||||
import static org.ethereum.util.RlpTestData.result10;
|
||||
import static org.ethereum.util.RlpTestData.result11;
|
||||
import static org.ethereum.util.RlpTestData.result12;
|
||||
import static org.ethereum.util.RlpTestData.result13;
|
||||
import static org.ethereum.util.RlpTestData.result14;
|
||||
import static org.ethereum.util.RlpTestData.result15;
|
||||
import static org.ethereum.util.RlpTestData.result16;
|
||||
import static org.ethereum.util.RlpTestData.test01;
|
||||
import static org.ethereum.util.RlpTestData.test02;
|
||||
import static org.ethereum.util.RlpTestData.test03;
|
||||
import static org.ethereum.util.RlpTestData.test04;
|
||||
import static org.ethereum.util.RlpTestData.test05;
|
||||
import static org.ethereum.util.RlpTestData.test06;
|
||||
import static org.ethereum.util.RlpTestData.test07;
|
||||
import static org.ethereum.util.RlpTestData.test08;
|
||||
import static org.ethereum.util.RlpTestData.test09;
|
||||
import static org.ethereum.util.RlpTestData.test10;
|
||||
import static org.ethereum.util.RlpTestData.test11;
|
||||
import static org.ethereum.util.RlpTestData.test12;
|
||||
import static org.ethereum.util.RlpTestData.test13;
|
||||
import static org.ethereum.util.RlpTestData.test14;
|
||||
import static org.ethereum.util.RlpTestData.test15;
|
||||
import static org.ethereum.util.RlpTestData.test16;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.cedarsoftware.util.DeepEquals;
|
||||
|
||||
public class RlpEncoderTest {
|
||||
|
||||
/************************************
|
||||
* Test data from: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP
|
||||
*
|
||||
* Using assertEquals(String, String) instead of assertArrayEquals to see the actual content when the test fails.
|
||||
*/
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testEncodeNull() {
|
||||
RlpEncoder.encode(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeEmptyString() {
|
||||
String test = "";
|
||||
String expected = "80";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals(test, bytesToAscii(decodeResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeShortString() {
|
||||
String test = "dog";
|
||||
String expected = "83646f67";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals(test, bytesToAscii(decodeResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSingleCharacter() {
|
||||
String test = "d";
|
||||
String expected = "64";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals(test, bytesToAscii(decodeResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeLongString() {
|
||||
String test = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"; // length = 56
|
||||
String expected = "b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals(test, bytesToAscii(decodeResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeZero() {
|
||||
Integer test = new Integer(0);
|
||||
String expected = "80";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
int result = toInt(decodeResult);
|
||||
assertEquals(test, Integer.valueOf(result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSmallInteger() {
|
||||
Integer test = new Integer(15);
|
||||
String expected = "0f";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
int result = toInt(decodeResult);
|
||||
assertEquals(test, Integer.valueOf(result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeMediumInteger() {
|
||||
Integer test = new Integer(1000);
|
||||
String expected = "8203e8";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
int result = toInt(decodeResult);
|
||||
assertEquals(test, Integer.valueOf(result));
|
||||
|
||||
test = new Integer(1024);
|
||||
expected = "820400";
|
||||
encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
result = toInt(decodeResult);
|
||||
assertEquals(test, Integer.valueOf(result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeBigInteger() {
|
||||
BigInteger test = new BigInteger("100102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 16);
|
||||
String expected = "a0100102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals(test, new BigInteger(decodeResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestEncodeEmptyList() {
|
||||
String[] test = new String[0];
|
||||
String expected = "c0";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertTrue(decodeResult.length == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeShortStringList() {
|
||||
String[] test = new String[] { "cat", "dog" };
|
||||
String expected = "c88363617483646f67";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals("cat", bytesToAscii((byte[]) decodeResult[0]));
|
||||
assertEquals("dog", bytesToAscii((byte[]) decodeResult[1]));
|
||||
|
||||
test = new String[] { "dog", "god", "cat" };
|
||||
expected = "cc83646f6783676f6483636174";
|
||||
encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals("dog", bytesToAscii((byte[]) decodeResult[0]));
|
||||
assertEquals("god", bytesToAscii((byte[]) decodeResult[1]));
|
||||
assertEquals("cat", bytesToAscii((byte[]) decodeResult[2]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeLongStringList() {
|
||||
String element1 = "cat";
|
||||
String element2 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
|
||||
String[] test = new String[] { element1, element2 };
|
||||
String expected = "f83e83636174b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974";
|
||||
byte[] encoderesult = (byte[]) RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals(element1, bytesToAscii((byte[]) decodeResult[0]));
|
||||
assertEquals(element2, bytesToAscii((byte[]) decodeResult[1]));
|
||||
}
|
||||
|
||||
//multilist:
|
||||
//in: [ 1, ["cat"], "dog", [ 2 ] ],
|
||||
//out: "cc01c48363617483646f67c102"
|
||||
//in: [ [ ["cat"], ["dog"] ], [ [1] [2] ], [] ],
|
||||
//out: "cdc88363617483646f67c20102c0"
|
||||
@Test
|
||||
public void testEncodeMultiList() {
|
||||
Object[] test = new Object[] { 1, new Object[] { "cat" }, "dog", new Object[] { 2 } };
|
||||
String expected = "cc01c48363617483646f67c102";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals(1, toInt( (byte[]) decodeResult[0] ));
|
||||
assertEquals("cat", bytesToAscii( ((byte[]) ((Object[]) decodeResult[1])[0] )));
|
||||
assertEquals("dog", bytesToAscii( (byte[]) decodeResult[2]));
|
||||
assertEquals(2, toInt( ((byte[]) ((Object[]) decodeResult[3])[0] )));
|
||||
|
||||
test = new Object[] { new Object[] { "cat", "dog" }, new Object[] { 1, 2 }, new Object[] { } };
|
||||
expected = "cdc88363617483646f67c20102c0";
|
||||
encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertEquals("cat", bytesToAscii( ((byte[]) ((Object[]) decodeResult[0])[0] )));
|
||||
assertEquals("dog", bytesToAscii( ((byte[]) ((Object[]) decodeResult[0])[1] )));
|
||||
assertEquals(1, toInt( ((byte[]) ((Object[]) decodeResult[1])[0] )));
|
||||
assertEquals(2, toInt( ((byte[]) ((Object[]) decodeResult[1])[1] )));
|
||||
assertTrue( ( ((Object[]) decodeResult[2]).length == 0 ));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeEmptyListOfList() {
|
||||
// list = [ [ [], [] ], [] ],
|
||||
Object[] test = new Object[] { new Object[] { new Object[] {}, new Object[] {} }, new Object[] {} };
|
||||
String expected = "c4c2c0c0c0";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertTrue( decodeResult.length == 2 );
|
||||
assertTrue( ( (Object[]) (decodeResult[0] ) ).length == 2);
|
||||
assertTrue( ( (Object[]) (decodeResult[1] ) ).length == 0);
|
||||
assertTrue( ( (Object[]) ( (Object[]) ( decodeResult[0] ) )[0]).length == 0);
|
||||
assertTrue( ( (Object[]) ( (Object[]) ( decodeResult[0] ) )[1]).length == 0);
|
||||
}
|
||||
|
||||
//The set theoretical representation of two
|
||||
@Test
|
||||
public void testEncodeRepOfTwoListOfList() {
|
||||
//list: [ [], [[]], [ [], [[]] ] ]
|
||||
Object[] test = new Object[] { new Object[] { }, new Object[] { new Object[] {} }, new Object[] { new Object[] {}, new Object[] { new Object[] { } } } };
|
||||
String expected = "c7c0c1c0c3c0c1c0";
|
||||
byte[] encoderesult = RlpEncoder.encode(test);
|
||||
assertEquals(expected, asHex(encoderesult));
|
||||
|
||||
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
|
||||
assertTrue( decodeResult.length == 3 );
|
||||
assertTrue( ( (Object[]) (decodeResult[0]) ).length == 0);
|
||||
assertTrue( ( (Object[]) (decodeResult[1]) ).length == 1);
|
||||
assertTrue( ( (Object[]) (decodeResult[2]) ).length == 2);
|
||||
assertTrue( ( (Object[]) ( (Object[]) (decodeResult[1]) )[0]).length == 0);
|
||||
assertTrue( ( (Object[]) ( (Object[]) (decodeResult[2]) )[0]).length == 0);
|
||||
assertTrue( ( (Object[]) ( (Object[]) (decodeResult[2]) )[1]).length == 1);
|
||||
assertTrue( ( (Object[]) ( (Object[]) ( (Object[]) (decodeResult[2]) )[1] )[0]).length == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRlpEncode() {
|
||||
|
||||
assertEquals(result01, asHex(RlpEncoder.encode(test01)));
|
||||
assertEquals(result02, asHex(RlpEncoder.encode(test02)));
|
||||
assertEquals(result03, asHex(RlpEncoder.encode(test03)));
|
||||
assertEquals(result04, asHex(RlpEncoder.encode(test04)));
|
||||
assertEquals(result05, asHex(RlpEncoder.encode(test05)));
|
||||
assertEquals(result06, asHex(RlpEncoder.encode(test06)));
|
||||
assertEquals(result07, asHex(RlpEncoder.encode(test07)));
|
||||
assertEquals(result08, asHex(RlpEncoder.encode(test08)));
|
||||
assertEquals(result09, asHex(RlpEncoder.encode(test09)));
|
||||
assertEquals(result10, asHex(RlpEncoder.encode(test10)));
|
||||
assertEquals(result11, asHex(RlpEncoder.encode(test11)));
|
||||
assertEquals(result12, asHex(RlpEncoder.encode(test12)));
|
||||
assertEquals(result13, asHex(RlpEncoder.encode(test13)));
|
||||
assertEquals(result14, asHex(RlpEncoder.encode(test14)));
|
||||
assertEquals(result15, asHex(RlpEncoder.encode(test15)));
|
||||
assertEquals(result16, asHex(RlpEncoder.encode(test16)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRlpDecode() {
|
||||
int pos = 0;
|
||||
byte[] decodedByte;
|
||||
byte[] decodedData;
|
||||
Object[] decodedList;
|
||||
|
||||
decodedByte = (byte[]) RlpEncoder.decode(fromHex(result01), pos).getDecoded();
|
||||
assertEquals(test01, toInt(decodedByte));
|
||||
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result02), pos).getDecoded();
|
||||
assertEquals(test02, bytesToAscii(decodedData));
|
||||
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result03), pos).getDecoded();
|
||||
assertEquals(test03, bytesToAscii(decodedData));
|
||||
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result04), pos).getDecoded();
|
||||
assertEquals(test04, bytesToAscii(decodedData));
|
||||
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result05), pos).getDecoded();
|
||||
assertEquals(test05, bytesToAscii(decodedData));
|
||||
|
||||
decodedList = (Object[]) RlpEncoder.decode(fromHex(result06), pos).getDecoded();
|
||||
assertEquals(test06[0], bytesToAscii((byte[]) decodedList[0]));
|
||||
assertEquals(test06[1], bytesToAscii((byte[]) decodedList[1]));
|
||||
|
||||
decodedList = (Object[]) RlpEncoder.decode(fromHex(result07), pos).getDecoded();
|
||||
assertEquals(test07[0], bytesToAscii((byte[]) decodedList[0]));
|
||||
assertEquals(test07[1], bytesToAscii((byte[]) decodedList[1]));
|
||||
assertEquals(test07[2], bytesToAscii((byte[]) decodedList[2]));
|
||||
|
||||
// 1
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result08), pos).getDecoded();
|
||||
assertEquals(test08, toInt(decodedData));
|
||||
|
||||
// 10
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result09), pos).getDecoded();
|
||||
assertEquals(test09, toInt(decodedData));
|
||||
|
||||
// 100
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result10), pos).getDecoded();
|
||||
assertEquals(test10, toInt(decodedData));
|
||||
|
||||
// 1000
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result11), pos).getDecoded();
|
||||
assertEquals(test11, toInt(decodedData));
|
||||
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result12), pos).getDecoded();
|
||||
assertTrue(test12.compareTo(new BigInteger(decodedData)) == 0);
|
||||
|
||||
decodedData = (byte[]) RlpEncoder.decode(fromHex(result13), pos).getDecoded();
|
||||
assertTrue(test13.compareTo(new BigInteger(decodedData)) == 0);
|
||||
|
||||
// Need to test with different expected value, because decoding doesn't recognize types
|
||||
Object testObject1 = RlpEncoder.decode(fromHex(result14), pos).getDecoded();
|
||||
assertTrue(DeepEquals.deepEquals(expected14, testObject1));
|
||||
|
||||
Object testObject2 = RlpEncoder.decode(fromHex(result15), pos).getDecoded();
|
||||
assertTrue(DeepEquals.deepEquals(test15, testObject2));
|
||||
|
||||
// Need to test with different expected value, because decoding doesn't recognize types
|
||||
Object testObject3 = RlpEncoder.decode(fromHex(result16), pos).getDecoded();
|
||||
assertTrue(DeepEquals.deepEquals(expected16, testObject3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeLength() {
|
||||
int length;
|
||||
int offset;
|
||||
byte[] encodedLength;
|
||||
String expected;
|
||||
|
||||
// length < 56
|
||||
length = 1; offset = 128;
|
||||
encodedLength = RlpEncoder.encodeLength(length, offset);
|
||||
expected = "81";
|
||||
assertEquals(expected, asHex(encodedLength));
|
||||
|
||||
// 56 > length < 2^64
|
||||
length = 56; offset = 192;
|
||||
encodedLength = RlpEncoder.encodeLength(length, offset);
|
||||
expected = "f838";
|
||||
assertEquals(expected, asHex(encodedLength));
|
||||
|
||||
// length > 2^64
|
||||
// TODO: Fix this test - when casting double to int, information gets lost since 'int' is max (2^31)-1
|
||||
double maxLength = Math.pow(256, 8); offset = 192;
|
||||
try {
|
||||
encodedLength = RlpEncoder.encodeLength( (int) maxLength, offset);
|
||||
System.out.println("length: " + length + ", offset: " + offset + ", encoded: " + Arrays.toString(encodedLength));
|
||||
fail("Expecting RuntimeException: 'Input too long'");
|
||||
} catch(RuntimeException e) {
|
||||
// Success!
|
||||
}
|
||||
}
|
||||
|
||||
// Code from: http://stackoverflow.com/a/9855338/459349
|
||||
protected final static char[] hexArray = "0123456789abcdef".toCharArray();
|
||||
private static String asHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for ( int j = 0; j < bytes.length; j++ ) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
// Code from: http://stackoverflow.com/a/4785776/459349
|
||||
private String bytesToAscii(byte[] b) {
|
||||
String hex = asHex(b);
|
||||
StringBuilder output = new StringBuilder();
|
||||
for (int i = 0; i < hex.length(); i+=2) {
|
||||
String str = hex.substring(i, i+2);
|
||||
output.append((char)Integer.parseInt(str, 16));
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
// Code from: http://stackoverflow.com/a/140861/459349
|
||||
public static byte[] fromHex(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package org.ethereum.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class RlpTestData {
|
||||
|
||||
/***********************************
|
||||
* https://github.com/ethereum/tests/blob/master/rlptest.txt
|
||||
*/
|
||||
public static int test01 = 0;
|
||||
public static String result01 = "80";
|
||||
|
||||
public static String test02 = "";
|
||||
public static String result02 = "80";
|
||||
|
||||
public static String test03 = "d";
|
||||
public static String result03 = "64";
|
||||
|
||||
public static String test04 = "cat";
|
||||
public static String result04 = "83636174";
|
||||
|
||||
public static String test05 = "dog";
|
||||
public static String result05 = "83646f67";
|
||||
|
||||
public static String[] test06 = new String[] { "cat", "dog" };
|
||||
public static String result06 = "c88363617483646f67";
|
||||
|
||||
public static String[] test07 = new String[] { "dog", "god", "cat" };
|
||||
public static String result07 = "cc83646f6783676f6483636174";
|
||||
|
||||
public static int test08 = 1;
|
||||
public static String result08 = "01";
|
||||
|
||||
public static int test09 = 10;
|
||||
public static String result09 = "0a";
|
||||
|
||||
public static int test10 = 100;
|
||||
public static String result10 = "64";
|
||||
|
||||
public static int test11 = 1000;
|
||||
public static String result11 = "8203e8";
|
||||
|
||||
public static BigInteger test12 = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639935");
|
||||
public static String result12 = "a100ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
|
||||
|
||||
public static BigInteger test13 = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639936");
|
||||
public static String result13 = "a1010000000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
public static Object[] test14 = new Object[] { 1, 2, new Object[] {} };
|
||||
public static String result14 = "c30102c0";
|
||||
public static Object[] expected14 = new Object[] { new byte[] { 1 }, new byte[] { 2 }, new Object[] {} };
|
||||
|
||||
public static Object[] test15 = new Object[] { new Object[] { new Object[] {}, new Object[] {} }, new Object[] {} };
|
||||
public static String result15 = "c4c2c0c0c0";
|
||||
|
||||
public static Object[] test16 = new Object[] { "zw", new Object[] { 4 }, "wz" };
|
||||
public static String result16 = "c8827a77c10482777a";
|
||||
public static Object[] expected16 = new Object[] { new byte[] { 122, 119 }, new Object[] { new byte[] { 4 } }, new byte[] { 119, 122 } };
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.ethereum.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ValueTest {
|
||||
|
||||
@Test
|
||||
public void testCmp() {
|
||||
Value val1 = new Value("hello");
|
||||
Value val2 = new Value("world");
|
||||
|
||||
assertFalse("Expected values not to be equal", val1.cmp(val2));
|
||||
|
||||
Value val3 = new Value("hello");
|
||||
Value val4 = new Value("hello");
|
||||
|
||||
assertTrue("Expected values to be equal", val3.cmp(val4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypes() {
|
||||
Value str = new Value("str");
|
||||
assertEquals(str.asString(), "str");
|
||||
|
||||
Value num = new Value(1);
|
||||
assertEquals(num.asInt(), 1);
|
||||
|
||||
Value inter = new Value(new Object[]{1});
|
||||
Object[] interExp = new Object[]{1};
|
||||
assertTrue(new Value(inter.asObj()).cmp(new Value(interExp)));
|
||||
|
||||
Value byt = new Value(new byte[]{1, 2, 3, 4});
|
||||
byte[] bytExp = new byte[]{1, 2, 3, 4};
|
||||
assertTrue(Arrays.equals(byt.asBytes(), bytExp));
|
||||
|
||||
Value bigInt = new Value(BigInteger.valueOf(10));
|
||||
BigInteger bigExp = BigInteger.valueOf(10);
|
||||
assertEquals(bigInt.asBigInt(), bigExp);
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue