Merge pull request #75 from nicksavers/master

Multiple performance fixes
This commit is contained in:
romanman 2014-08-04 12:34:34 +03:00
commit a70761ad2b
14 changed files with 172 additions and 123 deletions

View File

@ -73,9 +73,6 @@ public class Block {
timestamp, extraData, nonce);
this.txsState = new Trie(null);
byte[] stateRoot = WorldManager.getInstance().getRepository().getRootHash();
this.header.setStateRoot(stateRoot);
this.header.setTxTrieRoot(txsState.getRootHash());
this.transactionsList = transactionsList;
this.uncleList = uncleList;

View File

@ -1,6 +1,7 @@
package org.ethereum.core;
import org.ethereum.crypto.HashUtil;
import org.ethereum.db.Repository;
import org.ethereum.manager.WorldManager;
import org.ethereum.util.RLP;
import org.slf4j.Logger;
@ -64,17 +65,18 @@ public class Genesis extends Block {
NUMBER, MIN_GAS_PRICE, GAS_LIMIT, GAS_USED, TIMESTAMP,
EXTRA_DATA, NONCE, null, null);
Repository repository = WorldManager.getInstance().getRepository();
// The proof-of-concept series include a development premine, making the state root hash
// some value stateRoot. The latest documentation should be consulted for the value of the state root.
for (String address : premine) {
WorldManager.getInstance().getRepository().createAccount(Hex.decode(address));
WorldManager.getInstance().getRepository().addBalance (Hex.decode(address), BigInteger.valueOf(2).pow(200) );
repository.createAccount(Hex.decode(address));
repository.addBalance (Hex.decode(address), BigInteger.valueOf(2).pow(200) );
}
this.setStateRoot(WorldManager.getInstance().getRepository().getRootHash());
WorldManager.getInstance().getRepository().dumpState(0, 0, null);
setStateRoot(repository.getWorldState().getRootHash());
repository.dumpState(0, 0, null);
logger.info("Genesis-hash: " + Hex.toHexString(this.getHash()));
logger.info("Genesis-stateRoot: " + Hex.toHexString(this.getStateRoot()));
logger.info("Genesis-hash: {}", Hex.toHexString(this.getHash()));
logger.info("Genesis-stateRoot: {}", Hex.toHexString(this.getStateRoot()));
}
public static Block getInstance() {

View File

@ -7,18 +7,16 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.ethereum.db.ByteArrayWrapper;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.RLP;
import org.ethereum.util.Utils;
import org.spongycastle.util.encoders.Hex;
import org.ethereum.util.LRUMap;
public class HashUtil {
private static final int MAX_ENTRIES = 1000; // Should contain most commonly hashed values
private static LRUMap<ByteArrayWrapper, byte[]> sha3Cache = new LRUMap<>(0, MAX_ENTRIES);
public static final byte[] EMPTY_DATA_HASH = HashUtil.sha3(new byte[0]);
private static final int MAX_ENTRIES = 100; // Should contain most commonly hashed values
private static LRUMap<ByteArrayWrapper, byte[]> sha3Cache = new LRUMap<>(0, MAX_ENTRIES);
public static final byte[] EMPTY_DATA_HASH = Hex.decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
private static final MessageDigest sha256digest;
@ -35,14 +33,15 @@ public class HashUtil {
}
public static byte[] sha3(byte[] input) {
ByteArrayWrapper inputByteArray = new ByteArrayWrapper(input);
if(sha3Cache.keySet().contains(inputByteArray))
return sha3Cache.get(inputByteArray);
byte[] result = SHA3Helper.sha3(input);
sha3Cache.put(inputByteArray, result);
return result;
ByteArrayWrapper inputByteArray = new ByteArrayWrapper(input);
byte[] result = sha3Cache.get(inputByteArray);
if(result != null)
return result;
result = SHA3Helper.sha3(input);
sha3Cache.put(inputByteArray, result);
return result;
}
/**
* Calculates RIGTMOST160(SHA3(input)). This is used in address calculations.
*/
@ -62,7 +61,7 @@ public class HashUtil {
byte[] encSender = RLP.encodeElement(addr);
byte[] encNonce = RLP.encodeElement(nonce);
byte[] newAddress = HashUtil.sha3omit12(RLP.encodeList(encSender, encNonce));
byte[] newAddress = sha3omit12(RLP.encodeList(encSender, encNonce));
return newAddress;
}

View File

@ -52,7 +52,7 @@ public class ContractDetails {
}
} else{
storageTrie.update(key.getData(), RLP.encodeElement( value.getNoLeadZeroesData() ));
storageTrie.update(key.getData(), RLP.encodeElement(value.getNoLeadZeroesData()));
int index = storageKeys.indexOf(key);
if (index != -1) {
storageKeys.remove(index);
@ -94,7 +94,11 @@ public class ContractDetails {
public byte[] getStorageHash() {
getEncoded();
storageTrie = new Trie(null);
// calc the trie for root hash
for (int i = 0; i < storageKeys.size(); ++i){
storageTrie.update(storageKeys.get(i).getData(), RLP.encodeElement( storageValues.get(i).getNoLeadZeroesData() ));
}
return storageTrie.getRootHash();
}
@ -124,7 +128,7 @@ public class ContractDetails {
for (int i = 0; i < keys.size(); ++i) {
DataWord key = storageKeys.get(i);
DataWord value = storageValues.get(i);
storageTrie.update(key.getData(), RLP.encodeElement( value.getNoLeadZeroesData() ));
storageTrie.update(key.getData(), RLP.encodeElement(value.getNoLeadZeroesData()));
}
this.code = code.getRLPData();
@ -149,12 +153,6 @@ public class ContractDetails {
values[i] = RLP.encodeElement(value.getNoLeadZeroesData());
}
storageTrie = new Trie(null);
// calc the trie for root hash
for (int i = 0; i < storageKeys.size(); ++i){
storageTrie.update(storageKeys.get(i).getData(), values[i]);
}
byte[] rlpKeysList = RLP.encodeList(keys);
byte[] rlpValuesList = RLP.encodeList(values);
byte[] rlpCode = RLP.encodeElement(code);

View File

@ -7,7 +7,6 @@ import org.ethereum.core.Blockchain;
import org.ethereum.core.Genesis;
import org.ethereum.crypto.HashUtil;
import org.ethereum.json.JSONHelper;
import org.ethereum.manager.WorldManager;
import org.ethereum.trie.TrackTrie;
import org.ethereum.trie.Trie;
import org.ethereum.util.ByteUtil;
@ -181,9 +180,9 @@ public class Repository {
byte[] accountStateRLP = accountStateDB.get(addr);
if (accountStateRLP.length == 0) {
if (accountStateRLP.length == 0)
return null;
}
AccountState state = new AccountState(accountStateRLP);
return state;
}
@ -196,11 +195,10 @@ public class Repository {
logger.debug("Get contract details for: [ {} ]", Hex.toHexString(addr));
byte[] accountDetailsRLP = contractDetailsDB.get(addr);
if (accountDetailsRLP == null) {
return null;
}
if (accountDetailsRLP == null)
return null;
if (logger.isDebugEnabled())
logger.debug("Contract details RLP: [ {} ]", Hex.toHexString(accountDetailsRLP));
@ -214,10 +212,9 @@ public class Repository {
AccountState state = getAccountState(addr);
if (state == null){
if (state == null)
state = createAccount(addr);
}
BigInteger newBalance = state.addToBalance(value);
if (logger.isDebugEnabled())
@ -347,10 +344,6 @@ public class Repository {
contractDetailsDB.delete(addr);
}
public byte[] getRootHash() {
return this.worldState.getRootHash();
}
public List<ByteArrayWrapper> dumpKeys(){
return stateDB.dumpKeys();
}
@ -413,7 +406,7 @@ public class Repository {
bw.write("\n");
}
String rootHash = Hex.toHexString(WorldManager.getInstance().getRepository().getRootHash());
String rootHash = Hex.toHexString(this.getWorldState().getRootHash());
bw.write(
String.format(" => Global State Root: [ %s ]", rootHash)
);

View File

@ -3,6 +3,7 @@ package org.ethereum.trie;
import static java.util.Arrays.copyOfRange;
import static org.spongycastle.util.Arrays.concatenate;
import static org.ethereum.util.CompactEncoder.*;
import static org.ethereum.util.ByteUtil.matchingNibbleLength;
import java.util.Arrays;
@ -399,15 +400,6 @@ public class Trie implements TrieFacade {
* 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;
}
// Created an array of empty elements of requred length
private Object[] emptyStringSlice(int l) {
Object[] slice = new Object[l];
@ -418,7 +410,6 @@ public class Trie implements TrieFacade {
}
public byte[] getRootHash() {
Object root = this.getRoot();
if (root == null
|| (root instanceof byte[] && ((byte[]) root).length == 0)
|| (root instanceof String && "".equals((String) root))) {

View File

@ -60,6 +60,24 @@ public class ByteUtil {
return data;
}
/**
* Returns the amount of nibbles that match each other from 0 ...
* amount will never be larger than smallest input
*
* @param a - first input
* @param b second input
* @return number of bytes that match
*/
public static int matchingNibbleLength(byte[] a, byte[] b) {
int i = 0;
int length = a.length < b.length ? a.length : b.length;
while (i < length) {
if (a[i] != b[i])
break;
i++;
}
return i;
}
public static byte[] longToBytes(long l) {
return ByteBuffer.allocate(8).putLong(l).array();

View File

@ -1,13 +1,14 @@
package org.ethereum.util;
import java.io.ByteArrayOutputStream;
import static java.util.Arrays.copyOfRange;
import static java.util.Arrays.copyOf;
import static java.util.Arrays.copyOfRange;
import static org.ethereum.util.ByteUtil.appendByte;
import static org.spongycastle.util.Arrays.concatenate;
import static org.spongycastle.util.encoders.Hex.toHexString;
import static org.spongycastle.util.encoders.Hex.encode;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
/**
* Compact encoding of hex sequence with optional terminator
@ -85,11 +86,11 @@ public class CompactEncoder {
*/
public static byte[] unpackToNibbles(byte[] str) {
byte[] base = binToNibbles(str);
base = copyOf(base, base.length-1);
base = copyOf(base, base.length - 1);
if (base[0] >= 2) {
base = appendByte(base, TERMINATOR);
}
if (base[0]%2 == 1) {
if (base[0] % 2 == 1) {
base = copyOfRange(base, 1, base.length);
} else {
base = copyOfRange(base, 2, base.length);
@ -101,15 +102,14 @@ public class CompactEncoder {
* Transforms a binary array to hexadecimal format + terminator
*
* @return array with each individual nibble adding a terminator at the end
*/
*/
public static byte[] binToNibbles(byte[] str) {
byte[] hexSlice = new byte[0];
String hexEncoded = toHexString(str);
for (char value : hexEncoded.toCharArray()) {
hexSlice = appendByte(hexSlice, (byte) hexBase.indexOf(value));
byte[] hexEncoded = encode(str);
ByteBuffer slice = ByteBuffer.allocate(hexEncoded.length + 1);
for (byte b : hexEncoded) {
slice.put((byte)hexBase.indexOf(b));
}
hexSlice = appendByte(hexSlice, TERMINATOR);
return hexSlice;
slice.put(TERMINATOR);
return slice.array();
}
}

View File

@ -306,7 +306,7 @@ public class Program {
this.refundGas(refundGas, "remain gas from the internal call");
if (logger.isInfoEnabled()){
logger.info("The remain gas refunded, account: [ {} ], gas: [ {} ] ",
logger.info("The remaining gas is refunded, account: [ {} ], gas: [ {} ] ",
Hex.toHexString(this.getOwnerAddress().getLast20Bytes()),
refundGas);
}
@ -317,7 +317,7 @@ public class Program {
* That method implement internal calls
* and code invocations
*
* @param gas - gas to pay for the call, remain gas will be refunded to the caller
* @param gas - gas to pay for the call, remaining gas will be refunded to the caller
* @param toAddressDW - address to call
* @param endowmentValue - the value that can be transfer along with the code execution
* @param inDataOffs - start of memory to be input data to the call
@ -431,13 +431,13 @@ public class Program {
BigInteger refundGas = gas.value().subtract(BigInteger.valueOf(result.getGasUsed()));
if (refundGas.compareTo(BigInteger.ZERO) == 1) {
this.refundGas(refundGas.intValue(), "remain gas from the internal call");
logger.info("The remain gas refunded, account: [ {} ], gas: [ {} ] ",
refundGas.toString(), refundGas.toString());
this.refundGas(refundGas.intValue(), "remaining gas from the internal call");
logger.info("The remaining gas refunded, account: [ {} ], gas: [ {} ] ",
Hex.toHexString(senderAddress), refundGas.toString());
}
} else {
this.refundGas(gas.intValue(), "remain gas from the internal call");
this.refundGas(gas.intValue(), "remaining gas from the internal call");
}
}

View File

@ -75,7 +75,7 @@ public class ProgramInvokeFactory {
long gaslimit = block.getGasLimit();
if (logger.isInfoEnabled()) {
logger.info("Program invocation: \n" +
logger.info("Top level call: \n" +
"address={}\n" +
"origin={}\n" +
"caller={}\n" +
@ -144,7 +144,7 @@ public class ProgramInvokeFactory {
if (logger.isInfoEnabled()) {
logger.info("Program invocation: \n" +
logger.info("Internal call: \n" +
"address={}\n" +
"origin={}\n" +
"caller={}\n" +
@ -152,27 +152,15 @@ public class ProgramInvokeFactory {
"gasPrice={}\n" +
"gas={}\n" +
"callValue={}\n" +
"data={}\n" +
"lastHash={}\n" +
"coinbase={}\n" +
"timestamp={}\n" +
"blockNumber={}\n" +
"difficulty={}\n" +
"gaslimit={}\n",
Hex.toHexString(address.getData()),
Hex.toHexString(origin.getData()),
Hex.toHexString(caller.getData()),
new BigInteger(balance.getData()).longValue(),
new BigInteger(gasPrice.getData()).longValue(),
new BigInteger(gas.getData()).longValue(),
Hex.toHexString(callValue.getData()),
data == null ? "null": Hex.toHexString(data),
Hex.toHexString(lastHash.getData()),
Hex.toHexString(coinbase.getData()),
Hex.toHexString(timestamp.getData()),
new BigInteger(number.getData()).longValue(),
Hex.toHexString(difficulty.getData()),
new BigInteger(gasLimit.getData()).longValue());
"data={}\n",
Hex.toHexString(address.getLast20Bytes()),
Hex.toHexString(origin.getLast20Bytes()),
Hex.toHexString(caller.getLast20Bytes()),
balance.longValue(),
gasPrice.longValue(),
gas.longValue(),
callValue.longValue(),
data == null ? "null": Hex.toHexString(data));
}
return new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue,

View File

@ -55,6 +55,7 @@ public class VM {
private Logger logger = LoggerFactory.getLogger("VM");
private static BigInteger _32_ = BigInteger.valueOf(32);
private static String logString = "[ {} ]\t Op: [ {} ]\t Gas: [ {} ]\t Deep: [ {} ] Hint: [ {} ]";
public void step(Program program) {
@ -65,10 +66,14 @@ public class VM {
int oldMemSize = program.getMemSize();
String hint = "";
long gasBefore = program.getGas().longValue();
int stepBefore = program.getPC();
switch (OpCode.code(op)) {
case SHA3:
program.spendGas(GasCost.SHA3, OpCode.code(op).name());
break;
program.spendGas(GasCost.SHA3, OpCode.code(op).name());
break;
case SLOAD:
program.spendGas(GasCost.SLOAD, OpCode.code(op).name());
break;
@ -89,8 +94,7 @@ public class VM {
program.spendGas(GasCost.STEP, OpCode.code(op).name());
break;
}
String hint = "";
switch (OpCode.code(op)) {
/**
* Stop and Arithmetic Operations
@ -271,7 +275,7 @@ public class VM {
DataWord word2 = program.stackPop();
if (logger.isInfoEnabled())
hint = word1.longValue() + " = " + word2.longValue();
hint = word1.longValue() + " == " + word2.longValue();
if (word1.xor(word2).isZero()) {
word1.and(DataWord.ZERO);
@ -696,9 +700,10 @@ public class VM {
DataWord inSize = program.stackPop();
if (logger.isInfoEnabled())
logger.info("[ {} ] Op: [ {} ] Gas: [ {} ] Deep: [ {} ] Hint: [ {} ]" ,program.getPC(),
OpCode.code(op).name(), program.getGas().longValue(),
program.invokeData.getCallDeep(), hint);
logger.info(logString, program.getPC(), OpCode.code(op)
.name(), program.getGas().longValue(),
program.invokeData.getCallDeep(), hint);
program.createContract(value, inOffset, inSize);
program.step();
@ -715,11 +720,11 @@ public class VM {
DataWord outDataSize = program.stackPop();
if (logger.isInfoEnabled())
logger.info("[ {} ] Op: [ {} ] Gas: [ {} ] Deep: [ {} ] Hint: [ {} ]" ,program.getPC(),
OpCode.code(op).name(), program.getGas().longValue(),
program.invokeData.getCallDeep(), hint);
logger.info(logString, program.getPC(), OpCode.code(op)
.name(), program.getGas().longValue(),
program.invokeData.getCallDeep(), hint);
program.callToAddress(gas, toAddress, value, inDataOffs, inDataSize,outDataOffs, outDataSize);
program.callToAddress(gas, toAddress, value, inDataOffs, inDataSize, outDataOffs, outDataSize);
program.step();
} break;
@ -744,14 +749,12 @@ public class VM {
default:{
}
}
if (logger.isInfoEnabled())
if (!OpCode.code(op).equals(CALL) && !OpCode.code(op).equals(CREATE))
logger.info("[ {} ] Op: [ {} ] Gas: [ {} ] Deep: [ {} ] Hint: [ {} ]" ,program.getPC(),
OpCode.code(op).name(), program.getGas().longValue(),
program.invokeData.getCallDeep(), hint);
if (logger.isInfoEnabled())
if (!OpCode.code(op).equals(CALL) && !OpCode.code(op).equals(CREATE))
logger.info(logString, stepBefore, OpCode.code(op).name(),
gasBefore, program.invokeData.getCallDeep(), hint);
// memory gas calc
int newMemSize = program.getMemSize();
int memoryUsage = (newMemSize - oldMemSize) /32;

View File

@ -84,7 +84,7 @@ samples.dir = samples
# the existing database will be
# destroyed and all the data will be
# downloaded from peers again
database.reset = false
database.reset = true
# place to save physical storage files
database.dir = database
@ -92,7 +92,7 @@ database.dir = database
# this string is computed
# to be eventually the address
# that get the miner reward
coinbase.secret = "monkey"
coinbase.secret = monkey
# for testing purposes
# all the state will be dumped

View File

@ -62,6 +62,66 @@ public class ByteUtilTest {
assertArrayEquals(expected, ByteUtil.stripLeadingZeroes(test1));
assertArrayEquals(expected, ByteUtil.stripLeadingZeroes(test2));
}
@Test
public void testMatchingNibbleLength1() {
// a larger than b
byte[] a = new byte[] { 0x00, 0x01 };
byte[] b = new byte[] { 0x00 };
int result = ByteUtil.matchingNibbleLength(a, b);
assertEquals(1, result);
}
@Test
public void testMatchingNibbleLength2() {
// b larger than a
byte[] a = new byte[] { 0x00 };
byte[] b = new byte[] { 0x00, 0x01 };
int result = ByteUtil.matchingNibbleLength(a, b);
assertEquals(1, result);
}
@Test
public void testMatchingNibbleLength3() {
// a and b the same length equal
byte[] a = new byte[] { 0x00 };
byte[] b = new byte[] { 0x00 };
int result = ByteUtil.matchingNibbleLength(a, b);
assertEquals(1, result);
}
@Test
public void testMatchingNibbleLength4() {
// a and b the same length not equal
byte[] a = new byte[] { 0x01 };
byte[] b = new byte[] { 0x00 };
int result = ByteUtil.matchingNibbleLength(a, b);
assertEquals(0, result);
}
@Test(expected=NullPointerException.class)
public void testMatchingNibbleLength5() {
// a == null
byte[] a = null;
byte[] b = new byte[] { 0x00 };
ByteUtil.matchingNibbleLength(a, b);
}
@Test(expected=NullPointerException.class)
public void testMatchingNibbleLength6() {
// b == null
byte[] a = new byte[] { 0x00 };
byte[] b = null;
ByteUtil.matchingNibbleLength(a, b);
}
@Test
public void testMatchingNibbleLength7() {
// a or b is empty
byte[] a = new byte[0];
byte[] b = new byte[] { 0x00 };
int result = ByteUtil.matchingNibbleLength(a, b);
assertEquals(0, result);
}
/**
* This test shows the difference between iterating over,

View File

@ -73,4 +73,4 @@ database.reset = true
# this string is computed
# to be eventually the address
# that get the miner reward
coinbase.secret = "monkey"
coinbase.secret = monkey