From 96773417ff8577347f2e2739da951a4deac50405 Mon Sep 17 00:00:00 2001 From: nicksavers Date: Mon, 5 May 2014 02:53:56 +0200 Subject: [PATCH 1/7] Cleanup code --- .../main/java/org/ethereum/crypto/ECKey.java | 7 +- .../main/java/org/ethereum/geodb/IpGeoDB.java | 5 -- .../java/org/ethereum/manager/MainData.java | 8 +- .../org/ethereum/net/MessageDeserializer.java | 48 +++++------- .../net/client/EthereumProtocolHandler.java | 4 +- .../ethereum/net/message/BlocksMessage.java | 16 ++-- .../ethereum/net/message/StaticMessages.java | 2 - .../net/message/TransactionsMessage.java | 12 +-- .../net/vo/{BlockData.java => Block.java} | 30 +++----- .../java/org/ethereum/net/vo/PeerData.java | 24 ++---- ...{TransactionData.java => Transaction.java} | 71 ++++++------------ .../main/java/org/ethereum/util/Utils.java | 5 -- ...ereum-icon-old.png => ethereum-icon-1.png} | Bin ...um-icon - Copy.png => ethereum-icon-2.png} | Bin .../java/org/ethereum/block/BlockTest.java | 4 +- .../java/org/ethereum/net/MessagesTest.java | 34 ++------- 16 files changed, 93 insertions(+), 177 deletions(-) rename ethereumj-core/src/main/java/org/ethereum/net/vo/{BlockData.java => Block.java} (85%) rename ethereumj-core/src/main/java/org/ethereum/net/vo/{TransactionData.java => Transaction.java} (70%) rename ethereumj-core/src/main/resources/{ethereum-icon-old.png => ethereum-icon-1.png} (100%) rename ethereumj-core/src/main/resources/{ethereum-icon - Copy.png => ethereum-icon-2.png} (100%) diff --git a/ethereumj-core/src/main/java/org/ethereum/crypto/ECKey.java b/ethereumj-core/src/main/java/org/ethereum/crypto/ECKey.java index d42d1bdf..5e4dec15 100644 --- a/ethereumj-core/src/main/java/org/ethereum/crypto/ECKey.java +++ b/ethereumj-core/src/main/java/org/ethereum/crypto/ECKey.java @@ -27,7 +27,6 @@ import java.util.Arrays; import javax.annotation.Nullable; -import org.ethereum.util.ByteUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.asn1.sec.SECNamedCurves; @@ -297,6 +296,12 @@ public class ECKey implements Serializable { public static ECDSASignature fromComponents(byte[] r, byte[] s) { return new ECDSASignature(new BigInteger(r), new BigInteger(s)); } + + public static ECDSASignature fromComponents(byte[] r, byte[] s, byte v) { + ECDSASignature signature = new ECDSASignature(new BigInteger(r), new BigInteger(s)); + signature.v = v; + return signature; + } /** * Will automatically adjust the S component to be less than or equal to half the curve order, if necessary. diff --git a/ethereumj-core/src/main/java/org/ethereum/geodb/IpGeoDB.java b/ethereumj-core/src/main/java/org/ethereum/geodb/IpGeoDB.java index 52ac2874..702b7ba3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/geodb/IpGeoDB.java +++ b/ethereumj-core/src/main/java/org/ethereum/geodb/IpGeoDB.java @@ -16,11 +16,9 @@ public class IpGeoDB { // change static{ try { - URL geiIpDBFile = ClassLoader.getSystemResource("GeoLiteCity.dat"); File file = new File(geiIpDBFile.toURI()); cl = new LookupService(file); - } catch (Throwable e) { e.printStackTrace(); } @@ -28,17 +26,14 @@ public class IpGeoDB { // change private static LookupService cl; - public static Location getLocationForIp(InetAddress ip){ try { return cl.getLocation(ip); } catch (Throwable e) { - // todo: think about this exception, maybe you can do something more reasonable System.out.println(e.getMessage()); // e.printStackTrace(); } - return null; } } diff --git a/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java b/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java index d897ad13..fd573583 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java @@ -3,9 +3,9 @@ package org.ethereum.manager; import com.maxmind.geoip.Location; import org.ethereum.geodb.IpGeoDB; -import org.ethereum.net.vo.BlockData; +import org.ethereum.net.vo.Block; import org.ethereum.net.vo.PeerData; -import org.ethereum.net.vo.TransactionData; +import org.ethereum.net.vo.Transaction; import java.util.*; @@ -30,6 +30,6 @@ public class MainData { } } - public void addBlocks(List blocks) {} - public void addTransactions(List transactions) {} + public void addBlocks(List blocks) {} + public void addTransactions(List transactions) {} } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/MessageDeserializer.java b/ethereumj-core/src/main/java/org/ethereum/net/MessageDeserializer.java index e5c0ef8e..a3515de6 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/MessageDeserializer.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/MessageDeserializer.java @@ -15,75 +15,51 @@ public class MessageDeserializer { int pos = startPos; while(pos < endPos){ - // It's a list with a payload more than 55 bytes // data[0] - 0xF7 = how many next bytes allocated // for the length of the list if ((msgData[pos] & 0xFF) >= 0xF7){ - - byte lenghtOfLenght = (byte) (msgData[pos] - 0xF7); - byte pow = (byte) (lenghtOfLenght - 1); - - int length = 0; - for (int i = 1; i <= lenghtOfLenght; ++i){ - length += msgData[pos + i] << (8 * pow); - pow--; - } - + byte lengthOfLength = (byte) (msgData[pos] - 0xF7); + int length = calcLength(lengthOfLength, msgData, pos); // now we can parse an item for data[1]..data[length] System.out.println("-- level: [" + level + "] Found big list length: " + length); - deserialize(msgData, level + 1, pos + lenghtOfLenght + 1, pos + lenghtOfLenght + length); - pos += lenghtOfLenght + length + 1 ; + deserialize(msgData, level + 1, pos + lengthOfLength + 1, pos + lengthOfLength + length); + pos += lengthOfLength + length + 1 ; continue; } - // It's a list with a payload less than 55 bytes if ((msgData[pos] & 0xFF) >= 0xC0 && (msgData[pos] & 0xFF) < 0xF7){ - byte length = (byte) (msgData[pos] - 0xC0); System.out.println("-- level: [" + level + "] Found small list length: " + length); deserialize(msgData, level + 1, pos + 1, pos + length + 1); pos += 1 + length; continue; } - // It's an item with a payload more than 55 bytes // data[0] - 0xB7 = how much next bytes allocated for // the length of the string if ((msgData[pos] & 0xFF) >= 0xB7 && (msgData[pos] & 0xFF) < 0xC0) { - - byte lenghtOfLenght = (byte) (msgData[pos] - 0xB7); - byte pow = (byte) (lenghtOfLenght - 1); - - int length = 0; - for (int i = 1; i <= lenghtOfLenght; ++i){ - length += msgData[pos + i] << (8 * pow); - pow--; - } + byte lengthOfLength = (byte) (msgData[pos] - 0xB7); + int length = calcLength(lengthOfLength, msgData, pos); // now we can parse an item for data[1]..data[length] System.out.println("-- level: [" + level + "] Found big item length: " + length); - pos += lenghtOfLenght + length + 1 ; + pos += lengthOfLength + length + 1 ; continue; } - - // It's an item less than 55 bytes long, // data[0] - 0x80 == lenght of the item if ((msgData[pos] & 0xFF) > 0x80 && (msgData[pos] & 0xFF) < 0xB7) { - byte length = (byte) (msgData[pos] - 0x80); System.out.println("-- level: [" + level + "] Found small item length: " + length); pos += 1 + length; continue; } - // null item if ((msgData[pos] & 0xFF) == 0x80){ System.out.println("-- level: [" + level + "] Found null item: "); pos += 1; continue; } - // single byte item if ((msgData[pos] & 0xFF) < 0x80) { System.out.println("-- level: [" + level + "] Found single item: "); @@ -92,4 +68,14 @@ public class MessageDeserializer { } } } + + private static int calcLength(int lengthOfLength, byte[] msgData, int pos) { + byte pow = (byte) (lengthOfLength - 1); + int length = 0; + for (int i = 1; i <= lengthOfLength; ++i){ + length += msgData[pos + i] << (8 * pow); + pow--; + } + return length; + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java index 2847f1d9..bd6f6f94 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java @@ -35,7 +35,7 @@ import org.ethereum.net.message.PeersMessage; import org.ethereum.net.message.StaticMessages; import org.ethereum.net.message.TransactionsMessage; import org.ethereum.net.rlp.RLPList; -import org.ethereum.net.vo.BlockData; +import org.ethereum.net.vo.Block; import org.ethereum.util.Utils; import org.spongycastle.util.encoders.Hex; @@ -266,7 +266,7 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter { RLP.parseObjects(payload, rlpList); BlocksMessage blocksMessage = new BlocksMessage(rlpList); - List blockList = blocksMessage.getBlockDataList(); + List blockList = blocksMessage.getBlockDataList(); MainData.instance.addBlocks(blockList); System.out.println(blocksMessage); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/BlocksMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/message/BlocksMessage.java index 33cd3a4d..eaa48756 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/message/BlocksMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/BlocksMessage.java @@ -8,8 +8,8 @@ import static org.ethereum.net.Command.BLOCKS; import org.ethereum.net.Command; import org.ethereum.net.rlp.RLPItem; import org.ethereum.net.rlp.RLPList; -import org.ethereum.net.vo.BlockData; -import org.ethereum.net.vo.TransactionData; +import org.ethereum.net.vo.Block; +import org.ethereum.net.vo.Transaction; /** * www.ethereumJ.com @@ -18,7 +18,7 @@ import org.ethereum.net.vo.TransactionData; */ public class BlocksMessage extends Message { - private List blockDataList = new ArrayList(); + private List blockDataList = new ArrayList(); public BlocksMessage(RLPList rawData) { super(rawData); @@ -34,7 +34,7 @@ public class BlocksMessage extends Message { for (int i = 1; i < paramsList.size(); ++i){ RLPList rlpData = ((RLPList)paramsList.getElement(i)); - BlockData blockData = new BlockData(rlpData); + Block blockData = new Block(rlpData); this.blockDataList.add(blockData); } parsed = true; @@ -45,7 +45,7 @@ public class BlocksMessage extends Message { return null; } - public List getBlockDataList() { + public List getBlockDataList() { if (!parsed) parseRLP(); return blockDataList; } @@ -53,11 +53,11 @@ public class BlocksMessage extends Message { public String toString() { StringBuffer sb = new StringBuffer(); - for (BlockData blockData : this.getBlockDataList()){ + for (Block blockData : this.getBlockDataList()){ sb.append(" ").append( blockData.toString() ).append("\n"); - List transactions = blockData.getTransactionsList(); - for (TransactionData transactionData : transactions){ + List transactions = blockData.getTransactionsList(); + for (Transaction transactionData : transactions){ sb.append("[").append(transactionData).append("]\n"); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java b/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java index ff894675..de293a28 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java @@ -46,9 +46,7 @@ public class StaticMessages { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0xC2, (byte) 0x01, (byte) 0x08}; - public static final byte[] GET_CHAIN = { - (byte) 0x22, (byte) 0x40, (byte) 0x08, (byte) 0x91, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x27, (byte) 0xF8, (byte) 0x25, (byte) 0x14, (byte) 0xA0, (byte) 0xAB, (byte) 0x6B, (byte) 0x9A, (byte) 0x56, (byte) 0x13, (byte) 0x97, (byte) 0x0F, (byte) 0xAA, (byte) 0x77, (byte) 0x1B, (byte) 0x12, (byte) 0xD4, (byte) 0x49, diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java index 1e1cd6c6..cb991896 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java @@ -4,7 +4,7 @@ import static org.ethereum.net.Command.TRANSACTIONS; import org.ethereum.net.Command; import org.ethereum.net.rlp.RLPItem; import org.ethereum.net.rlp.RLPList; -import org.ethereum.net.vo.TransactionData; +import org.ethereum.net.vo.Transaction; import java.util.ArrayList; import java.util.List; @@ -16,7 +16,7 @@ import java.util.List; */ public class TransactionsMessage extends Message { - private List transactions = new ArrayList(); + private List transactions = new ArrayList(); public TransactionsMessage() { } @@ -33,17 +33,17 @@ public class TransactionsMessage extends Message { throw new Error("TransactionMessage: parsing for mal data"); } - transactions = new ArrayList(); + transactions = new ArrayList(); int size = paramsList.getList().size(); for (int i = 1; i < size; ++i){ RLPList rlpTxData = (RLPList) paramsList.getElement(i); - TransactionData tx = new TransactionData(rlpTxData); + Transaction tx = new Transaction(rlpTxData); transactions.add(tx); } parsed = true; } - public List getTransactions() { + public List getTransactions() { if (!parsed) parseRLP(); return transactions; } @@ -56,7 +56,7 @@ public class TransactionsMessage extends Message { public String toString(){ if(!parsed) parseRLP(); StringBuffer sb = new StringBuffer(); - for (TransactionData transactionData : transactions){ + for (Transaction transactionData : transactions){ sb.append(" ").append(transactionData).append("\n"); } return "Transactions Message [\n" + sb.toString() + " ]"; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/BlockData.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java similarity index 85% rename from ethereumj-core/src/main/java/org/ethereum/net/vo/BlockData.java rename to ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java index a91ad7e6..15efefe5 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/vo/BlockData.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java @@ -7,7 +7,6 @@ import org.ethereum.net.rlp.RLPList; import org.ethereum.util.Utils; import java.math.BigInteger; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -16,10 +15,10 @@ import java.util.List; * User: Roman Mandeleil * Created on: 13/04/14 19:34 */ -public class BlockData { +public class Block { - RLPList rawData; - boolean parsed = false; + private RLPList rawData; + private boolean parsed = false; private byte[] hash; private byte[] parentHash; @@ -33,15 +32,15 @@ public class BlockData { private byte[] extraData; private byte[] nonce; - List transactionsList = new ArrayList(); - List uncleList = new ArrayList(); + List transactionsList = new ArrayList(); + List uncleList = new ArrayList(); - public BlockData(RLPList rawData) { + public Block(RLPList rawData) { this.rawData = rawData; this.parsed = false; } - public BlockData(byte[] parentHash, byte[] unclesHash, byte[] coinbase, byte[] stateHash, byte[] txListHash, byte[] difficulty, long timestamp, byte[] extraData, byte[] nonce, List transactionsList, List uncleList) { + public Block(byte[] parentHash, byte[] unclesHash, byte[] coinbase, byte[] stateHash, byte[] txListHash, byte[] difficulty, long timestamp, byte[] extraData, byte[] nonce, List transactionsList, List uncleList) { this.parentHash = parentHash; this.unclesHash = unclesHash; this.coinbase = coinbase; @@ -54,7 +53,6 @@ public class BlockData { this.transactionsList = transactionsList; this.uncleList = uncleList; this.parsed = true; - } // [parent_hash, uncles_hash, coinbase, state_root, tx_list_hash, difficulty, timestamp, extradata, nonce] @@ -76,24 +74,18 @@ public class BlockData { this.extraData = ((RLPItem) params.get(7)).getData(); this.nonce = ((RLPItem) params.get(8)).getData(); - // parse transactions List transactions = ((RLPList) rawData.getElement(1)).getList(); - for (RLPElement rlpTx : transactions){ - - TransactionData tx = new TransactionData((RLPList)rlpTx); + Transaction tx = new Transaction((RLPList)rlpTx); this.transactionsList.add(tx); } - // parse uncles List uncleBlocks = ((RLPList) rawData.getElement(2)).getList(); for (RLPElement rawUncle : uncleBlocks){ - - BlockData blockData = new BlockData((RLPList)rawUncle); + Block blockData = new Block((RLPList)rawUncle); this.uncleList.add(blockData); } - this.parsed = true; } @@ -148,12 +140,12 @@ public class BlockData { return nonce; } - public List getTransactionsList() { + public List getTransactionsList() { if (!parsed) parseRLP(); return transactionsList; } - public List getUncleList() { + public List getUncleList() { if (!parsed) parseRLP(); return uncleList; } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/PeerData.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/PeerData.java index d98a81ca..0dbba4d7 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/vo/PeerData.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/PeerData.java @@ -1,7 +1,6 @@ package org.ethereum.net.vo; import org.spongycastle.util.encoders.Hex; -import org.ethereum.net.rlp.RLPList; import java.net.InetAddress; import java.net.UnknownHostException; @@ -13,39 +12,27 @@ import java.net.UnknownHostException; */ public class PeerData { - RLPList rawData; - boolean parsed = false; + private byte[] ip; + private short port; + private byte[] peerId; - byte[] ip; - short port; - byte[] peerId; - - transient boolean isOnline = false; - transient long lastCheckTime = 0; - - public PeerData(RLPList rlpList){ - rawData = rlpList; - parsed = false; - } + private transient boolean isOnline = false; + private transient long lastCheckTime = 0; public PeerData(byte[] ip, short port, byte[] peerId) { this.ip = ip; this.port = port; this.peerId = peerId; - parsed = true; } public InetAddress getInetAddress(){ - InetAddress addr = null; - try { addr = InetAddress.getByAddress(ip); } catch (UnknownHostException e) { e.printStackTrace(); throw new Error("malformed ip"); } - return addr; } @@ -77,7 +64,6 @@ public class PeerData { this.lastCheckTime = lastCheckTime; } - @Override public String toString() { return "Peer: [ ip=" + getInetAddress()+ ", port=" + getPort() + ", peerId=" + Hex.toHexString( getPeerId() ) + "]"; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/TransactionData.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java similarity index 70% rename from ethereumj-core/src/main/java/org/ethereum/net/vo/TransactionData.java rename to ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java index e910f884..0787885e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/vo/TransactionData.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java @@ -10,44 +10,41 @@ import org.ethereum.util.Utils; * User: Roman Mandeleil * Created on: 21/04/14 09:19 */ -public class TransactionData { - - RLPList rawData; - boolean parsed = false; +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 ] - byte[] hash; - byte[] nonce; - byte[] value; + private byte[] hash; + private byte[] nonce; + private byte[] value; // In creation transaction the receive address is - 0 - byte[] receiveAddress; - byte[] gasPrice; - byte[] gas; + private byte[] receiveAddress; + private byte[] gasPrice; + private byte[] gas; // Contract creation [data] will hold the contract // for other transaction [data] can hold data - byte[] data; - byte[] init; - + private byte[] data; + private byte[] init; // Signature - byte signatureV; - byte[] signatureR; - byte[] signatureS; + private byte signatureV; + private byte[] signatureR; + private byte[] signatureS; - - public TransactionData(RLPList rawData) { + public Transaction(RLPList rawData) { this.rawData = rawData; parsed = false; } - public TransactionData(byte[] nonce, byte[] value, byte[] recieveAddress, byte[] gasPrice, byte[] gas, byte[] data, byte signatureV, byte[] signatureR, byte[] signatureS) { + public Transaction(byte[] nonce, byte[] value, byte[] recieveAddress, byte[] gasPrice, byte[] gas, byte[] data, byte signatureV, byte[] signatureR, byte[] signatureS) { this.nonce = nonce; this.value = value; this.receiveAddress = recieveAddress; @@ -60,31 +57,22 @@ public class TransactionData { parsed = true; } - public void rlpParse(){ - if (rawData.size() == 9){ // Simple transaction + 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.data = ((RLPItem) rawData.getElement(5)).getData(); - 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.data = ((RLPItem) rawData.getElement(5)).getData(); + if (rawData.size() == 9){ // Simple transaction this.signatureV = ((RLPItem) rawData.getElement(6)).getData()[0]; this.signatureR = ((RLPItem) rawData.getElement(7)).getData(); this.signatureS = ((RLPItem) rawData.getElement(8)).getData(); } else if (rawData.size() == 10){ // Contract creation transaction - - 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.data = ((RLPItem) rawData.getElement(5)).getData(); this.init = ((RLPItem) rawData.getElement(6)).getData(); this.signatureV = ((RLPItem) rawData.getElement(7)).getData()[0]; this.signatureR = ((RLPItem) rawData.getElement(8)).getData(); @@ -95,7 +83,6 @@ public class TransactionData { this.parsed = true; } - public RLPList getRawData() { return rawData; } @@ -110,61 +97,51 @@ public class TransactionData { } public byte[] getNonce() { - if (!parsed) rlpParse(); return nonce; } public byte[] getValue() { - if (!parsed) rlpParse(); return value; } public byte[] getReceiveAddress() { - if (!parsed) rlpParse(); return receiveAddress; } public byte[] getGasPrice() { - if (!parsed) rlpParse(); return gasPrice; } public byte[] getGas() { - if (!parsed) rlpParse(); return gas; } public byte[] getData() { - if (!parsed) rlpParse(); return data; } public byte[] getInit() { - if (!parsed) rlpParse(); return init; } public byte getSignatureV() { - if (!parsed) rlpParse(); return signatureV; } public byte[] getSignatureR() { - if (!parsed) rlpParse(); return signatureR; } public byte[] getSignatureS() { - if (!parsed) rlpParse(); return signatureS; } diff --git a/ethereumj-core/src/main/java/org/ethereum/util/Utils.java b/ethereumj-core/src/main/java/org/ethereum/util/Utils.java index 223da3d3..07a1f4c3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/Utils.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/Utils.java @@ -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; diff --git a/ethereumj-core/src/main/resources/ethereum-icon-old.png b/ethereumj-core/src/main/resources/ethereum-icon-1.png similarity index 100% rename from ethereumj-core/src/main/resources/ethereum-icon-old.png rename to ethereumj-core/src/main/resources/ethereum-icon-1.png diff --git a/ethereumj-core/src/main/resources/ethereum-icon - Copy.png b/ethereumj-core/src/main/resources/ethereum-icon-2.png similarity index 100% rename from ethereumj-core/src/main/resources/ethereum-icon - Copy.png rename to ethereumj-core/src/main/resources/ethereum-icon-2.png diff --git a/ethereumj-core/src/test/java/org/ethereum/block/BlockTest.java b/ethereumj-core/src/test/java/org/ethereum/block/BlockTest.java index 8ab86f09..8230f7a7 100644 --- a/ethereumj-core/src/test/java/org/ethereum/block/BlockTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/block/BlockTest.java @@ -4,7 +4,7 @@ import org.spongycastle.util.encoders.Hex; import org.ethereum.crypto.HashUtil; import org.ethereum.net.RLP; import org.ethereum.net.rlp.RLPList; -import org.ethereum.net.vo.BlockData; +import org.ethereum.net.vo.Block; import org.junit.Test; import java.io.IOException; @@ -131,7 +131,7 @@ public class BlockTest { RLPList rlpList = new RLPList(); RLP.parseObjects(payload, rlpList); - BlockData blockData = new BlockData(rlpList); + Block blockData = new Block(rlpList); RLPList.recursivePrint(rlpList); } } diff --git a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java index 42ea4b5b..1cd22c34 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java @@ -13,9 +13,9 @@ import org.ethereum.net.message.NotInChainMessage; import org.ethereum.net.message.PeersMessage; import org.ethereum.net.message.TransactionsMessage; import org.ethereum.net.rlp.RLPList; -import org.ethereum.net.vo.BlockData; +import org.ethereum.net.vo.Block; import org.ethereum.net.vo.PeerData; -import org.ethereum.net.vo.TransactionData; +import org.ethereum.net.vo.Transaction; import org.ethereum.util.Utils; import org.junit.Test; import org.spongycastle.util.encoders.Hex; @@ -76,7 +76,6 @@ public class MessagesTest { Utils.toHexString(helloMessage.getPeerId()).toUpperCase() ); } - /* DISCONNECT_MESSAGE */ @Test /* DisconnectMessage 1 */ @@ -109,7 +108,6 @@ public class MessagesTest { DisconnectMessage.REASON_TCP_ERROR); } - /* PEERS */ @Test /* PeersMessage 1*/ @@ -131,7 +129,6 @@ public class MessagesTest { assertEquals(30303, peerData.getPort()); assertEquals("82A8A5831D3B4FB76CF130CDC8A2B162A85D005D82A1DCC9B73239035EADE6347EDE2FFC86571ABE348EA38699CE886AA3D425FE58182C433434AB4CFD7B5B88", Utils.toHexString( peerData.getPeerId() ).toUpperCase()); - } @Test /* PeersMessage 2 */ @@ -160,7 +157,6 @@ public class MessagesTest { assertEquals(30303, peerData.getPort()); assertEquals("F6155F1A60143B7D9D5D1A440D7D52FE6809F69E0C6F1E0024457E0D71DD88ADE3B13AAA940C89AC0610952B48BD832C42E343A13E61FFDB06010CFFC345E053", Utils.toHexString( peerData.getPeerId() ).toUpperCase()); - } @Test /* Peers msg parsing performance*/ @@ -199,7 +195,7 @@ public class MessagesTest { assertEquals(1, transactionsMessage.getTransactions().size()); - TransactionData tx = + Transaction tx = transactionsMessage.getTransactions().get(0); assertEquals("558A3797E0DD3FBFAF761F1ADD6749C7D5DB313FDAC5CBA59F40E28AF7BBACD1", @@ -236,7 +232,6 @@ public class MessagesTest { Utils.toHexString( tx.getSignatureS() ).toUpperCase()); } - @Test /* Transactions message 2 */ public void test_9(){ @@ -251,7 +246,7 @@ public class MessagesTest { assertEquals(3, transactionsMessage.getTransactions().size()); - TransactionData tx = + Transaction tx = transactionsMessage.getTransactions().get(0); assertEquals("4B7D9670A92BF120D5B43400543B69304A14D767CF836A7F6ABFF4EDDE092895", @@ -287,7 +282,6 @@ public class MessagesTest { assertEquals("6D254E662BF7450DD8D835160CBB053463FED0B53F2CDD7F3EA8731919C8E8CC", Utils.toHexString( tx.getSignatureS() ).toUpperCase()); - tx = transactionsMessage.getTransactions().get(2); assertEquals("B0251A1BB20B44459DB5B5444AB53EDD9E12C46D0BA07FA401A797BEB967D53C", @@ -322,10 +316,8 @@ public class MessagesTest { assertEquals("1E87172A861F6C37B5A9E3A5D0D7393152A7FBE41530E5BB8AC8F35433E5931B", Utils.toHexString(tx.getSignatureS()).toUpperCase()); - } - /* BLOCKS */ @Test /* BlocksMessage parsing 1*/ @@ -339,12 +331,12 @@ public class MessagesTest { RLP.parseObjects(payload, rlpList); BlocksMessage blocksMessage = new BlocksMessage(rlpList); - List list = blocksMessage.getBlockDataList(); + List list = blocksMessage.getBlockDataList(); System.out.println(blocksMessage); assertEquals(1, list.size()); - BlockData block = list.get(0); + Block block = list.get(0); assertEquals("36A24B56C6104E5A5C0E70B0553F1A4D6109D065D718D7443A6A475EC8C83905", Utils.toHexString(block.getHash()).toUpperCase()); @@ -373,7 +365,6 @@ public class MessagesTest { Utils.toHexString(block.getNonce()).toUpperCase()); } - @Test /* BlocksMessage really big message parsing */ public void test11(){ @@ -384,13 +375,13 @@ public class MessagesTest { RLP.parseObjects(payload, rlpList); BlocksMessage blocksMessage = new BlocksMessage(rlpList); - List list = blocksMessage.getBlockDataList(); + List list = blocksMessage.getBlockDataList(); System.out.println(blocksMessage); assertEquals(32, list.size()); - BlockData block = list.get(31); + Block block = list.get(31); assertEquals("518916DFB79C390BD7BFF75712174512C2F96BEC42A3F573355507AD1588CE0C", Utils.toHexString(block.getHash()).toUpperCase()); @@ -419,11 +410,8 @@ public class MessagesTest { Utils.toHexString(block.getNonce()).toUpperCase()); System.out.println(blocksMessage); - } - - /* GET_CHAIN */ @Test /* GET_CHAIN message parsing*/ @@ -449,12 +437,8 @@ public class MessagesTest { assertEquals("03AF21F3939C29C231200B1F790F16421A8923254CBF2A90455B9B8F28BE4562", Utils.toHexString( getChainMessage.getBlockHashList().get(25) ).toUpperCase()); - - - } - /* NOT_IN_CHAIN */ @Test /* NotInChainMessage parsing 1 */ @@ -472,7 +456,5 @@ public class MessagesTest { assertEquals("E5E441F0877116011CCDECE2501A50B40C40418377037E16D0282B2B5E347138", Utils.toHexString(notInChainMessage.getHash()).toUpperCase()); } - - } From 1f3455f5c6755ac17a72a8c4d8b38da46af1b39a Mon Sep 17 00:00:00 2001 From: nicksavers Date: Mon, 5 May 2014 03:00:49 +0200 Subject: [PATCH 2/7] Use ECDSASignature wrapper for Transaction signature --- .../main/java/org/ethereum/net/vo/Block.java | 5 +- .../java/org/ethereum/net/vo/Transaction.java | 47 +++++++------------ .../java/org/ethereum/net/MessagesTest.java | 18 +++---- 3 files changed, 28 insertions(+), 42 deletions(-) diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java index 15efefe5..1147101a 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java @@ -32,8 +32,8 @@ public class Block { private byte[] extraData; private byte[] nonce; - List transactionsList = new ArrayList(); - List uncleList = new ArrayList(); + private List transactionsList = new ArrayList(); + private List uncleList = new ArrayList(); public Block(RLPList rawData) { this.rawData = rawData; @@ -151,7 +151,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(); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java index 0787885e..5777532e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java @@ -1,5 +1,6 @@ package org.ethereum.net.vo; +import org.ethereum.crypto.ECKey.ECDSASignature; import org.ethereum.crypto.HashUtil; import org.ethereum.net.rlp.RLPItem; import org.ethereum.net.rlp.RLPList; @@ -35,25 +36,21 @@ public class Transaction { private byte[] init; // Signature - private byte signatureV; - private byte[] signatureR; - private byte[] signatureS; + private ECDSASignature signature; public Transaction(RLPList rawData) { this.rawData = rawData; parsed = false; } - public Transaction(byte[] nonce, byte[] value, byte[] recieveAddress, byte[] gasPrice, byte[] gas, byte[] data, byte signatureV, byte[] signatureR, byte[] signatureS) { + public Transaction(byte[] nonce, byte[] value, byte[] recieveAddress, byte[] gasPrice, byte[] gas, byte[] data, byte v, byte[] r, byte[] s) { this.nonce = nonce; this.value = value; this.receiveAddress = recieveAddress; this.gasPrice = gasPrice; this.gas = gas; this.data = data; - this.signatureV = signatureV; - this.signatureR = signatureR; - this.signatureS = signatureS; + this.signature = ECDSASignature.fromComponents(r, s, v); parsed = true; } @@ -68,16 +65,16 @@ public class Transaction { this.data = ((RLPItem) rawData.getElement(5)).getData(); if (rawData.size() == 9){ // Simple transaction - this.signatureV = ((RLPItem) rawData.getElement(6)).getData()[0]; - this.signatureR = ((RLPItem) rawData.getElement(7)).getData(); - this.signatureS = ((RLPItem) rawData.getElement(8)).getData(); - + byte v = ((RLPItem) rawData.getElement(6)).getData()[0]; + 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.signatureV = ((RLPItem) rawData.getElement(7)).getData()[0]; - this.signatureR = ((RLPItem) rawData.getElement(8)).getData(); - this.signatureS = ((RLPItem) rawData.getElement(9)).getData(); - + byte v = ((RLPItem) rawData.getElement(7)).getData()[0]; + 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"); this.parsed = true; @@ -131,19 +128,9 @@ public class Transaction { return init; } - public byte getSignatureV() { + public ECDSASignature getSignature() { if (!parsed) rlpParse(); - return signatureV; - } - - public byte[] getSignatureR() { - if (!parsed) rlpParse(); - return signatureR; - } - - public byte[] getSignatureS() { - if (!parsed) rlpParse(); - return signatureS; + return signature; } @Override @@ -157,9 +144,9 @@ public class Transaction { ", gas=" + Utils.toHexString(gas) + ", data=" + Utils.toHexString(data) + ", init=" + Utils.toHexString(init) + - ", signatureV=" + signatureV + - ", signatureR=" + Utils.toHexString(signatureR) + - ", signatureS=" + Utils.toHexString(signatureS) + + ", signatureV=" + signature.v + + ", signatureR=" + Utils.toHexString(signature.r.toByteArray()) + + ", signatureS=" + Utils.toHexString(signature.s.toByteArray()) + ']'; } } diff --git a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java index 1cd22c34..5812f3e3 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java @@ -223,13 +223,13 @@ public class MessagesTest { Utils.toHexString( tx.getInit() ).toUpperCase()); assertEquals("1B", - Utils.toHexString( new byte[] {tx.getSignatureV()} ).toUpperCase()); + Utils.toHexString( new byte[] {tx.getSignature().v} ).toUpperCase()); assertEquals("5E3868194605F1647593B842725818CCFA6A38651A728715133A8E97CDCFAC54", - Utils.toHexString( tx.getSignatureR() ).toUpperCase()); + Utils.toHexString( tx.getSignature().r.toByteArray() ).toUpperCase()); assertEquals("0FF91628D04B215EBCCFD5F4FC34CC1B45DF32F6B4609FBB0DE42E8522264467", - Utils.toHexString( tx.getSignatureS() ).toUpperCase()); + Utils.toHexString( tx.getSignature().s.toByteArray() ).toUpperCase()); } @Test /* Transactions message 2 */ @@ -274,13 +274,13 @@ public class MessagesTest { Utils.toHexString( tx.getInit() ).toUpperCase()); assertEquals("1C", - Utils.toHexString( new byte[] {tx.getSignatureV()} ).toUpperCase()); + Utils.toHexString( new byte[] {tx.getSignature().v} ).toUpperCase()); assertEquals("7F6EB94576346488C6253197BDE6A7E59DDC36F2773672C849402AA9C402C3C4", - Utils.toHexString( tx.getSignatureR() ).toUpperCase()); + Utils.toHexString( tx.getSignature().r.toByteArray() ).toUpperCase()); assertEquals("6D254E662BF7450DD8D835160CBB053463FED0B53F2CDD7F3EA8731919C8E8CC", - Utils.toHexString( tx.getSignatureS() ).toUpperCase()); + Utils.toHexString( tx.getSignature().s.toByteArray() ).toUpperCase()); tx = transactionsMessage.getTransactions().get(2); @@ -309,13 +309,13 @@ public class MessagesTest { Utils.toHexString( tx.getInit() ).toUpperCase()); assertEquals("1B", - Utils.toHexString( new byte[] {tx.getSignatureV()} ).toUpperCase()); + Utils.toHexString( new byte[] {tx.getSignature().v} ).toUpperCase()); assertEquals("D05887574456C6DE8F7A0D172342C2CBDD4CF7AFE15D9DBB8B75B748BA6791C9", - Utils.toHexString( tx.getSignatureR() ).toUpperCase()); + Utils.toHexString( tx.getSignature().r.toByteArray() ).toUpperCase()); assertEquals("1E87172A861F6C37B5A9E3A5D0D7393152A7FBE41530E5BB8AC8F35433E5931B", - Utils.toHexString(tx.getSignatureS()).toUpperCase()); + Utils.toHexString(tx.getSignature().s.toByteArray()).toUpperCase()); } /* BLOCKS */ From 6fca92b62f267286ad8baf3d0515880528f76a5a Mon Sep 17 00:00:00 2001 From: nicksavers Date: Mon, 5 May 2014 03:38:09 +0200 Subject: [PATCH 3/7] Document Transaction and Block and add VM OpCode enum --- .../java/org/ethereum/crypto/vm/OpCode.java | 144 ++++++++++++++++++ .../main/java/org/ethereum/net/vo/Block.java | 71 +++++++-- .../java/org/ethereum/net/vo/Transaction.java | 58 ++++--- .../java/org/ethereum/net/MessagesTest.java | 10 +- 4 files changed, 246 insertions(+), 37 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/crypto/vm/OpCode.java diff --git a/ethereumj-core/src/main/java/org/ethereum/crypto/vm/OpCode.java b/ethereumj-core/src/main/java/org/ethereum/crypto/vm/OpCode.java new file mode 100644 index 00000000..1cac5dd2 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/crypto/vm/OpCode.java @@ -0,0 +1,144 @@ +package org.ethereum.crypto.vm; + +/** + * Instruction set for the Ethereum Virtual Machine + */ +public enum OpCode { + + /** + * Stop and Arithmetic Operations + */ + + STOP(0x00), + ADD(0x01), + MUL(0x02), + SUB(0x03), + DIV(0x04), + SDIV(0x05), + MOD(0x06), + SMOD(0x07), + EXP(0x08), + NEG(0x09), + LT(0x0a), + GT(0x0b), + EQ(0x0c), + NOT(0x0d), + + /** + * Bitwise Logic Operations + */ + + AND(0x10), + OR(0x11), + XOR(0x12), + BYTE(0x13), + + /** + * SHA3 + */ + + SHA3(0x20), + + /** + * Environmental Information + */ + + ADDRESS(0x30), + BALANCE(0x31), + ORIGIN(0x32), + CALLER(0x33), + CALLVALUE(0x34), + CALLDATALOAD(0x35), + CALLDATASIZE(0x36), + CALLDATACOPY(0x37), + CODESIZE(0x38), + CODECOPY(0x39), + GASPRICE(0x3a), + + /** + * Block Information + */ + + PREVHASH(0x40), + COINBASE(0x41), + TIMESTAMP(0x42), + NUMBER(0x43), + DIFFICULTY(0x44), + GASLIMIT(0x45), + + /** + * Memory, Storage and Flow Operations + */ + + POP(0x50), + DUP(0x51), + SWAP(0x52), + MLOAD(0x53), + MSTORE(0x54), + MSTORE8(0x55), + SLOAD(0x56), + SSTORE(0x57), + JUMP(0x58), + JUMPI(0x59), + PC(0x5a), + MSIZE(0x5b), + GAS(0x5c), + + /** + * Push Operations + */ + + PUSH1(0x60), + PUSH2(0x61), + PUSH3(0x62), + PUSH4(0x63), + PUSH5(0x64), + PUSH6(0x65), + PUSH7(0x66), + PUSH8(0x67), + PUSH9(0x68), + PUSH10(0x69), + PUSH11(0x6a), + PUSH12(0x6b), + PUSH13(0x6c), + PUSH14(0x6d), + PUSH15(0x6e), + PUSH16(0x6f), + PUSH17(0x70), + PUSH18(0x71), + PUSH19(0x72), + PUSH20(0x73), + PUSH21(0x74), + PUSH22(0x75), + PUSH23(0x76), + PUSH24(0x77), + PUSH25(0x78), + PUSH26(0x79), + PUSH27(0x7a), + PUSH28(0x7b), + PUSH29(0x7c), + PUSH30(0x7d), + PUSH31(0x7e), + PUSH32(0x7f), + + /** + * System operations + */ + + CREATE(0xf0), + CALL(0xf1), + RETURN(0xf2), + SUICIDE(0xff); + + private int opcode; + + private OpCode(int opcode) { + this.opcode = opcode; + } + + public int getOpCode() { + return this.opcode; + } + + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java index 1147101a..0fbc97c8 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/Block.java @@ -11,21 +11,41 @@ import java.util.ArrayList; import java.util.List; /** - * www.ethereumJ.com - * User: Roman Mandeleil - * Created on: 13/04/14 19:34 + * The block in Ethereum is the collection of relevant pieces of information + * (known as the blockheader), H, together with information corresponding to + * the comprised transactions, R, and a set of other blockheaders U that are known + * to have a parent equal to the present block’s parent’s parent + * (such blocks are known as uncles). */ public class Block { + private static int LIMIT_FACTOR = (int) Math.pow(2, 16); + private static double EMA_FACTOR = 1.5; + /* A scalar value equal to the current limit of gas expenditure per block */ + private static int GAS_LIMIT = (int) Math.pow(10, 6); + private RLPList rawData; private boolean parsed = false; private byte[] hash; + + /* The SHA3 256-bit hash of the parent block, in its entirety */ private byte[] parentHash; + /* The SHA3 256-bit hash of the uncles list portion of this block */ private byte[] unclesHash; + /* The 160-bit address to which all fees collected from the + * successful mining of this block be transferred; formally */ private byte[] coinbase; + /* The SHA3 256-bit hash of the root node of the state trie, + * after all transactions are executed and finalisations applied */ private byte[] stateHash; - private byte[] txListHash; + /* The SHA3 256-bit hash of the root node of the trie structure + * populated with each transaction in the transactions list portion + * of the block */ + private byte[] txTrieHash; + /* A scalar value corresponding to the difficulty level of this block. + * This can be calculated from the previous block’s difficulty level + * and the timestamp */ private byte[] difficulty; private long timestamp; @@ -40,12 +60,12 @@ public class Block { this.parsed = false; } - public Block(byte[] parentHash, byte[] unclesHash, byte[] coinbase, byte[] stateHash, byte[] txListHash, byte[] difficulty, long timestamp, byte[] extraData, byte[] nonce, List transactionsList, List uncleList) { + public Block(byte[] parentHash, byte[] unclesHash, byte[] coinbase, byte[] stateHash, byte[] txTrieHash, byte[] difficulty, long timestamp, byte[] extraData, byte[] nonce, List transactionsList, List uncleList) { this.parentHash = parentHash; this.unclesHash = unclesHash; this.coinbase = coinbase; this.stateHash = stateHash; - this.txListHash = txListHash; + this.txTrieHash = txTrieHash; this.difficulty = difficulty; this.timestamp = timestamp; this.extraData = extraData; @@ -66,7 +86,7 @@ public class Block { this.unclesHash = ((RLPItem) params.get(1)).getData(); this.coinbase = ((RLPItem) params.get(2)).getData(); this.stateHash = ((RLPItem) params.get(3)).getData(); - this.txListHash = ((RLPItem) params.get(4)).getData(); + this.txTrieHash = ((RLPItem) params.get(4)).getData(); this.difficulty = ((RLPItem) params.get(5)).getData(); byte[] tsBytes = ((RLPItem) params.get(6)).getData(); @@ -95,6 +115,11 @@ public class Block { return hash; } + public Block getParent() { + // TODO: Implement + return null; + } + public byte[] getParentHash() { if (!parsed) parseRLP(); return parentHash; @@ -115,9 +140,9 @@ public class Block { return stateHash; } - public byte[] getTxListHash() { + public byte[] getTxTrieHash() { if (!parsed) parseRLP(); - return txListHash; + return txTrieHash; } public byte[] getDifficulty() { @@ -160,11 +185,37 @@ public class Block { ", unclesHash=" + Utils.toHexString(unclesHash) + ", coinbase=" + Utils.toHexString(coinbase) + ", stateHash=" + Utils.toHexString(stateHash) + - ", txListHash=" + Utils.toHexString(txListHash) + + ", txTrieHash=" + Utils.toHexString(txTrieHash) + ", difficulty=" + Utils.toHexString(difficulty) + ", timestamp=" + timestamp + ", extraData=" + Utils.toHexString(extraData) + ", nonce=" + Utils.toHexString(nonce) + ']'; } + + /** + * Because every transaction published into the blockchain imposes on the + * network the cost of needing to download and verify it, there is a need + * for some regulatory mechanism to prevent abuse. + * + * To solve this we simply institute a floating cap: + * + * No block can have more operations than BLK_LIMIT_FACTOR times + * the long-term exponential moving average. + * + * Specifically: + * + * blk.oplimit = floor((blk.parent.oplimit * (EMAFACTOR - 1) + * + floor(GAS_LIMIT * BLK_LIMIT_FACTOR)) / EMA_FACTOR) + * + * BLK_LIMIT_FACTOR and EMA_FACTOR are constants that will be set + * to 65536 and 1.5 for the time being, but will likely be changed + * after further analysis. + * + * @return + */ + public double getOplimit() { + return Math.floor((this.getParent().getOplimit() * (EMA_FACTOR - 1) + + Math.floor(GAS_LIMIT * LIMIT_FACTOR)) / EMA_FACTOR); + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java index 5777532e..62475570 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java @@ -7,35 +7,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 ] - + /* 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 ] + */ + + /* 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) { @@ -48,7 +62,7 @@ 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; @@ -61,7 +75,7 @@ public class Transaction { 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 @@ -113,9 +127,9 @@ public class Transaction { return gasPrice; } - public byte[] getGas() { + public byte[] getGasLimit() { if (!parsed) rlpParse(); - return gas; + return gasLimit; } public byte[] getData() { @@ -141,7 +155,7 @@ public class Transaction { ", value=" + Utils.toHexString(value) + ", receiveAddress=" + Utils.toHexString(receiveAddress) + ", gasPrice=" + Utils.toHexString(gasPrice) + - ", gas=" + Utils.toHexString(gas) + + ", gas=" + Utils.toHexString(gasLimit) + ", data=" + Utils.toHexString(data) + ", init=" + Utils.toHexString(init) + ", signatureV=" + signature.v + diff --git a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java index 5812f3e3..0d8cb9d5 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java @@ -214,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()); @@ -265,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()); @@ -300,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()); @@ -354,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()); @@ -399,7 +399,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()); From 01a9fcc7e22304eec96eba4eff16a0f8f3ee91c3 Mon Sep 17 00:00:00 2001 From: nicksavers Date: Mon, 5 May 2014 04:10:38 +0200 Subject: [PATCH 4/7] Add Trie datastructure, utility classes and leveldb dependency --- ethereumj-core/pom.xml | 12 + .../net/client/EthereumProtocolHandler.java | 2 +- .../ethereum/net/message/HelloMessage.java | 4 +- .../java/org/ethereum/net/{ => rlp}/RLP.java | 5 +- .../main/java/org/ethereum/trie/Cache.java | 117 +++++ .../src/main/java/org/ethereum/trie/Node.java | 80 ++++ .../src/main/java/org/ethereum/trie/Trie.java | 368 +++++++++++++++ .../java/org/ethereum/trie/TrieIterator.java | 85 ++++ .../org/ethereum/util/CompactEncoder.java | 108 +++++ .../java/org/ethereum/util/DecodeResult.java | 19 + .../java/org/ethereum/util/RlpEncoder.java | 213 +++++++++ .../main/java/org/ethereum/util/Value.java | 160 +++++++ .../java/org/ethereum/block/BlockTest.java | 2 +- .../java/org/ethereum/net/MessagesTest.java | 1 + .../test/java/org/ethereum/trie/MockDB.java | 132 ++++++ .../test/java/org/ethereum/trie/TrieTest.java | 305 +++++++++++++ .../org/ethereum/util/CompactEncoderTest.java | 74 +++ .../org/ethereum/{net => util}/RLPTest.java | 3 +- .../org/ethereum/util/RlpEncoderTest.java | 425 ++++++++++++++++++ .../java/org/ethereum/util/RlpTestData.java | 59 +++ .../java/org/ethereum/util/ValueTest.java | 47 ++ 21 files changed, 2213 insertions(+), 8 deletions(-) rename ethereumj-core/src/main/java/org/ethereum/net/{ => rlp}/RLP.java (99%) create mode 100644 ethereumj-core/src/main/java/org/ethereum/trie/Cache.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/trie/Node.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/trie/Trie.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/util/DecodeResult.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/util/RlpEncoder.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/util/Value.java create mode 100644 ethereumj-core/src/test/java/org/ethereum/trie/MockDB.java create mode 100644 ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java create mode 100644 ethereumj-core/src/test/java/org/ethereum/util/CompactEncoderTest.java rename ethereumj-core/src/test/java/org/ethereum/{net => util}/RLPTest.java (99%) create mode 100644 ethereumj-core/src/test/java/org/ethereum/util/RlpEncoderTest.java create mode 100644 ethereumj-core/src/test/java/org/ethereum/util/RlpTestData.java create mode 100644 ethereumj-core/src/test/java/org/ethereum/util/ValueTest.java diff --git a/ethereumj-core/pom.xml b/ethereumj-core/pom.xml index 982c8a02..41506f21 100644 --- a/ethereumj-core/pom.xml +++ b/ethereumj-core/pom.xml @@ -35,6 +35,18 @@ core ${spongycastle.version} + + org.iq80.leveldb + leveldb + ${leveldb.version} + + + + com.cedarsoftware + java-util + 1.8.0 + test + com.fifesoft rsyntaxtextarea diff --git a/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java index bd6f6f94..a9a47763 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java @@ -24,7 +24,6 @@ import java.util.TimerTask; import org.ethereum.gui.PeerListener; import org.ethereum.manager.MainData; import org.ethereum.net.Command; -import org.ethereum.net.RLP; import org.ethereum.net.message.BlocksMessage; import org.ethereum.net.message.DisconnectMessage; import org.ethereum.net.message.GetChainMessage; @@ -34,6 +33,7 @@ import org.ethereum.net.message.NotInChainMessage; import org.ethereum.net.message.PeersMessage; import org.ethereum.net.message.StaticMessages; import org.ethereum.net.message.TransactionsMessage; +import org.ethereum.net.rlp.RLP; import org.ethereum.net.rlp.RLPList; import org.ethereum.net.vo.Block; import org.ethereum.util.Utils; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/HelloMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/message/HelloMessage.java index ef026bfc..713dc72a 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/message/HelloMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/HelloMessage.java @@ -1,8 +1,10 @@ package org.ethereum.net.message; import org.spongycastle.util.encoders.Hex; + import static org.ethereum.net.Command.HELLO; -import org.ethereum.net.RLP; + +import org.ethereum.net.rlp.RLP; import org.ethereum.net.rlp.RLPItem; import org.ethereum.net.rlp.RLPList; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/RLP.java b/ethereumj-core/src/main/java/org/ethereum/net/rlp/RLP.java similarity index 99% rename from ethereumj-core/src/main/java/org/ethereum/net/RLP.java rename to ethereumj-core/src/main/java/org/ethereum/net/rlp/RLP.java index bec70ebe..9a9e9db8 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/RLP.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlp/RLP.java @@ -1,4 +1,4 @@ -package org.ethereum.net; +package org.ethereum.net.rlp; import java.io.UnsupportedEncodingException; import java.math.BigInteger; @@ -8,9 +8,6 @@ import java.util.Arrays; import java.util.List; import java.util.Queue; -import org.ethereum.net.rlp.RLPItem; -import org.ethereum.net.rlp.RLPList; - public class RLP { public static byte decodeOneByteItem(byte[] data, int index) { diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java b/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java new file mode 100644 index 00000000..7ead2c11 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java @@ -0,0 +1,117 @@ +package org.ethereum.trie; + +import static org.iq80.leveldb.impl.Iq80DBFactory.factory; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.ethereum.crypto.HashUtil; +import org.ethereum.util.Value; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.Options; +import org.spongycastle.util.encoders.Hex; + +public class Cache { + + private Map nodes; + private DB db; + private boolean isDirty; + + public Cache(DB db) { + if(db == null) { + try { + /* **** Experimental LevelDB Code **** */ + Options options = new Options(); + options.createIfMissing(true); + this.db = factory.open(new File("ethereumdb"), options); + /* **** Experimental LevelDB Code **** */ + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + this.db = db; + nodes = new HashMap(); + } + + public Object put(Object o) { + Value value = new Value(o); + byte[] enc = value.encode(); + if (enc.length >= 32) { + byte[] sha = Hex.encode(HashUtil.sha3(enc)); + this.nodes.put(sha, new Node(sha, value, true)); + this.isDirty = true; + return sha; + } + return value; + } + + public Value get(byte[] key) { + // First check if the key is the cache + if (this.nodes.get(key) != null) { + return this.nodes.get(key).getValue(); + } + // Get the key of the database instead and cache it + byte[] data = this.db.get(key); + Value value = new Value(data); + // Create caching node + this.nodes.put(key, new Node(key, value, false)); + + return value; + } + + public void delete(byte[] key) { + this.nodes.remove(key); + this.db.delete(key); + } + + public void commit() { + // Don't try to commit if it isn't dirty + if (!this.isDirty) { + return; + } + + for (byte[] key : this.nodes.keySet()) { + Node node = this.nodes.get(key); + if (node.isDirty()) { + this.db.put(key, node.getValue().encode()); + node.setDirty(false); + } + } + this.isDirty = false; + + // If the nodes grows beyond the 200 entries we simple empty it + // FIXME come up with something better + if (this.nodes.size() > 200) { + this.nodes = new HashMap(); + } + } + + public void undo() { + Iterator> iter = this.nodes.entrySet().iterator(); + while (iter.hasNext()) { + if(iter.next().getValue().isDirty()){ + iter.remove(); + } + } + this.isDirty = false; + } + + public boolean isDirty() { + return isDirty; + } + + public void setDirty(boolean isDirty) { + this.isDirty = isDirty; + } + + public Map getNodes() { + return nodes; + } + + public DB getDb() { + return db; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Node.java b/ethereumj-core/src/main/java/org/ethereum/trie/Node.java new file mode 100644 index 00000000..f37c68d6 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/trie/Node.java @@ -0,0 +1,80 @@ +package org.ethereum.trie; + +import org.ethereum.util.Value; + +/** + * A Node in a Merkle Patricia Tree is one of the following: + * + * - NULL (represented as the empty string) + * - A two-item array [ key, value ] + * - A 17-item array [ v0 ... v15, vt ] + * + * The idea is that in the event that there is a long path of nodes + * each with only one element, we shortcut the descent by setting up + * a [ key, value ] node, where the key gives the hexadecimal path + * to descend, in the compact encoding described above, and the value + * is just the hash of the node like in the standard radix tree. + * + * R + * / \ + * / \ + * N N + * / \ / \ + * L L L L + * + * + * Also, we add another conceptual change: internal nodes can no longer + * have values, only leaves with no children of their own can; however, + * since to be fully generic we want the key/value store to be able to + * store keys like 'dog' and 'doge' at the same time, we simply add + * a terminator symbol (16) to the alphabet so there is never a value + * "en-route" to another value. Where a node is referenced inside a node, + * what is included is H(rlp.encode(x)) where H(x) = sha3(x) if len(x) >= 32 else x + * and rlp.encode is the RLP encoding function. + * + * Note that when updating a trie, you will need to store the key/value pair (sha3(x), x) + * in a persistent lookup table when you create a node with length >= 32, + * but if the node is shorter than that then you do not need to store anything + * when length < 32 for the obvious reason that the function f(x) = x is reversible. + * + */ +public class Node { + + private byte[] key; // 1 key for 2-item array, 16 keys for 17-item array + private Value value; + private boolean dirty; + + public Node(byte[] key, Value val) { + this(key, val, false); + } + + public Node(byte[] key, Value val, boolean dirty) { + this.key = key; + this.value = val; + this.dirty = dirty; + } + + public Node copy() { + return new Node(this.key.clone(), this.value, this.dirty); + } + + public boolean isDirty() { + return dirty; + } + + public void setDirty(boolean ditry) { + this.dirty = ditry; + } + + public byte[] getKey() { + return key; + } + + public Value getValue() { + return value; + } + + public Object[] getItems() { + return new Object[] { key, value }; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java b/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java new file mode 100644 index 00000000..e0e83afc --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java @@ -0,0 +1,368 @@ +package org.ethereum.trie; + +import static java.util.Arrays.copyOfRange; +import static org.spongycastle.util.Arrays.concatenate; + +import java.util.Arrays; + +import org.ethereum.util.CompactEncoder; +import org.ethereum.util.Value; +import org.iq80.leveldb.DB; + +import com.cedarsoftware.util.DeepEquals; + +public class Trie { + + private static byte PAIR_SIZE = 2; + private static byte LIST_SIZE = 17; + + // A (modified) Radix Trie implementation. The Trie implements + // a caching mechanism and will used cached values if they are + // present. If a node is not present in the cache it will try to + // fetch it from the database and store the cached value. + // Please note that the data isn't persisted unless `Sync` is + // explicitly called. + private Object prevRoot; + private Object root; + private Cache cache; + + public Trie(DB db) { + this(db, ""); + } + + public Trie(DB db, Object root) { + this.cache = new Cache(db); + this.root = root; + this.prevRoot = root; + } + + public TrieIterator getIterator() { + return new TrieIterator(this); + } + + public Cache getCache() { + return this.cache; + } + + public Object getPrevRoot() { + return prevRoot; + } + + public Object getRoot() { + return root; + } + + public void setRoot(Node root) { + this.root = root; + } + + public void setCache(Cache cache) { + this.cache = cache; + } + + /************************************** + * Public (query) interface functions * + **************************************/ + + /** + * Insert key/value pair into trie + * + * @param key + * @param value + */ + public void update(String key, String value) { + if (key == null) + throw new NullPointerException("Key should not be blank"); + byte[] k = CompactEncoder.hexDecode(key.getBytes()); + this.root = this.insertOrDelete(this.root, k, value.getBytes()); + } + + /** + * Retrieve a value from a node + * + * @param key + * @return value + */ + public String get(String key) { + byte[] k = CompactEncoder.hexDecode(key.getBytes()); + Value c = new Value( this.get(this.root, k) ); + return c.asString(); + } + + /** + * Delete a key/value pair from the trie + * + * @param key + */ + public void delete(String key) { + this.update(key, ""); + } + + /**************************************** + * Private functions * + ****************************************/ + + private Object get(Object node, byte[] key) { + + // Return the node if key is empty (= found) + if (key.length == 0 || isEmptyNode(node)) { + return node; + } + + Value currentNode = this.getNode(node); + int length = currentNode.length(); + + if (length == PAIR_SIZE) { + // Decode the key + byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes()); + Object v = currentNode.get(1).asObj(); + + if (key.length >= k.length && Arrays.equals(k, copyOfRange(key, 0, k.length))) { + return this.get(v, copyOfRange(key, k.length, key.length)); + } else { + return ""; + } + } else if (length == LIST_SIZE) { + return this.get(currentNode.get(key[0]).asObj(), copyOfRange(key, 1, key.length)); + } + + // It shouldn't come this far + throw new RuntimeException("Unexpected Node length: " + length); + } + + private Object insertOrDelete(Object node, byte[] key, byte[] value) { + if (value.length != 0) { + return this.insert(node, key, value); + } else { + return this.delete(node, key); + } + } + + /** + * Update or add the item inside a node + * return the updated node with rlp encoded + */ + private Object insert(Object node, byte[] key, Object value) { + + if (key.length == 0) { + return value; + } + + if (isEmptyNode(node)) { + Object[] newNode = new Object[] { CompactEncoder.encode(key), value }; + return this.put(newNode); + } + + Value currentNode = this.getNode(node); + + // Check for "special" 2 slice type node + if (currentNode.length() == PAIR_SIZE) { + // Decode the key + + byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes()); + Object v = currentNode.get(1).asObj(); + + // Matching key pair (ie. there's already an object with this key) + if (Arrays.equals(k, key)) { + Object[] newNode = new Object[] {CompactEncoder.encode(key), value}; + return this.put(newNode); + } + + Object newHash; + int matchingLength = matchingNibbleLength(key, k); + if (matchingLength == k.length) { + // Insert the hash, creating a new node + newHash = this.insert(v, copyOfRange(key, matchingLength, key.length), value); + } else { + // Expand the 2 length slice to a 17 length slice + Object oldNode = this.insert("", copyOfRange(k, matchingLength+1, k.length), v); + Object newNode = this.insert("", copyOfRange(key, matchingLength+1, key.length), value); + // Create an expanded slice + Object[] scaledSlice = emptyStringSlice(17); + // Set the copied and new node + scaledSlice[k[matchingLength]] = oldNode; + scaledSlice[key[matchingLength]] = newNode; + newHash = this.put(scaledSlice); + } + + if (matchingLength == 0) { + // End of the chain, return + return newHash; + } else { + Object[] newNode = new Object[] {CompactEncoder.encode(copyOfRange(key, 0, matchingLength)), newHash}; + return this.put(newNode); + } + } else { + // Copy the current node over to the new node + Object[] newNode = copyNode(currentNode); + // Replace the first nibble in the key + newNode[key[0]] = this.insert(currentNode.get(key[0]).asObj(), copyOfRange(key, 1, key.length), value); + + return this.put(newNode); + } + } + + private Object delete(Object node, byte[] key) { + + if (key.length == 0 || isEmptyNode(node)) { + return ""; + } + + // New node + Value currentNode = this.getNode(node); + // Check for "special" 2 slice type node + if (currentNode.length() == PAIR_SIZE) { + // Decode the key + byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes()); + Object v = currentNode.get(1).asObj(); + + // Matching key pair (ie. there's already an object with this key) + if (Arrays.equals(k, key)) { + return ""; + } else if (Arrays.equals(copyOfRange(key, 0, k.length), k)) { + Object hash = this.delete(v, copyOfRange(key, k.length, key.length)); + Value child = this.getNode(hash); + + Object newNode; + if (child.length() == PAIR_SIZE) { + byte[] newKey = concatenate(k, CompactEncoder.decode(child.get(0).asBytes())); + newNode = new Object[] {CompactEncoder.encode(newKey), child.get(1).asObj()}; + } else { + newNode = new Object[] {currentNode.get(0).asString(), hash}; + } + return this.put(newNode); + } else { + return node; + } + } else { + // Copy the current node over to a new node + Object[] itemList = copyNode(currentNode); + + // Replace the first nibble in the key + itemList[key[0]] = this.delete(itemList[key[0]], copyOfRange(key, 1, key.length)); + byte amount = -1; + for (byte i = 0; i < LIST_SIZE; i++) { + if (itemList[i] != "") { + if (amount == -1) { + amount = i; + } else { + amount = -2; + } + } + } + + Object[] newNode = null; + if (amount == 16) { + newNode = new Object[] { CompactEncoder.encode(new byte[] {16} ), itemList[amount]}; + } else if (amount >= 0) { + Value child = this.getNode(itemList[amount]); + if (child.length() == PAIR_SIZE) { + key = concatenate(new byte[]{amount}, CompactEncoder.decode(child.get(0).asBytes())); + newNode = new Object[] {CompactEncoder.encode(key), child.get(1).asObj()}; + } else if (child.length() == LIST_SIZE) { + newNode = new Object[] { CompactEncoder.encode(new byte[]{amount}), itemList[amount]}; + } + } else { + newNode = itemList; + } + return this.put(newNode); + } + } + + /** + * Helper method to retrieve the actual node + * If the node is not a list and length is > 32 bytes get the actual node from the db + * + * @param node + * @return + */ + private Value getNode(Object node) { + Value n = new Value(node); + + if (!n.get(0).isNull()) { + return n; + } + + String str = n.asString(); + if (str.length() == 0) { + return n; + } else if (str.length() < 32) { + return new Value(str.getBytes()); + } + return this.cache.get(n.asBytes()); + } + + private Object put(Object node) { + /* TODO? + c := Conv(t.Root) + fmt.Println(c.Type(), c.Length()) + if c.Type() == reflect.String && c.AsString() == "" { + return enc + } + */ + return this.cache.put(node); + } + + private boolean isEmptyNode(Object node) { + Value n = new Value(node); + return (node == null || (n.isString() && (n.asString() == "" || n.get(0).isNull())) || n.length() == 0); + } + + private Object[] copyNode(Value currentNode) { + Object[] itemList = emptyStringSlice(LIST_SIZE); + for (int i = 0; i < LIST_SIZE; i++) { + Object cpy = currentNode.get(i).asObj(); + if (cpy != null) { + itemList[i] = cpy; + } + } + return itemList; + } + + // Simple compare function which creates a rlp value out of the evaluated objects + public boolean cmp(Trie trie) { + return DeepEquals.deepEquals(this.root, trie.getRoot()); + } + + // Save the cached value to the database. + public void sync() { + this.cache.commit(); + this.prevRoot = this.root; + } + + public void undo() { + this.cache.undo(); + this.root = this.prevRoot; + } + + // Returns a copy of this trie + public Trie copy() { + Trie trie = new Trie(this.cache.getDb(), this.root); + for (byte[] key : this.cache.getNodes().keySet()) { + Node node = this.cache.getNodes().get(key); + trie.cache.getNodes().put(key, node.copy()); + } + return trie; + } + + /******************************** + * Utility functions * + *******************************/ + + // Returns the amount of nibbles that match each other from 0 ... + private int matchingNibbleLength(byte[] a, byte[] b) { + int i = 0; + while (Arrays.equals(copyOfRange(a, 0, i+1), copyOfRange(b, 0, i+1)) && i < b.length) { + i++; + } + return i; + } + + private Object[] emptyStringSlice(int l) { + Object[] slice = new Object[l]; + for (int i = 0; i < l; i++) { + slice[i] = ""; + } + return slice; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java b/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java new file mode 100644 index 00000000..9b43817e --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/trie/TrieIterator.java @@ -0,0 +1,85 @@ +package org.ethereum.trie; + +import java.util.List; + +import org.ethereum.util.CompactEncoder; +import org.ethereum.util.Value; + +public class TrieIterator { + + Trie trie; + String key; + String value; + + List shas; + List values; + + public TrieIterator(Trie t) { + this.trie = t; + } + + // Some time in the near future this will need refactoring :-) + // XXX Note to self, IsSlice == inline node. Str == sha3 to node + private void workNode(Value currentNode) { + if (currentNode.length() == 2) { + byte[] k = CompactEncoder.decode(currentNode.get(0).asBytes()); + + if (currentNode.get(1).asString() == "") { + this.workNode(currentNode.get(1)); + } else { + if (k[k.length-1] == 16) { + this.values.add( currentNode.get(1).asString() ); + } else { + this.shas.add(currentNode.get(1).asBytes()); + this.getNode(currentNode.get(1).asBytes()); + } + } + } else { + for (int i = 0; i < currentNode.length(); i++) { + if (i == 16 && currentNode.get(i).length() != 0) { + this.values.add( currentNode.get(i).asString() ); + } else { + if (currentNode.get(i).asString() == "") { + this.workNode(currentNode.get(i)); + } else { + String val = currentNode.get(i).asString(); + if (val != "") { + this.shas.add(currentNode.get(1).asBytes()); + this.getNode(val.getBytes()); + } + } + } + } + } + } + + private void getNode(byte[] node) { + Value currentNode = this.trie.getCache().get(node); + this.workNode(currentNode); + } + + private List collect() { + if (this.trie.getRoot() == "") { + return null; + } + this.getNode(new Value(this.trie.getRoot()).asBytes()); + return this.shas; + } + + public int purge() { + List shas = this.collect(); + + for (byte[] sha : shas) { + this.trie.getCache().delete(sha); + } + return this.values.size(); + } + + private String getKey() { + return ""; + } + + private String getValue() { + return ""; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java b/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java new file mode 100644 index 00000000..c88305f0 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/util/CompactEncoder.java @@ -0,0 +1,108 @@ +package org.ethereum.util; + +import java.io.ByteArrayOutputStream; + +import static java.util.Arrays.copyOfRange; +import static java.util.Arrays.copyOf; + +import static org.ethereum.util.ByteUtil.appendByte; +import static org.spongycastle.util.Arrays.concatenate; +import static org.spongycastle.util.encoders.Hex.toHexString; + +/** + * + * Compact encoding of hex sequence with optional terminator + * + * The traditional compact way of encoding a hex string is to convert it into binary + * - that is, a string like 0f1248 would become three bytes 15, 18, 72. However, + * this approach has one slight problem: what if the length of the hex string is odd? + * In that case, there is no way to distinguish between, say, 0f1248 and f1248. + * + * Additionally, our application in the Merkle Patricia tree requires the additional feature + * that a hex string can also have a special "terminator symbol" at the end (denoted by the 'T'). + * A terminator symbol can occur only once, and only at the end. + * + * An alternative way of thinking about this to not think of there being a terminator symbol, + * but instead treat bit specifying the existence of the terminator symbol as a bit specifying + * that the given node encodes a final node, where the value is an actual value, rather than + * the hash of yet another node. + * + * To solve both of these issues, we force the first nibble of the final byte-stream to encode + * two flags, specifying oddness of length (ignoring the 'T' symbol) and terminator status; + * these are placed, respectively, into the two lowest significant bits of the first nibble. + * In the case of an even-length hex string, we must introduce a second nibble (of value zero) + * to ensure the hex-string is even in length and thus is representable by a whole number of bytes. + * + * Examples: + * > [ 1, 2, 3, 4, 5 ] + * '\x11\x23\x45' + * > [ 0, 1, 2, 3, 4, 5 ] + * '\x00\x01\x23\x45' + * > [ 0, 15, 1, 12, 11, 8, T ] + * '\x20\x0f\x1c\xb8' + * > [ 15, 1, 12, 11, 8, T ] + * '\x3f\x1c\xb8' + * + */ +public class CompactEncoder { + + private final static byte TERMINATOR = 16; + private final static String hexBase = "0123456789abcdef"; + + public static byte[] encode(byte[] hexSlice) { + int terminator = 0; + + if (hexSlice[hexSlice.length-1] == TERMINATOR) { + terminator = 1; + hexSlice = copyOf(hexSlice, hexSlice.length-1); + } + + int oddlen = hexSlice.length % 2; + int flag = 2*terminator + oddlen; + if (oddlen != 0) { + byte[] flags = new byte[] { (byte) flag}; + hexSlice = concatenate(flags, hexSlice); + } else { + byte[] flags = new byte[] { (byte) flag, 0}; + hexSlice = concatenate(flags, hexSlice); + } + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (int i = 0; i < hexSlice.length; i += 2) { + buffer.write( 16*hexSlice[i] + hexSlice[i+1] ); + } + return buffer.toByteArray(); + } + + /** + * Strips hex slices + */ + public static byte[] decode(byte[] str) { + byte[] base = hexDecode(str); + base = copyOf(base, base.length-1); + if (base[0] >= 2) { + base = appendByte(base, TERMINATOR); + } + if (base[0]%2 == 1) { + base = copyOfRange(base, 1, base.length); + } else { + base = copyOfRange(base, 2, base.length); + } + return base; + } + + /** + * Transforms a binary array to hexadecimal format, + * returns array with each individual nibble adding a terminator at the end + */ + public static byte[] hexDecode(byte[] str) { + byte[] hexSlice = new byte[0]; + String hexEncoded = toHexString(str); + for (char value : hexEncoded.toCharArray()) { + hexSlice = appendByte(hexSlice, (byte) hexBase.indexOf(value)); + } + hexSlice = appendByte(hexSlice, TERMINATOR); + + return hexSlice; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/util/DecodeResult.java b/ethereumj-core/src/main/java/org/ethereum/util/DecodeResult.java new file mode 100644 index 00000000..d7213a06 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/util/DecodeResult.java @@ -0,0 +1,19 @@ +package org.ethereum.util; + +public class DecodeResult { + + private int pos; + private Object decoded; + + public DecodeResult(int pos, Object decoded) { + this.pos = pos; + this.decoded = decoded; + } + + public int getPos() { + return pos; + } + public Object getDecoded() { + return decoded; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/util/RlpEncoder.java b/ethereumj-core/src/main/java/org/ethereum/util/RlpEncoder.java new file mode 100644 index 00000000..c3cebafb --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/util/RlpEncoder.java @@ -0,0 +1,213 @@ +package org.ethereum.util; + +import java.math.BigInteger; +import java.util.ArrayList; + +import static java.util.Arrays.copyOfRange; +import static org.spongycastle.util.Arrays.concatenate; + +import java.util.List; + +/** + * Recursive Length Prefix (RLP) encoding. + * + * The purpose of RLP is to encode arbitrarily nested arrays of binary data, and + * RLP is the main encoding method used to serialize objects in Ethereum. The + * only purpose of RLP is to encode structure; encoding specific atomic data + * types (eg. strings, ints, floats) is left up to higher-order protocols; in + * Ethereum the standard is that integers are represented in big endian binary + * form. If one wishes to use RLP to encode a dictionary, the two suggested + * canonical forms are to either use [[k1,v1],[k2,v2]...] with keys in + * lexicographic order or to use the higher-level Patricia Tree encoding as + * Ethereum does. + * + * The RLP encoding function takes in an item. An item is defined as follows: + * + * - A string (ie. byte array) is an item - A list of items is an item + * + * For example, an empty string is an item, as is the string containing the word + * "cat", a list containing any number of strings, as well as more complex data + * structures like ["cat",["puppy","cow"],"horse",[[]],"pig",[""],"sheep"]. Note + * that in the context of the rest of this article, "string" will be used as a + * synonym for "a certain number of bytes of binary data"; no special encodings + * are used and no knowledge about the content of the strings is implied. + * + * See: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP + */ +public class RlpEncoder extends CompactEncoder { + + /** Allow for content up to size of 2^64 bytes **/ + private static double MAX_ITEM_LENGTH = Math.pow(256, 8); + + /** + [5:30:35 PM] Vitalik Buterin: 56 bytes maximizes the benefit of both options + [5:30:41 PM] Vitalik Buterin: if we went with 60 + [5:31:03 PM] Vitalik Buterin: then we would have only had 4 slots for long strings so RLP would not have been able to store objects above 4gb + [5:31:08 PM] Vitalik Buterin: if we went with 48 + [5:31:18 PM] Vitalik Buterin: then RLP would be fine for 2^128 space, but that's way too much + [5:31:32 PM] Vitalik Buterin: so 56 and 2^64 space seems like the right place to put the cutoff + [5:31:44 PM] Vitalik Buterin: also, that's where Bitcoin's varint does the cutof + **/ + private static int SIZE_THRESHOLD = 56; + + /** RLP encoding rules are defined as follows: */ + + /* + * For a single byte whose value is in the [0x00, 0x7f] range, that byte is + * its own RLP encoding. + */ + + /* + * If a string is 0-55 bytes long, the RLP encoding consists of a single + * byte with value 0x80 plus the length of the string followed by the + * string. The range of the first byte is thus [0x80, 0xb7]. + */ + private static int offsetShortItem = 0x80; + + /* + * If a string is more than 55 bytes long, the RLP encoding consists of a + * single byte with value 0xb7 plus the length of the length of the string + * in binary form, followed by the length of the string, followed by the + * string. For example, a length-1024 string would be encoded as + * \xb9\x04\x00 followed by the string. The range of the first byte is thus + * [0xb8, 0xbf]. + */ + private static int offsetLongItem = 0xb8; + + /* + * If the total payload of a list (i.e. the combined length of all its + * items) is 0-55 bytes long, the RLP encoding consists of a single byte + * with value 0xc0 plus the length of the list followed by the concatenation + * of the RLP encodings of the items. The range of the first byte is thus + * [0xc0, 0xf7]. + */ + private static int offsetShortList = 0xc0; + + /* + * If the total payload of a list is more than 55 bytes long, the RLP + * encoding consists of a single byte with value 0xf7 plus the length of the + * length of the list in binary form, followed by the length of the list, + * followed by the concatenation of the RLP encodings of the items. The + * range of the first byte is thus [0xf8, 0xff]. + */ + private static int offsetLongList = 0xf8; + private static int maxPrefix = 0xff; + + public static byte[] encode(Object input) { + Value val = new Value(input); + if (val.isList()) { + List 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 slice = new ArrayList(); + 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(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/util/Value.java b/ethereumj-core/src/main/java/org/ethereum/util/Value.java new file mode 100644 index 00000000..49daeaa6 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/util/Value.java @@ -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 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; + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/block/BlockTest.java b/ethereumj-core/src/test/java/org/ethereum/block/BlockTest.java index 8230f7a7..e08c5fb6 100644 --- a/ethereumj-core/src/test/java/org/ethereum/block/BlockTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/block/BlockTest.java @@ -2,7 +2,7 @@ package org.ethereum.block; import org.spongycastle.util.encoders.Hex; import org.ethereum.crypto.HashUtil; -import org.ethereum.net.RLP; +import org.ethereum.net.rlp.RLP; import org.ethereum.net.rlp.RLPList; import org.ethereum.net.vo.Block; import org.junit.Test; diff --git a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java index 0d8cb9d5..8e82664e 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java @@ -12,6 +12,7 @@ import org.ethereum.net.message.HelloMessage; import org.ethereum.net.message.NotInChainMessage; import org.ethereum.net.message.PeersMessage; import org.ethereum.net.message.TransactionsMessage; +import org.ethereum.net.rlp.RLP; import org.ethereum.net.rlp.RLPList; import org.ethereum.net.vo.Block; import org.ethereum.net.vo.PeerData; diff --git a/ethereumj-core/src/test/java/org/ethereum/trie/MockDB.java b/ethereumj-core/src/test/java/org/ethereum/trie/MockDB.java new file mode 100644 index 00000000..2ed8c9c3 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/trie/MockDB.java @@ -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; + } + +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java b/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java new file mode 100644 index 00000000..4b26fabd --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java @@ -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"); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/util/CompactEncoderTest.java b/ethereumj-core/src/test/java/org/ethereum/util/CompactEncoderTest.java new file mode 100644 index 00000000..35df3377 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/util/CompactEncoderTest.java @@ -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)); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/net/RLPTest.java b/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java similarity index 99% rename from ethereumj-core/src/test/java/org/ethereum/net/RLPTest.java rename to ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java index 1e48f6fa..0ab7c5f0 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/RLPTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java @@ -1,7 +1,8 @@ -package org.ethereum.net; +package org.ethereum.util; import org.spongycastle.util.encoders.Hex; import org.ethereum.crypto.HashUtil; +import org.ethereum.net.rlp.RLP; import org.ethereum.net.rlp.RLPList; import org.ethereum.util.Utils; import org.junit.Test; diff --git a/ethereumj-core/src/test/java/org/ethereum/util/RlpEncoderTest.java b/ethereumj-core/src/test/java/org/ethereum/util/RlpEncoderTest.java new file mode 100644 index 00000000..25ed44e7 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/util/RlpEncoderTest.java @@ -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; + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/util/RlpTestData.java b/ethereumj-core/src/test/java/org/ethereum/util/RlpTestData.java new file mode 100644 index 00000000..44faaad2 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/util/RlpTestData.java @@ -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 } }; +} diff --git a/ethereumj-core/src/test/java/org/ethereum/util/ValueTest.java b/ethereumj-core/src/test/java/org/ethereum/util/ValueTest.java new file mode 100644 index 00000000..4638acc3 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/util/ValueTest.java @@ -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); + } + + +} From f57bb68e067d0239c63cb7ea0f77abcf8eb08aee Mon Sep 17 00:00:00 2001 From: nicksavers Date: Mon, 5 May 2014 12:30:17 +0200 Subject: [PATCH 5/7] Remove unused exception class --- .../ethereum/crypto/KeyCrypterException.java | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 ethereumj-core/src/main/java/org/ethereum/crypto/KeyCrypterException.java diff --git a/ethereumj-core/src/main/java/org/ethereum/crypto/KeyCrypterException.java b/ethereumj-core/src/main/java/org/ethereum/crypto/KeyCrypterException.java deleted file mode 100644 index a2ed9927..00000000 --- a/ethereumj-core/src/main/java/org/ethereum/crypto/KeyCrypterException.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.ethereum.crypto; -/** - *

Exception to provide the following to {@link EncrypterDecrypterOpenSSL}:

- *
    - *
  • Provision of encryption / decryption exception
  • - *
- *

This base exception acts as a general failure mode not attributable to a specific cause (other than - * that reported in the exception message). Since this is in English, it may not be worth reporting directly - * to the user other than as part of a "general failure to parse" response.

- */ -public class KeyCrypterException extends RuntimeException { - private static final long serialVersionUID = -4441989608332681377L; - - public KeyCrypterException(String s) { - super(s); - } - - public KeyCrypterException(String s, Throwable throwable) { - super(s, throwable); - } -} \ No newline at end of file From b7d827a87a30652c75a419280bd86c212cdfdecd Mon Sep 17 00:00:00 2001 From: nicksavers Date: Mon, 5 May 2014 12:55:25 +0200 Subject: [PATCH 6/7] Add sign function to Transaction --- .../net/message/TransactionsMessage.java | 6 ++-- .../java/org/ethereum/net/vo/Transaction.java | 34 ++++++++++++++++--- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java index cb991896..1a839310 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java @@ -29,7 +29,7 @@ public class TransactionsMessage extends Message { public void parseRLP() { RLPList paramsList = (RLPList) rawData.getElement(0); - if ( Command.fromInt(((RLPItem)(paramsList).getElement(0)).getData()[0] & 0xFF) != TRANSACTIONS){ + if (Command.fromInt(((RLPItem)(paramsList).getElement(0)).getData()[0] & 0xFF) != TRANSACTIONS) { throw new Error("TransactionMessage: parsing for mal data"); } @@ -53,10 +53,10 @@ public class TransactionsMessage extends Message { return null; } - public String toString(){ + public String toString() { if(!parsed) parseRLP(); StringBuffer sb = new StringBuffer(); - for (Transaction transactionData : transactions){ + for (Transaction transactionData : transactions) { sb.append(" ").append(transactionData).append("\n"); } return "Transactions Message [\n" + sb.toString() + " ]"; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java index 62475570..ac3031c3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/vo/Transaction.java @@ -1,6 +1,7 @@ package org.ethereum.net.vo; import org.ethereum.crypto.ECKey.ECDSASignature; +import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; import org.ethereum.net.rlp.RLPItem; import org.ethereum.net.rlp.RLPList; @@ -19,10 +20,10 @@ 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 ] + /* 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 */ @@ -146,7 +147,32 @@ public class Transaction { if (!parsed) rlpParse(); return signature; } + + /********* + * 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(); From 4e08b4ee6535283d49f31813417f1c1eeb80fca5 Mon Sep 17 00:00:00 2001 From: nicksavers Date: Mon, 5 May 2014 15:01:37 +0200 Subject: [PATCH 7/7] Move ReasonCode to enum --- .../net/message/DisconnectMessage.java | 19 +++------ .../org/ethereum/net/message/ReasonCode.java | 42 +++++++++++++++++++ .../java/org/ethereum/net/MessagesTest.java | 7 ++-- 3 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/net/message/ReasonCode.java diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/DisconnectMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/message/DisconnectMessage.java index a7729514..47769063 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/message/DisconnectMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/DisconnectMessage.java @@ -4,6 +4,7 @@ import org.ethereum.net.rlp.RLPItem; import org.ethereum.net.rlp.RLPList; import org.ethereum.net.Command; import static org.ethereum.net.Command.DISCONNECT; +import static org.ethereum.net.message.ReasonCode.DISCONNECT_REQUESTED; /** * www.ethereumJ.com @@ -12,17 +13,7 @@ import static org.ethereum.net.Command.DISCONNECT; */ public class DisconnectMessage extends Message { - private byte reason; - - public static byte REASON_DISCONNECT_REQUESTED = 0x00; - public static byte REASON_TCP_ERROR = 0x01; - public static byte REASON_BAD_PROTOCOL = 0x02; - public static byte REASON_USELESS_PEER = 0x03; - public static byte REASON_TOO_MANY_PEERS = 0x04; - public static byte REASON_ALREADY_CONNECTED = 0x05; - public static byte REASON_WRONG_GENESIS = 0x06; - public static byte REASON_INCOMPATIBLE_PROTOCOL = 0x07; - public static byte REASON_PEER_QUITING = 0x08; + private ReasonCode reason; public DisconnectMessage(RLPList rawData) { super(rawData); @@ -39,9 +30,9 @@ public class DisconnectMessage extends Message { byte[] reasonB = ((RLPItem)paramsList.getElement(1)).getData(); if (reasonB == null){ - this.reason = 0; + this.reason = DISCONNECT_REQUESTED; } else { - this.reason = reasonB[0]; + this.reason = ReasonCode.fromInt(reasonB[0]); } this.parsed = true; // todo: what to do when mal data ? @@ -52,7 +43,7 @@ public class DisconnectMessage extends Message { return null; } - public byte getReason() { + public ReasonCode getReason() { if (!parsed) parseRLP(); return reason; } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/ReasonCode.java b/ethereumj-core/src/main/java/org/ethereum/net/message/ReasonCode.java new file mode 100644 index 00000000..d4b15987 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/ReasonCode.java @@ -0,0 +1,42 @@ +package org.ethereum.net.message; + +import java.util.HashMap; +import java.util.Map; + +public enum ReasonCode { + + DISCONNECT_REQUESTED(0x00), + TCP_ERROR(0x01), + BAD_PROTOCOL(0x02), + USELESS_PEER(0x03), + TOO_MANY_PEERS(0x04), + ALREADY_CONNECTED(0x05), + WRONG_GENESIS(0x06), + INCOMPATIBLE_PROTOCOL(0x07), + PEER_QUITING(0x08), + UNKNOWN(0xFF); + + private int reason; + + private static final Map intToTypeMap = new HashMap(); + static { + for (ReasonCode type : ReasonCode.values()) { + intToTypeMap.put(type.reason, type); + } + } + + private ReasonCode(int cmd) { + this.reason = cmd; + } + + public static ReasonCode fromInt(int i) { + ReasonCode type = intToTypeMap.get(Integer.valueOf(i)); + if (type == null) + return ReasonCode.UNKNOWN; + return type; + } + + public byte asByte() { + return (byte) reason; + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java index 8e82664e..8c1c809a 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java @@ -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; @@ -90,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 */ @@ -105,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 */