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 }