Merge cleanup changes

This commit is contained in:
nicksavers 2014-05-05 19:38:35 +02:00
commit 395be5d95e
31 changed files with 2562 additions and 163 deletions

View File

@ -35,6 +35,18 @@
<artifactId>core</artifactId>
<version>${spongycastle.version}</version>
</dependency>
<dependency>
<groupId>org.iq80.leveldb</groupId>
<artifactId>leveldb</artifactId>
<version>${leveldb.version}</version>
</dependency>
<!-- Added Cedarsoft for DeepEqual function -->
<dependency>
<groupId>com.cedarsoftware</groupId>
<artifactId>java-util</artifactId>
<version>1.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fifesoft</groupId>
<artifactId>rsyntaxtextarea</artifactId>

View File

@ -1,21 +0,0 @@
package org.ethereum.crypto;
/**
* <p>Exception to provide the following to {@link EncrypterDecrypterOpenSSL}:</p>
* <ul>
* <li>Provision of encryption / decryption exception</li>
* </ul>
* <p>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.</p>
*/
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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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<Integer, ReasonCode> intToTypeMap = new HashMap<Integer, ReasonCode>();
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;
}
}

View File

@ -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,

View File

@ -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){

View File

@ -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 blocks parents 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 blocks 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<Transaction> transactionsList, List<Block> uncleList) {
public Block(byte[] parentHash, byte[] unclesHash, byte[] coinbase, byte[] stateHash, byte[] txTrieHash, byte[] difficulty, long timestamp, byte[] extraData, byte[] nonce, List<Transaction> transactionsList, List<Block> 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<RLPElement> transactions = ((RLPList) rawData.getElement(1)).getList();
for (RLPElement rlpTx : transactions){
Transaction tx = new Transaction((RLPList)rlpTx);
this.transactionsList.add(tx);
}
// parse uncles
List<RLPElement> 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);
}
}

View File

@ -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;
}

View File

@ -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 +

View File

@ -0,0 +1,117 @@
package org.ethereum.trie;
import static org.iq80.leveldb.impl.Iq80DBFactory.factory;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.ethereum.crypto.HashUtil;
import org.ethereum.util.Value;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.Options;
import org.spongycastle.util.encoders.Hex;
public class Cache {
private Map<byte[], Node> nodes;
private DB db;
private boolean isDirty;
public Cache(DB db) {
if(db == null) {
try {
/* **** Experimental LevelDB Code **** */
Options options = new Options();
options.createIfMissing(true);
this.db = factory.open(new File("ethereumdb"), options);
/* **** Experimental LevelDB Code **** */
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
this.db = db;
nodes = new HashMap<byte[], Node>();
}
public Object put(Object o) {
Value value = new Value(o);
byte[] enc = value.encode();
if (enc.length >= 32) {
byte[] sha = Hex.encode(HashUtil.sha3(enc));
this.nodes.put(sha, new Node(sha, value, true));
this.isDirty = true;
return sha;
}
return value;
}
public Value get(byte[] key) {
// First check if the key is the cache
if (this.nodes.get(key) != null) {
return this.nodes.get(key).getValue();
}
// Get the key of the database instead and cache it
byte[] data = this.db.get(key);
Value value = new Value(data);
// Create caching node
this.nodes.put(key, new Node(key, value, false));
return value;
}
public void delete(byte[] key) {
this.nodes.remove(key);
this.db.delete(key);
}
public void commit() {
// Don't try to commit if it isn't dirty
if (!this.isDirty) {
return;
}
for (byte[] key : this.nodes.keySet()) {
Node node = this.nodes.get(key);
if (node.isDirty()) {
this.db.put(key, node.getValue().encode());
node.setDirty(false);
}
}
this.isDirty = false;
// If the nodes grows beyond the 200 entries we simple empty it
// FIXME come up with something better
if (this.nodes.size() > 200) {
this.nodes = new HashMap<byte[], Node>();
}
}
public void undo() {
Iterator<Map.Entry<byte[], Node>> iter = this.nodes.entrySet().iterator();
while (iter.hasNext()) {
if(iter.next().getValue().isDirty()){
iter.remove();
}
}
this.isDirty = false;
}
public boolean isDirty() {
return isDirty;
}
public void setDirty(boolean isDirty) {
this.isDirty = isDirty;
}
public Map<byte[], Node> getNodes() {
return nodes;
}
public DB getDb() {
return db;
}
}

View File

@ -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 };
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,85 @@
package org.ethereum.trie;
import java.util.List;
import org.ethereum.util.CompactEncoder;
import org.ethereum.util.Value;
public class TrieIterator {
Trie trie;
String key;
String value;
List<byte[]> shas;
List<String> values;
public TrieIterator(Trie t) {
this.trie = t;
}
// Some time in the near future this will need refactoring :-)
// XXX Note to self, IsSlice == inline node. Str == sha3 to node
private void workNode(Value currentNode) {
if (currentNode.length() == 2) {
byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes());
if (currentNode.get(1).asString() == "") {
this.workNode(currentNode.get(1));
} else {
if (k[k.length-1] == 16) {
this.values.add( currentNode.get(1).asString() );
} else {
this.shas.add(currentNode.get(1).asBytes());
this.getNode(currentNode.get(1).asBytes());
}
}
} else {
for (int i = 0; i < currentNode.length(); i++) {
if (i == 16 && currentNode.get(i).length() != 0) {
this.values.add( currentNode.get(i).asString() );
} else {
if (currentNode.get(i).asString() == "") {
this.workNode(currentNode.get(i));
} else {
String val = currentNode.get(i).asString();
if (val != "") {
this.shas.add(currentNode.get(1).asBytes());
this.getNode(val.getBytes());
}
}
}
}
}
}
private void getNode(byte[] node) {
Value currentNode = this.trie.getCache().get(node);
this.workNode(currentNode);
}
private List<byte[]> collect() {
if (this.trie.getRoot() == "") {
return null;
}
this.getNode(new Value(this.trie.getRoot()).asBytes());
return this.shas;
}
public int purge() {
List<byte[]> shas = this.collect();
for (byte[] sha : shas) {
this.trie.getCache().delete(sha);
}
return this.values.size();
}
private String getKey() {
return "";
}
private String getValue() {
return "";
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,213 @@
package org.ethereum.util;
import java.math.BigInteger;
import java.util.ArrayList;
import static java.util.Arrays.copyOfRange;
import static org.spongycastle.util.Arrays.concatenate;
import java.util.List;
/**
* Recursive Length Prefix (RLP) encoding.
*
* The purpose of RLP is to encode arbitrarily nested arrays of binary data, and
* RLP is the main encoding method used to serialize objects in Ethereum. The
* only purpose of RLP is to encode structure; encoding specific atomic data
* types (eg. strings, ints, floats) is left up to higher-order protocols; in
* Ethereum the standard is that integers are represented in big endian binary
* form. If one wishes to use RLP to encode a dictionary, the two suggested
* canonical forms are to either use [[k1,v1],[k2,v2]...] with keys in
* lexicographic order or to use the higher-level Patricia Tree encoding as
* Ethereum does.
*
* The RLP encoding function takes in an item. An item is defined as follows:
*
* - A string (ie. byte array) is an item - A list of items is an item
*
* For example, an empty string is an item, as is the string containing the word
* "cat", a list containing any number of strings, as well as more complex data
* structures like ["cat",["puppy","cow"],"horse",[[]],"pig",[""],"sheep"]. Note
* that in the context of the rest of this article, "string" will be used as a
* synonym for "a certain number of bytes of binary data"; no special encodings
* are used and no knowledge about the content of the strings is implied.
*
* See: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP
*/
public class RlpEncoder extends CompactEncoder {
/** Allow for content up to size of 2^64 bytes **/
private static double MAX_ITEM_LENGTH = Math.pow(256, 8);
/**
[5:30:35 PM] Vitalik Buterin: 56 bytes maximizes the benefit of both options
[5:30:41 PM] Vitalik Buterin: if we went with 60
[5:31:03 PM] Vitalik Buterin: then we would have only had 4 slots for long strings so RLP would not have been able to store objects above 4gb
[5:31:08 PM] Vitalik Buterin: if we went with 48
[5:31:18 PM] Vitalik Buterin: then RLP would be fine for 2^128 space, but that's way too much
[5:31:32 PM] Vitalik Buterin: so 56 and 2^64 space seems like the right place to put the cutoff
[5:31:44 PM] Vitalik Buterin: also, that's where Bitcoin's varint does the cutof
**/
private static int SIZE_THRESHOLD = 56;
/** RLP encoding rules are defined as follows: */
/*
* For a single byte whose value is in the [0x00, 0x7f] range, that byte is
* its own RLP encoding.
*/
/*
* If a string is 0-55 bytes long, the RLP encoding consists of a single
* byte with value 0x80 plus the length of the string followed by the
* string. The range of the first byte is thus [0x80, 0xb7].
*/
private static int offsetShortItem = 0x80;
/*
* If a string is more than 55 bytes long, the RLP encoding consists of a
* single byte with value 0xb7 plus the length of the length of the string
* in binary form, followed by the length of the string, followed by the
* string. For example, a length-1024 string would be encoded as
* \xb9\x04\x00 followed by the string. The range of the first byte is thus
* [0xb8, 0xbf].
*/
private static int offsetLongItem = 0xb8;
/*
* If the total payload of a list (i.e. the combined length of all its
* items) is 0-55 bytes long, the RLP encoding consists of a single byte
* with value 0xc0 plus the length of the list followed by the concatenation
* of the RLP encodings of the items. The range of the first byte is thus
* [0xc0, 0xf7].
*/
private static int offsetShortList = 0xc0;
/*
* If the total payload of a list is more than 55 bytes long, the RLP
* encoding consists of a single byte with value 0xf7 plus the length of the
* length of the list in binary form, followed by the length of the list,
* followed by the concatenation of the RLP encodings of the items. The
* range of the first byte is thus [0xf8, 0xff].
*/
private static int offsetLongList = 0xf8;
private static int maxPrefix = 0xff;
public static byte[] encode(Object input) {
Value val = new Value(input);
if (val.isList()) {
List<Object> inputArray = val.asList();
if (inputArray.size() == 0) {
return encodeLength(inputArray.size(), offsetShortList);
}
byte[] output = new byte[0];
for (Object object : inputArray) {
output = concatenate(output, encode(object));
}
byte[] prefix = encodeLength(output.length, offsetShortList);
return concatenate(prefix, output);
} else {
byte[] inputAsHex = asHex(input);
if(inputAsHex.length == 1) {
return inputAsHex;
} else {
byte[] firstByte = encodeLength(inputAsHex.length, offsetShortItem);
return concatenate(firstByte, inputAsHex);
}
}
}
public static DecodeResult decode(byte[] data, int pos) {
if (data == null || data.length < 1) {
return null;
}
int prefix = data[pos] & maxPrefix;
if (prefix == offsetShortItem) {
return new DecodeResult(pos+1, new byte[0]); // means no length or 0
} else if (prefix < offsetShortItem) {
return new DecodeResult(pos+1, new byte[] { data[pos] }); // byte is its own RLP encoding
} else if (prefix < offsetLongItem){
int len = prefix - offsetShortItem; // length of the encoded bytes
return new DecodeResult(pos+1+len, copyOfRange(data, pos+1, pos+1+len));
} else if (prefix < offsetShortList) {
int lenlen = prefix - offsetLongItem + 1; // length of length the encoded bytes
int lenbytes = toInt(copyOfRange(data, pos+1, pos+1+lenlen)); // length of encoded bytes
return new DecodeResult(pos+1+lenlen+lenbytes, copyOfRange(data, pos+1+lenlen, pos+1+lenlen+lenbytes));
} else if (prefix < offsetLongList) {
int len = prefix - offsetShortList; // length of the encoded list
int prevPos = pos; pos++;
return decodeList(data, pos, prevPos, len);
} else if (prefix < maxPrefix) {
int lenlen = prefix - offsetLongList + 1; // length of length the encoded list
int lenlist = toInt(copyOfRange(data, pos+1, pos+1+lenlen)); // length of encoded bytes
pos = pos + lenlen + 1;
int prevPos = lenlist;
return decodeList(data, pos, prevPos, lenlist);
} else {
throw new RuntimeException("Only byte values between 0x00 and 0xFF are supported, but got: " + prefix);
}
}
/** Integer limitation goes up to 2^31-1 so length can never be bigger than MAX_ITEM_LENGTH */
public static byte[] encodeLength(int length, int offset) {
if (length < SIZE_THRESHOLD) {
byte firstByte = (byte) (length + offset);
return new byte[] { firstByte };
} else if (length < MAX_ITEM_LENGTH) {
byte[] binaryLength = BigInteger.valueOf(length).toByteArray();
byte firstByte = (byte) (binaryLength.length + offset + SIZE_THRESHOLD - 1 );
return concatenate(new byte[] { firstByte }, binaryLength);
} else {
throw new RuntimeException("Input too long");
}
}
private static DecodeResult decodeList(byte[] data, int pos, int prevPos, int len) {
List<Object> slice = new ArrayList<Object>();
for (int i = 0; i < len;) {
// Get the next item in the data list and append it
DecodeResult result = decode(data, pos);
slice.add(result.getDecoded());
// Increment pos by the amount bytes in the previous read
prevPos = result.getPos();
i += (prevPos - pos);
pos = prevPos;
}
return new DecodeResult(pos, slice.toArray());
}
public static byte[] asHex(Object input) {
if (input instanceof byte[]) {
return (byte[]) input;
} else if (input instanceof String) {
String inputString = (String) input;
return inputString.getBytes();
} else if(input instanceof Integer) {
Integer inputInt = (Integer) input;
return (inputInt == 0) ? new byte[0] : BigInteger.valueOf(inputInt.longValue()).toByteArray();
} else if(input instanceof BigInteger) {
BigInteger inputBigInt = (BigInteger) input;
return (inputBigInt == BigInteger.ZERO) ? new byte[0] : inputBigInt.toByteArray();
} else if (input instanceof Value) {
Value val = (Value) input;
return asHex(val.asObj());
}
throw new RuntimeException("Unsupported type: Only accepting String, Integer and BigInteger for now");
}
/**
* Cast hex encoded value from byte[] to int
*
* Limited to Integer.MAX_VALUE: 2^32-1
*
* @param b array contains the hex values
* @return int value of all hex values together.
*/
public static int toInt(byte[] b) {
if (b == null || b.length == 0) {
return 0;
}
return new BigInteger(b).intValue();
}
}

View File

@ -49,9 +49,7 @@ public class Utils {
}
public static void printHexStringForByte(byte data){
System.out.print("[");
String hexNum = Integer.toHexString ((int) data & 0xFF);
if (((int) data & 0xFF) < 16) {
hexNum = "0" + hexNum;
@ -62,10 +60,8 @@ public class Utils {
}
public static void printHexStringForByteArray(byte[] data){
System.out.print("[");
for (int i = 0; i < data.length; ++i){
String hexNum = Integer.toHexString ((int) data[i] & 0xFF);
if (((int) data[i] & 0xFF) < 16) {
hexNum = "0" + hexNum;
@ -78,7 +74,6 @@ public class Utils {
}
public static ImageIcon getImageIcon(String resource){
URL imageURL = ClassLoader.getSystemResource(resource);
ImageIcon image = new ImageIcon(imageURL);
return image;

View File

@ -0,0 +1,160 @@
package org.ethereum.util;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import com.cedarsoftware.util.DeepEquals;
/**
* Class to encapsulate an object and provide utilities for conversion
*/
public class Value {
private Object value;
public void fromRlpEncoded(byte[] data) {
if (data.length != 0) {
this.value = RlpEncoder.decode(data, 0).getDecoded();
}
}
public Value(Object obj) {
if (obj instanceof Value) {
this.value = ((Value) obj).asObj();
} else {
this.value = obj;
}
}
/* *****************
* Convert
* *****************/
public Object asObj() {
return value;
}
public List<Object> asList() {
Object[] valueArray = (Object[]) value;
return Arrays.asList(valueArray);
}
public int asInt() {
if (isInt()) {
return (Integer) value;
} else if (isBytes()) {
return new BigInteger(asBytes()).intValue();
}
return 0;
}
public long asLong() {
if (isLong()) {
return (Long) value;
} else if (isBytes()) {
return new BigInteger(asBytes()).longValue();
}
return 0;
}
public BigInteger asBigInt() {
return (BigInteger) value;
}
public String asString() {
if (isBytes()) {
return new String((byte[]) value);
} else if (isString()) {
return (String) value;
}
return "";
}
public byte[] asBytes() {
if(isBytes()) {
return (byte[]) value;
} else if(isString()) {
return asString().getBytes();
}
return new byte[0];
}
public int[] asSlice() {
return (int[]) value;
}
public Value get(int index) {
if(isList()) {
// Guard for OutOfBounds
if (asList().size() <= index) {
return new Value(null);
}
if (index < 0) {
throw new RuntimeException("Negative index not allowed");
}
return new Value(asList().get(index));
}
// If this wasn't a slice you probably shouldn't be using this function
return new Value(null);
}
/* *****************
* Utility
* *****************/
public byte[] encode() {
return RlpEncoder.encode(value);
}
public boolean cmp(Value o) {
return DeepEquals.deepEquals(this, o);
}
/* *****************
* Checks
* *****************/
public boolean isList() {
return value != null && value.getClass().isArray() && !value.getClass().getComponentType().isPrimitive();
}
public boolean isString() {
return value instanceof String;
}
public boolean isInt() {
return value instanceof Integer;
}
public boolean isLong() {
return value instanceof Long;
}
public boolean isBigInt() {
return value instanceof BigInteger;
}
public boolean isBytes() {
return value instanceof byte[];
}
public boolean isNull() {
return value == null;
}
public boolean isEmpty() {
return !isNull() && isList() && asList().size() == 0;
}
public int length() {
if (isList()) {
return asList().size();
} else if (isBytes()) {
return asBytes().length;
} else if (isString()) {
return asString().length();
}
return 0;
}
}

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -11,6 +11,7 @@ import org.ethereum.net.message.GetChainMessage;
import org.ethereum.net.message.HelloMessage;
import org.ethereum.net.message.NotInChainMessage;
import org.ethereum.net.message.PeersMessage;
import org.ethereum.net.message.ReasonCode;
import org.ethereum.net.message.TransactionsMessage;
import org.ethereum.net.rlp.RLP;
import org.ethereum.net.rlp.RLPList;
@ -77,7 +78,6 @@ public class MessagesTest {
Utils.toHexString(helloMessage.getPeerId()).toUpperCase() );
}
/* DISCONNECT_MESSAGE */
@Test /* DisconnectMessage 1 */
@ -91,8 +91,7 @@ public class MessagesTest {
DisconnectMessage disconnectMessage = new DisconnectMessage(rlpList);
System.out.println(disconnectMessage);
assertEquals(disconnectMessage.getReason(),
DisconnectMessage.REASON_DISCONNECT_REQUESTED);
assertEquals(disconnectMessage.getReason(), ReasonCode.DISCONNECT_REQUESTED);
}
@Test /* DisconnectMessage 2 */
@ -106,8 +105,7 @@ public class MessagesTest {
DisconnectMessage disconnectMessage = new DisconnectMessage(rlpList);
System.out.println(disconnectMessage);
assertEquals(disconnectMessage.getReason(),
DisconnectMessage.REASON_TCP_ERROR);
assertEquals(disconnectMessage.getReason(), ReasonCode.TCP_ERROR);
}
/* PEERS */
@ -216,7 +214,7 @@ public class MessagesTest {
Utils.toHexString( tx.getGasPrice() ).toUpperCase());
assertEquals("64",
Utils.toHexString( tx.getGas() ).toUpperCase());
Utils.toHexString( tx.getGasLimit() ).toUpperCase());
assertEquals("NULL",
Utils.toHexString( tx.getData() ).toUpperCase());
@ -267,7 +265,7 @@ public class MessagesTest {
Utils.toHexString( tx.getGasPrice() ).toUpperCase());
assertEquals("2710",
Utils.toHexString( tx.getGas() ).toUpperCase());
Utils.toHexString( tx.getGasLimit() ).toUpperCase());
assertEquals("606956330C0D630000003359366000530A0D630000003359602060005301356000533557604060005301600054630000000C58",
Utils.toHexString( tx.getData() ).toUpperCase());
@ -302,7 +300,7 @@ public class MessagesTest {
Utils.toHexString( tx.getGasPrice() ).toUpperCase());
assertEquals("2710",
Utils.toHexString( tx.getGas() ).toUpperCase());
Utils.toHexString( tx.getGasLimit() ).toUpperCase());
assertEquals("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000002D0ACEEE7E5AB874E22CCF8D1A649F59106D74E8",
Utils.toHexString( tx.getData() ).toUpperCase());
@ -317,8 +315,7 @@ public class MessagesTest {
Utils.toHexString( tx.getSignature().r.toByteArray() ).toUpperCase());
assertEquals("1E87172A861F6C37B5A9E3A5D0D7393152A7FBE41530E5BB8AC8F35433E5931B",
Utils.toHexString( tx.getSignature().s.toByteArray() ).toUpperCase());
Utils.toHexString(tx.getSignature().s.toByteArray()).toUpperCase());
}
/* BLOCKS */
@ -357,7 +354,7 @@ public class MessagesTest {
Utils.toHexString(block.getStateHash()).toUpperCase());
assertEquals("1DCC4DE8DEC75D7AAB85B567B6CCD41AD312451B948A7413F0A142FD40D49347",
Utils.toHexString(block.getTxListHash()).toUpperCase());
Utils.toHexString(block.getTxTrieHash()).toUpperCase());
assertEquals("02471A26", Utils.toHexString(block.getDifficulty()).toUpperCase());
assertEquals(1398260220, block.getTimestamp());
@ -401,7 +398,7 @@ public class MessagesTest {
Utils.toHexString(block.getStateHash()).toUpperCase());
assertEquals("9BFE4817D274EA3EB8672E9FE848C3885B53BBBD1D7C26E6039F90FB96B942B0",
Utils.toHexString(block.getTxListHash()).toUpperCase());
Utils.toHexString(block.getTxTrieHash()).toUpperCase());
assertEquals("3FF000", Utils.toHexString(block.getDifficulty()).toUpperCase());
assertEquals(1396643511, block.getTimestamp());

View File

@ -0,0 +1,132 @@
package org.ethereum.trie;
import java.io.IOException;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBException;
import org.iq80.leveldb.DBIterator;
import org.iq80.leveldb.Range;
import org.iq80.leveldb.ReadOptions;
import org.iq80.leveldb.Snapshot;
import org.iq80.leveldb.WriteBatch;
import org.iq80.leveldb.WriteOptions;
public class MockDB implements DB {
private int addedItems;
@Override
public void close() throws IOException {
// TODO Auto-generated method stub
}
@Override
public void compactRange(byte[] arg0, byte[] arg1) throws DBException {
// TODO Auto-generated method stub
}
@Override
public WriteBatch createWriteBatch() {
// TODO Auto-generated method stub
return null;
}
@Override
public void delete(byte[] arg0) throws DBException {
// TODO Auto-generated method stub
}
@Override
public Snapshot delete(byte[] arg0, WriteOptions arg1)
throws DBException {
// TODO Auto-generated method stub
return null;
}
@Override
public byte[] get(byte[] arg0) throws DBException {
// TODO Auto-generated method stub
return null;
}
@Override
public byte[] get(byte[] arg0, ReadOptions arg1) throws DBException {
// TODO Auto-generated method stub
return null;
}
@Override
public long[] getApproximateSizes(Range... arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getProperty(String arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public Snapshot getSnapshot() {
// TODO Auto-generated method stub
return null;
}
@Override
public DBIterator iterator() {
// TODO Auto-generated method stub
return null;
}
@Override
public DBIterator iterator(ReadOptions arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public void put(byte[] arg0, byte[] arg1) throws DBException {
// TODO Auto-generated method stub
addedItems++;
}
@Override
public Snapshot put(byte[] arg0, byte[] arg1, WriteOptions arg2)
throws DBException {
// TODO Auto-generated method stub
return null;
}
@Override
public void resumeCompactions() {
// TODO Auto-generated method stub
}
@Override
public void suspendCompactions() throws InterruptedException {
// TODO Auto-generated method stub
}
@Override
public void write(WriteBatch arg0) throws DBException {
// TODO Auto-generated method stub
}
@Override
public Snapshot write(WriteBatch arg0, WriteOptions arg1)
throws DBException {
// TODO Auto-generated method stub
return null;
}
/**
* Returns the number of items added to this Mock DB
*
* @return int
*/
public int getAddedItems() {
return addedItems;
}
}

View File

@ -0,0 +1,305 @@
package org.ethereum.trie;
import static org.junit.Assert.*;
import org.ethereum.trie.Trie;
import org.junit.Test;
import com.cedarsoftware.util.DeepEquals;
public class TrieTest {
private static String LONG_STRING = "1234567890abcdefghijklmnopqrstuvwxxzABCEFGHIJKLMNOPQRSTUVWXYZ";
private static String c = "c";
private static String ca = "ca";
private static String cat = "cat";
private static String dog = "dog";
private static String doge = "doge";
private static String test = "test";
private static String dude = "dude";
private MockDB mockDb = new MockDB();
// ROOT: [ '\x16', A ]
// A: [ '', '', '', '', B, '', '', '', C, '', '', '', '', '', '', '', '' ]
// B: [ '\x00\x6f', D ]
// D: [ '', '', '', '', '', '', E, '', '', '', '', '', '', '', '', '', 'verb' ]
// E: [ '\x17', F ]
// F: [ '', '', '', '', '', '', G, '', '', '', '', '', '', '', '', '', 'puppy' ]
// G: [ '\x35', 'coin' ]
// C: [ '\x20\x6f\x72\x73\x65', 'stallion' ]
@Test
public void testEmptyKey() {
Trie trie = new Trie(mockDb);
trie.update("", dog);
String result = trie.get("");
assertEquals(dog, result);
}
@Test
public void testInsertShortString() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
String result = trie.get(cat);
assertEquals(dog, result);
}
@Test
public void testInsertLongString() {
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
String result = trie.get(cat);
assertEquals(LONG_STRING, result);
}
@Test
public void testInsertMultipleItems1() {
Trie trie = new Trie(mockDb);
trie.update(ca, dude);
trie.update(cat, dog);
trie.update(dog, test);
trie.update(doge, LONG_STRING);
trie.update(test, LONG_STRING);
String result1 = trie.get(ca);
String result2 = trie.get(cat);
String result3 = trie.get(dog);
String result4 = trie.get(doge);
String result5 = trie.get(test);
assertEquals(dude, result1);
assertEquals(dog, result2);
assertEquals(test, result3);
assertEquals(LONG_STRING, result4);
assertEquals(LONG_STRING, result5);
}
@Test
public void testInsertMultipleItems2() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
trie.update(ca, dude);
trie.update(doge, LONG_STRING);
trie.update(dog, test);
trie.update(test, LONG_STRING);
String result1 = trie.get(cat);
String result2 = trie.get(ca);
String result3 = trie.get(doge);
String result4 = trie.get(dog);
String result5 = trie.get(test);
assertEquals(dog, result1);
assertEquals(dude, result2);
assertEquals(LONG_STRING, result3);
assertEquals(test, result4);
assertEquals(LONG_STRING, result5);
}
@Test
public void testUpdateShortToShortString() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
trie.update(cat, dog+"1");
String result = trie.get(cat);
assertEquals(dog+"1", result);
}
@Test
public void testUpdateLongToLongString() {
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
trie.update(cat, LONG_STRING+"1");
String result = trie.get(cat);
assertEquals(LONG_STRING+"1", result);
}
@Test
public void testUpdateShortToLongString() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
trie.update(cat, LONG_STRING+"1");
String result = trie.get(cat);
assertEquals(LONG_STRING+"1", result);
}
@Test
public void testUpdateLongToShortString() {
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
trie.update(cat, dog+"1");
String result = trie.get(cat);
assertEquals(dog+"1", result);
}
@Test
public void testDeleteShortString1() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
Object expected = trie.getRoot();
trie.update(ca, dude);
trie.delete(ca);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
}
@Test
public void testDeleteShortString2() {
Trie trie = new Trie(mockDb);
trie.update(ca, dude);
Object expected = trie.getRoot();
trie.update(cat, dog);
trie.delete(cat);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
}
@Test
public void testDeleteShortString3() {
Trie trie = new Trie(mockDb);
trie.update(cat, dude);
Object expected = trie.getRoot();
trie.update(dog, test);
trie.delete(dog);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
}
@Test
public void testDeleteLongString1() {
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
Object expected = trie.getRoot();
trie.update(dog, LONG_STRING);
trie.delete(dog);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
}
@Test
public void testDeleteLongString2() {
Trie trie = new Trie(mockDb);
trie.update(ca, LONG_STRING);
Object expected = trie.getRoot();
trie.update(cat, LONG_STRING);
trie.delete(cat);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
}
@Test
public void testDeleteLongString3() {
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
Object expected = trie.getRoot();
trie.update(ca, LONG_STRING);
trie.delete(ca);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
}
@Test
public void testDeleteMultipleItems1() {
Trie trie = new Trie(mockDb);
trie.update(cat, dog);
trie.update(ca, dude);
trie.update(doge, LONG_STRING);
Object expected = trie.getRoot();
trie.update(dog, test);
trie.update(test, LONG_STRING);
trie.delete(dog);
trie.delete(test);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
}
@Test
public void testDeleteMultipleItems2() {
Trie trie = new Trie(mockDb);
trie.update(c, LONG_STRING);
Object expected = trie.getRoot();
trie.update(ca, LONG_STRING);
trie.update(cat, LONG_STRING);
trie.delete(ca);
trie.delete(cat);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
}
@Test
public void testDeleteAll() {
Trie trie = new Trie(mockDb);
Object expected = trie.getRoot();
trie.update(ca, dude);
trie.update(cat, dog);
trie.update(doge, LONG_STRING);
trie.delete(ca);
trie.delete(cat);
trie.delete(doge);
Object result = trie.getRoot();
assertTrue("Tries are not equal", DeepEquals.deepEquals(expected, result));
}
@Test
public void testTrieCmp() {
Trie trie1 = new Trie(mockDb);
Trie trie2 = new Trie(mockDb);
trie1.update(doge, LONG_STRING);
trie2.update(doge, LONG_STRING);
assertTrue("Expected tries to be equal", trie1.cmp(trie2));
trie1.update(dog, LONG_STRING);
trie2.update(cat, LONG_STRING);
assertFalse("Expected tries not to be equal", trie1.cmp(trie2));
}
@Test
public void testTrieSync() {
Trie trie = new Trie(mockDb);
trie.update(dog, LONG_STRING);
assertEquals("Expected no data in database", mockDb.getAddedItems(), 0);
trie.sync();
assertNotEquals("Expected data to be persisted", mockDb.getAddedItems(), 0);
}
@Test
public void TestTrieDirtyTracking() {
Trie trie = new Trie(mockDb);
trie.update(dog, LONG_STRING);
assertTrue("Expected trie to be dirty", trie.getCache().isDirty());
trie.sync();
assertFalse("Expected trie not to be dirty", trie.getCache().isDirty());
trie.update(test, LONG_STRING);
trie.getCache().undo();
assertFalse("Expected trie not to be dirty", trie.getCache().isDirty());
}
@Test
public void TestTrieReset() {
Trie trie = new Trie(mockDb);
trie.update(cat, LONG_STRING);
assertNotEquals("Expected cached nodes", 0, trie.getCache().getNodes().size());
trie.getCache().undo();
assertEquals("Expected no nodes after undo", 0, trie.getCache().getNodes().size());
}
@Test
public void testTrieCopy() {
fail("To be implemented");
}
@Test
public void testTrieUndo() {
fail("To be implemented");
}
}

View File

@ -0,0 +1,74 @@
package org.ethereum.util;
import static org.junit.Assert.*;
import org.ethereum.util.CompactEncoder;
import org.junit.Test;
public class CompactEncoderTest {
private final static byte T = 16; // terminator
@Test
public void testCompactEncodeOddCompact() {
byte[] test = new byte[] { 1, 2, 3, 4, 5 };
byte[] expectedData = new byte[] { 0x11, 0x23, 0x45 };
assertArrayEquals("odd compact encode fail", expectedData, CompactEncoder.encode(test));
}
@Test
public void testCompactEncodeEvenCompact() {
byte[] test = new byte[] { 0, 1, 2, 3, 4, 5 };
byte[] expectedData = new byte[] { 0x00, 0x01, 0x23, 0x45 };
assertArrayEquals("even compact encode fail", expectedData, CompactEncoder.encode(test));
}
@Test
public void testCompactEncodeEvenTerminated() {
byte[] test = new byte[] { 0, 15, 1, 12, 11, 8, T };
byte[] expectedData = new byte[] { 0x20, 0x0f, 0x1c, (byte) 0xb8 };
assertArrayEquals("even terminated compact encode fail", expectedData, CompactEncoder.encode(test));
}
@Test
public void testCompactEncodeOddTerminated() {
byte[] test = new byte[] { 15, 1, 12, 11, 8, T };
byte[] expectedData = new byte[] { 0x3f, 0x1c, (byte) 0xb8 };
assertArrayEquals("odd terminated compact encode fail", expectedData, CompactEncoder.encode(test));
}
@Test
public void testCompactDecodeOddCompact() {
byte[] test = new byte[] { 0x11, 0x23, 0x45 };
byte[] expected = new byte[] {1, 2, 3, 4, 5};
assertArrayEquals("odd compact decode fail", expected, CompactEncoder.decode(test));
}
@Test
public void testCompactDecodeEvenCompact() {
byte[] test = new byte[] { 0x00, 0x01, 0x23, 0x45 };
byte[] expected = new byte[] {0, 1, 2, 3, 4, 5};
assertArrayEquals("even compact decode fail", expected, CompactEncoder.decode(test));
}
@Test
public void testCompactDecodeEvenTerminated() {
byte[] test = new byte[] { 0x20, 0x0f, 0x1c, (byte) 0xb8 };
byte[] expected = new byte[] {0, 15, 1, 12, 11, 8, T};
assertArrayEquals("even terminated compact decode fail", expected, CompactEncoder.decode(test));
}
@Test
public void testCompactDecodeOddTerminated() {
byte[] test = new byte[] { 0x3f, 0x1c, (byte) 0xb8 };
byte[] expected = new byte[] {15, 1, 12, 11, 8, T};
assertArrayEquals("odd terminated compact decode fail", expected, CompactEncoder.decode(test));
}
@Test
public void testCompactHexDecode() {
byte[] test = "stallion".getBytes();
byte[] result = new byte[] { 7, 3, 7, 4, 6, 1, 6, 12, 6, 12, 6, 9, 6, 15, 6, 14, T };
assertArrayEquals(result, CompactEncoder.hexDecode(test));
}
}

View File

@ -1,4 +1,4 @@
package org.ethereum.net;
package org.ethereum.util;
import org.spongycastle.util.encoders.Hex;
import org.ethereum.crypto.HashUtil;

View File

@ -0,0 +1,425 @@
package org.ethereum.util;
import static org.ethereum.util.RlpEncoder.toInt;
import static org.ethereum.util.RlpTestData.expected14;
import static org.ethereum.util.RlpTestData.expected16;
import static org.ethereum.util.RlpTestData.result01;
import static org.ethereum.util.RlpTestData.result02;
import static org.ethereum.util.RlpTestData.result03;
import static org.ethereum.util.RlpTestData.result04;
import static org.ethereum.util.RlpTestData.result05;
import static org.ethereum.util.RlpTestData.result06;
import static org.ethereum.util.RlpTestData.result07;
import static org.ethereum.util.RlpTestData.result08;
import static org.ethereum.util.RlpTestData.result09;
import static org.ethereum.util.RlpTestData.result10;
import static org.ethereum.util.RlpTestData.result11;
import static org.ethereum.util.RlpTestData.result12;
import static org.ethereum.util.RlpTestData.result13;
import static org.ethereum.util.RlpTestData.result14;
import static org.ethereum.util.RlpTestData.result15;
import static org.ethereum.util.RlpTestData.result16;
import static org.ethereum.util.RlpTestData.test01;
import static org.ethereum.util.RlpTestData.test02;
import static org.ethereum.util.RlpTestData.test03;
import static org.ethereum.util.RlpTestData.test04;
import static org.ethereum.util.RlpTestData.test05;
import static org.ethereum.util.RlpTestData.test06;
import static org.ethereum.util.RlpTestData.test07;
import static org.ethereum.util.RlpTestData.test08;
import static org.ethereum.util.RlpTestData.test09;
import static org.ethereum.util.RlpTestData.test10;
import static org.ethereum.util.RlpTestData.test11;
import static org.ethereum.util.RlpTestData.test12;
import static org.ethereum.util.RlpTestData.test13;
import static org.ethereum.util.RlpTestData.test14;
import static org.ethereum.util.RlpTestData.test15;
import static org.ethereum.util.RlpTestData.test16;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.math.BigInteger;
import java.util.Arrays;
import org.junit.Test;
import com.cedarsoftware.util.DeepEquals;
public class RlpEncoderTest {
/************************************
* Test data from: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP
*
* Using assertEquals(String, String) instead of assertArrayEquals to see the actual content when the test fails.
*/
@Test(expected = RuntimeException.class)
public void testEncodeNull() {
RlpEncoder.encode(null);
}
@Test
public void testEncodeEmptyString() {
String test = "";
String expected = "80";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, bytesToAscii(decodeResult));
}
@Test
public void testEncodeShortString() {
String test = "dog";
String expected = "83646f67";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, bytesToAscii(decodeResult));
}
@Test
public void testEncodeSingleCharacter() {
String test = "d";
String expected = "64";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, bytesToAscii(decodeResult));
}
@Test
public void testEncodeLongString() {
String test = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"; // length = 56
String expected = "b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, bytesToAscii(decodeResult));
}
@Test
public void testEncodeZero() {
Integer test = new Integer(0);
String expected = "80";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
int result = toInt(decodeResult);
assertEquals(test, Integer.valueOf(result));
}
@Test
public void testEncodeSmallInteger() {
Integer test = new Integer(15);
String expected = "0f";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
int result = toInt(decodeResult);
assertEquals(test, Integer.valueOf(result));
}
@Test
public void testEncodeMediumInteger() {
Integer test = new Integer(1000);
String expected = "8203e8";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
int result = toInt(decodeResult);
assertEquals(test, Integer.valueOf(result));
test = new Integer(1024);
expected = "820400";
encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
result = toInt(decodeResult);
assertEquals(test, Integer.valueOf(result));
}
@Test
public void testEncodeBigInteger() {
BigInteger test = new BigInteger("100102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 16);
String expected = "a0100102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
byte[] decodeResult = (byte[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(test, new BigInteger(decodeResult));
}
@Test
public void TestEncodeEmptyList() {
String[] test = new String[0];
String expected = "c0";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertTrue(decodeResult.length == 0);
}
@Test
public void testEncodeShortStringList() {
String[] test = new String[] { "cat", "dog" };
String expected = "c88363617483646f67";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals("cat", bytesToAscii((byte[]) decodeResult[0]));
assertEquals("dog", bytesToAscii((byte[]) decodeResult[1]));
test = new String[] { "dog", "god", "cat" };
expected = "cc83646f6783676f6483636174";
encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals("dog", bytesToAscii((byte[]) decodeResult[0]));
assertEquals("god", bytesToAscii((byte[]) decodeResult[1]));
assertEquals("cat", bytesToAscii((byte[]) decodeResult[2]));
}
@Test
public void testEncodeLongStringList() {
String element1 = "cat";
String element2 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
String[] test = new String[] { element1, element2 };
String expected = "f83e83636174b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974";
byte[] encoderesult = (byte[]) RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(element1, bytesToAscii((byte[]) decodeResult[0]));
assertEquals(element2, bytesToAscii((byte[]) decodeResult[1]));
}
//multilist:
//in: [ 1, ["cat"], "dog", [ 2 ] ],
//out: "cc01c48363617483646f67c102"
//in: [ [ ["cat"], ["dog"] ], [ [1] [2] ], [] ],
//out: "cdc88363617483646f67c20102c0"
@Test
public void testEncodeMultiList() {
Object[] test = new Object[] { 1, new Object[] { "cat" }, "dog", new Object[] { 2 } };
String expected = "cc01c48363617483646f67c102";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals(1, toInt( (byte[]) decodeResult[0] ));
assertEquals("cat", bytesToAscii( ((byte[]) ((Object[]) decodeResult[1])[0] )));
assertEquals("dog", bytesToAscii( (byte[]) decodeResult[2]));
assertEquals(2, toInt( ((byte[]) ((Object[]) decodeResult[3])[0] )));
test = new Object[] { new Object[] { "cat", "dog" }, new Object[] { 1, 2 }, new Object[] { } };
expected = "cdc88363617483646f67c20102c0";
encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertEquals("cat", bytesToAscii( ((byte[]) ((Object[]) decodeResult[0])[0] )));
assertEquals("dog", bytesToAscii( ((byte[]) ((Object[]) decodeResult[0])[1] )));
assertEquals(1, toInt( ((byte[]) ((Object[]) decodeResult[1])[0] )));
assertEquals(2, toInt( ((byte[]) ((Object[]) decodeResult[1])[1] )));
assertTrue( ( ((Object[]) decodeResult[2]).length == 0 ));
}
@Test
public void testEncodeEmptyListOfList() {
// list = [ [ [], [] ], [] ],
Object[] test = new Object[] { new Object[] { new Object[] {}, new Object[] {} }, new Object[] {} };
String expected = "c4c2c0c0c0";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertTrue( decodeResult.length == 2 );
assertTrue( ( (Object[]) (decodeResult[0] ) ).length == 2);
assertTrue( ( (Object[]) (decodeResult[1] ) ).length == 0);
assertTrue( ( (Object[]) ( (Object[]) ( decodeResult[0] ) )[0]).length == 0);
assertTrue( ( (Object[]) ( (Object[]) ( decodeResult[0] ) )[1]).length == 0);
}
//The set theoretical representation of two
@Test
public void testEncodeRepOfTwoListOfList() {
//list: [ [], [[]], [ [], [[]] ] ]
Object[] test = new Object[] { new Object[] { }, new Object[] { new Object[] {} }, new Object[] { new Object[] {}, new Object[] { new Object[] { } } } };
String expected = "c7c0c1c0c3c0c1c0";
byte[] encoderesult = RlpEncoder.encode(test);
assertEquals(expected, asHex(encoderesult));
Object[] decodeResult = (Object[]) RlpEncoder.decode(encoderesult, 0).getDecoded();
assertTrue( decodeResult.length == 3 );
assertTrue( ( (Object[]) (decodeResult[0]) ).length == 0);
assertTrue( ( (Object[]) (decodeResult[1]) ).length == 1);
assertTrue( ( (Object[]) (decodeResult[2]) ).length == 2);
assertTrue( ( (Object[]) ( (Object[]) (decodeResult[1]) )[0]).length == 0);
assertTrue( ( (Object[]) ( (Object[]) (decodeResult[2]) )[0]).length == 0);
assertTrue( ( (Object[]) ( (Object[]) (decodeResult[2]) )[1]).length == 1);
assertTrue( ( (Object[]) ( (Object[]) ( (Object[]) (decodeResult[2]) )[1] )[0]).length == 0);
}
@Test
public void testRlpEncode() {
assertEquals(result01, asHex(RlpEncoder.encode(test01)));
assertEquals(result02, asHex(RlpEncoder.encode(test02)));
assertEquals(result03, asHex(RlpEncoder.encode(test03)));
assertEquals(result04, asHex(RlpEncoder.encode(test04)));
assertEquals(result05, asHex(RlpEncoder.encode(test05)));
assertEquals(result06, asHex(RlpEncoder.encode(test06)));
assertEquals(result07, asHex(RlpEncoder.encode(test07)));
assertEquals(result08, asHex(RlpEncoder.encode(test08)));
assertEquals(result09, asHex(RlpEncoder.encode(test09)));
assertEquals(result10, asHex(RlpEncoder.encode(test10)));
assertEquals(result11, asHex(RlpEncoder.encode(test11)));
assertEquals(result12, asHex(RlpEncoder.encode(test12)));
assertEquals(result13, asHex(RlpEncoder.encode(test13)));
assertEquals(result14, asHex(RlpEncoder.encode(test14)));
assertEquals(result15, asHex(RlpEncoder.encode(test15)));
assertEquals(result16, asHex(RlpEncoder.encode(test16)));
}
@Test
public void testRlpDecode() {
int pos = 0;
byte[] decodedByte;
byte[] decodedData;
Object[] decodedList;
decodedByte = (byte[]) RlpEncoder.decode(fromHex(result01), pos).getDecoded();
assertEquals(test01, toInt(decodedByte));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result02), pos).getDecoded();
assertEquals(test02, bytesToAscii(decodedData));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result03), pos).getDecoded();
assertEquals(test03, bytesToAscii(decodedData));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result04), pos).getDecoded();
assertEquals(test04, bytesToAscii(decodedData));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result05), pos).getDecoded();
assertEquals(test05, bytesToAscii(decodedData));
decodedList = (Object[]) RlpEncoder.decode(fromHex(result06), pos).getDecoded();
assertEquals(test06[0], bytesToAscii((byte[]) decodedList[0]));
assertEquals(test06[1], bytesToAscii((byte[]) decodedList[1]));
decodedList = (Object[]) RlpEncoder.decode(fromHex(result07), pos).getDecoded();
assertEquals(test07[0], bytesToAscii((byte[]) decodedList[0]));
assertEquals(test07[1], bytesToAscii((byte[]) decodedList[1]));
assertEquals(test07[2], bytesToAscii((byte[]) decodedList[2]));
// 1
decodedData = (byte[]) RlpEncoder.decode(fromHex(result08), pos).getDecoded();
assertEquals(test08, toInt(decodedData));
// 10
decodedData = (byte[]) RlpEncoder.decode(fromHex(result09), pos).getDecoded();
assertEquals(test09, toInt(decodedData));
// 100
decodedData = (byte[]) RlpEncoder.decode(fromHex(result10), pos).getDecoded();
assertEquals(test10, toInt(decodedData));
// 1000
decodedData = (byte[]) RlpEncoder.decode(fromHex(result11), pos).getDecoded();
assertEquals(test11, toInt(decodedData));
decodedData = (byte[]) RlpEncoder.decode(fromHex(result12), pos).getDecoded();
assertTrue(test12.compareTo(new BigInteger(decodedData)) == 0);
decodedData = (byte[]) RlpEncoder.decode(fromHex(result13), pos).getDecoded();
assertTrue(test13.compareTo(new BigInteger(decodedData)) == 0);
// Need to test with different expected value, because decoding doesn't recognize types
Object testObject1 = RlpEncoder.decode(fromHex(result14), pos).getDecoded();
assertTrue(DeepEquals.deepEquals(expected14, testObject1));
Object testObject2 = RlpEncoder.decode(fromHex(result15), pos).getDecoded();
assertTrue(DeepEquals.deepEquals(test15, testObject2));
// Need to test with different expected value, because decoding doesn't recognize types
Object testObject3 = RlpEncoder.decode(fromHex(result16), pos).getDecoded();
assertTrue(DeepEquals.deepEquals(expected16, testObject3));
}
@Test
public void testEncodeLength() {
int length;
int offset;
byte[] encodedLength;
String expected;
// length < 56
length = 1; offset = 128;
encodedLength = RlpEncoder.encodeLength(length, offset);
expected = "81";
assertEquals(expected, asHex(encodedLength));
// 56 > length < 2^64
length = 56; offset = 192;
encodedLength = RlpEncoder.encodeLength(length, offset);
expected = "f838";
assertEquals(expected, asHex(encodedLength));
// length > 2^64
// TODO: Fix this test - when casting double to int, information gets lost since 'int' is max (2^31)-1
double maxLength = Math.pow(256, 8); offset = 192;
try {
encodedLength = RlpEncoder.encodeLength( (int) maxLength, offset);
System.out.println("length: " + length + ", offset: " + offset + ", encoded: " + Arrays.toString(encodedLength));
fail("Expecting RuntimeException: 'Input too long'");
} catch(RuntimeException e) {
// Success!
}
}
// Code from: http://stackoverflow.com/a/9855338/459349
protected final static char[] hexArray = "0123456789abcdef".toCharArray();
private static String asHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
// Code from: http://stackoverflow.com/a/4785776/459349
private String bytesToAscii(byte[] b) {
String hex = asHex(b);
StringBuilder output = new StringBuilder();
for (int i = 0; i < hex.length(); i+=2) {
String str = hex.substring(i, i+2);
output.append((char)Integer.parseInt(str, 16));
}
return output.toString();
}
// Code from: http://stackoverflow.com/a/140861/459349
public static byte[] fromHex(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}

View File

@ -0,0 +1,59 @@
package org.ethereum.util;
import java.math.BigInteger;
public class RlpTestData {
/***********************************
* https://github.com/ethereum/tests/blob/master/rlptest.txt
*/
public static int test01 = 0;
public static String result01 = "80";
public static String test02 = "";
public static String result02 = "80";
public static String test03 = "d";
public static String result03 = "64";
public static String test04 = "cat";
public static String result04 = "83636174";
public static String test05 = "dog";
public static String result05 = "83646f67";
public static String[] test06 = new String[] { "cat", "dog" };
public static String result06 = "c88363617483646f67";
public static String[] test07 = new String[] { "dog", "god", "cat" };
public static String result07 = "cc83646f6783676f6483636174";
public static int test08 = 1;
public static String result08 = "01";
public static int test09 = 10;
public static String result09 = "0a";
public static int test10 = 100;
public static String result10 = "64";
public static int test11 = 1000;
public static String result11 = "8203e8";
public static BigInteger test12 = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639935");
public static String result12 = "a100ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
public static BigInteger test13 = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639936");
public static String result13 = "a1010000000000000000000000000000000000000000000000000000000000000000";
public static Object[] test14 = new Object[] { 1, 2, new Object[] {} };
public static String result14 = "c30102c0";
public static Object[] expected14 = new Object[] { new byte[] { 1 }, new byte[] { 2 }, new Object[] {} };
public static Object[] test15 = new Object[] { new Object[] { new Object[] {}, new Object[] {} }, new Object[] {} };
public static String result15 = "c4c2c0c0c0";
public static Object[] test16 = new Object[] { "zw", new Object[] { 4 }, "wz" };
public static String result16 = "c8827a77c10482777a";
public static Object[] expected16 = new Object[] { new byte[] { 122, 119 }, new Object[] { new byte[] { 4 } }, new byte[] { 119, 122 } };
}

View File

@ -0,0 +1,47 @@
package org.ethereum.util;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.util.Arrays;
import org.junit.Test;
public class ValueTest {
@Test
public void testCmp() {
Value val1 = new Value("hello");
Value val2 = new Value("world");
assertFalse("Expected values not to be equal", val1.cmp(val2));
Value val3 = new Value("hello");
Value val4 = new Value("hello");
assertTrue("Expected values to be equal", val3.cmp(val4));
}
@Test
public void testTypes() {
Value str = new Value("str");
assertEquals(str.asString(), "str");
Value num = new Value(1);
assertEquals(num.asInt(), 1);
Value inter = new Value(new Object[]{1});
Object[] interExp = new Object[]{1};
assertTrue(new Value(inter.asObj()).cmp(new Value(interExp)));
Value byt = new Value(new byte[]{1, 2, 3, 4});
byte[] bytExp = new byte[]{1, 2, 3, 4};
assertTrue(Arrays.equals(byt.asBytes(), bytExp));
Value bigInt = new Value(BigInteger.valueOf(10));
BigInteger bigExp = BigInteger.valueOf(10);
assertEquals(bigInt.asBigInt(), bigExp);
}
}