Switch to consistent interface name for Trie

This commit is contained in:
nicksavers 2014-09-21 14:47:39 +02:00
parent 6c1de3e483
commit ce38ac5e89
20 changed files with 642 additions and 612 deletions

View File

@ -2,6 +2,7 @@ package org.ethereum.core;
import org.ethereum.crypto.HashUtil; import org.ethereum.crypto.HashUtil;
import org.ethereum.trie.Trie; import org.ethereum.trie.Trie;
import org.ethereum.trie.TrieImpl;
import org.ethereum.util.ByteUtil; import org.ethereum.util.ByteUtil;
import org.ethereum.util.RLP; import org.ethereum.util.RLP;
import org.ethereum.util.RLPElement; import org.ethereum.util.RLPElement;
@ -286,7 +287,7 @@ public class Block {
private void parseTxs(byte[] expectedRoot, RLPList txReceipts) { private void parseTxs(byte[] expectedRoot, RLPList txReceipts) {
this.txsState = new Trie(null); this.txsState = new TrieImpl(null);
for (int i = 0; i < txReceipts.size(); i++) { for (int i = 0; i < txReceipts.size(); i++) {
RLPElement rlpTxReceipt = txReceipts.get(i); RLPElement rlpTxReceipt = txReceipts.get(i);
RLPElement txData = ((RLPList)rlpTxReceipt).get(0); RLPElement txData = ((RLPList)rlpTxReceipt).get(0);

View File

@ -44,12 +44,10 @@ import static org.ethereum.core.Denomination.SZABO;
* </ol> * </ol>
* See <a href="https://github.com/ethereum/wiki/wiki/White-Paper#blockchain-and-mining">Ethereum Whitepaper</a> * See <a href="https://github.com/ethereum/wiki/wiki/White-Paper#blockchain-and-mining">Ethereum Whitepaper</a>
* *
*
* www.ethereumJ.com * www.ethereumJ.com
* @authors: Roman Mandeleil, * @authors: Roman Mandeleil,
* Nick Savers * Nick Savers
* Created on: 20/05/2014 10:44 * Created on: 20/05/2014 10:44
*
*/ */
public class BlockchainImpl implements Blockchain { public class BlockchainImpl implements Blockchain {
@ -214,7 +212,6 @@ public class BlockchainImpl implements Blockchain {
logger.info("*** Last block added [ #{} ]", block.getNumber()); logger.info("*** Last block added [ #{} ]", block.getNumber());
} }
/** /**
* Apply the transaction to the world state. * Apply the transaction to the world state.
* *

View File

@ -2,6 +2,7 @@ package org.ethereum.core;
import org.ethereum.crypto.HashUtil; import org.ethereum.crypto.HashUtil;
import org.ethereum.trie.Trie; import org.ethereum.trie.Trie;
import org.ethereum.trie.TrieImpl;
import org.ethereum.util.RLP; import org.ethereum.util.RLP;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -64,7 +65,7 @@ public class Genesis extends Block {
NUMBER, MIN_GAS_PRICE, GAS_LIMIT, GAS_USED, TIMESTAMP, NUMBER, MIN_GAS_PRICE, GAS_LIMIT, GAS_USED, TIMESTAMP,
EXTRA_DATA, NONCE, null, null); EXTRA_DATA, NONCE, null, null);
Trie state = new Trie(null); Trie state = new TrieImpl(null);
// The proof-of-concept series include a development premine, making the state root hash // The proof-of-concept series include a development premine, making the state root hash
// some value stateRoot. The latest documentation should be consulted for the value of the state root. // some value stateRoot. The latest documentation should be consulted for the value of the state root.
for (String address : premine) { for (String address : premine) {

View File

@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.ethereum.trie.Trie; import org.ethereum.trie.Trie;
import org.ethereum.trie.TrieImpl;
import org.ethereum.util.RLP; import org.ethereum.util.RLP;
import org.ethereum.util.RLPElement; import org.ethereum.util.RLPElement;
import org.ethereum.util.RLPItem; import org.ethereum.util.RLPItem;
@ -28,7 +29,7 @@ public class ContractDetails {
private byte[] code; private byte[] code;
private Trie storageTrie = new Trie(null); private Trie storageTrie = new TrieImpl(null);
public ContractDetails() { public ContractDetails() {
} }
@ -88,7 +89,7 @@ public class ContractDetails {
public byte[] getStorageHash() { public byte[] getStorageHash() {
storageTrie = new Trie(null); storageTrie = new TrieImpl(null);
// calc the trie for root hash // calc the trie for root hash
for (int i = 0; i < storageKeys.size(); ++i){ for (int i = 0; i < storageKeys.size(); ++i){
storageTrie.update(storageKeys.get(i).getData(), RLP storageTrie.update(storageKeys.get(i).getData(), RLP

View File

@ -12,8 +12,8 @@ import org.ethereum.json.JSONHelper;
import org.ethereum.listener.EthereumListener; import org.ethereum.listener.EthereumListener;
import org.ethereum.manager.WorldManager; import org.ethereum.manager.WorldManager;
import org.ethereum.trie.TrackTrie; import org.ethereum.trie.TrackTrie;
import org.ethereum.trie.TrieImpl;
import org.ethereum.trie.Trie; import org.ethereum.trie.Trie;
import org.ethereum.trie.TrieFacade;
import org.ethereum.util.ByteUtil; import org.ethereum.util.ByteUtil;
import org.ethereum.vm.DataWord; import org.ethereum.vm.DataWord;
import org.iq80.leveldb.DBIterator; import org.iq80.leveldb.DBIterator;
@ -84,7 +84,7 @@ public class RepositoryImpl implements Repository {
detailsDB = new DatabaseImpl(detailsDbName); detailsDB = new DatabaseImpl(detailsDbName);
contractDetailsDB = new TrackDatabase(detailsDB); contractDetailsDB = new TrackDatabase(detailsDB);
stateDB = new DatabaseImpl(stateDbName); stateDB = new DatabaseImpl(stateDbName);
worldState = new Trie(stateDB.getDb()); worldState = new TrieImpl(stateDB.getDb());
accountStateDB = new TrackTrie(worldState); accountStateDB = new TrackTrie(worldState);
} }
@ -210,7 +210,7 @@ public class RepositoryImpl implements Repository {
return state; return state;
} }
public TrieFacade getWorldState() { public Trie getWorldState() {
return worldState; return worldState;
} }

View File

@ -2,13 +2,6 @@ package org.ethereum.facade;
import org.ethereum.core.Block; import org.ethereum.core.Block;
/**
* www.ethereumJ.com
*
* @author: Roman Mandeleil
* Created on: 06/09/2014 08:31
*/
public interface Blockchain { public interface Blockchain {
public int getSize(); public int getSize();

View File

@ -27,7 +27,6 @@ public interface Ethereum {
*/ */
public PeerData findOnlinePeer(PeerData excludePeer) ; public PeerData findOnlinePeer(PeerData excludePeer) ;
/** /**
* Find an online peer but not from excluded list * Find an online peer but not from excluded list
* *

View File

@ -6,7 +6,7 @@ import org.ethereum.core.AccountState;
import org.ethereum.core.Block; import org.ethereum.core.Block;
import org.ethereum.core.BlockchainImpl; import org.ethereum.core.BlockchainImpl;
import org.ethereum.db.ContractDetails; import org.ethereum.db.ContractDetails;
import org.ethereum.trie.TrieFacade; import org.ethereum.trie.Trie;
import org.ethereum.vm.DataWord; import org.ethereum.vm.DataWord;
import org.iq80.leveldb.DBIterator; import org.iq80.leveldb.DBIterator;
@ -144,7 +144,7 @@ public interface Repository {
* *
* @return the <code>Trie</code> representing the entire current state * @return the <code>Trie</code> representing the entire current state
*/ */
public TrieFacade getWorldState(); public Trie getWorldState();
/** /**
* Load the blockchain into cache memory * Load the blockchain into cache memory

View File

@ -12,7 +12,7 @@ import java.util.Set;
* Created on: 29/08/2014 10:46 * Created on: 29/08/2014 10:46
*/ */
public class CollectFullSetOfNodes implements Trie.ScanAction { public class CollectFullSetOfNodes implements TrieImpl.ScanAction {
Set<byte[]> nodes = new HashSet<>(); Set<byte[]> nodes = new HashSet<>();
@Override @Override

View File

@ -9,7 +9,7 @@ import org.ethereum.util.Value;
* Created on: 29/08/2014 10:46 * Created on: 29/08/2014 10:46
*/ */
public class CountAllNodes implements Trie.ScanAction { public class CountAllNodes implements TrieImpl.ScanAction {
int counted = 0; int counted = 0;

View File

@ -10,7 +10,7 @@ import org.spongycastle.util.encoders.Hex;
* Created on: 29/08/2014 10:46 * Created on: 29/08/2014 10:46
*/ */
public class TraceAllNodes implements Trie.ScanAction { public class TraceAllNodes implements TrieImpl.ScanAction {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();

View File

@ -9,20 +9,23 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* www.ethereumJ.com * The TrackTrie is a wrapper around and actual Modified Merkle Patricia Trie
* to keep track of changes which can be rolled back or committed down into
* the original trie after successful execution of a transaction.
* *
* www.ethereumJ.com
* @author: Roman Mandeleil * @author: Roman Mandeleil
* Created on: 11/06/2014 19:47 * Created on: 11/06/2014 19:47
*/ */
public class TrackTrie implements TrieFacade { public class TrackTrie implements Trie {
private TrieFacade trie; private Trie trie;
private boolean trackingChanges = false; private boolean trackingChanges = false;
private Map<ByteArrayWrapper, byte[]> changes; private Map<ByteArrayWrapper, byte[]> changes;
private List<ByteArrayWrapper> deletes; private List<ByteArrayWrapper> deletes;
public TrackTrie(TrieFacade trie) { public TrackTrie(Trie trie) {
this.trie = trie; this.trie = trie;
} }
@ -33,12 +36,10 @@ public class TrackTrie implements TrieFacade {
} }
public void commitTrack() { public void commitTrack() {
for (ByteArrayWrapper key : changes.keySet()) { for (ByteArrayWrapper key : changes.keySet())
trie.update(key.getData(), changes.get(key)); trie.update(key.getData(), changes.get(key));
} for (ByteArrayWrapper key : deletes)
for (ByteArrayWrapper key : deletes) {
trie.update(key.getData(), ByteUtil.EMPTY_BYTE_ARRAY); trie.update(key.getData(), ByteUtil.EMPTY_BYTE_ARRAY);
}
changes = null; changes = null;
trackingChanges = false; trackingChanges = false;
} }
@ -52,12 +53,11 @@ public class TrackTrie implements TrieFacade {
@Override @Override
public void update(byte[] key, byte[] value) { public void update(byte[] key, byte[] value) {
if (trackingChanges) { if (trackingChanges)
changes.put(new ByteArrayWrapper(key), value); changes.put(new ByteArrayWrapper(key), value);
} else { else
trie.update(key, value); trie.update(key, value);
} }
}
@Override @Override
public byte[] get(byte[] key) { public byte[] get(byte[] key) {
@ -77,10 +77,9 @@ public class TrackTrie implements TrieFacade {
if (trackingChanges) { if (trackingChanges) {
ByteArrayWrapper wKey = new ByteArrayWrapper(key); ByteArrayWrapper wKey = new ByteArrayWrapper(key);
deletes.add(wKey); deletes.add(wKey);
} else { } else
trie.delete(key); trie.delete(key);
} }
}
@Override @Override
public byte[] getRootHash() { public byte[] getRootHash() {
@ -91,4 +90,14 @@ public class TrackTrie implements TrieFacade {
public String getTrieDump() { public String getTrieDump() {
return trie.getTrieDump(); return trie.getTrieDump();
} }
@Override
public void setRoot(byte[] root) {
trie.setRoot(root);
}
@Override
public void sync() {
trie.sync();
}
} }

View File

@ -1,517 +1,52 @@
package org.ethereum.trie; package org.ethereum.trie;
import static java.util.Arrays.copyOfRange; /**
import static org.ethereum.util.ByteUtil.matchingNibbleLength; * Trie interface for the main data structure in Ethereum
import static org.ethereum.util.CompactEncoder.binToNibbles; * which is used to store both the account state and storage of each account.
import static org.ethereum.util.CompactEncoder.packNibbles; */
import static org.ethereum.util.CompactEncoder.unpackToNibbles; public interface Trie {
import static org.spongycastle.util.Arrays.concatenate;
import java.util.*;
import org.ethereum.crypto.HashUtil;
import org.ethereum.db.ByteArrayWrapper;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.Value;
import org.iq80.leveldb.DB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
/** /**
* The modified Merkle Patricia tree (trie) provides a persistent data structure * Adds or updates a value to the trie for the specified key
* 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. * @param key - any length byte array
* If a node is not present in the cache it will try to fetch it from the database and * @param value - an rlp encoded byte representation of the object to store
* store the cached value.
*
* Please note that the data isn't persisted unless `sync` is explicitly called.
*
* www.ethereumJ.com
* @author: Nick Savers
* Created on: 20/05/2014 10:44
*/ */
public class Trie implements TrieFacade { public void update(byte[] key, byte[] value);
private Logger logger = LoggerFactory.getLogger("trie");
private static byte PAIR_SIZE = 2;
private static byte LIST_SIZE = 17;
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(Object root) {
this.root = root;
}
public void setCache(Cache cache) {
this.cache = cache;
}
/**************************************
* Public (query) interface functions *
**************************************/
/** /**
* Insert key/value pair into trie * Gets a value from the trie for a given key
* *
* @param key * @param key - any length byte array
* @param value * @return value - an rlp encoded byte array of the stored object
*/ */
public void update(String key, String value) { public byte[] get(byte[] key);
this.update(key.getBytes(), value.getBytes());
}
/** /**
* Insert key/value pair into trie * Deletes a value from the trie for a given key
* *
* @param key * @param key - any length byte array
* @param value
*/ */
public void update(byte[] key, byte[] value) { public void delete(byte[] key);
if (key == null)
throw new NullPointerException("Key should not be blank");
byte[] k = binToNibbles(key);
this.root = this.insertOrDelete(this.root, k, value);
if(logger.isDebugEnabled()) {
logger.debug("Added key {} and value {}", Hex.toHexString(key), Hex.toHexString(value));
logger.debug("New root-hash: {}", Hex.toHexString(this.getRootHash()));
}
}
/** /**
* Retrieve a value from a node * Returns a SHA-3 hash from the top node of the trie
* *
* @param key * @return 32-byte SHA-3 hash representing the entire contents of the trie.
* @return value
*/ */
public byte[] get(String key) { public byte[] getRootHash();
return this.get(key.getBytes());
}
/** /**
* Retrieve a value from a node * Set the top node of the trie
* *
* @param key * @param root - 32-byte SHA-3 hash of the root node
* @return value
*/ */
public byte[] get(byte[] key) { public void setRoot(byte[] root);
if(logger.isDebugEnabled()) {
logger.debug("Retrieving key {}", Hex.toHexString(key));
}
byte[] k = binToNibbles(key);
Value c = new Value( this.get(this.root, k) );
return (c == null)? null : c.asBytes();
}
/** /**
* Delete a key/value pair from the trie * Commit all the changes until now
*
* @param key
*/ */
public void delete(byte[] key) { public void sync();
delete(new String(key));
if(logger.isDebugEnabled()) { public String getTrieDump();
logger.debug("Deleted value for key {}", Hex.toHexString(key));
logger.debug("New root-hash: {}", Hex.toHexString(this.getRootHash()));
}
}
/**
* Delete a key/value pair from the trie
*
* @param key
*/
public void delete(String key) {
this.update(key.getBytes(), "".getBytes());
}
/****************************************
* 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);
if (currentNode == null) return null;
if (currentNode.length() == PAIR_SIZE) {
// Decode the key
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))) {
return this.get(v, copyOfRange(key, k.length, key.length));
} else {
return "";
}
} else {
return this.get(currentNode.get(key[0]).asObj(), copyOfRange(key, 1, key.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[] { packNibbles(key), value };
return this.putToCache(newNode);
}
Value currentNode = this.getNode(node);
// Check for "special" 2 slice type node
if (currentNode.length() == PAIR_SIZE) {
// Decode the key
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[] {packNibbles(key), value};
return this.putToCache(newNode);
}
Object newHash;
int matchingLength = matchingNibbleLength(key, k);
if (matchingLength == k.length) {
// Insert the hash, creating a new node
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 putToCache 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
Object[] scaledSlice = emptyStringSlice(17);
// Set the copied and new node
scaledSlice[k[matchingLength]] = oldNode;
scaledSlice[key[matchingLength]] = newNode;
newHash = this.putToCache(scaledSlice);
}
if (matchingLength == 0) {
// End of the chain, return
return newHash;
} else {
Object[] newNode = new Object[] { packNibbles(copyOfRange(key, 0, matchingLength)), newHash};
return this.putToCache(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.putToCache(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 = 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)) {
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, unpackToNibbles(child.get(0).asBytes()));
newNode = new Object[] {packNibbles(newKey), child.get(1).asObj()};
} else {
newNode = new Object[] {currentNode.get(0).asString(), hash};
}
return this.putToCache(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[] { 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}, unpackToNibbles(child.get(0).asBytes()));
newNode = new Object[] {packNibbles(key), child.get(1).asObj()};
} else if (child.length() == LIST_SIZE) {
newNode = new Object[] { packNibbles(new byte[]{amount}), itemList[amount]};
}
} else {
newNode = itemList;
}
return this.putToCache(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 val = new Value(node);
// in that case we got a node
// so no need to encode it
if (!val.isBytes()) {
return val;
}
byte[] keyBytes = val.asBytes();
if (keyBytes.length == 0) {
return val;
} else if (keyBytes.length < 32) {
return new Value(keyBytes);
}
return this.cache.get(keyBytes);
}
private Object putToCache(Object node) {
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 compared the tries based on their stateRoot
public boolean cmp(Trie trie) {
return Arrays.equals(this.getRootHash(), trie.getRootHash());
}
// 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 (ByteArrayWrapper key : this.cache.getNodes().keySet()) {
Node node = this.cache.getNodes().get(key);
trie.cache.getNodes().put(key, node.copy());
}
return trie;
}
/********************************
* Utility functions *
*******************************/
// Created an array of empty elements of required length
private Object[] emptyStringSlice(int l) {
Object[] slice = new Object[l];
for (int i = 0; i < l; i++) {
slice[i] = "";
}
return slice;
}
public byte[] getRootHash() {
if (root == null
|| (root instanceof byte[] && ((byte[]) root).length == 0)
|| (root instanceof String && "".equals((String) root))) {
return ByteUtil.EMPTY_BYTE_ARRAY;
} else if (root instanceof byte[]) {
return (byte[]) this.getRoot();
} else {
Value rootValue = new Value(this.getRoot());
byte[] val = rootValue.encode();
return HashUtil.sha3(val);
}
}
/*
* insert/delete operations on a Trie structure
* leaves unnecessary nodes, this method scans the
* cache and removes them. The method is not thread
* safe, the tree should not be modified during the
* cleaning process.
*/
public void cleanCacheGarbage() {
CollectFullSetOfNodes collectAction = new CollectFullSetOfNodes();
long startTime = System.currentTimeMillis();
this.scanTree(this.getRootHash(), collectAction);
Set<byte[]> hashSet = collectAction.getCollectedHashes();
Map<ByteArrayWrapper, Node> nodes = this.getCache().getNodes();
Set<ByteArrayWrapper> toRemoveSet = new HashSet<>();
for (ByteArrayWrapper key : nodes.keySet()) {
if (!hashSet.contains(key.getData())) {
toRemoveSet.add(key);
}
}
for (ByteArrayWrapper key : toRemoveSet) {
this.getCache().delete(key.getData());
if (logger.isTraceEnabled())
logger.trace("Garbage collected node: [ {} ]",
Hex.toHexString( key.getData() ));
}
logger.info("Garbage collected node list, size: [ {} ]", toRemoveSet.size());
logger.info("Garbage collection time: [ {}ms ]", System.currentTimeMillis() - startTime);
}
public void scanTree(byte[] hash, ScanAction scanAction) {
Value node = this.getCache().get(hash);
if (node == null) return;
if (node.isList()) {
List<Object> siblings = node.asList();
if (siblings.size() == PAIR_SIZE) {
Value val = new Value(siblings.get(1));
if (val.isHashCode())
scanTree(val.asBytes(), scanAction);
} else {
for (int j = 0; j < LIST_SIZE; ++j) {
Value val = new Value(siblings.get(j));
if (val.isHashCode())
scanTree(val.asBytes(), scanAction);
}
}
scanAction.doOnNode(hash, node);
}
}
public String getTrieDump() {
String root = "";
TraceAllNodes traceAction = new TraceAllNodes();
this.scanTree(this.getRootHash(), traceAction);
if (this.getRoot() instanceof Value) {
root = "root: " + Hex.toHexString(getRootHash()) + " => " + this.getRoot() + "\n";
} else {
root = "root: " + Hex.toHexString(getRootHash()) + "\n";
}
return root + traceAction.getOutput();
}
public interface ScanAction {
public void doOnNode(byte[] hash, Value node);
}
} }

View File

@ -1,18 +0,0 @@
package org.ethereum.trie;
/**
* www.ethereumJ.com
*
* @author: Roman Mandeleil
* Created on: 11/06/2014 19:44
*/
public interface TrieFacade {
public void update(byte[] key, byte[] value);
public byte[] get(byte[] key);
public void delete(byte[] key);
public byte[] getRootHash();
public String getTrieDump();
}

View File

@ -0,0 +1,517 @@
package org.ethereum.trie;
import static java.util.Arrays.copyOfRange;
import static org.ethereum.util.ByteUtil.matchingNibbleLength;
import static org.ethereum.util.CompactEncoder.binToNibbles;
import static org.ethereum.util.CompactEncoder.packNibbles;
import static org.ethereum.util.CompactEncoder.unpackToNibbles;
import static org.spongycastle.util.Arrays.concatenate;
import java.util.*;
import org.ethereum.crypto.HashUtil;
import org.ethereum.db.ByteArrayWrapper;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.Value;
import org.iq80.leveldb.DB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
/**
* 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.
*
* <b>Note:</b> the data isn't persisted unless `sync` is explicitly called.
*
* www.ethereumJ.com
* @author: Nick Savers
* Created on: 20/05/2014 10:44
*/
public class TrieImpl implements Trie {
private Logger logger = LoggerFactory.getLogger("trie");
private static byte PAIR_SIZE = 2;
private static byte LIST_SIZE = 17;
private Object prevRoot;
private Object root;
private Cache cache;
public TrieImpl(DB db) {
this(db, "");
}
public TrieImpl(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(byte[] 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) {
this.update(key.getBytes(), value.getBytes());
}
/**
* Insert key/value pair into trie
*
* @param key
* @param value
*/
public void update(byte[] key, byte[] value) {
if (key == null)
throw new NullPointerException("Key should not be blank");
byte[] k = binToNibbles(key);
this.root = this.insertOrDelete(this.root, k, value);
if(logger.isDebugEnabled()) {
logger.debug("Added key {} and value {}", Hex.toHexString(key), Hex.toHexString(value));
logger.debug("New root-hash: {}", Hex.toHexString(this.getRootHash()));
}
}
/**
* Retrieve a value from a node
*
* @param key
* @return value
*/
public byte[] get(String key) {
return this.get(key.getBytes());
}
/**
* Retrieve a value from a node
*
* @param key
* @return value
*/
public byte[] get(byte[] key) {
if(logger.isDebugEnabled()) {
logger.debug("Retrieving key {}", Hex.toHexString(key));
}
byte[] k = binToNibbles(key);
Value c = new Value( this.get(this.root, k) );
return (c == null)? null : c.asBytes();
}
/**
* Delete a key/value pair from the trie
*
* @param key
*/
public void delete(byte[] key) {
delete(new String(key));
if(logger.isDebugEnabled()) {
logger.debug("Deleted value for key {}", Hex.toHexString(key));
logger.debug("New root-hash: {}", Hex.toHexString(this.getRootHash()));
}
}
/**
* Delete a key/value pair from the trie
*
* @param key
*/
public void delete(String key) {
this.update(key.getBytes(), "".getBytes());
}
/****************************************
* 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);
if (currentNode == null) return null;
if (currentNode.length() == PAIR_SIZE) {
// Decode the key
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))) {
return this.get(v, copyOfRange(key, k.length, key.length));
} else {
return "";
}
} else {
return this.get(currentNode.get(key[0]).asObj(), copyOfRange(key, 1, key.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[] { packNibbles(key), value };
return this.putToCache(newNode);
}
Value currentNode = this.getNode(node);
// Check for "special" 2 slice type node
if (currentNode.length() == PAIR_SIZE) {
// Decode the key
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[] {packNibbles(key), value};
return this.putToCache(newNode);
}
Object newHash;
int matchingLength = matchingNibbleLength(key, k);
if (matchingLength == k.length) {
// Insert the hash, creating a new node
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 putToCache 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
Object[] scaledSlice = emptyStringSlice(17);
// Set the copied and new node
scaledSlice[k[matchingLength]] = oldNode;
scaledSlice[key[matchingLength]] = newNode;
newHash = this.putToCache(scaledSlice);
}
if (matchingLength == 0) {
// End of the chain, return
return newHash;
} else {
Object[] newNode = new Object[] { packNibbles(copyOfRange(key, 0, matchingLength)), newHash};
return this.putToCache(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.putToCache(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 = 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)) {
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, unpackToNibbles(child.get(0).asBytes()));
newNode = new Object[] {packNibbles(newKey), child.get(1).asObj()};
} else {
newNode = new Object[] {currentNode.get(0).asString(), hash};
}
return this.putToCache(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[] { 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}, unpackToNibbles(child.get(0).asBytes()));
newNode = new Object[] {packNibbles(key), child.get(1).asObj()};
} else if (child.length() == LIST_SIZE) {
newNode = new Object[] { packNibbles(new byte[]{amount}), itemList[amount]};
}
} else {
newNode = itemList;
}
return this.putToCache(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 val = new Value(node);
// in that case we got a node
// so no need to encode it
if (!val.isBytes()) {
return val;
}
byte[] keyBytes = val.asBytes();
if (keyBytes.length == 0) {
return val;
} else if (keyBytes.length < 32) {
return new Value(keyBytes);
}
return this.cache.get(keyBytes);
}
private Object putToCache(Object node) {
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 compared the tries based on their stateRoot
public boolean cmp(TrieImpl trie) {
return Arrays.equals(this.getRootHash(), trie.getRootHash());
}
// 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 TrieImpl copy() {
TrieImpl trie = new TrieImpl(this.cache.getDb(), this.root);
for (ByteArrayWrapper key : this.cache.getNodes().keySet()) {
Node node = this.cache.getNodes().get(key);
trie.cache.getNodes().put(key, node.copy());
}
return trie;
}
/********************************
* Utility functions *
*******************************/
// Created an array of empty elements of required length
private Object[] emptyStringSlice(int l) {
Object[] slice = new Object[l];
for (int i = 0; i < l; i++) {
slice[i] = "";
}
return slice;
}
public byte[] getRootHash() {
if (root == null
|| (root instanceof byte[] && ((byte[]) root).length == 0)
|| (root instanceof String && "".equals((String) root))) {
return ByteUtil.EMPTY_BYTE_ARRAY;
} else if (root instanceof byte[]) {
return (byte[]) this.getRoot();
} else {
Value rootValue = new Value(this.getRoot());
byte[] val = rootValue.encode();
return HashUtil.sha3(val);
}
}
/*
* insert/delete operations on a Trie structure
* leaves unnecessary nodes, this method scans the
* cache and removes them. The method is not thread
* safe, the tree should not be modified during the
* cleaning process.
*/
public void cleanCacheGarbage() {
CollectFullSetOfNodes collectAction = new CollectFullSetOfNodes();
long startTime = System.currentTimeMillis();
this.scanTree(this.getRootHash(), collectAction);
Set<byte[]> hashSet = collectAction.getCollectedHashes();
Map<ByteArrayWrapper, Node> nodes = this.getCache().getNodes();
Set<ByteArrayWrapper> toRemoveSet = new HashSet<>();
for (ByteArrayWrapper key : nodes.keySet()) {
if (!hashSet.contains(key.getData())) {
toRemoveSet.add(key);
}
}
for (ByteArrayWrapper key : toRemoveSet) {
this.getCache().delete(key.getData());
if (logger.isTraceEnabled())
logger.trace("Garbage collected node: [ {} ]",
Hex.toHexString( key.getData() ));
}
logger.info("Garbage collected node list, size: [ {} ]", toRemoveSet.size());
logger.info("Garbage collection time: [ {}ms ]", System.currentTimeMillis() - startTime);
}
public void scanTree(byte[] hash, ScanAction scanAction) {
Value node = this.getCache().get(hash);
if (node == null) return;
if (node.isList()) {
List<Object> siblings = node.asList();
if (siblings.size() == PAIR_SIZE) {
Value val = new Value(siblings.get(1));
if (val.isHashCode())
scanTree(val.asBytes(), scanAction);
} else {
for (int j = 0; j < LIST_SIZE; ++j) {
Value val = new Value(siblings.get(j));
if (val.isHashCode())
scanTree(val.asBytes(), scanAction);
}
}
scanAction.doOnNode(hash, node);
}
}
public String getTrieDump() {
String root = "";
TraceAllNodes traceAction = new TraceAllNodes();
this.scanTree(this.getRootHash(), traceAction);
if (this.getRoot() instanceof Value) {
root = "root: " + Hex.toHexString(getRootHash()) + " => " + this.getRoot() + "\n";
} else {
root = "root: " + Hex.toHexString(getRootHash()) + "\n";
}
return root + traceAction.getOutput();
}
public interface ScanAction {
public void doOnNode(byte[] hash, Value node);
}
}

View File

@ -12,14 +12,14 @@ import static org.ethereum.util.CompactEncoder.unpackToNibbles;
*/ */
public class TrieIterator { public class TrieIterator {
private Trie trie; private TrieImpl trie;
private String key; private String key;
private String value; private String value;
private List<byte[]> shas; private List<byte[]> shas;
private List<String> values; private List<String> values;
public TrieIterator(Trie t) { public TrieIterator(TrieImpl t) {
this.trie = t; this.trie = t;
} }

View File

@ -4,6 +4,7 @@ import java.io.Serializable;
import org.spongycastle.util.encoders.Hex; import org.spongycastle.util.encoders.Hex;
@SuppressWarnings("serial")
public class DecodeResult implements Serializable { public class DecodeResult implements Serializable {
private int pos; private int pos;

View File

@ -7,6 +7,7 @@ import java.math.BigInteger;
import org.ethereum.crypto.HashUtil; import org.ethereum.crypto.HashUtil;
import org.ethereum.db.MockDB; import org.ethereum.db.MockDB;
import org.ethereum.trie.Trie; import org.ethereum.trie.Trie;
import org.ethereum.trie.TrieImpl;
import org.ethereum.util.RLP; import org.ethereum.util.RLP;
import org.junit.Test; import org.junit.Test;
import org.spongycastle.util.encoders.Hex; import org.spongycastle.util.encoders.Hex;
@ -42,7 +43,7 @@ public class StateTest {
TransactionReceipt tr = new TransactionReceipt(tx, postTxState, cumGas); TransactionReceipt tr = new TransactionReceipt(tx, postTxState, cumGas);
Trie trie = new Trie(new MockDB()); Trie trie = new TrieImpl(new MockDB());
trie.update(RLP.encodeInt(0), tr.getEncoded()); trie.update(RLP.encodeInt(0), tr.getEncoded());
String txTrieRoot = Hex.toHexString(trie.getRootHash()); String txTrieRoot = Hex.toHexString(trie.getRootHash());
assertEquals(expected, txTrieRoot); assertEquals(expected, txTrieRoot);
@ -160,7 +161,7 @@ public class StateTest {
private Trie generateGenesisState() { private Trie generateGenesisState() {
Trie trie = new Trie(new MockDB()); Trie trie = new TrieImpl(new MockDB());
for (String address : Genesis.getPremine()) { for (String address : Genesis.getPremine()) {
AccountState acct = new AccountState(BigInteger.ZERO, BigInteger.valueOf(2).pow(200)); AccountState acct = new AccountState(BigInteger.ZERO, BigInteger.valueOf(2).pow(200));
trie.update(Hex.decode(address), acct.getEncoded()); trie.update(Hex.decode(address), acct.getEncoded());

View File

@ -59,7 +59,7 @@ public class TrieTest {
@Test @Test
public void testEmptyKey() { public void testEmptyKey() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("", dog); trie.update("", dog);
assertEquals(dog, new String(trie.get(""))); assertEquals(dog, new String(trie.get("")));
@ -67,7 +67,7 @@ public class TrieTest {
@Test @Test
public void testInsertShortString() { public void testInsertShortString() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, new String(trie.get(cat))); assertEquals(dog, new String(trie.get(cat)));
@ -75,7 +75,7 @@ public class TrieTest {
@Test @Test
public void testInsertLongString() { public void testInsertLongString() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, new String(trie.get(cat))); assertEquals(LONG_STRING, new String(trie.get(cat)));
@ -83,7 +83,7 @@ public class TrieTest {
@Test @Test
public void testInsertMultipleItems1() { public void testInsertMultipleItems1() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(ca, dude); trie.update(ca, dude);
assertEquals(dude, new String(trie.get(ca))); assertEquals(dude, new String(trie.get(ca)));
@ -109,7 +109,7 @@ public class TrieTest {
@Test @Test
public void testInsertMultipleItems2() { public void testInsertMultipleItems2() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, new String(trie.get(cat))); assertEquals(dog, new String(trie.get(cat)));
@ -136,7 +136,7 @@ public class TrieTest {
@Test @Test
public void testUpdateShortToShortString() { public void testUpdateShortToShortString() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, new String(trie.get(cat))); assertEquals(dog, new String(trie.get(cat)));
@ -147,7 +147,7 @@ public class TrieTest {
@Test @Test
public void testUpdateLongToLongString() { public void testUpdateLongToLongString() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, new String(trie.get(cat))); assertEquals(LONG_STRING, new String(trie.get(cat)));
trie.update(cat, LONG_STRING+"1"); trie.update(cat, LONG_STRING+"1");
@ -156,7 +156,7 @@ public class TrieTest {
@Test @Test
public void testUpdateShortToLongString() { public void testUpdateShortToLongString() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, new String(trie.get(cat))); assertEquals(dog, new String(trie.get(cat)));
@ -167,7 +167,7 @@ public class TrieTest {
@Test @Test
public void testUpdateLongToShortString() { public void testUpdateLongToShortString() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, new String(trie.get(cat))); assertEquals(LONG_STRING, new String(trie.get(cat)));
@ -180,7 +180,7 @@ public class TrieTest {
public void testDeleteShortString1() { public void testDeleteShortString1() {
String ROOT_HASH_BEFORE = "a9539c810cc2e8fa20785bdd78ec36cc1dab4b41f0d531e80a5e5fd25c3037ee"; String ROOT_HASH_BEFORE = "a9539c810cc2e8fa20785bdd78ec36cc1dab4b41f0d531e80a5e5fd25c3037ee";
String ROOT_HASH_AFTER = "fc5120b4a711bca1f5bb54769525b11b3fb9a8d6ac0b8bf08cbb248770521758"; String ROOT_HASH_AFTER = "fc5120b4a711bca1f5bb54769525b11b3fb9a8d6ac0b8bf08cbb248770521758";
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, new String(trie.get(cat))); assertEquals(dog, new String(trie.get(cat)));
@ -198,7 +198,7 @@ public class TrieTest {
public void testDeleteShortString2() { public void testDeleteShortString2() {
String ROOT_HASH_BEFORE = "a9539c810cc2e8fa20785bdd78ec36cc1dab4b41f0d531e80a5e5fd25c3037ee"; String ROOT_HASH_BEFORE = "a9539c810cc2e8fa20785bdd78ec36cc1dab4b41f0d531e80a5e5fd25c3037ee";
String ROOT_HASH_AFTER = "b25e1b5be78dbadf6c4e817c6d170bbb47e9916f8f6cc4607c5f3819ce98497b"; String ROOT_HASH_AFTER = "b25e1b5be78dbadf6c4e817c6d170bbb47e9916f8f6cc4607c5f3819ce98497b";
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(ca, dude); trie.update(ca, dude);
assertEquals(dude, new String(trie.get(ca))); assertEquals(dude, new String(trie.get(ca)));
@ -216,7 +216,7 @@ public class TrieTest {
public void testDeleteShortString3() { public void testDeleteShortString3() {
String ROOT_HASH_BEFORE = "778ab82a7e8236ea2ff7bb9cfa46688e7241c1fd445bf2941416881a6ee192eb"; String ROOT_HASH_BEFORE = "778ab82a7e8236ea2ff7bb9cfa46688e7241c1fd445bf2941416881a6ee192eb";
String ROOT_HASH_AFTER = "05875807b8f3e735188d2479add82f96dee4db5aff00dc63f07a7e27d0deab65"; String ROOT_HASH_AFTER = "05875807b8f3e735188d2479add82f96dee4db5aff00dc63f07a7e27d0deab65";
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, dude); trie.update(cat, dude);
assertEquals(dude, new String(trie.get(cat))); assertEquals(dude, new String(trie.get(cat)));
@ -234,7 +234,7 @@ public class TrieTest {
public void testDeleteLongString1() { public void testDeleteLongString1() {
String ROOT_HASH_BEFORE = "318961a1c8f3724286e8e80d312352f01450bc4892c165cc7614e1c2e5a0012a"; String ROOT_HASH_BEFORE = "318961a1c8f3724286e8e80d312352f01450bc4892c165cc7614e1c2e5a0012a";
String ROOT_HASH_AFTER = "63356ecf33b083e244122fca7a9b128cc7620d438d5d62e4f8b5168f1fb0527b"; String ROOT_HASH_AFTER = "63356ecf33b083e244122fca7a9b128cc7620d438d5d62e4f8b5168f1fb0527b";
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, new String(trie.get(cat))); assertEquals(LONG_STRING, new String(trie.get(cat)));
@ -252,7 +252,7 @@ public class TrieTest {
public void testDeleteLongString2() { public void testDeleteLongString2() {
String ROOT_HASH_BEFORE = "e020de34ca26f8d373ff2c0a8ac3a4cb9032bfa7a194c68330b7ac3584a1d388"; String ROOT_HASH_BEFORE = "e020de34ca26f8d373ff2c0a8ac3a4cb9032bfa7a194c68330b7ac3584a1d388";
String ROOT_HASH_AFTER = "334511f0c4897677b782d13a6fa1e58e18de6b24879d57ced430bad5ac831cb2"; String ROOT_HASH_AFTER = "334511f0c4897677b782d13a6fa1e58e18de6b24879d57ced430bad5ac831cb2";
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(ca, LONG_STRING); trie.update(ca, LONG_STRING);
assertEquals(LONG_STRING, new String(trie.get(ca))); assertEquals(LONG_STRING, new String(trie.get(ca)));
@ -270,7 +270,7 @@ public class TrieTest {
public void testDeleteLongString3() { public void testDeleteLongString3() {
String ROOT_HASH_BEFORE = "e020de34ca26f8d373ff2c0a8ac3a4cb9032bfa7a194c68330b7ac3584a1d388"; String ROOT_HASH_BEFORE = "e020de34ca26f8d373ff2c0a8ac3a4cb9032bfa7a194c68330b7ac3584a1d388";
String ROOT_HASH_AFTER = "63356ecf33b083e244122fca7a9b128cc7620d438d5d62e4f8b5168f1fb0527b"; String ROOT_HASH_AFTER = "63356ecf33b083e244122fca7a9b128cc7620d438d5d62e4f8b5168f1fb0527b";
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertEquals(LONG_STRING, new String(trie.get(cat))); assertEquals(LONG_STRING, new String(trie.get(cat)));
@ -289,7 +289,7 @@ public class TrieTest {
String ROOT_HASH_BEFORE = "3a784eddf1936515f0313b073f99e3bd65c38689021d24855f62a9601ea41717"; String ROOT_HASH_BEFORE = "3a784eddf1936515f0313b073f99e3bd65c38689021d24855f62a9601ea41717";
String ROOT_HASH_AFTER1 = "60a2e75cfa153c4af2783bd6cb48fd6bed84c6381bc2c8f02792c046b46c0653"; String ROOT_HASH_AFTER1 = "60a2e75cfa153c4af2783bd6cb48fd6bed84c6381bc2c8f02792c046b46c0653";
String ROOT_HASH_AFTER2 = "a84739b4762ddf15e3acc4e6957e5ab2bbfaaef00fe9d436a7369c6f058ec90d"; String ROOT_HASH_AFTER2 = "a84739b4762ddf15e3acc4e6957e5ab2bbfaaef00fe9d436a7369c6f058ec90d";
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, dog); trie.update(cat, dog);
assertEquals(dog, new String(trie.get(cat))); assertEquals(dog, new String(trie.get(cat)));
@ -322,7 +322,7 @@ public class TrieTest {
String ROOT_HASH_AFTER1 = "f586af4a476ba853fca8cea1fbde27cd17d537d18f64269fe09b02aa7fe55a9e"; String ROOT_HASH_AFTER1 = "f586af4a476ba853fca8cea1fbde27cd17d537d18f64269fe09b02aa7fe55a9e";
String ROOT_HASH_AFTER2 = "c59fdc16a80b11cc2f7a8b107bb0c954c0d8059e49c760ec3660eea64053ac91"; String ROOT_HASH_AFTER2 = "c59fdc16a80b11cc2f7a8b107bb0c954c0d8059e49c760ec3660eea64053ac91";
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(c, LONG_STRING); trie.update(c, LONG_STRING);
assertEquals(LONG_STRING, new String(trie.get(c))); assertEquals(LONG_STRING, new String(trie.get(c)));
@ -345,7 +345,7 @@ public class TrieTest {
@Test @Test
public void testDeleteAll() { public void testDeleteAll() {
String ROOT_HASH_BEFORE = "a84739b4762ddf15e3acc4e6957e5ab2bbfaaef00fe9d436a7369c6f058ec90d"; String ROOT_HASH_BEFORE = "a84739b4762ddf15e3acc4e6957e5ab2bbfaaef00fe9d436a7369c6f058ec90d";
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
assertEquals(ROOT_HASH_EMPTY, Hex.toHexString(trie.getRootHash())); assertEquals(ROOT_HASH_EMPTY, Hex.toHexString(trie.getRootHash()));
trie.update(ca, dude); trie.update(ca, dude);
@ -361,8 +361,8 @@ public class TrieTest {
@Test @Test
public void testTrieCmp() { public void testTrieCmp() {
Trie trie1 = new Trie(mockDb); TrieImpl trie1 = new TrieImpl(mockDb);
Trie trie2 = new Trie(mockDb); TrieImpl trie2 = new TrieImpl(mockDb);
trie1.update(doge, LONG_STRING); trie1.update(doge, LONG_STRING);
trie2.update(doge, LONG_STRING); trie2.update(doge, LONG_STRING);
@ -377,7 +377,7 @@ public class TrieTest {
@Test @Test
public void testTrieSync() { public void testTrieSync() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(dog, LONG_STRING); trie.update(dog, LONG_STRING);
assertEquals("Expected no data in database", mockDb.getAddedItems(), 0); assertEquals("Expected no data in database", mockDb.getAddedItems(), 0);
@ -388,7 +388,7 @@ public class TrieTest {
@Test @Test
public void TestTrieDirtyTracking() { public void TestTrieDirtyTracking() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(dog, LONG_STRING); trie.update(dog, LONG_STRING);
assertTrue("Expected trie to be dirty", trie.getCache().isDirty()); assertTrue("Expected trie to be dirty", trie.getCache().isDirty());
@ -402,7 +402,7 @@ public class TrieTest {
@Test @Test
public void TestTrieReset() { public void TestTrieReset() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update(cat, LONG_STRING); trie.update(cat, LONG_STRING);
assertNotEquals("Expected cached nodes", 0, trie.getCache().getNodes().size()); assertNotEquals("Expected cached nodes", 0, trie.getCache().getNodes().size());
@ -414,9 +414,9 @@ public class TrieTest {
@Test @Test
public void testTrieCopy() { public void testTrieCopy() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("doe", "reindeer"); trie.update("doe", "reindeer");
Trie trie2 = trie.copy(); TrieImpl trie2 = trie.copy();
assertFalse(trie.equals(trie2)); // avoid possibility that its just a reference copy assertFalse(trie.equals(trie2)); // avoid possibility that its just a reference copy
assertEquals(Hex.toHexString(trie.getRootHash()), Hex.toHexString(trie2.getRootHash())); assertEquals(Hex.toHexString(trie.getRootHash()), Hex.toHexString(trie2.getRootHash()));
assertTrue(trie.cmp(trie2)); assertTrue(trie.cmp(trie2));
@ -424,7 +424,7 @@ public class TrieTest {
@Test @Test
public void testTrieUndo() { public void testTrieUndo() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("doe", "reindeer"); trie.update("doe", "reindeer");
assertEquals("11a0327cfcc5b7689b6b6d727e1f5f8846c1137caaa9fc871ba31b7cce1b703e", Hex.toHexString(trie.getRootHash())); assertEquals("11a0327cfcc5b7689b6b6d727e1f5f8846c1137caaa9fc871ba31b7cce1b703e", Hex.toHexString(trie.getRootHash()));
trie.sync(); trie.sync();
@ -440,7 +440,7 @@ public class TrieTest {
@Test @Test
public void testSingleItem() { public void testSingleItem() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); trie.update("A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
assertEquals("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab", Hex.toHexString(trie.getRootHash())); assertEquals("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab", Hex.toHexString(trie.getRootHash()));
@ -448,7 +448,7 @@ public class TrieTest {
@Test @Test
public void testDogs() { public void testDogs() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("doe", "reindeer"); trie.update("doe", "reindeer");
assertEquals("11a0327cfcc5b7689b6b6d727e1f5f8846c1137caaa9fc871ba31b7cce1b703e", Hex.toHexString(trie.getRootHash())); assertEquals("11a0327cfcc5b7689b6b6d727e1f5f8846c1137caaa9fc871ba31b7cce1b703e", Hex.toHexString(trie.getRootHash()));
@ -461,7 +461,7 @@ public class TrieTest {
@Test @Test
public void testPuppy() { public void testPuppy() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("do", "verb"); trie.update("do", "verb");
trie.update("doge", "coin"); trie.update("doge", "coin");
trie.update("horse", "stallion"); trie.update("horse", "stallion");
@ -472,7 +472,7 @@ public class TrieTest {
@Test @Test
public void testEmptyValues() { public void testEmptyValues() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("do", "verb"); trie.update("do", "verb");
trie.update("ether", "wookiedoo"); trie.update("ether", "wookiedoo");
trie.update("horse", "stallion"); trie.update("horse", "stallion");
@ -487,7 +487,7 @@ public class TrieTest {
@Test @Test
public void testFoo() { public void testFoo() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("foo", "bar"); trie.update("foo", "bar");
trie.update("food", "bat"); trie.update("food", "bat");
trie.update("food", "bass"); trie.update("food", "bass");
@ -497,7 +497,7 @@ public class TrieTest {
@Test @Test
public void testSmallValues() { public void testSmallValues() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("be", "e"); trie.update("be", "e");
trie.update("dog", "puppy"); trie.update("dog", "puppy");
@ -507,7 +507,7 @@ public class TrieTest {
@Test @Test
public void testTesty() { public void testTesty() {
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("test", "test"); trie.update("test", "test");
assertEquals("85d106d4edff3b7a4889e91251d0a87d7c17a1dda648ebdba8c6060825be23b8", Hex.toHexString(trie.getRootHash())); assertEquals("85d106d4edff3b7a4889e91251d0a87d7c17a1dda648ebdba8c6060825be23b8", Hex.toHexString(trie.getRootHash()));
@ -526,7 +526,7 @@ public class TrieTest {
List<String> randomWords = Arrays.asList(randomDictionary.split(",")); List<String> randomWords = Arrays.asList(randomDictionary.split(","));
HashMap<String, String> testerMap = new HashMap<>(); HashMap<String, String> testerMap = new HashMap<>();
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
Random generator = new Random(); Random generator = new Random();
// Random insertion // Random insertion
@ -583,7 +583,7 @@ public class TrieTest {
// *** Part - 1 *** // *** Part - 1 ***
// 1. load the data from massive-upload.dmp // 1. load the data from massive-upload.dmp
// which includes deletes/upadtes (5000 operations) // which includes deletes/upadtes (5000 operations)
Trie trieSingle = new Trie(mockDb_2); TrieImpl trieSingle = new TrieImpl(mockDb_2);
for (int i = 0; i < strData.size() ; ++i){ for (int i = 0; i < strData.size() ; ++i){
String[] keyVal= strData.get(i).split("="); String[] keyVal= strData.get(i).split("=");
@ -603,7 +603,7 @@ public class TrieTest {
// 1. part of the data loaded // 1. part of the data loaded
// 2. the trie cache sync to the db // 2. the trie cache sync to the db
// 3. the rest of the data loaded with part of the trie not in the cache // 3. the rest of the data loaded with part of the trie not in the cache
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
for (int i = 0; i < 2000; ++i){ for (int i = 0; i < 2000; ++i){
@ -618,7 +618,7 @@ public class TrieTest {
trie.cleanCacheGarbage(); trie.cleanCacheGarbage();
trie.sync(); trie.sync();
Trie trie2 = new Trie(mockDb, trie.getRootHash()); TrieImpl trie2 = new TrieImpl(mockDb, trie.getRootHash());
for (int i = 2000; i < strData.size(); ++i){ for (int i = 2000; i < strData.size(); ++i){
@ -644,7 +644,7 @@ public class TrieTest {
List<String> randomWords = Arrays.asList(randomDictionary.split(",")); List<String> randomWords = Arrays.asList(randomDictionary.split(","));
HashMap<String, String> testerMap = new HashMap<>(); HashMap<String, String> testerMap = new HashMap<>();
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
Random generator = new Random(); Random generator = new Random();
// Random insertion // Random insertion
@ -674,7 +674,7 @@ public class TrieTest {
Assert.assertEquals(mapWord2, treeWord2); Assert.assertEquals(mapWord2, treeWord2);
} }
Trie trie2 = new Trie(mockDb, trie.getRootHash()); TrieImpl trie2 = new TrieImpl(mockDb, trie.getRootHash());
// Assert the result now // Assert the result now
keys = testerMap.keySet().iterator(); keys = testerMap.keySet().iterator();
@ -693,7 +693,7 @@ public class TrieTest {
@Test @Test
public void testRollbackTrie() throws URISyntaxException, IOException { public void testRollbackTrie() throws URISyntaxException, IOException {
Trie trieSingle = new Trie(mockDb); TrieImpl trieSingle = new TrieImpl(mockDb);
URL massiveUpload_1 = ClassLoader URL massiveUpload_1 = ClassLoader
.getSystemResource("trie/massive-upload.dmp"); .getSystemResource("trie/massive-upload.dmp");
@ -745,10 +745,10 @@ public class TrieTest {
@Test @Test
public void testGetFromRootNode() { public void testGetFromRootNode() {
Trie trie1 = new Trie(mockDb); TrieImpl trie1 = new TrieImpl(mockDb);
trie1.update(cat, LONG_STRING); trie1.update(cat, LONG_STRING);
trie1.sync(); trie1.sync();
Trie trie2 = new Trie(mockDb, trie1.getRootHash()); TrieImpl trie2 = new TrieImpl(mockDb, trie1.getRootHash());
assertEquals(LONG_STRING, new String(trie2.get(cat))); assertEquals(LONG_STRING, new String(trie2.get(cat)));
} }
@ -774,7 +774,7 @@ public class TrieTest {
byte[] val3 = Hex.decode("94412e0c4f0102f3f0ac63f0a125bce36ca75d4e0d"); byte[] val3 = Hex.decode("94412e0c4f0102f3f0ac63f0a125bce36ca75d4e0d");
byte[] val4 = Hex.decode("01"); byte[] val4 = Hex.decode("01");
Trie storage = new Trie(new org.ethereum.db.MockDB()); TrieImpl storage = new TrieImpl(new org.ethereum.db.MockDB());
storage.update(key1, val1); storage.update(key1, val1);
storage.update(key2, val2); storage.update(key2, val2);
storage.update(key3, val3); storage.update(key3, val3);
@ -815,7 +815,7 @@ public class TrieTest {
// TEST: load trie out of this run up to block#33 // TEST: load trie out of this run up to block#33
byte[] rootNode = Hex.decode("bb690805d24882bc7ccae6fc0f80ac146274d5b81c6a6e9c882cd9b0a649c9c7"); byte[] rootNode = Hex.decode("bb690805d24882bc7ccae6fc0f80ac146274d5b81c6a6e9c882cd9b0a649c9c7");
Trie trie = new Trie(db.getDb(), rootNode); TrieImpl trie = new TrieImpl(db.getDb(), rootNode);
// first key added in genesis // first key added in genesis
byte[] val1 = trie.get(Hex.decode("51ba59315b3a95761d0863b05ccc7a7f54703d99")); byte[] val1 = trie.get(Hex.decode("51ba59315b3a95761d0863b05ccc7a7f54703d99"));
@ -844,7 +844,7 @@ public class TrieTest {
// each time dump the entire trie // each time dump the entire trie
public void testSample_1(){ public void testSample_1(){
Trie trie = new Trie(mockDb); TrieImpl trie = new TrieImpl(mockDb);
trie.update("dog", "puppy"); trie.update("dog", "puppy");
String dmp = trie.getTrieDump(); String dmp = trie.getTrieDump();

View File

@ -6,7 +6,6 @@ import org.ethereum.vm.Program.OutOfGasException;
import org.ethereum.vm.Program.PcOverflowException; import org.ethereum.vm.Program.PcOverflowException;
import org.ethereum.vm.Program.StackTooSmallException; import org.ethereum.vm.Program.StackTooSmallException;
import org.junit.FixMethodOrder; import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import org.spongycastle.util.encoders.Hex; import org.spongycastle.util.encoders.Hex;
@ -1524,7 +1523,6 @@ public class VMTest {
} }
@Test // SSTORE OP @Test // SSTORE OP
@Ignore
public void testSSTORE_1() { public void testSSTORE_1() {
VM vm = new VM(); VM vm = new VM();
@ -1546,7 +1544,6 @@ public class VMTest {
} }
@Test // SSTORE OP @Test // SSTORE OP
@Ignore
public void testSSTORE_2() { public void testSSTORE_2() {
VM vm = new VM(); VM vm = new VM();
@ -1586,7 +1583,6 @@ public class VMTest {
} }
@Test // SLOAD OP @Test // SLOAD OP
@Ignore
public void testSLOAD_1() { public void testSLOAD_1() {
VM vm = new VM(); VM vm = new VM();
@ -1601,7 +1597,6 @@ public class VMTest {
} }
@Test // SLOAD OP @Test // SLOAD OP
@Ignore
public void testSLOAD_2() { public void testSLOAD_2() {
VM vm = new VM(); VM vm = new VM();
@ -1619,7 +1614,6 @@ public class VMTest {
} }
@Test // SLOAD OP @Test // SLOAD OP
@Ignore
public void testSLOAD_3() { public void testSLOAD_3() {
VM vm = new VM(); VM vm = new VM();
@ -1667,7 +1661,6 @@ public class VMTest {
@Test // PC OP @Test // PC OP
@Ignore
public void testPC_2() { public void testPC_2() {
VM vm = new VM(); VM vm = new VM();