Coverage Summary for Class: TrieConverter (co.rsk.trie)

Class Class, % Method, % Line, %
TrieConverter 100% (1/1) 25% (2/8) 4.8% (5/105)


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 co.rsk.trie; 20  21 import co.rsk.core.RskAddress; 22 import co.rsk.core.types.ints.Uint24; 23 import co.rsk.crypto.Keccak256; 24 import co.rsk.metrics.profilers.Metric; 25 import co.rsk.metrics.profilers.Profiler; 26 import co.rsk.metrics.profilers.ProfilerFactory; 27 import co.rsk.remasc.RemascTransaction; 28 import co.rsk.util.MaxSizeHashMap; 29 import org.ethereum.core.AccountState; 30 import org.ethereum.crypto.HashUtil; 31 import org.ethereum.crypto.Keccak256Helper; 32 import org.ethereum.db.TrieKeyMapper; 33 import org.ethereum.util.RLP; 34 import org.ethereum.vm.DataWord; 35  36 import java.util.Map; 37  38 public class TrieConverter { 39  40  private static final Profiler profiler = ProfilerFactory.getInstance(); 41  private static final byte LEFT_CHILD_IMPLICIT_KEY = (byte) 0x00; 42  private static final byte RIGHT_CHILD_IMPLICIT_KEY = (byte) 0x01; 43  44  private final Map<Keccak256, byte[]> cacheHashes; 45  private final Map<Keccak256, Trie> cacheStorage; 46  47  public TrieConverter() { 48  cacheHashes = new MaxSizeHashMap<>(500_000, true); 49  cacheStorage = new MaxSizeHashMap<>(600_000, true); 50  } 51  52  public byte[] getOrchidAccountTrieRoot(Trie src) { 53  Metric metric = profiler.start(Profiler.PROFILING_TYPE.TRIE_CONVERTER_GET_ACCOUNT_ROOT); 54  byte[] trieRoot = cacheHashes.computeIfAbsent(src.getHash(), k -> { 55  Trie trie = getOrchidAccountTrieRoot(src.getSharedPath(), src, true); 56  return trie == null ? HashUtil.EMPTY_TRIE_HASH : trie.getHashOrchid(true).getBytes(); 57  }); 58  profiler.stop(metric); 59  return trieRoot; 60  } 61  62  private Trie getOrchidAccountTrieRoot(TrieKeySlice key, Trie src, boolean removeFirst8bits) { 63  if (src == null || src.isEmptyTrie()) { 64  return null; 65  } 66  67  TrieKeySlice sharedPath = src.getSharedPath(); 68  if (removeFirst8bits) { 69  if (sharedPath.length() < 8) { 70  throw new IllegalStateException("Unable to remove first 8-bits if path length is less than 8"); 71  } 72  73  sharedPath = sharedPath.slice(8, sharedPath.length()); 74  } 75  76  Trie child0 = src.getNodeReference(LEFT_CHILD_IMPLICIT_KEY).getNode().orElse(null); 77  Trie child0Hash = null; 78  Trie child1 = src.getNodeReference(RIGHT_CHILD_IMPLICIT_KEY).getNode().orElse(null); 79  Trie child1Hash = null; 80  81  boolean isRemascAccount = key.length() == (1 + TrieKeyMapper.SECURE_KEY_SIZE + RemascTransaction.REMASC_ADDRESS.getBytes().length) * Byte.SIZE; 82  if ((key.length() == (1 + TrieKeyMapper.SECURE_KEY_SIZE + RskAddress.LENGTH_IN_BYTES) * Byte.SIZE || isRemascAccount) && src.getValue() != null) { 83  // We've reached the Account level. From now on everything will be different. 84  AccountState astate = new AccountState(src.getValue()); 85  OrchidAccountState oldState = new OrchidAccountState(astate.getNonce(), astate.getBalance()); 86  // child1 (0x80) will be the code 87  if (child1 != null) { 88  oldState.setCodeHash(child1.getValueHash().getBytes()); 89  } else { 90  oldState.setCodeHash(Keccak256Helper.keccak256(new byte[0])); 91  } 92  // the child0 side will be the storage. The first child corresponds to the 93  // 8-bit zero prefix. 1 bit is consumed by the branch. 7-bits left. We check that 94  if (child0 != null) { 95  if (child0.getSharedPath().length() != 7) { 96  throw new IllegalStateException("First child must be 7-bits length"); 97  } 98  // We'll create an ad-hoc trie for storage cells, the first 99  // child0's child is the root of this try. What if it has two children? 100  // This can happen if there are two hashed storage keys, one begining with 101  // 0 and another with 1. 102  TrieKeySlice child0Key = key.rebuildSharedPath(LEFT_CHILD_IMPLICIT_KEY, child0.getSharedPath()); 103  Trie root = getOrchidStateRoot(child0Key, child0, true, false); 104  oldState.setStateRoot(root.getHashOrchid(true).getBytes()); 105  } else if (isRemascAccount) { 106  oldState.setStateRoot(Keccak256Helper.keccak256(RLP.encodeElement(new byte[0]))); 107  } 108  109  byte[] avalue = oldState.getEncoded(); 110  TrieKeySlice orchidKey; 111  if (isRemascAccount) { 112  orchidKey = extractOrchidAccountKeyPathFromUnitrieKey(key, sharedPath.length(), RemascTransaction.REMASC_ADDRESS.getBytes().length, TrieKeyMapper.REMASC_ACCOUNT_KEY_SIZE); 113  } else { 114  orchidKey = extractOrchidAccountKeyPathFromUnitrieKey(key, sharedPath.length(), RskAddress.LENGTH_IN_BYTES, TrieKeyMapper.SECURE_ACCOUNT_KEY_SIZE); 115  } 116  117  return new Trie( 118  null, orchidKey, avalue, NodeReference.empty(), NodeReference.empty(), 119  new Uint24(avalue.length), null 120  ); 121  } 122  123  if (child0 != null) { 124  TrieKeySlice child0Key = key.rebuildSharedPath(LEFT_CHILD_IMPLICIT_KEY, child0.getSharedPath()); 125  child0Hash = getOrchidAccountTrieRoot(child0Key, child0, false); 126  } 127  128  if (child1 != null) { 129  TrieKeySlice child1Key = key.rebuildSharedPath(RIGHT_CHILD_IMPLICIT_KEY, child1.getSharedPath()); 130  child1Hash = getOrchidAccountTrieRoot(child1Key, child1, false); 131  } 132  133  NodeReference left = new NodeReference(null, child0Hash, null); 134  NodeReference right = new NodeReference(null, child1Hash, null); 135  136  return new Trie( 137  null, sharedPath, src.getValue(), left, right, 138  src.getValueLength(), src.getValueHash() 139  ); 140  } 141  142  private Trie getOrchidStateRoot( 143  TrieKeySlice key, 144  Trie unitrieStorageRoot, 145  boolean removeFirstNodePrefix, 146  boolean onlyChild) { 147  148  Trie storageNodeHash = cacheStorage.get(unitrieStorageRoot.getHash()); 149  if (storageNodeHash != null && !onlyChild && !removeFirstNodePrefix) { 150  return storageNodeHash; 151  } 152  153  Trie child0 = unitrieStorageRoot.getNodeReference(LEFT_CHILD_IMPLICIT_KEY).getNode().orElse(null); 154  Trie child1 = unitrieStorageRoot.getNodeReference(RIGHT_CHILD_IMPLICIT_KEY).getNode().orElse(null); 155  Trie child0Hash = null; 156  if (child0 != null) { 157  TrieKeySlice child0Key = key.rebuildSharedPath(LEFT_CHILD_IMPLICIT_KEY, child0.getSharedPath()); 158  child0Hash = getOrchidStateRoot(child0Key, child0, false, removeFirstNodePrefix && child1 == null); 159  } 160  161  Trie child1Hash = null; 162  if (child1 != null) { 163  TrieKeySlice child1Key = key.rebuildSharedPath(RIGHT_CHILD_IMPLICIT_KEY, child1.getSharedPath()); 164  child1Hash = getOrchidStateRoot(child1Key, child1, false, removeFirstNodePrefix && child0 == null); 165  } 166  167  TrieKeySlice sharedPath = unitrieStorageRoot.getSharedPath(); 168  byte[] value = unitrieStorageRoot.getValue(); 169  Uint24 valueLength = unitrieStorageRoot.getValueLength(); 170  Keccak256 valueHash = unitrieStorageRoot.getValueHash(); 171  172  if (removeFirstNodePrefix) { 173  sharedPath = TrieKeySlice.empty(); 174  value = null; // also remove value 175  valueLength = Uint24.ZERO; 176  valueHash = null; 177  if (child0 != null && child1 == null) { 178  return child0Hash; 179  } 180  if (child0 == null && child1 != null) { 181  return child1Hash; 182  } 183  } 184  185  if (onlyChild) { 186  sharedPath = key.slice(key.length() - (sharedPath.length() + 1), key.length()); 187  } 188  189  if (!removeFirstNodePrefix && child0Hash == null && child1Hash == null) { // terminal node 190  sharedPath = extractOrchidStorageKeyPathFromUnitrieKey(key, sharedPath); 191  } else { 192  // 42 = DOMAIN_PREFIX(1) + SECURE_KEY_SIZE(10) + RskAddress(20) + STORAGE_PREFIX(1) + SECURE_KEY_SIZE(10) 193  if (key.length() >= 42 * Byte.SIZE) { 194  // there is a branching ahead of the needed shared 10 bytes 195  throw new IllegalArgumentException("The unitrie storage doesn't share as much structure as we need to rebuild the Orchid trie"); 196  } 197  } 198  199  NodeReference left = new NodeReference(null, child0Hash, null); 200  NodeReference right = new NodeReference(null, child1Hash, null); 201  Trie newNode = new Trie( 202  null, sharedPath, value, left, right, 203  valueLength, valueHash 204  ); 205  if (!onlyChild) { 206  cacheStorage.put(unitrieStorageRoot.getHash(), newNode); 207  } 208  209  return newNode; 210  } 211  212  private TrieKeySlice extractOrchidAccountKeyPathFromUnitrieKey(TrieKeySlice key, int sharedPathLength, int addressLengthInBytes, int unitrieKeySizeInBytes) { 213  if (sharedPathLength < (unitrieKeySizeInBytes - TrieKeyMapper.SECURE_KEY_SIZE) * Byte.SIZE) { // = 20 bytes = RskAddress.LENGTH_IN_BYTES 214  throw new IllegalArgumentException("The unitrie doesn't share as much structure as we need to rebuild the Orchid trie"); 215  } 216  217  byte[] encodedKey = key.slice(key.length() - addressLengthInBytes * Byte.SIZE, key.length()).encode(); 218  byte[] orchidTrieSecureKey = Keccak256Helper.keccak256(encodedKey); 219  TrieKeySlice expandedOrchidTrieSecureKey = TrieKeySlice.fromKey(orchidTrieSecureKey); 220  // the length of the structure that's shared between the Orchid trie and the Unitrie 221  int commonTriePathLength = unitrieKeySizeInBytes * Byte.SIZE - sharedPathLength; 222  223  return expandedOrchidTrieSecureKey.slice(commonTriePathLength, expandedOrchidTrieSecureKey.length()); 224  } 225  226  private TrieKeySlice extractOrchidStorageKeyPathFromUnitrieKey(TrieKeySlice key, TrieKeySlice sharedPath) { 227  // 42 = DOMAIN_PREFIX(1) + SECURE_KEY_SIZE(10) + RskAddress(20) + STORAGE_PREFIX(1) + SECURE_KEY_SIZE(10) 228  int leadingZeroesToAdd = 42 * Byte.SIZE + DataWord.BYTES * Byte.SIZE - key.length(); 229  TrieKeySlice unsecuredKey = key.slice(42 * Byte.SIZE, key.length()).leftPad(leadingZeroesToAdd); 230  byte[] orchidTrieSecureKey = Keccak256Helper.keccak256(unsecuredKey.encode()); 231  232  TrieKeySlice expandedOrchidTrieSecureKey = TrieKeySlice.fromKey(orchidTrieSecureKey); 233  234  int nonSharedPathOffset = 42 * Byte.SIZE - sharedPath.length() - leadingZeroesToAdd; 235  236  return expandedOrchidTrieSecureKey.slice(nonSharedPathOffset, expandedOrchidTrieSecureKey.length()); 237  } 238  239 }