org.ethereum.rpc.Web3Impl.java Source code

Java tutorial

Introduction

Here is the source code for org.ethereum.rpc.Web3Impl.java

Source

/*
 * This file is part of RskJ
 * Copyright (C) 2017 RSK Labs Ltd.
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.ethereum.rpc;

import co.rsk.core.SnapshotManager;
import co.rsk.mine.MinerManager;
import co.rsk.peg.Bridge;
import co.rsk.rpc.ModuleDescription;
import com.google.common.annotations.VisibleForTesting;
import co.rsk.config.RskSystemProperties;
import co.rsk.config.WalletAccount;
import co.rsk.core.Wallet;
import co.rsk.net.BlockProcessor;
import co.rsk.peg.BridgeState;
import co.rsk.peg.BridgeStateReader;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.map.HashedMap;
import org.ethereum.config.SystemProperties;
import org.ethereum.config.blockchain.RegTestConfig;
import org.ethereum.core.*;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.ethereum.db.TransactionInfo;
import org.ethereum.facade.Ethereum;
import org.ethereum.listener.CompositeEthereumListener;
import org.ethereum.listener.EthereumListener;
import org.ethereum.listener.EthereumListenerAdapter;
import org.ethereum.manager.WorldManager;
import org.ethereum.net.client.Capability;
import org.ethereum.net.server.Channel;
import org.ethereum.net.server.ChannelManager;
import org.ethereum.rpc.dto.*;
import org.ethereum.rpc.exception.JsonRpcInvalidParamException;
import org.ethereum.rpc.exception.JsonRpcUnimplementedMethodException;
import org.ethereum.solidity.compiler.SolidityCompiler;
import org.ethereum.util.BuildInfo;
import org.ethereum.vm.DataWord;
import org.ethereum.vm.GasCost;
import org.ethereum.vm.LogInfo;
import org.ethereum.vm.PrecompiledContracts;
import org.ethereum.vm.program.ProgramResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static java.lang.Math.max;
import static org.ethereum.rpc.TypeConverter.*;

public class Web3Impl implements Web3 {
    private static final Logger logger = LoggerFactory.getLogger("web3");

    private SnapshotManager snapshotManager = new SnapshotManager();
    private MinerManager minerManager = new MinerManager();

    public WorldManager worldManager;

    public org.ethereum.facade.Repository repository;

    public Ethereum eth;

    private String baseClientVersion = "RskJ";

    CompositeEthereumListener compositeEthereumListener;

    long initialBlockNumber;
    long maxBlockNumberSeen;

    private final Object filterLock = new Object();

    private Wallet wallet;

    private SolidityCompiler solidityCompiler;

    public Web3Impl(SolidityCompiler compiler, Wallet wallet) {
        this.solidityCompiler = compiler;
        this.wallet = wallet;
    }

    public Web3Impl(Ethereum eth, RskSystemProperties properties, Wallet wallet) {
        this.eth = eth;
        this.worldManager = eth.getWorldManager();
        this.repository = eth.getRepository();
        this.wallet = wallet;

        initialBlockNumber = this.worldManager.getBlockchain().getBestBlock().getNumber();

        compositeEthereumListener = new CompositeEthereumListener();

        this.solidityCompiler = this.worldManager.getSolidityCompiler();

        compositeEthereumListener.addListener(this.setupListener());

        this.eth.addListener(compositeEthereumListener);

        // TODO adding default accounts, just so that integration tests pass
        if (properties.getBlockchainConfig() instanceof RegTestConfig) {
            personal_newAccountWithSeed("cow");
        }

        String secret = properties.coinbaseSecret();
        personal_newAccountWithSeed(secret);

        // initializes wallet accounts based on configuration
        List<WalletAccount> accs = properties.walletAccounts();

        for (WalletAccount acc : accs)
            eth_addAccount(acc.getPrivateKey());
    }

    public EthereumListener setupListener() {
        return new EthereumListenerAdapter() {
            @Override
            public void onBlock(Block block, List<TransactionReceipt> receipts) {
                logger.trace("Start onBlock");

                synchronized (filterLock) {
                    for (Filter filter : installedFilters.values()) {
                        filter.newBlockReceived(block);
                    }
                }

                logger.trace("End onBlock");
            }

            @Override
            public void onPendingTransactionsReceived(List<Transaction> transactions) {
                synchronized (filterLock) {
                    for (Filter filter : installedFilters.values()) {
                        for (Transaction tx : transactions) {
                            filter.newPendingTx(tx);
                        }
                    }
                }
            }
        };
    }

    public long JSonHexToLong(String x) throws Exception {
        if (!x.startsWith("0x"))
            throw new Exception("Incorrect hex syntax");
        x = x.substring(2);
        return Long.parseLong(x, 16);
    }

    public int JSonHexToInt(String x) throws Exception {
        if (!x.startsWith("0x"))
            throw new Exception("Incorrect hex syntax");
        x = x.substring(2);
        return Integer.parseInt(x, 16);
    }

    public String JSonHexToHex(String x) throws Exception {
        if (!x.startsWith("0x"))
            throw new Exception("Incorrect hex syntax");
        x = x.substring(2);
        return x;
    }

    public String[] toJsonHexArray(Collection<String> c) {
        String[] arr = new String[c.size()];
        int i = 0;
        for (String item : c) {
            arr[i++] = toJsonHex(item);
        }
        return arr;
    }

    public String[] listObjectHashtoJsonHexArray(Collection<SerializableObject> c) {
        String[] arr = new String[c.size()];
        int i = 0;
        for (SerializableObject item : c) {
            // Todo: Which hash is required? RawHash or Hash ?
            arr[i++] = toJsonHex(item.getHash());
        }
        return arr;
    }

    public String web3_clientVersion() {

        String clientVersion = baseClientVersion + "/" + SystemProperties.CONFIG.projectVersion() + "/"
                + System.getProperty("os.name") + "/Java1.8/" + SystemProperties.CONFIG.projectVersionModifier()
                + "-" + BuildInfo.getBuildHash();

        if (logger.isDebugEnabled())
            logger.debug("web3_clientVersion(): " + clientVersion);

        return clientVersion;

    }

    public String web3_sha3(String data) throws Exception {
        String s = null;
        try {
            byte[] result = HashUtil.sha3(data.getBytes(StandardCharsets.UTF_8));
            return s = TypeConverter.toJsonHex(result);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("web3_sha3({}): {}", data, s);
        }
    }

    public String net_version() {
        String s = null;
        try {
            byte netVersion = RskSystemProperties.RSKCONFIG.getBlockchainConfig().getCommonConstants().getChainId();
            return s = Byte.toString(netVersion);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("net_version(): " + s);
        }
    }

    public String net_peerCount() {
        String s = null;
        try {

            ChannelManager channelManager = worldManager.getChannelManager();
            int n = channelManager.getActivePeers().size();
            return s = TypeConverter.toJsonHex(n);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("net_peerCount(): " + s);
        }
    }

    public boolean net_listening() {
        Boolean s = null;
        try {
            return s = eth.getPeerServer().isListening();
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("net_listening(): " + s);
        }
    }

    public String rsk_protocolVersion() {
        String s = null;
        try {
            int version = 0;
            for (Capability capability : worldManager.getConfigCapabilities().getConfigCapabilities()) {
                if (capability.isRSK()) {
                    version = max(version, capability.getVersion());
                }
            }
            return s = Integer.toString(version);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("rsk_protocolVersion(): " + s);
        }
    }

    public String eth_protocolVersion() {
        return rsk_protocolVersion();
    }

    public Object eth_syncing() {
        Blockchain blockchain = worldManager.getBlockchain();

        long currentBlock = worldManager.getBlockchain().getBestBlock().getNumber();
        BlockProcessor processor = worldManager.getNodeBlockProcessor();
        if (processor == null) {
            // TODO(raltman): processor should never be null. Change Web3Impl to request BlockProcessor from Roostock.
            return false;
        }
        long highestBlock = processor.getLastKnownBlockNumber();

        if (currentBlock >= highestBlock)
            return false;

        SyncingResult s = new SyncingResult();
        try {
            s.startingBlock = TypeConverter.toJsonHex(initialBlockNumber);
            s.currentBlock = TypeConverter.toJsonHex(currentBlock);
            s.highestBlock = toJsonHex(highestBlock);

            return s;
        } finally {
            logger.debug("eth_syncing(): starting {}, current {}, highest {} ", s.startingBlock, s.currentBlock,
                    s.highestBlock);
        }
    }

    public String eth_coinbase() {
        String s = null;
        try {
            return s = toJsonHex(worldManager.getMinerServer().getCoinbaseAddress());
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_coinbase(): " + s);
        }
    }

    public boolean eth_mining() {
        Boolean s = null;
        try {
            return s = worldManager.getMinerClient().isMining();
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_mining(): " + s);
        }
    }

    public String eth_hashrate() {
        BigDecimal hashesPerSecond = BigDecimal.ZERO;
        if (RskSystemProperties.RSKCONFIG.minerServerEnabled()) {
            BigInteger hashesPerHour = this.worldManager.getHashRateCalculator().calculateNodeHashRate(1L,
                    TimeUnit.HOURS);
            hashesPerSecond = new BigDecimal(hashesPerHour).divide(new BigDecimal(TimeUnit.HOURS.toSeconds(1)), 3,
                    RoundingMode.HALF_UP);
        }

        String result = hashesPerSecond.toString();

        if (logger.isDebugEnabled())
            logger.debug("eth_hashrate(): " + result);

        return result;
    }

    public String eth_netHashrate() {
        BigInteger hashesPerHour = this.worldManager.getHashRateCalculator().calculateNetHashRate(1L,
                TimeUnit.HOURS);
        BigDecimal hashesPerSecond = new BigDecimal(hashesPerHour)
                .divide(new BigDecimal(TimeUnit.HOURS.toSeconds(1)), 3, RoundingMode.HALF_UP);

        String result = hashesPerSecond.toString();

        if (logger.isDebugEnabled())
            logger.debug("eth_netHashrate(): " + result);

        return result;
    }

    @Override
    public String[] net_peerList() {
        Collection<Channel> peers = worldManager.getChannelManager().getActivePeers();
        List<String> response = new ArrayList<>();
        peers.forEach(channel -> response.add(channel.toString()));
        return response.stream().toArray(String[]::new);
    }

    public String eth_gasPrice() {
        String gasPrice = null;
        try {
            return gasPrice = TypeConverter.toJsonHex(eth.getGasPrice());
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_gasPrice(): " + gasPrice);
        }
    }

    public String[] eth_accounts() {
        String[] s = null;
        try {
            return s = personal_listAccounts();
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_accounts(): " + Arrays.toString(s));
        }
    }

    public String eth_blockNumber() {
        Blockchain blockchain = worldManager.getBlockchain();
        Block bestBlock;

        synchronized (blockchain) {
            bestBlock = blockchain.getBestBlock();
        }

        long b = 0;
        if (bestBlock != null) {
            b = bestBlock.getNumber();
        }

        logger.debug("eth_blockNumber(): {}", b);

        return toJsonHex(b);
    }

    public String eth_getBalance(String address, String block) throws Exception {
        /* HEX String  - an integer block number
        *  String "earliest"  for the earliest/genesis block
        *  String "latest"  - for the latest mined block
        *  String "pending"  - for the pending state/transactions
        */
        Repository repository = getRepoByJsonBlockId(block);

        if (repository == null)
            throw new NullPointerException();

        byte[] addressAsByteArray = StringHexToByteArray(address);
        BigInteger balance = repository.getBalance(addressAsByteArray);

        return toJsonHex(balance);
    }

    public String eth_getBalance(String address) throws Exception {
        byte[] addressAsByteArray = StringHexToByteArray(address);
        BigInteger balance = this.repository.getBalance(addressAsByteArray);

        return toJsonHex(balance);
    }

    @Override
    public String eth_getStorageAt(String address, String storageIdx, String blockId) throws Exception {
        String s = null;
        try {
            byte[] addressAsByteArray = StringHexToByteArray(address);
            Repository repository = getRepoByJsonBlockId(blockId);
            if (repository == null)
                return null;
            DataWord storageValue = repository.getStorageValue(addressAsByteArray,
                    new DataWord(StringHexToByteArray(storageIdx)));
            if (storageValue != null) {
                return s = TypeConverter.toJsonHex(storageValue.getData());
            } else {
                return null;
            }
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getStorageAt(" + address + ", " + storageIdx + ", " + blockId + "): " + s);
        }
    }

    @Override
    public String eth_getTransactionCount(String address, String blockId) throws Exception {
        String s = null;
        try {
            byte[] addressAsByteArray = TypeConverter.StringHexToByteArray(address);

            Repository repository = getRepoByJsonBlockId(blockId);
            if (repository != null) {
                BigInteger nonce = repository.getNonce(addressAsByteArray);
                return s = TypeConverter.toJsonHex(nonce);
            } else {
                return null;
            }
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getTransactionCount(" + address + ", " + blockId + "): " + s);
        }
    }

    public Block getBlockByJSonHash(String blockHash) throws Exception {
        byte[] bhash = StringHexToByteArray(blockHash);
        return worldManager.getBlockchain().getBlockByHash(bhash);
    }

    public String eth_getBlockTransactionCountByHash(String blockHash) throws Exception {
        String s = null;
        try {
            Block b = getBlockByJSonHash(blockHash);
            if (b == null)
                return null;
            long n = b.getTransactionsList().size();
            return s = TypeConverter.toJsonHex(n);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getBlockTransactionCountByHash(" + blockHash + "): " + s);
        }
    }

    public Block getBlockByNumberOrStr(String bnOrId) throws Exception {
        synchronized (worldManager.getBlockchain()) {
            Block b;
            if (bnOrId.equals("latest"))
                b = worldManager.getBlockchain().getBestBlock();
            else if (bnOrId.equals("earliest"))
                b = worldManager.getBlockchain().getBlockByNumber(0);
            else if (bnOrId.equals("pending"))
                throw new JsonRpcUnimplementedMethodException(
                        "The method don't support 'pending' as a parameter yet");
            else {
                long bn = JSonHexToLong(bnOrId);
                b = worldManager.getBlockchain().getBlockByNumber(bn);
            }
            return b;
        }
    }

    public String eth_getBlockTransactionCountByNumber(String bnOrId) throws Exception {
        String s = null;
        try {
            List<Transaction> list = getTransactionsByJsonBlockId(bnOrId);
            if (list == null)
                return null;
            long n = list.size();
            return s = TypeConverter.toJsonHex(n);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getBlockTransactionCountByNumber(" + bnOrId + "): " + s);
        }
    }

    public String eth_getUncleCountByBlockHash(String blockHash) throws Exception {
        Block b = getBlockByJSonHash(blockHash);
        long n = b.getUncleList().size();
        return toJsonHex(n);
    }

    public String eth_getUncleCountByBlockNumber(String bnOrId) throws Exception {
        Block b = getBlockByNumberOrStr(bnOrId);
        long n = b.getUncleList().size();
        return toJsonHex(n);
    }

    public String eth_getCode(String address, String blockId) throws Exception {
        if (blockId == null)
            throw new NullPointerException();

        String s = null;
        try {
            Block block = getByJsonBlockId(blockId);
            if (block == null)
                return null;
            byte[] addressAsByteArray = TypeConverter.StringHexToByteArray(address);
            Repository repository = getRepoByJsonBlockId(blockId);
            if (repository != null) {
                byte[] code = repository.getCode(addressAsByteArray);
                s = TypeConverter.toJsonHex(code);
            }
            return s;
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getCode(" + address + ", " + blockId + "): " + s);
        }
    }

    public String eth_sign(String addr, String data) throws Exception {
        String s = null;
        try {
            return s = this.sign(data, getAccount(JSonHexToHex(addr)));
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_sign({}, {}): {}", addr, data, s);
        }
    }

    private String sign(String data, Account account) {
        if (account == null)
            throw new JsonRpcInvalidParamException("Account not found");

        byte[] dataHash = TypeConverter.StringHexToByteArray(data);
        ECKey.ECDSASignature signature = account.getEcKey().sign(dataHash);

        String signatureAsString = signature.r.toString() + signature.s.toString() + signature.v;

        // byte[] rlpSig = RLP.encode(signature);
        return TypeConverter.toJsonHex(signatureAsString);
    }

    public String eth_sendTransaction(CallArguments args) throws Exception {
        return this.sendTransaction(args, this.getAccount(args.from));
    }

    private String sendTransaction(CallArguments args, Account account) throws Exception {
        String s = null;
        try {
            if (account == null)
                throw new JsonRpcInvalidParamException("From address private key could not be found in this node");

            String toAddress = args.to != null ? Hex.toHexString(StringHexToByteArray(args.to)) : null;

            BigInteger value = args.value != null ? TypeConverter.StringNumberAsBigInt(args.value)
                    : BigInteger.ZERO;
            BigInteger gasPrice = args.gasPrice != null ? TypeConverter.StringNumberAsBigInt(args.gasPrice)
                    : BigInteger.ZERO;
            BigInteger gasLimit = args.gas != null ? TypeConverter.StringNumberAsBigInt(args.gas)
                    : BigInteger.valueOf(GasCost.TRANSACTION_DEFAULT);

            if (args.data != null && args.data.startsWith("0x"))
                args.data = args.data.substring(2);

            PendingState pendingState = worldManager.getPendingState();
            synchronized (pendingState) {
                BigInteger accountNonce = args.nonce != null ? TypeConverter.StringNumberAsBigInt(args.nonce)
                        : (pendingState.getRepository().getNonce(account.getAddress()));
                Transaction tx = Transaction.create(toAddress, value, accountNonce, gasPrice, gasLimit, args.data);
                tx.sign(account.getEcKey().getPrivKeyBytes());
                eth.submitTransaction(tx);
                s = TypeConverter.toJsonHex(tx.getHash());
            }
            return s;
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_sendTransaction({}): {}", args, s);
        }

    }

    public String eth_sendRawTransaction(String rawData) throws Exception {
        String s = null;
        try {
            Transaction tx = new Transaction(StringHexToByteArray(rawData));

            if (null == tx.getGasLimit() || null == tx.getGasPrice() || null == tx.getValue()) {
                throw new JsonRpcInvalidParamException("Missing parameter, gasPrice, gas or value");
            }

            eth.submitTransaction(tx);

            return s = TypeConverter.toJsonHex(tx.getHash());
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_sendRawTransaction(" + rawData + "): " + s);
        }
    }

    public ProgramResult createCallTxAndExecute(CallArguments args, byte[] keyToSign) throws Exception {
        byte[] nonce = new byte[] { 0 };
        Transaction tx = Transaction.create(nonce, args);

        byte[] signingKey = (keyToSign == null) ? new byte[32] : keyToSign;

        tx.sign(signingKey);

        Block block = worldManager.getBlockchain().getBestBlock();

        return eth.callConstantCallTransaction(tx, block);
    }

    public String eth_call(CallArguments args, String bnOrId) throws Exception {
        if (!bnOrId.equals("latest"))
            throw new JsonRpcUnimplementedMethodException("Method only supports 'latest' as a parameter so far.");

        ProgramResult res = createCallTxAndExecute(args, this.getKeyToSign(args.from));
        return toJsonHex(res.getHReturn());
    }

    public String eth_estimateGas(CallArguments args) throws Exception {
        ProgramResult res = createCallTxAndExecute(args, this.getKeyToSign(args.from));
        return toJsonHex(res.getGasUsed());
    }

    public BlockResult getBlockResult(Block b, boolean fullTx) {
        if (b == null)
            return null;

        byte[] mergeHeader = b.getBitcoinMergedMiningHeader();

        boolean isPending = (mergeHeader == null || mergeHeader.length == 0) && !b.isGenesis();

        BlockResult br = new BlockResult();
        br.number = isPending ? null : TypeConverter.toJsonHex(b.getNumber());
        br.hash = isPending ? null : TypeConverter.toJsonHex(b.getHash());
        br.parentHash = TypeConverter.toJsonHex(b.getParentHash());
        br.sha3Uncles = TypeConverter.toJsonHex(b.getUnclesHash());
        br.logsBloom = isPending ? null : TypeConverter.toJsonHex(b.getLogBloom());
        br.transactionsRoot = TypeConverter.toJsonHex(b.getTxTrieRoot());
        br.stateRoot = TypeConverter.toJsonHex(b.getStateRoot());
        br.receiptsRoot = TypeConverter.toJsonHex(b.getReceiptsRoot());
        br.miner = isPending ? null : TypeConverter.toJsonHex(b.getCoinbase());
        br.difficulty = TypeConverter.toJsonHex(b.getDifficulty());
        br.totalDifficulty = TypeConverter.toJsonHex(worldManager.getBlockchain().getTotalDifficulty());
        br.extraData = TypeConverter.toJsonHex(b.getExtraData());
        br.size = TypeConverter.toJsonHex(b.getEncoded().length);
        br.gasLimit = TypeConverter.toJsonHex(b.getGasLimit());
        BigInteger mgp = b.getMinGasPriceAsInteger();
        br.minimumGasPrice = mgp != null ? mgp.toString() : "";
        br.gasUsed = TypeConverter.toJsonHex(b.getGasUsed());
        br.timestamp = TypeConverter.toJsonHex(b.getTimestamp());

        List<Object> txes = new ArrayList<>();
        if (fullTx) {
            for (int i = 0; i < b.getTransactionsList().size(); i++) {
                txes.add(new TransactionResultDTO(b, i, b.getTransactionsList().get(i)));
            }
        } else {
            for (Transaction tx : b.getTransactionsList()) {
                txes.add(toJsonHex(tx.getHash()));
            }
        }
        br.transactions = txes.toArray();

        List<String> ul = new ArrayList<>();
        for (BlockHeader header : b.getUncleList()) {
            ul.add(toJsonHex(header.getHash()));
        }
        br.uncles = ul.toArray(new String[ul.size()]);

        return br;
    }

    public BlockResult eth_getBlockByHash(String blockHash, Boolean fullTransactionObjects) throws Exception {
        BlockResult s = null;
        try {
            Block b = getBlockByJSonHash(blockHash);
            return getBlockResult(b, fullTransactionObjects);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getBlockByHash(" + blockHash + ", " + fullTransactionObjects + "): " + s);
        }
    }

    public BlockResult eth_getBlockByNumber(String bnOrId, Boolean fullTransactionObjects) throws Exception {
        BlockResult s = null;
        try {
            Block b = getByJsonBlockId(bnOrId);

            return s = (b == null ? null : getBlockResult(b, fullTransactionObjects));
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getBlockByNumber(" + bnOrId + ", " + fullTransactionObjects + "): " + s);
        }
    }

    public TransactionResultDTO eth_getTransactionByHash(String transactionHash) throws Exception {
        TransactionResultDTO s = null;
        try {
            Blockchain blockchain = worldManager.getBlockchain();

            byte[] txHash = StringHexToByteArray(transactionHash);
            Block block = null;

            TransactionInfo txInfo = blockchain.getTransactionInfo(txHash);

            if (txInfo == null) {
                if (transactionHash != null && transactionHash.startsWith("0x"))
                    transactionHash = transactionHash.substring(2);

                List<Transaction> txs = this.getTransactionsByJsonBlockId("pending");

                for (Transaction tx : txs) {
                    if (Hex.toHexString(tx.getHash()).equals(transactionHash)) {
                        return s = new TransactionResultDTO(null, null, tx);
                    }
                }
            } else {
                block = blockchain.getBlockByHash(txInfo.getBlockHash());
                // need to return txes only from main chain
                Block mainBlock = blockchain.getBlockByNumber(block.getNumber());
                if (!Arrays.equals(block.getHash(), mainBlock.getHash())) {
                    return null;
                }
                txInfo.setTransaction(block.getTransactionsList().get(txInfo.getIndex()));
            }

            if (txInfo == null) {
                return null;
            }
            return s = new TransactionResultDTO(block, txInfo.getIndex(), txInfo.getReceipt().getTransaction());
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getTransactionByHash(" + transactionHash + "): " + s);
        }
    }

    public TransactionResultDTO eth_getTransactionByBlockHashAndIndex(String blockHash, String index)
            throws Exception {
        TransactionResultDTO s = null;
        try {
            Block b = getBlockByJSonHash(blockHash);
            if (b == null)
                return null;
            int idx = JSonHexToInt(index);
            if (idx >= b.getTransactionsList().size())
                return null;
            Transaction tx = b.getTransactionsList().get(idx);
            return s = new TransactionResultDTO(b, idx, tx);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getTransactionByBlockHashAndIndex(" + blockHash + ", " + index + "): " + s);
        }
    }

    public TransactionResultDTO eth_getTransactionByBlockNumberAndIndex(String bnOrId, String index)
            throws Exception {
        TransactionResultDTO s = null;
        try {
            Block b = getByJsonBlockId(bnOrId);
            List<Transaction> txs = getTransactionsByJsonBlockId(bnOrId);
            if (txs == null)
                return null;
            int idx = JSonHexToInt(index);
            if (idx >= txs.size())
                return null;
            Transaction tx = txs.get(idx);
            return s = new TransactionResultDTO(b, idx, tx);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getTransactionByBlockNumberAndIndex(" + bnOrId + ", " + index + "): " + s);
        }
    }

    public TransactionReceiptDTO eth_getTransactionReceipt(String transactionHash) throws Exception {
        logger.trace("eth_getTransactionReceipt(" + transactionHash + ")");

        Blockchain blockchain = worldManager.getBlockchain();
        byte[] hash = StringHexToByteArray(transactionHash);
        TransactionInfo txInfo = blockchain.getReceiptStore().getInMainChain(hash, worldManager.getBlockStore());

        if (txInfo == null) {
            logger.trace("No transaction info for " + transactionHash);
            return null;
        }

        Block block = worldManager.getBlockStore().getBlockByHash(txInfo.getBlockHash());
        Transaction tx = block.getTransactionsList().get(txInfo.getIndex());
        txInfo.setTransaction(tx);

        return new TransactionReceiptDTO(block, txInfo);
    }

    @Override
    public BlockResult eth_getUncleByBlockHashAndIndex(String blockHash, String uncleIdx) throws Exception {
        BlockResult s = null;
        try {
            Block block = this.worldManager.getBlockchain().getBlockByHash(StringHexToByteArray(blockHash));
            if (block == null)
                return null;
            int idx = JSonHexToInt(uncleIdx);
            if (idx >= block.getUncleList().size())
                return null;
            BlockHeader uncleHeader = block.getUncleList().get(idx);
            Block uncle = this.worldManager.getBlockchain().getBlockByHash(uncleHeader.getHash());
            if (uncle == null) {
                uncle = new Block(uncleHeader, Collections.emptyList(), Collections.emptyList());
            }
            return s = getBlockResult(uncle, false);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getUncleByBlockHashAndIndex(" + blockHash + ", " + uncleIdx + "): " + s);
        }
    }

    @Override
    public BlockResult eth_getUncleByBlockNumberAndIndex(String blockId, String uncleIdx) throws Exception {
        BlockResult s = null;
        try {
            Block block = getByJsonBlockId(blockId);
            return s = block == null ? null
                    : eth_getUncleByBlockHashAndIndex(Hex.toHexString(block.getHash()), uncleIdx);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getUncleByBlockNumberAndIndex(" + blockId + ", " + uncleIdx + "): " + s);
        }
    }

    @Override
    public String[] eth_getCompilers() {
        String[] s = null;
        try {
            return s = new String[] { "solidity" };
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getCompilers(): " + Arrays.toString(s));
        }
    }

    @Override
    public Map<String, CompilationResultDTO> eth_compileLLL(String contract) {
        throw new UnsupportedOperationException("LLL compiler not supported");
    }

    @Override
    public Map<String, CompilationResultDTO> eth_compileSolidity(String contract) throws Exception {
        Map<String, CompilationResultDTO> compilationResultDTOMap = new HashedMap<>();
        try {
            SolidityCompiler.Result res = solidityCompiler.compile(contract.getBytes(StandardCharsets.UTF_8), true,
                    SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN);
            if (!res.errors.isEmpty()) {
                throw new RuntimeException("Compilation error: " + res.errors);
            }
            org.ethereum.solidity.compiler.CompilationResult result = org.ethereum.solidity.compiler.CompilationResult
                    .parse(res.output);
            org.ethereum.solidity.compiler.CompilationResult.ContractMetadata contractMetadata = result.contracts
                    .values().iterator().next();

            CompilationInfoDTO compilationInfo = new CompilationInfoDTO();
            compilationInfo.setSource(contract);
            compilationInfo.setLanguage("Solidity");
            compilationInfo.setLanguageVersion("0");
            compilationInfo.setCompilerVersion(result.version);
            compilationInfo.setAbiDefinition(new CallTransaction.Contract(contractMetadata.abi));

            CompilationResultDTO compilationResult = new CompilationResultDTO(contractMetadata, compilationInfo);
            String contractName = (String) result.contracts.keySet().toArray()[0];

            compilationResultDTOMap.put(contractName, compilationResult);

            return compilationResultDTOMap;
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_compileSolidity(" + contract + ")" + compilationResultDTOMap);
        }
    }

    @Override
    public Map<String, CompilationResultDTO> eth_compileSerpent(String contract) {
        throw new UnsupportedOperationException("Serpent compiler not supported");
    }

    static class Filter {
        static abstract class FilterEvent {
            public abstract Object getJsonEventObject();
        }

        List<FilterEvent> events = new ArrayList<>();

        public synchronized boolean hasNew() {
            return !events.isEmpty();
        }

        public synchronized Object[] poll() {
            Object[] ret = new Object[events.size()];
            for (int i = 0; i < ret.length; i++) {
                ret[i] = events.get(i).getJsonEventObject();
            }
            this.events.clear();
            return ret;
        }

        protected synchronized void add(FilterEvent evt) {
            events.add(evt);
        }

        public void newBlockReceived(Block b) {
        }

        public void newPendingTx(Transaction tx) {
            // add TransactionReceipt for PendingTx
        }
    }

    static class NewBlockFilter extends Filter {
        class NewBlockFilterEvent extends FilterEvent {
            public final Block b;

            NewBlockFilterEvent(Block b) {
                this.b = b;
            }

            @Override
            public String getJsonEventObject() {
                return toJsonHex(b.getHash());
            }
        }

        public void newBlockReceived(Block b) {
            add(new NewBlockFilterEvent(b));
        }
    }

    static class PendingTransactionFilter extends Filter {
        class PendingTransactionFilterEvent extends FilterEvent {
            private final Transaction tx;

            PendingTransactionFilterEvent(Transaction tx) {
                this.tx = tx;
            }

            @Override
            public String getJsonEventObject() {
                return toJsonHex(tx.getHash());
            }
        }

        public void newPendingTx(Transaction tx) {
            add(new PendingTransactionFilterEvent(tx));
        }
    }

    class JsonLogFilter extends Filter {
        class LogFilterEvent extends FilterEvent {
            private final LogFilterElement el;

            LogFilterEvent(LogFilterElement el) {
                this.el = el;
            }

            @Override
            public LogFilterElement getJsonEventObject() {
                return el;
            }
        }

        LogFilter logFilter;
        boolean onNewBlock;
        boolean onPendingTx;

        public JsonLogFilter(LogFilter logFilter) {
            this.logFilter = logFilter;
        }

        void onLogMatch(LogInfo logInfo, Block b, int txIndex, Transaction tx, int logIdx) {
            add(new LogFilterEvent(new LogFilterElement(logInfo, b, txIndex, tx, logIdx)));
        }

        void onTransactionReceipt(TransactionReceipt receipt, Block b, int txIndex) {
            if (logFilter.matchBloom(receipt.getBloomFilter())) {
                int logIdx = 0;
                for (LogInfo logInfo : receipt.getLogInfoList()) {
                    if (logFilter.matchBloom(logInfo.getBloom()) && logFilter.matchesExactly(logInfo)) {
                        onLogMatch(logInfo, b, txIndex, receipt.getTransaction(), logIdx);
                    }
                    logIdx++;
                }
            }
        }

        void onTransaction(Transaction tx, Block b, int txIndex) {
            TransactionInfo txInfo = worldManager.getBlockchain().getTransactionInfo(tx.getHash());
            TransactionReceipt receipt = txInfo.getReceipt();

            LogFilterElement[] logs = new LogFilterElement[receipt.getLogInfoList().size()];
            for (int i = 0; i < logs.length; i++) {
                LogInfo logInfo = receipt.getLogInfoList().get(i);
                if (logFilter.matchesContractAddress(logInfo.getAddress())) {
                    onTransactionReceipt(receipt, b, txIndex);
                }
            }
        }

        void onBlock(Block b) {
            if (logFilter.matchBloom(new Bloom(b.getLogBloom()))) {
                int txIdx = 0;
                for (Transaction tx : b.getTransactionsList()) {
                    onTransaction(tx, b, txIdx);
                    txIdx++;
                }
            }
        }

        @Override
        public void newBlockReceived(Block b) {
            if (onNewBlock)
                onBlock(b);
        }

        @Override
        public void newPendingTx(Transaction tx) {
            //empty method
        }
    }

    AtomicInteger filterCounter = new AtomicInteger(1);
    Map<Integer, Filter> installedFilters = new Hashtable<>();

    @Override
    public String eth_newFilter(FilterRequest fr) throws Exception {
        String str = null;
        try {
            LogFilter logFilter = new LogFilter();

            if (fr.address instanceof String) {
                logFilter.withContractAddress(StringHexToByteArray((String) fr.address));
            } else if (fr.address instanceof String[]) {
                List<byte[]> addr = new ArrayList<>();
                for (String s : ((String[]) fr.address)) {
                    addr.add(StringHexToByteArray(s));
                }
                logFilter.withContractAddress(addr.toArray(new byte[0][]));
            }

            if (fr.topics != null) {
                for (Object topic : fr.topics) {
                    if (topic == null) {
                        logFilter.withTopic(null);
                    } else if (topic instanceof String) {
                        logFilter.withTopic(new DataWord(StringHexToByteArray((String) topic)).getData());
                    } else if (topic instanceof String[]) {
                        List<byte[]> t = new ArrayList<>();
                        for (String s : ((String[]) topic)) {
                            t.add(new DataWord(StringHexToByteArray(s)).getData());
                        }
                        logFilter.withTopic(t.toArray(new byte[0][]));
                    }
                }
            }

            JsonLogFilter filter = new JsonLogFilter(logFilter);

            int id;

            synchronized (filterLock) {
                id = filterCounter.getAndIncrement();
                installedFilters.put(id, filter);
            }

            Block blockFrom = fr.fromBlock == null ? null : getBlockByNumberOrStr(fr.fromBlock);
            Block blockTo = fr.toBlock == null ? null : getBlockByNumberOrStr(fr.toBlock);

            if (blockFrom != null) {
                // need to add historical data
                blockTo = blockTo == null ? worldManager.getBlockchain().getBestBlock() : blockTo;
                for (long blockNum = blockFrom.getNumber(); blockNum <= blockTo.getNumber(); blockNum++) {
                    filter.onBlock(worldManager.getBlockchain().getBlockByNumber(blockNum));
                }
            }

            // the following is not precisely documented
            if ("pending".equalsIgnoreCase(fr.fromBlock) || "pending".equalsIgnoreCase(fr.toBlock)) {
                filter.onPendingTx = true;
            } else if ("latest".equalsIgnoreCase(fr.fromBlock) || "latest".equalsIgnoreCase(fr.toBlock)) {
                filter.onNewBlock = true;
            }

            // RSK brute force
            filter.onNewBlock = true;

            return str = toJsonHex(id);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_newFilter(" + fr + "): " + str);
        }
    }

    @Override
    public String eth_newBlockFilter() {
        String s = null;
        try {
            int id;

            synchronized (filterLock) {
                id = filterCounter.getAndIncrement();
                installedFilters.put(id, new NewBlockFilter());
            }

            return s = toJsonHex(id);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_newBlockFilter(): " + s);
        }
    }

    @Override
    public String eth_newPendingTransactionFilter() {
        String s = null;
        try {
            int id;

            synchronized (filterLock) {
                id = filterCounter.getAndIncrement();
                installedFilters.put(id, new PendingTransactionFilter());
            }

            return s = toJsonHex(id);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_newPendingTransactionFilter(): " + s);
        }
    }

    @Override
    public boolean eth_uninstallFilter(String id) {
        Boolean s = null;
        try {
            if (id == null)
                return false;

            synchronized (filterLock) {
                return s = installedFilters.remove(StringHexToBigInteger(id).intValue()) != null;
            }
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_uninstallFilter(" + id + "): " + s);
        }
    }

    @Override
    public Object[] eth_getFilterChanges(String id) {
        Object[] s = null;
        try {
            synchronized (filterLock) {
                Filter filter = installedFilters.get(StringHexToBigInteger(id).intValue());
                if (filter == null)
                    return null;
                return s = filter.poll();
            }
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_getFilterChanges(" + id + "): " + Arrays.toString(s));
        }
    }

    @Override
    public Object[] eth_getFilterLogs(String id) {
        logger.debug("eth_getFilterLogs ...");
        return eth_getFilterChanges(id);
    }

    @Override
    public Object[] eth_getLogs(FilterRequest fr) throws Exception {
        logger.debug("eth_getLogs ...");
        String id = eth_newFilter(fr);
        Object[] ret = eth_getFilterChanges(id);
        eth_uninstallFilter(id);
        return ret;
    }

    @Override
    public Map<String, String> rpc_modules() {
        logger.debug("rpc_modules...");

        Map<String, String> map = new HashMap<>();

        for (ModuleDescription module : RskSystemProperties.RSKCONFIG.getRpcModules())
            if (module.isEnabled())
                map.put(module.getName(), module.getVersion());

        return map;
    }

    public void db_putString() {
    }

    public void db_getString() {
    }

    public boolean eth_submitWork(String nonce, String header, String mince) {
        throw new UnsupportedOperationException("Not implemeted yet");
    }

    public boolean eth_submitHashrate(String Hashrate, String ID) {
        throw new UnsupportedOperationException("Not implemeted yet");
    }

    public void db_putHex() {
    }

    public void db_getHex() {
    }

    private List<Transaction> getTransactionsByJsonBlockId(String id) {
        if ("pending".equalsIgnoreCase(id)) {
            return worldManager.getPendingState().getAllPendingTransactions();
        } else {
            Block block = getByJsonBlockId(id);
            return block != null ? block.getTransactionsList() : null;
        }
    }

    private Block getByJsonBlockId(String id) {
        if ("earliest".equalsIgnoreCase(id)) {
            return worldManager.getBlockchain().getBlockByNumber(0);
        } else if ("latest".equalsIgnoreCase(id)) {
            return worldManager.getBlockchain().getBestBlock();
        } else if ("pending".equalsIgnoreCase(id)) {
            throw new JsonRpcUnimplementedMethodException("The method don't support 'pending' as a parameter yet");
        } else {
            try {
                long blockNumber = StringHexToBigInteger(id).longValue();
                return worldManager.getBlockchain().getBlockByNumber(blockNumber);
            } catch (NumberFormatException | StringIndexOutOfBoundsException e) {
                throw new JsonRpcInvalidParamException("invalid blocknumber " + id);
            }
        }
    }

    private Repository getRepoByJsonBlockId(String id) {
        if ("pending".equalsIgnoreCase(id)) {
            return worldManager.getPendingState().getRepository();
        } else {
            Block block = getByJsonBlockId(id);
            if (block != null) {
                return ((Repository) this.repository).getSnapshotTo(block.getStateRoot());
            } else {
                return null;
            }
        }
    }

    @Override
    public String personal_newAccountWithSeed(String seed) {
        String s = null;
        try {
            byte[] address = this.wallet.addAccountWithSeed(seed);
            return s = toJsonHex(address);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("personal_newAccountWithSeed(*****): " + s);
        }
    }

    @Override
    public String personal_newAccount(String passphrase) {
        String s = null;
        try {
            byte[] address = this.wallet.addAccount(passphrase);
            return s = toJsonHex(address);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("personal_newAccount(*****): " + s);
        }
    }

    public String eth_addAccount(String privKey) {
        String s = null;
        try {
            byte[] address = this.wallet.addAccountWithPrivateKey(Hex.decode(privKey));
            return s = toJsonHex(address);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_addAccount(*****): " + s);
        }
    }

    @Override
    public String personal_importRawKey(String key, String passphrase) {
        String s = null;
        try {
            byte[] address = this.wallet.addAccountWithPrivateKey(Hex.decode(key), passphrase);
            return s = toJsonHex(address);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("personal_importRawKey(*****): " + s);
        }
    }

    @Override
    public String personal_dumpRawKey(String address) throws Exception {
        String s = null;
        try {
            Account account = getAccount(JSonHexToHex(address));

            if (account == null)
                throw new Exception("Address private key is locked or could not be found in this node");

            return s = toJsonHex(Hex.toHexString(account.getEcKey().getPrivKeyBytes()));
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("personal_dumpRawKey(*****): " + s);
        }
    }

    @Override
    public String[] personal_listAccounts() {
        return this.listAccounts(this.wallet.getAccountAddresses());
    }

    private String[] listAccounts(List<byte[]> addresses) {
        String[] ret = new String[addresses.size()];
        try {
            int i = 0;
            for (byte[] address : addresses) {
                ret[i++] = toJsonHex(address);
            }
            return ret;
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("personal_listAccounts(): {}", Arrays.toString(ret));
        }
    }

    @Override
    public String personal_sendTransaction(CallArguments args, String passphrase) throws Exception {
        String s = null;
        try {
            return s = sendPersonalTransacction(args, getAccount(args.from, passphrase));
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("eth_sendTransaction(" + args + "): " + s);
        }
    }

    private String sendPersonalTransacction(CallArguments args, Account account) throws Exception {
        if (account == null)
            throw new Exception("From address private key could not be found in this node");

        String toAddress = args.to != null ? Hex.toHexString(StringHexToByteArray(args.to)) : null;

        BigInteger accountNonce = args.nonce != null ? TypeConverter.StringNumberAsBigInt(args.nonce)
                : (worldManager.getPendingState().getRepository().getNonce(account.getAddress()));
        BigInteger value = args.value != null ? TypeConverter.StringNumberAsBigInt(args.value) : BigInteger.ZERO;
        BigInteger gasPrice = args.gasPrice != null ? TypeConverter.StringNumberAsBigInt(args.gasPrice)
                : BigInteger.ZERO;
        BigInteger gasLimit = args.gas != null ? TypeConverter.StringNumberAsBigInt(args.gas)
                : BigInteger.valueOf(GasCost.TRANSACTION);

        if (args.data != null && args.data.startsWith("0x"))
            args.data = args.data.substring(2);

        Transaction tx = Transaction.create(toAddress, value, accountNonce, gasPrice, gasLimit, args.data);

        tx.sign(account.getEcKey().getPrivKeyBytes());

        eth.submitTransaction(tx);

        return TypeConverter.toJsonHex(tx.getHash());
    }

    @Override
    public boolean personal_unlockAccount(String address, String passphrase, String duration) {
        long dur = (long) 1000 * 60 * 30;
        if (duration != null && duration.length() > 0) {
            try {
                dur = JSonHexToLong(duration);
            } catch (Exception e) {
                throw new JsonRpcInvalidParamException("Can't parse duration param", e);
            }
        }

        return this.wallet.unlockAccount(StringHexToByteArray(address), passphrase, dur);
    }

    public Map<String, Object> eth_bridgeState() throws Exception {
        CallArguments arguments = new CallArguments();
        arguments.to = "0x" + PrecompiledContracts.BRIDGE_ADDR;
        arguments.data = Hex.toHexString(Bridge.GET_STATE_FOR_DEBUGGING.encodeSignature());
        arguments.gasPrice = "0x0";
        arguments.value = "0x0";
        arguments.gas = "0xf4240";
        ProgramResult res = createCallTxAndExecute(arguments, new byte[32]);
        BridgeState state = BridgeStateReader.readSate(TypeConverter.removeZeroX(toJsonHex(res.getHReturn())));
        return state.stateToMap();
    }

    @Override
    public boolean personal_lockAccount(String address) {
        return this.wallet.lockAccount(StringHexToByteArray(address));
    }

    private Account getDefaultAccount() {
        List<byte[]> accountAddresses = this.wallet.getAccountAddresses();

        if (!CollectionUtils.isEmpty(accountAddresses)) {
            return this.wallet.getAccount(accountAddresses.get(0));
        }

        return null;
    }

    @VisibleForTesting
    public Account getAccount(String address) {
        return this.wallet.getAccount(StringHexToByteArray(address));
    }

    @VisibleForTesting
    public Account getAccount(String address, String passphrase) {
        return this.wallet.getAccount(StringHexToByteArray(address), passphrase);
    }

    private byte[] getKeyToSign(String address) {
        byte[] privateKey = new byte[32];

        Account account;
        account = (address != null) ? this.wallet.getAccount(StringHexToByteArray(address))
                : this.getDefaultAccount();

        if (account != null)
            privateKey = account.getEcKey().getPrivKeyBytes();

        return privateKey;
    }

    @Override
    public String evm_snapshot() {
        Blockchain blockchain = worldManager.getBlockchain();

        int snapshotId = snapshotManager.takeSnapshot(blockchain);

        logger.debug("evm_snapshot(): {}", snapshotId);

        return toJsonHex(snapshotId);
    }

    @Override
    public boolean evm_revert(String snapshotId) {
        try {
            int sid = StringHexToBigInteger(snapshotId).intValue();
            return snapshotManager.revertToSnapshot(worldManager.getBlockchain(), sid);
        } catch (NumberFormatException | StringIndexOutOfBoundsException e) {
            throw new JsonRpcInvalidParamException("invalid snapshot id " + snapshotId, e);
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("evm_revert({})", snapshotId);
        }
    }

    @Override
    public void evm_reset() {
        snapshotManager.resetSnapshots(worldManager.getBlockchain());
        if (logger.isDebugEnabled())
            logger.debug("evm_reset()");
    }

    @Override
    public void evm_mine() {
        minerManager.mineBlock(worldManager.getBlockchain(), worldManager.getMinerClient(),
                worldManager.getMinerServer());
        if (logger.isDebugEnabled())
            logger.debug("evm_mine()");
    }

    @Override
    public String evm_increaseTime(String seconds) {
        try {
            long nseconds = StringHexToBigInteger(seconds).longValue();
            String result = toJsonHex(worldManager.getMinerServer().increaseTime(nseconds));
            if (logger.isDebugEnabled())
                logger.debug("evm_increaseTime({}): {}", seconds, result);
            return result;
        } catch (NumberFormatException | StringIndexOutOfBoundsException e) {
            throw new JsonRpcInvalidParamException("invalid number of seconds " + seconds, e);
        }
    }
}