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 }