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 }