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 }