diff --git a/.circleci/config.yml b/.circleci/config.yml index fa5b396a34f..2aaa0121d5b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,12 +14,13 @@ jobs: - checkout - run: name: multi_os_result - command: curl http://60.205.215.34/multi_os_result + command: echo "curl http://60.205.215.34/multi_os_result" +# +# - run: +# name: Daily Build Report +# command: curl http://60.205.215.34/Daily_Build_Task_Report +# +# - run: +# name: Download Links +# command: sh DownloadLinks.sh - - run: - name: Daily Build Report - command: curl http://60.205.215.34/Daily_Build_Task_Report - - - run: - name: Download Links - command: sh DownloadLinks.sh diff --git a/README.md b/README.md index ec1e80146f2..486d301281a 100644 --- a/README.md +++ b/README.md @@ -42,319 +42,61 @@
- How to Build • - How to Run • - Links • + Quick Start • + Deploy • + Developer Community • Documentation • - Contributing • - Community + Contributing • + Projects • + Resource
## What's TRON? -TRON is a project dedicated to building the infrastructure for a truly decentralized Internet. +TRON is a project dedicated to building the infrastructure for a truly decentralized Internet. -The Tron Protocol, one of the largest blockchain based operating systems in the world, offers scalable, high-availability and high-throughput support that underlies all the decentralized applications in the TRON ecosystem. -TRON enables large-scale development and engagement. With over 2000 transactions per second (TPS), high concurrency, low latency and massive data transmission, TRON is ideal for building decentralized entertainment applications. Free features and incentive systems allow developers to create premium app experiences for users. +* Tron Protocol, one of the largest blockchain based operating systems in the world, offers scalable, high-availability and high-throughput support that underlies all the decentralized applications in the TRON ecosystem. -TRON Protocol and the Tron Virtual Machine (TVM) allow anyone to develop decentralized applications (DAPPs) for themselves or their communities with smart contracts thereby making decentralized crowdfunding and token issuance easier than ever. +* Tron Virtual Machine (TVM) allow anyone to develop decentralized applications (DAPPs) for themselves or their communities with smart contracts thereby making decentralized crowdfunding and token issuance easier than ever. -# How to Build - -## Prepare dependencies - -* JDK 1.8 (JDK 1.9+ are not supported yet) -* On Linux Ubuntu system (e.g. Ubuntu 16.04.4 LTS), ensure that the machine has [__Oracle JDK 8__](https://www.digitalocean.com/community/tutorials/how-to-install-java-with-apt-get-on-ubuntu-16-04), instead of having __Open JDK 8__ in the system. If you are building the source code by using __Open JDK 8__, you will get [__Build Failed__](https://github.com/tronprotocol/java-tron/issues/337) result. -* Open **UDP** ports for connection to the network -* **MINIMUM** 2 ENERGY Cores - -## Build and Deploy automatically using scripts - -- Please take a look at the [Tron Deployment Scripts](https://github.com/tronprotocol/TronDeployment) repository. - -## Getting the code with git - -* Use Git from the Terminal, see the [Setting up Git](https://help.github.com/articles/set-up-git/) and [Fork a Repo](https://help.github.com/articles/fork-a-repo/) articles. -* develop branch: the newest code -* master branch: more stable than develop. -In the shell command, type: -```bash -git clone https://github.com/tronprotocol/java-tron.git -git checkout -t origin/master -``` - -* For Mac, you can also install **[GitHub for Mac](https://mac.github.com/)** then **[fork and clone our repository](https://guides.github.com/activities/forking/)**. - -* If you'd rather not use Git, [Download the ZIP](https://github.com/tronprotocol/java-tron/archive/develop.zip) - -## Including java-tron as dependency - -* If you don't want to checkout the code and build the project, you can include it directly as a dependency - -**Using gradle:** - -``` -repositories { - maven { url 'https://jitpack.io' } -} -dependencies { - implementation 'com.github.tronprotocol:java-tron:develop-SNAPSHOT' -} -``` - -**Using maven:** - -```xml -... -stackSize
+ * . Verifies that the stack is at least stackSize
*
* @param stackSize int
* @throws StackTooSmallException If the stack is smaller than stackSize
@@ -345,22 +342,12 @@ public void memorySave(DataWord addrB, DataWord value) {
memory.write(addrB.intValue(), value.getData(), value.getData().length, false);
}
- public void memorySaveLimited(int addr, byte[] data, int dataSize) {
- memory.write(addr, data, dataSize, true);
- }
-
public void memorySave(int addr, byte[] value) {
memory.write(addr, value, value.length, false);
}
- public void memoryExpand(DataWord outDataOffs, DataWord outDataSize) {
- if (!outDataSize.isZero()) {
- memory.extend(outDataOffs.intValue(), outDataSize.intValue());
- }
- }
-
/**
- * Allocates a piece of memory and stores value at given offset address
+ * . Allocates a piece of memory and stores value at given offset address
*
* @param addr is the offset address
* @param allocSize size of memory needed to write
@@ -370,6 +357,15 @@ public void memorySave(int addr, int allocSize, byte[] value) {
memory.extendAndWrite(addr, allocSize, value);
}
+ public void memorySaveLimited(int addr, byte[] data, int dataSize) {
+ memory.write(addr, data, dataSize, true);
+ }
+
+ public void memoryExpand(DataWord outDataOffs, DataWord outDataSize) {
+ if (!outDataSize.isZero()) {
+ memory.extend(outDataOffs.intValue(), outDataSize.intValue());
+ }
+ }
public DataWord memoryLoad(DataWord addr) {
return memory.readWord(addr.intValue());
@@ -384,7 +380,7 @@ public byte[] memoryChunk(int offset, int size) {
}
/**
- * Allocates extra memory in the program for a specified size, calculated from a given offset
+ * . Allocates extra memory in the program for a specified size, calculated from a given offset
*
* @param offset the memory address offset
* @param size the number of bytes to allocate
@@ -426,6 +422,10 @@ public void suicide(DataWord obtainerAddress) {
transferAllToken(getContractState(), owner, obtainer);
}
} catch (ContractValidateException e) {
+ if (VMConfig.allowTvmConstantinople()) {
+ throw new TransferException(
+ "transfer all token or transfer all trx failed in suicide: %s", e.getMessage());
+ }
throw new BytecodeExecutionException("transfer failure");
}
}
@@ -444,48 +444,78 @@ public void createContract(DataWord value, DataWord memStart, DataWord memSize)
stackPushZero();
return;
}
+ // [1] FETCH THE CODE FROM THE MEMORY
+ byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue());
- byte[] senderAddress = convertToTronAddress(this.getContractAddress().getLast20Bytes());
+ byte[] newAddress = Wallet
+ .generateContractAddress(rootTransactionId, nonce);
- long endowment = value.value().longValueExact();
- if (getContractState().getBalance(senderAddress) < endowment) {
- stackPushZero();
- return;
- }
+ createContractImpl(value, programCode, newAddress, false);
+ }
- // [1] FETCH THE CODE FROM THE MEMORY
- byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue());
+ private void createContractImpl(DataWord value, byte[] programCode, byte[] newAddress,
+ boolean isCreate2) {
+ byte[] senderAddress = convertToTronAddress(this.getContractAddress().getLast20Bytes());
if (logger.isDebugEnabled()) {
logger.debug("creating a new contract inside contract run: [{}]",
Hex.toHexString(senderAddress));
}
- byte[] newAddress = Wallet
- .generateContractAddress(rootTransactionId, nonce);
+ long endowment = value.value().longValueExact();
+ if (getContractState().getBalance(senderAddress) < endowment) {
+ stackPushZero();
+ return;
+ }
- AccountCapsule existingAddr = getContractState().getAccount(newAddress);
- boolean contractAlreadyExists = existingAddr != null;
+ AccountCapsule existingAccount = getContractState().getAccount(newAddress);
+ boolean contractAlreadyExists = existingAccount != null;
+ if (VMConfig.allowTvmConstantinople()) {
+ contractAlreadyExists =
+ contractAlreadyExists && isContractExist(existingAccount, getContractState());
+ }
Deposit deposit = getContractState().newDepositChild();
+ if (VMConfig.allowTvmConstantinople()) {
+ if (existingAccount == null) {
+ deposit.createAccount(newAddress, "CreatedByContract",
+ AccountType.Contract);
+ } else if (!contractAlreadyExists) {
+ existingAccount.updateAccountType(AccountType.Contract);
+ existingAccount.clearDelegatedResource();
+ deposit.updateAccount(newAddress, existingAccount);
+ }
- //In case of hashing collisions, check for any balance before createAccount()
- long oldBalance = deposit.getBalance(newAddress);
-
- SmartContract newSmartContract = SmartContract.newBuilder()
- .setContractAddress(ByteString.copyFrom(newAddress)).setConsumeUserResourcePercent(100)
- .setOriginAddress(ByteString.copyFrom(senderAddress)).build();
- deposit.createContract(newAddress, new ContractCapsule(newSmartContract));
- deposit.createAccount(newAddress, "CreatedByContract",
- Protocol.AccountType.Contract);
+ if (!contractAlreadyExists) {
+ Builder builder = SmartContract.newBuilder();
+ builder.setContractAddress(ByteString.copyFrom(newAddress))
+ .setConsumeUserResourcePercent(100)
+ .setOriginAddress(ByteString.copyFrom(senderAddress));
+ if (isCreate2) {
+ builder.setTrxHash(ByteString.copyFrom(rootTransactionId));
+ }
+ SmartContract newSmartContract = builder.build();
+ deposit.createContract(newAddress, new ContractCapsule(newSmartContract));
+ }
+ } else {
+ deposit.createAccount(newAddress, "CreatedByContract",
+ Protocol.AccountType.Contract);
+ SmartContract newSmartContract = SmartContract.newBuilder()
+ .setContractAddress(ByteString.copyFrom(newAddress)).setConsumeUserResourcePercent(100)
+ .setOriginAddress(ByteString.copyFrom(senderAddress)).build();
+ deposit.createContract(newAddress, new ContractCapsule(newSmartContract));
+ // In case of hashing collisions, check for any balance before createAccount()
+ long oldBalance = deposit.getBalance(newAddress);
+ deposit.addBalance(newAddress, oldBalance);
+ }
- deposit.addBalance(newAddress, oldBalance);
// [4] TRANSFER THE BALANCE
long newBalance = 0L;
if (!byTestingSuite() && endowment > 0) {
try {
TransferActuator.validateForSmartContract(deposit, senderAddress, newAddress, endowment);
} catch (ContractValidateException e) {
+ // TODO: unreachable exception
throw new BytecodeExecutionException(VALIDATE_FOR_SMART_CONTRACT_FAILURE);
}
deposit.addBalance(senderAddress, -endowment);
@@ -517,7 +547,6 @@ this, new DataWord(newAddress), getContractAddress(), value, new DataWord(0),
VM vm = new VM(config);
Program program = new Program(programCode, programInvoke, internalTx, config, this.blockCap);
program.setRootTransactionId(this.rootTransactionId);
- program.setRootCallConstant(this.isRootCallConstant);
vm.play(program);
createResult = program.getResult();
getTrace().merge(program.getTrace());
@@ -536,7 +565,7 @@ this, new DataWord(newAddress), getContractAddress(), value, new DataWord(0),
if (!createResult.isRevert()) {
if (afterSpend < 0) {
createResult.setException(
- Program.Exception.notEnoughSpendEnergy("No energy to save just created contract code",
+ Exception.notEnoughSpendEnergy("No energy to save just created contract code",
saveCodeEnergy, programInvoke.getEnergyLimit() - createResult.getEnergyUsed()));
} else {
createResult.spendEnergy(saveCodeEnergy);
@@ -588,7 +617,7 @@ public void refundEnergyAfterVM(DataWord energyLimit, ProgramResult result) {
}
/**
- * That method is for internal code invocations
+ * . That method is for internal code invocations
*
* - Normal calls invoke a specified contract which updates itself - Stateless calls invoke code
* from another contract, within the context of the caller
@@ -621,7 +650,17 @@ public void callToAddress(MessageCall msg) {
Deposit deposit = getContractState().newDepositChild();
// 2.1 PERFORM THE VALUE (endowment) PART
- long endowment = msg.getEndowment().value().longValueExact();
+ long endowment;
+ try {
+ endowment = msg.getEndowment().value().longValueExact();
+ } catch (ArithmeticException e) {
+ if (VMConfig.allowTvmConstantinople()) {
+ refundEnergy(msg.getEnergy().longValue(), "endowment out of long range");
+ throw new TransferException("endowment out of long range");
+ } else {
+ throw e;
+ }
+ }
// transfer trx validation
byte[] tokenId = null;
@@ -636,9 +675,8 @@ public void callToAddress(MessageCall msg) {
refundEnergy(msg.getEnergy().longValue(), "refund energy from message call");
return;
}
- }
- // transfer trc10 token validation
- else {
+ } else {
+ // transfer trc10 token validation
tokenId = String.valueOf(msg.getTokenId().longValue()).getBytes();
long senderBalance = deposit.getTokenBalance(senderAddress, tokenId);
if (senderBalance < endowment) {
@@ -668,6 +706,10 @@ public void callToAddress(MessageCall msg) {
TransferActuator
.validateForSmartContract(deposit, senderAddress, contextAddress, endowment);
} catch (ContractValidateException e) {
+ if (VMConfig.allowTvmConstantinople()) {
+ refundEnergy(msg.getEnergy().longValue(), "refund energy from message call");
+ throw new TransferException("transfer trx failed: %s", e.getMessage());
+ }
throw new BytecodeExecutionException(VALIDATE_FOR_SMART_CONTRACT_FAILURE);
}
deposit.addBalance(senderAddress, -endowment);
@@ -677,6 +719,10 @@ public void callToAddress(MessageCall msg) {
TransferAssetActuator.validateForSmartContract(deposit, senderAddress, contextAddress,
tokenId, endowment);
} catch (ContractValidateException e) {
+ if (VMConfig.allowTvmConstantinople()) {
+ refundEnergy(msg.getEnergy().longValue(), "refund energy from message call");
+ throw new TransferException("transfer trc10 failed: %s", e.getMessage());
+ }
throw new BytecodeExecutionException(VALIDATE_FOR_SMART_CONTRACT_FAILURE);
}
deposit.addTokenBalance(senderAddress, tokenId, -endowment);
@@ -709,7 +755,6 @@ this, new DataWord(contextAddress),
Program program = new Program(programCode, programInvoke, internalTx, config,
this.blockCap);
program.setRootTransactionId(this.rootTransactionId);
- program.setRootCallConstant(this.isRootCallConstant);
vm.play(program);
callResult = program.getResult();
@@ -784,7 +829,8 @@ public void resetNonce() {
public void spendEnergy(long energyValue, String opName) {
if (getEnergylimitLeftLong() < energyValue) {
throw new OutOfEnergyException(
- "Not enough energy for '%s' operation executing: curInvokeEnergyLimit[%d], curOpEnergy[%d], usedEnergy[%d]",
+ "Not enough energy for '%s' operation executing: curInvokeEnergyLimit[%d],"
+ + " curOpEnergy[%d], usedEnergy[%d]",
opName, invoke.getEnergyLimit(), energyValue, getResult().getEnergyUsed());
}
getResult().spendEnergy(energyValue);
@@ -800,8 +846,8 @@ public void checkCPUTimeLimit(String opName) {
long vmNowInUs = System.nanoTime() / 1000;
if (vmNowInUs > getVmShouldEndInUs()) {
logger.info(
- "minTimeRatio: {}, maxTimeRatio: {}, vm should end time in us: {}, " +
- "vm now time in us: {}, vm start time in us: {}",
+ "minTimeRatio: {}, maxTimeRatio: {}, vm should end time in us: {}, "
+ + "vm now time in us: {}, vm start time in us: {}",
Args.getInstance().getMinTimeRatio(), Args.getInstance().getMaxTimeRatio(),
getVmShouldEndInUs(), vmNowInUs, getVmStartInUs());
throw Exception.notEnoughTime(opName);
@@ -845,6 +891,29 @@ public byte[] getCodeAt(DataWord address) {
return nullToEmpty(code);
}
+ public byte[] getCodeHashAt(DataWord address) {
+ byte[] tronAddr = convertToTronAddress(address.getLast20Bytes());
+ AccountCapsule account = getContractState().getAccount(tronAddr);
+ if (account != null) {
+ ContractCapsule contract = getContractState().getContract(tronAddr);
+ byte[] codeHash;
+ if (contract != null) {
+ codeHash = contract.getCodeHash();
+ if (ByteUtil.isNullOrZeroArray(codeHash)) {
+ byte[] code = getCodeAt(address);
+ codeHash = Hash.sha3(code);
+ contract.setCodeHash(codeHash);
+ getContractState().updateContract(tronAddr, contract);
+ }
+ } else {
+ codeHash = Hash.sha3(new byte[0]);
+ }
+ return codeHash;
+ } else {
+ return EMPTY_BYTE_ARRAY;
+ }
+ }
+
public DataWord getContractAddress() {
return invoke.getContractAddress().clone();
}
@@ -1169,6 +1238,15 @@ public static String stringifyMultiline(byte[] code) {
return sb.toString();
}
+ public void createContract2(DataWord value, DataWord memStart, DataWord memSize, DataWord salt) {
+ byte[] senderAddress = convertToTronAddress(this.getCallerAddress().getLast20Bytes());
+ byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue());
+
+ byte[] contractAddress = Wallet
+ .generateContractAddress2(senderAddress, salt.getData(), programCode);
+ createContractImpl(value, programCode, contractAddress, true);
+ }
+
static class ByteCodeIterator {
private byte[] code;
@@ -1235,8 +1313,8 @@ static BitSet buildReachableBytecodesMask(byte[] code) {
gotos.add(jumpPC);
}
}
- if (it.getCurOpcode() == OpCode.JUMP || it.getCurOpcode() == OpCode.RETURN ||
- it.getCurOpcode() == OpCode.STOP) {
+ if (it.getCurOpcode() == OpCode.JUMP || it.getCurOpcode() == OpCode.RETURN
+ || it.getCurOpcode() == OpCode.STOP) {
if (gotos.isEmpty()) {
break;
}
@@ -1318,9 +1396,8 @@ public void callToPrecompiledAddress(MessageCall msg,
// transfer trx validation
if (!isTokenTransfer) {
senderBalance = deposit.getBalance(senderAddress);
- }
- // transfer trc10 token validation
- else {
+ } else {
+ // transfer trc10 token validation
tokenId = String.valueOf(msg.getTokenId().longValue()).getBytes();
senderBalance = deposit.getTokenBalance(senderAddress, tokenId);
}
@@ -1362,12 +1439,12 @@ public void callToPrecompiledAddress(MessageCall msg,
this.stackPushZero();
} else {
// Delegate or not. if is delegated, we will use msg sender, otherwise use contract address
- contract.setCallerAddress(convertToTronAddress(msg.getType().callIsDelegate() ?
- getCallerAddress().getLast20Bytes() : getContractAddress().getLast20Bytes()));
+ contract.setCallerAddress(convertToTronAddress(msg.getType().callIsDelegate()
+ ? getCallerAddress().getLast20Bytes() : getContractAddress().getLast20Bytes()));
// this is the depositImpl, not contractState as above
contract.setDeposit(deposit);
contract.setResult(this.result);
- contract.setRootCallConstant(getRootCallConstant().booleanValue());
+ contract.setStaticCall(isStaticCall());
PairUses reflection to gracefully fall back to the Java implementation if
+ * {@code Unsafe} isn't available.
+ */
+ private static class LexicographicalComparerHolder {
+ static final String UNSAFE_COMPARER_NAME =
+ LexicographicalComparerHolder.class.getName() + "$UnsafeComparer";
+
+ static final Comparer
+ * 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, integers, 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
+ *
+ * @author Roman Mandeleil
+ * @since 01.04.2014
+ */
+public class RLP {
+
+ private static final Logger logger = LoggerFactory.getLogger("rlp");
+
+
+ public static final byte[] EMPTY_ELEMENT_RLP = encodeElement(new byte[0]);
+
+ private static final int MAX_DEPTH = 16;
+
+ /**
+ * Allow for content up to size of 2^64 bytes *
+ */
+ private static final double MAX_ITEM_LENGTH = Math.pow(256, 8);
+
+ /**
+ * Reason for threshold according to Vitalik Buterin: - 56 bytes maximizes the benefit of both
+ * options - if we went with 60 then we would have only had 4 slots for long strings so RLP would
+ * not have been able to store objects above 4gb - if we went with 48 then RLP would be fine for
+ * 2^128 space, but that's way too much - so 56 and 2^64 space seems like the right place to put
+ * the cutoff - also, that's where Bitcoin's varint does the cutof
+ */
+ private static final 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.
+ */
+
+ /**
+ * [0x80] 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 final int OFFSET_SHORT_ITEM = 0x80;
+
+ /**
+ * [0xb7] 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 final int OFFSET_LONG_ITEM = 0xb7;
+
+ /**
+ * [0xc0] 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 final int OFFSET_SHORT_LIST = 0xc0;
+
+ /**
+ * [0xf7] 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 final int OFFSET_LONG_LIST = 0xf7;
+
+
+ /* ******************************************************
+ * DECODING *
+ * ******************************************************/
+
+ private static byte decodeOneByteItem(byte[] data, int index) {
+ // null item
+ if ((data[index] & 0xFF) == OFFSET_SHORT_ITEM) {
+ return (byte) (data[index] - OFFSET_SHORT_ITEM);
+ }
+ // single byte item
+ if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) {
+ return data[index];
+ }
+ // single byte item
+ if ((data[index] & 0xFF) == OFFSET_SHORT_ITEM + 1) {
+ return data[index + 1];
+ }
+ return 0;
+ }
+
+ public static int decodeInt(byte[] data, int index) {
+
+ int value = 0;
+ // NOTE: From RLP doc:
+ // Ethereum integers must be represented in big endian binary form
+ // with no leading zeroes (thus making the integer value zero be
+ // equivalent to the empty byte array)
+
+ if (data[index] == 0x00) {
+ throw new RuntimeException("not a number");
+ } else if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) {
+
+ return data[index];
+
+ } else if ((data[index] & 0xFF) <= OFFSET_SHORT_ITEM + Integer.BYTES) {
+
+ byte length = (byte) (data[index] - OFFSET_SHORT_ITEM);
+ byte pow = (byte) (length - 1);
+ for (int i = 1; i <= length; ++i) {
+ // << (8 * pow) == bit shift to 0 (*1), 8 (*256) , 16 (*65..)..
+ value += (data[index + i] & 0xFF) << (8 * pow);
+ pow--;
+ }
+ } else {
+
+ // If there are more than 4 bytes, it is not going
+ // to decode properly into an int.
+ throw new RuntimeException("wrong decode attempt");
+ }
+ return value;
+ }
+
+ static short decodeShort(byte[] data, int index) {
+
+ short value = 0;
+
+ if (data[index] == 0x00) {
+ throw new RuntimeException("not a number");
+ } else if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) {
+
+ return data[index];
+
+ } else if ((data[index] & 0xFF) <= OFFSET_SHORT_ITEM + Short.BYTES) {
+
+ byte length = (byte) (data[index] - OFFSET_SHORT_ITEM);
+ byte pow = (byte) (length - 1);
+ for (int i = 1; i <= length; ++i) {
+ // << (8 * pow) == bit shift to 0 (*1), 8 (*256) , 16 (*65..)
+ value += (data[index + i] & 0xFF) << (8 * pow);
+ pow--;
+ }
+ } else {
+
+ // If there are more than 2 bytes, it is not going
+ // to decode properly into a short.
+ throw new RuntimeException("wrong decode attempt");
+ }
+ return value;
+ }
+
+ public static long decodeLong(byte[] data, int index) {
+
+ long value = 0;
+
+ if (data[index] == 0x00) {
+ throw new RuntimeException("not a number");
+ } else if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) {
+
+ return data[index];
+
+ } else if ((data[index] & 0xFF) <= OFFSET_SHORT_ITEM + Long.BYTES) {
+
+ byte length = (byte) (data[index] - OFFSET_SHORT_ITEM);
+ byte pow = (byte) (length - 1);
+ for (int i = 1; i <= length; ++i) {
+ // << (8 * pow) == bit shift to 0 (*1), 8 (*256) , 16 (*65..)..
+ value += (long) (data[index + i] & 0xFF) << (8 * pow);
+ pow--;
+ }
+ } else {
+
+ // If there are more than 8 bytes, it is not going
+ // to decode properly into a long.
+ throw new RuntimeException("wrong decode attempt");
+ }
+ return value;
+ }
+
+ private static String decodeStringItem(byte[] data, int index) {
+
+ final byte[] valueBytes = decodeItemBytes(data, index);
+
+ if (valueBytes.length == 0) {
+ // shortcut
+ return "";
+ } else {
+ return new String(valueBytes);
+ }
+ }
+
+ public static BigInteger decodeBigInteger(byte[] data, int index) {
+
+ final byte[] valueBytes = decodeItemBytes(data, index);
+
+ if (valueBytes.length == 0) {
+ // shortcut
+ return BigInteger.ZERO;
+ } else {
+ BigInteger res = new BigInteger(1, valueBytes);
+ return res;
+ }
+ }
+
+ private static byte[] decodeByteArray(byte[] data, int index) {
+
+ return decodeItemBytes(data, index);
+ }
+
+ private static int nextItemLength(byte[] data, int index) {
+
+ if (index >= data.length) {
+ return -1;
+ }
+ // [0xf8, 0xff]
+ if ((data[index] & 0xFF) > OFFSET_LONG_LIST) {
+ byte lengthOfLength = (byte) (data[index] - OFFSET_LONG_LIST);
+
+ return calcLength(lengthOfLength, data, index);
+ }
+ // [0xc0, 0xf7]
+ if ((data[index] & 0xFF) >= OFFSET_SHORT_LIST
+ && (data[index] & 0xFF) <= OFFSET_LONG_LIST) {
+
+ return (byte) ((data[index] & 0xFF) - OFFSET_SHORT_LIST);
+ }
+ // [0xb8, 0xbf]
+ if ((data[index] & 0xFF) > OFFSET_LONG_ITEM
+ && (data[index] & 0xFF) < OFFSET_SHORT_LIST) {
+
+ byte lengthOfLength = (byte) (data[index] - OFFSET_LONG_ITEM);
+ return calcLength(lengthOfLength, data, index);
+ }
+ // [0x81, 0xb7]
+ if ((data[index] & 0xFF) > OFFSET_SHORT_ITEM
+ && (data[index] & 0xFF) <= OFFSET_LONG_ITEM) {
+ return (byte) ((data[index] & 0xFF) - OFFSET_SHORT_ITEM);
+ }
+ // [0x00, 0x80]
+ if ((data[index] & 0xFF) <= OFFSET_SHORT_ITEM) {
+ return 1;
+ }
+ return -1;
+ }
+
+ public static byte[] decodeIP4Bytes(byte[] data, int index) {
+
+ int offset = 1;
+
+ final byte[] result = new byte[4];
+ for (int i = 0; i < 4; i++) {
+ result[i] = decodeOneByteItem(data, index + offset);
+ if ((data[index + offset] & 0xFF) > OFFSET_SHORT_ITEM) {
+ offset += 2;
+ } else {
+ offset += 1;
+ }
+ }
+
+ // return IP address
+ return result;
+ }
+
+ public static int getFirstListElement(byte[] payload, int pos) {
+
+ if (pos >= payload.length) {
+ return -1;
+ }
+
+ // [0xf8, 0xff]
+ if ((payload[pos] & 0xFF) > OFFSET_LONG_LIST) {
+ byte lengthOfLength = (byte) (payload[pos] - OFFSET_LONG_LIST);
+ return pos + lengthOfLength + 1;
+ }
+ // [0xc0, 0xf7]
+ if ((payload[pos] & 0xFF) >= OFFSET_SHORT_LIST
+ && (payload[pos] & 0xFF) <= OFFSET_LONG_LIST) {
+ return pos + 1;
+ }
+ // [0xb8, 0xbf]
+ if ((payload[pos] & 0xFF) > OFFSET_LONG_ITEM
+ && (payload[pos] & 0xFF) < OFFSET_SHORT_LIST) {
+ byte lengthOfLength = (byte) (payload[pos] - OFFSET_LONG_ITEM);
+ return pos + lengthOfLength + 1;
+ }
+ return -1;
+ }
+
+ public static int getNextElementIndex(byte[] payload, int pos) {
+
+ if (pos >= payload.length) {
+ return -1;
+ }
+
+ // [0xf8, 0xff]
+ if ((payload[pos] & 0xFF) > OFFSET_LONG_LIST) {
+ byte lengthOfLength = (byte) (payload[pos] - OFFSET_LONG_LIST);
+ int length = calcLength(lengthOfLength, payload, pos);
+ return pos + lengthOfLength + length + 1;
+ }
+ // [0xc0, 0xf7]
+ if ((payload[pos] & 0xFF) >= OFFSET_SHORT_LIST
+ && (payload[pos] & 0xFF) <= OFFSET_LONG_LIST) {
+
+ byte length = (byte) ((payload[pos] & 0xFF) - OFFSET_SHORT_LIST);
+ return pos + 1 + length;
+ }
+ // [0xb8, 0xbf]
+ if ((payload[pos] & 0xFF) > OFFSET_LONG_ITEM
+ && (payload[pos] & 0xFF) < OFFSET_SHORT_LIST) {
+
+ byte lengthOfLength = (byte) (payload[pos] - OFFSET_LONG_ITEM);
+ int length = calcLength(lengthOfLength, payload, pos);
+ return pos + lengthOfLength + length + 1;
+ }
+ // [0x81, 0xb7]
+ if ((payload[pos] & 0xFF) > OFFSET_SHORT_ITEM
+ && (payload[pos] & 0xFF) <= OFFSET_LONG_ITEM) {
+
+ byte length = (byte) ((payload[pos] & 0xFF) - OFFSET_SHORT_ITEM);
+ return pos + 1 + length;
+ }
+ // []0x80]
+ if ((payload[pos] & 0xFF) == OFFSET_SHORT_ITEM) {
+ return pos + 1;
+ }
+ // [0x00, 0x7f]
+ if ((payload[pos] & 0xFF) < OFFSET_SHORT_ITEM) {
+ return pos + 1;
+ }
+ return -1;
+ }
+
+ /**
+ * Parse length of long item or list. RLP supports lengths with up to 8 bytes long, but due to
+ * java limitation it returns either encoded length or {@link Integer#MAX_VALUE} in case if
+ * encoded length is greater
+ *
+ * @param lengthOfLength length of length in bytes
+ * @param msgData message
+ * @param pos position to parse from
+ * @return calculated length
+ */
+ 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) {
+
+ int bt = msgData[pos + i] & 0xFF;
+ int shift = 8 * pow;
+
+ // no leading zeros are acceptable
+ if (bt == 0 && length == 0) {
+ throw new RuntimeException("RLP length contains leading zeros");
+ }
+
+ // return MAX_VALUE if index of highest bit is more than 31
+ if (32 - Integer.numberOfLeadingZeros(bt) + shift > 31) {
+ return Integer.MAX_VALUE;
+ }
+
+ length += bt << shift;
+ pow--;
+ }
+
+ // check that length is in payload bounds
+ verifyLength(length, msgData.length - pos - lengthOfLength);
+
+ return length;
+ }
+
+ public static byte getCommandCode(byte[] data) {
+ int index = getFirstListElement(data, 0);
+ final byte command = data[index];
+ return ((command & 0xFF) == OFFSET_SHORT_ITEM) ? 0 : command;
+ }
+
+ /**
+ * Parse wire byte[] message into RLP elements
+ *
+ * @param msgData - raw RLP data
+ * @param depthLimit - limits depth of decoding
+ * @return rlpList - outcome of recursive RLP structure
+ */
+ public static RLPList decode2(byte[] msgData, int depthLimit) {
+ if (depthLimit < 1) {
+ throw new RuntimeException("Depth limit should be 1 or higher");
+ }
+ RLPList rlpList = new RLPList();
+ fullTraverse(msgData, 0, 0, msgData.length, rlpList, depthLimit);
+ return rlpList;
+ }
+
+ /**
+ * Parse wire byte[] message into RLP elements
+ *
+ * @param msgData - raw RLP data
+ * @return rlpList - outcome of recursive RLP structure
+ */
+ public static RLPList decode2(byte[] msgData) {
+ RLPList rlpList = new RLPList();
+ fullTraverse(msgData, 0, 0, msgData.length, rlpList, Integer.MAX_VALUE);
+ return rlpList;
+ }
+
+ /**
+ * Decodes RLP with list without going deep after 1st level list (actually, 2nd as 1st level is
+ * wrap only)
+ *
+ * So assuming you've packed several byte[] with {@link #encodeList(byte[]...)}, you could use
+ * this method to unpack them, getting RLPList with RLPItem's holding byte[] inside
+ *
+ * @param msgData rlp data
+ * @return list of RLPItems
+ */
+ public static RLPList unwrapList(byte[] msgData) {
+ return (RLPList) decode2(msgData, 2).get(0);
+ }
+
+ public static RLPElement decode2OneItem(byte[] msgData, int startPos) {
+ RLPList rlpList = new RLPList();
+ fullTraverse(msgData, 0, startPos, startPos + 1, rlpList, Integer.MAX_VALUE);
+ return rlpList.get(0);
+ }
+
+ /**
+ * Get exactly one message payload
+ */
+ static void fullTraverse(byte[] msgData, int level, int startPos,
+ int endPos, RLPList rlpList, int depth) {
+ if (level > MAX_DEPTH) {
+ throw new RuntimeException(
+ String.format("Error: Traversing over max RLP depth (%s)", MAX_DEPTH));
+ }
+
+ try {
+ if (msgData == null || msgData.length == 0) {
+ return;
+ }
+ int pos = startPos;
+
+ while (pos < endPos) {
+
+ logger.debug("fullTraverse: level: " + level + " startPos: " + pos + " endPos: " + 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) > OFFSET_LONG_LIST) {
+
+ byte lengthOfLength = (byte) (msgData[pos] - OFFSET_LONG_LIST);
+ int length = calcLength(lengthOfLength, msgData, pos);
+
+ if (length < SIZE_THRESHOLD) {
+ throw new RuntimeException("Short list has been encoded as long list");
+ }
+
+ // check that length is in payload bounds
+ verifyLength(length, msgData.length - pos - lengthOfLength);
+
+ byte[] rlpData = new byte[lengthOfLength + length + 1];
+ System.arraycopy(msgData, pos, rlpData, 0, lengthOfLength
+ + length + 1);
+
+ if (level + 1 < depth) {
+ RLPList newLevelList = new RLPList();
+ newLevelList.setRLPData(rlpData);
+
+ fullTraverse(msgData, level + 1, pos + lengthOfLength + 1,
+ pos + lengthOfLength + length + 1, newLevelList, depth);
+ rlpList.add(newLevelList);
+ } else {
+ rlpList.add(new RLPItem(rlpData));
+ }
+
+ pos += lengthOfLength + length + 1;
+ continue;
+ }
+ // It's a list with a payload less than 55 bytes
+ if ((msgData[pos] & 0xFF) >= OFFSET_SHORT_LIST
+ && (msgData[pos] & 0xFF) <= OFFSET_LONG_LIST) {
+
+ byte length = (byte) ((msgData[pos] & 0xFF) - OFFSET_SHORT_LIST);
+
+ byte[] rlpData = new byte[length + 1];
+ System.arraycopy(msgData, pos, rlpData, 0, length + 1);
+
+ if (level + 1 < depth) {
+ RLPList newLevelList = new RLPList();
+ newLevelList.setRLPData(rlpData);
+
+ if (length > 0) {
+ fullTraverse(msgData, level + 1, pos + 1, pos + length + 1, newLevelList, depth);
+ }
+ rlpList.add(newLevelList);
+ } else {
+ rlpList.add(new RLPItem(rlpData));
+ }
+
+ 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) > OFFSET_LONG_ITEM
+ && (msgData[pos] & 0xFF) < OFFSET_SHORT_LIST) {
+
+ byte lengthOfLength = (byte) (msgData[pos] - OFFSET_LONG_ITEM);
+ int length = calcLength(lengthOfLength, msgData, pos);
+
+ if (length < SIZE_THRESHOLD) {
+ throw new RuntimeException("Short item has been encoded as long item");
+ }
+
+ // check that length is in payload bounds
+ verifyLength(length, msgData.length - pos - lengthOfLength);
+
+ // now we can parse an item for data[1]..data[length]
+ byte[] item = new byte[length];
+ System.arraycopy(msgData, pos + lengthOfLength + 1, item,
+ 0, length);
+
+ RLPItem rlpItem = new RLPItem(item);
+ rlpList.add(rlpItem);
+ pos += lengthOfLength + length + 1;
+
+ continue;
+ }
+ // It's an item less than 55 bytes long,
+ // data[0] - 0x80 == length of the item
+ if ((msgData[pos] & 0xFF) > OFFSET_SHORT_ITEM
+ && (msgData[pos] & 0xFF) <= OFFSET_LONG_ITEM) {
+
+ byte length = (byte) ((msgData[pos] & 0xFF) - OFFSET_SHORT_ITEM);
+
+ byte[] item = new byte[length];
+ System.arraycopy(msgData, pos + 1, item, 0, length);
+
+ if (length == 1 && (item[0] & 0xFF) < OFFSET_SHORT_ITEM) {
+ throw new RuntimeException("Single byte has been encoded as byte string");
+ }
+
+ RLPItem rlpItem = new RLPItem(item);
+ rlpList.add(rlpItem);
+ pos += 1 + length;
+
+ continue;
+ }
+ // null item
+ if ((msgData[pos] & 0xFF) == OFFSET_SHORT_ITEM) {
+ byte[] item = ByteUtil.EMPTY_BYTE_ARRAY;
+ RLPItem rlpItem = new RLPItem(item);
+ rlpList.add(rlpItem);
+ pos += 1;
+ continue;
+ }
+ // single byte item
+ if ((msgData[pos] & 0xFF) < OFFSET_SHORT_ITEM) {
+
+ byte[] item = {(byte) (msgData[pos] & 0xFF)};
+
+ RLPItem rlpItem = new RLPItem(item);
+ rlpList.add(rlpItem);
+ pos += 1;
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "RLP wrong encoding (" + Hex.toHexString(msgData, startPos, endPos - startPos) + ")", e);
+ } catch (OutOfMemoryError e) {
+ throw new RuntimeException("Invalid RLP (excessive mem allocation while parsing) (" + Hex
+ .toHexString(msgData, startPos, endPos - startPos) + ")", e);
+ }
+ }
+
+ /**
+ * Compares supplied length information with maximum possible
+ *
+ * @param suppliedLength Length info from header
+ * @param availableLength Length of remaining object
+ * @throws RuntimeException if supplied length is bigger than available
+ */
+ private static void verifyLength(int suppliedLength, int availableLength) {
+ if (suppliedLength > availableLength) {
+ throw new RuntimeException(String.format("Length parsed from RLP (%s bytes) is greater " +
+ "than possible size of data (%s bytes)", suppliedLength, availableLength));
+ }
+ }
+
+ /**
+ * Reads any RLP encoded byte-array and returns all objects as byte-array or list of byte-arrays
+ *
+ * @param data RLP encoded byte-array
+ * @param pos position in the array to start reading
+ * @return DecodeResult encapsulates the decoded items as a single Object and the final read
+ * position
+ */
+ public static DecodeResult decode(byte[] data, int pos) {
+ if (data == null || data.length < 1) {
+ return null;
+ }
+ int prefix = data[pos] & 0xFF;
+ if (prefix == OFFSET_SHORT_ITEM) { // 0x80
+ return new DecodeResult(pos + 1, ""); // means no length or 0
+ } else if (prefix < OFFSET_SHORT_ITEM) { // [0x00, 0x7f]
+ return new DecodeResult(pos + 1, new byte[]{data[pos]}); // byte is its own RLP encoding
+ } else if (prefix <= OFFSET_LONG_ITEM) { // [0x81, 0xb7]
+ int len = prefix - OFFSET_SHORT_ITEM; // length of the encoded bytes
+ return new DecodeResult(pos + 1 + len, copyOfRange(data, pos + 1, pos + 1 + len));
+ } else if (prefix < OFFSET_SHORT_LIST) { // [0xb8, 0xbf]
+ int lenlen = prefix - OFFSET_LONG_ITEM; // length of length the encoded bytes
+ int lenbytes = byteArrayToInt(
+ copyOfRange(data, pos + 1, pos + 1 + lenlen)); // length of encoded bytes
+ // check that length is in payload bounds
+ verifyLength(lenbytes, data.length - pos - 1 - lenlen);
+ return new DecodeResult(pos + 1 + lenlen + lenbytes,
+ copyOfRange(data, pos + 1 + lenlen, pos + 1 + lenlen
+ + lenbytes));
+ } else if (prefix <= OFFSET_LONG_LIST) { // [0xc0, 0xf7]
+ int len = prefix - OFFSET_SHORT_LIST; // length of the encoded list
+ int prevPos = pos;
+ pos++;
+ return decodeList(data, pos, len);
+ } else if (prefix <= 0xFF) { // [0xf8, 0xff]
+ int lenlen = prefix - OFFSET_LONG_LIST; // length of length the encoded list
+ int lenlist = byteArrayToInt(
+ copyOfRange(data, pos + 1, pos + 1 + lenlen)); // length of encoded bytes
+ pos = pos + lenlen + 1; // start at position of first element in list
+ int prevPos = lenlist;
+ return decodeList(data, pos, lenlist);
+ } else {
+ throw new RuntimeException(
+ "Only byte values between 0x00 and 0xFF are supported, but got: " + prefix);
+ }
+ }
+
+ public static final class LList {
+
+ private final byte[] rlp;
+ private final int[] offsets = new int[32];
+ private final int[] lens = new int[32];
+ private int cnt;
+
+ public LList(byte[] rlp) {
+ this.rlp = rlp;
+ }
+
+ public byte[] getEncoded() {
+ byte encoded[][] = new byte[cnt][];
+ for (int i = 0; i < cnt; i++) {
+ encoded[i] = encodeElement(getBytes(i));
+ }
+ return encodeList(encoded);
+ }
+
+ public void add(int off, int len, boolean isList) {
+ offsets[cnt] = off;
+ lens[cnt] = isList ? (-1 - len) : len;
+ cnt++;
+ }
+
+ public byte[] getBytes(int idx) {
+ int len = lens[idx];
+ len = len < 0 ? (-len - 1) : len;
+ byte[] ret = new byte[len];
+ System.arraycopy(rlp, offsets[idx], ret, 0, len);
+ return ret;
+ }
+
+ public LList getList(int idx) {
+ return decodeLazyList(rlp, offsets[idx], -lens[idx] - 1);
+ }
+
+ public boolean isList(int idx) {
+ return lens[idx] < 0;
+ }
+
+ public int size() {
+ return cnt;
+ }
+ }
+
+ public static LList decodeLazyList(byte[] data) {
+ LList lList = decodeLazyList(data, 0, data.length);
+ return lList == null ? null : lList.getList(0);
+ }
+
+ public static LList decodeLazyList(byte[] data, int pos, int length) {
+ if (data == null || data.length < 1) {
+ return null;
+ }
+ LList ret = new LList(data);
+ int end = pos + length;
+
+ while (pos < end) {
+ int prefix = data[pos] & 0xFF;
+ if (prefix == OFFSET_SHORT_ITEM) { // 0x80
+ ret.add(pos, 0, false); // means no length or 0
+ pos++;
+ } else if (prefix < OFFSET_SHORT_ITEM) { // [0x00, 0x7f]
+ ret.add(pos, 1, false); // means no length or 0
+ pos++;
+ } else if (prefix <= OFFSET_LONG_ITEM) { // [0x81, 0xb7]
+ int len = prefix - OFFSET_SHORT_ITEM; // length of the encoded bytes
+ ret.add(pos + 1, len, false);
+ pos += len + 1;
+ } else if (prefix < OFFSET_SHORT_LIST) { // [0xb8, 0xbf]
+ int lenlen = prefix - OFFSET_LONG_ITEM; // length of length the encoded bytes
+ int lenbytes = byteArrayToInt(
+ copyOfRange(data, pos + 1, pos + 1 + lenlen)); // length of encoded bytes
+ // check that length is in payload bounds
+ verifyLength(lenbytes, data.length - pos - 1 - lenlen);
+ ret.add(pos + 1 + lenlen, lenbytes, false);
+ pos += 1 + lenlen + lenbytes;
+ } else if (prefix <= OFFSET_LONG_LIST) { // [0xc0, 0xf7]
+ int len = prefix - OFFSET_SHORT_LIST; // length of the encoded list
+ ret.add(pos + 1, len, true);
+ pos += 1 + len;
+ } else if (prefix <= 0xFF) { // [0xf8, 0xff]
+ int lenlen = prefix - OFFSET_LONG_LIST; // length of length the encoded list
+ int lenlist = byteArrayToInt(
+ copyOfRange(data, pos + 1, pos + 1 + lenlen)); // length of encoded bytes
+ // check that length is in payload bounds
+ verifyLength(lenlist, data.length - pos - 1 - lenlen);
+ ret.add(pos + 1 + lenlen, lenlist, true);
+ pos += 1 + lenlen + lenlist; // start at position of first element in list
+ } else {
+ throw new RuntimeException(
+ "Only byte values between 0x00 and 0xFF are supported, but got: " + prefix);
+ }
+ }
+ return ret;
+ }
+
+
+ private static DecodeResult decodeList(byte[] data, int pos, int len) {
+ // check that length is in payload bounds
+ verifyLength(len, data.length - pos);
+ int prevPos;
+ List