Coverage Summary for Class: PersonalModuleWalletEnabled (co.rsk.rpc.modules.personal)

Class Class, % Method, % Line, %
PersonalModuleWalletEnabled 100% (1/1) 100% (16/16) 83.9% (73/87)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2017 RSK Labs Ltd. 4  * 5  * This program is free software: you can redistribute it and/or modify 6  * it under the terms of the GNU Lesser General Public License as published by 7  * the Free Software Foundation, either version 3 of the License, or 8  * (at your option) any later version. 9  * 10  * This program is distributed in the hope that it will be useful, 11  * but WITHOUT ANY WARRANTY; without even the implied warranty of 12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13  * GNU Lesser General Public License for more details. 14  * 15  * You should have received a copy of the GNU Lesser General Public License 16  * along with this program. If not, see <http://www.gnu.org/licenses/>. 17  */ 18  19 package co.rsk.rpc.modules.personal; 20  21 import co.rsk.config.RskSystemProperties; 22 import co.rsk.config.WalletAccount; 23 import co.rsk.core.RskAddress; 24 import co.rsk.core.Wallet; 25 import org.bouncycastle.util.encoders.Hex; 26 import org.ethereum.core.Account; 27 import org.ethereum.core.Transaction; 28 import org.ethereum.core.TransactionPool; 29 import org.ethereum.facade.Ethereum; 30 import org.ethereum.rpc.TypeConverter; 31 import org.ethereum.rpc.Web3; 32 import org.ethereum.util.ByteUtil; 33 import org.ethereum.vm.GasCost; 34 import org.slf4j.Logger; 35 import org.slf4j.LoggerFactory; 36  37 import java.math.BigInteger; 38 import java.util.Arrays; 39  40 import static org.ethereum.rpc.exception.RskJsonRpcRequestException.invalidParamError; 41  42 public class PersonalModuleWalletEnabled implements PersonalModule { 43  44  private static final Logger LOGGER = LoggerFactory.getLogger("web3"); 45  46  private final Ethereum eth; 47  private final Wallet wallet; 48  private final TransactionPool transactionPool; 49  private final RskSystemProperties config; 50  51  public PersonalModuleWalletEnabled(RskSystemProperties config, Ethereum eth, Wallet wallet, TransactionPool transactionPool) { 52  this.config = config; 53  this.eth = eth; 54  this.wallet = wallet; 55  this.transactionPool = transactionPool; 56  } 57  58  @Override 59  public void init() { 60  // dev node has 10 accouts with balance (in rsk-dev.json 61  // with seed cow, cow1..cow9 62  if (config.getNetworkConstants().seedCowAccounts()) { 63  newAccountWithSeed("cow"); 64  65  for (int k = 1; k <= 9; k++) { 66  newAccountWithSeed("cow" + k); 67  } 68  } 69  70  // This creates a new account based on a configured secret passphrase, 71  // which is then used to set the current miner coinbase address. 72  // Generally used for testing, since you usually don't want to store 73  // wallets in production for security reasons. 74  Account coinbaseAccount = config.localCoinbaseAccount(); 75  if (coinbaseAccount != null) { 76  this.wallet.addAccount(coinbaseAccount); 77  } 78  79  // initializes wallet accounts based on configuration 80  for (WalletAccount acc : config.walletAccounts()) { 81  this.wallet.addAccountWithPrivateKey(Hex.decode(acc.getPrivateKey())); 82  } 83  } 84  85  @Override 86  public String newAccountWithSeed(String seed) { 87  String s = null; 88  89  try { 90  byte[] address = this.wallet.addAccountWithSeed(seed); 91  92  return s = TypeConverter.toJsonHex(address); 93  } finally { 94  LOGGER.debug("personal_newAccountWithSeed(*****): {}", s); 95  } 96  } 97  98  @Override 99  public String newAccount(String passphrase) { 100  String s = null; 101  102  try { 103  RskAddress address = this.wallet.addAccount(passphrase); 104  // Unlock immediately with no specified duration 105  unlockAccount(address, passphrase, null); 106  return s = address.toJsonString(); 107  } finally { 108  LOGGER.debug("personal_newAccount(*****): {}", s); 109  } 110  } 111  112  @Override 113  public String[] listAccounts() { 114  String[] ret = null; 115  try { 116  return ret = wallet.getAccountAddressesAsHex(); 117  } finally { 118  LOGGER.debug("personal_listAccounts(): {}", Arrays.toString(ret)); 119  } 120  } 121  122  @Override 123  public String importRawKey(String key, String passphrase) { 124  String s = null; 125  try { 126  RskAddress address = this.wallet.addAccountWithPrivateKey(Hex.decode(key), passphrase); 127  unlockAccount(address, passphrase, null); 128  return s = address.toJsonString(); 129  } finally { 130  LOGGER.debug("personal_importRawKey(*****): {}", s); 131  } 132  } 133  134  @Override 135  public String sendTransaction(Web3.CallArguments args, String passphrase) throws Exception { 136  String s = null; 137  try { 138  return s = sendTransaction(args, getAccount(args.from, passphrase)); 139  } finally { 140  LOGGER.debug("eth_sendTransaction({}): {}", args, s); 141  } 142  } 143  144  @Override 145  public boolean unlockAccount(String address, String passphrase, String duration) { 146  return unlockAccount(new RskAddress(address), passphrase, duration); 147  } 148  149  private boolean unlockAccount(RskAddress addr, String passphrase, String duration) { 150  long dur = (long) 1000 * 60 * 30; 151  if (duration != null && duration.length() > 0) { 152  try { 153  dur = convertFromJsonHexToLong(duration); 154  } catch (Exception e) { 155  throw invalidParamError("Can't parse duration param", e); 156  } 157  } 158  159  return this.wallet.unlockAccount(addr, passphrase, dur); 160  } 161  162  @Override 163  public boolean lockAccount(String address) { 164  return this.wallet.lockAccount(new RskAddress(address)); 165  } 166  167  @Override 168  public String dumpRawKey(String address) throws Exception { 169  String s = null; 170  try { 171  Account account = wallet.getAccount(new RskAddress(convertFromJsonHexToHex(address))); 172  if (account == null) { 173  throw new Exception("Address private key is locked or could not be found in this node"); 174  } 175  176  return s = TypeConverter.toJsonHex(ByteUtil.toHexString(account.getEcKey().getPrivKeyBytes())); 177  } finally { 178  LOGGER.debug("personal_dumpRawKey(*****): {}", s); 179  } 180  } 181  182  private Account getAccount(String from, String passphrase) { 183  return wallet.getAccount(new RskAddress(from), passphrase); 184  } 185  186  private String sendTransaction(Web3.CallArguments args, Account account) throws Exception { 187  if (account == null) { 188  throw new Exception("From address private key could not be found in this node"); 189  } 190  191  String toAddress = args.to != null ? ByteUtil.toHexString(TypeConverter.stringHexToByteArray(args.to)) : null; 192  193  BigInteger accountNonce = args.nonce != null ? TypeConverter.stringNumberAsBigInt(args.nonce) : transactionPool.getPendingState().getNonce(account.getAddress()); 194  BigInteger value = args.value != null ? TypeConverter.stringNumberAsBigInt(args.value) : BigInteger.ZERO; 195  BigInteger gasPrice = args.gasPrice != null ? TypeConverter.stringNumberAsBigInt(args.gasPrice) : BigInteger.ZERO; 196  BigInteger gasLimit = args.gas != null ? TypeConverter.stringNumberAsBigInt(args.gas) : BigInteger.valueOf(GasCost.TRANSACTION); 197  198  if (args.data != null && args.data.startsWith("0x")) { 199  args.data = args.data.substring(2); 200  } 201  202  Transaction tx = Transaction 203  .builder() 204  .nonce(accountNonce) 205  .gasPrice(gasPrice) 206  .gasLimit(gasLimit) 207  .destination(toAddress == null ? null : Hex.decode(toAddress)) 208  .data(args.data == null ? null : Hex.decode(args.data)) 209  .chainId(config.getNetworkConstants().getChainId()) 210  .value(value) 211  .build(); 212  213  tx.sign(account.getEcKey().getPrivKeyBytes()); 214  215  eth.submitTransaction(tx); 216  217  return tx.getHash().toJsonString(); 218  } 219  220  private String convertFromJsonHexToHex(String x) throws Exception { 221  if (!x.startsWith("0x")) { 222  throw new Exception("Incorrect hex syntax"); 223  } 224  225  return x.substring(2); 226  } 227  228  private long convertFromJsonHexToLong(String x) throws Exception { 229  if (!x.startsWith("0x")) { 230  throw new Exception("Incorrect hex syntax"); 231  } 232  return Long.parseLong(x.substring(2), 16); 233  } 234 }