/* * Copyright (c) [2016] [ <ether.camp> ] * 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 <http://www.gnu.org/licenses/>. */ package org.ethereum.jsonrpc; import org.apache.commons.collections4.map.LRUMap; import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; import org.ethereum.core.*; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; import org.ethereum.db.BlockStore; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.core.TransactionInfo; import org.ethereum.db.TransactionStore; import org.ethereum.facade.Ethereum; import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.manager.WorldManager; import org.ethereum.mine.BlockMiner; import org.ethereum.net.client.Capability; import org.ethereum.net.client.ConfigCapabilities; import org.ethereum.net.rlpx.Node; import org.ethereum.net.server.ChannelManager; import org.ethereum.net.server.PeerServer; import org.ethereum.solidity.compiler.SolidityCompiler; import org.ethereum.sync.SyncManager; import org.ethereum.util.BuildInfo; import org.ethereum.util.ByteUtil; import org.ethereum.util.RLP; import org.ethereum.vm.DataWord; import org.ethereum.vm.LogInfo; import org.ethereum.vm.program.invoke.ProgramInvokeFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.math.BigInteger; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import static java.lang.Math.max; import static org.ethereum.crypto.HashUtil.sha3; import static org.ethereum.jsonrpc.TypeConverter.*; import static org.ethereum.jsonrpc.TypeConverter.StringHexToByteArray; import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; import static org.ethereum.util.ByteUtil.bigIntegerToBytes; /** * Created by Anton Nashatyrev on 25.11.2015. */ @Component @Lazy public class JsonRpcImpl implements JsonRpc { private static final Logger logger = LoggerFactory.getLogger("jsonrpc"); public class BinaryCallArguments { public long nonce; public long gasPrice; public long gasLimit; public String toAddress; public long value; public byte[] data; public void setArguments(CallArguments args) throws Exception { nonce = 0; if (args.nonce != null && args.nonce.length() != 0) nonce = JSonHexToLong(args.nonce); gasPrice = 0; if (args.gasPrice != null && args.gasPrice.length()!=0) gasPrice = JSonHexToLong(args.gasPrice); gasLimit = 4_000_000; if (args.gas != null && args.gas.length()!=0) gasLimit = JSonHexToLong(args.gas); toAddress = null; if (args.to != null && !args.to.isEmpty()) toAddress = JSonHexToHex(args.to); value=0; if (args.value != null && args.value.length()!=0) value = JSonHexToLong(args.value); data = null; if (args.data != null && args.data.length()!=0) data = TypeConverter.StringHexToByteArray(args.data); } } @Autowired SystemProperties config; @Autowired ConfigCapabilities configCapabilities; @Autowired public WorldManager worldManager; @Autowired public Repository repository; @Autowired Ethereum eth; @Autowired PeerServer peerServer; @Autowired SyncManager syncManager; @Autowired TransactionStore txStore; @Autowired ChannelManager channelManager; @Autowired BlockMiner blockMiner; @Autowired TransactionStore transactionStore; @Autowired PendingStateImpl pendingState; @Autowired SolidityCompiler solidityCompiler; @Autowired ProgramInvokeFactory programInvokeFactory; @Autowired CommonConfig commonConfig = CommonConfig.getDefault(); BlockchainImpl blockchain; CompositeEthereumListener compositeEthereumListener; long initialBlockNumber; Map<ByteArrayWrapper, Account> accounts = new HashMap<>(); AtomicInteger filterCounter = new AtomicInteger(1); Map<Integer, Filter> installedFilters = new Hashtable<>(); Map<ByteArrayWrapper, TransactionReceipt> pendingReceipts = Collections.synchronizedMap(new LRUMap<ByteArrayWrapper, TransactionReceipt>(1024)); @Autowired public JsonRpcImpl(final BlockchainImpl blockchain, final CompositeEthereumListener compositeEthereumListener) { this.blockchain = blockchain; this.compositeEthereumListener = compositeEthereumListener; initialBlockNumber = blockchain.getBestBlock().getNumber(); compositeEthereumListener.addListener(new EthereumListenerAdapter() { @Override public void onBlock(Block block, List<TransactionReceipt> receipts) { for (Filter filter : installedFilters.values()) { filter.newBlockReceived(block); } } @Override public void onPendingTransactionsReceived(List<Transaction> transactions) { for (Filter filter : installedFilters.values()) { for (Transaction tx : transactions) { filter.newPendingTx(tx); } } } @Override public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { ByteArrayWrapper txHashW = new ByteArrayWrapper(txReceipt.getTransaction().getHash()); if (state.isPending() || state == PendingTransactionState.DROPPED) { pendingReceipts.put(txHashW, txReceipt); } else { pendingReceipts.remove(txHashW); } } }); } 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 Block getBlockByJSonHash(String blockHash) throws Exception { byte[] bhash = TypeConverter.StringHexToByteArray(blockHash); return worldManager.getBlockchain().getBlockByHash(bhash); } private Block getByJsonBlockId(String id) { if ("earliest".equalsIgnoreCase(id)) { return blockchain.getBlockByNumber(0); } else if ("latest".equalsIgnoreCase(id)) { return blockchain.getBestBlock(); } else if ("pending".equalsIgnoreCase(id)) { return null; } else { long blockNumber = StringHexToBigInteger(id).longValue(); return blockchain.getBlockByNumber(blockNumber); } } private Repository getRepoByJsonBlockId(String id) { if ("pending".equalsIgnoreCase(id)) { return pendingState.getRepository(); } else { Block block = getByJsonBlockId(id); return this.repository.getSnapshotTo(block.getStateRoot()); } } private List<Transaction> getTransactionsByJsonBlockId(String id) { if ("pending".equalsIgnoreCase(id)) { return pendingState.getPendingTransactions(); } else { Block block = getByJsonBlockId(id); return block != null ? block.getTransactionsList() : null; } } protected Account getAccount(String address) throws Exception { return accounts.get(new ByteArrayWrapper(StringHexToByteArray(address))); } protected Account addAccount(String seed) { return addAccount(ECKey.fromPrivate(sha3(seed.getBytes()))); } protected Account addAccount(ECKey key) { Account account = new Account(); account.init(key); accounts.put(new ByteArrayWrapper(account.getAddress()), account); return account; } public String web3_clientVersion() { String s = "EthereumJ" + "/v" + config.projectVersion() + "/" + System.getProperty("os.name") + "/Java1.7/" + config.projectVersionModifier() + "-" + BuildInfo.buildHash; if (logger.isDebugEnabled()) logger.debug("web3_clientVersion(): " + s); return s; }; public String web3_sha3(String data) throws Exception { String s = null; try { byte[] result = HashUtil.sha3(TypeConverter.StringHexToByteArray(data)); return s = TypeConverter.toJsonHex(result); } finally { if (logger.isDebugEnabled()) logger.debug("web3_sha3(" + data + "): " + s); } } public String net_version() { String s = null; try { return s = eth_protocolVersion(); } finally { if (logger.isDebugEnabled()) logger.debug("net_version(): " + s); } } public String net_peerCount(){ String s = null; try { 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 = peerServer.isListening(); }finally { if (logger.isDebugEnabled()) logger.debug("net_listening(): " + s); } } public String eth_protocolVersion(){ String s = null; try { int version = 0; for (Capability capability : configCapabilities.getConfigCapabilities()) { if (capability.isEth()) { version = max(version, capability.getVersion()); } } return s = Integer.toString(version); } finally { if (logger.isDebugEnabled()) logger.debug("eth_protocolVersion(): " + s); } } public SyncingResult eth_syncing(){ SyncingResult s = new SyncingResult(); try { s.startingBlock = TypeConverter.toJsonHex(initialBlockNumber); s.currentBlock = TypeConverter.toJsonHex(blockchain.getBestBlock().getNumber()); s.highestBlock = TypeConverter.toJsonHex(syncManager.getLastKnownBlockNumber()); return s; }finally { if (logger.isDebugEnabled()) logger.debug("eth_syncing(): " + s); } }; public String eth_coinbase() { String s = null; try { return s = toJsonHex(blockchain.getMinerCoinbase()); } finally { if (logger.isDebugEnabled()) logger.debug("eth_coinbase(): " + s); } } public boolean eth_mining() { Boolean s = null; try { return s = blockMiner.isMining(); } finally { if (logger.isDebugEnabled()) logger.debug("eth_mining(): " + s); } } public String eth_hashrate() { String s = null; try { return s = null; } finally { if (logger.isDebugEnabled()) logger.debug("eth_hashrate(): " + s); } } public String eth_gasPrice(){ String s = null; try { return s = TypeConverter.toJsonHex(eth.getGasPrice()); } finally { if (logger.isDebugEnabled()) logger.debug("eth_gasPrice(): " + s); } } 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(){ String s = null; try { Block bestBlock = blockchain.getBestBlock(); long b = 0; if (bestBlock != null) { b = bestBlock.getNumber(); } return s = TypeConverter.toJsonHex(b); } finally { if (logger.isDebugEnabled()) logger.debug("eth_blockNumber(): " + s); } } public String eth_getBalance(String address, String blockId) throws Exception { String s = null; try { byte[] addressAsByteArray = TypeConverter.StringHexToByteArray(address); BigInteger balance = getRepoByJsonBlockId(blockId).getBalance(addressAsByteArray); return s = TypeConverter.toJsonHex(balance); } finally { if (logger.isDebugEnabled()) logger.debug("eth_getBalance(" + address + ", " + blockId + "): " + s); } } public String eth_getBalance(String address) throws Exception { String s = null; try { return s = eth_getBalance(address, "latest"); } finally { if (logger.isDebugEnabled()) logger.debug("eth_getBalance(" + address + "): " + s); } } @Override public String eth_getStorageAt(String address, String storageIdx, String blockId) throws Exception { String s = null; try { byte[] addressAsByteArray = StringHexToByteArray(address); DataWord storageValue = getRepoByJsonBlockId(blockId). getStorageValue(addressAsByteArray, new DataWord(StringHexToByteArray(storageIdx))); return s = TypeConverter.toJsonHex(storageValue.getData()); } 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); BigInteger nonce = getRepoByJsonBlockId(blockId).getNonce(addressAsByteArray); return s = TypeConverter.toJsonHex(nonce); } finally { if (logger.isDebugEnabled()) logger.debug("eth_getTransactionCount(" + address + ", " + blockId + "): " + s); } } 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 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 { String s = null; try { Block b = getBlockByJSonHash(blockHash); if (b == null) return null; long n = b.getUncleList().size(); return s = TypeConverter.toJsonHex(n); } finally { if (logger.isDebugEnabled()) logger.debug("eth_getUncleCountByBlockHash(" + blockHash + "): " + s); } } public String eth_getUncleCountByBlockNumber(String bnOrId) throws Exception { String s = null; try { Block b = getByJsonBlockId(bnOrId); if (b == null) return null; long n = b.getUncleList().size(); return s = TypeConverter.toJsonHex(n); } finally { if (logger.isDebugEnabled()) logger.debug("eth_getUncleCountByBlockNumber(" + bnOrId + "): " + s); } } public String eth_getCode(String address, String blockId) throws Exception { String s = null; try { byte[] addressAsByteArray = TypeConverter.StringHexToByteArray(address); byte[] code = getRepoByJsonBlockId(blockId).getCode(addressAsByteArray); return s = TypeConverter.toJsonHex(code); } 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 { String ha = JSonHexToHex(addr); Account account = getAccount(ha); if (account==null) throw new Exception("Inexistent account"); // Todo: is not clear from the spec what hash function must be used to sign byte[] masgHash= HashUtil.sha3(TypeConverter.StringHexToByteArray(data)); ECKey.ECDSASignature signature = account.getEcKey().sign(masgHash); // Todo: is not clear if result should be RlpEncoded or serialized by other means byte[] rlpSig = RLP.encode(signature); return s = TypeConverter.toJsonHex(rlpSig); } finally { if (logger.isDebugEnabled()) logger.debug("eth_sign(" + addr + ", " + data + "): " + s); } } public String eth_sendTransaction(CallArguments args) throws Exception { String s = null; try { Account account = getAccount(JSonHexToHex(args.from)); if (account == null) throw new Exception("From address private key could not be found in this node"); if (args.data != null && args.data.startsWith("0x")) args.data = args.data.substring(2); Transaction tx = new Transaction( args.nonce != null ? StringHexToByteArray(args.nonce) : bigIntegerToBytes(pendingState.getRepository().getNonce(account.getAddress())), args.gasPrice != null ? StringHexToByteArray(args.gasPrice) : ByteUtil.longToBytesNoLeadZeroes(eth.getGasPrice()), args.gas != null ? StringHexToByteArray(args.gas) : ByteUtil.longToBytes(90_000), args.to != null ? StringHexToByteArray(args.to) : EMPTY_BYTE_ARRAY, args.value != null ? StringHexToByteArray(args.value) : EMPTY_BYTE_ARRAY, args.data != null ? StringHexToByteArray(args.data) : EMPTY_BYTE_ARRAY, eth.getChainIdForNextBlock()); tx.sign(account.getEcKey().getPrivKeyBytes()); eth.submitTransaction(tx); return s = TypeConverter.toJsonHex(tx.getHash()); } finally { if (logger.isDebugEnabled()) logger.debug("eth_sendTransaction(" + args + "): " + s); } } public String eth_sendTransaction(String from, String to, String gas, String gasPrice, String value,String data,String nonce) throws Exception { String s = null; try { Transaction tx = new Transaction( TypeConverter.StringHexToByteArray(nonce), TypeConverter.StringHexToByteArray(gasPrice), TypeConverter.StringHexToByteArray(gas), TypeConverter.StringHexToByteArray(to), /*receiveAddress*/ TypeConverter.StringHexToByteArray(value), TypeConverter.StringHexToByteArray(data), eth.getChainIdForNextBlock()); Account account = getAccount(from); if (account == null) throw new RuntimeException("No account " + from); tx.sign(account.getEcKey()); eth.submitTransaction(tx); return s = TypeConverter.toJsonHex(tx.getHash()); } finally { if (logger.isDebugEnabled()) logger.debug("eth_sendTransaction(" + "from = [" + from + "], to = [" + to + "], gas = [" + gas + "], gasPrice = [" + gasPrice + "], value = [" + value + "], data = [" + data + "], nonce = [" + nonce + "]" + "): " + s); } } public String eth_sendRawTransaction(String rawData) throws Exception { String s = null; try { Transaction tx = new Transaction(StringHexToByteArray(rawData)); tx.verify(); eth.submitTransaction(tx); return s = TypeConverter.toJsonHex(tx.getHash()); } finally { if (logger.isDebugEnabled()) logger.debug("eth_sendRawTransaction(" + rawData + "): " + s); } } public TransactionReceipt createCallTxAndExecute(CallArguments args, Block block) throws Exception { Repository repository = ((Repository) worldManager.getRepository()) .getSnapshotTo(block.getStateRoot()) .startTracking(); return createCallTxAndExecute(args, block, repository, worldManager.getBlockStore()); } public TransactionReceipt createCallTxAndExecute(CallArguments args, Block block, Repository repository, BlockStore blockStore) throws Exception { BinaryCallArguments bca = new BinaryCallArguments(); bca.setArguments(args); Transaction tx = CallTransaction.createRawTransaction(0, bca.gasPrice, bca.gasLimit, bca.toAddress, bca.value, bca.data); // put mock signature if not present if (tx.getSignature() == null) { tx.sign(ECKey.fromPrivate(new byte[32])); } try { TransactionExecutor executor = new TransactionExecutor (tx, block.getCoinbase(), repository, blockStore, programInvokeFactory, block, new EthereumListenerAdapter(), 0) .withCommonConfig(commonConfig) .setLocalCall(true); executor.init(); executor.execute(); executor.go(); executor.finalization(); return executor.getReceipt(); } finally { repository.rollback(); } } public String eth_call(CallArguments args, String bnOrId) throws Exception { String s = null; try { TransactionReceipt res; if ("pending".equals(bnOrId)) { Block pendingBlock = blockchain.createNewBlock(blockchain.getBestBlock(), pendingState.getPendingTransactions(), Collections.<BlockHeader>emptyList()); res = createCallTxAndExecute(args, pendingBlock, pendingState.getRepository(), worldManager.getBlockStore()); } else { res = createCallTxAndExecute(args, getByJsonBlockId(bnOrId)); } return s = TypeConverter.toJsonHex(res.getExecutionResult()); } finally { if (logger.isDebugEnabled()) logger.debug("eth_call(" + args + "): " + s); } } public String eth_estimateGas(CallArguments args) throws Exception { String s = null; try { TransactionReceipt res = createCallTxAndExecute(args, blockchain.getBestBlock()); return s = TypeConverter.toJsonHex(res.getGasUsed()); } finally { if (logger.isDebugEnabled()) logger.debug("eth_estimateGas(" + args + "): " + s); } } public BlockResult getBlockResult(Block b, boolean fullTx) { if (b==null) return null; boolean isPending = ByteUtil.byteArrayToLong(b.getNonce()) == 0; 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.nonce = isPending ? null : TypeConverter.toJsonHex(b.getNonce()); 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(blockchain.getTotalDifficulty()); if (b.getExtraData() != null) br.extraData =TypeConverter.toJsonHex(b.getExtraData()); br.size = TypeConverter.toJsonHex(b.getEncoded().length); br.gasLimit =TypeConverter.toJsonHex(b.getGasLimit()); 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; if ("pending".equalsIgnoreCase(bnOrId)) { b = blockchain.createNewBlock(blockchain.getBestBlock(), pendingState.getPendingTransactions(), Collections.<BlockHeader>emptyList()); } else { 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 { byte[] txHash = StringHexToByteArray(transactionHash); Block block = null; TransactionInfo txInfo = blockchain.getTransactionInfo(txHash); if (txInfo == null) { TransactionReceipt receipt = pendingReceipts.get(new ByteArrayWrapper(txHash)); if (receipt == null) { return null; } txInfo = new TransactionInfo(receipt); } 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())); } 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 { TransactionReceiptDTO s = null; try { byte[] hash = TypeConverter.StringHexToByteArray(transactionHash); TransactionReceipt pendingReceipt = pendingReceipts.get(new ByteArrayWrapper(hash)); TransactionInfo txInfo; Block block; if (pendingReceipt != null) { txInfo = new TransactionInfo(pendingReceipt); block = null; } else { txInfo = blockchain.getTransactionInfo(hash); if (txInfo == null) return null; 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; } } return s = new TransactionReceiptDTO(block, txInfo); } finally { if (logger.isDebugEnabled()) logger.debug("eth_getTransactionReceipt(" + transactionHash + "): " + s); } } @Override public TransactionReceiptDTOExt ethj_getTransactionReceipt(String transactionHash) throws Exception { TransactionReceiptDTOExt s = null; try { byte[] hash = TypeConverter.StringHexToByteArray(transactionHash); TransactionReceipt pendingReceipt = pendingReceipts.get(new ByteArrayWrapper(hash)); TransactionInfo txInfo; Block block; if (pendingReceipt != null) { txInfo = new TransactionInfo(pendingReceipt); block = null; } else { txInfo = blockchain.getTransactionInfo(hash); if (txInfo == null) return null; 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; } } return s = new TransactionReceiptDTOExt(block, txInfo); } finally { if (logger.isDebugEnabled()) logger.debug("eth_getTransactionReceipt(" + transactionHash + "): " + s); } } @Override public BlockResult eth_getUncleByBlockHashAndIndex(String blockHash, String uncleIdx) throws Exception { BlockResult s = null; try { Block block = blockchain.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 = blockchain.getBlockByHash(uncleHeader.getHash()); if (uncle == null) { uncle = new Block(uncleHeader, Collections.<Transaction>emptyList(), Collections.<BlockHeader>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(toJsonHex(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 CompilationResult eth_compileLLL(String contract) { throw new UnsupportedOperationException("LLL compiler not supported"); } @Override public CompilationResult eth_compileSolidity(String contract) throws Exception { CompilationResult s = null; try { SolidityCompiler.Result res = solidityCompiler.compileSrc( contract.getBytes(), true, true, SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN); if (res.isFailed()) { throw new RuntimeException("Compilation error: " + res.errors); } org.ethereum.solidity.compiler.CompilationResult result = org.ethereum.solidity.compiler.CompilationResult.parse(res.output); CompilationResult ret = new CompilationResult(); org.ethereum.solidity.compiler.CompilationResult.ContractMetadata contractMetadata = result.contracts.values().iterator().next(); ret.code = toJsonHex(contractMetadata.bin); ret.info = new CompilationInfo(); ret.info.source = contract; ret.info.language = "Solidity"; ret.info.languageVersion = "0"; ret.info.compilerVersion = result.version; ret.info.abiDefinition = new CallTransaction.Contract(contractMetadata.abi).functions; return s = ret; } finally { if (logger.isDebugEnabled()) logger.debug("eth_compileSolidity(" + contract + ")" + s); } } @Override public CompilationResult eth_compileSerpent(String contract){ throw new UnsupportedOperationException("Serpent compiler not supported"); } @Override public String eth_resend() { throw new UnsupportedOperationException("JSON RPC method eth_resend not implemented yet"); } @Override public String eth_pendingTransactions() { throw new UnsupportedOperationException("JSON RPC method eth_pendingTransactions not implemented yet"); } static class Filter { static final int MAX_EVENT_COUNT = 1024; // prevent OOM when Filers are forgotten static abstract class FilterEvent { public abstract Object getJsonEventObject(); } List<FilterEvent> events = new LinkedList<>(); 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); if (events.size() > MAX_EVENT_COUNT) events.remove(0); } public void newBlockReceived(Block b) {} public void newPendingTx(Transaction tx) {} } 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) { if (logFilter.matchesContractAddress(tx.getReceiveAddress())) { TransactionInfo txInfo = blockchain.getTransactionInfo(tx.getHash()); onTransactionReceipt(txInfo.getReceipt(), 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) { // TODO add TransactionReceipt for PendingTx // if (onPendingTx) } } @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 = filterCounter.getAndIncrement(); installedFilters.put(id, filter); Block blockFrom = fr.fromBlock == null ? null : getByJsonBlockId(fr.fromBlock); Block blockTo = fr.toBlock == null ? null : getByJsonBlockId(fr.toBlock); if (blockFrom != null) { // need to add historical data blockTo = blockTo == null ? blockchain.getBestBlock() : blockTo; for (long blockNum = blockFrom.getNumber(); blockNum <= blockTo.getNumber(); blockNum++) { filter.onBlock(blockchain.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; } 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 = 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 = 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; 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 { 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 String eth_getWork() { throw new UnsupportedOperationException("JSON RPC method eth_getWork not implemented yet"); } @Override public String eth_submitWork() { throw new UnsupportedOperationException("JSON RPC method eth_submitWork not implemented yet"); } @Override public String eth_submitHashrate() { throw new UnsupportedOperationException("JSON RPC method eth_submitHashrate not implemented yet"); } @Override public String db_putString() { throw new UnsupportedOperationException("JSON RPC method db_putString not implemented yet"); } @Override public String db_getString() { throw new UnsupportedOperationException("JSON RPC method db_getString not implemented yet"); } @Override public String db_putHex() { throw new UnsupportedOperationException("JSON RPC method db_putHex not implemented yet"); } @Override public String db_getHex() { throw new UnsupportedOperationException("JSON RPC method db_getHex not implemented yet"); } @Override public String shh_post() { throw new UnsupportedOperationException("JSON RPC method shh_post not implemented yet"); } @Override public String shh_version() { throw new UnsupportedOperationException("JSON RPC method shh_version not implemented yet"); } @Override public String shh_newIdentity() { throw new UnsupportedOperationException("JSON RPC method shh_newIdentity not implemented yet"); } @Override public String shh_hasIdentity() { throw new UnsupportedOperationException("JSON RPC method shh_hasIdentity not implemented yet"); } @Override public String shh_newGroup() { throw new UnsupportedOperationException("JSON RPC method shh_newGroup not implemented yet"); } @Override public String shh_addToGroup() { throw new UnsupportedOperationException("JSON RPC method shh_addToGroup not implemented yet"); } @Override public String shh_newFilter() { throw new UnsupportedOperationException("JSON RPC method shh_newFilter not implemented yet"); } @Override public String shh_uninstallFilter() { throw new UnsupportedOperationException("JSON RPC method shh_uninstallFilter not implemented yet"); } @Override public String shh_getFilterChanges() { throw new UnsupportedOperationException("JSON RPC method shh_getFilterChanges not implemented yet"); } @Override public String shh_getMessages() { throw new UnsupportedOperationException("JSON RPC method shh_getMessages not implemented yet"); } @Override public boolean admin_addPeer(String s) { eth.connect(new Node(s)); return true; } @Override public String admin_exportChain() { throw new UnsupportedOperationException("JSON RPC method admin_exportChain not implemented yet"); } @Override public String admin_importChain() { throw new UnsupportedOperationException("JSON RPC method admin_importChain not implemented yet"); } @Override public String admin_sleepBlocks() { throw new UnsupportedOperationException("JSON RPC method admin_sleepBlocks not implemented yet"); } @Override public String admin_verbosity() { throw new UnsupportedOperationException("JSON RPC method admin_verbosity not implemented yet"); } @Override public String admin_setSolc() { throw new UnsupportedOperationException("JSON RPC method admin_setSolc not implemented yet"); } @Override public String admin_startRPC() { throw new UnsupportedOperationException("JSON RPC method admin_startRPC not implemented yet"); } @Override public String admin_stopRPC() { throw new UnsupportedOperationException("JSON RPC method admin_stopRPC not implemented yet"); } @Override public String admin_setGlobalRegistrar() { throw new UnsupportedOperationException("JSON RPC method admin_setGlobalRegistrar not implemented yet"); } @Override public String admin_setHashReg() { throw new UnsupportedOperationException("JSON RPC method admin_setHashReg not implemented yet"); } @Override public String admin_setUrlHint() { throw new UnsupportedOperationException("JSON RPC method admin_setUrlHint not implemented yet"); } @Override public String admin_saveInfo() { throw new UnsupportedOperationException("JSON RPC method admin_saveInfo not implemented yet"); } @Override public String admin_register() { throw new UnsupportedOperationException("JSON RPC method admin_register not implemented yet"); } @Override public String admin_registerUrl() { throw new UnsupportedOperationException("JSON RPC method admin_registerUrl not implemented yet"); } @Override public String admin_startNatSpec() { throw new UnsupportedOperationException("JSON RPC method admin_startNatSpec not implemented yet"); } @Override public String admin_stopNatSpec() { throw new UnsupportedOperationException("JSON RPC method admin_stopNatSpec not implemented yet"); } @Override public String admin_getContractInfo() { throw new UnsupportedOperationException("JSON RPC method admin_getContractInfo not implemented yet"); } @Override public String admin_httpGet() { throw new UnsupportedOperationException("JSON RPC method admin_httpGet not implemented yet"); } @Override public String admin_nodeInfo() { throw new UnsupportedOperationException("JSON RPC method admin_nodeInfo not implemented yet"); } @Override public String admin_peers() { throw new UnsupportedOperationException("JSON RPC method admin_peers not implemented yet"); } @Override public String admin_datadir() { throw new UnsupportedOperationException("JSON RPC method admin_datadir not implemented yet"); } @Override public String net_addPeer() { throw new UnsupportedOperationException("JSON RPC method net_addPeer not implemented yet"); } @Override public boolean miner_start() { blockMiner.startMining(); return true; } @Override public boolean miner_stop() { blockMiner.stopMining(); return true; } @Override public boolean miner_setEtherbase(String coinBase) throws Exception { blockchain.setMinerCoinbase(TypeConverter.StringHexToByteArray(coinBase)); return true; } @Override public boolean miner_setExtra(String data) throws Exception { blockchain.setMinerExtraData(TypeConverter.StringHexToByteArray(data)); return true; } @Override public boolean miner_setGasPrice(String newMinGasPrice) { blockMiner.setMinGasPrice(TypeConverter.StringHexToBigInteger(newMinGasPrice)); return true; } @Override public boolean miner_startAutoDAG() { return false; } @Override public boolean miner_stopAutoDAG() { return false; } @Override public boolean miner_makeDAG() { return false; } @Override public String miner_hashrate() { return "0x01"; } @Override public String debug_printBlock() { throw new UnsupportedOperationException("JSON RPC method debug_printBlock not implemented yet"); } @Override public String debug_getBlockRlp() { throw new UnsupportedOperationException("JSON RPC method debug_getBlockRlp not implemented yet"); } @Override public String debug_setHead() { throw new UnsupportedOperationException("JSON RPC method debug_setHead not implemented yet"); } @Override public String debug_processBlock() { throw new UnsupportedOperationException("JSON RPC method debug_processBlock not implemented yet"); } @Override public String debug_seedHash() { throw new UnsupportedOperationException("JSON RPC method debug_seedHash not implemented yet"); } @Override public String debug_dumpBlock() { throw new UnsupportedOperationException("JSON RPC method debug_dumpBlock not implemented yet"); } @Override public String debug_metrics() { throw new UnsupportedOperationException("JSON RPC method debug_metrics not implemented yet"); } @Override public String personal_newAccount(String seed) { String s = null; try { Account account = addAccount(seed); return s = toJsonHex(account.getAddress()); } finally { if (logger.isDebugEnabled()) logger.debug("personal_newAccount(*****): " + s); } } @Override public boolean personal_unlockAccount(String addr, String pass, String duration) { String s = null; try { return true; } finally { if (logger.isDebugEnabled()) logger.debug("personal_unlockAccount(" + addr + ", ***, " + duration + "): " + s); } } @Override public String[] personal_listAccounts() { String[] ret = new String[accounts.size()]; try { int i = 0; for (ByteArrayWrapper addr : accounts.keySet()) { ret[i++] = toJsonHex(addr.getData()); } return ret; } finally { if (logger.isDebugEnabled()) logger.debug("personal_listAccounts(): " + Arrays.toString(ret)); } } }