diff --git a/build.gradle b/build.gradle index 9cd22a32ad..c0514945f8 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,8 @@ subprojects { group = 'org.ethereum' - version = config.versionNumber + ("master" == gitCurBranch() ? "-RELEASE" : "-SNAPSHOT") + version = config.versionNumber + ("master" == gitCurBranch() ? "-RELEASE" : + "research/casper" == gitCurBranch() ? "-casper-SNAPSHOT" : "-SNAPSHOT") println("Building version: " + version + " (from branch " + gitCurBranch() + ")") diff --git a/circle.yml b/circle.yml index 2d46999d4d..8e0ad34370 100644 --- a/circle.yml +++ b/circle.yml @@ -27,7 +27,7 @@ dependencies: - cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash/. ~/.ethash # Cache gradle artifacts - (cd ~/ethereumj && ./gradlew compileJava) - - (cd ~ && git clone --depth 1 -b develop https://github.com/ether-camp/ethereum-harmony.git) + - (cd ~ && git clone --depth 1 -b research/casper https://github.com/ether-camp/ethereum-harmony.git) - (cd ~/ethereum-harmony && ./gradlew dependencyManagement) test: diff --git a/ethereumj-core/build.gradle b/ethereumj-core/build.gradle index f04354bc42..cd50506400 100644 --- a/ethereumj-core/build.gradle +++ b/ethereumj-core/build.gradle @@ -56,6 +56,12 @@ task runTest (type: JavaExec) { jvmArgs = applicationDefaultJvmArgs + '-Dethereumj.conf.res=test.conf' } +task runCasper (type: JavaExec) { + main = 'org.ethereum.casper.Start' + classpath = sourceSets.main.runtimeClasspath + jvmArgs = applicationDefaultJvmArgs + '-Dethereumj.conf.res=casper.conf' +} + /** * This is TCK test command line option, * to run the test: @@ -214,7 +220,8 @@ artifactoryPublish.onlyIf { project.version.endsWith('-SNAPSHOT') && (branchName.startsWith('master') || branchName.startsWith('develop') || - branchName.startsWith('stage')) + branchName.startsWith('stage') || + branchName.startsWith('research/casper')) } bintray { diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/Start.java b/ethereumj-core/src/main/java/org/ethereum/casper/Start.java new file mode 100644 index 0000000000..445658391a --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/Start.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper; + +import org.apache.commons.lang3.StringUtils; +import org.ethereum.casper.config.CasperConfig; +import org.ethereum.cli.CLIInterface; +import org.ethereum.config.SystemProperties; +import org.ethereum.facade.Ethereum; +import org.ethereum.facade.EthereumFactory; +import org.ethereum.mine.Ethash; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URISyntaxException; + +public class Start { + + private static final Logger logger = LoggerFactory.getLogger("general"); + + public static void main(String args[]) throws IOException, URISyntaxException { + logger.info("EthereumJ Casper edition"); + CLIInterface.call(args); + + final SystemProperties config = SystemProperties.getDefault(); + final boolean actionBlocksLoader = !config.blocksLoader().equals(""); + final boolean actionGenerateDag = !StringUtils.isEmpty(System.getProperty("ethash.blockNumber")); + + if (actionBlocksLoader || actionGenerateDag) { + config.setSyncEnabled(false); + config.setDiscoveryEnabled(false); + } + + if (actionGenerateDag) { + new Ethash(config, Long.parseLong(System.getProperty("ethash.blockNumber"))).getFullDataset(); + // DAG file has been created, lets exit + System.exit(0); + } else { + Ethereum ethereum = EthereumFactory.createEthereum(new Class[] {CasperConfig.class}); + + if (actionBlocksLoader) { + ethereum.getBlockLoader().loadBlocks(); + } + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/config/CasperBeanConfig.java b/ethereumj-core/src/main/java/org/ethereum/casper/config/CasperBeanConfig.java new file mode 100644 index 0000000000..6489fe8a83 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/config/CasperBeanConfig.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.config; + +import org.ethereum.casper.core.CasperPendingStateImpl; +import org.ethereum.casper.manager.CasperWorldManager; +import org.ethereum.casper.mine.CasperBlockMiner; +import org.ethereum.config.CommonConfig; +import org.ethereum.config.SystemProperties; +import org.ethereum.core.Blockchain; +import org.ethereum.core.PendingState; +import org.ethereum.casper.core.CasperBlockchain; +import org.ethereum.listener.CompositeEthereumListener; +import org.ethereum.manager.WorldManager; +import org.ethereum.mine.BlockMiner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement +public class CasperBeanConfig extends CommonConfig { + + @Override + @Bean + public Blockchain blockchain() { + return new CasperBlockchain(systemProperties()); + } + + @Bean + @Override + public WorldManager worldManager() { + return new CasperWorldManager(systemProperties(), repository(), blockchain()); + } + + @Bean + @Override + public PendingState pendingState() { + return new CasperPendingStateImpl(ethereumListener); + } + + @Bean + @Override + public BlockMiner blockMiner() { + return new CasperBlockMiner(systemProperties(), (CompositeEthereumListener) ethereumListener, + blockchain(), pendingState()); + } + + @Bean + @Override + public SystemProperties systemProperties() { + return CasperProperties.getDefault(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/config/CasperConfig.java b/ethereumj-core/src/main/java/org/ethereum/casper/config/CasperConfig.java new file mode 100644 index 0000000000..7d5b380a63 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/config/CasperConfig.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.config; + +import org.ethereum.config.CommonConfig; +import org.ethereum.config.DefaultConfig; +import org.ethereum.config.NoAutoscan; +import org.ethereum.config.SystemProperties; +import org.ethereum.datasource.Source; +import org.ethereum.db.BlockStore; +import org.ethereum.db.IndexedBlockStore; +import org.ethereum.db.PruneManager; +import org.ethereum.db.TransactionStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.Import; + +@Configuration +@ComponentScan( + basePackages = "org.ethereum", + excludeFilters = {@ComponentScan.Filter(NoAutoscan.class), + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {CommonConfig.class, DefaultConfig.class})}, + includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {CasperBeanConfig.class})} +) +@Import(CasperBeanConfig.class) +public class CasperConfig { + private static Logger logger = LoggerFactory.getLogger("general"); + + @Autowired + ApplicationContext appCtx; + + @Autowired + CasperBeanConfig beanConfig; + + @Autowired + SystemProperties config; + + public CasperConfig() { + Thread.setDefaultUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception", e)); + } + + @Bean + public BlockStore blockStore(){ + beanConfig.fastSyncCleanUp(); + IndexedBlockStore indexedBlockStore = new IndexedBlockStore(); + Source block = beanConfig.cachedDbSource("block"); + Source index = beanConfig.cachedDbSource("index"); + indexedBlockStore.init(index, block); + + return indexedBlockStore; + } + + @Bean(name = "finalizedBlocks") + public Source finalizedBlockStore() { + return beanConfig.cachedDbSource("finalized"); + } + + @Bean + public TransactionStore transactionStore() { + beanConfig.fastSyncCleanUp(); + return new TransactionStore(beanConfig.cachedDbSource("transactions")); + } + + @Bean + public PruneManager pruneManager() { + if (config.databasePruneDepth() >= 0) { + return new PruneManager((IndexedBlockStore) blockStore(), beanConfig.stateSource().getJournalSource(), + beanConfig.stateSource().getNoJournalSource(), config.databasePruneDepth()); + } else { + return new PruneManager(null, null, null, -1); // dummy + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/config/CasperProperties.java b/ethereumj-core/src/main/java/org/ethereum/casper/config/CasperProperties.java new file mode 100644 index 0000000000..4137a174f2 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/config/CasperProperties.java @@ -0,0 +1,85 @@ +package org.ethereum.casper.config; + +import org.ethereum.config.SystemProperties; +import org.ethereum.util.ByteUtil; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + +import static org.ethereum.casper.config.net.CasperTestConfig.EPOCH_LENGTH; + +public class CasperProperties extends SystemProperties { + + private static CasperProperties CONFIG; + + private byte[] casperAddress = null; + + public static SystemProperties getDefault() { + if (CONFIG == null) { + CONFIG = new CasperProperties(); + } + return CONFIG; + } + + public byte[] getCasperAddress() { + return casperAddress; + } + + public void setCasperAddress(byte[] casperAddress) { + this.casperAddress = casperAddress; + } + + public int getCasperEpochLength() { + return EPOCH_LENGTH; + } + + public byte[] getCasperValidatorPrivateKey() { + String key = config.getString("casper.validator.privateKey"); + if (key == null) return null; + return ByteUtil.hexStringToBytes(key); + } + + public long getCasperValidatorDeposit() { + return config.getLong("casper.validator.deposit"); + } + + public Boolean getCasperValidatorEnabled() { + return config.getBoolean("casper.validator.enabled"); + } + + public String getCasperAbi() { + final String abiLocation = config.getString("casper.contractAbi"); + return readFile(abiLocation); + } + + public String getCasperBin() { + final String binLocation = config.getString("casper.contractBin"); + return readFile(binLocation); + } + + private static String readFile(final String location) { + try { + InputStream is = SystemProperties.class.getResourceAsStream(location); + + if (is != null) { + return readStream(is); + } else { + logger.error("File not found `{}`", location); + throw new RuntimeException(String.format("File not found `%s`", location)); + } + } catch (Exception ex) { + String errorMsg = String.format("Error while reading file from %s", location); + logger.error(errorMsg, ex); + throw new RuntimeException(errorMsg, ex); + } + } + + private static String readStream(InputStream input) throws IOException { + try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input))) { + return buffer.lines().collect(Collectors.joining("\n")); + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/config/net/CasperTestConfig.java b/ethereumj-core/src/main/java/org/ethereum/casper/config/net/CasperTestConfig.java new file mode 100644 index 0000000000..b682c2fd65 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/config/net/CasperTestConfig.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.config.net; + +import org.ethereum.casper.validator.NullSenderTxValidator; +import org.ethereum.config.BlockchainConfig; +import org.ethereum.config.Constants; +import org.ethereum.config.ConstantsAdapter; +import org.ethereum.config.blockchain.ByzantiumConfig; +import org.ethereum.config.blockchain.Eip150HFConfig; +import org.ethereum.core.Transaction; +import org.ethereum.crypto.ECKey; +import org.ethereum.vm.GasCost; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static org.ethereum.config.blockchain.HomesteadConfig.SECP256K1N_HALF; + +public class CasperTestConfig extends ByzantiumConfig { + + public static final int EPOCH_LENGTH = 50; + public static final int WITHDRAWAL_DELAY = 5; + public static final int DYNASTY_LOGOUT_DELAY = 5; + public static final double BASE_INTEREST_FACTOR = 0.1; + public static final double BASE_PENALTY_FACTOR = 0.0001; + public static final int MIN_DEPOSIT_ETH = 1500; + public final static ECKey NULL_SIGN_SENDER = ECKey.fromPrivate(Hex.decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + + private final Constants constants; + + private List nullSenderTxValidators = new ArrayList<>(); + + public CasperTestConfig(BlockchainConfig parent) { + + super(parent); + constants = new ConstantsAdapter(super.getConstants()) { + private final BigInteger BLOCK_REWARD = new BigInteger("1000000000000000000"); // 1 ETH + + private final BigInteger MINIMUM_DIFFICULTY = BigInteger.valueOf(8192); + + @Override + public BigInteger getBLOCK_REWARD() { + return BLOCK_REWARD; + } + + @Override + public BigInteger getMINIMUM_DIFFICULTY() { + return MINIMUM_DIFFICULTY; + } + }; + } + + static class CasperGasCost extends Eip150HFConfig.GasCostEip150HF { + public int getEXP_BYTE_GAS() { return 10; } // before spurious dragon hard fork + } + + private static final GasCost NEW_GAS_COST = new CasperGasCost(); + + + @Override + public GasCost getGasCost() { + return NEW_GAS_COST; + } + + @Override + public boolean eip161() { + return false; + } + + @Override + public Constants getConstants() { + return constants; + } + + @Override + public Integer getChainId() { + return null; + } + + @Override + public boolean acceptTransactionSignature(Transaction tx) { + if (tx.getSignature() != null) { + // Homestead-like check + if (!tx.getSignature().validateComponents() || + tx.getSignature().s.compareTo(SECP256K1N_HALF) > 0) return false; + } else { + boolean success = false; + for (NullSenderTxValidator validator : nullSenderTxValidators) { + if (validator.validate(tx)) { + success = true; + break; + } + } + if(!success) return false; + } + return tx.getChainId() == null || Objects.equals(getChainId(), tx.getChainId()); + } + + public void addNullSenderTxValidators(NullSenderTxValidator validator) { + nullSenderTxValidators.add(validator); + } +} \ No newline at end of file diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/config/net/CasperTestNetConfig.java b/ethereumj-core/src/main/java/org/ethereum/casper/config/net/CasperTestNetConfig.java new file mode 100644 index 0000000000..e89a745ceb --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/config/net/CasperTestNetConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.config.net; + +import org.ethereum.config.blockchain.FrontierConfig; +import org.ethereum.config.net.BaseNetConfig; + +public class CasperTestNetConfig extends BaseNetConfig { + + public static final CasperTestNetConfig INSTANCE = new CasperTestNetConfig(); + + public CasperTestNetConfig() { + add(0, new CasperTestConfig(new FrontierConfig())); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperBlockchain.java b/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperBlockchain.java new file mode 100644 index 0000000000..e17ddae3b3 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperBlockchain.java @@ -0,0 +1,489 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.core; + +import org.ethereum.casper.config.CasperProperties; +import org.ethereum.config.BlockchainConfig; +import org.ethereum.config.SystemProperties; +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.BlockchainImpl; +import org.ethereum.core.ImportResult; +import org.ethereum.core.Repository; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionExecutionSummary; +import org.ethereum.core.TransactionExecutor; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.datasource.Source; +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.util.ByteUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.math.BigInteger.ONE; +import static org.ethereum.casper.config.net.CasperTestConfig.NULL_SIGN_SENDER; +import static org.ethereum.core.ImportResult.EXIST; +import static org.ethereum.core.ImportResult.IMPORTED_BEST; +import static org.ethereum.core.ImportResult.IMPORTED_NOT_BEST; +import static org.ethereum.core.ImportResult.INVALID_BLOCK; +import static org.ethereum.core.ImportResult.NO_PARENT; + +public class CasperBlockchain extends BlockchainImpl { + + private static final Logger logger = LoggerFactory.getLogger("blockchain"); + private static final Logger stateLogger = LoggerFactory.getLogger("state"); + + private static final BigInteger PRETTY_BIG = BigInteger.valueOf(10).pow(40); + private static final BigInteger NON_REVERT_MIN_DEPOSIT = BigInteger.valueOf(10).pow(18); + + private static final long EPOCH_SWITCH_GASLIMIT = 3_141_592; // Sames as block gas limit + + @Autowired + private CasperFacade casper; + + @Autowired + @Qualifier("finalizedBlocks") + private Source finalizedBlocks; + + public CasperBlockchain() { + throw new RuntimeException("Empty constructor not available"); + } + + @Autowired + public CasperBlockchain(SystemProperties config) { + super(config); + } + + private boolean switchRevertsFinalizedBlock(final Block block) { + if (block == null) return false; + Block oldHead = getBestBlock(); + Block newHead = block; + + // Assuming old head > new head and there are no finalized blocks between + while (oldHead.getNumber() > newHead.getNumber()) { + byte[] finalized = finalizedBlocks.get(oldHead.getHash()); + if (finalized != null) { + logger.warn("Attempt to revert failed: checkpoint {} is finalized", oldHead.getShortDescr()); + return true; + } + oldHead = getBlockByHash(oldHead.getParentHash()); + } + + // Assuming new head > old head and there could be connected in one chain + while (newHead.getNumber() > oldHead.getNumber()) { + newHead = getBlockByHash(newHead.getParentHash()); + if (newHead == null) { + logger.warn("Proposed head block {} is not connected with canonical chain", block.getShortDescr()); + return false; + } + } + + // As we are currently on one height we could be on one chain before any finalized block + while(!Arrays.areEqual(oldHead.getHash(), newHead.getHash())) { + byte[] finalized = finalizedBlocks.get(oldHead.getHash()); + if (finalized != null) { + logger.warn("Attempt to revert failed: checkpoint {} is finalized", oldHead.getShortDescr()); + return true; + } + oldHead = getBlockByHash(oldHead.getParentHash()); + newHead = getBlockByHash(newHead.getParentHash()); + if (newHead == null) { + logger.warn("Proposed head block {} is not connected with canonical chain", block.getShortDescr()); + return false; + } + } + return false; + } + + private BigInteger getScore(final Block block) { + Object[] res = casper.constCall(block, "last_justified_epoch"); + return ((BigInteger) res[0]).multiply(PRETTY_BIG).add(getPoWDifficulty(block)); + } + + @Override + public synchronized ImportResult tryToConnect(final Block block) { + + if (logger.isDebugEnabled()) + logger.debug("Try connect block hash: {}, number: {}", + Hex.toHexString(block.getHash()).substring(0, 6), + block.getNumber()); + + if (blockExists(block)) return EXIST; // retry of well known block + + // TODO: Remove try/catch, debug only + try { + return casperConnect(block); + } catch (Exception ex) { + throw ex; + } + } + + private synchronized ImportResult casperConnect(final Block block) { + final ImportResult ret; + final BlockSummary summary; + + if (blockStore.isBlockExist(block.getParentHash())) { + recordBlock(block); + Repository repo; + + Block parentBlock = getBlockByHash(block.getParentHash()); + Block bestBlock = getBestBlock(); + repo = getRepository().getSnapshotTo(parentBlock.getStateRoot()); + + // We already handle not matched block stateRoot and + // real root due to Casper background txs + State savedState = pushState(block.getParentHash()); + this.fork = true; + try { + summary = add(repo, block); + if (summary == null) { + return INVALID_BLOCK; + } + } catch (Throwable th) { + logger.error("Unexpected error: ", th); + return INVALID_BLOCK; + } finally { + this.fork = false; + } + + if (getScore(bestBlock).compareTo(getScore(block)) >= 0 || + switchRevertsFinalizedBlock(block)) { + logger.info("Skipping block {} which is not a descendant of current head checkpoint", block.getNumber()); + // Stay on previous branch + popState(); + ret = IMPORTED_NOT_BEST; + } else { + + // Main branch become this branch + // cause we proved that total difficulty + // is greater + blockStore.reBranch(block); + + // The main repository rebranch + setRepository(repo); + + dropState(); + + finalizeCheckpoint(block); + + ret = IMPORTED_BEST; + } + + listener.onBlock(summary); + listener.trace(String.format("Block chain size: [ %d ]", this.getSize())); + + if (ret == IMPORTED_BEST) { + eventDispatchThread.invokeLater(() -> getPendingState().processBest(block, summary.getReceipts())); + } + return ret; + } else { + return NO_PARENT; + } + } + + /** + * Finalizes Casper epoch checkpoint if needed + */ + private void finalizeCheckpoint(final Block block) { + Object[] res = casper.constCall(block, "last_finalized_epoch"); + long finalizedEpoch = ((BigInteger) res[0]).longValue(); + Object[] res2 = casper.constCall(block, "current_epoch"); + long currentEpoch = ((BigInteger) res2[0]).longValue(); + if (finalizedEpoch == currentEpoch - 1) { + // Actually one hash per epoch, just the getter for array + Object[] res3 = casper.constCall(block, "checkpoint_hashes", finalizedEpoch); + byte[] checkpointHash = (byte[]) res3[0]; + if (!Arrays.areEqual(checkpointHash, new byte[32])) { // new byte[32] == 00-filled + Block histBlock = getBlockByHash(checkpointHash); + Object[] res4 = casper.constCall(histBlock, "total_curdyn_deposits_scaled"); + BigInteger curDeposits = (BigInteger) res4[0]; + Object[] res5 = casper.constCall(histBlock, "total_prevdyn_deposits_scaled"); + BigInteger prevDeposits = (BigInteger) res5[0]; + if (curDeposits.compareTo(NON_REVERT_MIN_DEPOSIT) > 0 && + prevDeposits.compareTo(NON_REVERT_MIN_DEPOSIT) > 0) { + finalizedBlocks.put(checkpointHash, new byte[] {0x01}); // True + logger.info("Finalized checkpoint {} {}", finalizedEpoch, Hex.toHexString(checkpointHash)); + } else { + logger.info("Trivially finalized checkpoint {}", finalizedEpoch); + } + } + } + } + + @Override + protected boolean checkBlockSummary(BlockSummary summary, Repository track) { + boolean res = super.checkBlockSummary(summary, track); + if (!res) { // Already bad block + return false; + } + + // Casper-specific checks + + // Check for failed casper txs + TransactionReceipt failedCasperVote = null; + for (int i = 0; i < summary.getReceipts().size(); ++i) { + TransactionReceipt receipt = summary.getReceipts().get(i); + if(!receipt.isSuccessful() && casper.isVote(receipt.getTransaction())) { + failedCasperVote = receipt; + break; + } + } + if (failedCasperVote != null) { + logger.warn("Block contains failed casper vote (receipt: {}, tx: {})", + failedCasperVote, failedCasperVote.getTransaction()); + return false; + } + + return true; + } + + @Override + protected BlockSummary applyBlock(Repository track, Block block) { + + logger.debug("applyBlock: block: [{}] tx.list: [{}]", block.getNumber(), block.getTransactionsList().size()); + + BlockchainConfig blockchainConfig = config.getBlockchainConfig().getConfigForBlock(block.getNumber()); + blockchainConfig.hardForkTransfers(block, track); + initCasper(track, block); + + long saveTime = System.nanoTime(); + int i = 1; + long totalGasUsed = 0; + List receipts = new ArrayList<>(); + List summaries = new ArrayList<>(); + + List txs = new ArrayList<>(block.getTransactionsList()); + + // Initialize the next epoch in the Casper contract + int epochLength = ((CasperProperties) config).getCasperEpochLength(); + if(block.getNumber() % epochLength == 0 && block.getNumber() != 0) { + long startingEpoch = block.getNumber() / epochLength; + byte[] data = casper.getContract().getByName("initialize_epoch").encode(startingEpoch); + Transaction tx = new Transaction( + ByteUtil.bigIntegerToBytes(track.getNonce(NULL_SIGN_SENDER.getAddress())), + new byte[0], + ByteUtil.longToBytesNoLeadZeroes(EPOCH_SWITCH_GASLIMIT), + casper.getAddress(), + new byte[0], + data + ); + tx.sign(NULL_SIGN_SENDER); + txs.add(0, tx); + } + + for (Transaction tx : txs) { + stateLogger.debug("apply block: [{}] tx: [{}] ", block.getNumber(), i); + + Repository txTrack = track.startTracking(); + TransactionExecutor executor = createTransactionExecutor(tx, block.getCoinbase(), txTrack, + block, totalGasUsed); + + executor.init(); + executor.execute(); + executor.go(); + TransactionExecutionSummary summary = executor.finalization(); + + totalGasUsed += executor.getGasUsed(); + + txTrack.commit(); + final TransactionReceipt receipt = executor.getReceipt(); + + if (blockchainConfig.eip658()) { + receipt.setTxStatus(receipt.isSuccessful()); + } else { + receipt.setPostTxState(track.getRoot()); + } + + stateLogger.info("block: [{}] executed tx: [{}] \n state: [{}]", block.getNumber(), i, + Hex.toHexString(track.getRoot())); + + stateLogger.info("[{}] ", receipt.toString()); + + if (stateLogger.isInfoEnabled()) + stateLogger.info("tx[{}].receipt: [{}] ", i, Hex.toHexString(receipt.getEncoded())); + + // TODO +// if (block.getNumber() >= config.traceStartBlock()) +// repository.dumpState(block, totalGasUsed, i++, tx.getHash()); + + receipts.add(receipt); + if (summary != null) { + summaries.add(summary); + } + } + + Map rewards = addReward(track, block, summaries); + + stateLogger.info("applied reward for block: [{}] \n state: [{}]", + block.getNumber(), + Hex.toHexString(track.getRoot())); + + + // TODO +// if (block.getNumber() >= config.traceStartBlock()) +// repository.dumpState(block, totalGasUsed, 0, null); + + long totalTime = System.nanoTime() - saveTime; + adminInfo.addBlockExecTime(totalTime); + logger.debug("block: num: [{}] hash: [{}], executed after: [{}]nano", block.getNumber(), block.getShortHash(), totalTime); + + return new BlockSummary(block, rewards, receipts, summaries); + } + + @Override + public TransactionExecutor createTransactionExecutor(Transaction transaction, byte[] minerCoinbase, Repository track, + Block currentBlock, long gasUsedInTheBlock) { + return new CasperTransactionExecutor(transaction, minerCoinbase, + track, blockStore, getProgramInvokeFactory(), currentBlock, listener, gasUsedInTheBlock) + .withCommonConfig(commonConfig); + } + + private void initCasper(Repository track, Block block) { + // All changes should be applied only just after genesis, before 1st block state changes + if (block.getNumber() != 1) + return; + + List txs = casper.getInitTxs(); + byte[] casperAddress = ((CasperProperties) config).getCasperAddress(); + byte[] coinbase = blockStore.getChainBlockByNumber(0).getCoinbase(); + + txs.forEach((tx) -> { + // We need money! + track.addBalance(NULL_SIGN_SENDER.getAddress(), BigInteger.valueOf(15).pow(18)); + + Repository txTrack = track.startTracking(); + TransactionExecutor executor = createTransactionExecutor(tx, coinbase, txTrack, block, 0); + + executor.init(); + executor.execute(); + executor.go(); + executor.finalization(); + + byte[] contractAddress = executor.getReceipt().getTransaction().getContractAddress(); + if (contractAddress != null) { + logger.info("Casper init: contract deployed at {}, tx: [{}]", Hex.toHexString(contractAddress), tx); + } + if (!executor.getReceipt().isSuccessful()) { + logger.error("Casper init failed on tx [{}], receipt [{}], breaking", tx, executor.getReceipt()); + throw new RuntimeException("Casper initialization transactions on 1st block failed"); + } + + txTrack.commit(); + BigInteger restBalance = track.getBalance(NULL_SIGN_SENDER.getAddress()); + track.addBalance(NULL_SIGN_SENDER.getAddress(), restBalance.negate()); + track.addBalance(casperAddress, track.getBalance(casperAddress).negate()); + track.addBalance(casperAddress, BigInteger.valueOf(10).pow(25)); + }); + } + + /** + * This mechanism enforces a homeostasis in terms of the time between blocks; + * a smaller period between the last two blocks results in an increase in the + * difficulty level and thus additional computation required, lengthening the + * likely next period. Conversely, if the period is too large, the difficulty, + * and expected time to the next block, is reduced. + */ + @Override + protected boolean isValid(Repository repo, Block block) { + + boolean isValid = true; + + if (!block.isGenesis()) { + isValid = isValid(block.getHeader()); + + // Sanity checks + String trieHash = Hex.toHexString(block.getTxTrieRoot()); + String trieListHash = Hex.toHexString(calcTxTrie(block.getTransactionsList())); + + + if (!trieHash.equals(trieListHash)) { + logger.warn("Block's given Trie Hash doesn't match: {} != {}", trieHash, trieListHash); + return false; + } + +// if (!validateUncles(block)) return false; + + List txs = block.getTransactionsList(); + if (!txs.isEmpty()) { +// Repository parentRepo = repository; +// if (!Arrays.equals(bestBlock.getHash(), block.getParentHash())) { +// parentRepo = repository.getSnapshotTo(getBlockByHash(block.getParentHash()).getStateRoot()); +// } + + Map curNonce = new HashMap<>(); + + boolean votesStarted = false; + for (Transaction tx : txs) { + byte[] txSender = tx.getSender(); + ByteArrayWrapper key = new ByteArrayWrapper(txSender); + BigInteger expectedNonce = curNonce.get(key); + if (expectedNonce == null) { + expectedNonce = repo.getNonce(txSender); + } + // We shouldn't track nonce for NULL_SENDER + if (!key.equals(new ByteArrayWrapper(Transaction.NULL_SENDER))) { + curNonce.put(key, expectedNonce.add(ONE)); + } else { + curNonce.put(key, expectedNonce); + } + BigInteger txNonce = new BigInteger(1, tx.getNonce()); + if (!expectedNonce.equals(txNonce)) { + logger.warn("Invalid transaction: Tx nonce {} != expected nonce {} (parent nonce: {}): {}", + txNonce, expectedNonce, repo.getNonce(txSender), tx); + return false; + } + + // Casper votes txs should come after regular txs + if (votesStarted && !casper.isVote(tx)) { + logger.warn("Invalid transaction: all transactions should be before casper votes: {}", tx); + return false; + } + + if (!votesStarted && casper.isVote(tx)) { + votesStarted = true; + } + } + } + } + + return isValid; + } + + private BigInteger getPoWDifficulty(final Block block) { + + return blockStore.getTotalDifficultyForHash(block.getHash()); + } + + public void setCasper(CasperFacade casper) { + this.casper = casper; + } + + public void setFinalizedBlocks(Source finalizedBlocks) { + this.finalizedBlocks = finalizedBlocks; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperFacade.java b/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperFacade.java new file mode 100644 index 0000000000..1c1d762945 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperFacade.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.core; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.ethereum.casper.config.CasperProperties; +import org.ethereum.casper.config.net.CasperTestConfig; +import org.ethereum.config.SystemProperties; +import org.ethereum.core.Block; +import org.ethereum.core.CallTransaction; +import org.ethereum.core.Repository; +import org.ethereum.core.Transaction; +import org.ethereum.crypto.ECKey; +import org.ethereum.facade.Ethereum; +import org.ethereum.util.ByteUtil; +import org.ethereum.util.FastByteComparisons; +import org.ethereum.casper.validator.NullSenderTxValidator; +import org.ethereum.vm.program.ProgramResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.ethereum.casper.config.net.CasperTestConfig.BASE_INTEREST_FACTOR; +import static org.ethereum.casper.config.net.CasperTestConfig.BASE_PENALTY_FACTOR; +import static org.ethereum.casper.config.net.CasperTestConfig.DYNASTY_LOGOUT_DELAY; +import static org.ethereum.casper.config.net.CasperTestConfig.MIN_DEPOSIT_ETH; +import static org.ethereum.casper.config.net.CasperTestConfig.NULL_SIGN_SENDER; +import static org.ethereum.casper.config.net.CasperTestConfig.WITHDRAWAL_DELAY; +import static org.ethereum.crypto.HashUtil.sha3; + +@Component +public class CasperFacade { + + private static final Logger logger = LoggerFactory.getLogger("casper"); + + private static final byte[] CASPER_VOTE_DATA_HEAD = Hex.decode("e9dc0614"); + private static final byte[] CASPER_EPOCH_DATA_HEAD = Hex.decode("5dcffc17"); + + private CasperProperties systemProperties; + + private Ethereum ethereum; + + private CallTransaction.Contract casper = null; + + private String contractAddress; // FIXME: why should we have casper addresses in two places?. It's already in SystemProperties + + private List initTxs; + + public CallTransaction.Contract getContract() { + init(); + return casper; + } + + private void init() { + if (casper != null) { + return; + } + + Pair> res = makeInitTxes(); + byte[] casperAddress = res.getKey(); + this.initTxs = res.getValue(); + systemProperties.setCasperAddress(casperAddress); + CasperTestConfig config = (CasperTestConfig) systemProperties.getBlockchainConfig().getConfigForBlock(0); + config.addNullSenderTxValidators(new NullSenderTxValidator(this::isVote)); + this.contractAddress = Hex.toHexString(casperAddress); + logger.info("Casper contract address set to [0x{}]", contractAddress); + String casperAbi = systemProperties.getCasperAbi(); + this.casper = new CallTransaction.Contract(casperAbi); + } + + public Object[] constCall(String func, Object... funcArgs) { + init(); + ProgramResult r = ethereum.callConstantFunction(contractAddress, + casper.getByName(func), funcArgs); + return casper.getByName(func).decodeResult(r.getHReturn()); + } + + + public Object[] constCall(Block block, String func, Object... funcArgs) { + init(); + Transaction tx = CallTransaction.createCallTransaction(0, 0, 100000000000000L, + contractAddress, 0, casper.getByName(func), funcArgs); + ProgramResult r = ethereum.callConstantFunction(block, contractAddress, + casper.getByName(func), funcArgs); + return casper.getByName(func).decodeResult(r.getHReturn()); + } + + /** + * All service txs shouldn't use gas + * Currently it's votes and epoch change + */ + public boolean isServiceTx(Transaction transaction) { + return isServiceTx(transaction, getAddress()); + } + + public boolean isVote(Transaction transaction) { + return isVote(transaction, getAddress()); + } + + public static boolean isServiceTx(Transaction transaction, byte[] casperAddress) { + return isVote(transaction, casperAddress) || + isEpochSwitch(transaction, casperAddress); + } + + public static boolean isVote(Transaction transaction, byte[] casperAddress) { + if (!Arrays.equals(transaction.getSender(), Transaction.NULL_SENDER)) + return false; + if (casperAddress == null) + return false; + if (!Arrays.equals(transaction.getReceiveAddress(), casperAddress)) + return false; + + return FastByteComparisons.compareTo(transaction.getData(), 0, CASPER_VOTE_DATA_HEAD.length, + CASPER_VOTE_DATA_HEAD, 0, CASPER_VOTE_DATA_HEAD.length) == 0; + } + + private static boolean isEpochSwitch(Transaction transaction, byte[] casperAddress) { + if (!Arrays.equals(transaction.getSender(), NULL_SIGN_SENDER.getAddress())) + return false; + if (casperAddress == null) + return false; + if (!Arrays.equals(transaction.getReceiveAddress(), casperAddress)) + return false; + + return FastByteComparisons.compareTo(transaction.getData(), 0, CASPER_EPOCH_DATA_HEAD.length, + CASPER_EPOCH_DATA_HEAD, 0, CASPER_EPOCH_DATA_HEAD.length) == 0; + } + + public byte[] getAddress() { + init(); + return systemProperties.getCasperAddress(); + } + + public List getInitTxs() { + init(); + return initTxs; + } + + public void setEthereum(Ethereum ethereum) { + this.ethereum = ethereum; + } + + @Autowired + public void setSystemProperties(SystemProperties systemProperties) { + this.systemProperties = (CasperProperties) systemProperties; + } + + /** + * @return key: Casper contract address + * value: Casper state initialization transactions + */ + private Pair> makeInitTxes() { + final byte[] casperAddress = new byte[20]; + + // All contracts except Casper itself + final String VIPER_RLP_DECODER_TX = "0xf9035b808506fc23ac0083045f788080b903486103305660006109ac5260006109cc527f0100000000000000000000000000000000000000000000000000000000000000600035046109ec526000610a0c5260006109005260c06109ec51101515585760f86109ec51101561006e5760bf6109ec510336141558576001610a0c52610098565b60013560f76109ec51036020035260005160f66109ec510301361415585760f66109ec5103610a0c525b61022060016064818352015b36610a0c511015156100b557610291565b7f0100000000000000000000000000000000000000000000000000000000000000610a0c5135046109ec526109cc5160206109ac51026040015260016109ac51016109ac5260806109ec51101561013b5760016109cc5161044001526001610a0c516109cc5161046001376001610a0c5101610a0c5260216109cc51016109cc52610281565b60b86109ec5110156101d15760806109ec51036109cc51610440015260806109ec51036001610a0c51016109cc51610460013760816109ec5114156101ac5760807f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350410151558575b607f6109ec5103610a0c5101610a0c5260606109ec51036109cc51016109cc52610280565b60c06109ec51101561027d576001610a0c51013560b76109ec510360200352600051610a2c526038610a2c5110157f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350402155857610a2c516109cc516104400152610a2c5160b66109ec5103610a0c51016109cc516104600137610a2c5160b66109ec5103610a0c510101610a0c526020610a2c51016109cc51016109cc5261027f565bfe5b5b5b81516001018083528114156100a4575b5050601f6109ac511115155857602060206109ac5102016109005260206109005103610a0c5261022060016064818352015b6000610a0c5112156102d45761030a565b61090051610a0c516040015101610a0c51610900516104400301526020610a0c5103610a0c5281516001018083528114156102c3575b50506109cc516109005101610420526109cc5161090051016109005161044003f35b61000461033003610004600039610004610330036000f31b2d4f"; + final String SIG_HASHER_TX = "0xf9016d808506fc23ac0083026a508080b9015a6101488061000e6000396101565660007f01000000000000000000000000000000000000000000000000000000000000006000350460f8811215610038576001915061003f565b60f6810391505b508060005b368312156100c8577f01000000000000000000000000000000000000000000000000000000000000008335048391506080811215610087576001840193506100c2565b60b881121561009d57607f8103840193506100c1565b60c08112156100c05760b68103600185013560b783036020035260005101840193505b5b5b50610044565b81810360388112156100f4578060c00160005380836001378060010160002060e052602060e0f3610143565b61010081121561010557600161011b565b6201000081121561011757600261011a565b60035b5b8160005280601f038160f701815382856020378282600101018120610140526020610140f350505b505050505b6000f31b2d4f"; + final String PURITY_CHECKER_TX = "0xf90467808506fc23ac00830583c88080b904546104428061000e60003961045056600061033f537c0100000000000000000000000000000000000000000000000000000000600035047f80010000000000000000000000000000000000000030ffff1c0e00000000000060205263a1903eab8114156103f7573659905901600090523660048237600435608052506080513b806020015990590160009052818152602081019050905060a0526080513b600060a0516080513c6080513b8060200260200159905901600090528181526020810190509050610100526080513b806020026020015990590160009052818152602081019050905061016052600060005b602060a05103518212156103c957610100601f8360a051010351066020518160020a161561010a57fe5b80606013151561011e57607f811315610121565b60005b1561014f5780607f036101000a60018460a0510101510482602002610160510152605e8103830192506103b2565b60f18114801561015f5780610164565b60f282145b905080156101725780610177565b60f482145b9050156103aa5760028212151561019e5760606001830360200261010051015112156101a1565b60005b156101bc57607f6001830360200261010051015113156101bf565b60005b156101d157600282036102605261031e565b6004821215156101f057600360018303602002610100510151146101f3565b60005b1561020d57605a6002830360200261010051015114610210565b60005b1561022b57606060038303602002610100510151121561022e565b60005b1561024957607f60038303602002610100510151131561024c565b60005b1561025e57600482036102605261031d565b60028212151561027d57605a6001830360200261010051015114610280565b60005b1561029257600282036102605261031c565b6002821215156102b157609060018303602002610100510151146102b4565b60005b156102c657600282036102605261031b565b6002821215156102e65760806001830360200261010051015112156102e9565b60005b156103035760906001830360200261010051015112610306565b60005b1561031857600282036102605261031a565bfe5b5b5b5b5b604060405990590160009052600081526102605160200261016051015181602001528090502054156103555760016102a052610393565b60306102605160200261010051015114156103755760016102a052610392565b60606102605160200261010051015114156103915760016102a0525b5b5b6102a051151561039f57fe5b6001830192506103b1565b6001830192505b5b8082602002610100510152600182019150506100e0565b50506001604060405990590160009052600081526080518160200152809050205560016102e05260206102e0f35b63c23697a8811415610440573659905901600090523660048237600435608052506040604059905901600090526000815260805181602001528090502054610300526020610300f35b505b6000f31b2d4f"; + + List txStrs = new ArrayList<>(); + txStrs.add(VIPER_RLP_DECODER_TX); + txStrs.add(SIG_HASHER_TX); + txStrs.add(PURITY_CHECKER_TX); + + Repository track = (Repository) ethereum.getSnapshotTo(ethereum.getBlockchain().getBlockByNumber(0).getStateRoot()); + BigInteger nonce = track.getNonce(NULL_SIGN_SENDER.getAddress()); + List txs = new ArrayList<>(); + final long gasPriceFund = 25_000_000_000L; + for (int i = 0; i < txStrs.size(); ++i) { + Transaction deployTx = new Transaction(ByteUtil.hexStringToBytes(txStrs.get(i))); + BigInteger value = BigInteger.ZERO; + value = value.add(ByteUtil.bytesToBigInteger(deployTx.getValue())); + value = value.add( + ByteUtil.bytesToBigInteger(deployTx.getGasPrice()) + .multiply(ByteUtil.bytesToBigInteger(deployTx.getGasLimit())) + ); + Transaction fundTx = new Transaction( + ByteUtil.bigIntegerToBytes(nonce), + ByteUtil.longToBytesNoLeadZeroes(gasPriceFund), + ByteUtil.longToBytesNoLeadZeroes(90_000), + deployTx.getSender(), + ByteUtil.bigIntegerToBytes(value), + new byte[0], + null + ); + fundTx.sign(NULL_SIGN_SENDER); + txs.add(fundTx); + txs.add(deployTx); + nonce = nonce.add(BigInteger.ONE); + } + + // 0 - fund, 1 - rlp, 2 - fund, 3 - sig hasher, 4 - fund, 5 - purity checker + byte[] sigHasherContract = txs.get(3).getContractAddress(); + byte[] purityCheckerContract = txs.get(5).getContractAddress(); + + // Casper! + try { + // Sources: + // https://github.com/ethereum/casper/blob/9106ad647857e6a545f55d7f6193bdc03bb9f5cd/casper/contracts/simple_casper.v.py + String casperBinStr = systemProperties.getCasperBin(); + byte[] casperBin = ByteUtil.hexStringToBytes(casperBinStr); + + CallTransaction.Contract contract = new CallTransaction.Contract(systemProperties.getCasperAbi()); + + byte[] casperInit = contract.getConstructor().encodeArguments( + systemProperties.getCasperEpochLength(), // Epoch length + WITHDRAWAL_DELAY, // Withdrawal delay + DYNASTY_LOGOUT_DELAY, // Logout delay + ECKey.fromPrivate(sha3("0".getBytes())).getAddress(), // Owner + sigHasherContract, // Signature hasher contract + purityCheckerContract, // Purity checker contract + BASE_INTEREST_FACTOR, // Base interest factor + BASE_PENALTY_FACTOR, // Base penalty factor + BigInteger.valueOf(MIN_DEPOSIT_ETH).multiply(BigInteger.TEN.pow(18)) // Minimum validator deposit in wei + ); + + Transaction tx = new Transaction( + ByteUtil.bigIntegerToBytes(nonce), + ByteUtil.longToBytesNoLeadZeroes(gasPriceFund), + ByteUtil.longToBytesNoLeadZeroes(5_000_000), + new byte[0], + ByteUtil.longToBytesNoLeadZeroes(0), + ArrayUtils.addAll(casperBin, casperInit), // Merge contract and constructor args + null); + tx.sign(NULL_SIGN_SENDER); + + // set casperAddress + System.arraycopy(tx.getContractAddress(), 0, casperAddress, 0, 20); + txs.add(tx); + } catch (Exception ex) { + throw new RuntimeException("Failed to generate Casper init transactions", ex); + } + + return Pair.of(casperAddress, txs); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperPendingStateImpl.java b/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperPendingStateImpl.java new file mode 100644 index 0000000000..557b55c3b5 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperPendingStateImpl.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.core; + +import org.ethereum.casper.config.CasperProperties; +import org.ethereum.core.Block; +import org.ethereum.core.PendingStateImpl; +import org.ethereum.core.Repository; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionExecutor; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.listener.EthereumListener; +import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.util.ByteUtil; + +public class CasperPendingStateImpl extends PendingStateImpl { + + public CasperPendingStateImpl(EthereumListener listener) { + super(listener); + } + + @Override + protected boolean receiptIsValid(TransactionReceipt receipt) { + boolean isValid = super.receiptIsValid(receipt); + if (isValid) { + return true; + } else if (CasperFacade.isVote(receipt.getTransaction(), ((CasperProperties) config).getCasperAddress())) { + return receipt.isSuccessful(); + } + + return false; + } + + @Override + protected String validate(Transaction tx) { + try { + tx.verify(); + } catch (Exception e) { + return String.format("Invalid transaction: %s", e.getMessage()); + } + + if (CasperFacade.isVote(tx, ((CasperProperties) config).getCasperAddress())) { + return null; // Doesn't require more checks + } + + if (config.getMineMinGasPrice().compareTo(ByteUtil.bytesToBigInteger(tx.getGasPrice())) > 0) { + return "Too low gas price for transaction: " + ByteUtil.bytesToBigInteger(tx.getGasPrice()); + } + + return null; + } + + + @Override + protected TransactionExecutor createTransactionExecutor(Transaction transaction, byte[] minerCoinbase, + Repository track, Block currentBlock) { + return new CasperTransactionExecutor(transaction, minerCoinbase, + track, blockStore, programInvokeFactory, currentBlock, new EthereumListenerAdapter(), 0) + .withCommonConfig(commonConfig); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperTransactionExecutor.java b/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperTransactionExecutor.java new file mode 100644 index 0000000000..ab73b17235 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/core/CasperTransactionExecutor.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.core; +import org.ethereum.casper.config.CasperProperties; +import org.ethereum.core.Block; +import org.ethereum.core.CommonTransactionExecutor; +import org.ethereum.core.Repository; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionExecutionSummary; +import org.ethereum.db.BlockStore; +import org.ethereum.listener.EthereumListener; +import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.vm.program.invoke.ProgramInvokeFactory; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Arrays; +import static org.ethereum.util.BIUtil.toBI; + +public class CasperTransactionExecutor extends CommonTransactionExecutor { + + public CasperTransactionExecutor(Transaction tx, byte[] coinbase, Repository track, BlockStore blockStore, + ProgramInvokeFactory programInvokeFactory, Block currentBlock) { + + super(tx, coinbase, track, blockStore, programInvokeFactory, currentBlock, new EthereumListenerAdapter(), 0); + } + + public CasperTransactionExecutor(Transaction tx, byte[] coinbase, Repository track, BlockStore blockStore, + ProgramInvokeFactory programInvokeFactory, Block currentBlock, + EthereumListener listener, long gasUsedInTheBlock) { + + super(tx, coinbase, track, blockStore, programInvokeFactory, currentBlock, listener, gasUsedInTheBlock); + } + + @Override + public void init() { + super.init(); + // Already failed on common validation + if (execError != null) { + return; + } + + // More validations for Casper + + // EIP 208 + if (Arrays.equals(tx.getSender(), Transaction.NULL_SENDER) && toBI(tx.getNonce()).compareTo(BigInteger.ZERO) > 0) { + execError(String.format("Null sender transaction should use 0 nonce, %s instead", toBI(tx.getNonce()))); + + return; + } + + // EIP 208 + if (Arrays.equals(tx.getSender(), Transaction.NULL_SENDER) && + (toBI(tx.getValue()).compareTo(BigInteger.ZERO) > 0 || toBI(tx.getGasPrice()).compareTo(BigInteger.ZERO) > 0)) { + execError(String.format("Null sender transaction should have 0 value (actual %s), " + + "and 0 gasprice (actual: %s)", toBI(tx.getValue()), toBI(tx.getGasPrice()))); + + return; + } + } + + @Override + public void execute() { + + if (!readyToExecute) return; + + if (!localCall && !isCasperServiceTx()) { + track.increaseNonce(tx.getSender()); + + BigInteger txGasLimit = toBI(tx.getGasLimit()); + BigInteger txGasCost = toBI(tx.getGasPrice()).multiply(txGasLimit); + track.addBalance(tx.getSender(), txGasCost.negate()); + + if (logger.isInfoEnabled()) + logger.info("Paying: txGasCost: [{}], gasPrice: [{}], gasLimit: [{}]", txGasCost, toBI(tx.getGasPrice()), txGasLimit); + } + + if (tx.isContractCreation()) { + create(); + } else { + call(); + } + } + + private boolean isCasperServiceTx() { + return CasperFacade.isServiceTx(tx, ((CasperProperties) config).getCasperAddress()); + } + + @Override + protected void payRewards(final TransactionExecutionSummary summary) { + if (isCasperServiceTx()) { + // Return money to sender for service Casper tx + track.addBalance(tx.getSender(), summary.getFee()); + logger.info("Refunded successful Casper Vote from [{}]", Hex.toHexString(tx.getSender())); + } else { + // Transfer fees to miner + track.addBalance(coinbase, summary.getFee()); + touchedAccounts.add(coinbase); + logger.info("Pay fees to miner: [{}], feesEarned: [{}]", Hex.toHexString(coinbase), summary.getFee()); + } + } + + @Override + public long getGasUsed() { + long gasUsed = super.getGasUsed(); + // Casper service txs have 0 cost. If it's failed, it'll be discarded on higher level + // FIXME: NOTE: (gasLeftOver + gasRefund) is used in rewards debug, so adjusting only gasUsed may produce some bugs in the future + if (isCasperServiceTx()) { + gasUsed = 0; + } + return gasUsed; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/manager/CasperWorldManager.java b/ethereumj-core/src/main/java/org/ethereum/casper/manager/CasperWorldManager.java new file mode 100644 index 0000000000..4c75e1c245 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/manager/CasperWorldManager.java @@ -0,0 +1,33 @@ +package org.ethereum.casper.manager; + +import org.ethereum.casper.core.CasperFacade; +import org.ethereum.casper.service.CasperValidatorService; +import org.ethereum.config.SystemProperties; +import org.ethereum.core.Blockchain; +import org.ethereum.core.Repository; +import org.ethereum.manager.WorldManager; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PostConstruct; + +public class CasperWorldManager extends WorldManager { + + @Autowired + private CasperValidatorService casperValidatorService; + + @Autowired + private CasperFacade casper; + + + public CasperWorldManager(SystemProperties config, Repository repository, Blockchain blockchain) { + super(config, repository, blockchain); + } + + @PostConstruct + @Override + protected void init() { + super.init(); + casper.setEthereum(ethereum); + casperValidatorService.init(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/mine/CasperBlockMiner.java b/ethereumj-core/src/main/java/org/ethereum/casper/mine/CasperBlockMiner.java new file mode 100644 index 0000000000..1d27b0cc12 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/mine/CasperBlockMiner.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.mine; + +import org.ethereum.casper.core.CasperFacade; +import org.ethereum.config.SystemProperties; +import org.ethereum.core.Block; +import org.ethereum.core.Blockchain; +import org.ethereum.core.PendingState; +import org.ethereum.core.PendingStateImpl; +import org.ethereum.core.Transaction; +import org.ethereum.listener.CompositeEthereumListener; +import org.ethereum.mine.BlockMiner; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +public class CasperBlockMiner extends BlockMiner { + @Autowired + CasperFacade casper; + + public CasperBlockMiner(SystemProperties config, CompositeEthereumListener listener, + Blockchain blockchain, PendingState pendingState) { + super(config, listener, blockchain, pendingState); + } + + @Override + protected boolean isAcceptableTx(Transaction tx) { + if (casper.isVote(tx)) { + return true; + } + return super.isAcceptableTx(tx); + } + + @Override + protected Block getNewBlockForMining() { + Block bestBlockchain = blockchain.getBestBlock(); + Block bestPendingState = ((PendingStateImpl) pendingState).getBestBlock(); + + logger.debug("getNewBlockForMining best blocks: PendingState: " + bestPendingState.getShortDescr() + + ", Blockchain: " + bestBlockchain.getShortDescr()); + + // Casper txs should come after regular + List pendingTxs = getAllPendingTransactions(); + pendingTxs.sort((tx1, tx2) -> { + boolean tx1isVote = casper.isVote(tx1); + boolean tx2isVote = casper.isVote(tx2); + return Boolean.compare(tx1isVote, tx2isVote); + }); + + return createOptimizedBlock(bestPendingState, pendingTxs, getUncles(bestPendingState)); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/service/CasperValidatorService.java b/ethereumj-core/src/main/java/org/ethereum/casper/service/CasperValidatorService.java new file mode 100644 index 0000000000..c38b779b5c --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/service/CasperValidatorService.java @@ -0,0 +1,626 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.service; + +import org.apache.commons.lang3.ArrayUtils; +import org.ethereum.casper.config.CasperProperties; +import org.ethereum.casper.core.CasperFacade; +import org.ethereum.config.SystemProperties; +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.Blockchain; +import org.ethereum.core.Repository; +import org.ethereum.core.Transaction; +import org.ethereum.crypto.ECKey; +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.facade.Ethereum; +import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.sync.SyncManager; +import org.ethereum.util.ByteUtil; +import org.ethereum.util.RLP; +import org.ethereum.util.blockchain.EtherUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.BigIntegers; +import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import static org.ethereum.crypto.HashUtil.sha3; +import static org.ethereum.casper.service.CasperValidatorService.ValidatorState.LOGGED_OUT; +import static org.ethereum.casper.service.CasperValidatorService.ValidatorState.UNINITIATED; +import static org.ethereum.casper.service.CasperValidatorService.ValidatorState.VOTING; +import static org.ethereum.casper.service.CasperValidatorService.ValidatorState.WAITING_FOR_LOGIN; +import static org.ethereum.casper.service.CasperValidatorService.ValidatorState.WAITING_FOR_LOGOUT; +import static org.ethereum.casper.service.CasperValidatorService.ValidatorState.WAITING_FOR_VALCODE; +import static org.ethereum.casper.service.CasperValidatorService.ValidatorState.WAITING_FOR_WITHDRAWABLE; +import static org.ethereum.casper.service.CasperValidatorService.ValidatorState.WAITING_FOR_WITHDRAWN; + +@Component +public class CasperValidatorService { + private static final Logger logger = LoggerFactory.getLogger("casper.validator"); + + private SyncManager syncManager; + + private CasperFacade casper; + + private Blockchain blockchain; + + private Repository repository; + + private CasperProperties config; + + private Ethereum ethereum; + + private byte[] valContractAddress = null; + + private ECKey coinbase; + + private static final int LOGOUT_COOLDOWN = 60 * 1000; // In millis + + private static final long DEFAULT_GASLIMIT = 2_500_000; // Enough for deposit and logout + + private Map votes = new HashMap<>(); + + private BigInteger depositSize; + + private boolean started = false; + + private long latestTargetEpoch = -1; + private long latestSourceEpoch = -1; + + private long lastLogoutBroadcast = -1; + + + private byte[] validationContractCode(byte[] address) { + // FIXME: It's better to have some contract here + // Originally LLL: + // ['seq', + // ['return', [0], + // ['lll', + // ['seq', + // ['calldatacopy', 0, 0, 128], + // ['call', 3000, 1, 0, 0, 128, 0, 32], + // ['mstore', 0, ['eq', ['mload', 0], ]], + // ['return', 0, 32] + // ], + // [0]] + // ] + // ] + String part1 = "61003d5660806000600037602060006080600060006001610bb8f15073"; + String part2 = "6000511460005260206000f35b61000461003d0361000460003961000461003d036000f3"; + return Hex.decode(part1 + Hex.toHexString(address) + part2); + } + + protected byte[] makeVote(long validatorIndex, byte[] targetHash, long targetEpoch, long sourceEpoch, ECKey sender) { + byte[] sigHash = sha3(RLP.smartEncodeList(validatorIndex, new ByteArrayWrapper(targetHash), targetEpoch, sourceEpoch)); + byte[] vrs = make3IntSignature(sigHash, sender); + return RLP.smartEncodeList(validatorIndex, new ByteArrayWrapper(targetHash), targetEpoch, sourceEpoch, new ByteArrayWrapper(vrs)); + } + + private byte[] makeLogout(long validatorIndex, long epoch, ECKey sender) { + byte[] sigHash = sha3(RLP.smartEncodeList(validatorIndex, epoch)); + byte[] vrs = make3IntSignature(sigHash, sender); + return RLP.smartEncodeList(validatorIndex, epoch, new ByteArrayWrapper(vrs)); + } + + private byte[] make3IntSignature(byte[] data, ECKey signer) { + ECKey.ECDSASignature signature = signer.sign(data); + byte[] v, r, s; // encoding as 32-byte ints + v = BigIntegers.asUnsignedByteArray(32, BigInteger.valueOf(signature.v)); // FIXME: If we'll have chainId it would fail + r = BigIntegers.asUnsignedByteArray(32, signature.r); + s = BigIntegers.asUnsignedByteArray(32, signature.s); + byte[] vr = ArrayUtils.addAll(v, r); + byte[] vrs = ArrayUtils.addAll(vr, s); + + return vrs; + } + + public enum ValidatorState { + UNINITIATED(1), // Check if logged in, and if not deploy a valcode contract + WAITING_FOR_VALCODE(2), // Wait for valcode contract to be included, then submit deposit + WAITING_FOR_LOGIN(3), // Wait for validator to login, then change state to `voting` + VOTING(4), // Vote on each new epoch + WAITING_FOR_LOGOUT(5), // + WAITING_FOR_WITHDRAWABLE(6), + WAITING_FOR_WITHDRAWN(7), + LOGGED_OUT(8); + + int i; + ValidatorState(int i) { + this.i = i; + } + + @Override + public String toString() { + return this.name(); + } + } + + private ValidatorState state = UNINITIATED; + + private Map handlers = new HashMap<>(); + + @Autowired + public CasperValidatorService(Ethereum ethereum, SystemProperties config) { + this.ethereum = ethereum; + this.config = (CasperProperties) config; + + handlers.put(UNINITIATED, this::checkLoggedIn); + handlers.put(WAITING_FOR_VALCODE, this::checkValcode); + handlers.put(WAITING_FOR_LOGIN, this::checkLoggedIn); + handlers.put(VOTING, this::vote); + handlers.put(WAITING_FOR_LOGOUT, this::voteThenLogout); + handlers.put(WAITING_FOR_WITHDRAWABLE, this::checkWithdrawable); + handlers.put(WAITING_FOR_WITHDRAWN, this::checkWithdrawn); + handlers.put(LOGGED_OUT, this::checkLoggedIn); + } + + public void init() { + if (Boolean.TRUE.equals(config.getCasperValidatorEnabled())) { + start(); + } + } + + public synchronized void start() { + if (!started) { + this.coinbase = ECKey.fromPrivate(this.config.getCasperValidatorPrivateKey()); + this.depositSize = EtherUtil.convert(this.config.getCasperValidatorDeposit(), EtherUtil.Unit.ETHER); + logger.info("Starting casper validator with coinbase 0x{}", Hex.toHexString(coinbase.getAddress())); + // FIXME: Actually we should listen only to HEAD changes + ethereum.addListener(new EthereumListenerAdapter() { + @Override + public void onBlock(BlockSummary blockSummary) { + newBlockConsumer().accept(blockSummary.getBlock()); + } + }); + this.started = true; + } else { + logger.warn("Attempting to start casper validator but it's already started"); + } + } + + private Consumer newBlockConsumer() { + return block -> { + if(!syncManager.isSyncDone()) return; + logCasperInfo(); + + handlers.get(state).run(); + }; + } + + public void reLogin() { + logger.info("Attempting to relogin casper validator with coinbase 0x{}", Hex.toHexString(coinbase.getAddress())); + if (!state.equals(LOGGED_OUT)) { + throw new RuntimeException(String.format("Validator is not logged, out, cannot relogin. " + + "Current state: %s", state)); + } + this.depositSize = EtherUtil.convert(config.getCasperValidatorDeposit(), EtherUtil.Unit.ETHER); + } + + /** + * Check if logged in, and if not deploy a valcode contract + */ + private void checkLoggedIn() { + Long validatorIndex = getValidatorIndex(); + // (1) Check if the validator has ever deposited funds + if (validatorIndex == 0 && depositSize.compareTo(BigInteger.ZERO) > 0) { + // The validator hasn't deposited funds but deposit flag is set, so deposit! + broadcastValcodeTx(); + setState(WAITING_FOR_VALCODE); + return; + } else if (validatorIndex == 0) { + // The validator hasn't deposited funds and we have no intention to, so return! + return; + } + initValContractAddress(); + this.depositSize = BigInteger.ZERO; // FIXME: Is it correct place for it? + + // (2) Check if the validator is logged in + long currentEpoch = getCurrentEpoch(); + if (!isLoggedIn(currentEpoch, validatorIndex)) { + // The validator isn't logged in, so return! + return; + } + + setState(VOTING); + } + + private void broadcastValcodeTx() { + Transaction tx = makeTx(null, null, validationContractCode(coinbase.getAddress()), null, null, null, null); + + ethereum.sendTransaction(tx, + transaction -> { + logger.info("Validation code contract creation tx sent, txHash: {}", ByteUtil.toHexString(tx.getHash())); + valContractAddress = transaction.getContractAddress(); // FIXME: it's not true until we didn't get tx exec in our state + }, + throwable -> logger.error("Validation code contract creation tx was not sent", throwable), + throwable -> logger.warn("Simulation of validation contract tx execution failed", throwable) + ); + } + + private Transaction makeTx(byte[] receiveAddress, BigInteger value, byte[] data, byte[] gasPrice, byte[] gasLimit, + BigInteger nonce, Boolean signed) { + if (nonce == null) { + nonce = repository.getNonce(coinbase.getAddress()); + } + if (gasLimit == null) { + gasLimit = ByteUtil.longToBytesNoLeadZeroes(DEFAULT_GASLIMIT); + } + if (value == null) { + value = BigInteger.ZERO; + } + if (data == null) { + data = new byte[0]; + } + if (signed == null) { + signed = Boolean.TRUE; + } + if (gasPrice == null && signed) { + gasPrice = ByteUtil.bigIntegerToBytes(BigInteger.valueOf(110).multiply(BigInteger.TEN.pow(9))); + } + + Transaction tx = new Transaction( + ByteUtil.bigIntegerToBytes(nonce), + gasPrice, + gasLimit, + receiveAddress, + ByteUtil.bigIntegerToBytes(value), + data, + ethereum.getChainIdForNextBlock()); + + if (signed) tx.sign(coinbase); + + return tx; + } + + + + private long getValidatorIndex() { + return constCallCasperForLong("validator_indexes", new ByteArrayWrapper(coinbase.getAddress())); + } + + private void initValContractAddress() { // Actually it's not used after deposit + if (valContractAddress != null) + return; + byte[] address = (byte[]) casper.constCall("validators__addr", getValidatorIndex())[0]; + if (!Arrays.equals(address, new byte[20])) { + logger.info("Valcode contract found at {}", address); + this.valContractAddress = address; + } + } + + /** + * Wait for valcode contract to be included, then submit deposit + */ + private void checkValcode() { + if (valContractAddress == null || repository.getCode(valContractAddress) == null || + repository.getCode(valContractAddress).length == 0) { + // Valcode still not deployed! or lost + return; + } + if (repository.getBalance(coinbase.getAddress()).compareTo(depositSize) < 0) { + logger.info("Cannot login as validator: Not enough ETH! Balance: {}, Deposit: {}", + repository.getBalance(coinbase.getAddress()), depositSize); + return; + } + broadcastDepositTx(); + setState(WAITING_FOR_LOGIN); + } + + + private void broadcastDepositTx() { + // Create deposit transaction + logger.info("Broadcasting deposit tx on value: {}", depositSize); // TODO: make me more readable than wei + Transaction depositTx = makeDepositTx(valContractAddress, coinbase.getAddress(), depositSize); + this.depositSize = BigInteger.ZERO; + ethereum.sendTransaction(depositTx, + tx -> logger.info("Deposit tx successfully submitted to the net, txHash {}", ByteUtil.toHexString(tx.getHash())), + throwable -> logger.error("Failed to send deposit tx", throwable), + throwable -> logger.warn("Simulation of deposit tx execution failed", throwable) + ); + logger.info("Broadcasting deposit tx {}", depositTx); + } + + private Transaction makeDepositTx(byte[] valContractAddress, byte[] coinbaseAddress, BigInteger deposit) { + byte[] functionCallBytes = casper.getContract().getByName("deposit").encode( + new ByteArrayWrapper(valContractAddress), + new ByteArrayWrapper(coinbaseAddress)); + Transaction tx = makeTx(casper.getAddress(), deposit, functionCallBytes, null, + ByteUtil.longToBytesNoLeadZeroes(1_000_000), + null, true); + return tx; + + } + + private Transaction makeVoteTx(byte[] voteData) { + byte[] functionCallBytes = casper.getContract().getByName("vote").encode(voteData); + Transaction tx = makeTx(casper.getAddress(), null, functionCallBytes, null, + ByteUtil.longToBytesNoLeadZeroes(1_000_000), + BigInteger.ZERO, false); + return tx; + + } + + private Transaction makeLogoutTx(byte[] logoutData) { + byte[] functionCallBytes = casper.getContract().getByName("logout").encode(new ByteArrayWrapper(logoutData)); + Transaction tx = makeTx(casper.getAddress(), null, functionCallBytes, null, null, + null, true); + return tx; + + } + + private Transaction makeWithdrawTx(long validatorIndex) { + byte[] functionCallBytes = casper.getContract().getByName("withdraw").encode(validatorIndex); + Transaction tx = makeTx(casper.getAddress(), null, functionCallBytes, null, null, + null, true); + return tx; + } + + private boolean isLoggedIn(long targetEpoch, long validatorIndex) { + long startDynasty = constCallCasperForLong("validators__start_dynasty", validatorIndex); + long endDynasty = constCallCasperForLong("validators__end_dynasty", validatorIndex); + long currentDynasty = constCallCasperForLong("dynasty_in_epoch", targetEpoch); + long pastDynasty = currentDynasty - 1; + boolean inCurrentDynasty = ((startDynasty <= currentDynasty) && + (currentDynasty < endDynasty)); + boolean inPrevDynasty = ((startDynasty <= pastDynasty) && + (pastDynasty < endDynasty)); + + return inCurrentDynasty || inPrevDynasty; + } + + // TODO: integrate with composite ethereum listener + private void setState(ValidatorState newState) { + logger.info("Changing validator state from {} to {}", state, newState); + this.state = newState; + } + + private byte[] getEpochBlockHash(long epoch) { + if (epoch == 0) { + return Hex.decode("0000000000000000000000000000000000000000000000000000000000000000"); + } + return blockchain.getBlockByNumber(epoch * config.getCasperEpochLength() - 1).getHash(); + } + + public void voteThenLogout() { + long epoch = getEpoch(); + long validatorIndex = getValidatorIndex(); + // Verify that we are not already logged out + if (!isLoggedIn(epoch, validatorIndex)) { + // If we logged out, start waiting for withdrawal + logger.info("Validator logged out!"); + setState(WAITING_FOR_WITHDRAWABLE); + return; + } + vote(); + broadcastLogoutTx(); + setState(WAITING_FOR_LOGOUT); + } + + // FIXME: WHY there are 2 methods for the same thing??? + private long getEpoch() { + return blockchain.getBestBlock().getNumber() / config.getCasperEpochLength(); // floor division + } + + private long getCurrentEpoch() { // FIXME: WHY there are 2 methods for the same thing??? + return constCallCasperForLong("current_epoch"); + } + + private boolean vote() { + long epoch = getEpoch(); + + // NO DOUBLE VOTE: Don't vote if we have already + if (votes.containsKey(epoch)) { + return false; + } + + long validatorIndex = getValidatorIndex(); + + // Make sure we are logged in + if (!isLoggedIn(epoch, validatorIndex)) { + throw new RuntimeException("Cannot vote: Validator not logged in!"); + } + + // Don't start too early + if (blockchain.getBestBlock().getNumber() % config.getCasperEpochLength() <= config.getCasperEpochLength() / 4) { + return false; + } + + byte[] voteData = getRecommendedVoteData(validatorIndex); + votes.put(epoch, voteData); + // FIXME: I don't like that epoch could be not the same as in data + // FIXME: also synchronize required !!! + + // Send the vote! + Transaction voteTx = makeVoteTx(voteData); + ethereum.sendTransaction(voteTx, + tx -> logger.info("Vote sent!, txHash: {}", ByteUtil.toHexString(tx.getHash())), + throwable -> logger.error("Failed to sent vote", throwable), + throwable -> logger.warn("Simulation of vote tx exec failed", throwable) + ); + logger.info("Sending vote tx: {}", voteTx); + + return true; + } + + private void broadcastLogoutTx() { + if (lastLogoutBroadcast > (System.currentTimeMillis() - LOGOUT_COOLDOWN)) { + return; + } + + this.lastLogoutBroadcast = System.currentTimeMillis(); + long epoch = getEpoch(); + + // Generate the message + byte[] logoutData = makeLogout(getValidatorIndex(), epoch, coinbase); + Transaction logoutTx = makeLogoutTx(logoutData); + logger.info("Logout Tx broadcasted: {}", logoutTx); + ethereum.sendTransaction(logoutTx, + transaction -> logger.info("Successfully sent logout tx, txHash: {}", ByteUtil.toHexString(transaction.getHash())), + throwable -> logger.error("Failed to send logout", throwable), + throwable -> logger.warn("Simulation of logout tx exec failed", throwable) + ); + } + + private void checkWithdrawable() { + long validatorIndex = getValidatorIndex(); + if (validatorIndex == 0) { + logger.info("Validator is already deleted!"); + setState(LOGGED_OUT); + return; + } + long validatorEndDynasty = constCallCasperForLong("validators__end_dynasty", validatorIndex); + long endEpoch = constCallCasperForLong("dynasty_start_epoch", validatorEndDynasty + 1); + + // Check Casper to see if we can withdraw + long curEpoch = getCurrentEpoch(); + long withdrawalDelay = constCallCasperForLong("withdrawal_delay"); + if (curEpoch >= (endEpoch + withdrawalDelay)) { + // Make withdraw tx & broadcast + Transaction withdrawTx = makeWithdrawTx(validatorIndex); + ethereum.sendTransaction(withdrawTx, + transaction -> logger.info("Successfully sent withdraw tx, txHash {}", ByteUtil.toHexString(transaction.getHash())), + throwable -> logger.error("Failed to send withdraw", throwable), + throwable -> logger.warn("Simulation of withdraw tx exec failed", throwable) + ); + // Set the state to waiting for withdrawn + setState(WAITING_FOR_WITHDRAWN); + } + } + + private void checkWithdrawn() { + // Check that we have been withdrawn--validator index will now be zero + if (constCallCasperForLong("validator_indexes", new ByteArrayWrapper(coinbase.getAddress())) == 0) { + setState(LOGGED_OUT); + } + } + + private void logCasperInfo() { + long curEpoch = getCurrentEpoch(); + long expectedSourceEpoch = constCallCasperForLong("expected_source_epoch"); + BigInteger curDepositsScaled = (BigInteger) casper.constCall("total_curdyn_deposits_scaled")[0]; + BigInteger prevDepositsScaled = (BigInteger) casper.constCall("total_prevdyn_deposits_scaled")[0]; + BigDecimal curVotes = (BigDecimal) casper.constCall("votes__cur_dyn_votes", curEpoch, expectedSourceEpoch)[0]; + BigDecimal prevVotes = (BigDecimal) casper.constCall("votes__prev_dyn_votes", curEpoch, expectedSourceEpoch)[0]; + BigDecimal scaleFactor = (BigDecimal) casper.constCall("deposit_scale_factor", curEpoch)[0]; + BigDecimal curVotesScaled = curVotes.multiply(scaleFactor); + BigDecimal prevVotesScaled = prevVotes.multiply(scaleFactor); + BigDecimal curVotesPct = BigDecimal.ZERO; + BigDecimal prevVotesPct = BigDecimal.ZERO; + if (curDepositsScaled.compareTo(BigInteger.ZERO) > 0 ) { + curVotesPct = curVotesScaled.multiply(BigDecimal.valueOf(100)).divide(new BigDecimal(curDepositsScaled), MathContext.DECIMAL32); + } + if (prevDepositsScaled.compareTo(BigInteger.ZERO) > 0 ) { + prevVotesPct = prevVotesScaled.multiply(BigDecimal.valueOf(100)).divide(new BigDecimal(prevDepositsScaled), MathContext.DECIMAL32); + } + + long lastFinalizedEpoch = constCallCasperForLong("last_finalized_epoch"); + long lastJustifiedEpoch = constCallCasperForLong("last_justified_epoch"); + BigDecimal lastNonvoterRescale = (BigDecimal) casper.constCall("last_nonvoter_rescale")[0]; + BigDecimal lastVoterRescale = (BigDecimal) casper.constCall("last_voter_rescale")[0]; + String logStr = String.format( + "CASPER STATUS: epoch %d, %.3f / %.3f ETH (%.2f %%) voted from current dynasty, " + + "%.3f / %.3f ETH (%.2f %%) voted from previous dynasty, last finalized epoch %d justified %d " + + "expected source %d. Nonvoter deposits last rescaled %.5fx, voter deposits %.5fx", + curEpoch, + curVotesScaled.divide(BigDecimal.TEN.pow(18), MathContext.DECIMAL32), + new BigDecimal(curDepositsScaled).divide(BigDecimal.TEN.pow(18), MathContext.DECIMAL32), + curVotesPct, + prevVotesScaled.divide(BigDecimal.TEN.pow(18), MathContext.DECIMAL32), + new BigDecimal(prevDepositsScaled).divide(BigDecimal.TEN.pow(18), MathContext.DECIMAL32), + prevVotesPct, + lastFinalizedEpoch, + lastJustifiedEpoch, + expectedSourceEpoch, + lastNonvoterRescale, + lastVoterRescale); + logger.info(logStr); + + long valIndex = getValidatorIndex(); + BigDecimal myDeposit = (BigDecimal) casper.constCall("validators__deposit", valIndex)[0]; + BigDecimal myDepositScaled = myDeposit.multiply(scaleFactor); + String myStr = String.format( + "MY VALIDATOR STATUS: epoch %d, index #%d, deposit: %.3f ETH", + curEpoch, + valIndex, + myDepositScaled.divide(BigDecimal.TEN.pow(18), MathContext.DECIMAL32) + ); + logger.info(myStr); + } + + private byte[] getRecommendedVoteData(long validatorIndex) { + long curEpoch = getCurrentEpoch(); + if (curEpoch == 0) { + return null; + } + // NOTE: Using `epoch_blockhash` because currently calls to `blockhash` within contracts + // in the ephemeral state are off by one, so we can't use `get_recommended_target_hash()` :( + // target_hash = self.epoch_blockhash(current_epoch) + // ANSWER: Though, I'll try + + byte[] targetHash = (byte[]) casper.constCall("recommended_target_hash")[0]; + long sourceEpoch = constCallCasperForLong("recommended_source_epoch"); + + if (targetHash == null) { + return null; + } + + // Prevent NO_SURROUND slash + if (curEpoch < latestTargetEpoch || sourceEpoch < latestSourceEpoch) { + return null; + } + this.latestTargetEpoch = curEpoch; + this.latestSourceEpoch = sourceEpoch; + + return makeVote(validatorIndex, targetHash, curEpoch, sourceEpoch, coinbase); + } + + private long constCallCasperForLong(String func, Object... funcArgs) { + Object[] res = casper.constCall(func, funcArgs); + return ((BigInteger) res[0]).longValue(); + } + + @Autowired + public void setSyncManager(SyncManager syncManager) { + this.syncManager = syncManager; + } + + @Autowired + public void setBlockchain(Blockchain blockchain) { + this.blockchain = blockchain; + } + + @Autowired + public void setCasper(CasperFacade casper) { + this.casper = casper; + } + + @Autowired + public void setRepository(Repository repository) { + this.repository = repository; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/casper/validator/NullSenderTxValidator.java b/ethereumj-core/src/main/java/org/ethereum/casper/validator/NullSenderTxValidator.java new file mode 100644 index 0000000000..808ecfa085 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/casper/validator/NullSenderTxValidator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.casper.validator; + +import java.util.function.Function; +import org.ethereum.core.Transaction; +import org.ethereum.validator.AbstractValidationRule; + +/** + * Customizable validator for checking NULL_SENDER tx for acceptance + */ +public class NullSenderTxValidator extends AbstractValidationRule { + + private Function validator; + + public NullSenderTxValidator(Function validator) { + this.validator = validator; + } + + @Override + public Class getEntityClass() { + return Transaction.class; + } + + /** + * Runs transaction validation and returns its result + * + * @param transaction + */ + public Boolean validate(Transaction transaction) { + return validator.apply(transaction); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java index a8cde08ed1..49167f1808 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java @@ -24,7 +24,10 @@ import org.ethereum.datasource.leveldb.LevelDbDataSource; import org.ethereum.datasource.rocksdb.RocksDbDataSource; import org.ethereum.db.*; +import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListener; +import org.ethereum.manager.WorldManager; +import org.ethereum.mine.BlockMiner; import org.ethereum.net.eth.handler.Eth63; import org.ethereum.sync.FastSyncManager; import org.ethereum.validator.*; @@ -32,7 +35,9 @@ import org.ethereum.vm.program.ProgramPrecompile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.*; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -45,13 +50,16 @@ @Configuration @EnableTransactionManagement -@ComponentScan( - basePackages = "org.ethereum", - excludeFilters = @ComponentScan.Filter(NoAutoscan.class)) public class CommonConfig { private static final Logger logger = LoggerFactory.getLogger("general"); private Set dbSources = new HashSet<>(); + @Autowired + protected ApplicationContext ctx; + + @Autowired + protected EthereumListener ethereumListener; + private static CommonConfig defaultInstance; public static CommonConfig getDefault() { @@ -68,7 +76,7 @@ public Source precompileSource() { @Bean public SystemProperties systemProperties() { - return SystemProperties.getSpringDefault(); + return SystemProperties.getDefault(); } @Bean @@ -297,4 +305,24 @@ public PeerSource peerSource() { dbSources.add(dbSource); return new PeerSource(dbSource); } + + @Bean + public Blockchain blockchain() { + return new BlockchainImpl(systemProperties()); + } + + @Bean + public WorldManager worldManager() { + return new WorldManager(systemProperties(), repository(), blockchain()); + } + + @Bean + public PendingState pendingState() { + return new PendingStateImpl(ethereumListener); + } + + @Bean + public BlockMiner blockMiner() { + return new BlockMiner(systemProperties(), (CompositeEthereumListener) ethereumListener, blockchain(), pendingState()); + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/config/DefaultConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/DefaultConfig.java index a530665047..c23dc0a377 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/DefaultConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/DefaultConfig.java @@ -27,7 +27,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.Import; /** @@ -36,6 +38,11 @@ * Created on: 27/01/2015 01:05 */ @Configuration +@ComponentScan( + basePackages = "org.ethereum", + excludeFilters = {@ComponentScan.Filter(NoAutoscan.class), + @ComponentScan.Filter(type = FilterType.REGEX, pattern = "org\\.ethereum\\.casper\\..*")} +) @Import(CommonConfig.class) public class DefaultConfig { private static Logger logger = LoggerFactory.getLogger("general"); diff --git a/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java b/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java index 46ba76125a..0fc4015f1b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java @@ -22,6 +22,7 @@ import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigRenderOptions; import org.apache.commons.lang3.tuple.Pair; +import org.ethereum.casper.config.net.CasperTestNetConfig; import org.ethereum.config.blockchain.OlympicConfig; import org.ethereum.config.net.*; import org.ethereum.core.Genesis; @@ -70,7 +71,7 @@ * @since 22.05.2014 */ public class SystemProperties { - private static Logger logger = LoggerFactory.getLogger("general"); + protected static Logger logger = LoggerFactory.getLogger("general"); public final static String PROPERTY_DB_DIR = "database.dir"; public final static String PROPERTY_LISTEN_PORT = "peer.listen.port"; @@ -132,7 +133,7 @@ static boolean isUseOnlySpringConfig() { private @interface ValidateMe {}; - private Config config; + protected Config config; // mutable options for tests private String databaseDir = null; @@ -150,7 +151,7 @@ static boolean isUseOnlySpringConfig() { private Boolean discoveryEnabled = null; private GenesisJson genesisJson; - private BlockchainNetConfig blockchainConfig; + protected BlockchainNetConfig blockchainConfig; private Genesis genesis; private Boolean vmTrace; private Boolean recordInternalTransactionsData; @@ -342,6 +343,9 @@ public BlockchainNetConfig getBlockchainConfig() { case "testnet": blockchainConfig = new TestNetConfig(); break; + case "casper": + blockchainConfig = new CasperTestNetConfig(); + break; default: throw new RuntimeException("Unknown value for 'blockchain.config.name': '" + config.getString("blockchain.config.name") + "'"); } diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java index 63e4a17b82..2ce71310e0 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java @@ -45,7 +45,7 @@ public class Eip150HFConfig implements BlockchainConfig, BlockchainNetConfig { protected BlockchainConfig parent; - static class GasCostEip150HF extends GasCost { + public static class GasCostEip150HF extends GasCost { public int getBALANCE() { return 400; } public int getEXT_CODE_SIZE() { return 700; } public int getEXT_CODE_COPY() { return 700; } diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/HomesteadConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/HomesteadConfig.java index 745d7e212a..270fd7f670 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/HomesteadConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/HomesteadConfig.java @@ -29,7 +29,7 @@ */ public class HomesteadConfig extends FrontierConfig { - static final BigInteger SECP256K1N_HALF = Constants.getSECP256K1N().divide(BigInteger.valueOf(2)); + public static final BigInteger SECP256K1N_HALF = Constants.getSECP256K1N().divide(BigInteger.valueOf(2)); public static class HomesteadConstants extends FrontierConstants { @Override @@ -68,7 +68,6 @@ public long getTransactionCost(Transaction tx) { @Override public boolean acceptTransactionSignature(Transaction tx) { if (!super.acceptTransactionSignature(tx)) return false; - if (tx.getSignature() == null) return false; return tx.getSignature().s.compareTo(SECP256K1N_HALF) <= 0; } } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Block.java b/ethereumj-core/src/main/java/org/ethereum/core/Block.java index f22768c530..53e8febab5 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Block.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Block.java @@ -386,6 +386,7 @@ public boolean isParentOf(Block block) { } public boolean isGenesis() { + parseRLP(); return this.header.isGenesis(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java index 03322a5acb..16019e8176 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java @@ -76,5 +76,10 @@ public interface Blockchain { List getListOfBodiesByHashes(List hashes); + TransactionExecutor createTransactionExecutor(Transaction transaction, byte[] minerCoinbase, Repository track, + Block currentBlock, long gasUsedInTheBlock); + Block createNewBlock(Block parent, List transactions, List uncles); + + BlockSummary createNewBlockSummary(Block parent, List transactions, List uncles); } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java index 2035d50815..22894691ce 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java @@ -17,7 +17,6 @@ */ package org.ethereum.core; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.ethereum.config.BlockchainConfig; import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; @@ -29,7 +28,6 @@ import org.ethereum.listener.EthereumListener; import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.manager.AdminInfo; -import org.ethereum.sync.SyncManager; import org.ethereum.util.*; import org.ethereum.validator.DependentBlockHeaderRule; import org.ethereum.validator.ParentBlockHeaderValidator; @@ -40,7 +38,6 @@ import org.spongycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; import java.io.BufferedWriter; import java.io.File; @@ -55,9 +52,7 @@ import java.util.Map; import java.util.Set; import java.util.Stack; -import java.util.concurrent.*; -import static java.lang.Math.PI; import static java.lang.Math.max; import static java.lang.Runtime.getRuntime; import static java.math.BigInteger.ONE; @@ -66,7 +61,6 @@ import static org.ethereum.core.Denomination.SZABO; import static org.ethereum.core.ImportResult.*; import static org.ethereum.crypto.HashUtil.sha3; -import static org.ethereum.util.BIUtil.isMoreThan; /** * The Ethereum blockchain is in many ways similar to the Bitcoin blockchain, @@ -97,7 +91,6 @@ * @author Nick Savers * @since 20.05.2014 */ -@Component public class BlockchainImpl implements Blockchain, org.ethereum.facade.Blockchain { @@ -123,13 +116,13 @@ public class BlockchainImpl implements Blockchain, org.ethereum.facade.Blockchai private BigInteger totalDifficulty = ZERO; @Autowired - private EthereumListener listener; + protected EthereumListener listener; @Autowired ProgramInvokeFactory programInvokeFactory; @Autowired - private AdminInfo adminInfo; + protected AdminInfo adminInfo; @Autowired private DependentBlockHeaderRule parentHeaderValidator; @@ -138,13 +131,10 @@ public class BlockchainImpl implements Blockchain, org.ethereum.facade.Blockchai private PendingState pendingState; @Autowired - EventDispatchThread eventDispatchThread; + protected EventDispatchThread eventDispatchThread; @Autowired - CommonConfig commonConfig = CommonConfig.getDefault(); - - @Autowired - SyncManager syncManager; + protected CommonConfig commonConfig = CommonConfig.getDefault(); @Autowired PruneManager pruneManager; @@ -155,7 +145,7 @@ public class BlockchainImpl implements Blockchain, org.ethereum.facade.Blockchai @Autowired DbFlushManager dbFlushManager; - SystemProperties config = SystemProperties.getDefault(); + protected SystemProperties config = SystemProperties.getDefault(); private List altChains = new ArrayList<>(); private List garbage = new ArrayList<>(); @@ -163,7 +153,7 @@ public class BlockchainImpl implements Blockchain, org.ethereum.facade.Blockchai long exitOn = Long.MAX_VALUE; public boolean byTest = false; - private boolean fork = false; + protected boolean fork = false; private byte[] minerCoinbase; private byte[] minerExtraData; @@ -211,13 +201,23 @@ public BlockchainImpl withEthereumListener(EthereumListener listener) { return this; } - public BlockchainImpl withSyncManager(SyncManager syncManager) { - this.syncManager = syncManager; + public BlockchainImpl withParentBlockHeaderValidator(ParentBlockHeaderValidator parentHeaderValidator) { + this.parentHeaderValidator = parentHeaderValidator; return this; } - public BlockchainImpl withParentBlockHeaderValidator(ParentBlockHeaderValidator parentHeaderValidator) { - this.parentHeaderValidator = parentHeaderValidator; + public BlockchainImpl withEventDispatchThread(EventDispatchThread eventDispatchThread) { + this.eventDispatchThread = eventDispatchThread; + return this; + } + + public BlockchainImpl withCommonConfig(CommonConfig commonConfig) { + this.commonConfig = commonConfig; + return this; + } + + public BlockchainImpl withBlockStore(BlockStore blockStore) { + this.blockStore = blockStore; return this; } @@ -340,7 +340,7 @@ public ProgramInvokeFactory getProgramInvokeFactory() { return programInvokeFactory; } - private State pushState(byte[] bestBlockHash) { + protected State pushState(byte[] bestBlockHash) { State push = stateStack.push(new State()); this.bestBlock = blockStore.getBlockByHash(bestBlockHash); totalDifficulty = blockStore.getTotalDifficultyForHash(bestBlockHash); @@ -348,7 +348,7 @@ private State pushState(byte[] bestBlockHash) { return push; } - private void popState() { + protected void popState() { State state = stateStack.pop(); this.repository = repository.getSnapshotTo(state.root); this.bestBlock = state.savedBest; @@ -411,6 +411,12 @@ public synchronized ImportResult tryToConnect(final Block block) { Hex.toHexString(block.getHash()).substring(0, 6), block.getNumber()); + if (blockExists(block)) return EXIST; // retry of well known block + + return tryToConnectImpl(block); + } + + protected boolean blockExists(final Block block) { if (blockStore.getMaxNumber() >= block.getNumber() && blockStore.isBlockExist(block.getHash())) { @@ -419,9 +425,12 @@ public synchronized ImportResult tryToConnect(final Block block) { Hex.toHexString(block.getHash()).substring(0, 6), block.getNumber()); - // retry of well known block - return EXIST; + return true; } + return false; + } + + protected ImportResult tryToConnectImpl(final Block block) { final ImportResult ret; @@ -463,15 +472,23 @@ public synchronized ImportResult tryToConnect(final Block block) { return ret; } - public synchronized Block createNewBlock(Block parent, List txs, List uncles) { + public synchronized BlockSummary createNewBlockSummary(Block parent, List txs, List uncles) { long time = System.currentTimeMillis() / 1000; // adjust time to parent block this may happen due to system clocks difference if (parent.getTimestamp() >= time) time = parent.getTimestamp() + 1; - return createNewBlock(parent, txs, uncles, time); + return createNewBlockSummary(parent, txs, uncles, time); + } + + public synchronized Block createNewBlock(Block parent, List txs, List uncles) { + return createNewBlockSummary(parent, txs, uncles).getBlock(); } public synchronized Block createNewBlock(Block parent, List txs, List uncles, long time) { + return createNewBlockSummary(parent, txs, uncles, time).getBlock(); + } + + public synchronized BlockSummary createNewBlockSummary(Block parent, List txs, List uncles, long time) { final long blockNumber = parent.getNumber() + 1; final byte[] extraData = config.getBlockchainConfig().getConfigForBlock(blockNumber).getExtraData(minerExtraData, blockNumber); @@ -514,7 +531,7 @@ public synchronized Block createNewBlock(Block parent, List txs, Li block.getHeader().setGasUsed(receipts.size() > 0 ? receipts.get(receipts.size() - 1).getCumulativeGasLong() : 0); block.getHeader().setReceiptsRoot(calcReceiptsTrie(receipts)); - return block; + return summary; } @Override @@ -575,64 +592,67 @@ public synchronized BlockSummary addImpl(Repository repo, final Block block) { } BlockSummary summary = processBlock(repo, block); - final List receipts = summary.getReceipts(); // Sanity checks + if(!checkBlockSummary(summary, repo)) { + repo.rollback(); + summary = null; + } + + if (summary != null) { + final List receipts = summary.getReceipts(); + repo.commit(); + updateTotalDifficulty(block); + summary.setTotalDifficulty(getTotalDifficulty()); + + if (!byTest) { + dbFlushManager.commit(() -> { + storeBlock(block, receipts); + repository.commit(); + }); + } else { + storeBlock(block, receipts); + } + } + + return summary; + } + + /** + * Sanity checks of block import result + * @return true if block is good, otherwise false + */ + protected boolean checkBlockSummary(final BlockSummary summary, final Repository track) { + Block block = summary.getBlock(); + final List receipts = summary.getReceipts(); if (!FastByteComparisons.equal(block.getReceiptsRoot(), calcReceiptsTrie(receipts))) { logger.warn("Block's given Receipt Hash doesn't match: {} != {}", Hex.toHexString(block.getReceiptsRoot()), Hex.toHexString(calcReceiptsTrie(receipts))); logger.warn("Calculated receipts: " + receipts); - repo.rollback(); - summary = null; + return false; } if (!FastByteComparisons.equal(block.getLogBloom(), calcLogBloom(receipts))) { logger.warn("Block's given logBloom Hash doesn't match: {} != {}", Hex.toHexString(block.getLogBloom()), Hex.toHexString(calcLogBloom(receipts))); - repo.rollback(); - summary = null; + return false; } - if (!FastByteComparisons.equal(block.getStateRoot(), repo.getRoot())) { + if (!FastByteComparisons.equal(block.getStateRoot(), track.getRoot())) { - stateLogger.warn("BLOCK: State conflict or received invalid block. block: {} worldstate {} mismatch", block.getNumber(), Hex.toHexString(repo.getRoot())); + stateLogger.warn("BLOCK: State conflict or received invalid block. block: {} worldstate {} mismatch", block.getNumber(), Hex.toHexString(track.getRoot())); stateLogger.warn("Conflict block dump: {}", Hex.toHexString(block.getEncoded())); -// track.rollback(); -// repository.rollback(); - repository = repository.getSnapshotTo(origRoot); - - // block is bad so 'rollback' the state root to the original state -// ((RepositoryImpl) repository).setRoot(origRoot); - -// track.rollback(); - // block is bad so 'rollback' the state root to the original state -// ((RepositoryImpl) repository).setRoot(origRoot); - if (config.exitOnBlockConflict() && !byTest) { + track.rollback(); adminInfo.lostConsensus(); System.out.println("CONFLICT: BLOCK #" + block.getNumber() + ", dump: " + Hex.toHexString(block.getEncoded())); System.exit(1); } else { - summary = null; - } - } - - if (summary != null) { - repo.commit(); - updateTotalDifficulty(block); - summary.setTotalDifficulty(getTotalDifficulty()); - - if (!byTest) { - dbFlushManager.commit(() -> { - storeBlock(block, receipts); - repository.commit(); - }); - } else { - storeBlock(block, receipts); + return false; } } - return summary; + return true; // Everything is good! } @Override @@ -708,7 +728,7 @@ public boolean isValid(BlockHeader header) { * likely next period. Conversely, if the period is too large, the difficulty, * and expected time to the next block, is reduced. */ - private boolean isValid(Repository repo, Block block) { + protected boolean isValid(Repository repo, Block block) { boolean isValid = true; @@ -850,7 +870,7 @@ private BlockSummary processBlock(Repository track, Block block) { } } - private BlockSummary applyBlock(Repository track, Block block) { + protected BlockSummary applyBlock(Repository track, Block block) { logger.debug("applyBlock: block: [{}] tx.list: [{}]", block.getNumber(), block.getTransactionsList().size()); @@ -867,9 +887,8 @@ private BlockSummary applyBlock(Repository track, Block block) { stateLogger.debug("apply block: [{}] tx: [{}] ", block.getNumber(), i); Repository txTrack = track.startTracking(); - TransactionExecutor executor = new TransactionExecutor(tx, block.getCoinbase(), - txTrack, blockStore, programInvokeFactory, block, listener, totalGasUsed) - .withCommonConfig(commonConfig); + TransactionExecutor executor = createTransactionExecutor(tx, block.getCoinbase(), + txTrack, block, totalGasUsed); executor.init(); executor.execute(); @@ -923,13 +942,20 @@ private BlockSummary applyBlock(Repository track, Block block) { return new BlockSummary(block, rewards, receipts, summaries); } + public TransactionExecutor createTransactionExecutor(Transaction transaction, byte[] minerCoinbase, Repository track, + Block currentBlock, long gasUsedInTheBlock) { + return new CommonTransactionExecutor(transaction, minerCoinbase, + track, blockStore, programInvokeFactory, currentBlock, listener, gasUsedInTheBlock) + .withCommonConfig(commonConfig); + } + /** * Add reward to block- and every uncle coinbase * assuming the entire block is valid. * * @param block object containing the header and uncles */ - private Map addReward(Repository track, Block block, List summaries) { + protected Map addReward(Repository track, Block block, List summaries) { Map rewards = new HashMap<>(); @@ -1046,7 +1072,7 @@ public void setTotalDifficulty(BigInteger totalDifficulty) { this.totalDifficulty = totalDifficulty; } - private void recordBlock(Block block) { + protected void recordBlock(Block block) { if (!config.recordBlocks()) return; @@ -1285,7 +1311,7 @@ public List getListOfBodiesByHashes(List hashes) { return bodies; } - private class State { + protected class State { // Repository savedRepo = repository; byte[] root = repository.getRoot(); Block savedBest = bestBlock; diff --git a/ethereumj-core/src/main/java/org/ethereum/core/CallTransaction.java b/ethereumj-core/src/main/java/org/ethereum/core/CallTransaction.java index 260efb8e1b..c93277a3f1 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/CallTransaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/CallTransaction.java @@ -22,9 +22,11 @@ import static org.apache.commons.lang3.StringUtils.stripEnd; import static org.ethereum.crypto.HashUtil.sha3; import static org.ethereum.solidity.SolidityType.IntType; +import static org.ethereum.util.ByteUtil.hexStringToBytes; import static org.ethereum.util.ByteUtil.longToBytesNoLeadZeroes; import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -38,7 +40,6 @@ import org.ethereum.util.ByteUtil; import org.ethereum.util.FastByteComparisons; import org.ethereum.vm.LogInfo; -import org.spongycastle.util.encoders.Hex; /** * Creates a contract function call transaction. @@ -57,7 +58,7 @@ public static Transaction createRawTransaction(long nonce, long gasPrice, long g Transaction tx = new Transaction(longToBytesNoLeadZeroes(nonce), longToBytesNoLeadZeroes(gasPrice), longToBytesNoLeadZeroes(gasLimit), - toAddress == null ? null : Hex.decode(toAddress), + toAddress == null ? null : hexStringToBytes(toAddress), longToBytesNoLeadZeroes(value), data, null); @@ -100,6 +101,7 @@ public enum FunctionType { fallback } + @JsonIgnoreProperties(ignoreUnknown = true) public static class Function { public boolean anonymous; public boolean constant; diff --git a/ethereumj-core/src/main/java/org/ethereum/core/CommonTransactionExecutor.java b/ethereumj-core/src/main/java/org/ethereum/core/CommonTransactionExecutor.java new file mode 100644 index 0000000000..864b0d03f1 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/core/CommonTransactionExecutor.java @@ -0,0 +1,530 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.core; + +import org.apache.commons.lang3.tuple.Pair; +import org.ethereum.config.BlockchainConfig; +import org.ethereum.config.CommonConfig; +import org.ethereum.config.SystemProperties; +import org.ethereum.db.BlockStore; +import org.ethereum.db.ContractDetails; +import org.ethereum.listener.EthereumListener; +import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.util.ByteArraySet; +import org.ethereum.vm.DataWord; +import org.ethereum.vm.LogInfo; +import org.ethereum.vm.PrecompiledContracts; +import org.ethereum.vm.VM; +import org.ethereum.vm.program.Program; +import org.ethereum.vm.program.ProgramResult; +import org.ethereum.vm.program.invoke.ProgramInvoke; +import org.ethereum.vm.program.invoke.ProgramInvokeFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; + +import java.util.List; + +import static org.apache.commons.lang3.ArrayUtils.getLength; +import static org.apache.commons.lang3.ArrayUtils.isEmpty; +import static org.ethereum.util.BIUtil.isCovers; +import static org.ethereum.util.BIUtil.isNotEqual; +import static org.ethereum.util.BIUtil.toBI; +import static org.ethereum.util.BIUtil.transfer; +import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; +import static org.ethereum.util.ByteUtil.toHexString; +import static org.ethereum.vm.VMUtils.saveProgramTraceFile; +import static org.ethereum.vm.VMUtils.zipAndEncode; + +/** + * @author Roman Mandeleil + * @since 19.12.2014 + */ +public class CommonTransactionExecutor implements TransactionExecutor { + + protected static final Logger logger = LoggerFactory.getLogger("execute"); + private static final Logger stateLogger = LoggerFactory.getLogger("state"); + + protected SystemProperties config; + protected CommonConfig commonConfig; + BlockchainConfig blockchainConfig; + + protected Transaction tx; + protected Repository track; + private Repository cacheTrack; + private BlockStore blockStore; + private final long gasUsedInTheBlock; + protected boolean readyToExecute = false; + protected String execError; + + private ProgramInvokeFactory programInvokeFactory; + protected byte[] coinbase; + + private TransactionReceipt receipt; + private ProgramResult result = new ProgramResult(); + private Block currentBlock; + + private final EthereumListener listener; + + private VM vm; + private Program program; + + PrecompiledContracts.PrecompiledContract precompiledContract; + + BigInteger m_endGas = BigInteger.ZERO; + long basicTxCost = 0; + List logs = null; + + protected ByteArraySet touchedAccounts = new ByteArraySet(); + + protected boolean localCall = false; + + public CommonTransactionExecutor(Transaction tx, byte[] coinbase, Repository track, BlockStore blockStore, + ProgramInvokeFactory programInvokeFactory, Block currentBlock) { + + this(tx, coinbase, track, blockStore, programInvokeFactory, currentBlock, new EthereumListenerAdapter(), 0); + } + + public CommonTransactionExecutor(Transaction tx, byte[] coinbase, Repository track, BlockStore blockStore, + ProgramInvokeFactory programInvokeFactory, Block currentBlock, + EthereumListener listener, long gasUsedInTheBlock) { + + this.tx = tx; + this.coinbase = coinbase; + this.track = track; + this.cacheTrack = track.startTracking(); + this.blockStore = blockStore; + this.programInvokeFactory = programInvokeFactory; + this.currentBlock = currentBlock; + this.listener = listener; + this.gasUsedInTheBlock = gasUsedInTheBlock; + this.m_endGas = toBI(tx.getGasLimit()); + withCommonConfig(CommonConfig.getDefault()); + } + + @Override + public TransactionExecutor withCommonConfig(CommonConfig commonConfig) { + this.commonConfig = commonConfig; + this.config = commonConfig.systemProperties(); + this.blockchainConfig = config.getBlockchainConfig().getConfigForBlock(currentBlock.getNumber()); + return this; + } + + protected void execError(String err) { + logger.warn(err); + execError = err; + } + + /** + * Do all the basic validation, if the executor + * will be ready to run the transaction at the end + * set readyToExecute = true + */ + @Override + public void init() { + basicTxCost = tx.transactionCost(config.getBlockchainConfig(), currentBlock); + + if (localCall) { + readyToExecute = true; + return; + } + + BigInteger txGasLimit = new BigInteger(1, tx.getGasLimit()); + BigInteger curBlockGasLimit = new BigInteger(1, currentBlock.getGasLimit()); + + boolean cumulativeGasReached = txGasLimit.add(BigInteger.valueOf(gasUsedInTheBlock)).compareTo(curBlockGasLimit) > 0; + if (cumulativeGasReached) { + + execError(String.format("Too much gas used in this block: Require: %s Got: %s", new BigInteger(1, currentBlock.getGasLimit()).longValue() - toBI(tx.getGasLimit()).longValue(), toBI(tx.getGasLimit()).longValue())); + + return; + } + + if (txGasLimit.compareTo(BigInteger.valueOf(basicTxCost)) < 0) { + + execError(String.format("Not enough gas for transaction execution: Require: %s Got: %s", basicTxCost, txGasLimit)); + + return; + } + + BigInteger reqNonce = track.getNonce(tx.getSender()); + BigInteger txNonce = toBI(tx.getNonce()); + if (isNotEqual(reqNonce, txNonce)) { + execError(String.format("Invalid nonce: required: %s , tx.nonce: %s", reqNonce, txNonce)); + + return; + } + + BigInteger txGasCost = toBI(tx.getGasPrice()).multiply(txGasLimit); + BigInteger totalCost = toBI(tx.getValue()).add(txGasCost); + BigInteger senderBalance = track.getBalance(tx.getSender()); + + if (!isCovers(senderBalance, totalCost)) { + + execError(String.format("Not enough cash: Require: %s, Sender cash: %s", totalCost, senderBalance)); + + return; + } + + if (!isSignatureValid()) { + execError("Transaction signature not accepted: " + tx.getSignature()); + return; + } + + readyToExecute = true; + } + + protected boolean isSignatureValid() { + return blockchainConfig.acceptTransactionSignature(tx); + } + + @Override + public void execute() { + + if (!readyToExecute) return; + + if (!localCall) { + track.increaseNonce(tx.getSender()); + + BigInteger txGasLimit = toBI(tx.getGasLimit()); + BigInteger txGasCost = toBI(tx.getGasPrice()).multiply(txGasLimit); + track.addBalance(tx.getSender(), txGasCost.negate()); + + if (logger.isInfoEnabled()) + logger.info("Paying: txGasCost: [{}], gasPrice: [{}], gasLimit: [{}]", txGasCost, toBI(tx.getGasPrice()), txGasLimit); + } + + if (tx.isContractCreation()) { + create(); + } else { + call(); + } + } + + protected void call() { + if (!readyToExecute) return; + + byte[] targetAddress = tx.getReceiveAddress(); + precompiledContract = PrecompiledContracts.getContractForAddress(new DataWord(targetAddress), blockchainConfig); + + if (precompiledContract != null) { + long requiredGas = precompiledContract.getGasForData(tx.getData()); + + BigInteger spendingGas = BigInteger.valueOf(requiredGas).add(BigInteger.valueOf(basicTxCost)); + + if (!localCall && m_endGas.compareTo(spendingGas) < 0) { + // no refund + // no endowment + execError("Out of Gas calling precompiled contract 0x" + Hex.toHexString(targetAddress) + + ", required: " + spendingGas + ", left: " + m_endGas); + m_endGas = BigInteger.ZERO; + return; + } else { + + m_endGas = m_endGas.subtract(spendingGas); + + // FIXME: save return for vm trace + Pair out = precompiledContract.execute(tx.getData()); + + if (!out.getLeft()) { + execError("Error executing precompiled contract 0x" + Hex.toHexString(targetAddress)); + m_endGas = BigInteger.ZERO; + return; + } + } + + } else { + + byte[] code = track.getCode(targetAddress); + if (isEmpty(code)) { + m_endGas = m_endGas.subtract(BigInteger.valueOf(basicTxCost)); + result.spendGas(basicTxCost); + } else { + ProgramInvoke programInvoke = + programInvokeFactory.createProgramInvoke(tx, currentBlock, cacheTrack, blockStore); + + this.vm = new VM(config); + this.program = new Program(track.getCodeHash(targetAddress), code, programInvoke, tx, config).withCommonConfig(commonConfig); + } + } + + BigInteger endowment = toBI(tx.getValue()); + transfer(cacheTrack, tx.getSender(), targetAddress, endowment); + + touchedAccounts.add(targetAddress); + } + + protected void create() { + byte[] newContractAddress = tx.getContractAddress(); + + AccountState existingAddr = cacheTrack.getAccountState(newContractAddress); + if (existingAddr != null && existingAddr.isContractExist(blockchainConfig)) { + execError("Trying to create a contract with existing contract address: 0x" + Hex.toHexString(newContractAddress)); + m_endGas = BigInteger.ZERO; + return; + } + + //In case of hashing collisions (for TCK tests only), check for any balance before createAccount() + BigInteger oldBalance = track.getBalance(newContractAddress); + cacheTrack.createAccount(tx.getContractAddress()); + cacheTrack.addBalance(newContractAddress, oldBalance); + if (blockchainConfig.eip161()) { + cacheTrack.increaseNonce(newContractAddress); + } + + if (isEmpty(tx.getData())) { + m_endGas = m_endGas.subtract(BigInteger.valueOf(basicTxCost)); + result.spendGas(basicTxCost); + } else { + ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(tx, currentBlock, cacheTrack, blockStore); + + this.vm = new VM(config); + this.program = new Program(tx.getData(), programInvoke, tx, config).withCommonConfig(commonConfig); + + // reset storage if the contract with the same address already exists + // TCK test case only - normally this is near-impossible situation in the real network + // TODO make via Trie.clear() without keyset +// ContractDetails contractDetails = program.getStorage().getContractDetails(newContractAddress); +// for (DataWord key : contractDetails.getStorageKeys()) { +// program.storageSave(key, DataWord.ZERO); +// } + } + + BigInteger endowment = toBI(tx.getValue()); + transfer(cacheTrack, tx.getSender(), newContractAddress, endowment); + + touchedAccounts.add(newContractAddress); + } + + @Override + public void go() { + if (!readyToExecute) return; + + try { + + if (vm != null) { + + // Charge basic cost of the transaction + program.spendGas(tx.transactionCost(config.getBlockchainConfig(), currentBlock), "TRANSACTION COST"); + + if (config.playVM()) + vm.play(program); + + result = program.getResult(); + m_endGas = toBI(tx.getGasLimit()).subtract(toBI(program.getResult().getGasUsed())); + + if (tx.isContractCreation() && !result.isRevert()) { + int returnDataGasValue = getLength(program.getResult().getHReturn()) * + blockchainConfig.getGasCost().getCREATE_DATA(); + if (m_endGas.compareTo(BigInteger.valueOf(returnDataGasValue)) < 0) { + // Not enough gas to return contract code + if (!blockchainConfig.getConstants().createEmptyContractOnOOG()) { + program.setRuntimeFailure(Program.Exception.notEnoughSpendingGas("No gas to return just created contract", + returnDataGasValue, program)); + result = program.getResult(); + } + result.setHReturn(EMPTY_BYTE_ARRAY); + } else if (getLength(result.getHReturn()) > blockchainConfig.getConstants().getMAX_CONTRACT_SZIE()) { + // Contract size too large + program.setRuntimeFailure(Program.Exception.notEnoughSpendingGas("Contract size too large: " + getLength(result.getHReturn()), + returnDataGasValue, program)); + result = program.getResult(); + result.setHReturn(EMPTY_BYTE_ARRAY); + } else { + // Contract successfully created + m_endGas = m_endGas.subtract(BigInteger.valueOf(returnDataGasValue)); + cacheTrack.saveCode(tx.getContractAddress(), result.getHReturn()); + } + } + + String err = config.getBlockchainConfig().getConfigForBlock(currentBlock.getNumber()). + validateTransactionChanges(blockStore, currentBlock, tx, null); + if (err != null) { + program.setRuntimeFailure(new RuntimeException("Transaction changes validation failed: " + err)); + } + + + if (result.getException() != null || result.isRevert()) { + result.getDeleteAccounts().clear(); + result.getLogInfoList().clear(); + result.resetFutureRefund(); + rollback(); + + if (result.getException() != null) { + throw result.getException(); + } else { + execError("REVERT opcode executed"); + } + } else { + touchedAccounts.addAll(result.getTouchedAccounts()); + cacheTrack.commit(); + } + + } else { + cacheTrack.commit(); + } + + } catch (Throwable e) { + + // TODO: catch whatever they will throw on you !!! +// https://github.com/ethereum/cpp-ethereum/blob/develop/libethereum/Executive.cpp#L241 + rollback(); + m_endGas = BigInteger.ZERO; + execError(e.getMessage()); + } + } + + private void rollback() { + + cacheTrack.rollback(); + + // remove touched account + touchedAccounts.remove( + tx.isContractCreation() ? tx.getContractAddress() : tx.getReceiveAddress()); + } + + @Override + public TransactionExecutionSummary finalization() { + if (!readyToExecute) return null; + + TransactionExecutionSummary.Builder summaryBuilder = TransactionExecutionSummary.builderFor(tx) + .gasLeftover(m_endGas) + .logs(result.getLogInfoList()) + .result(result.getHReturn()); + + if (result != null) { + // Accumulate refunds for suicides + result.addFutureRefund(result.getDeleteAccounts().size() * config.getBlockchainConfig(). + getConfigForBlock(currentBlock.getNumber()).getGasCost().getSUICIDE_REFUND()); + long gasRefund = Math.min(result.getFutureRefund(), getGasUsed() / 2); + byte[] addr = tx.isContractCreation() ? tx.getContractAddress() : tx.getReceiveAddress(); + m_endGas = m_endGas.add(BigInteger.valueOf(gasRefund)); + + summaryBuilder + .gasUsed(toBI(result.getGasUsed())) + .gasRefund(toBI(gasRefund)) + .deletedAccounts(result.getDeleteAccounts()) + .internalTransactions(result.getInternalTransactions()); + + ContractDetails contractDetails = track.getContractDetails(addr); + if (contractDetails != null) { + // TODO +// summaryBuilder.storageDiff(track.getContractDetails(addr).getStorage()); +// +// if (program != null) { +// summaryBuilder.touchedStorage(contractDetails.getStorage(), program.getStorageDiff()); +// } + } + + if (result.getException() != null) { + summaryBuilder.markAsFailed(); + } + } + + TransactionExecutionSummary summary = summaryBuilder.build(); + + // Refund for gas leftover + track.addBalance(tx.getSender(), summary.getLeftover().add(summary.getRefund())); + logger.info("Pay total refund to sender: [{}], refund val: [{}]", Hex.toHexString(tx.getSender()), summary.getRefund()); + + payRewards(summary); + + if (result != null) { + logs = result.getLogInfoList(); + // Traverse list of suicides + for (DataWord address : result.getDeleteAccounts()) { + track.delete(address.getLast20Bytes()); + } + } + + if (blockchainConfig.eip161()) { + for (byte[] acctAddr : touchedAccounts) { + AccountState state = track.getAccountState(acctAddr); + if (state != null && state.isEmpty()) { + track.delete(acctAddr); + } + } + } + + + listener.onTransactionExecuted(summary); + + if (config.vmTrace() && program != null && result != null) { + String trace = program.getTrace() + .result(result.getHReturn()) + .error(result.getException()) + .toString(); + + + if (config.vmTraceCompressed()) { + trace = zipAndEncode(trace); + } + + String txHash = toHexString(tx.getHash()); + saveProgramTraceFile(config, txHash, trace); + listener.onVMTraceCreated(txHash, trace); + } + return summary; + } + + protected void payRewards(final TransactionExecutionSummary summary) { + // Transfer fees to miner + track.addBalance(coinbase, summary.getFee()); + touchedAccounts.add(coinbase); + logger.info("Pay fees to miner: [{}], feesEarned: [{}]", Hex.toHexString(coinbase), summary.getFee()); + } + + @Override + public TransactionExecutor setLocalCall(boolean localCall) { + this.localCall = localCall; + return this; + } + + @Override + public TransactionReceipt getReceipt() { + if (receipt == null) { + receipt = new TransactionReceipt(); + long totalGasUsed = gasUsedInTheBlock + getGasUsed(); + receipt.setCumulativeGas(totalGasUsed); + receipt.setTransaction(tx); + receipt.setLogInfoList(getVMLogs()); + receipt.setGasUsed(getGasUsed()); + receipt.setExecutionResult(getResult().getHReturn()); + receipt.setError(execError); +// receipt.setPostTxState(track.getRoot()); // TODO later when RepositoryTrack.getRoot() is implemented + } + return receipt; + } + + @Override + public List getVMLogs() { + return logs; + } + + @Override + public ProgramResult getResult() { + return result; + } + + @Override + public long getGasUsed() { + return toBI(tx.getGasLimit()).subtract(m_endGas).longValue(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/core/PendingState.java b/ethereumj-core/src/main/java/org/ethereum/core/PendingState.java index 96297e5031..73e54c6025 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/PendingState.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/PendingState.java @@ -18,6 +18,7 @@ package org.ethereum.core; import java.util.List; +import java.util.function.Consumer; /** * @author Mikhail Kalinin @@ -43,6 +44,16 @@ public interface PendingState extends org.ethereum.facade.PendingState { */ void addPendingTransaction(Transaction tx); + /** + * Adds transaction to the list of pending state txs
+ * For the moment this list is populated with txs sent by our peer only
+ * Triggers an update of pending state + * + * @param tx transaction + * @param errorConsumer Fires if tx execution failed on current state in PendingState + */ + void addPendingTransaction(Transaction tx, Consumer errorConsumer); + /** * It should be called on each block imported as BEST
* Does several things: diff --git a/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java b/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java index a29556da4b..dd1495d2c5 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java @@ -24,10 +24,10 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.TreeSet; +import java.util.function.Consumer; import org.apache.commons.collections4.map.LRUMap; import org.ethereum.config.CommonConfig; @@ -45,7 +45,6 @@ import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; /** * Keeps logic providing pending state management @@ -53,7 +52,6 @@ * @author Mikhail Kalinin * @since 28.09.2015 */ -@Component public class PendingStateImpl implements PendingState { public static class TransactionSortedSet extends TreeSet { @@ -72,25 +70,21 @@ public TransactionSortedSet() { private static final Logger logger = LoggerFactory.getLogger("pending"); @Autowired - private SystemProperties config = SystemProperties.getDefault(); + protected SystemProperties config = SystemProperties.getDefault(); @Autowired - CommonConfig commonConfig = CommonConfig.getDefault(); + protected CommonConfig commonConfig = CommonConfig.getDefault(); - @Autowired private EthereumListener listener; @Autowired - private BlockchainImpl blockchain; + private Blockchain blockchain; - @Autowired - private BlockStore blockStore; + protected BlockStore blockStore; - @Autowired private TransactionStore transactionStore; - @Autowired - private ProgramInvokeFactory programInvokeFactory; + protected ProgramInvokeFactory programInvokeFactory; // private Repository repository; @@ -106,13 +100,9 @@ public TransactionSortedSet() { private Block best = null; @Autowired - public PendingStateImpl(final EthereumListener listener, final BlockchainImpl blockchain) { + public PendingStateImpl(final EthereumListener listener) { this.listener = listener; - this.blockchain = blockchain; // this.repository = blockchain.getRepository(); - this.blockStore = blockchain.getBlockStore(); - this.programInvokeFactory = blockchain.getProgramInvokeFactory(); - this.transactionStore = blockchain.getTransactionStore(); } public void init() { @@ -120,7 +110,7 @@ public void init() { } private Repository getOrigRepository() { - return blockchain.getRepositorySnapshot(); + return ((BlockchainImpl) blockchain).getRepositorySnapshot(); } @Override @@ -159,6 +149,29 @@ public void addPendingTransaction(Transaction tx) { addPendingTransactions(Collections.singletonList(tx)); } + @Override + public void addPendingTransaction(Transaction tx, Consumer errorConsumer) { + int unknownTx = 0; + List newPending = new ArrayList<>(); + if (addNewTxIfNotExist(tx)) { + unknownTx++; + TransactionReceipt receipt = addPendingTransactionImpl(tx); + if (receiptIsValid(receipt)) { + newPending.add(tx); + } else { + errorConsumer.accept(new Throwable("Tx execution simulation failed: " + receipt.getError())); + } + } + + logger.debug("Wire transaction list added: total: {}, new: {}, valid (added to pending): {} (current #of known txs: {})", + 1, unknownTx, newPending, receivedTxs.size()); + + if (!newPending.isEmpty()) { + listener.onPendingTransactionsReceived(newPending); + listener.onPendingStateChanged(PendingStateImpl.this); + } + } + @Override public synchronized List addPendingTransactions(List transactions) { int unknownTx = 0; @@ -166,7 +179,7 @@ public synchronized List addPendingTransactions(List t for (Transaction tx : transactions) { if (addNewTxIfNotExist(tx)) { unknownTx++; - if (addPendingTransactionImpl(tx)) { + if (receiptIsValid(addPendingTransactionImpl(tx))) { newPending.add(tx); } } @@ -183,6 +196,10 @@ public synchronized List addPendingTransactions(List t return newPending; } + protected boolean receiptIsValid(TransactionReceipt receipt) { + return receipt.isValid(); + } + public synchronized void trackTransaction(Transaction tx) { List infos = transactionStore.get(tx.getHash()); if (!infos.isEmpty()) { @@ -214,9 +231,9 @@ private void fireTxUpdate(TransactionReceipt txReceipt, PendingTransactionState * Executes pending tx on the latest best block * Fires pending state update * @param tx Transaction - * @return True if transaction gets NEW_PENDING state, False if DROPPED + * @return execution receipt */ - private boolean addPendingTransactionImpl(final Transaction tx) { + private TransactionReceipt addPendingTransactionImpl(final Transaction tx) { TransactionReceipt newReceipt = new TransactionReceipt(); newReceipt.setTransaction(tx); @@ -229,13 +246,13 @@ private boolean addPendingTransactionImpl(final Transaction tx) { txReceipt = executeTx(tx); } - if (!txReceipt.isValid()) { + if (!receiptIsValid(txReceipt)) { fireTxUpdate(txReceipt, DROPPED, getBestBlock()); } else { pendingTransactions.add(new PendingTransaction(tx, getBestBlock().getNumber())); fireTxUpdate(txReceipt, NEW_PENDING, getBestBlock()); } - return txReceipt.isValid(); + return txReceipt; } private TransactionReceipt createDroppedReceipt(Transaction tx, String error) { @@ -246,7 +263,7 @@ private TransactionReceipt createDroppedReceipt(Transaction tx, String error) { } // validations which are not performed within executeTx - private String validate(Transaction tx) { + protected String validate(Transaction tx) { try { tx.verify(); } catch (Exception e) { @@ -411,11 +428,8 @@ private TransactionReceipt executeTx(Transaction tx) { logger.trace("Apply pending state tx: {}", Hex.toHexString(tx.getHash())); Block best = getBestBlock(); - - TransactionExecutor executor = new TransactionExecutor( - tx, best.getCoinbase(), getRepository(), - blockStore, programInvokeFactory, createFakePendingBlock(), new EthereumListenerAdapter(), 0) - .withCommonConfig(commonConfig); + TransactionExecutor executor = createTransactionExecutor(tx, best.getCoinbase(), getRepository(), + createFakePendingBlock()); executor.init(); executor.execute(); @@ -425,6 +439,13 @@ blockStore, programInvokeFactory, createFakePendingBlock(), new EthereumListener return executor.getReceipt(); } + protected TransactionExecutor createTransactionExecutor(Transaction transaction, byte[] minerCoinbase, + Repository track, Block currentBlock) { + return new CommonTransactionExecutor(transaction, minerCoinbase, + track, blockStore, programInvokeFactory, currentBlock, new EthereumListenerAdapter(), 0) + .withCommonConfig(commonConfig); + } + private Block createFakePendingBlock() { // creating fake lightweight calculated block with no hashes calculations Block block = new Block(best.getHash(), @@ -447,7 +468,15 @@ private Block createFakePendingBlock() { return block; } + @Autowired public void setBlockchain(BlockchainImpl blockchain) { this.blockchain = blockchain; + this.blockStore = blockchain.getBlockStore(); + this.programInvokeFactory = blockchain.getProgramInvokeFactory(); + this.transactionStore = blockchain.getTransactionStore(); + } + + public void setCommonConfig(CommonConfig commonConfig) { + this.commonConfig = commonConfig; } } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java index 6789ffac0e..59e84f11b1 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java @@ -55,6 +55,7 @@ public class Transaction { private static final Logger logger = LoggerFactory.getLogger(Transaction.class); private static final BigInteger DEFAULT_GAS_PRICE = new BigInteger("10000000000000"); private static final BigInteger DEFAULT_BALANCE_GAS = new BigInteger("21000"); + public static final byte[] NULL_SENDER = Hex.decode("ffffffffffffffffffffffffffffffffffffffff"); public static final int HASH_LENGTH = 32; public static final int ADDRESS_LENGTH = 20; @@ -159,7 +160,8 @@ public Transaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiv } - private Integer extractChainIdFromV(BigInteger bv) { + private Integer extractChainIdFromRawSignature(BigInteger bv, byte[] r, byte[] s) { + if (r == null && s == null) return bv.intValue(); // EIP 86 if (bv.bitLength() > 31) return Integer.MAX_VALUE; // chainId is limited to 31 bits, longer are not valid for now long v = bv.longValue(); if (v == LOWER_REAL_V || v == (LOWER_REAL_V + 1)) return null; @@ -212,10 +214,12 @@ public synchronized void rlpParse() { if (transaction.get(6).getRLPData() != null) { byte[] vData = transaction.get(6).getRLPData(); BigInteger v = ByteUtil.bytesToBigInteger(vData); - this.chainId = extractChainIdFromV(v); byte[] r = transaction.get(7).getRLPData(); byte[] s = transaction.get(8).getRLPData(); - this.signature = ECDSASignature.fromComponents(r, s, getRealV(v)); + this.chainId = extractChainIdFromRawSignature(v, r, s); + if (r != null && s != null) { + this.signature = ECDSASignature.fromComponents(r, s, getRealV(v)); + } } else { logger.debug("RLP encoded tx is not signed!"); } @@ -380,7 +384,8 @@ public synchronized byte[] getSender() { if (sendAddress == null && getSignature() != null) { sendAddress = ECKey.signatureToAddress(getRawHash(), getSignature()); } - return sendAddress; + // TODO: check for side-effects + return sendAddress == null ? NULL_SENDER : sendAddress; } catch (SignatureException e) { logger.error(e.getMessage(), e); } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java index 214dc9f2b3..3416d54db6 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java @@ -1,507 +1,75 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ package org.ethereum.core; -import org.apache.commons.lang3.tuple.Pair; -import org.ethereum.config.BlockchainConfig; import org.ethereum.config.CommonConfig; -import org.ethereum.config.SystemProperties; -import org.ethereum.db.BlockStore; -import org.ethereum.db.ContractDetails; -import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; -import org.ethereum.util.ByteArraySet; -import org.ethereum.vm.*; -import org.ethereum.vm.program.Program; +import org.ethereum.vm.LogInfo; import org.ethereum.vm.program.ProgramResult; -import org.ethereum.vm.program.invoke.ProgramInvoke; -import org.ethereum.vm.program.invoke.ProgramInvokeFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.spongycastle.util.encoders.Hex; -import java.math.BigInteger; import java.util.List; -import static org.apache.commons.lang3.ArrayUtils.getLength; -import static org.apache.commons.lang3.ArrayUtils.isEmpty; -import static org.ethereum.util.BIUtil.*; -import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; -import static org.ethereum.util.ByteUtil.toHexString; -import static org.ethereum.vm.VMUtils.saveProgramTraceFile; -import static org.ethereum.vm.VMUtils.zipAndEncode; - /** - * @author Roman Mandeleil - * @since 19.12.2014 + * Executes transaction using Ethereum VM + * Usual usage looks like this: + * 1. init() + * 2. execute() + * 3. go() + * 4. finalization() <- get Summary here + * After that all results of transaction execution + * could be obtained using all other methods */ -public class TransactionExecutor { - - private static final Logger logger = LoggerFactory.getLogger("execute"); - private static final Logger stateLogger = LoggerFactory.getLogger("state"); - - SystemProperties config; - CommonConfig commonConfig; - BlockchainConfig blockchainConfig; - - private Transaction tx; - private Repository track; - private Repository cacheTrack; - private BlockStore blockStore; - private final long gasUsedInTheBlock; - private boolean readyToExecute = false; - private String execError; - - private ProgramInvokeFactory programInvokeFactory; - private byte[] coinbase; - - private TransactionReceipt receipt; - private ProgramResult result = new ProgramResult(); - private Block currentBlock; - - private final EthereumListener listener; - - private VM vm; - private Program program; - - PrecompiledContracts.PrecompiledContract precompiledContract; - - BigInteger m_endGas = BigInteger.ZERO; - long basicTxCost = 0; - List logs = null; - - private ByteArraySet touchedAccounts = new ByteArraySet(); - - boolean localCall = false; - - public TransactionExecutor(Transaction tx, byte[] coinbase, Repository track, BlockStore blockStore, - ProgramInvokeFactory programInvokeFactory, Block currentBlock) { - - this(tx, coinbase, track, blockStore, programInvokeFactory, currentBlock, new EthereumListenerAdapter(), 0); - } - - public TransactionExecutor(Transaction tx, byte[] coinbase, Repository track, BlockStore blockStore, - ProgramInvokeFactory programInvokeFactory, Block currentBlock, - EthereumListener listener, long gasUsedInTheBlock) { - - this.tx = tx; - this.coinbase = coinbase; - this.track = track; - this.cacheTrack = track.startTracking(); - this.blockStore = blockStore; - this.programInvokeFactory = programInvokeFactory; - this.currentBlock = currentBlock; - this.listener = listener; - this.gasUsedInTheBlock = gasUsedInTheBlock; - this.m_endGas = toBI(tx.getGasLimit()); - withCommonConfig(CommonConfig.getDefault()); - } - - public TransactionExecutor withCommonConfig(CommonConfig commonConfig) { - this.commonConfig = commonConfig; - this.config = commonConfig.systemProperties(); - this.blockchainConfig = config.getBlockchainConfig().getConfigForBlock(currentBlock.getNumber()); - return this; - } - - private void execError(String err) { - logger.warn(err); - execError = err; - } +public interface TransactionExecutor { + TransactionExecutor withCommonConfig(CommonConfig commonConfig); /** * Do all the basic validation, if the executor * will be ready to run the transaction at the end * set readyToExecute = true */ - public void init() { - basicTxCost = tx.transactionCost(config.getBlockchainConfig(), currentBlock); - - if (localCall) { - readyToExecute = true; - return; - } - - BigInteger txGasLimit = new BigInteger(1, tx.getGasLimit()); - BigInteger curBlockGasLimit = new BigInteger(1, currentBlock.getGasLimit()); - - boolean cumulativeGasReached = txGasLimit.add(BigInteger.valueOf(gasUsedInTheBlock)).compareTo(curBlockGasLimit) > 0; - if (cumulativeGasReached) { - - execError(String.format("Too much gas used in this block: Require: %s Got: %s", new BigInteger(1, currentBlock.getGasLimit()).longValue() - toBI(tx.getGasLimit()).longValue(), toBI(tx.getGasLimit()).longValue())); - - return; - } - - if (txGasLimit.compareTo(BigInteger.valueOf(basicTxCost)) < 0) { - - execError(String.format("Not enough gas for transaction execution: Require: %s Got: %s", basicTxCost, txGasLimit)); - - return; - } - - BigInteger reqNonce = track.getNonce(tx.getSender()); - BigInteger txNonce = toBI(tx.getNonce()); - if (isNotEqual(reqNonce, txNonce)) { - execError(String.format("Invalid nonce: required: %s , tx.nonce: %s", reqNonce, txNonce)); - - return; - } - - BigInteger txGasCost = toBI(tx.getGasPrice()).multiply(txGasLimit); - BigInteger totalCost = toBI(tx.getValue()).add(txGasCost); - BigInteger senderBalance = track.getBalance(tx.getSender()); - - if (!isCovers(senderBalance, totalCost)) { - - execError(String.format("Not enough cash: Require: %s, Sender cash: %s", totalCost, senderBalance)); - - return; - } - - if (!blockchainConfig.acceptTransactionSignature(tx)) { - execError("Transaction signature not accepted: " + tx.getSignature()); - return; - } - - readyToExecute = true; - } - - public void execute() { - - if (!readyToExecute) return; - - if (!localCall) { - track.increaseNonce(tx.getSender()); - - BigInteger txGasLimit = toBI(tx.getGasLimit()); - BigInteger txGasCost = toBI(tx.getGasPrice()).multiply(txGasLimit); - track.addBalance(tx.getSender(), txGasCost.negate()); - - if (logger.isInfoEnabled()) - logger.info("Paying: txGasCost: [{}], gasPrice: [{}], gasLimit: [{}]", txGasCost, toBI(tx.getGasPrice()), txGasLimit); - } - - if (tx.isContractCreation()) { - create(); - } else { - call(); - } - } - - private void call() { - if (!readyToExecute) return; - - byte[] targetAddress = tx.getReceiveAddress(); - precompiledContract = PrecompiledContracts.getContractForAddress(new DataWord(targetAddress), blockchainConfig); - - if (precompiledContract != null) { - long requiredGas = precompiledContract.getGasForData(tx.getData()); - - BigInteger spendingGas = BigInteger.valueOf(requiredGas).add(BigInteger.valueOf(basicTxCost)); - - if (!localCall && m_endGas.compareTo(spendingGas) < 0) { - // no refund - // no endowment - execError("Out of Gas calling precompiled contract 0x" + Hex.toHexString(targetAddress) + - ", required: " + spendingGas + ", left: " + m_endGas); - m_endGas = BigInteger.ZERO; - return; - } else { - - m_endGas = m_endGas.subtract(spendingGas); - - // FIXME: save return for vm trace - Pair out = precompiledContract.execute(tx.getData()); - - if (!out.getLeft()) { - execError("Error executing precompiled contract 0x" + Hex.toHexString(targetAddress)); - m_endGas = BigInteger.ZERO; - return; - } - } - - } else { - - byte[] code = track.getCode(targetAddress); - if (isEmpty(code)) { - m_endGas = m_endGas.subtract(BigInteger.valueOf(basicTxCost)); - result.spendGas(basicTxCost); - } else { - ProgramInvoke programInvoke = - programInvokeFactory.createProgramInvoke(tx, currentBlock, cacheTrack, blockStore); - - this.vm = new VM(config); - this.program = new Program(track.getCodeHash(targetAddress), code, programInvoke, tx, config).withCommonConfig(commonConfig); - } - } - - BigInteger endowment = toBI(tx.getValue()); - transfer(cacheTrack, tx.getSender(), targetAddress, endowment); - - touchedAccounts.add(targetAddress); - } - - private void create() { - byte[] newContractAddress = tx.getContractAddress(); - - AccountState existingAddr = cacheTrack.getAccountState(newContractAddress); - if (existingAddr != null && existingAddr.isContractExist(blockchainConfig)) { - execError("Trying to create a contract with existing contract address: 0x" + Hex.toHexString(newContractAddress)); - m_endGas = BigInteger.ZERO; - return; - } + void init(); - //In case of hashing collisions (for TCK tests only), check for any balance before createAccount() - BigInteger oldBalance = track.getBalance(newContractAddress); - cacheTrack.createAccount(tx.getContractAddress()); - cacheTrack.addBalance(newContractAddress, oldBalance); - if (blockchainConfig.eip161()) { - cacheTrack.increaseNonce(newContractAddress); - } - - if (isEmpty(tx.getData())) { - m_endGas = m_endGas.subtract(BigInteger.valueOf(basicTxCost)); - result.spendGas(basicTxCost); - } else { - ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(tx, currentBlock, cacheTrack, blockStore); - - this.vm = new VM(config); - this.program = new Program(tx.getData(), programInvoke, tx, config).withCommonConfig(commonConfig); - - // reset storage if the contract with the same address already exists - // TCK test case only - normally this is near-impossible situation in the real network - // TODO make via Trie.clear() without keyset -// ContractDetails contractDetails = program.getStorage().getContractDetails(newContractAddress); -// for (DataWord key : contractDetails.getStorageKeys()) { -// program.storageSave(key, DataWord.ZERO); -// } - } - - BigInteger endowment = toBI(tx.getValue()); - transfer(cacheTrack, tx.getSender(), newContractAddress, endowment); - - touchedAccounts.add(newContractAddress); - } - - public void go() { - if (!readyToExecute) return; - - try { - - if (vm != null) { - - // Charge basic cost of the transaction - program.spendGas(tx.transactionCost(config.getBlockchainConfig(), currentBlock), "TRANSACTION COST"); - - if (config.playVM()) - vm.play(program); - - result = program.getResult(); - m_endGas = toBI(tx.getGasLimit()).subtract(toBI(program.getResult().getGasUsed())); - - if (tx.isContractCreation() && !result.isRevert()) { - int returnDataGasValue = getLength(program.getResult().getHReturn()) * - blockchainConfig.getGasCost().getCREATE_DATA(); - if (m_endGas.compareTo(BigInteger.valueOf(returnDataGasValue)) < 0) { - // Not enough gas to return contract code - if (!blockchainConfig.getConstants().createEmptyContractOnOOG()) { - program.setRuntimeFailure(Program.Exception.notEnoughSpendingGas("No gas to return just created contract", - returnDataGasValue, program)); - result = program.getResult(); - } - result.setHReturn(EMPTY_BYTE_ARRAY); - } else if (getLength(result.getHReturn()) > blockchainConfig.getConstants().getMAX_CONTRACT_SZIE()) { - // Contract size too large - program.setRuntimeFailure(Program.Exception.notEnoughSpendingGas("Contract size too large: " + getLength(result.getHReturn()), - returnDataGasValue, program)); - result = program.getResult(); - result.setHReturn(EMPTY_BYTE_ARRAY); - } else { - // Contract successfully created - m_endGas = m_endGas.subtract(BigInteger.valueOf(returnDataGasValue)); - cacheTrack.saveCode(tx.getContractAddress(), result.getHReturn()); - } - } - - String err = config.getBlockchainConfig().getConfigForBlock(currentBlock.getNumber()). - validateTransactionChanges(blockStore, currentBlock, tx, null); - if (err != null) { - program.setRuntimeFailure(new RuntimeException("Transaction changes validation failed: " + err)); - } - - - if (result.getException() != null || result.isRevert()) { - result.getDeleteAccounts().clear(); - result.getLogInfoList().clear(); - result.resetFutureRefund(); - rollback(); - - if (result.getException() != null) { - throw result.getException(); - } else { - execError("REVERT opcode executed"); - } - } else { - touchedAccounts.addAll(result.getTouchedAccounts()); - cacheTrack.commit(); - } - - } else { - cacheTrack.commit(); - } - - } catch (Throwable e) { - - // TODO: catch whatever they will throw on you !!! -// https://github.com/ethereum/cpp-ethereum/blob/develop/libethereum/Executive.cpp#L241 - rollback(); - m_endGas = BigInteger.ZERO; - execError(e.getMessage()); - } - } - - private void rollback() { - - cacheTrack.rollback(); - - // remove touched account - touchedAccounts.remove( - tx.isContractCreation() ? tx.getContractAddress() : tx.getReceiveAddress()); - } - - public TransactionExecutionSummary finalization() { - if (!readyToExecute) return null; - - TransactionExecutionSummary.Builder summaryBuilder = TransactionExecutionSummary.builderFor(tx) - .gasLeftover(m_endGas) - .logs(result.getLogInfoList()) - .result(result.getHReturn()); - - if (result != null) { - // Accumulate refunds for suicides - result.addFutureRefund(result.getDeleteAccounts().size() * config.getBlockchainConfig(). - getConfigForBlock(currentBlock.getNumber()).getGasCost().getSUICIDE_REFUND()); - long gasRefund = Math.min(result.getFutureRefund(), getGasUsed() / 2); - byte[] addr = tx.isContractCreation() ? tx.getContractAddress() : tx.getReceiveAddress(); - m_endGas = m_endGas.add(BigInteger.valueOf(gasRefund)); - - summaryBuilder - .gasUsed(toBI(result.getGasUsed())) - .gasRefund(toBI(gasRefund)) - .deletedAccounts(result.getDeleteAccounts()) - .internalTransactions(result.getInternalTransactions()); - - ContractDetails contractDetails = track.getContractDetails(addr); - if (contractDetails != null) { - // TODO -// summaryBuilder.storageDiff(track.getContractDetails(addr).getStorage()); -// -// if (program != null) { -// summaryBuilder.touchedStorage(contractDetails.getStorage(), program.getStorageDiff()); -// } - } - - if (result.getException() != null) { - summaryBuilder.markAsFailed(); - } - } - - TransactionExecutionSummary summary = summaryBuilder.build(); - - // Refund for gas leftover - track.addBalance(tx.getSender(), summary.getLeftover().add(summary.getRefund())); - logger.info("Pay total refund to sender: [{}], refund val: [{}]", Hex.toHexString(tx.getSender()), summary.getRefund()); - - // Transfer fees to miner - track.addBalance(coinbase, summary.getFee()); - touchedAccounts.add(coinbase); - logger.info("Pay fees to miner: [{}], feesEarned: [{}]", Hex.toHexString(coinbase), summary.getFee()); - - if (result != null) { - logs = result.getLogInfoList(); - // Traverse list of suicides - for (DataWord address : result.getDeleteAccounts()) { - track.delete(address.getLast20Bytes()); - } - } - - if (blockchainConfig.eip161()) { - for (byte[] acctAddr : touchedAccounts) { - AccountState state = track.getAccountState(acctAddr); - if (state != null && state.isEmpty()) { - track.delete(acctAddr); - } - } - } - - - listener.onTransactionExecuted(summary); - - if (config.vmTrace() && program != null && result != null) { - String trace = program.getTrace() - .result(result.getHReturn()) - .error(result.getException()) - .toString(); - - - if (config.vmTraceCompressed()) { - trace = zipAndEncode(trace); - } - - String txHash = toHexString(tx.getHash()); - saveProgramTraceFile(config, txHash, trace); - listener.onVMTraceCreated(txHash, trace); - } - return summary; - } + /** + * Opening steps of transaction + * If transaction should create contract it creates, + * if it's calling existing it's loaded into VM + */ + void execute(); - public TransactionExecutor setLocalCall(boolean localCall) { - this.localCall = localCall; - return this; - } + /** + * Main execution of transaction + * If contract is involved program execution is done on this step + */ + void go(); + /** + * Called after execution is finished + * Pays rewards, do state modifications required by specs etc. + * Combines results from execution to Summary + */ + TransactionExecutionSummary finalization(); - public TransactionReceipt getReceipt() { - if (receipt == null) { - receipt = new TransactionReceipt(); - long totalGasUsed = gasUsedInTheBlock + getGasUsed(); - receipt.setCumulativeGas(totalGasUsed); - receipt.setTransaction(tx); - receipt.setLogInfoList(getVMLogs()); - receipt.setGasUsed(getGasUsed()); - receipt.setExecutionResult(getResult().getHReturn()); - receipt.setError(execError); -// receipt.setPostTxState(track.getRoot()); // TODO later when RepositoryTrack.getRoot() is implemented - } - return receipt; - } + /** + * Local execution does not spend gas on VM work + * Usually executor with localCall turned on is + * used for calling of constant methods + */ + TransactionExecutor setLocalCall(boolean localCall); - public List getVMLogs() { - return logs; - } + /** + * @return {@link TransactionReceipt} filled with data from execution result + */ + TransactionReceipt getReceipt(); - public ProgramResult getResult() { - return result; - } + /** + * @return list of {@link LogInfo}'s submitted during VM execution + */ + List getVMLogs(); - public long getGasUsed() { - return toBI(tx.getGasLimit()).subtract(m_endGas).longValue(); - } + /** + * Result of program execution + */ + ProgramResult getResult(); + /** + * @return amount of gas used for tx execution + */ + long getGasUsed(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/db/IndexedBlockStore.java b/ethereumj-core/src/main/java/org/ethereum/db/IndexedBlockStore.java index a42a71a4c5..d675e3d959 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/IndexedBlockStore.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/IndexedBlockStore.java @@ -37,6 +37,7 @@ import java.util.List; import static java.math.BigInteger.ZERO; +import static org.ethereum.crypto.HashUtil.sha3; import static org.ethereum.crypto.HashUtil.shortHash; import static org.spongycastle.util.Arrays.areEqual; diff --git a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryRoot.java b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryRoot.java index f6902e8c5b..8c67bff70c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryRoot.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryRoot.java @@ -17,6 +17,7 @@ */ package org.ethereum.db; +import org.ethereum.config.SystemProperties; import org.ethereum.core.AccountState; import org.ethereum.core.Repository; import org.ethereum.datasource.*; @@ -108,6 +109,15 @@ public RepositoryRoot(final Source stateDS, byte[] root) { init(accountStateCache, codeCache, storageCache); } + /** + * Same as {@link #RepositoryRoot(Source, byte[])} but more correct + * when autowiring is not active as you get default SystemProperties otherwise + */ + public RepositoryRoot(final Source stateDS, byte[] root, final SystemProperties props) { + this(stateDS, root); + this.config = props; + } + @Override public synchronized void commit() { super.commit(); @@ -131,7 +141,7 @@ public synchronized void flush() { @Override public Repository getSnapshotTo(byte[] root) { - return new RepositoryRoot(stateDS, root); + return new RepositoryRoot(stateDS, root, config); } @Override diff --git a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryWrapper.java b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryWrapper.java index 86f05e638d..2eb21853e9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryWrapper.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryWrapper.java @@ -19,6 +19,7 @@ import org.ethereum.core.AccountState; import org.ethereum.core.Block; +import org.ethereum.core.Blockchain; import org.ethereum.core.BlockchainImpl; import org.ethereum.core.Repository; import org.ethereum.vm.DataWord; @@ -40,7 +41,6 @@ @Component public class RepositoryWrapper implements Repository { - @Autowired BlockchainImpl blockchain; public RepositoryWrapper() { @@ -215,4 +215,9 @@ public Set getStorageKeys(byte[] addr) { public Map getStorage(byte[] addr, @Nullable Collection keys) { return blockchain.getRepository().getStorage(addr, keys); } + + @Autowired + public void setBlockchain(Blockchain blockchain) { + this.blockchain = (BlockchainImpl) blockchain; + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/facade/Ethereum.java b/ethereumj-core/src/main/java/org/ethereum/facade/Ethereum.java index bed44abcfb..69015ba125 100644 --- a/ethereumj-core/src/main/java/org/ethereum/facade/Ethereum.java +++ b/ethereumj-core/src/main/java/org/ethereum/facade/Ethereum.java @@ -22,6 +22,7 @@ import org.ethereum.listener.EthereumListener; import org.ethereum.manager.AdminInfo; import org.ethereum.manager.BlockLoader; +import org.ethereum.manager.WorldManager; import org.ethereum.mine.BlockMiner; import org.ethereum.net.client.PeerClient; import org.ethereum.net.rlpx.Node; @@ -32,7 +33,9 @@ import java.math.BigInteger; import java.net.InetAddress; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; +import java.util.function.Consumer; /** * @author Roman Mandeleil @@ -92,8 +95,28 @@ Transaction createTransaction(BigInteger nonce, * @param transaction submit transaction to the net, return option to wait for net * return this transaction as approved */ + @Deprecated Future submitTransaction(Transaction transaction); + /** + * Submit transaction to the net, return option to wait for net + * @param transaction + * @return future of waiting for net + */ + CompletableFuture sendTransaction(Transaction transaction); + + /** + * Submit transaction to the net, return option to wait for net + * @param tx Transaction + * @param successConsumer Fires when tx is sent to the net + * @param errorConsumer Fires when tx is not sent to the net due to error + * @param simErrorConsumer Runs tx on pending state one time and fires error if it fails, + * so transaction execution is simulated on the current state. + * Also though one tx broadcast is guaranteed no matter whether it's correct or not, + * tx is not broadcasted to more peers if it's execution on pending state failed. + */ + void sendTransaction(Transaction tx, Consumer successConsumer, Consumer errorConsumer, + Consumer simErrorConsumer); /** * Executes the transaction based on the specified block but doesn't change the blockchain state @@ -146,6 +169,18 @@ ProgramResult callConstantFunction(String receiveAddress, CallTransaction.Functi ProgramResult callConstantFunction(String receiveAddress, ECKey senderPrivateKey, CallTransaction.Function function, Object... funcArgs); + /** + * Call a contract function locally on the state defined by input block + * without sending transaction to the network and without changing contract storage. + * @param block block replicates state for transaction execution, so it should be in local blockchain + * @param receiveAddress hex encoded contract address + * @param function contract function + * @param funcArgs function arguments + * @return function result. The return value can be fetched via {@link ProgramResult#getHReturn()} + * and decoded with {@link org.ethereum.core.CallTransaction.Function#decodeResult(byte[])}. + */ ProgramResult callConstantFunction(Block block, String receiveAddress, + CallTransaction.Function function, Object... funcArgs); + /** * Returns the Repository instance which always refers to the latest (best block) state * It is always better using {@link #getLastRepositorySnapshot()} to work on immutable @@ -217,4 +252,6 @@ ProgramResult callConstantFunction(String receiveAddress, ECKey senderPrivateKey Integer getChainIdForNextBlock(); void exitOn(long number); + + void setWorldManager(WorldManager worldManager); } diff --git a/ethereumj-core/src/main/java/org/ethereum/facade/EthereumImpl.java b/ethereumj-core/src/main/java/org/ethereum/facade/EthereumImpl.java index ad9191eb98..495ab78b6b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/facade/EthereumImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/facade/EthereumImpl.java @@ -17,7 +17,6 @@ */ package org.ethereum.facade; -import org.apache.commons.lang3.ArrayUtils; import org.ethereum.config.BlockchainConfig; import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; @@ -56,8 +55,10 @@ import java.math.BigInteger; import java.net.InetAddress; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.function.Consumer; /** * @author Roman Mandeleil @@ -69,7 +70,6 @@ public class EthereumImpl implements Ethereum, SmartLifecycle { private static final Logger logger = LoggerFactory.getLogger("facade"); private static final Logger gLogger = LoggerFactory.getLogger("general"); - @Autowired WorldManager worldManager; @Autowired @@ -220,6 +220,37 @@ protected Transaction adapt(List adapteeResult) throws ExecutionExc }; } + @Override + public CompletableFuture sendTransaction(Transaction transaction) { + CompletableFuture future = new CompletableFuture<>(); + final TransactionTask transactionTask = new TransactionTask(transaction, channelManager, future); + + TransactionExecutor.instance.submitTransaction(transactionTask); + + pendingState.addPendingTransaction(transaction); + + return future; + } + + @Override + public void sendTransaction(Transaction tx, Consumer successConsumer, Consumer errorConsumer, + Consumer simErrorConsumer) { + CompletableFuture future = new CompletableFuture<>(); + final TransactionTask transactionTask = new TransactionTask(tx, channelManager, future); + + TransactionExecutor.instance.submitTransaction(transactionTask); + + pendingState.addPendingTransaction(tx, simErrorConsumer); + + future.whenComplete((transaction, throwable) -> { + if (throwable != null) { + errorConsumer.accept(throwable); + } else { + successConsumer.accept(transaction); + } + }); + } + @Override public TransactionReceipt callConstant(Transaction tx, Block block) { if (tx.getSignature() == null) { @@ -247,10 +278,10 @@ public BlockSummary replayBlock(Block block) { for (Transaction tx : block.getTransactionsList()) { Repository txTrack = track.startTracking(); - org.ethereum.core.TransactionExecutor executor = new org.ethereum.core.TransactionExecutor( - tx, block.getCoinbase(), txTrack, worldManager.getBlockStore(), - programInvokeFactory, block, worldManager.getListener(), 0) - .withCommonConfig(commonConfig); + org.ethereum.core.TransactionExecutor executor = + ((org.ethereum.core.Blockchain) getBlockchain()).createTransactionExecutor( + tx, block.getCoinbase(), txTrack, block, 0 + ); executor.init(); executor.execute(); @@ -279,8 +310,8 @@ private org.ethereum.core.TransactionExecutor callConstantImpl(Transaction tx, B .startTracking(); try { - org.ethereum.core.TransactionExecutor executor = new org.ethereum.core.TransactionExecutor - (tx, block.getCoinbase(), repository, worldManager.getBlockStore(), + org.ethereum.core.TransactionExecutor executor = new CommonTransactionExecutor( + tx, block.getCoinbase(), repository, worldManager.getBlockStore(), programInvokeFactory, block, new EthereumListenerAdapter(), 0) .withCommonConfig(commonConfig) .setLocalCall(true); @@ -302,6 +333,15 @@ public ProgramResult callConstantFunction(String receiveAddress, return callConstantFunction(receiveAddress, ECKey.fromPrivate(new byte[32]), function, funcArgs); } + public ProgramResult callConstantFunction(Block block, String receiveAddress, + CallTransaction.Function function, Object... funcArgs) { + Transaction tx = CallTransaction.createCallTransaction(0, 0, 100000000000000L, + receiveAddress, 0, function, funcArgs); + tx.sign(ECKey.fromPrivate(new byte[32])); + + return callConstantImpl(tx, block).getResult(); + } + @Override public ProgramResult callConstantFunction(String receiveAddress, ECKey senderPrivateKey, CallTransaction.Function function, Object... funcArgs) { @@ -390,6 +430,26 @@ public void initSyncing() { worldManager.initSyncing(); } + @Override + public void setWorldManager(WorldManager worldManager) { + this.worldManager = worldManager; + } + + public void setCommonConfig(CommonConfig commonConfig) { + this.commonConfig = commonConfig; + } + + public void setProgramInvokeFactory(ProgramInvokeFactory programInvokeFactory) { + this.programInvokeFactory = programInvokeFactory; + } + + public void setPendingState(PendingState pendingState) { + this.pendingState = pendingState; + } + + public void setChannelManager(ChannelManager channelManager) { + this.channelManager = channelManager; + } /** * For testing purposes and 'hackers' diff --git a/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java b/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java index c73d08eb1c..5575e4ccf4 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java @@ -20,8 +20,8 @@ import org.ethereum.config.SystemProperties; import org.ethereum.core.*; import org.ethereum.db.BlockStore; -import org.ethereum.db.ByteArrayWrapper; import org.ethereum.db.DbFlushManager; +import org.ethereum.facade.Ethereum; import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListener; import org.ethereum.net.client.PeerClient; @@ -37,7 +37,6 @@ import org.spongycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.math.BigInteger; @@ -53,7 +52,6 @@ * @author Roman Mandeleil * @since 01.06.2014 */ -@Component public class WorldManager { private static final Logger logger = LoggerFactory.getLogger("general"); @@ -92,32 +90,36 @@ public class WorldManager { private DbFlushManager dbFlushManager; @Autowired - private ApplicationContext ctx; + protected ApplicationContext ctx; - private SystemProperties config; + protected Ethereum ethereum; + protected SystemProperties config; + + @Autowired private EthereumListener listener; - private Blockchain blockchain; + protected Blockchain blockchain; private Repository repository; + @Autowired private BlockStore blockStore; @Autowired public WorldManager(final SystemProperties config, final Repository repository, - final EthereumListener listener, final Blockchain blockchain, - final BlockStore blockStore) { - this.listener = listener; + final Blockchain blockchain) { this.blockchain = blockchain; this.repository = repository; - this.blockStore = blockStore; this.config = config; - loadBlockchain(); } @PostConstruct - private void init() { + protected void init() { + this.ethereum = ctx.getBean(Ethereum.class); + ethereum.setWorldManager(this); + loadBlockchain(); + channelManager.init(ethereum); syncManager.init(channelManager, pool); } diff --git a/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java b/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java index ed36f9fc12..d3514dddec 100644 --- a/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java +++ b/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java @@ -30,6 +30,7 @@ import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.mine.MinerIfc.MiningResult; +import org.ethereum.util.FastByteComparisons; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +39,7 @@ import java.math.BigInteger; import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import static java.lang.Math.max; @@ -46,13 +48,12 @@ * * Created by Anton Nashatyrev on 10.12.2015. */ -@Component public class BlockMiner { - private static final Logger logger = LoggerFactory.getLogger("mine"); + protected static final Logger logger = LoggerFactory.getLogger("mine"); private static ExecutorService executor = Executors.newSingleThreadExecutor(); - private Blockchain blockchain; + protected Blockchain blockchain; private BlockStore blockStore; @@ -63,7 +64,7 @@ public class BlockMiner { private CompositeEthereumListener listener; - private SystemProperties config; + protected SystemProperties config; private List listeners = new CopyOnWriteArrayList<>(); @@ -83,12 +84,11 @@ public class BlockMiner { @Autowired public BlockMiner(final SystemProperties config, final CompositeEthereumListener listener, - final Blockchain blockchain, final BlockStore blockStore, - final PendingState pendingState) { + final Blockchain blockchain, final PendingState pendingState) { this.listener = listener; this.config = config; this.blockchain = blockchain; - this.blockStore = blockStore; + this.blockStore = ((BlockchainImpl) blockchain).getBlockStore(); this.pendingState = pendingState; UNCLE_LIST_LIMIT = config.getBlockchainConfig().getCommonConstants().getUNCLE_LIST_LIMIT(); UNCLE_GENERATION_LIMIT = config.getBlockchainConfig().getCommonConstants().getUNCLE_GENERATION_LIMIT(); @@ -246,8 +246,30 @@ protected Block getNewBlockForMining() { logger.debug("getNewBlockForMining best blocks: PendingState: " + bestPendingState.getShortDescr() + ", Blockchain: " + bestBlockchain.getShortDescr()); - Block newMiningBlock = blockchain.createNewBlock(bestPendingState, getAllPendingTransactions(), - getUncles(bestPendingState)); + return createOptimizedBlock(bestPendingState, getAllPendingTransactions(), getUncles(bestPendingState)); + } + + protected Block createOptimizedBlock(Block bestPendingBlock, final List txs, List uncles) { + boolean optimized = false; + Block newMiningBlock = null; + while(!optimized) { + List transactions = new ArrayList<>(txs); + BlockSummary summary = blockchain.createNewBlockSummary(bestPendingBlock, transactions, uncles); + AtomicBoolean badTxFound = new AtomicBoolean(false); + summary.getReceipts().forEach(receipt -> { + if (!receipt.isSuccessful() && (receipt.getGasUsed() == null || receipt.getGasUsed().length == 0)) { + transactions.removeIf(transaction -> { + boolean match = FastByteComparisons.equal(transaction.getHash(), receipt.getTransaction().getHash()); + badTxFound.set(true); + return match; + }); + } + }); + if (!badTxFound.get()) { + optimized = true; + newMiningBlock = summary.getBlock(); + } + } return newMiningBlock; } @@ -273,7 +295,7 @@ protected void restartMining() { try { // wow, block mined! final Block minedBlock = task.get().block; - blockMined(minedBlock); + blockMined(minedBlock, task); } catch (InterruptedException | CancellationException e) { // OK, we've been cancelled, just exit } catch (Exception e) { @@ -294,7 +316,7 @@ private Block cloneBlock(Block block) { return new Block(block.getEncoded()); } - protected void blockMined(Block newBlock) throws InterruptedException { + protected void blockMined(Block newBlock, ListenableFuture task) throws InterruptedException { long t = System.currentTimeMillis(); if (t - lastBlockMinedTime < minBlockTimeout) { long sleepTime = minBlockTimeout - (t - lastBlockMinedTime); @@ -303,6 +325,13 @@ protected void blockMined(Block newBlock) throws InterruptedException { Thread.sleep(sleepTime); } + synchronized (BlockMiner.class) { + if (!currentMiningTasks.contains(task)) { // Task is removed from current tasks so it's cancelled + logger.debug("Discarding block [{}] because mining task was already cancelled", newBlock.getShortDescr()); + return; + } + } + fireBlockMined(newBlock); logger.info("Wow, block mined !!!: {}", newBlock.toString()); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth62.java b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth62.java index a726c23f56..2733bb121e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth62.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth62.java @@ -35,7 +35,6 @@ import org.ethereum.sync.PeerState; import org.ethereum.sync.SyncStatistics; import org.ethereum.util.ByteUtil; -import org.ethereum.util.Utils; import org.ethereum.validator.BlockHeaderRule; import org.ethereum.validator.BlockHeaderValidator; import org.slf4j.Logger; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/FrameCodec.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/FrameCodec.java index 022cc2c33d..43a585c8fe 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/FrameCodec.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/FrameCodec.java @@ -33,7 +33,9 @@ import java.io.*; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.ethereum.util.RLP.decode2OneItem; @@ -51,6 +53,7 @@ public class FrameCodec { private int contextId = -1; private int totalFrameSize = -1; private int protocol; + private Map contextFrameIndex = new HashMap<>(); public FrameCodec(EncryptionHandshake.Secrets secrets) { this.mac = secrets.mac; @@ -111,7 +114,10 @@ public void writeFrame(Frame frame, ByteBuf buf) throws IOException { public void writeFrame(Frame frame, OutputStream out) throws IOException { byte[] headBuffer = new byte[32]; - byte[] ptype = RLP.encodeInt((int) frame.type); // FIXME encodeLong + byte[] ptype = new byte[0]; + if (frame.type != -1) { // Type is actual only for 1st frame + ptype = RLP.encodeLong(frame.type); + } int totalSize = frame.size + ptype.length; headBuffer[0] = (byte)(totalSize >> 16); headBuffer[1] = (byte)(totalSize >> 8); @@ -163,7 +169,7 @@ public List readFrames(ByteBuf buf) throws IOException { } } - public List readFrames(DataInput inp) throws IOException { + public synchronized List readFrames(DataInput inp) throws IOException { if (!isHeadRead) { byte[] headBuffer = new byte[32]; try { @@ -189,6 +195,7 @@ public List readFrames(DataInput inp) throws IOException { contextId = Util.rlpDecodeInt(rlpList.get(1)); if (rlpList.size() > 2) { totalFrameSize = Util.rlpDecodeInt(rlpList.get(2)); + contextFrameIndex.put(contextId, totalFrameSize); } } @@ -208,10 +215,29 @@ public List readFrames(DataInput inp) throws IOException { ingressMac.update(buffer, 0, frameSize); dec.processBytes(buffer, 0, frameSize, buffer, 0); int pos = 0; - long type = RLP.decodeLong(buffer, pos); - pos = RLP.getNextElementIndex(buffer, pos); + long type = -1; + // According to frame specification only first frame comes with type and pos + if (totalFrameSize > 0 || contextFrameIndex.get(contextId) == null) { + type = RLP.decodeLong(buffer, pos); + pos = RLP.getNextElementIndex(buffer, pos); + } InputStream payload = new ByteArrayInputStream(buffer, pos, totalBodySize - pos); int size = totalBodySize - pos; + + if (contextFrameIndex.get(contextId) != null) { + if (type != -1) { // Type is part of body too, so we should deduct it too + size += RLP.encodeLong(type).length; + } + int curSize = contextFrameIndex.get(contextId); + curSize -= size; + + if (curSize > 0) { + contextFrameIndex.put(contextId, curSize); + } else { + contextFrameIndex.remove(contextId); + } + } + byte[] macBuffer = new byte[ingressMac.getDigestSize()]; // Frame MAC diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/MessageCodec.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/MessageCodec.java index 038879f7e8..8c464386c2 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/MessageCodec.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/MessageCodec.java @@ -34,6 +34,7 @@ import org.ethereum.net.server.Channel; import org.ethereum.net.shh.ShhMessageCodes; import org.ethereum.net.swarm.bzz.BzzMessageCodes; +import org.ethereum.util.RLP; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; @@ -93,7 +94,6 @@ private MessageCodec(final SystemProperties config) { @Override protected void decode(ChannelHandlerContext ctx, Frame frame, List out) throws Exception { - Frame completeFrame = null; if (frame.isChunked()) { if (!supportChunkedFrames && frame.totalFrameSize > 0) { throw new RuntimeException("Faming is not supported in this configuration."); @@ -157,15 +157,14 @@ private Message decodeMessage(ChannelHandlerContext ctx, List frames) thr Message msg; try { msg = createMessage((byte) frameType, payload); + if (loggerNet.isDebugEnabled()) + loggerNet.debug("From: {} Recv: {}", channel, msg.toString()); } catch (Exception ex) { loggerNet.debug("Incorrectly encoded message from: \t{}, dropping peer", channel); channel.disconnect(ReasonCode.BAD_PROTOCOL); return null; } - if (loggerNet.isDebugEnabled()) - loggerNet.debug("From: {} Recv: {}", channel, msg.toString()); - ethereumListener.onRecvMessage(channel, msg); channel.getNodeStatistics().rlpxInMessages.add(); @@ -201,14 +200,18 @@ private List splitMessageToFrames(Message msg) { int newPos = min(curPos + maxFramePayloadSize, bytes.length); byte[] frameBytes = curPos == 0 && newPos == bytes.length ? bytes : Arrays.copyOfRange(bytes, curPos, newPos); - ret.add(new Frame(code, frameBytes)); + if (curPos == 0) { // 1st frame needs type + ret.add(new Frame(code, frameBytes)); + } else { // Next frames don't need type + ret.add(new Frame(-1, frameBytes)); + } curPos = newPos; } if (ret.size() > 1) { // frame has been split int contextId = contextIdCounter.getAndIncrement(); - ret.get(0).totalFrameSize = bytes.length; + ret.get(0).totalFrameSize = bytes.length + RLP.encodeLong(code).length; // type is part of the body loggerWire.debug("Message (size " + bytes.length + ") split to " + ret.size() + " frames. Context-id: " + contextId); for (Frame frame : ret) { frame.contextId = contextId; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/server/Channel.java b/ethereumj-core/src/main/java/org/ethereum/net/server/Channel.java index 10f8996f8c..bdd305900f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/server/Channel.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/server/Channel.java @@ -161,14 +161,17 @@ public void publicRLPxHandshakeFinished(ChannelHandlerContext ctx, FrameCodec fr logger.debug("publicRLPxHandshakeFinished with " + ctx.channel().remoteAddress()); - messageCodec.setSupportChunkedFrames(false); - FrameCodecHandler frameCodecHandler = new FrameCodecHandler(frameCodec, this); ctx.pipeline().addLast("medianFrameCodec", frameCodecHandler); if (SnappyCodec.isSupported(Math.min(config.defaultP2PVersion(), helloRemote.getP2PVersion()))) { ctx.pipeline().addLast("snappyCodec", new SnappyCodec(this)); + messageCodec.setSupportChunkedFrames(false); logger.debug("{}: use snappy compression", ctx.channel()); + } else { + // FIXME: Actually frames are not used by every P2Pv4 client + // only Pyethereum is known + messageCodec.setSupportChunkedFrames(true); } ctx.pipeline().addLast("messageCodec", messageCodec); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/server/ChannelManager.java b/ethereumj-core/src/main/java/org/ethereum/net/server/ChannelManager.java index dd079a3247..e01112ae84 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/server/ChannelManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/server/ChannelManager.java @@ -82,8 +82,7 @@ public class ChannelManager { @Autowired SyncPool syncPool; - @Autowired - private Ethereum ethereum; + private Ethereum ethereum; // FIXME: Ethereum -> World -> Channel @Autowired private PendingState pendingState; @@ -124,6 +123,10 @@ private ChannelManager(final SystemProperties config, final SyncManager syncMana this.txDistributeThread.start(); } + public void init(Ethereum ethereum) { + this.ethereum = ethereum; + } + public void connect(Node node) { if (logger.isTraceEnabled()) logger.trace( "Peer {}: initiate connection", diff --git a/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionTask.java b/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionTask.java index ce9863f51e..67d9b59f93 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionTask.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionTask.java @@ -27,6 +27,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import static java.lang.Thread.sleep; @@ -41,6 +42,7 @@ public class TransactionTask implements Callable> { private final List tx; private final ChannelManager channelManager; private final Channel receivedFrom; + private final CompletableFuture future; public TransactionTask(Transaction tx, ChannelManager channelManager) { this(Collections.singletonList(tx), channelManager); @@ -54,6 +56,14 @@ public TransactionTask(List tx, ChannelManager channelManager, Chan this.tx = tx; this.channelManager = channelManager; this.receivedFrom = receivedFrom; + this.future = null; + } + + public TransactionTask(Transaction tx, ChannelManager channelManager, CompletableFuture future) { + this.tx = Collections.singletonList(tx); + this.channelManager = channelManager; + this.future = future; + this.receivedFrom = null; } @Override @@ -62,10 +72,16 @@ public List call() throws Exception { try { logger.info("submit tx: {}", tx.toString()); channelManager.sendTransaction(tx, receivedFrom); + if (future != null) { + future.complete(tx.get(0)); + } return tx; } catch (Throwable th) { logger.warn("Exception caught: {}", th); + if (future != null) { + future.completeExceptionally(th); + } } return null; } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/swarm/Util.java b/ethereumj-core/src/main/java/org/ethereum/net/swarm/Util.java index 895c241501..e996195b34 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/swarm/Util.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/swarm/Util.java @@ -1,20 +1,20 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ package org.ethereum.net.swarm; import org.ethereum.util.ByteUtil; @@ -124,11 +124,6 @@ public static byte[] uInt16ToBytes(int uInt16) { public static long curTime() { return TIMER.curTime();} - public static byte[] rlpEncodeLong(long n) { - // TODO for now leaving int cast - return RLP.encodeInt((int) n); - } - public static byte rlpDecodeByte(RLPElement elem) { return (byte) rlpDecodeInt(elem); } @@ -149,26 +144,6 @@ public static String rlpDecodeString(RLPElement elem) { return new String(b); } - public static byte[] rlpEncodeList(Object ... elems) { - byte[][] encodedElems = new byte[elems.length][]; - for (int i =0; i < elems.length; i++) { - if (elems[i] instanceof Byte) { - encodedElems[i] = RLP.encodeByte((Byte) elems[i]); - } else if (elems[i] instanceof Integer) { - encodedElems[i] = RLP.encodeInt((Integer) elems[i]); - } else if (elems[i] instanceof Long) { - encodedElems[i] = rlpEncodeLong((Long) elems[i]); - } else if (elems[i] instanceof String) { - encodedElems[i] = RLP.encodeString((String) elems[i]); - } else if (elems[i] instanceof byte[]) { - encodedElems[i] = ((byte[]) elems[i]); - } else { - throw new RuntimeException("Unsupported object: " + elems[i]); - } - } - return RLP.encodeList(encodedElems); - } - public static SectionReader stringToReader(String s) { return new ArrayReader(s.getBytes(StandardCharsets.UTF_8)); } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzStatusMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzStatusMessage.java index a70dfea809..22cfcaef6e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzStatusMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzStatusMessage.java @@ -78,9 +78,9 @@ private void encode() { byte[][] capabilities = new byte[this.capabilities.size()][]; for (int i = 0; i < this.capabilities.size(); i++) { Capability capability = this.capabilities.get(i); - capabilities[i] = rlpEncodeList(capability.getName(),capability.getVersion()); + capabilities[i] = RLP.smartEncodeList(capability.getName(),capability.getVersion()); } - this.encoded = rlpEncodeList(version, id, addr.encodeRlp(), networkId, rlpEncodeList(capabilities)); + this.encoded = RLP.smartEncodeList(version, id, addr.encodeRlp(), networkId, RLP.encodeList(capabilities)); } @Override diff --git a/ethereumj-core/src/main/java/org/ethereum/solidity/SolidityType.java b/ethereumj-core/src/main/java/org/ethereum/solidity/SolidityType.java index 87f3a9e914..c8f041b9b3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/solidity/SolidityType.java +++ b/ethereumj-core/src/main/java/org/ethereum/solidity/SolidityType.java @@ -19,12 +19,15 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; +import org.ethereum.db.ByteArrayWrapper; import org.ethereum.util.ByteUtil; -import org.ethereum.vm.DataWord; import org.spongycastle.util.encoders.Hex; import java.lang.reflect.Array; +import java.math.BigDecimal; import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -63,6 +66,8 @@ public static SolidityType getType(String typeName) { if ("bytes".equals(typeName)) return new BytesType(); if ("function".equals(typeName)) return new FunctionType(); if (typeName.startsWith("bytes")) return new Bytes32Type(typeName); + // FIXME: actually we have 'fixedNM' in Solidity and 'decimal' in Vyper + if (typeName.startsWith("decimal")) return new DecimalType(typeName); throw new RuntimeException("Unknown type: " + typeName); } @@ -256,6 +261,8 @@ public byte[] encode(Object value) { byte[] bb; if (value instanceof byte[]) { bb = (byte[]) value; + } else if (value instanceof ByteArrayWrapper) { + bb = ((ByteArrayWrapper) value).getData(); } else if (value instanceof String) { bb = ((String) value).getBytes(); } else { @@ -387,6 +394,8 @@ public byte[] encode(Object value) { bigInt = (BigInteger) value; } else if (value instanceof Number) { bigInt = new BigInteger(value.toString()); + } else if (value instanceof ByteArrayWrapper) { + bigInt = ByteUtil.bytesToBigInteger(((ByteArrayWrapper) value).getData()); } else if (value instanceof byte[]) { bigInt = ByteUtil.bytesToBigInteger((byte[]) value); } else { @@ -411,6 +420,70 @@ public static byte[] encodeInt(BigInteger bigInt) { } } + public static class DecimalType extends SolidityType { + + // FIXME: what if it's not 10 digits after point + private static int DECIMAL_PLACES = 10; + + public DecimalType(String name) { + super(name); + } + + @Override + public String getCanonicalName() { + if (getName().equals("decimal")) return "decimal10"; + return super.getCanonicalName(); + } + + @Override + public byte[] encode(Object value) { + BigDecimal bigDecimal; + + if (value instanceof String) { + String s = ((String)value).toLowerCase().trim(); + int radix = 10; + if (s.startsWith("0x")) { + s = s.substring(2); + radix = 16; + } else if (s.contains("a") || s.contains("b") || s.contains("c") || + s.contains("d") || s.contains("e") || s.contains("f")) { + radix = 16; + } + + BigInteger bigInt = new BigInteger(s, radix); + bigDecimal = fromBigInt(bigInt, DECIMAL_PLACES); + } else if (value instanceof BigDecimal) { + bigDecimal = (BigDecimal) value; + } else if (value instanceof Number) { + bigDecimal = new BigDecimal(value.toString()); + } else if (value instanceof byte[]) { + BigInteger bigInt = ByteUtil.bytesToBigInteger((byte[]) value); + bigDecimal = fromBigInt(bigInt, DECIMAL_PLACES); + } else { + throw new RuntimeException("Invalid value for type '" + this + "': " + value + " (" + value.getClass() + ")"); + } + return encodeFixed(bigDecimal); + } + + private static BigDecimal fromBigInt(final BigInteger bigInt, final int decimalDigits) { + // FIXME: is it really rounding down?? cannot find Solidity/Vyper practice + return new BigDecimal(bigInt).divide(BigDecimal.valueOf(Math.pow(10, decimalDigits)), RoundingMode.DOWN); + } + + @Override + public Object decode(byte[] encoded, int offset) { + return decodeFixed(encoded, offset); + } + + public static BigDecimal decodeFixed(byte[] encoded, int offset) { + return fromBigInt(new BigInteger(Arrays.copyOfRange(encoded, offset, offset + 32)), DECIMAL_PLACES); + } + public static byte[] encodeFixed(BigDecimal bigDecimal) { + BigInteger bigInt = bigDecimal.multiply(BigDecimal.valueOf(Math.pow(10, DECIMAL_PLACES))).toBigInteger(); + return ByteUtil.bigIntegerToBytesSigned(bigInt, 32); + } + } + public static class BoolType extends IntType { public BoolType() { super("bool"); diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java b/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java index ff7c494a60..f89b59be0f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java @@ -274,9 +274,7 @@ private void produceQueue() { wrapper.getBlock().getTransactionsList().size(), ts); if (wrapper.isNewBlock() && !syncDone) { - syncDone = true; - channelManager.onSyncDone(true); - compositeEthereumListener.onSyncDone(syncDoneType); + makeSyncDone(); } } @@ -311,6 +309,14 @@ private void produceQueue() { } } + public synchronized void makeSyncDone() { + if(!syncDone) { + syncDone = true; + channelManager.onSyncDone(true); + compositeEthereumListener.onSyncDone(syncDoneType); + } + } + /** * Adds NEW block to the queue * diff --git a/ethereumj-core/src/main/java/org/ethereum/util/RLP.java b/ethereumj-core/src/main/java/org/ethereum/util/RLP.java index 859530b85f..60fe5e1d0f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/RLP.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/RLP.java @@ -934,6 +934,15 @@ else if ((singleInt & 0xFFFFFF) == singleInt) } } + public static byte[] encodeLong(long singleLong) { + if (singleLong <= Integer.MAX_VALUE) { + return encodeInt((int) singleLong); + } else { + // TODO: optimize me + return encodeBigInteger(BigInteger.valueOf(singleLong)); + } + } + public static byte[] encodeString(String srcString) { return encodeElement(srcString.getBytes()); } @@ -1123,6 +1132,45 @@ public static byte[] encodeSet(Set data) { return output; } + /** + * NOTE: Expects byte[] objects are already RLP-encoded while everything else is not + * + * @param elements Any elements, supports Byte, Short, Integer, Long, + * BigInteger, String, byte[], ByteArrayWrapper + * @return RLP list byte encoded + */ + public static byte[] smartEncodeList(Object... elements) { + + if (elements == null) { + return new byte[]{(byte) OFFSET_SHORT_LIST}; + } + + byte[][] encodedElems = new byte[elements.length][]; + for (int i =0; i < elements.length; i++) { + // TODO: consider using {@link: encode(Object)} + if (elements[i] instanceof Byte) { + encodedElems[i] = encodeByte((Byte) elements[i]); + } else if (elements[i] instanceof Short) { + encodedElems[i] = encodeShort((Short) elements[i]); + } else if (elements[i] instanceof Integer) { + encodedElems[i] = encodeInt((Integer) elements[i]); + } else if (elements[i] instanceof Long) { + encodedElems[i] = encodeLong((Long) elements[i]); + } else if (elements[i] instanceof BigInteger) { + encodedElems[i] = encodeBigInteger((BigInteger) elements[i]); + } else if (elements[i] instanceof String) { + encodedElems[i] = encodeString((String) elements[i]); + } else if (elements[i] instanceof ByteArrayWrapper) { + encodedElems[i] = encodeElement(((ByteArrayWrapper) elements[i]).getData()); + } else if (elements[i] instanceof byte[]) { + encodedElems[i] = ((byte[]) elements[i]); + } else { + throw new RuntimeException("Unsupported object: " + elements[i]); + } + } + return RLP.encodeList(encodedElems); + } + public static byte[] encodeList(byte[]... elements) { if (elements == null) { diff --git a/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java b/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java index 818d63d759..f7d2f37653 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java @@ -36,7 +36,6 @@ import org.ethereum.solidity.compiler.CompilationResult; import org.ethereum.solidity.compiler.CompilationResult.ContractMetadata; import org.ethereum.solidity.compiler.SolidityCompiler; -import org.ethereum.sync.SyncManager; import org.ethereum.util.ByteUtil; import org.ethereum.util.FastByteComparisons; import org.ethereum.validator.DependentBlockHeaderRuleAdapter; @@ -61,9 +60,9 @@ public class StandaloneBlockchain implements LocalBlockchain { Genesis genesis; byte[] coinbase; - BlockchainImpl blockchain; - PendingStateImpl pendingState; - CompositeEthereumListener listener; + protected BlockchainImpl blockchain; + protected PendingStateImpl pendingState; + protected CompositeEthereumListener listener; ECKey txSender; long gasPrice; long gasLimit; @@ -77,11 +76,11 @@ public class StandaloneBlockchain implements LocalBlockchain { long time = 0; long timeIncrement = 13; - private HashMapDB stateDS; - JournalSource pruningStateDS; - PruneManager pruneManager; + protected HashMapDB stateDS; + protected JournalSource pruningStateDS; + protected PruneManager pruneManager; - private BlockSummary lastSummary; + protected BlockSummary lastSummary; class PendingTx { ECKey sender; @@ -206,7 +205,13 @@ private Map createTransactions(Block parent) { BigInteger bcNonce = repoSnapshot.getNonce(tx.sender.getAddress()); nonce = bcNonce.longValue(); } - nonces.put(senderW, nonce + 1); + // FIXME: Casper only + // Increase nonce only for non-casper txs + Long newNonce = nonce; + if (!Arrays.equals(tx.sender.getAddress(), Transaction.NULL_SENDER)) { + ++newNonce; + } + nonces.put(senderW, newNonce); byte[] toAddress = tx.targetContract != null ? tx.targetContract.getAddress() : tx.toAddress; @@ -228,6 +233,10 @@ public PendingStateImpl getPendingState() { return pendingState; } + public CompositeEthereumListener getListener() { + return listener; + } + public void generatePendingTransactions() { pendingState.addPendingTransactions(new ArrayList<>(createTransactions(getBlockchain().getBestBlock()).values())); } @@ -237,6 +246,14 @@ public Block createBlock() { return createForkBlock(getBlockchain().getBestBlock()); } + public Block createBlock(byte[] minerCoinbase) { + byte[] curCoinbase = coinbase; + getBlockchain().setMinerCoinbase(minerCoinbase); + Block block = createForkBlock(getBlockchain().getBestBlock()); + getBlockchain().setMinerCoinbase(curCoinbase); + return block; + } + @Override public Block createForkBlock(Block parent) { try { @@ -259,12 +276,15 @@ public Block createForkBlock(Block parent) { } List pendingTxes = new ArrayList<>(txes.keySet()); - for (int i = 0; i < lastSummary.getReceipts().size(); i++) { - pendingTxes.get(i).txResult.receipt = lastSummary.getReceipts().get(i); - pendingTxes.get(i).txResult.executionSummary = getTxSummary(lastSummary, i); + // FIXME: Not sure it's correct for non-casper cases + // The issue is that Casper has background txs. It's not included in txs but it affects receipts + for (int i = 0; i < pendingTxes.size(); i++) { + final PendingTx currentTx = pendingTxes.get(i); + currentTx.txResult.receipt = lastSummary.getReceipts().get(i); + currentTx.txResult.executionSummary = getTxSummary(lastSummary, i); + submittedTxes.removeIf(tx -> tx == currentTx); // same object } - submittedTxes.clear(); return b; } catch (InterruptedException|ExecutionException e) { throw new RuntimeException(e); @@ -483,15 +503,14 @@ private BlockchainImpl createBlockchain(Genesis genesis) { listener = new CompositeEthereumListener(); BlockchainImpl blockchain = new BlockchainImpl(blockStore, repository) - .withEthereumListener(listener) - .withSyncManager(new SyncManager()); + .withEthereumListener(listener); blockchain.setParentHeaderValidator(new DependentBlockHeaderRuleAdapter()); blockchain.setProgramInvokeFactory(programInvokeFactory); blockchain.setPruneManager(pruneManager); blockchain.byTest = true; - pendingState = new PendingStateImpl(listener, blockchain); + pendingState = new PendingStateImpl(listener); pendingState.setBlockchain(blockchain); blockchain.setPendingState(pendingState); @@ -592,8 +611,8 @@ public Object[] callConstFunction(Block callBlock, String functionName, Object.. Repository repository = getBlockchain().getRepository().getSnapshotTo(callBlock.getStateRoot()).startTracking(); try { - org.ethereum.core.TransactionExecutor executor = new org.ethereum.core.TransactionExecutor - (tx, callBlock.getCoinbase(), repository, getBlockchain().getBlockStore(), + org.ethereum.core.TransactionExecutor executor = new CommonTransactionExecutor( + tx, callBlock.getCoinbase(), repository, getBlockchain().getBlockStore(), getBlockchain().getProgramInvokeFactory(), callBlock) .setLocalCall(true); diff --git a/ethereumj-core/src/main/resources/casper.conf b/ethereumj-core/src/main/resources/casper.conf new file mode 100644 index 0000000000..429b652067 --- /dev/null +++ b/ethereumj-core/src/main/resources/casper.conf @@ -0,0 +1,80 @@ +peer.discovery = { + enabled = false + + # List of the peers to start + # the search of the online peers + # values: [ip:port, ip:port, ip:port ...] + ip.list = [ + "54.167.247.63:30303", + "35.169.148.66:30303", + "34.239.105.125:30303", + "54.226.126.100:30303", + "34.201.104.186:30303", + "34.224.156.160:30303", + "54.226.243.26:30303", + "13.230.118.134:30303", + "18.196.33.71:30303", + "34.201.104.186:30303" +#"127.0.0.1:30308" + ] +} + +peer.active = [ + {url = "enode://d3260a710a752b926bb3328ebe29bfb568e4fb3b4c7ff59450738661113fb21f5efbdf42904c706a9f152275890840345a5bc990745919eeb2dfc2c481d778ee@54.167.247.63:30303"}, + {url = "enode://a120401858c93f0be73ae7765930174689cad026df332f7e06a047ead917cee193e9210e899c3143cce55dd991493227ecea15de42aa05b9b730d2189e19b567@35.169.148.66:30303"}, + {url = "enode://6f5210fab6b7530acd2a66da1c2568289bb065e6811673d5f24bf274491b037585da1ec854e172f8dcdf3e7c5dff4ef8a270c597bbc1928a43385586e1e9f431@34.239.105.125:30303"}, + {url = "enode://8d4a6aec3e340f9e859fd72ced95d2a0b49766772ac914810f6888bc2be5f95acf241aa94de8cd47a92dec1e8f96d86f4c052cc020e7c8e5438f6f18211dfeb1@54.226.126.100:30303"}, + {url = "enode://9e1b6f599f4aa29897c817dd4c68c11a60bb6388cf83d4606e82c6fc742363fb5b7e306c39f8887196f1c436274404503d50b7d023e99b53c8d97f197438c60b@34.201.104.186:30303"}, + {url = "enode://8cae2ed1214f394ae36d8a737066572c2f45a01b13aca8cc9a42e4425c7d8f8a362674d1b34f9417840e584ddb00eccf7fe53af51f5d1a32c31aab74b6ed423e@34.224.156.160:30303"}, + {url = "enode://e0e4620880078ba43a3c11bd19490f1cce6d7fdbf9e0fab7560b784409134405e0286881031016d8dad556ff89c8a729d5bcb87a7db91a2c047ad56bf19a3581@54.226.243.26:30303"}, + {url = "enode://ca9df451335b0bd48f6196675303b7173f8c69e63ebce6b8fb063a8e9d58156e13932f21f496aca6fd0b2ac6b34cf1276a098d5e94aa244cb0126b5a05a1d958@13.230.118.134:30303"}, + {url = "enode://58239d442b8167d59cdf22162778c94e2549ff5981c196dc4233b6ec3e860a698ad7bf4e11e8146b76ecedd6ff465f97b890ba89a9dee051c975ac7f322dbfd8@18.196.33.71:30303"}, + {url="enode://9e1b6f599f4aa29897c817dd4c68c11a60bb6388cf83d4606e82c6fc742363fb5b7e306c39f8887196f1c436274404503d50b7d023e99b53c8d97f197438c60b@34.201.104.186:30303"} +#{url = "enode://7a2e5857abb1786cc0c4cd135ec406c81e64457a4eec59555b9b32d20188497122adcac6b59e47beadb583d6de4c29bfa7a465a3c01c2eaaf35ec3d31d04d4a7@127.0.0.1:30308"} +] + +# Network id +peer.networkId = 1307 +peer.listen.port = 30301 + +# the folder resources/genesis +# contains several versions of +# genesis configuration according +# to the network the peer will run on +genesis = casper.json + +database { + # place to save physical storage files + dir = database-casper +} + +#vm.structured { +#trace = true +#dir = vmtrace +#compressed = true +#initStorageLimit = 10000 +#} + +blockchain.config.name = "casper" + +# Casper specific settings, used only with Casper strategy +casper { + # Casper contract BIN (required) + contractBin = "/casper/casper.bin" + + # Casper contract ABI (required) + contractAbi = "/casper/casper.abi" + + # Casper validator settings (required) + validator { + # Is Casper validator on (required) + enabled = false + # Validator coinbase (withdraw/deposit address) private key (optional) + # TODO: It should be something more secure + privateKey = "396ecd4115d4e20d4846060013823e1687b95960398f6f148f0fd64e70af13ac" + # Deposit in ethereum, 1500 is minimum (optional) + deposit = 2000 + } +} + +mine.start=false diff --git a/ethereumj-core/src/main/resources/casper/casper.abi b/ethereumj-core/src/main/resources/casper/casper.abi new file mode 100644 index 0000000000..46d0b96b9d --- /dev/null +++ b/ethereumj-core/src/main/resources/casper/casper.abi @@ -0,0 +1,726 @@ +[{ + "name": "Deposit", + "inputs": [{ + "type": "address", + "name": "_from", + "indexed": true + }, { + "type": "int128", + "name": "_validator_index", + "indexed": true + }, { + "type": "address", + "name": "_validation_address", + "indexed": false + }, { + "type": "int128", + "name": "_start_dyn", + "indexed": false + }, { + "type": "int128", + "name": "_amount", + "indexed": false + }], + "anonymous": false, + "type": "event" +}, { + "name": "Vote", + "inputs": [{ + "type": "address", + "name": "_from", + "indexed": true + }, { + "type": "int128", + "name": "_validator_index", + "indexed": true + }, { + "type": "bytes32", + "name": "_target_hash", + "indexed": true + }, { + "type": "int128", + "name": "_target_epoch", + "indexed": false + }, { + "type": "int128", + "name": "_source_epoch", + "indexed": false + }], + "anonymous": false, + "type": "event" +}, { + "name": "Logout", + "inputs": [{ + "type": "address", + "name": "_from", + "indexed": true + }, { + "type": "int128", + "name": "_validator_index", + "indexed": true + }, { + "type": "int128", + "name": "_end_dyn", + "indexed": false + }], + "anonymous": false, + "type": "event" +}, { + "name": "Withdraw", + "inputs": [{ + "type": "address", + "name": "_to", + "indexed": true + }, { + "type": "int128", + "name": "_validator_index", + "indexed": true + }, { + "type": "int128", + "name": "_amount", + "indexed": false + }], + "anonymous": false, + "type": "event" +}, { + "name": "Slash", + "inputs": [{ + "type": "address", + "name": "_from", + "indexed": true + }, { + "type": "address", + "name": "_offender", + "indexed": true + }, { + "type": "int128", + "name": "_offender_index", + "indexed": true + }, { + "type": "int128", + "name": "_bounty", + "indexed": false + }, { + "type": "int128", + "name": "_destroyed", + "indexed": false + }], + "anonymous": false, + "type": "event" +}, { + "name": "Epoch", + "inputs": [{ + "type": "int128", + "name": "_number", + "indexed": true + }, { + "type": "bytes32", + "name": "_checkpoint_hash", + "indexed": true + }, { + "type": "bool", + "name": "_is_justified", + "indexed": false + }, { + "type": "bool", + "name": "_is_finalized", + "indexed": false + }], + "anonymous": false, + "type": "event" +}, { + "name": "__init__", + "outputs": [], + "inputs": [{ + "type": "int128", + "name": "_epoch_length" + }, { + "type": "int128", + "name": "_withdrawal_delay" + }, { + "type": "int128", + "name": "_dynasty_logout_delay" + }, { + "type": "address", + "name": "_owner" + }, { + "type": "address", + "name": "_sighasher" + }, { + "type": "address", + "name": "_purity_checker" + }, { + "type": "decimal10", + "name": "_base_interest_factor" + }, { + "type": "decimal10", + "name": "_base_penalty_factor" + }, { + "type": "int128", + "name": "_min_deposit_size" + }], + "constant": false, + "payable": false, + "type": "constructor" +}, { + "name": "main_hash_voted_frac", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 2683 +}, { + "name": "deposit_size", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "validator_index" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 2691 +}, { + "name": "total_curdyn_deposits_scaled", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 2295 +}, { + "name": "total_prevdyn_deposits_scaled", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 2325 +}, { + "name": "recommended_source_epoch", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 603 +}, { + "name": "recommended_target_hash", + "outputs": [{ + "type": "bytes32", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 480 +}, { + "name": "initialize_epoch", + "outputs": [], + "inputs": [{ + "type": "int128", + "name": "epoch" + }], + "constant": false, + "payable": false, + "type": "function", + "gas": 359815 +}, { + "name": "deposit", + "outputs": [], + "inputs": [{ + "type": "address", + "name": "validation_addr" + }, { + "type": "address", + "name": "withdrawal_addr" + }], + "constant": false, + "payable": true, + "type": "function", + "gas": 672097 +}, { + "name": "logout", + "outputs": [], + "inputs": [{ + "type": "bytes", + "name": "logout_msg" + }], + "constant": false, + "payable": false, + "type": "function", + "gas": 759852 +}, { + "name": "withdraw", + "outputs": [], + "inputs": [{ + "type": "int128", + "name": "validator_index" + }], + "constant": false, + "payable": false, + "type": "function", + "gas": 89731 +}, { + "name": "vote", + "outputs": [], + "inputs": [{ + "type": "bytes", + "name": "vote_msg" + }], + "constant": false, + "payable": false, + "type": "function", + "gas": 1017478 +}, { + "name": "slash", + "outputs": [], + "inputs": [{ + "type": "bytes", + "name": "vote_msg_1" + }, { + "type": "bytes", + "name": "vote_msg_2" + }], + "constant": false, + "payable": false, + "type": "function", + "gas": 1582631 +}, { + "name": "owner_withdraw", + "outputs": [], + "inputs": [], + "constant": false, + "payable": false, + "type": "function", + "gas": 41080 +}, { + "name": "change_owner", + "outputs": [], + "inputs": [{ + "type": "address", + "name": "new_owner" + }], + "constant": false, + "payable": false, + "type": "function", + "gas": 21221 +}, { + "name": "validators__deposit", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1410 +}, { + "name": "validators__start_dynasty", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1440 +}, { + "name": "validators__end_dynasty", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1470 +}, { + "name": "validators__addr", + "outputs": [{ + "type": "address", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1494 +}, { + "name": "validators__withdrawal_addr", + "outputs": [{ + "type": "address", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1530 +}, { + "name": "checkpoint_hashes", + "outputs": [{ + "type": "bytes32", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1482 +}, { + "name": "next_validator_index", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1323 +}, { + "name": "validator_indexes", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [{ + "type": "address", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1491 +}, { + "name": "dynasty", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1383 +}, { + "name": "dynasty_wei_delta", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1602 +}, { + "name": "dynasty_start_epoch", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1632 +}, { + "name": "dynasty_in_epoch", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1662 +}, { + "name": "votes__cur_dyn_votes", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }, { + "type": "int128", + "name": "arg1" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1941 +}, { + "name": "votes__prev_dyn_votes", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }, { + "type": "int128", + "name": "arg1" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1977 +}, { + "name": "votes__vote_bitmap", + "outputs": [{ + "type": "uint256", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }, { + "type": "bytes32", + "name": "arg1" + }, { + "type": "int128", + "name": "arg2" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 2094 +}, { + "name": "votes__is_justified", + "outputs": [{ + "type": "bool", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1860 +}, { + "name": "votes__is_finalized", + "outputs": [{ + "type": "bool", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1890 +}, { + "name": "main_hash_justified", + "outputs": [{ + "type": "bool", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1653 +}, { + "name": "deposit_scale_factor", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [{ + "type": "int128", + "name": "arg0" + }], + "constant": true, + "payable": false, + "type": "function", + "gas": 1872 +}, { + "name": "last_nonvoter_rescale", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1713 +}, { + "name": "last_voter_rescale", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1743 +}, { + "name": "current_epoch", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1773 +}, { + "name": "last_finalized_epoch", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1803 +}, { + "name": "last_justified_epoch", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1833 +}, { + "name": "expected_source_epoch", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1863 +}, { + "name": "epoch_length", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1893 +}, { + "name": "withdrawal_delay", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1923 +}, { + "name": "dynasty_logout_delay", + "outputs": [{ + "type": "int128", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1953 +}, { + "name": "reward_factor", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 1983 +}, { + "name": "base_interest_factor", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 2013 +}, { + "name": "base_penalty_factor", + "outputs": [{ + "type": "decimal10", + "name": "out" + }], + "inputs": [], + "constant": true, + "payable": false, + "type": "function", + "gas": 2043 +}] \ No newline at end of file diff --git a/ethereumj-core/src/main/resources/casper/casper.bin b/ethereumj-core/src/main/resources/casper/casper.bin new file mode 100644 index 0000000000..a6c615b867 --- /dev/null +++ b/ethereumj-core/src/main/resources/casper/casper.bin @@ -0,0 +1 @@ +600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a0526101206145686101403934156100a857600080fd5b606051602061456860c03960c051806040519013585780919012156100cc57600080fd5b50606051602060206145680160c03960c051806040519013585780919012156100f457600080fd5b50606051602060406145680160c03960c0518060405190135857809190121561011c57600080fd5b50602060606145680160c03960c051602051811061013957600080fd5b50602060806145680160c03960c051602051811061015657600080fd5b50602060a06145680160c03960c051602051811061017357600080fd5b5060605160206101006145680160c03960c0518060405190135857809190121561019c57600080fd5b506101405160145561016051601555610180516016556101a05160175561020051601b5561022051601c5561024051601d556101c0516018556101e051601955600160025568056bc75e2d63100000600c60c052602060c020556000600455600060a0516014548061020d57600080fd5b6402540be4004302058060805190135857809190121561022c57600080fd5b1215610274576402540be4006402540be3ff60a0516014548061024e57600080fd5b6402540be4004302058060805190135857809190121561026d57600080fd5b03056102ab565b6402540be40060a0516014548061028a57600080fd5b6402540be400430205806080519013585780919012156102a957600080fd5b055b600f55600060065560006007556c0c9f2c9cd04674edea40000000601e5561455056600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a0526337df0ba8600051141561017b5734156100ac57600080fd5b60a051601254600f54600a60c052602060c0200160c052602060c02060c052602060c0200154600654806100df57600080fd5b806402540be400830205905090508060805190135857809190121561010357600080fd5b60a0516012546003600f54600a60c052602060c0200160c052602060c0200160c052602060c02001546007548061013957600080fd5b806402540be400830205905090508060805190135857809190121561015d57600080fd5b8082131561016b578061016d565b815b9050905060005260206000f3005b6399fb5eec6000511415610315576020600461014037341561019c57600080fd5b606051600435806040519013585780919012156101b857600080fd5b50600060a051600f54600c60c052602060c0200154600161014051600060c052602060c0200160c052602060c020015481810281198383830514176101fc57600080fd5b6402540be40081059050905090508060805190135857809190121561022057600080fd5b121561029e576402540be4006402540be3ff60a051600f54600c60c052602060c0200154600161014051600060c052602060c0200160c052602060c0200154818102811983838305141761027357600080fd5b6402540be40081059050905090508060805190135857809190121561029757600080fd5b030561030b565b6402540be40060a051600f54600c60c052602060c0200154600161014051600060c052602060c0200160c052602060c020015481810281198383830514176102e557600080fd5b6402540be40081059050905090508060805190135857809190121561030957600080fd5b055b60005260206000f3005b638ed137b0600051141561044257341561032e57600080fd5b600060a051600f54600c60c052602060c0200154600654818102811983838305141761035957600080fd5b6402540be40081059050905090508060805190135857809190121561037d57600080fd5b12156103e3576402540be4006402540be3ff60a051600f54600c60c052602060c020015460065481810281198383830514176103b857600080fd5b6402540be4008105905090509050806080519013585780919012156103dc57600080fd5b0305610438565b6402540be40060a051600f54600c60c052602060c0200154600654818102811983838305141761041257600080fd5b6402540be40081059050905090508060805190135857809190121561043657600080fd5b055b60005260206000f3005b6385f44a88600051141561056f57341561045b57600080fd5b600060a051600f54600c60c052602060c0200154600754818102811983838305141761048657600080fd5b6402540be4008105905090509050806080519013585780919012156104aa57600080fd5b1215610510576402540be4006402540be3ff60a051600f54600c60c052602060c020015460075481810281198383830514176104e557600080fd5b6402540be40081059050905090508060805190135857809190121561050957600080fd5b0305610565565b6402540be40060a051600f54600c60c052602060c0200154600754818102811983838305141761053f57600080fd5b6402540be40081059050905090508060805190135857809190121561056357600080fd5b055b60005260206000f3005b6310f24635600051141561059557341561058857600080fd5b60125460005260206000f3005b63855f372c60005114156106125734156105ae57600080fd5b6060516001606051601454600f5402806040519013585780919012156105d357600080fd5b03806040519013585780919012156105ea57600080fd5b61010043038112156105fb57600080fd5b43811061060757600080fd5b4060005260206000f3005b63de7f9975600051141561064e57341561062b57600080fd5b30331461063757600080fd5b6000600754136000600654131660005260206000f3005b63d286bb82600051141561076957341561066757600080fd5b30331461067357600080fd5b600f54610140526001606051600261014051038060405190135857809190121561069c57600080fd5b600a60c052602060c0200160c052602060c0200154156107235760046060516001825401806040519013585780919012156106d657600080fd5b815550600654600755600660a051600454600560c052602060c02001548254018060805190135857809190121561070c57600080fd5b81555061014051600454600860c052602060c02001555b60045461014051600960c052602060c0200155600b541561076257606051600161014051038060405190135857809190121561075e57600080fd5b6012555b6000600b55005b639a8eec7760005114156107b857341561078257600080fd5b30331461078e57600080fd5b606051601054600f5403806040519013585780919012156107ae57600080fd5b60005260206000f3005b63acc619c360005114156109c85734156107d157600080fd5b3033146107dd57600080fd5b600f5461014052600260206101e06004639a8eec776101805261019c6000305af161080757600080fd5b6101e05113156101605261016051156020610260600463de7f99756102005261021c6000305af161083757600080fd5b6102605115171561084d57600060005260206000f35b60a051601254606051600161014051038060405190135857809190121561087357600080fd5b600a60c052602060c0200160c052602060c02060c052602060c02001546006548061089d57600080fd5b806402540be40083020590509050806080519013585780919012156108c157600080fd5b6102805260a051601254600360605160016101405103806040519013585780919012156108ed57600080fd5b600a60c052602060c0200160c052602060c0200160c052602060c02001546007548061091857600080fd5b806402540be400830205905090508060805190135857809190121561093c57600080fd5b6102a052610280516102a051808213156109565780610958565b815b905090506102c05260a051600260a051601a546102c051818102811983838305141761098357600080fd5b6402540be4008105905090509050806080519013585780919012156109a757600080fd5b05806080519013585780919012156109be57600080fd5b60005260206000f3005b636526ed726000511415610b465734156109e157600080fd5b3033146109ed57600080fd5b600f54610140526001600b55600160026060516001610140510380604051901358578091901215610a1d57600080fd5b600a60c052602060c0200160c052602060c0200155600160016060516001610140510380604051901358578091901215610a5657600080fd5b600a60c052602060c0200160c052602060c02001556060516001610140510380604051901358578091901215610a8b57600080fd5b6011556060516001610140510380604051901358578091901215610aae57600080fd5b60105560016101a05260016101c05260406101605260016101a05260016101c0526060516001610140510380604051901358578091901215610aef57600080fd5b600160c052602060c02001546060516001610140510380604051901358578091901215610b1b57600080fd5b7f6940a3069a76fdb79d757f4dca548d7930f85e4bce3a3e2d06f5562bda0b10b9610160516101a0a3005b63db23eee06000511415610e76573415610b5f57600080fd5b303314610b6b57600080fd5b600f54610140526060516001600060a051670de0b6b3a764000060a0516060516001610140510380604051901358578091901215610ba857600080fd5b600c60c052602060c020015460075460065480821215610bc85780610bca565b815b905090508181028119838383051417610be257600080fd5b6402540be400810590509050905080608051901358578091901215610c0657600080fd5b0580608051901358578091901215610c1d57600080fd5b1215610cda576402540be4006402540be3ff60a051670de0b6b3a764000060a0516060516001610140510380604051901358578091901215610c5e57600080fd5b600c60c052602060c020015460075460065480821215610c7e5780610c80565b815b905090508181028119838383051417610c9857600080fd5b6402540be400810590509050905080608051901358578091901215610cbc57600080fd5b0580608051901358578091901215610cd357600080fd5b0305610d86565b6402540be40060a051670de0b6b3a764000060a0516060516001610140510380604051901358578091901215610d0f57600080fd5b600c60c052602060c020015460075460065480821215610d2f5780610d31565b815b905090508181028119838383051417610d4957600080fd5b6402540be400810590509050905080608051901358578091901215610d6d57600080fd5b0580608051901358578091901215610d8457600080fd5b055b0180604051901358578091901215610d9d57600080fd5b6101605260a0516404a817c80068056bc75e2d6310000061016051020580608051901358578091901215610dd057600080fd5b610180526101a060006014818352015b60a051600260a05160a0516101805180610df957600080fd5b68056bc75e2d6310000061016051020580608051901358578091901215610e1f57600080fd5b610180510180608051901358578091901215610e3a57600080fd5b0580608051901358578091901215610e5157600080fd5b610180525b8151600101808352811415610de0575b50506101805160005260206000f3005b635dcffc1760005114156112b35760206004610140373415610e9757600080fd5b60605160043580604051901358578091901215610eb357600080fd5b50600060a05160145480610ec657600080fd5b6402540be40043020580608051901358578091901215610ee557600080fd5b1215610f2d576402540be4006402540be3ff60a05160145480610f0757600080fd5b6402540be40043020580608051901358578091901215610f2657600080fd5b0305610f64565b6402540be40060a05160145480610f4357600080fd5b6402540be40043020580608051901358578091901215610f6257600080fd5b055b610160526060516001600f540180604051901358578091901215610f8757600080fd5b61014051146101605161014051131516610fa057600080fd5b61014051600f5560a05160206101e0600463acc619c36101805261019c6000305af1610fcb57600080fd5b6101e0516402540be4000180608051901358578091901215610fec57600080fd5b600e5560a051600e5460a051601a546402540be400018060805190135857809190121561101857600080fd5b8061102257600080fd5b806402540be400830205905090508060805190135857809190121561104657600080fd5b600d5560a051600d54606051600161014051038060405190135857809190121561106f57600080fd5b600c60c052602060c0200154818102811983838305141761108f57600080fd5b6402540be4008105905090509050806080519013585780919012156110b357600080fd5b61014051600c60c052602060c020015560206102c0600463de7f99756102605261027c6000305af16110e457600080fd5b6102c051156111db5760a051601b546020610360600463db23eee06103005261031c6000305af161111457600080fd5b610360518061112257600080fd5b806402540be400830205905090508060805190135857809190121561114657600080fd5b6102e05260a05160a05160206103e06004639a8eec776103805261039c6000305af161117157600080fd5b6103e051601c54818102811983838305141761118c57600080fd5b80905090509050806080519013585780919012156111a957600080fd5b6102e05101806080519013585780919012156111c457600080fd5b601a556000601a54136111d657600080fd5b611201565b600060006004636526ed726102005261021c6000305af16111fb57600080fd5b6000601a555b60006000600463d286bb826104005261041c6000305af161122157600080fd5b60206104c0600463855f372c6104605261047c6000305af161124257600080fd5b6104c05161014051600160c052602060c020015560006105205260006105405260406104e05260006105205260006105405261014051600160c052602060c0200154610140517f6940a3069a76fdb79d757f4dca548d7930f85e4bce3a3e2d06f5562bda0b10b96104e051610520a3005b63f9609f08600051141561167b57604060046101403760043560205181106112da57600080fd5b5060243560205181106112ec57600080fd5b50600060a051601454806112ff57600080fd5b6402540be4004302058060805190135857809190121561131e57600080fd5b1215611366576402540be4006402540be3ff60a0516014548061134057600080fd5b6402540be4004302058060805190135857809190121561135f57600080fd5b030561139d565b6402540be40060a0516014548061137c57600080fd5b6402540be4004302058060805190135857809190121561139b57600080fd5b055b600f54146113aa57600080fd5b600060006004610180527fa1903eab000000000000000000000000000000000000000000000000000000006101a0526101806004806020846101e001018260208501600060046012f1505080518201915050610140516020826101e0010152602081019050806101e0526101e09050805160200180610260828460006004600a8704601201f161143957600080fd5b505060206103006102605161028060006019546207a120f161145a57600080fd5b60206102e0526102e060206000602083510381131561147857600080fd5b046020026020018101519050141561148f57600080fd5b61016051600360c052602060c0200154156114a957600080fd5b601d543412156114b857600080fd5b606051600260045401806040519013585780919012156114d757600080fd5b6103405260a051600f54600c60c052602060c0200154806114f757600080fd5b68056bc75e2d631000003402058060805190135857809190121561151a57600080fd5b61036052600254600060c052602060c0200160c052602060c020610140518155610360516001820155601e5460028201556103405160038201556101605160048201555060025461016051600360c052602060c0200155600260605160018254018060405190135857809190121561159157600080fd5b81555061034051600560c052602060c0200160a05161036051825401806080519013585780919012156115c357600080fd5b815550610140516103c052600361016051600360c052602060c0200154600060c052602060c0200160c052602060c02001546103e0523461040052606061038052610140516103c052600361016051600360c052602060c0200154600060c052602060c0200160c052602060c02001546103e052346104005261016051600360c052602060c0200154610160517fc913dcae46368ac8a73eb63d4f2077a2de58e994bf2cddf074ab3a0fcaa3a0e1610380516103c0a3005b6342310c326000511415611b7e576020600461014037341561169c57600080fd5b610420600435600401610160376104006004356004013511156116be57600080fd5b600060a051601454806116d057600080fd5b6402540be400430205806080519013585780919012156116ef57600080fd5b1215611737576402540be4006402540be3ff60a0516014548061171157600080fd5b6402540be4004302058060805190135857809190121561173057600080fd5b030561176e565b6402540be40060a0516014548061174d57600080fd5b6402540be4004302058060805190135857809190121561176c57600080fd5b055b600f541461177b57600080fd5b6101608051602001806105c0828460006004600a8704601201f161179e57600080fd5b50506020610a206105c0516105e0600060185462030d40f16117bf57600080fd5b6020610a0052610a006020600060208351038113156117dd57600080fd5b0460200260200181015190506105a052611000610160610560610a608251602084016000735185d17c44699cecc3133114f8df70753b856709610aa0f150506080610a60511461182c57600080fd5b610a6051610a600180602001516000825180602090135857809190121561185257600080fd5b601f6101000a820481151761186657600080fd5b606051816020036101000a83048060405190135857809190121561188957600080fd5b9050905090508152610a8051610a60018060200151600082518060209013585780919012156118b757600080fd5b601f6101000a82048115176118cb57600080fd5b606051816020036101000a8304806040519013585780919012156118ee57600080fd5b9050905090508160200152610aa051610a600180516020018083604001828460006004600a8704601201f161192257600080fd5b5050506110005161148052611020516114a0526110408051602001806114c0828460006004600a8704601201f161195857600080fd5b50506114a051600f54121561196c57600080fd5b600160006105a0516020826119000101526020810190506114c06104008060208461190001018260208501600060046078f150508051820191505080611900526119009050805160200180611d60828460006004600a8704601201f16119d157600080fd5b505060206121e0611d6051611d80600061148051600060c052602060c0200160c052602060c020546207a120f1611a0757600080fd5b60206121c0526121c0602060006020835103811315611a2557600080fd5b04602002602001810151905014611a3b57600080fd5b6060516016546004540180604051901358578091901215611a5b57600080fd5b6122205261222051600261148051600060c052602060c0200160c052602060c020015413611a8857600080fd5b61222051600261148051600060c052602060c0200160c052602060c020015561222051600560c052602060c0200160a051600161148051600060c052602060c0200160c052602060c020015482540380608051901358578091901215611aed57600080fd5b815550600261148051600060c052602060c0200160c052602060c020015461228052602061224052600261148051600060c052602060c0200160c052602060c02001546122805261148051600461148051600060c052602060c0200160c052602060c02001547fb0da0cd4d3f901fa403fa301bd3cad4934e079e9f1a5b61bdb33a03da2c9aaaf61224051612280a3005b6301b7af186000511415611c2b5760206004610140373415611b9f57600080fd5b303314611bab57600080fd5b60605160043580604051901358578091901215611bc757600080fd5b506000600461014051600060c052602060c0200160c052602060c0200154600360c052602060c020015561014051600060c052602060c0200160c052602060c020600081556000600182015560006002820155600060038201556000600482015550005b63edea14806000511415611f385760206004610140373415611c4c57600080fd5b60605160043580604051901358578091901215611c6857600080fd5b506060516001600261014051600060c052602060c0200160c052602060c02001540180604051901358578091901215611ca057600080fd5b6004541215611cae57600080fd5b6060516001600261014051600060c052602060c0200160c052602060c02001540180604051901358578091901215611ce557600080fd5b600860c052602060c020015461016052606051601554610160510180604051901358578091901215611d1657600080fd5b600f541215611d2457600080fd5b600060a05161016051600c60c052602060c0200154600161014051600060c052602060c0200160c052602060c02001548181028119838383051417611d6857600080fd5b6402540be400810590509050905080608051901358578091901215611d8c57600080fd5b1215611e0b576402540be4006402540be3ff60a05161016051600c60c052602060c0200154600161014051600060c052602060c0200160c052602060c02001548181028119838383051417611de057600080fd5b6402540be400810590509050905080608051901358578091901215611e0457600080fd5b0305611e79565b6402540be40060a05161016051600c60c052602060c0200154600161014051600060c052602060c0200160c052602060c02001548181028119838383051417611e5357600080fd5b6402540be400810590509050905080608051901358578091901215611e7757600080fd5b055b61018052600060006000600061018051600461014051600060c052602060c0200160c052602060c02001546000f1611eb057600080fd5b610180516101e05260206101a052610180516101e05261014051600461014051600060c052602060c0200160c052602060c02001547f499b9fc824d01426cfde5b95eebcfc53494a24d6316bb139b2333377f7c00e476101a0516101e0a36000600060246301b7af1861020052610140516102205261021c6000305af1611f3657600080fd5b005b63579f38b260005114156122825760406004610140373415611f5957600080fd5b303314611f6557600080fd5b60605160043580604051901358578091901215611f8157600080fd5b5060605160243580604051901358578091901215611f9e57600080fd5b50600161014051600060c052602060c0200160c052602060c0200160a0516402540be400610160510282540180608051901358578091901215611fe057600080fd5b815550600361014051600060c052602060c0200160c052602060c020015461018052600261014051600060c052602060c0200160c052602060c02001546101a0526004546101c05260605160016101c051038060405190135857809190121561204857600080fd5b6101e0526101a0516101c051126101c051610180511315161561209257600660a0516402540be40061016051028254018060805190135857809190121561208e57600080fd5b8155505b6101a0516101e051126101e05161018051131516156120d857600760a0516402540be4006101605102825401806080519013585780919012156120d457600080fd5b8155505b601e546101a051121561211f576101a051600560c052602060c0200160a0516402540be40061016051028254038060805190135857809190121561211b57600080fd5b8155505b6000600060006000600060a051600860a051600f54600c60c052602060c020015461016051818102811983838305141761215857600080fd5b809050905090508060805190135857809190121561217557600080fd5b058060805190135857809190121561218c57600080fd5b1215612208576402540be4006402540be3ff60a051600860a051600f54600c60c052602060c02001546101605181810281198383830514176121cd57600080fd5b80905090509050806080519013585780919012156121ea57600080fd5b058060805190135857809190121561220157600080fd5b0305612273565b6402540be40060a051600860a051600f54600c60c052602060c020015461016051818102811983838305141761223d57600080fd5b809050905090508060805190135857809190121561225a57600080fd5b058060805190135857809190121561227157600080fd5b055b416000f161228057600080fd5b005b63e9dc06146000511415612f4e57602060046101403734156122a357600080fd5b610420600435600401610160376104006004356004013511156122c557600080fd5b6101608051602001806105c0828460006004600a8704601201f16122e857600080fd5b50506020610a206105c0516105e0600060185462030d40f161230957600080fd5b6020610a0052610a0060206000602083510381131561232757600080fd5b0460200260200181015190506105a0526110c0610160610620610a608251602084016000735185d17c44699cecc3133114f8df70753b856709610dc0f1505060c0610a60511461237657600080fd5b610a6051610a600180602001516000825180602090135857809190121561239c57600080fd5b601f6101000a82048115176123b057600080fd5b606051816020036101000a8304806040519013585780919012156123d357600080fd5b90509050905081526020610a8051610a600151146123f057600080fd5b610a8051610a8001518160200152610aa051610a600180602001516000825180602090135857809190121561242457600080fd5b601f6101000a820481151761243857600080fd5b606051816020036101000a83048060405190135857809190121561245b57600080fd5b9050905090508160400152610ac051610a600180602001516000825180602090135857809190121561248c57600080fd5b601f6101000a82048115176124a057600080fd5b606051816020036101000a8304806040519013585780919012156124c357600080fd5b9050905090508160600152610ae051610a600180516020018083608001828460006004600a8704601201f16124f757600080fd5b5050506110c051611580526110e0516115a052611100516115c052611120516115e052611140805160200180611600828460006004600a8704601201f161253d57600080fd5b5050600160006105a051602082611a4001015260208101905061160061040080602084611a4001018260208501600060046078f150508051820191505080611a4052611a409050805160200180611ea0828460006004600a8704601201f16125a457600080fd5b50506020612320611ea051611ec0600061158051600060c052602060c0200160c052602060c020546207a120f16125da57600080fd5b6020612300526123006020600060208351038113156125f857600080fd5b0460200260200181015190501461260e57600080fd5b600160605161010061158051078060405190135857809190121561263157600080fd5b600081131515612649578060000360020a8204612650565b8060020a82025b90509050600060a0516101006402540be4006115805102058060805190135857809190121561267e57600080fd5b12156126bf576402540be4006402540be3ff60a0516101006402540be400611580510205806080519013585780919012156126b857600080fd5b03056126ef565b6402540be40060a0516101006402540be400611580510205806080519013585780919012156126ed57600080fd5b055b6115a05160046115c051600a60c052602060c0200160c052602060c0200160c052602060c0200160c052602060c0200154161561272b57600080fd5b60206123c0600463855f372c6123605261237c6000305af161274c57600080fd5b6123c0516115a0511461275e57600080fd5b600f546115c0511461276f57600080fd5b60026115e051600a60c052602060c0200160c052602060c020015461279357600080fd5b600361158051600060c052602060c0200160c052602060c02001546123e052600261158051600060c052602060c0200160c052602060c0200154612400526115c051600960c052602060c020015461242052606051600161242051038060405190135857809190121561280557600080fd5b61244052612400516124205112612420516123e05113151661246052612400516124405112612440516123e0511315166124805261248051612460511761284b57600080fd5b600160605161010061158051078060405190135857809190121561286e57600080fd5b600081131515612886578060000360020a820461288d565b8060020a82025b90509050600060a0516101006402540be400611580510205806080519013585780919012156128bb57600080fd5b12156128fc576402540be4006402540be3ff60a0516101006402540be400611580510205806080519013585780919012156128f557600080fd5b030561292c565b6402540be40060a0516101006402540be4006115805102058060805190135857809190121561292a57600080fd5b055b6115a05160046115c051600a60c052602060c0200160c052602060c0200160c052602060c0200160c052602060c020015417600060a0516101006402540be4006115805102058060805190135857809190121561298857600080fd5b12156129c9576402540be4006402540be3ff60a0516101006402540be400611580510205806080519013585780919012156129c257600080fd5b03056129f9565b6402540be40060a0516101006402540be400611580510205806080519013585780919012156129f757600080fd5b055b6115a05160046115c051600a60c052602060c0200160c052602060c0200160c052602060c0200160c052602060c02001556115e0516115c051600a60c052602060c0200160c052602060c02060c052602060c02001546124a0526115e05160036115c051600a60c052602060c0200160c052602060c0200160c052602060c02001546124c0526124605115612aef576124a060a051600161158051600060c052602060c0200160c052602060c020015482510180608051901358578091901215612ac257600080fd5b8152506124a0516115e0516115c051600a60c052602060c0200160c052602060c02060c052602060c02001555b6124805115612b62576124c060a051600161158051600060c052602060c0200160c052602060c020015482510180608051901358578091901215612b3257600080fd5b8152506124c0516115e05160036115c051600a60c052602060c0200160c052602060c0200160c052602060c02001555b6115e0516012541415612cd257600060a051601a54600161158051600060c052602060c0200160c052602060c02001548181028119838383051417612ba657600080fd5b6402540be400810590509050905080608051901358578091901215612bca57600080fd5b1215612c3c576402540be4006402540be3ff60a051601a54600161158051600060c052602060c0200160c052602060c02001548181028119838383051417612c1157600080fd5b6402540be400810590509050905080608051901358578091901215612c3557600080fd5b0305612c9d565b6402540be40060a051601a54600161158051600060c052602060c0200160c052602060c02001548181028119838383051417612c7757600080fd5b6402540be400810590509050905080608051901358578091901215612c9b57600080fd5b055b6124e05260006000604463579f38b26125005261158051612520526124e0516125405261251c6000305af1612cd157600080fd5b5b60026115c051600a60c052602060c0200160c052602060c02001541560a051600360a05160026007548181028119838383051417612d0f57600080fd5b8090509050905080608051901358578091901215612d2c57600080fd5b0580608051901358578091901215612d4357600080fd5b6124c051121560a051600360a05160026006548181028119838383051417612d6a57600080fd5b8090509050905080608051901358578091901215612d8757600080fd5b0580608051901358578091901215612d9e57600080fd5b6124a0511215161615612eda57600160026115c051600a60c052602060c0200160c052602060c02001556115c0516011556001600b5560016125e05260006126005260406125a05260016125e0526000612600526115c051600160c052602060c02001546115c0517f6940a3069a76fdb79d757f4dca548d7930f85e4bce3a3e2d06f5562bda0b10b96125a0516125e0a360605160016115e0510180604051901358578091901215612e4f57600080fd5b6115c0511415612ed957600160016115e051600a60c052602060c0200160c052602060c02001556115e0516010556001612660526001612680526040612620526001612660526001612680526115e051600160c052602060c02001546115e0517f6940a3069a76fdb79d757f4dca548d7930f85e4bce3a3e2d06f5562bda0b10b961262051612660a35b5b6115c0516126e0526115e0516127005260406126a0526115c0516126e0526115e051612700526115a05161158051600461158051600060c052602060c0200160c052602060c02001547f0fa814a7606272ed601db3d9974a12a26536f050d147ad209b7e7b0a95746f176126a0516126e0a4005b63cc20f16b600051141561395e5760406004610140373415612f6f57600080fd5b61042060043560040161018037610400600435600401351115612f9157600080fd5b6104206024356004016105c037610400602435600401351115612fb357600080fd5b610180805160200180610a20828460006004600a8704601201f1612fd657600080fd5b50506020610e80610a2051610a40600060185462030d40f1612ff757600080fd5b6020610e6052610e6060206000602083510381131561301557600080fd5b046020026020018101519050610a0052611520610180610620610ec08251602084016000735185d17c44699cecc3133114f8df70753b856709610dc0f1505060c0610ec0511461306457600080fd5b610ec051610ec00180602001516000825180602090135857809190121561308a57600080fd5b601f6101000a820481151761309e57600080fd5b606051816020036101000a8304806040519013585780919012156130c157600080fd5b90509050905081526020610ee051610ec00151146130de57600080fd5b610ee051610ee001518160200152610f0051610ec00180602001516000825180602090135857809190121561311257600080fd5b601f6101000a820481151761312657600080fd5b606051816020036101000a83048060405190135857809190121561314957600080fd5b9050905090508160400152610f2051610ec00180602001516000825180602090135857809190121561317a57600080fd5b601f6101000a820481151761318e57600080fd5b606051816020036101000a8304806040519013585780919012156131b157600080fd5b9050905090508160600152610f4051610ec00180516020018083608001828460006004600a8704601201f16131e557600080fd5b505050611520516119e05261156051611a005261158051611a20526115a0805160200180611a40828460006004600a8704601201f161322357600080fd5b505060016000610a0051602082611e80010152602081019050611a4061040080602084611e8001018260208501600060046078f150508051820191505080611e8052611e8090508051602001806122e0828460006004600a8704601201f161328a57600080fd5b505060206127606122e05161230060006119e051600060c052602060c0200160c052602060c020546207a120f16132c057600080fd5b6020612740526127406020600060208351038113156132de57600080fd5b046020026020018101519050146132f457600080fd5b6105c08051602001806127c0828460006004600a8704601201f161331757600080fd5b50506020612c206127c0516127e0600060185462030d40f161333857600080fd5b6020612c0052612c0060206000602083510381131561335657600080fd5b0460200260200181015190506127a0526132c06105c0610620612c608251602084016000735185d17c44699cecc3133114f8df70753b856709610dc0f1505060c0612c6051146133a557600080fd5b612c6051612c60018060200151600082518060209013585780919012156133cb57600080fd5b601f6101000a82048115176133df57600080fd5b606051816020036101000a83048060405190135857809190121561340257600080fd5b90509050905081526020612c8051612c6001511461341f57600080fd5b612c8051612c8001518160200152612ca051612c600180602001516000825180602090135857809190121561345357600080fd5b601f6101000a820481151761346757600080fd5b606051816020036101000a83048060405190135857809190121561348a57600080fd5b9050905090508160400152612cc051612c60018060200151600082518060209013585780919012156134bb57600080fd5b601f6101000a82048115176134cf57600080fd5b606051816020036101000a8304806040519013585780919012156134f257600080fd5b9050905090508160600152612ce051612c600180516020018083608001828460006004600a8704601201f161352657600080fd5b5050506132c05161378052613300516137a052613320516137c0526133408051602001806137e0828460006004600a8704601201f161356457600080fd5b5050600160006127a051602082613c200101526020810190506137e061040080602084613c2001018260208501600060046078f150508051820191505080613c2052613c209050805160200180614080828460006004600a8704601201f16135cb57600080fd5b50506020614500614080516140a0600061378051600060c052602060c0200160c052602060c020546207a120f161360157600080fd5b60206144e0526144e060206000602083510381131561361f57600080fd5b0460200260200181015190501461363557600080fd5b613780516119e0511461364757600080fd5b6127a051610a0051141561365a57600080fd5b6000614540526137a051611a00511415613679576001614540526136ad565b611a20516137c05112611a00516137a05113166137c051611a2051126137a051611a0051131617156136ac576001614540525b5b614540516136ba57600080fd5b602061460060246399fb5eec614580526119e0516145a05261459c6000305af16136e357600080fd5b6146005161456052600060a05160196402540be4006145605102058060805190135857809190121561371457600080fd5b1215613754576402540be4006402540be3ff60a05160196402540be4006145605102058060805190135857809190121561374d57600080fd5b0305613783565b6402540be40060a05160196402540be4006145605102058060805190135857809190121561378157600080fd5b055b61462052606051614620516145605103806040519013585780919012156137a957600080fd5b61464052601360605161464051825401806040519013585780919012156137cf57600080fd5b815550614620516146a052614640516146c052604061466052614620516146a052614640516146c0526119e05160046119e051600060c052602060c0200160c052602060c0200154337fb63bb46c05149b79f409b978407acff3bb3bfb5fb728ed2909fdb52677c220da614660516146a0a460016119e051600060c052602060c0200160c052602060c02001546146e0526060516001600454018060405190135857809190121561387f57600080fd5b600560c052602060c0200160a0516146e051825403806080519013585780919012156138aa57600080fd5b815550601e5460026119e051600060c052602060c0200160c052602060c0200154121561391b5760026119e051600060c052602060c0200160c052602060c0200154600560c052602060c0200160a0516146e0518254018060805190135857809190121561391757600080fd5b8155505b6000600060246301b7af18614700526119e0516147205261471c6000305af161394357600080fd5b600060006000600061462051336000f161395c57600080fd5b005b6334ef39f1600051141561399857341561397757600080fd5b60006000600060006013546017546000f161399157600080fd5b6000601355005b63253c8bd460005114156139df57602060046101403734156139b957600080fd5b60043560205181106139ca57600080fd5b503360175414156139dd57610140516017555b005b63bcc44d996000511415613a425760206004610140373415613a0057600080fd5b60605160043580604051901358578091901215613a1c57600080fd5b50600161014051600060c052602060c0200160c052602060c020015460005260206000f3005b63faf944fa6000511415613aa55760206004610140373415613a6357600080fd5b60605160043580604051901358578091901215613a7f57600080fd5b50600361014051600060c052602060c0200160c052602060c020015460005260206000f3005b63c253bf416000511415613b085760206004610140373415613ac657600080fd5b60605160043580604051901358578091901215613ae257600080fd5b50600261014051600060c052602060c0200160c052602060c020015460005260206000f3005b63bb0115f06000511415613b685760206004610140373415613b2957600080fd5b60605160043580604051901358578091901215613b4557600080fd5b5061014051600060c052602060c0200160c052602060c0205460005260206000f3005b635dbfd1ce6000511415613bcb5760206004610140373415613b8957600080fd5b60605160043580604051901358578091901215613ba557600080fd5b50600461014051600060c052602060c0200160c052602060c020015460005260206000f3005b638208b8756000511415613c235760206004610140373415613bec57600080fd5b60605160043580604051901358578091901215613c0857600080fd5b5061014051600160c052602060c020015460005260206000f3005b637d69c5d96000511415613c49573415613c3c57600080fd5b60025460005260206000f3005b63dc2059b36000511415613c965760206004610140373415613c6a57600080fd5b6004356020518110613c7b57600080fd5b5061014051600360c052602060c020015460005260206000f3005b637060054d6000511415613cbc573415613caf57600080fd5b60045460005260206000f3005b63c44dc6596000511415613d145760206004610140373415613cdd57600080fd5b60605160043580604051901358578091901215613cf957600080fd5b5061014051600560c052602060c020015460005260206000f3005b63a2630bae6000511415613d6c5760206004610140373415613d3557600080fd5b60605160043580604051901358578091901215613d5157600080fd5b5061014051600860c052602060c020015460005260206000f3005b6365ca80e66000511415613dc45760206004610140373415613d8d57600080fd5b60605160043580604051901358578091901215613da957600080fd5b5061014051600960c052602060c020015460005260206000f3005b63fd87ef9d6000511415613e4e5760406004610140373415613de557600080fd5b60605160043580604051901358578091901215613e0157600080fd5b5060605160243580604051901358578091901215613e1e57600080fd5b506101605161014051600a60c052602060c0200160c052602060c02060c052602060c020015460005260206000f3005b634ac756cb6000511415613edb5760406004610140373415613e6f57600080fd5b60605160043580604051901358578091901215613e8b57600080fd5b5060605160243580604051901358578091901215613ea857600080fd5b5061016051600361014051600a60c052602060c0200160c052602060c0200160c052602060c020015460005260206000f3005b63eb56ba9c6000511415613f755760606004610140373415613efc57600080fd5b60605160043580604051901358578091901215613f1857600080fd5b5060605160443580604051901358578091901215613f3557600080fd5b506101805161016051600461014051600a60c052602060c0200160c052602060c0200160c052602060c0200160c052602060c020015460005260206000f3005b63cce38da56000511415613fd85760206004610140373415613f9657600080fd5b60605160043580604051901358578091901215613fb257600080fd5b50600261014051600a60c052602060c0200160c052602060c020015460005260206000f3005b63634f21d2600051141561403b5760206004610140373415613ff957600080fd5b6060516004358060405190135857809190121561401557600080fd5b50600161014051600a60c052602060c0200160c052602060c020015460005260206000f3005b6399787ac6600051141561406157341561405457600080fd5b600b5460005260206000f3005b63e430757f60005114156140b9576020600461014037341561408257600080fd5b6060516004358060405190135857809190121561409e57600080fd5b5061014051600c60c052602060c020015460005260206000f3005b638a48440760005114156140df5734156140d257600080fd5b600d5460005260206000f3005b63e6b5736660005114156141055734156140f857600080fd5b600e5460005260206000f3005b639372b4e4600051141561412b57341561411e57600080fd5b600f5460005260206000f3005b632eff8759600051141561415157341561414457600080fd5b60105460005260206000f3005b635f611650600051141561417757341561416a57600080fd5b60115460005260206000f3005b635b03544a600051141561419d57341561419057600080fd5b60125460005260206000f3005b634231bfe160005114156141c35734156141b657600080fd5b60145460005260206000f3005b63eaa26f0f60005114156141e95734156141dc57600080fd5b60155460005260206000f3005b63a914bee8600051141561420f57341561420257600080fd5b60165460005260206000f3005b6307dcf45b600051141561423557341561422857600080fd5b601a5460005260206000f3005b63feaf82bd600051141561425b57341561424e57600080fd5b601b5460005260206000f3005b630f58c218600051141561428157341561427457600080fd5b601c5460005260206000f3005b5b6102ce614550036102ce6000396102ce614550036000f3 \ No newline at end of file diff --git a/ethereumj-core/src/main/resources/genesis/casper.json b/ethereumj-core/src/main/resources/genesis/casper.json new file mode 100644 index 0000000000..2b0cab4dbe --- /dev/null +++ b/ethereumj-core/src/main/resources/genesis/casper.json @@ -0,0 +1,27 @@ +{ + "nonce": "0x0000000000000056", + "difficulty": "0x2000", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x5f5e100", + "alloc": { + "b42e5cafe87d951c5cf0022bfdab06fe56ba2ad2": { + "balance": "1001002003004005006007008" + }, + "0xf63a243fe6922320a53dfc4270f02b8af0d4921d": { + "balance": "1001002003004005006007008" + }, + "b96611e02f9eff3c8afc6226d4ebfa81a821547c": { + "balance": "5125001002003004005006" + }, + "0000000000000000000000000000000000000010": { + "code": "6000355460205260206020f3" + }, + "0000000000000000000000000000000000000020": { + "code": "6000355460205260206020f3" + } + } +} \ No newline at end of file diff --git a/ethereumj-core/src/main/resources/logback.xml b/ethereumj-core/src/main/resources/logback.xml index e37112568a..5d7fd8eb6c 100644 --- a/ethereumj-core/src/main/resources/logback.xml +++ b/ethereumj-core/src/main/resources/logback.xml @@ -71,6 +71,8 @@ + + diff --git a/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java b/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java index d77774d7a6..22e6f1d267 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java @@ -301,6 +301,40 @@ public void doubleTransactionTest() throws Exception { Assert.assertTrue(importResult == ImportResult.IMPORTED_BEST); } + @Test + public void gasLimitExceed() throws Exception { + // Testing that tx in block when block.gasUsed + tx.gasLimit is not successful + // while block is correctly imported + + BlockchainImpl blockchain = createBlockchain(GenesisLoader.loadGenesis( + getClass().getResourceAsStream("/genesis/genesis-light.json"))); + blockchain.setMinerCoinbase(Hex.decode("ee0250c19ad59305b2bdb61f34b45b72fe37154f")); + Block parent = blockchain.getBestBlock(); + + ECKey senderKey = ECKey.fromPrivate(Hex.decode("3ec771c31cac8c0dba77a69e503765701d3c2bb62435888d4ffa38fed60c445c")); + byte[] receiverAddr = Hex.decode("31e2e1ed11951c7091dfba62cd4b7145e947219c"); + + Transaction tx1 = new Transaction(ByteUtil.intToBytesNoLeadZeroes(0), + ByteUtil.longToBytesNoLeadZeroes(50_000_000_000L), + ByteUtil.longToBytesNoLeadZeroes(0xfffff), + receiverAddr, new byte[]{88}, new byte[0]); + tx1.sign(senderKey); + Transaction tx2 = new Transaction(ByteUtil.intToBytesNoLeadZeroes(1), + ByteUtil.longToBytesNoLeadZeroes(50_000_000_000L), + Hex.decode("1000000000"), // same as block gas limit + receiverAddr, new byte[]{88}, new byte[0]); + tx2.sign(senderKey); + + Block b = blockchain.createNewBlock(parent, Arrays.asList(tx1, tx2), Collections.EMPTY_LIST); + Ethash.getForBlock(SystemProperties.getDefault(), b.getNumber()).mineLight(b).get(); + Repository repo = blockchain.getRepository().getSnapshotTo(parent.getStateRoot()); + BlockSummary summary = blockchain.add(repo, b); + Assert.assertNotNull(summary); + Assert.assertTrue(summary.getReceipts().get(0).isSuccessful()); + Assert.assertTrue(!summary.getReceipts().get(1).isSuccessful()); // second tx fails because of too big gas limit + Assert.assertEquals(0, summary.getReceipts().get(1).getGasUsed().length); // and no gas is used + } + @Test public void invalidBlockTotalDiff() throws Exception { // Check that importing invalid block doesn't affect totalDifficulty @@ -902,7 +936,7 @@ public static BlockchainImpl createBlockchain(Genesis genesis) { blockchain.byTest = true; - PendingStateImpl pendingState = new PendingStateImpl(listener, blockchain); + PendingStateImpl pendingState = new PendingStateImpl(listener); pendingState.setBlockchain(blockchain); blockchain.setPendingState(pendingState); diff --git a/ethereumj-core/src/test/java/org/ethereum/core/PendingStateLongRunTest.java b/ethereumj-core/src/test/java/org/ethereum/core/PendingStateLongRunTest.java index b50aadbda2..ccb81b577f 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/PendingStateLongRunTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/PendingStateLongRunTest.java @@ -20,7 +20,6 @@ import org.ethereum.config.CommonConfig; import org.ethereum.datasource.inmem.HashMapDB; import org.ethereum.db.RepositoryRoot; -import org.ethereum.db.ByteArrayWrapper; import org.ethereum.db.IndexedBlockStore; import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.validator.DependentBlockHeaderRuleAdapter; @@ -132,7 +131,7 @@ private Blockchain createBlockchain(Genesis genesis) { blockchain.byTest = true; - PendingStateImpl pendingState = new PendingStateImpl(new EthereumListenerAdapter(), blockchain); + PendingStateImpl pendingState = new PendingStateImpl(new EthereumListenerAdapter()); pendingState.setBlockchain(blockchain); blockchain.setPendingState(pendingState); diff --git a/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java b/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java index 94fe0ff5cb..327ce216c3 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java @@ -441,7 +441,7 @@ protected ProgramResult executeTransaction(Transaction tx) { Block bestBlock = block; - TransactionExecutor executor = new TransactionExecutor + TransactionExecutor executor = new CommonTransactionExecutor (txConst, bestBlock.getCoinbase(), track, new BlockStoreDummy(), invokeFactory, bestBlock) .setLocalCall(true); @@ -695,7 +695,7 @@ private Transaction createTx(BlockchainImpl blockchain, ECKey sender, byte[] rec private TransactionExecutor executeTransaction(BlockchainImpl blockchain, Transaction tx) { Repository track = blockchain.getRepository().startTracking(); - TransactionExecutor executor = new TransactionExecutor(tx, new byte[32], blockchain.getRepository(), + TransactionExecutor executor = new CommonTransactionExecutor(tx, new byte[32], blockchain.getRepository(), blockchain.getBlockStore(), blockchain.getProgramInvokeFactory(), blockchain.getBestBlock()); executor.init(); diff --git a/ethereumj-core/src/test/java/org/ethereum/core/casper/BadCasperValidatorTest.java b/ethereumj-core/src/test/java/org/ethereum/core/casper/BadCasperValidatorTest.java new file mode 100644 index 0000000000..c3892e3fb7 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/core/casper/BadCasperValidatorTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.core.casper; + +import org.ethereum.casper.service.CasperValidatorService; +import org.ethereum.config.BlockchainNetConfig; +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.crypto.ECKey; +import org.ethereum.listener.EthereumListener; +import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.sync.SyncManager; +import org.ethereum.util.FastByteComparisons; +import org.ethereum.util.blockchain.EtherUtil; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; +import org.spongycastle.util.encoders.Hex; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.TestCase.assertEquals; +import static org.ethereum.crypto.HashUtil.sha3; + +@Ignore // Takes too long to run regularly +public class BadCasperValidatorTest extends CasperBase { + + private int totalVotes = 0; + private int totalEpochs = 0; + + private Integer DEPOSIT_SIZE_ETH = 2000; + + protected CasperValidatorService service; + + final ECKey coinbase = ECKey.fromPrivate(sha3("cow".getBytes())); // Premined in light genesis + + @Override + BlockchainNetConfig config() { + return CasperValidatorTest.CASPER_EASY_CONFIG; + } + + @Before + @Override + public void setup() throws Exception { + super.setup(); + // Init with light Genesis + Resource casperGenesis = new ClassPathResource("/genesis/casper-test.json"); + systemProperties.useGenesis(casperGenesis.getInputStream()); + loadBlockchain(); + + + + BigInteger zeroEpoch = (BigInteger) casper.constCall("current_epoch")[0]; + assertEquals(0, zeroEpoch.longValue()); + + systemProperties.overrideParams( + "casper.validator.enabled", "true", + "casper.validator.privateKey", Hex.toHexString(coinbase.getPrivKeyBytes()), + "casper.validator.deposit", DEPOSIT_SIZE_ETH.toString() + ); + + bc.createBlock(); + + this.service = new CasperValidatorService(ethereum, systemProperties) { + @Override + protected byte[] makeVote(long validatorIndex, byte[] targetHash, long targetEpoch, long sourceEpoch, ECKey sender) { + // Make it send incorrect votes + return super.makeVote(validatorIndex, targetHash, targetEpoch + 1, sourceEpoch, sender); + } + }; + service.setBlockchain(blockchain); + SyncManager syncManager = Mockito.mock(SyncManager.class); + Mockito.when(syncManager.isSyncDone()).thenReturn(true); + service.setSyncManager(syncManager); + service.setCasper(casper); + service.setRepository(repository); + service.start(); + } + + private EthereumListener assertCasperReceipts = new EthereumListenerAdapter(){ + @Override + public void onBlock(BlockSummary blockSummary) { + blockSummary.getReceipts().stream().forEach(receipt -> { + if (FastByteComparisons.equal(receipt.getTransaction().getReceiveAddress(), casper.getAddress())) { + // We don't expect any error receipts + assert receipt.isSuccessful(); + // Following should be true for new epoch and votes, let's check it + if (casper.isServiceTx(receipt.getTransaction())) { + assert receipt.getGasUsed().length == 0; + assert blockSummary.getBlock().getGasUsed() == 0; + if (casper.isVote(receipt.getTransaction())) { + ++totalVotes; + } else { + ++totalEpochs; + } + } + } + }); + } + }; + + + /** + * Let's send bad votes + * these votes should not be included in blocks + * plus deposit size should decrease + */ + @Test + public void badVotesValidatorTest() throws Exception { + defaultListener.addListener(assertCasperReceipts); + + for (int i = 0; i < 10; ++i) { + Block block = bc.createBlock(); + } + BigDecimal curDeposit = calculateCurrentDepositSize(1); + assertTrue(curDeposit.compareTo(new BigDecimal(EtherUtil.convert(DEPOSIT_SIZE_ETH, EtherUtil.Unit.ETHER))) == 0); + + for (int i = 0; i < 300; ++i) { + Block block = bc.createBlock(); + } + assertEquals(6, totalEpochs); // floor division (300 + 10) / 50 + assertEquals(0, totalVotes); // No vote was included in any block + + // We've lost some money from our deposit because we are sleeping + BigDecimal decreasedDeposit = calculateCurrentDepositSize(1); + assertTrue(decreasedDeposit.compareTo(new BigDecimal(EtherUtil.convert(DEPOSIT_SIZE_ETH, EtherUtil.Unit.ETHER))) < 0); + } + + protected BigDecimal calculateCurrentDepositSize(long validatorIndex) { + return CasperValidatorTest.calculateCurrentDepositSize(validatorIndex, casper); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperBase.java b/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperBase.java new file mode 100644 index 0000000000..dc681e2198 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperBase.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.core.casper; + +import org.ethereum.casper.config.CasperBeanConfig; +import org.ethereum.casper.config.CasperProperties; +import org.ethereum.casper.config.net.CasperTestConfig; +import org.ethereum.casper.core.CasperBlockchain; +import org.ethereum.casper.core.CasperFacade; +import org.ethereum.casper.core.CasperPendingStateImpl; +import org.ethereum.config.BlockchainNetConfig; +import org.ethereum.config.CommonConfig; +import org.ethereum.config.SystemProperties; +import org.ethereum.casper.config.net.CasperTestNetConfig; +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.BlockchainImpl; +import org.ethereum.core.EventDispatchThread; +import org.ethereum.core.Genesis; +import org.ethereum.core.Repository; +import org.ethereum.core.TransactionExecutionSummary; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.datasource.CountingBytesSource; +import org.ethereum.datasource.JournalSource; +import org.ethereum.datasource.Source; +import org.ethereum.datasource.inmem.HashMapDB; +import org.ethereum.db.IndexedBlockStore; +import org.ethereum.db.PruneManager; +import org.ethereum.db.RepositoryRoot; +import org.ethereum.db.RepositoryWrapper; +import org.ethereum.db.TransactionStore; +import org.ethereum.facade.EthereumImpl; +import org.ethereum.listener.CompositeEthereumListener; +import org.ethereum.listener.EthereumListener; +import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.manager.AdminInfo; +import org.ethereum.manager.WorldManager; +import org.ethereum.net.server.ChannelManager; +import org.ethereum.util.blockchain.StandaloneBlockchain; +import org.ethereum.validator.DependentBlockHeaderRuleAdapter; +import org.ethereum.casper.validator.NullSenderTxValidator; +import org.ethereum.vm.program.ProgramPrecompile; +import org.ethereum.vm.program.invoke.ProgramInvokeFactoryImpl; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; + +// We have all mocks here and not all of them are used in every test, so strict stubs should be turned off +@RunWith(MockitoJUnitRunner.Silent.class) +public abstract class CasperBase { + @Mock + ApplicationContext context; + + @Spy + final CasperProperties systemProperties = new CasperProperties(); + + @InjectMocks + private CommonConfig commonConfig = new CasperBeanConfig() { + @Override + public Source precompileSource() { + return null; + } + + @Override + public SystemProperties systemProperties() { + return systemProperties; + } + }; + + CasperBlockchain blockchain; + + protected CompositeEthereumListener defaultListener = new CompositeEthereumListener(); + + @InjectMocks + EthereumImpl ethereum = new EthereumImpl(systemProperties, defaultListener); + + @InjectMocks + CasperFacade casper = new CasperFacade(); + + Repository repository = new RepositoryWrapper(); + + @InjectMocks + CasperPendingStateImpl casperPendingState = new CasperPendingStateImpl(defaultListener); + + StandaloneBlockchain bc; + + WorldManager worldManager; + + @Before + public void setup() throws Exception { + // Just trust me! + // FIXME: Make it a little bit readable + + BlockchainNetConfig config = config(); + ((CasperTestConfig) config.getConfigForBlock(0)).addNullSenderTxValidators(new NullSenderTxValidator(casper::isVote)); + systemProperties.setBlockchainConfig(config); + Resource casperGenesis = new ClassPathResource("/genesis/casper.json"); + systemProperties.useGenesis(casperGenesis.getInputStream()); + systemProperties.overrideParams( + "casper.contractBin", "/casper/casper.bin", + "casper.contractAbi", "/casper/casper.abi" + ); + + MockitoAnnotations.initMocks(this); + + this.ethereum.setCommonConfig(commonConfig); + this.worldManager = Mockito.mock(WorldManager.class); + + this.bc = new StandaloneBlockchain() { + @Override + public BlockchainImpl getBlockchain() { + if (blockchain == null) { + blockchain = createBlockchain(); + addEthereumListener(new EthereumListenerAdapter() { + @Override + public void onBlock(BlockSummary blockSummary) { + lastSummary = blockSummary; + } + }); + } + return blockchain; + } + + private BlockchainImpl createBlockchain() { + SystemProperties.getDefault().setBlockchainConfig(systemProperties.getBlockchainConfig()); + + IndexedBlockStore blockStore = new IndexedBlockStore(); + blockStore.init(new HashMapDB(), new HashMapDB()); + + stateDS = new HashMapDB<>(); + pruningStateDS = new JournalSource<>(new CountingBytesSource(stateDS)); + pruneManager = new PruneManager(blockStore, pruningStateDS, + stateDS, SystemProperties.getDefault().databasePruneDepth()); + + final RepositoryRoot repository = new RepositoryRoot(pruningStateDS); + + ProgramInvokeFactoryImpl programInvokeFactory = new ProgramInvokeFactoryImpl(); + listener = defaultListener; + + BlockchainImpl blockchain = new CasperBlockchain(systemProperties).withEthereumListener(listener) + .withAdminInfo(new AdminInfo()) + .withEventDispatchThread(new EventDispatchThread()) + .withTransactionStore(new TransactionStore(new HashMapDB())) + .withCommonConfig(commonConfig) + .withBlockStore(blockStore); + blockchain.setRepository(repository); + blockchain.setParentHeaderValidator(new DependentBlockHeaderRuleAdapter()); + blockchain.setProgramInvokeFactory(programInvokeFactory); + blockchain.setPruneManager(pruneManager); + ((CasperBlockchain) blockchain).setFinalizedBlocks(new HashMapDB<>()); + + blockchain.byTest = true; + + pendingState = casperPendingState; + pendingState.setCommonConfig(commonConfig); + + pendingState.setBlockchain(blockchain); + blockchain.setPendingState(pendingState); + return blockchain; + } + }.withNetConfig(systemProperties.getBlockchainConfig()); + + this.blockchain = (CasperBlockchain) bc.getBlockchain(); + casper.setEthereum(ethereum); + blockchain.setCasper(casper); + Mockito.when(context.getBean(CasperBlockchain.class)).thenReturn(blockchain); + Mockito.when(worldManager.getBlockchain()).thenReturn(blockchain); + Mockito.when(worldManager.getBlockStore()).thenReturn(blockchain.getBlockStore()); + ((RepositoryWrapper) repository).setBlockchain(bc.getBlockchain()); + Mockito.when(worldManager.getRepository()).thenReturn(repository); + doAnswer((Answer) invocation -> { + Object arg0 = invocation.getArgument(0); + defaultListener.addListener((EthereumListener) arg0); + return null; + }).when(worldManager).addListener(any(EthereumListener.class)); + ethereum.setWorldManager(worldManager); + ethereum.setProgramInvokeFactory(new ProgramInvokeFactoryImpl()); + ethereum.setPendingState(blockchain.getPendingState()); + ethereum.setChannelManager(Mockito.mock(ChannelManager.class)); + + // Push pending txs in StandaloneBlockchain + ethereum.addListener(new EthereumListenerAdapter(){ + @Override + public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + if (state.equals(PendingTransactionState.NEW_PENDING)) { + bc.submitTransaction(txReceipt.getTransaction()); + } + } + }); + } + + /** + * Same logic as in WorldManager.loadBlockchain + */ + protected void loadBlockchain() { + + Genesis genesis = Genesis.getInstance(systemProperties); + Genesis.populateRepository(repository, genesis); + +// repository.commitBlock(genesis.getHeader()); + repository.commit(); + + blockchain.getBlockStore().saveBlock(Genesis.getInstance(systemProperties), Genesis.getInstance(systemProperties).getCumulativeDifficulty(), true); + + blockchain.setBestBlock(Genesis.getInstance(systemProperties)); + blockchain.setTotalDifficulty(Genesis.getInstance(systemProperties).getCumulativeDifficulty()); + + defaultListener.onBlock(new BlockSummary(Genesis.getInstance(systemProperties), new HashMap(), new ArrayList(), new ArrayList())); +// repository.dumpState(Genesis.getInstance(config), 0, 0, null); + } + + BlockchainNetConfig config() { + return new CasperTestNetConfig(); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperEpochSwitchTest.java b/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperEpochSwitchTest.java new file mode 100644 index 0000000000..8d74454590 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperEpochSwitchTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.core.casper; + +import org.ethereum.config.BlockchainNetConfig; +import org.ethereum.core.Block; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import java.math.BigInteger; + +import static junit.framework.TestCase.assertEquals; + +public class CasperEpochSwitchTest extends CasperBase { + + @Override + BlockchainNetConfig config() { + return CasperValidatorTest.CASPER_EASY_CONFIG; + } + + @Test + public void epochStartTest() throws Exception { + // Init with light Genesis + Resource casperGenesis = new ClassPathResource("/genesis/casper-test.json"); + systemProperties.useGenesis(casperGenesis.getInputStream()); + loadBlockchain(); + + BigInteger zeroEpoch = (BigInteger) casper.constCall("current_epoch")[0]; + assertEquals(0, zeroEpoch.longValue()); + + for (int i = 0; i < 50; ++i) { + Block block = bc.createBlock(); + } + + BigInteger firstEpoch = (BigInteger) casper.constCall("current_epoch")[0]; + assertEquals(1, firstEpoch.longValue()); + + for (int i = 0; i < 50; ++i) { + Block block = bc.createBlock(); + } + + // Epochs switches and they are finalized and justified because there no deposits yet [insta_finalize] + BigInteger secondEpoch = (BigInteger) casper.constCall("current_epoch")[0]; + assertEquals(2, secondEpoch.longValue()); + + BigInteger lastFinalized = (BigInteger) casper.constCall("last_finalized_epoch")[0]; + assertEquals(1, lastFinalized.longValue()); + + BigInteger lastJustified = (BigInteger) casper.constCall("last_justified_epoch")[0]; + assertEquals(1, lastJustified.longValue()); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperStateInitTest.java b/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperStateInitTest.java new file mode 100644 index 0000000000..f14ab8e155 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperStateInitTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.core.casper; + +import org.ethereum.core.Block; +import org.ethereum.core.Genesis; +import org.ethereum.db.ByteArrayWrapper; +import org.junit.Ignore; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +import static junit.framework.TestCase.assertEquals; + +@Ignore // Takes too long to run usually +// FIXME: Update it with new casper address and state init from Pyeth when available +public class CasperStateInitTest extends CasperBase { + + /** + * Used same values like in Casper Test Network based on Pyethereum + */ + @Test + public void genesisPlusBlock() { + // Init with Genesis + loadBlockchain(); + + // Check after genesis + assertEquals(new ByteArrayWrapper(Hex.decode("f3f713c5ff3119287ae62861e3fd90d6afc94b57d06151007c409b86bf419d11")), + new ByteArrayWrapper(blockchain.getBestBlock().getStateRoot())); + assertEquals(new ByteArrayWrapper(Hex.decode("5d0dfcfbcb941825c7ed52c846dc2021e29374f6954c4eaf6f7352f63ec8cab4")), + new ByteArrayWrapper(blockchain.getBestBlock().getHash())); + + // Mine 1st block + Block block1 = bc.createBlock(Hex.decode("3535353535353535353535353535353535353535")); + + // Check after 1st block + assertEquals(new ByteArrayWrapper(Hex.decode("b1b5d87eeadab3ffc0e4045ee18fc63ccc06b8e9b7195af8f1a4450557c3818d")), + new ByteArrayWrapper(block1.getStateRoot())); + + assertEquals(new ByteArrayWrapper(Hex.decode("bd832b0cd3291c39ef67691858f35c71dfb3bf21")), + new ByteArrayWrapper(systemProperties.getCasperAddress())); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperValidatorTest.java b/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperValidatorTest.java new file mode 100644 index 0000000000..e68e87b213 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/core/casper/CasperValidatorTest.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.core.casper; + +import org.ethereum.casper.config.net.CasperTestConfig; +import org.ethereum.casper.core.CasperFacade; +import org.ethereum.config.BlockchainConfig; +import org.ethereum.config.BlockchainNetConfig; +import org.ethereum.config.Constants; +import org.ethereum.config.ConstantsAdapter; +import org.ethereum.config.blockchain.FrontierConfig; +import org.ethereum.config.net.BaseNetConfig; +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.crypto.ECKey; +import org.ethereum.casper.service.CasperValidatorService; +import org.ethereum.listener.EthereumListener; +import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.sync.SyncManager; +import org.ethereum.util.FastByteComparisons; +import org.ethereum.util.blockchain.EtherUtil; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; +import org.spongycastle.util.encoders.Hex; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.TestCase.assertEquals; +import static org.ethereum.casper.config.net.CasperTestConfig.DYNASTY_LOGOUT_DELAY; +import static org.ethereum.casper.config.net.CasperTestConfig.WITHDRAWAL_DELAY; +import static org.ethereum.crypto.HashUtil.sha3; + +@Ignore // Takes too long to run regularly +public class CasperValidatorTest extends CasperBase { + + private int totalVotes = 0; + private int totalEpochs = 0; + + private Integer DEPOSIT_SIZE_ETH = 2000; + + protected CasperValidatorService service; + + final ECKey coinbase = ECKey.fromPrivate(sha3("cow".getBytes())); // Premined in light genesis + + protected static class CasperEasyNetConfig extends BaseNetConfig { + private class CasperEasyConfig extends CasperTestConfig { + private final Constants constants; + CasperEasyConfig(BlockchainConfig parent) { + + super(parent); + constants = new ConstantsAdapter(super.getConstants()) { + private final BigInteger BLOCK_REWARD = new BigInteger("1000000000000000000"); // 1 ETH + + private final BigInteger MINIMUM_DIFFICULTY = BigInteger.ONE; + + @Override + public BigInteger getBLOCK_REWARD() { + return BLOCK_REWARD; + } + + @Override + public BigInteger getMINIMUM_DIFFICULTY() { + return MINIMUM_DIFFICULTY; + } + }; + } + + @Override + public Constants getConstants() { + return constants; + } + + @Override + public Integer getChainId() { + return 345; + } + } + + public CasperEasyNetConfig() { + add(0, new CasperEasyConfig(new FrontierConfig())); + } + } + + protected final static BlockchainNetConfig CASPER_EASY_CONFIG = new CasperEasyNetConfig(); + + @Override + BlockchainNetConfig config() { + return CASPER_EASY_CONFIG; + } + + @Before + @Override + public void setup() throws Exception { + super.setup(); + // Init with light Genesis + Resource casperGenesis = new ClassPathResource("/genesis/casper-test.json"); + systemProperties.useGenesis(casperGenesis.getInputStream()); + loadBlockchain(); + + + + BigInteger zeroEpoch = (BigInteger) casper.constCall("current_epoch")[0]; + assertEquals(0, zeroEpoch.longValue()); + + systemProperties.overrideParams( + "casper.validator.enabled", "true", + "casper.validator.privateKey", Hex.toHexString(coinbase.getPrivKeyBytes()), + "casper.validator.deposit", DEPOSIT_SIZE_ETH.toString() + ); + + bc.createBlock(); + + + this.service = new CasperValidatorService(ethereum, systemProperties); + service.setBlockchain(blockchain); + SyncManager syncManager = Mockito.mock(SyncManager.class); + Mockito.when(syncManager.isSyncDone()).thenReturn(true); + service.setSyncManager(syncManager); + service.setCasper(casper); + service.setRepository(repository); + service.start(); + } + + @Test + public void complexValidatorTest() throws Exception { + defaultListener.addListener(assertCasperReceipts); + BigInteger initialBalance = ethereum.getRepository().getBalance(coinbase.getAddress()); + + for (int i = 0; i < 10; ++i) { + Block block = bc.createBlock(); + } + + BigDecimal curDeposit = calculateCurrentDepositSize(1); + assertTrue(curDeposit.compareTo(new BigDecimal(EtherUtil.convert(DEPOSIT_SIZE_ETH, EtherUtil.Unit.ETHER))) == 0); + for (int i = 0; i < 300; ++i) { + Block block = bc.createBlock(); + } + // We've earned some money on top of our deposit as premium for our votes, which finalized epochs!! + BigDecimal increasedDeposit = calculateCurrentDepositSize(1); + assertTrue(increasedDeposit.compareTo(new BigDecimal(EtherUtil.convert(DEPOSIT_SIZE_ETH, EtherUtil.Unit.ETHER))) > 0); + + // We've left less than (initial - 2000 ETH) + assertTrue(ethereum.getRepository().getBalance(coinbase.getAddress()) + .compareTo(initialBalance.subtract(EtherUtil.convert(DEPOSIT_SIZE_ETH, EtherUtil.Unit.ETHER))) < 0); + // Let's logout + service.voteThenLogout(); + // Withdrawal delay is 5 logout epochs delay + 5 withdrawal + 1 overhead epoch + for (int i = 0; i < 50 * (DYNASTY_LOGOUT_DELAY + WITHDRAWAL_DELAY + 1); ++i) { + Block block = bc.createBlock(); + } + // We should have more than initialBalance in the end + assertTrue(ethereum.getRepository().getBalance(coinbase.getAddress()).compareTo(initialBalance) > 0); + + // Check that assertCasperReceipts was called + assertEquals(17, totalEpochs); // floor division (300 + 550 + 10) / 50 + assertEquals(9, totalVotes); // 5 votes for first 300 blocks (5 epochs as validator) + 4 votes for logout delay, last epoch is w/o vote + + // TODO: add more validators + } + + protected BigDecimal calculateCurrentDepositSize(long validatorIndex) { + return calculateCurrentDepositSize(validatorIndex, casper); + } + + protected static BigDecimal calculateCurrentDepositSize(long validatorIndex, CasperFacade casperFacade) { + BigDecimal scaleFactor = (BigDecimal) casperFacade.constCall("deposit_scale_factor", + (BigInteger) casperFacade.constCall("current_epoch")[0])[0]; + BigDecimal curDeposit = (BigDecimal) casperFacade.constCall("validators__deposit", validatorIndex)[0]; + BigDecimal scaledDepositWei = curDeposit.multiply(scaleFactor); + return scaledDepositWei; + } + + private EthereumListener assertCasperReceipts = new EthereumListenerAdapter(){ + @Override + public void onBlock(BlockSummary blockSummary) { + blockSummary.getReceipts().stream().forEach(receipt -> { + if (FastByteComparisons.equal(receipt.getTransaction().getReceiveAddress(), casper.getAddress())) { + // We don't expect any error receipts + assert receipt.isSuccessful(); + // Following should be true for new epoch and votes, let's check it + if (casper.isServiceTx(receipt.getTransaction())) { + assert receipt.getGasUsed().length == 0; + assert blockSummary.getBlock().getGasUsed() == 0; + if (casper.isVote(receipt.getTransaction())) { + ++totalVotes; + } else { + ++totalEpochs; + } + } + } + }); + } + }; +} diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/IterableTestRepository.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/IterableTestRepository.java index 4683ab2358..c5c8fe7d5a 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/IterableTestRepository.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/IterableTestRepository.java @@ -19,6 +19,7 @@ import org.ethereum.core.AccountState; import org.ethereum.core.Block; +import org.ethereum.core.Blockchain; import org.ethereum.core.Repository; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.db.ContractDetails; diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java index 16367962c7..9fdf981fd5 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java @@ -103,7 +103,7 @@ public List runTestCase(BlockTestCase testCase) { .withParentBlockHeaderValidator(CommonConfig.getDefault().parentHeaderValidator()); blockchain.byTest = true; - PendingStateImpl pendingState = new PendingStateImpl(new EthereumListenerAdapter(), blockchain); + PendingStateImpl pendingState = new PendingStateImpl(new EthereumListenerAdapter()); blockchain.setBestBlock(genesis); blockchain.setTotalDifficulty(genesis.getCumulativeDifficulty()); diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/runners/StateTestRunner.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/runners/StateTestRunner.java index a49e2c5e6d..fd75702af1 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/runners/StateTestRunner.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/runners/StateTestRunner.java @@ -71,7 +71,7 @@ protected ProgramResult executeTransaction(Transaction tx) { Repository track = repository.startTracking(); TransactionExecutor executor = - new TransactionExecutor(transaction, env.getCurrentCoinbase(), track, new BlockStoreDummy(), + new CommonTransactionExecutor(transaction, env.getCurrentCoinbase(), track, new BlockStoreDummy(), invokeFactory, blockchain.getBestBlock()); try{ diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java b/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java index f37a607b48..06e9dae334 100644 --- a/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java @@ -24,12 +24,12 @@ import org.ethereum.core.AccountState; import org.ethereum.core.Block; import org.ethereum.core.BlockSummary; +import org.ethereum.core.CommonTransactionExecutor; import org.ethereum.core.Repository; import org.ethereum.core.Transaction; import org.ethereum.core.TransactionExecutor; import org.ethereum.core.TransactionReceipt; import org.ethereum.db.ContractDetails; -import org.ethereum.db.RepositoryImpl; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; import org.ethereum.listener.EthereumListener; @@ -49,7 +49,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -207,8 +206,8 @@ public void onPendingTransactionsReceived(List transactions) { .getSnapshotTo(block.getStateRoot()) .startTracking(); try { - TransactionExecutor executor = new TransactionExecutor - (tx, block.getCoinbase(), repository, ethereum.getBlockchain().getBlockStore(), + TransactionExecutor executor = new CommonTransactionExecutor( + tx, block.getCoinbase(), repository, ethereum.getBlockchain().getBlockStore(), programInvokeFactory, block, new EthereumListenerAdapter(), 0) .withCommonConfig(commonConfig) .setLocalCall(true); diff --git a/ethereumj-core/src/test/java/org/ethereum/mine/CancelMiningTest.java b/ethereumj-core/src/test/java/org/ethereum/mine/CancelMiningTest.java new file mode 100644 index 0000000000..29bcda0a6a --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/mine/CancelMiningTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.mine; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import org.ethereum.config.SystemProperties; +import org.ethereum.config.blockchain.FrontierConfig; +import org.ethereum.core.Block; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.Blockchain; +import org.ethereum.core.ImportResult; +import org.ethereum.core.Transaction; +import org.ethereum.crypto.ECKey; +import org.ethereum.facade.EthereumImpl; +import org.ethereum.util.ByteUtil; +import org.ethereum.util.blockchain.StandaloneBlockchain; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.Collections.EMPTY_LIST; +import static junit.framework.TestCase.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; + + +/** + * Testing following case: + * Time limit for block is set + * When block is mined and time limit is not over, new tx is pushed in pending state, + * so the new block is mined and old one should be cancelled + */ +@Ignore +public class CancelMiningTest { + + private static final long MIN_BLOCK_TIME = 1000; + + static { + SystemProperties.getDefault().setBlockchainConfig(new FrontierConfig(new FrontierConfig.FrontierConstants() { + @Override + public BigInteger getMINIMUM_DIFFICULTY() { + return BigInteger.ONE; + } + })); + SystemProperties.getDefault().overrideParams("mine.minBlockTimeoutMsec", String.valueOf(MIN_BLOCK_TIME)); + } + + + private StandaloneBlockchain bc = new StandaloneBlockchain().withAutoblock(false); + + private AtomicInteger blocksImported = new AtomicInteger(0); + + private Map> miningFutures = new HashMap<>(); + + @Mock + private EthereumImpl ethereum; + + Blockchain blockchain = bc.getBlockchain(); // Just to init blockchain in StandaloneBlockchain + + @InjectMocks + @Resource + private BlockMiner blockMiner = new BlockMiner(SystemProperties.getDefault(), bc.getListener(), blockchain, + bc.getPendingState());; + + @Before + public void setup() { + + // Initialize mocks created above + MockitoAnnotations.initMocks(this); + + when(ethereum.addNewMinedBlock(any(Block.class))).thenAnswer(new Answer() { + @Override + public ImportResult answer(InvocationOnMock invocation) throws Throwable { + Block block = (Block) invocation.getArguments()[0]; + blocksImported.incrementAndGet(); + return bc.getBlockchain().tryToConnect(block); + } + }); + } + + @Test + public void onlyOneBlockShouldBeMined() throws Exception { + + blockMiner.setExternalMiner(new MinerIfc() { + @Override + public ListenableFuture mine(Block block) { + final SettableFuture futureBlock = SettableFuture.create(); + miningFutures.put(miningFutures.keySet().size() + 1, futureBlock); + return futureBlock; + } + + @Override + public boolean validate(BlockHeader blockHeader) { + return true; + } + }); + + Block block = bc.createBlock(); + assertEquals(1, block.getNumber()); + + + // Dummy to set last block time in Block miner + Block b = bc.getBlockchain().createNewBlock(bc.getBlockchain().getBestBlock(), EMPTY_LIST, EMPTY_LIST); + Ethash.getForBlock(SystemProperties.getDefault(), b.getNumber()).mineLight(b).get(); + // Run it in blocking way to finish + miningFutures.get(miningFutures.size()).set(new MinerIfc.MiningResult(ByteUtil.byteArrayToLong(b.getNonce()), b.getMixHash(), b)); + + for (int i = 0; i < 50; ++i) { + // This block we will cancel with tx + Block b2 = bc.getBlockchain().createNewBlock(bc.getBlockchain().getBestBlock(), EMPTY_LIST, EMPTY_LIST); + Ethash.getForBlock(SystemProperties.getDefault(), b2.getNumber()).mineLight(b2).get(); + // Run it non-blocking to fire new tx until task is finished + Executors.newSingleThreadExecutor().submit(() -> { + miningFutures.get(miningFutures.size()).set(new MinerIfc.MiningResult(ByteUtil.byteArrayToLong(b2.getNonce()), b2.getMixHash(), b2)); + }); + + ECKey alice = new ECKey(); + Transaction tx = bc.createTransaction(i, alice.getAddress(), 1000000, new byte[0]); + bc.getPendingState().addPendingTransaction(tx); + + Block b3 = bc.getBlockchain().createNewBlock(bc.getBlockchain().getBestBlock(), new ArrayList() {{ + add(tx); + }}, EMPTY_LIST); + + miningFutures.get(miningFutures.size()).set(new MinerIfc.MiningResult(ByteUtil.byteArrayToLong(b3.getNonce()), b3.getMixHash(), b3)); + + assertEquals(i + 3, bc.getBlockchain().getBestBlock().getNumber()); // + bc.createBlock() + assertEquals(i + 2, blocksImported.get()); // bc.createBlock() is not counted + assertEquals(1, bc.getBlockchain().getBestBlock().getTransactionsList().size()); + } + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/mine/ExternalMinerTest.java b/ethereumj-core/src/test/java/org/ethereum/mine/ExternalMinerTest.java index bb4efc3923..160eca492c 100644 --- a/ethereumj-core/src/test/java/org/ethereum/mine/ExternalMinerTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/mine/ExternalMinerTest.java @@ -66,7 +66,7 @@ public class ExternalMinerTest { @InjectMocks @Resource private BlockMiner blockMiner = new BlockMiner(SystemProperties.getDefault(), listener, bc.getBlockchain(), - bc.getBlockchain().getBlockStore(), bc.getPendingState());; + bc.getPendingState());; @Before public void setup() { diff --git a/ethereumj-core/src/test/resources/genesis/casper-test.json b/ethereumj-core/src/test/resources/genesis/casper-test.json new file mode 100644 index 0000000000..ac92304bbe --- /dev/null +++ b/ethereumj-core/src/test/resources/genesis/casper-test.json @@ -0,0 +1,30 @@ +{ + "nonce": "0x0000000000000056", + "difficulty": "0x01", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x5f5e100", + "alloc": { + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826": { + "balance": "1001002003004005006007008" + }, + "b42e5cafe87d951c5cf0022bfdab06fe56ba2ad2": { + "balance": "1001002003004005006007008" + }, + "0xf63a243fe6922320a53dfc4270f02b8af0d4921d": { + "balance": "1001002003004005006007008" + }, + "b96611e02f9eff3c8afc6226d4ebfa81a821547c": { + "balance": "5125001002003004005006" + }, + "0000000000000000000000000000000000000010": { + "code": "6000355460205260206020f3" + }, + "0000000000000000000000000000000000000020": { + "code": "6000355460205260206020f3" + } + } +} \ No newline at end of file