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