Coverage Summary for Class: TrieStoreImpl (co.rsk.trie)
Class |
Class, %
|
Method, %
|
Line, %
|
TrieStoreImpl |
100%
(1/1)
|
75%
(6/8)
|
84.4%
(38/45)
|
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.trie;
20
21 import org.ethereum.datasource.KeyValueDataSource;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 import java.util.Collections;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.WeakHashMap;
29
30 /**
31 * TrieStoreImpl store and retrieve Trie node by hash
32 *
33 * It saves/retrieves the serialized form (byte array) of a Trie node
34 *
35 * Internally, it uses a key value data source
36 *
37 * Created by ajlopez on 08/01/2017.
38 */
39 public class TrieStoreImpl implements TrieStore {
40
41 private static final Logger logger = LoggerFactory.getLogger("triestore");
42
43 private KeyValueDataSource store;
44
45 /** Weak references are removed once the tries are garbage collected */
46 private Set<Trie> savedTries = Collections
47 .newSetFromMap(Collections.synchronizedMap(new WeakHashMap<>()));
48
49 public TrieStoreImpl(KeyValueDataSource store) {
50 this.store = store;
51 }
52
53 /**
54 * Recursively saves all unsaved nodes of this trie to the underlying key-value store
55 */
56 @Override
57 public void save(Trie trie) {
58 logger.trace("Start saving trie root.");
59 save(trie, true, 0);
60 logger.trace("End saving trie root.");
61 }
62
63 /**
64 * @param forceSaveRoot allows saving the root node even if it's embeddable
65 */
66 private void save(Trie trie, boolean forceSaveRoot, int level) {
67 logger.trace("Start saving trie, level : {}", level);
68 if (savedTries.contains(trie)) {
69 // it is guaranteed that the children of a saved node are also saved
70 return;
71 }
72
73 byte[] trieKeyBytes = trie.getHash().getBytes();
74
75 if (forceSaveRoot && this.store.get(trieKeyBytes) != null) {
76 // the full trie is already saved
77 logger.trace("End saving trie, level : {}, already saved.", level);
78 return;
79 }
80
81 logger.trace("Start left trie. Level: {}", level);
82 trie.getLeft().getNode().ifPresent(t -> save(t, false, level + 1));
83 logger.trace("Start right trie. Level: {}", level);
84 trie.getRight().getNode().ifPresent(t -> save(t, false, level + 1));
85
86 if (trie.hasLongValue()) {
87 // Note that there is no distinction in keys between node data and value data. This could bring problems in
88 // the future when trying to garbage-collect the data. We could split the key spaces bit a single
89 // overwritten MSB of the hash. Also note that when storing a node that has long value it could be the case
90 // that the save the value here, but the value is already present in the database because other node shares
91 // the value. This is suboptimal, we could check existence here but maybe the database already has
92 // provisions to reduce the load in these cases where a key/value is set equal to the previous value.
93 // In particular our levelDB driver has not method to test for the existence of a key without retrieving the
94 // value also, so manually checking pre-existence here seems it will add overhead on the average case,
95 // instead of reducing it.
96 logger.trace("Putting in store, hasLongValue. Level: {}", level);
97 this.store.put(trie.getValueHash().getBytes(), trie.getValue());
98 logger.trace("End Putting in store, hasLongValue. Level: {}", level);
99 }
100
101 if (trie.isEmbeddable() && !forceSaveRoot) {
102 logger.trace("End Saving. Level: {}", level);
103 return;
104 }
105
106 logger.trace("Putting in store trie root.");
107 this.store.put(trieKeyBytes, trie.toMessage());
108 logger.trace("End putting in store trie root.");
109 savedTries.add(trie);
110 logger.trace("End Saving trie, level: {}.", level);
111 }
112
113 @Override
114 public void flush(){
115 this.store.flush();
116 }
117
118 @Override
119 public Optional<Trie> retrieve(byte[] hash) {
120 byte[] message = this.store.get(hash);
121 if (message == null) {
122 return Optional.empty();
123 }
124
125 Trie trie = Trie.fromMessage(message, this);
126 savedTries.add(trie);
127 return Optional.of(trie);
128 }
129
130 @Override
131 public byte[] retrieveValue(byte[] hash) {
132 return this.store.get(hash);
133 }
134
135 @Override
136 public void dispose() {
137 store.close();
138 }
139 }