Coverage Summary for Class: MutableRepository (org.ethereum.db)
Class |
Class, %
|
Method, %
|
Line, %
|
MutableRepository |
100%
(1/1)
|
56.8%
(21/37)
|
46.3%
(63/136)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2019 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 org.ethereum.db;
20
21 import co.rsk.core.Coin;
22 import co.rsk.core.RskAddress;
23 import co.rsk.core.types.ints.Uint24;
24 import co.rsk.crypto.Keccak256;
25 import co.rsk.db.MutableTrieCache;
26 import co.rsk.db.MutableTrieImpl;
27 import co.rsk.trie.MutableTrie;
28 import co.rsk.trie.Trie;
29 import co.rsk.trie.TrieKeySlice;
30 import co.rsk.trie.TrieStore;
31 import com.google.common.annotations.VisibleForTesting;
32 import org.ethereum.core.AccountState;
33 import org.ethereum.core.Repository;
34 import org.ethereum.crypto.HashUtil;
35 import org.ethereum.crypto.Keccak256Helper;
36 import org.ethereum.vm.DataWord;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import javax.annotation.Nonnull;
41 import java.math.BigInteger;
42 import java.util.*;
43
44 public class MutableRepository implements Repository {
45 private static final Logger logger = LoggerFactory.getLogger("repository");
46 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
47 public static final Keccak256 KECCAK_256_OF_EMPTY_ARRAY = new Keccak256(Keccak256Helper.keccak256(EMPTY_BYTE_ARRAY));
48 private static final byte[] ONE_BYTE_ARRAY = new byte[] { 0x01 };
49
50 private final TrieKeyMapper trieKeyMapper;
51 private final MutableTrie mutableTrie;
52
53 public MutableRepository(TrieStore trieStore, Trie trie) {
54 this(new MutableTrieImpl(trieStore, trie));
55 }
56
57 public MutableRepository(MutableTrie mutableTrie) {
58 this.trieKeyMapper = new TrieKeyMapper();
59 this.mutableTrie = mutableTrie;
60 }
61
62 @Override
63 public Trie getTrie() {
64 return mutableTrie.getTrie();
65 }
66
67 @Override
68 public synchronized AccountState createAccount(RskAddress addr) {
69 AccountState accountState = new AccountState();
70 updateAccountState(addr, accountState);
71 return accountState;
72 }
73
74 @Override
75 public synchronized void setupContract(RskAddress addr) {
76 byte[] prefix = trieKeyMapper.getAccountStoragePrefixKey(addr);
77 mutableTrie.put(prefix, ONE_BYTE_ARRAY);
78 }
79
80 @Override
81 public synchronized boolean isExist(RskAddress addr) {
82 // Here we assume size != 0 means the account exists
83 return mutableTrie.getValueLength(trieKeyMapper.getAccountKey(addr)).compareTo(Uint24.ZERO) > 0;
84 }
85
86 @Override
87 public synchronized AccountState getAccountState(RskAddress addr) {
88 AccountState result = null;
89 byte[] accountData = getAccountData(addr);
90
91 // If there is no account it returns null
92 if (accountData != null && accountData.length != 0) {
93 result = new AccountState(accountData);
94 }
95 return result;
96 }
97
98 @Override
99 public synchronized void delete(RskAddress addr) {
100 mutableTrie.deleteRecursive(trieKeyMapper.getAccountKey(addr));
101 }
102
103 @Override
104 public synchronized void hibernate(RskAddress addr) {
105 AccountState account = getAccountStateOrCreateNew(addr);
106
107 account.hibernate();
108 updateAccountState(addr, account);
109 }
110
111 @Override
112 public void setNonce(RskAddress addr,BigInteger nonce) {
113 AccountState account = getAccountStateOrCreateNew(addr);
114
115 account.setNonce(nonce);
116 updateAccountState(addr, account);
117 }
118
119 @Override
120 public synchronized BigInteger increaseNonce(RskAddress addr) {
121 AccountState account = getAccountStateOrCreateNew(addr);
122
123 account.incrementNonce();
124 updateAccountState(addr, account);
125 return account.getNonce();
126 }
127
128 @Override
129 public synchronized BigInteger getNonce(RskAddress addr) {
130 // Why would getNonce create an Account in the repository? The semantic of a get()
131 // is clear: do not change anything!
132 AccountState account = getAccountState(addr);
133 if (account == null) {
134 return BigInteger.ZERO;
135 }
136
137 return account.getNonce();
138 }
139
140 @Override
141 public synchronized void saveCode(RskAddress addr, byte[] code) {
142 byte[] key = trieKeyMapper.getCodeKey(addr);
143 mutableTrie.put(key, code);
144
145 if (code != null && code.length != 0 && !isExist(addr)) {
146 createAccount(addr);
147 }
148 }
149
150 @Override
151 public synchronized int getCodeLength(RskAddress addr) {
152 AccountState account = getAccountState(addr);
153 if (account == null || account.isHibernated()) {
154 return 0;
155 }
156
157 byte[] key = trieKeyMapper.getCodeKey(addr);
158 return mutableTrie.getValueLength(key).intValue();
159 }
160
161 @Override
162 public synchronized Keccak256 getCodeHashNonStandard(RskAddress addr) {
163
164 if (!isExist(addr)) {
165 return Keccak256.ZERO_HASH;
166 }
167
168 if (!isContract(addr)) {
169 return KECCAK_256_OF_EMPTY_ARRAY;
170 }
171
172 byte[] key = trieKeyMapper.getCodeKey(addr);
173 Optional<Keccak256> valueHash = mutableTrie.getValueHash(key);
174
175 //Returning ZERO_HASH is the non standard implementation we had pre RSKIP169 implementation
176 //and thus me must honor it.
177 return valueHash.orElse(Keccak256.ZERO_HASH);
178 }
179
180 @Override
181 public synchronized Keccak256 getCodeHashStandard(RskAddress addr) {
182
183 if (!isExist(addr)) {
184 return Keccak256.ZERO_HASH;
185 }
186
187 if (!isContract(addr)) {
188 return KECCAK_256_OF_EMPTY_ARRAY;
189 }
190
191 byte[] key = trieKeyMapper.getCodeKey(addr);
192
193 return mutableTrie.getValueHash(key).orElse(KECCAK_256_OF_EMPTY_ARRAY);
194 }
195
196 @Override
197 public synchronized byte[] getCode(RskAddress addr) {
198 if (!isExist(addr)) {
199 return EMPTY_BYTE_ARRAY;
200 }
201
202 AccountState account = getAccountState(addr);
203 if (account.isHibernated()) {
204 return EMPTY_BYTE_ARRAY;
205 }
206
207 byte[] key = trieKeyMapper.getCodeKey(addr);
208 return mutableTrie.get(key);
209 }
210
211 @Override
212 public boolean isContract(RskAddress addr) {
213 byte[] prefix = trieKeyMapper.getAccountStoragePrefixKey(addr);
214 return mutableTrie.get(prefix) != null;
215 }
216
217 @Override
218 public synchronized void addStorageRow(RskAddress addr, DataWord key, DataWord value) {
219 // DataWords are stored stripping leading zeros.
220 addStorageBytes(addr, key, value.getByteArrayForStorage());
221 }
222
223 @Override
224 public synchronized void addStorageBytes(RskAddress addr, DataWord key, byte[] value) {
225 // This should not happen in production because contracts are created before storage cells are added to them.
226 // But it happens in Repository tests, that create only storage row cells.
227 if (!isExist(addr)) {
228 createAccount(addr);
229 setupContract(addr);
230 }
231
232 byte[] triekey = trieKeyMapper.getAccountStorageKey(addr, key);
233
234 // Special case: if the value is an empty vector, we pass "null" which commands the trie to remove the item.
235 // Note that if the call comes from addStorageRow(), this method will already have replaced 0 by null, so the
236 // conversion here only applies if this is called directly. If suppose this only occurs in tests, but it can
237 // also occur in precompiled contracts that store data directly using this method.
238 if (value == null || value.length == 0) {
239 mutableTrie.put(triekey, null);
240 } else {
241 mutableTrie.put(triekey, value);
242 }
243 }
244
245 @Override
246 public synchronized DataWord getStorageValue(RskAddress addr, DataWord key) {
247 byte[] triekey = trieKeyMapper.getAccountStorageKey(addr, key);
248 byte[] value = mutableTrie.get(triekey);
249 if (value == null) {
250 return null;
251 }
252
253 return DataWord.valueOf(value);
254 }
255
256 @Override
257 public synchronized byte[] getStorageBytes(RskAddress addr, DataWord key) {
258 byte[] triekey = trieKeyMapper.getAccountStorageKey(addr, key);
259 return mutableTrie.get(triekey);
260 }
261
262 @Override
263 public Iterator<DataWord> getStorageKeys(RskAddress addr) {
264 // -1 b/c the first bit is implicit in the storage node
265 return mutableTrie.getStorageKeys(addr);
266 }
267
268 @Override
269 public int getStorageKeysCount(RskAddress addr) {
270 // FIXME(diegoll): I think it's kind of insane to iterate the whole tree looking for storage keys for this address
271 // I think we can keep a counter for the keys, using the find function for detecting duplicates and so on
272 int storageKeysCount = 0;
273 Iterator<DataWord> keysIterator = getStorageKeys(addr);
274 for(;keysIterator.hasNext(); keysIterator.next()) {
275 storageKeysCount ++;
276 }
277 return storageKeysCount;
278 }
279
280 @Override
281 public synchronized Coin getBalance(RskAddress addr) {
282 AccountState account = getAccountState(addr);
283 return (account == null) ? Coin.ZERO: account.getBalance();
284 }
285
286 @Override
287 public synchronized Coin addBalance(RskAddress addr, Coin value) {
288 AccountState account = getAccountStateOrCreateNew(addr);
289
290 Coin result = account.addToBalance(value);
291 updateAccountState(addr, account);
292
293 return result;
294 }
295
296 @Override
297 public synchronized Set<RskAddress> getAccountsKeys() {
298 Set<RskAddress> result = new HashSet<>();
299 //TODO(diegoll): this is needed when trie is a MutableTrieCache, check if makes sense to commit here
300 mutableTrie.commit();
301 Trie trie = mutableTrie.getTrie();
302 Iterator<Trie.IterationElement> preOrderIterator = trie.getPreOrderIterator();
303 while (preOrderIterator.hasNext()) {
304 TrieKeySlice nodeKey = preOrderIterator.next().getNodeKey();
305 int nodeKeyLength = nodeKey.length();
306 if (nodeKeyLength == (1 + TrieKeyMapper.SECURE_KEY_SIZE + RskAddress.LENGTH_IN_BYTES) * Byte.SIZE) {
307 byte[] address = nodeKey.slice(nodeKeyLength - RskAddress.LENGTH_IN_BYTES * Byte.SIZE, nodeKeyLength).encode();
308 result.add(new RskAddress(address));
309 }
310 }
311 return result;
312 }
313
314 // To start tracking, a new repository is created, with a MutableTrieCache in the middle
315 @Override
316 public synchronized Repository startTracking() {
317 return new MutableRepository(new MutableTrieCache(mutableTrie));
318 }
319
320 @Override
321 public void save() {
322 mutableTrie.save();
323 }
324
325 @Override
326 public synchronized void commit() {
327 mutableTrie.commit();
328 }
329
330 @Override
331 public synchronized void rollback() {
332 mutableTrie.rollback();
333 }
334
335 @Override
336 public synchronized byte[] getRoot() {
337 mutableTrie.save();
338
339 Keccak256 rootHash = mutableTrie.getHash();
340 logger.trace("getting repository root hash {}", rootHash);
341 return rootHash.getBytes();
342 }
343
344 @Override
345 public synchronized void updateAccountState(RskAddress addr, final AccountState accountState) {
346 byte[] accountKey = trieKeyMapper.getAccountKey(addr);
347 mutableTrie.put(accountKey, accountState.getEncoded());
348 }
349
350 @VisibleForTesting
351 public byte[] getStorageStateRoot(RskAddress addr) {
352 byte[] prefix = trieKeyMapper.getAccountStoragePrefixKey(addr);
353
354 // The value should be ONE_BYTE_ARRAY, but we don't need to check nothing else could be there.
355 Trie storageRootNode = mutableTrie.getTrie().find(prefix);
356 if (storageRootNode == null) {
357 return HashUtil.EMPTY_TRIE_HASH;
358 }
359
360 // Now it's a bit tricky what to return: if I return the storageRootNode hash then it's counting the "0x01"
361 // value, so the try one gets will never match the trie one gets if creating the trie without any other data.
362 // Unless the PDV trie is used. The best we can do is to return storageRootNode hash
363 return storageRootNode.getHash().getBytes();
364 }
365
366 @Nonnull
367 private synchronized AccountState getAccountStateOrCreateNew(RskAddress addr) {
368 AccountState account = getAccountState(addr);
369 return (account == null) ? createAccount(addr) : account;
370 }
371
372 private byte[] getAccountData(RskAddress addr) {
373 return mutableTrie.get(trieKeyMapper.getAccountKey(addr));
374 }
375 }