Exception to provide the following to {@link EncrypterDecrypterOpenSSL}:
- *
- *
Provision of encryption / decryption exception
- *
- *
This base exception acts as a general failure mode not attributable to a specific cause (other than
- * that reported in the exception message). Since this is in English, it may not be worth reporting directly
- * to the user other than as part of a "general failure to parse" response.
- */
-public class KeyCrypterException extends RuntimeException {
- private static final long serialVersionUID = -4441989608332681377L;
-
- public KeyCrypterException(String s) {
- super(s);
- }
-
- public KeyCrypterException(String s, Throwable throwable) {
- super(s, throwable);
- }
-}
\ No newline at end of file
diff --git a/ethereumj-core/src/main/java/org/ethereum/crypto/vm/OpCode.java b/ethereumj-core/src/main/java/org/ethereum/crypto/vm/OpCode.java
new file mode 100644
index 00000000..1cac5dd2
--- /dev/null
+++ b/ethereumj-core/src/main/java/org/ethereum/crypto/vm/OpCode.java
@@ -0,0 +1,144 @@
+package org.ethereum.crypto.vm;
+
+/**
+ * Instruction set for the Ethereum Virtual Machine
+ */
+public enum OpCode {
+
+ /**
+ * Stop and Arithmetic Operations
+ */
+
+ STOP(0x00),
+ ADD(0x01),
+ MUL(0x02),
+ SUB(0x03),
+ DIV(0x04),
+ SDIV(0x05),
+ MOD(0x06),
+ SMOD(0x07),
+ EXP(0x08),
+ NEG(0x09),
+ LT(0x0a),
+ GT(0x0b),
+ EQ(0x0c),
+ NOT(0x0d),
+
+ /**
+ * Bitwise Logic Operations
+ */
+
+ AND(0x10),
+ OR(0x11),
+ XOR(0x12),
+ BYTE(0x13),
+
+ /**
+ * SHA3
+ */
+
+ SHA3(0x20),
+
+ /**
+ * Environmental Information
+ */
+
+ ADDRESS(0x30),
+ BALANCE(0x31),
+ ORIGIN(0x32),
+ CALLER(0x33),
+ CALLVALUE(0x34),
+ CALLDATALOAD(0x35),
+ CALLDATASIZE(0x36),
+ CALLDATACOPY(0x37),
+ CODESIZE(0x38),
+ CODECOPY(0x39),
+ GASPRICE(0x3a),
+
+ /**
+ * Block Information
+ */
+
+ PREVHASH(0x40),
+ COINBASE(0x41),
+ TIMESTAMP(0x42),
+ NUMBER(0x43),
+ DIFFICULTY(0x44),
+ GASLIMIT(0x45),
+
+ /**
+ * Memory, Storage and Flow Operations
+ */
+
+ POP(0x50),
+ DUP(0x51),
+ SWAP(0x52),
+ MLOAD(0x53),
+ MSTORE(0x54),
+ MSTORE8(0x55),
+ SLOAD(0x56),
+ SSTORE(0x57),
+ JUMP(0x58),
+ JUMPI(0x59),
+ PC(0x5a),
+ MSIZE(0x5b),
+ GAS(0x5c),
+
+ /**
+ * Push Operations
+ */
+
+ PUSH1(0x60),
+ PUSH2(0x61),
+ PUSH3(0x62),
+ PUSH4(0x63),
+ PUSH5(0x64),
+ PUSH6(0x65),
+ PUSH7(0x66),
+ PUSH8(0x67),
+ PUSH9(0x68),
+ PUSH10(0x69),
+ PUSH11(0x6a),
+ PUSH12(0x6b),
+ PUSH13(0x6c),
+ PUSH14(0x6d),
+ PUSH15(0x6e),
+ PUSH16(0x6f),
+ PUSH17(0x70),
+ PUSH18(0x71),
+ PUSH19(0x72),
+ PUSH20(0x73),
+ PUSH21(0x74),
+ PUSH22(0x75),
+ PUSH23(0x76),
+ PUSH24(0x77),
+ PUSH25(0x78),
+ PUSH26(0x79),
+ PUSH27(0x7a),
+ PUSH28(0x7b),
+ PUSH29(0x7c),
+ PUSH30(0x7d),
+ PUSH31(0x7e),
+ PUSH32(0x7f),
+
+ /**
+ * System operations
+ */
+
+ CREATE(0xf0),
+ CALL(0xf1),
+ RETURN(0xf2),
+ SUICIDE(0xff);
+
+ private int opcode;
+
+ private OpCode(int opcode) {
+ this.opcode = opcode;
+ }
+
+ public int getOpCode() {
+ return this.opcode;
+ }
+
+
+}
diff --git a/ethereumj-core/src/main/java/org/ethereum/geodb/IpGeoDB.java b/ethereumj-core/src/main/java/org/ethereum/geodb/IpGeoDB.java
index 52ac2874..702b7ba3 100644
--- a/ethereumj-core/src/main/java/org/ethereum/geodb/IpGeoDB.java
+++ b/ethereumj-core/src/main/java/org/ethereum/geodb/IpGeoDB.java
@@ -16,11 +16,9 @@ public class IpGeoDB { // change
static{
try {
-
URL geiIpDBFile = ClassLoader.getSystemResource("GeoLiteCity.dat");
File file = new File(geiIpDBFile.toURI());
cl = new LookupService(file);
-
} catch (Throwable e) {
e.printStackTrace();
}
@@ -28,17 +26,14 @@ public class IpGeoDB { // change
private static LookupService cl;
-
public static Location getLocationForIp(InetAddress ip){
try {
return cl.getLocation(ip);
} catch (Throwable e) {
-
// todo: think about this exception, maybe you can do something more reasonable
System.out.println(e.getMessage());
// e.printStackTrace();
}
-
return null;
}
}
diff --git a/ethereumj-core/src/main/java/org/ethereum/net/MessageDeserializer.java b/ethereumj-core/src/main/java/org/ethereum/net/MessageDeserializer.java
index 1ad39d23..38d9a355 100644
--- a/ethereumj-core/src/main/java/org/ethereum/net/MessageDeserializer.java
+++ b/ethereumj-core/src/main/java/org/ethereum/net/MessageDeserializer.java
@@ -15,75 +15,51 @@ public class MessageDeserializer {
int pos = startPos;
while(pos < endPos){
-
// It's a list with a payload more than 55 bytes
// data[0] - 0xF7 = how many next bytes allocated
// for the length of the list
if ((msgData[pos] & 0xFF) >= 0xF7){
-
- byte lenghtOfLenght = (byte) (msgData[pos] - 0xF7);
- byte pow = (byte) (lenghtOfLenght - 1);
-
- int length = 0;
- for (int i = 1; i <= lenghtOfLenght; ++i){
- length += msgData[pos + i] << (8 * pow);
- pow--;
- }
-
+ byte lengthOfLength = (byte) (msgData[pos] - 0xF7);
+ int length = calcLength(lengthOfLength, msgData, pos);
// now we can parse an item for data[1]..data[length]
System.out.println("-- level: [" + level + "] Found big list length: " + length);
- deserialize(msgData, level + 1, pos + lenghtOfLenght + 1, pos + lenghtOfLenght + length);
- pos += lenghtOfLenght + length + 1 ;
+ deserialize(msgData, level + 1, pos + lengthOfLength + 1, pos + lengthOfLength + length);
+ pos += lengthOfLength + length + 1 ;
continue;
}
-
// It's a list with a payload less than 55 bytes
if ((msgData[pos] & 0xFF) >= 0xC0 && (msgData[pos] & 0xFF) < 0xF7){
-
byte length = (byte) (msgData[pos] - 0xC0);
System.out.println("-- level: [" + level + "] Found small list length: " + length);
deserialize(msgData, level + 1, pos + 1, pos + length + 1);
pos += 1 + length;
continue;
}
-
// It's an item with a payload more than 55 bytes
// data[0] - 0xB7 = how much next bytes allocated for
// the length of the string
if ((msgData[pos] & 0xFF) >= 0xB7 && (msgData[pos] & 0xFF) < 0xC0) {
-
- byte lenghtOfLenght = (byte) (msgData[pos] - 0xB7);
- byte pow = (byte) (lenghtOfLenght - 1);
-
- int length = 0;
- for (int i = 1; i <= lenghtOfLenght; ++i){
- length += msgData[pos + i] << (8 * pow);
- pow--;
- }
+ byte lengthOfLength = (byte) (msgData[pos] - 0xB7);
+ int length = calcLength(lengthOfLength, msgData, pos);
// now we can parse an item for data[1]..data[length]
System.out.println("-- level: [" + level + "] Found big item length: " + length);
- pos += lenghtOfLenght + length + 1 ;
+ pos += lengthOfLength + length + 1 ;
continue;
}
-
-
// It's an item less than 55 bytes long,
// data[0] - 0x80 == lenght of the item
if ((msgData[pos] & 0xFF) > 0x80 && (msgData[pos] & 0xFF) < 0xB7) {
-
byte length = (byte) (msgData[pos] - 0x80);
System.out.println("-- level: [" + level + "] Found small item length: " + length);
pos += 1 + length;
continue;
}
-
// null item
if ((msgData[pos] & 0xFF) == 0x80){
System.out.println("-- level: [" + level + "] Found null item: ");
pos += 1;
continue;
}
-
// single byte item
if ((msgData[pos] & 0xFF) < 0x80) {
System.out.println("-- level: [" + level + "] Found single item: ");
@@ -92,4 +68,14 @@ public class MessageDeserializer {
}
}
}
+
+ private static int calcLength(int lengthOfLength, byte[] msgData, int pos) {
+ byte pow = (byte) (lengthOfLength - 1);
+ int length = 0;
+ for (int i = 1; i <= lengthOfLength; ++i){
+ length += msgData[pos + i] << (8 * pow);
+ pow--;
+ }
+ return length;
+ }
}
diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/DisconnectMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/message/DisconnectMessage.java
index a7729514..47769063 100644
--- a/ethereumj-core/src/main/java/org/ethereum/net/message/DisconnectMessage.java
+++ b/ethereumj-core/src/main/java/org/ethereum/net/message/DisconnectMessage.java
@@ -4,6 +4,7 @@ import org.ethereum.net.rlp.RLPItem;
import org.ethereum.net.rlp.RLPList;
import org.ethereum.net.Command;
import static org.ethereum.net.Command.DISCONNECT;
+import static org.ethereum.net.message.ReasonCode.DISCONNECT_REQUESTED;
/**
* www.ethereumJ.com
@@ -12,17 +13,7 @@ import static org.ethereum.net.Command.DISCONNECT;
*/
public class DisconnectMessage extends Message {
- private byte reason;
-
- public static byte REASON_DISCONNECT_REQUESTED = 0x00;
- public static byte REASON_TCP_ERROR = 0x01;
- public static byte REASON_BAD_PROTOCOL = 0x02;
- public static byte REASON_USELESS_PEER = 0x03;
- public static byte REASON_TOO_MANY_PEERS = 0x04;
- public static byte REASON_ALREADY_CONNECTED = 0x05;
- public static byte REASON_WRONG_GENESIS = 0x06;
- public static byte REASON_INCOMPATIBLE_PROTOCOL = 0x07;
- public static byte REASON_PEER_QUITING = 0x08;
+ private ReasonCode reason;
public DisconnectMessage(RLPList rawData) {
super(rawData);
@@ -39,9 +30,9 @@ public class DisconnectMessage extends Message {
byte[] reasonB = ((RLPItem)paramsList.getElement(1)).getData();
if (reasonB == null){
- this.reason = 0;
+ this.reason = DISCONNECT_REQUESTED;
} else {
- this.reason = reasonB[0];
+ this.reason = ReasonCode.fromInt(reasonB[0]);
}
this.parsed = true;
// todo: what to do when mal data ?
@@ -52,7 +43,7 @@ public class DisconnectMessage extends Message {
return null;
}
- public byte getReason() {
+ public ReasonCode getReason() {
if (!parsed) parseRLP();
return reason;
}
diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/ReasonCode.java b/ethereumj-core/src/main/java/org/ethereum/net/message/ReasonCode.java
new file mode 100644
index 00000000..d4b15987
--- /dev/null
+++ b/ethereumj-core/src/main/java/org/ethereum/net/message/ReasonCode.java
@@ -0,0 +1,42 @@
+package org.ethereum.net.message;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum ReasonCode {
+
+ DISCONNECT_REQUESTED(0x00),
+ TCP_ERROR(0x01),
+ BAD_PROTOCOL(0x02),
+ USELESS_PEER(0x03),
+ TOO_MANY_PEERS(0x04),
+ ALREADY_CONNECTED(0x05),
+ WRONG_GENESIS(0x06),
+ INCOMPATIBLE_PROTOCOL(0x07),
+ PEER_QUITING(0x08),
+ UNKNOWN(0xFF);
+
+ private int reason;
+
+ private static final Map intToTypeMap = new HashMap();
+ static {
+ for (ReasonCode type : ReasonCode.values()) {
+ intToTypeMap.put(type.reason, type);
+ }
+ }
+
+ private ReasonCode(int cmd) {
+ this.reason = cmd;
+ }
+
+ public static ReasonCode fromInt(int i) {
+ ReasonCode type = intToTypeMap.get(Integer.valueOf(i));
+ if (type == null)
+ return ReasonCode.UNKNOWN;
+ return type;
+ }
+
+ public byte asByte() {
+ return (byte) reason;
+ }
+}
diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java b/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java
index ff894675..de293a28 100644
--- a/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java
+++ b/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java
@@ -46,9 +46,7 @@ public class StaticMessages {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03,
(byte) 0xC2, (byte) 0x01, (byte) 0x08};
-
public static final byte[] GET_CHAIN = {
-
(byte) 0x22, (byte) 0x40, (byte) 0x08, (byte) 0x91, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x27,
(byte) 0xF8, (byte) 0x25, (byte) 0x14, (byte) 0xA0, (byte) 0xAB, (byte) 0x6B, (byte) 0x9A, (byte) 0x56, (byte) 0x13,
(byte) 0x97, (byte) 0x0F, (byte) 0xAA, (byte) 0x77, (byte) 0x1B, (byte) 0x12, (byte) 0xD4, (byte) 0x49,
diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java
index cb991896..2b1ef845 100644
--- a/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java
+++ b/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java
@@ -29,7 +29,7 @@ public class TransactionsMessage extends Message {
public void parseRLP() {
RLPList paramsList = (RLPList) rawData.getElement(0);
- if ( Command.fromInt(((RLPItem)(paramsList).getElement(0)).getData()[0] & 0xFF) != TRANSACTIONS){
+ if (Command.fromInt(((RLPItem)(paramsList).getElement(0)).getData()[0] & 0xFF) != TRANSACTIONS) {
throw new Error("TransactionMessage: parsing for mal data");
}
@@ -53,7 +53,7 @@ public class TransactionsMessage extends Message {
return null;
}
- public String toString(){
+ public String toString() {
if(!parsed) parseRLP();
StringBuffer sb = new StringBuffer();
for (Transaction transactionData : transactions){
diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java
index 8dbbf558..98051165 100644
--- a/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java
+++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java
@@ -11,21 +11,41 @@ import java.util.ArrayList;
import java.util.List;
/**
- * www.ethereumJ.com
- * User: Roman Mandeleil
- * Created on: 13/04/14 19:34
+ * The block in Ethereum is the collection of relevant pieces of information
+ * (known as the blockheader), H, together with information corresponding to
+ * the comprised transactions, R, and a set of other blockheaders U that are known
+ * to have a parent equal to the present block’s parent’s parent
+ * (such blocks are known as uncles).
*/
public class Block {
+ private static int LIMIT_FACTOR = (int) Math.pow(2, 16);
+ private static double EMA_FACTOR = 1.5;
+ /* A scalar value equal to the current limit of gas expenditure per block */
+ private static int GAS_LIMIT = (int) Math.pow(10, 6);
+
private RLPList rawData;
private boolean parsed = false;
private byte[] hash;
+
+ /* The SHA3 256-bit hash of the parent block, in its entirety */
private byte[] parentHash;
+ /* The SHA3 256-bit hash of the uncles list portion of this block */
private byte[] unclesHash;
+ /* The 160-bit address to which all fees collected from the
+ * successful mining of this block be transferred; formally */
private byte[] coinbase;
+ /* The SHA3 256-bit hash of the root node of the state trie,
+ * after all transactions are executed and finalisations applied */
private byte[] stateHash;
- private byte[] txListHash;
+ /* The SHA3 256-bit hash of the root node of the trie structure
+ * populated with each transaction in the transactions list portion
+ * of the block */
+ private byte[] txTrieHash;
+ /* A scalar value corresponding to the difficulty level of this block.
+ * This can be calculated from the previous block’s difficulty level
+ * and the timestamp */
private byte[] difficulty;
private long timestamp;
@@ -40,12 +60,12 @@ public class Block {
this.parsed = false;
}
- public Block(byte[] parentHash, byte[] unclesHash, byte[] coinbase, byte[] stateHash, byte[] txListHash, byte[] difficulty, long timestamp, byte[] extraData, byte[] nonce, List transactionsList, List uncleList) {
+ public Block(byte[] parentHash, byte[] unclesHash, byte[] coinbase, byte[] stateHash, byte[] txTrieHash, byte[] difficulty, long timestamp, byte[] extraData, byte[] nonce, List transactionsList, List uncleList) {
this.parentHash = parentHash;
this.unclesHash = unclesHash;
this.coinbase = coinbase;
this.stateHash = stateHash;
- this.txListHash = txListHash;
+ this.txTrieHash = txTrieHash;
this.difficulty = difficulty;
this.timestamp = timestamp;
this.extraData = extraData;
@@ -66,7 +86,7 @@ public class Block {
this.unclesHash = ((RLPItem) params.get(1)).getData();
this.coinbase = ((RLPItem) params.get(2)).getData();
this.stateHash = ((RLPItem) params.get(3)).getData();
- this.txListHash = ((RLPItem) params.get(4)).getData();
+ this.txTrieHash = ((RLPItem) params.get(4)).getData();
this.difficulty = ((RLPItem) params.get(5)).getData();
byte[] tsBytes = ((RLPItem) params.get(6)).getData();
@@ -76,12 +96,10 @@ public class Block {
// parse transactions
List transactions = ((RLPList) rawData.getElement(1)).getList();
-
for (RLPElement rlpTx : transactions){
Transaction tx = new Transaction((RLPList)rlpTx);
this.transactionsList.add(tx);
}
-
// parse uncles
List uncleBlocks = ((RLPList) rawData.getElement(2)).getList();
for (RLPElement rawUncle : uncleBlocks){
@@ -96,6 +114,11 @@ public class Block {
return hash;
}
+ public Block getParent() {
+ // TODO: Implement
+ return null;
+ }
+
public byte[] getParentHash() {
if (!parsed) parseRLP();
return parentHash;
@@ -116,9 +139,9 @@ public class Block {
return stateHash;
}
- public byte[] getTxListHash() {
+ public byte[] getTxTrieHash() {
if (!parsed) parseRLP();
- return txListHash;
+ return txTrieHash;
}
public byte[] getDifficulty() {
@@ -152,7 +175,6 @@ public class Block {
}
// [parent_hash, uncles_hash, coinbase, state_root, tx_list_hash, difficulty, timestamp, extradata, nonce]
-
@Override
public String toString() {
if (!parsed) parseRLP();
@@ -162,11 +184,37 @@ public class Block {
", unclesHash=" + Utils.toHexString(unclesHash) +
", coinbase=" + Utils.toHexString(coinbase) +
", stateHash=" + Utils.toHexString(stateHash) +
- ", txListHash=" + Utils.toHexString(txListHash) +
+ ", txTrieHash=" + Utils.toHexString(txTrieHash) +
", difficulty=" + Utils.toHexString(difficulty) +
", timestamp=" + timestamp +
", extraData=" + Utils.toHexString(extraData) +
", nonce=" + Utils.toHexString(nonce) +
']';
}
+
+ /**
+ * Because every transaction published into the blockchain imposes on the
+ * network the cost of needing to download and verify it, there is a need
+ * for some regulatory mechanism to prevent abuse.
+ *
+ * To solve this we simply institute a floating cap:
+ *
+ * No block can have more operations than BLK_LIMIT_FACTOR times
+ * the long-term exponential moving average.
+ *
+ * Specifically:
+ *
+ * blk.oplimit = floor((blk.parent.oplimit * (EMAFACTOR - 1)
+ * + floor(GAS_LIMIT * BLK_LIMIT_FACTOR)) / EMA_FACTOR)
+ *
+ * BLK_LIMIT_FACTOR and EMA_FACTOR are constants that will be set
+ * to 65536 and 1.5 for the time being, but will likely be changed
+ * after further analysis.
+ *
+ * @return
+ */
+ public double getOplimit() {
+ return Math.floor((this.getParent().getOplimit() * (EMA_FACTOR - 1)
+ + Math.floor(GAS_LIMIT * LIMIT_FACTOR)) / EMA_FACTOR);
+ }
}
diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/PeerData.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/PeerData.java
index d1fa377a..6080fc88 100644
--- a/ethereumj-core/src/main/java/org/ethereum/net/vo/PeerData.java
+++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/PeerData.java
@@ -1,7 +1,6 @@
package org.ethereum.net.vo;
import org.spongycastle.util.encoders.Hex;
-import org.ethereum.net.rlp.RLPList;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -13,39 +12,27 @@ import java.net.UnknownHostException;
*/
public class PeerData {
- RLPList rawData;
- boolean parsed = false;
+ private byte[] ip;
+ private short port;
+ private byte[] peerId;
- byte[] ip;
- short port;
- byte[] peerId;
-
- transient boolean isOnline = false;
- transient long lastCheckTime = 0;
-
- public PeerData(RLPList rlpList){
- rawData = rlpList;
- parsed = false;
- }
+ private transient boolean isOnline = false;
+ private transient long lastCheckTime = 0;
public PeerData(byte[] ip, short port, byte[] peerId) {
this.ip = ip;
this.port = port;
this.peerId = peerId;
- parsed = true;
}
public InetAddress getInetAddress(){
-
InetAddress addr = null;
-
try {
addr = InetAddress.getByAddress(ip);
} catch (UnknownHostException e) {
e.printStackTrace();
throw new Error("malformed ip");
}
-
return addr;
}
diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java
index 20d6899d..7b3babfc 100644
--- a/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java
+++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java
@@ -8,35 +8,49 @@ import org.ethereum.net.rlp.RLPList;
import org.ethereum.util.Utils;
/**
- * www.ethereumJ.com
- * User: Roman Mandeleil
- * Created on: 21/04/14 09:19
+ * A transaction (formally, T ) is a single cryptographically
+ * signed instruction sent by an actor external to Ethereum.
+ * An external actor can be a person (via a mobile device or desktop computer)
+ * or could be from a piece of automated software running on a server.
+ * There are two types of transactions: those which result in message calls
+ * and those which result in the creation of new contracts.
*/
public class Transaction {
- private RLPList rawData;
- private boolean parsed = false;
-
-// creation contract tx or simple send tx
-// [ nonce, value, receiveAddress, gasPrice, gasDeposit, data, signatureV, signatureR, signatureS ]
-// or
-// [ nonce, endowment, 0, gasPrice, gasDeposit (for init), body, init, signatureV, signatureR, signatureS ]
+ private RLPList rawData;
+ private boolean parsed = false;
+ /* creation contract tx
+ * [ nonce, endowment, 0, gasPrice, gasDeposit (for init), body, init, signature(v, r, s) ]
+ * or simple send tx
+ * [ nonce, value, receiveAddress, gasPrice, gasDeposit, data, signature(v, r, s) ]
+ */
+
+ /* SHA3 hash of the rlpEncoded transaction */
private byte[] hash;
+ /* a counter used to make sure each transaction can only be processed once */
private byte[] nonce;
+ /* the amount of ether to transfer (calculated as wei) */
private byte[] value;
-
- // In creation transaction the receive address is - 0
- private byte[] receiveAddress;
+ /* the address of the destination account
+ * In creation transaction the receive address is - 0 */
+ private byte[] receiveAddress;
+ /* the amount of ether to pay as a transaction fee
+ * to the miner for each unit of gas */
private byte[] gasPrice;
- private byte[] gas;
-
- // Contract creation [data] will hold the contract
- // for other transaction [data] can hold data
+ /* the amount of "gas" to allow for the computation.
+ * Gas is the fuel of the computational engine;
+ * every computational step taken and every byte added
+ * to the state or transaction list consumes some gas. */
+ private byte[] gasLimit;
+ /* An unlimited size byte array specifying
+ * either input [data] of the message call
+ * or the [body] for a new contract */
private byte[] data;
+ /* Initialisation code for a new contract */
private byte[] init;
-
- // Signature
+ /* the elliptic curve signature
+ * (including public key recovery bits) */
private ECDSASignature signature;
public Transaction(RLPList rawData) {
@@ -49,42 +63,36 @@ public class Transaction {
this.value = value;
this.receiveAddress = recieveAddress;
this.gasPrice = gasPrice;
- this.gas = gas;
+ this.gasLimit = gas;
this.data = data;
this.signature = ECDSASignature.fromComponents(r, s, v);
parsed = true;
}
- public void rlpParse() {
-
+ public void rlpParse(){
this.hash = HashUtil.sha3(rawData.getRLPData());
this.nonce = ((RLPItem) rawData.getElement(0)).getData();
this.value = ((RLPItem) rawData.getElement(1)).getData();
this.receiveAddress = ((RLPItem) rawData.getElement(2)).getData();
this.gasPrice = ((RLPItem) rawData.getElement(3)).getData();
- this.gas = ((RLPItem) rawData.getElement(4)).getData();
+ this.gasLimit = ((RLPItem) rawData.getElement(4)).getData();
this.data = ((RLPItem) rawData.getElement(5)).getData();
-
- if (rawData.size() == 9){ // Simple transaction
+ if (rawData.size() == 9){ // Simple transaction
byte v = ((RLPItem) rawData.getElement(6)).getData()[0];
- byte[] r = ((RLPItem) rawData.getElement(7)).getData();
- byte[] s = ((RLPItem) rawData.getElement(8)).getData();
+ byte[] r = ((RLPItem) rawData.getElement(7)).getData();
+ byte[] s = ((RLPItem) rawData.getElement(8)).getData();
this.signature = ECDSASignature.fromComponents(r, s, v);
} else if (rawData.size() == 10){ // Contract creation transaction
-
- this.init = ((RLPItem) rawData.getElement(6)).getData();
+ this.init = ((RLPItem) rawData.getElement(6)).getData();
byte v = ((RLPItem) rawData.getElement(7)).getData()[0];
- byte[] r = ((RLPItem) rawData.getElement(8)).getData();
- byte[] s = ((RLPItem) rawData.getElement(9)).getData();
+ byte[] r = ((RLPItem) rawData.getElement(8)).getData();
+ byte[] s = ((RLPItem) rawData.getElement(9)).getData();
this.signature = ECDSASignature.fromComponents(r, s, v);
- } else
- throw new Error("Wrong tx data element list size");
-
+ } else throw new Error("Wrong tx data element list size");
this.parsed = true;
}
-
public RLPList getRawData() {
return rawData;
}
@@ -118,9 +126,9 @@ public class Transaction {
return gasPrice;
}
- public byte[] getGas() {
+ public byte[] getGasLimit() {
if (!parsed) rlpParse();
- return gas;
+ return gasLimit;
}
public byte[] getData() {
@@ -141,7 +149,32 @@ public class Transaction {
public boolean isContract() {
return this.receiveAddress.length == 0;
}
-
+
+ /*********
+ * Crypto
+ */
+
+ public ECKey getKey() {
+ byte[] hash = this.getHash();
+ return ECKey.recoverFromSignature(signature.v, signature, hash, true);
+ }
+
+ public byte[] sender() {
+ ECKey eckey = this.getKey();
+ // Validate the returned key.
+ // Return null if public key isn't in a correct format
+ if (!eckey.isPubKeyCanonical()) {
+ return null;
+ }
+ return eckey.getAddress();
+ }
+
+ public void sign(byte[] privKeyBytes) throws Exception {
+ byte[] hash = this.getHash();
+ ECKey key = ECKey.fromPrivate(privKeyBytes);
+ this.signature = key.sign(hash);
+ }
+
@Override
public String toString() {
if (!parsed) rlpParse();
@@ -150,7 +183,7 @@ public class Transaction {
", value=" + Utils.toHexString(value) +
", receiveAddress=" + Utils.toHexString(receiveAddress) +
", gasPrice=" + Utils.toHexString(gasPrice) +
- ", gas=" + Utils.toHexString(gas) +
+ ", gas=" + Utils.toHexString(gasLimit) +
", data=" + Utils.toHexString(data) +
", init=" + Utils.toHexString(init) +
", signatureV=" + signature.v +
diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java b/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java
new file mode 100644
index 00000000..7ead2c11
--- /dev/null
+++ b/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java
@@ -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 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();
+ }
+
+ 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();
+ }
+ }
+
+ public void undo() {
+ Iterator> 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 getNodes() {
+ return nodes;
+ }
+
+ public DB getDb() {
+ return db;
+ }
+}
diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Node.java b/ethereumj-core/src/main/java/org/ethereum/trie/Node.java
new file mode 100644
index 00000000..f37c68d6
--- /dev/null
+++ b/ethereumj-core/src/main/java/org/ethereum/trie/Node.java
@@ -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 };
+ }
+}
diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java b/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java
new file mode 100644
index 00000000..e0e83afc
--- /dev/null
+++ b/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java
@@ -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;
+ }
+}
diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java b/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java
new file mode 100644
index 00000000..9b43817e
--- /dev/null
+++ b/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java
@@ -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 shas;
+ List 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 collect() {
+ if (this.trie.getRoot() == "") {
+ return null;
+ }
+ this.getNode(new Value(this.trie.getRoot()).asBytes());
+ return this.shas;
+ }
+
+ public int purge() {
+ List shas = this.collect();
+
+ for (byte[] sha : shas) {
+ this.trie.getCache().delete(sha);
+ }
+ return this.values.size();
+ }
+
+ private String getKey() {
+ return "";
+ }
+
+ private String getValue() {
+ return "";
+ }
+}
diff --git a/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java b/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java
new file mode 100644
index 00000000..c88305f0
--- /dev/null
+++ b/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java
@@ -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;
+ }
+}
diff --git a/ethereumj-core/src/main/java/org/ethereum/util/DecodeResult.java b/ethereumj-core/src/main/java/org/ethereum/util/DecodeResult.java
new file mode 100644
index 00000000..d7213a06
--- /dev/null
+++ b/ethereumj-core/src/main/java/org/ethereum/util/DecodeResult.java
@@ -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;
+ }
+}
diff --git a/ethereumj-core/src/main/java/org/ethereum/util/RlpEncoder.java b/ethereumj-core/src/main/java/org/ethereum/util/RlpEncoder.java
new file mode 100644
index 00000000..c3cebafb
--- /dev/null
+++ b/ethereumj-core/src/main/java/org/ethereum/util/RlpEncoder.java
@@ -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