diff --git a/modAion/src/org/aion/zero/db/AionContractDetailsImpl.java b/modAion/src/org/aion/zero/db/AionContractDetailsImpl.java index 3edd2dbf0d..22f6fb00fb 100644 --- a/modAion/src/org/aion/zero/db/AionContractDetailsImpl.java +++ b/modAion/src/org/aion/zero/db/AionContractDetailsImpl.java @@ -28,7 +28,6 @@ import static org.aion.crypto.HashUtil.EMPTY_TRIE_HASH; import static org.aion.crypto.HashUtil.h256; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -149,13 +148,33 @@ public byte[] getStorageHash() { */ @Override public void decode(byte[] rlpCode) { + decode(rlpCode, false); + } + + /** + * Decodes an AionContractDetailsImpl object from the RLP encoding rlpCode with fast check does + * the contractDetails needs external storage. + * + * @param rlpCode The encoding to decode. + * @param fastCheck set fastCheck option. + */ + @Override + public void decode(byte[] rlpCode, boolean fastCheck) { RLPList data = RLP.decode2(rlpCode); RLPList rlpList = (RLPList) data.get(0); - RLPItem address = (RLPItem) rlpList.get(0); RLPItem isExternalStorage = (RLPItem) rlpList.get(1); - RLPItem storageRoot = (RLPItem) rlpList.get(2); RLPItem storage = (RLPItem) rlpList.get(3); + this.externalStorage = isExternalStorage.getRLPData().length > 0; + boolean keepStorageInMem = storage.getRLPData().length <= detailsInMemoryStorageLimit; + + // No externalStorage require. + if (fastCheck && !externalStorage && keepStorageInMem) { + return; + } + + RLPItem address = (RLPItem) rlpList.get(0); + RLPItem storageRoot = (RLPItem) rlpList.get(2); RLPElement code = rlpList.get(4); if (address.getRLPData() == null) { @@ -173,7 +192,6 @@ public void decode(byte[] rlpCode) { } // load/deserialize storage trie - this.externalStorage = !Arrays.equals(isExternalStorage.getRLPData(), EMPTY_BYTE_ARRAY); if (externalStorage) { storageTrie = new SecureTrie(getExternalStorageDataSource(), storageRoot.getRLPData()); } else { @@ -182,7 +200,7 @@ public void decode(byte[] rlpCode) { storageTrie.withPruningEnabled(prune > 0); // switch from in-memory to external storage - if (!externalStorage && storage.getRLPData().length > detailsInMemoryStorageLimit) { + if (!externalStorage && !keepStorageInMem) { externalStorage = true; storageTrie.getCache().setDB(getExternalStorageDataSource()); } diff --git a/modAionBase/src/org/aion/base/db/IContractDetails.java b/modAionBase/src/org/aion/base/db/IContractDetails.java index 46876ba942..8af05a607a 100644 --- a/modAionBase/src/org/aion/base/db/IContractDetails.java +++ b/modAionBase/src/org/aion/base/db/IContractDetails.java @@ -100,6 +100,16 @@ public interface IContractDetails { */ void decode(byte[] rlpCode); + /** + * Decodes an IContractDetails object from the RLP encoding rlpCode including the fast check + * optional. + * + * @implNote Implementing classes may not necessarily support this method. + * @param rlpCode The encoding to decode. + * @param fastCheck fast check does the contractDetails needs syncing with external storage + */ + void decode(byte[] rlpCode, boolean fastCheck); + /** * Sets the dirty value to dirty. * diff --git a/modAionBase/src/org/aion/base/db/IKeyValueStore.java b/modAionBase/src/org/aion/base/db/IKeyValueStore.java index ba63ef7d41..e1f3ba638b 100644 --- a/modAionBase/src/org/aion/base/db/IKeyValueStore.java +++ b/modAionBase/src/org/aion/base/db/IKeyValueStore.java @@ -36,9 +36,9 @@ package org.aion.base.db; import java.util.Collection; +import java.util.Iterator; import java.util.Map; import java.util.Optional; -import java.util.Set; /** * Functionality for a key-value cache allowing itemized updates. @@ -63,13 +63,15 @@ public interface IKeyValueStore extends AutoCloseable { boolean isEmpty(); /** - * Returns the set of keys for the database. + * Returns an {@link Iterator} over the set of keys stored in the database at the time when the + * keys were requested. A snapshot can be used to ensure that the entries do not change while + * iterating through the keys. * - * @return Set of keys + * @return an iterator over the set of stored keys * @throws RuntimeException if the data store is closed - * @apiNote Returns an empty set if the database keys could not be retrieved. + * @apiNote Returns an empty iterator if the database keys could not be retrieved. */ - Set keys(); + Iterator keys(); /** * get retrieves a value from the database, returning an optional, it is fulfilled if a value diff --git a/modAionImpl/src/org/aion/zero/impl/Version.java b/modAionImpl/src/org/aion/zero/impl/Version.java index 68ce7ff624..f3e982de95 100644 --- a/modAionImpl/src/org/aion/zero/impl/Version.java +++ b/modAionImpl/src/org/aion/zero/impl/Version.java @@ -24,7 +24,7 @@ package org.aion.zero.impl; public class Version { - public static final String KERNEL_VERSION = "0.3.2"; + public static final String KERNEL_VERSION = "0.3.2.2"; public static final String REPO_VERSION = "0.1.0"; public static final boolean FORK = true; } diff --git a/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java b/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java index 317c8c3398..27cd606eb6 100644 --- a/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java +++ b/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -332,8 +333,9 @@ public List getPoolTx() { List rtn = new ArrayList<>(); rwLock.readLock().lock(); try { - Set keySet = txPoolDatabase.keys(); - for (byte[] b : keySet) { + Iterator iterator = txPoolDatabase.keys(); + while (iterator.hasNext()) { + byte[] b = iterator.next(); if (txPoolDatabase.get(b).isPresent()) { rtn.add(txPoolDatabase.get(b).get()); } @@ -351,8 +353,9 @@ public List getCacheTx() { List rtn = new ArrayList<>(); rwLock.readLock().lock(); try { - Set keySet = pendingTxCacheDatabase.keys(); - for (byte[] b : keySet) { + Iterator iterator = pendingTxCacheDatabase.keys(); + while (iterator.hasNext()) { + byte[] b = iterator.next(); if (pendingTxCacheDatabase.get(b).isPresent()) { rtn.add(pendingTxCacheDatabase.get(b).get()); } diff --git a/modAionImpl/src/org/aion/zero/impl/db/PendingBlockStore.java b/modAionImpl/src/org/aion/zero/impl/db/PendingBlockStore.java index 932309497a..f87e264ed3 100644 --- a/modAionImpl/src/org/aion/zero/impl/db/PendingBlockStore.java +++ b/modAionImpl/src/org/aion/zero/impl/db/PendingBlockStore.java @@ -36,6 +36,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -519,7 +520,7 @@ private int addBlockRange(AionBlock first, List blockRange) { int getIndexSize() { databaseLock.readLock().lock(); try { - return indexSource.keys().size(); + return countDatabaseKeys(indexSource); } finally { databaseLock.readLock().unlock(); } @@ -533,7 +534,7 @@ int getIndexSize() { int getLevelSize() { databaseLock.readLock().lock(); try { - return levelDatabase.keys().size(); + return countDatabaseKeys(levelDatabase); } finally { databaseLock.readLock().unlock(); } @@ -547,12 +548,22 @@ int getLevelSize() { int getQueueSize() { databaseLock.readLock().lock(); try { - return queueDatabase.keys().size(); + return countDatabaseKeys(queueDatabase); } finally { databaseLock.readLock().unlock(); } } + private static int countDatabaseKeys(IByteArrayKeyValueDatabase db) { + int size = 0; + Iterator iterator = db.keys(); + while (iterator.hasNext()) { + iterator.next(); + size++; + } + return size; + } + /** * Retrieves blocks from storage based on the height of the first block in the range. * diff --git a/modAionImpl/src/org/aion/zero/impl/sync/TaskImportBlocks.java b/modAionImpl/src/org/aion/zero/impl/sync/TaskImportBlocks.java index f76fcb88da..6f6982cd4a 100644 --- a/modAionImpl/src/org/aion/zero/impl/sync/TaskImportBlocks.java +++ b/modAionImpl/src/org/aion/zero/impl/sync/TaskImportBlocks.java @@ -592,7 +592,7 @@ private ImportResult importBlock(AionBlock b, String displayId, PeerState state) // 1 sec import time and more than 10 min since last compact if (t2 - t1 > SLOW_IMPORT_TIME && t2 - lastCompactTime > COMPACT_FREQUENCY) { t1 = System.currentTimeMillis(); - this.chain.compactState(); + //this.chain.compactState(); t2 = System.currentTimeMillis(); log.info("Compacting state database due to slow IO time. Completed in {} ms.", t2 - t1); lastCompactTime = t2; diff --git a/modAionImpl/test/org/aion/zero/impl/BlockchainDataRecoveryTest.java b/modAionImpl/test/org/aion/zero/impl/BlockchainDataRecoveryTest.java index 131437bf17..0c33c3a419 100644 --- a/modAionImpl/test/org/aion/zero/impl/BlockchainDataRecoveryTest.java +++ b/modAionImpl/test/org/aion/zero/impl/BlockchainDataRecoveryTest.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import org.aion.base.db.IByteArrayKeyValueDatabase; @@ -263,7 +264,10 @@ public void testRecoverWorldStateWithoutGenesis() { repo.flush(); List statesToDelete = new ArrayList<>(); - statesToDelete.addAll(database.keys()); + Iterator iterator = database.keys(); + while (iterator.hasNext()) { + statesToDelete.add(iterator.next()); + } for (byte[] key : statesToDelete) { database.delete(key); diff --git a/modDbImpl/src/org/aion/db/generic/CacheIteratorWrapper.java b/modDbImpl/src/org/aion/db/generic/CacheIteratorWrapper.java new file mode 100644 index 0000000000..d95bb3c67c --- /dev/null +++ b/modDbImpl/src/org/aion/db/generic/CacheIteratorWrapper.java @@ -0,0 +1,76 @@ +package org.aion.db.generic; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.aion.base.util.ByteArrayWrapper; + +/** + * A wrapper for the iterator needed by {@link DatabaseWithCache} conforming to the {@link Iterator} + * interface. + * + * @implNote Assumes that the given database iterator does not return duplicate values. + * @author Alexandra Roatis + */ +public class CacheIteratorWrapper implements Iterator { + private final Iterator iterator; + private byte[] next; + private final List additions; + private final List removals; + + /** + * @implNote Building two wrappers for the same {@link Iterator} will lead to inconsistent + * behavior. + */ + public CacheIteratorWrapper( + final Iterator iterator, Map dirtyEntries) { + this.iterator = iterator; + additions = new ArrayList<>(); + removals = new ArrayList<>(); + + for (Map.Entry entry : dirtyEntries.entrySet()) { + if (entry.getValue() == null) { + removals.add(entry.getKey()); + } else { + additions.add(entry.getKey()); + } + } + } + + @Override + public boolean hasNext() { + boolean seek = true; + ByteArrayWrapper wrapper; + // check in the database iterator + while (seek && iterator.hasNext()) { + next = iterator.next(); + wrapper = ByteArrayWrapper.wrap(next); + if (removals.contains(wrapper)) { + // key deleted, move to next in iterator + removals.remove(wrapper); + } else if (additions.contains(wrapper)) { + // found an entry that was updated + seek = false; + additions.remove(wrapper); + } else { + // found an entry that was not changed + seek = false; + } + } + + // exhausted the initial iterator, trying the dirty entries + // check in the cached entries + if (seek && !additions.isEmpty()) { + next = additions.remove(0).getData(); + seek = false; + } + + return !seek; + } + + @Override + public byte[] next() { + return next; + } +} diff --git a/modDbImpl/src/org/aion/db/generic/DatabaseWithCache.java b/modDbImpl/src/org/aion/db/generic/DatabaseWithCache.java index 8aa1fe8f2f..9103a89e59 100644 --- a/modDbImpl/src/org/aion/db/generic/DatabaseWithCache.java +++ b/modDbImpl/src/org/aion/db/generic/DatabaseWithCache.java @@ -41,10 +41,9 @@ import com.google.common.primitives.Longs; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; +import java.util.Iterator; import java.util.Map; import java.util.Optional; -import java.util.Set; import org.aion.base.db.IByteArrayKeyValueDatabase; import org.aion.base.util.ByteArrayWrapper; import org.aion.db.impl.AbstractDB; @@ -378,26 +377,9 @@ public boolean isEmpty() { } @Override - public Set keys() { - - Set keys = new HashSet<>(); - + public Iterator keys() { check(); - - // add all database keys - keys.addAll(database.keys()); - - // add updated cached keys - dirtyEntries.forEach( - (k, v) -> { - if (v == null) { - keys.remove(k.getData()); - } else { - keys.add(k.getData()); - } - }); - - return keys; + return new CacheIteratorWrapper(database.keys(), dirtyEntries); } /** diff --git a/modDbImpl/src/org/aion/db/generic/LockedDatabase.java b/modDbImpl/src/org/aion/db/generic/LockedDatabase.java index c46f71bda8..e49447b577 100644 --- a/modDbImpl/src/org/aion/db/generic/LockedDatabase.java +++ b/modDbImpl/src/org/aion/db/generic/LockedDatabase.java @@ -24,9 +24,9 @@ package org.aion.db.generic; import java.util.Collection; +import java.util.Iterator; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.aion.base.db.IByteArrayKeyValueDatabase; @@ -220,7 +220,7 @@ public boolean isEmpty() { } @Override - public Set keys() { + public Iterator keys() { // acquire read lock lock.readLock().lock(); diff --git a/modDbImpl/src/org/aion/db/generic/TimedDatabase.java b/modDbImpl/src/org/aion/db/generic/TimedDatabase.java index 94c6621dd4..c9f745c3ff 100644 --- a/modDbImpl/src/org/aion/db/generic/TimedDatabase.java +++ b/modDbImpl/src/org/aion/db/generic/TimedDatabase.java @@ -24,9 +24,9 @@ package org.aion.db.generic; import java.util.Collection; +import java.util.Iterator; import java.util.Map; import java.util.Optional; -import java.util.Set; import org.aion.base.db.IByteArrayKeyValueDatabase; import org.aion.base.util.Hex; import org.aion.log.AionLoggerFactory; @@ -174,9 +174,9 @@ public boolean isEmpty() { } @Override - public Set keys() { + public Iterator keys() { long t1 = System.nanoTime(); - Set result = database.keys(); + Iterator result = database.keys(); long t2 = System.nanoTime(); LOG.debug(database.toString() + " keys() in " + (t2 - t1) + " ns."); diff --git a/modDbImpl/src/org/aion/db/impl/h2/H2MVMap.java b/modDbImpl/src/org/aion/db/impl/h2/H2MVMap.java index c67003e1c8..ead75db5a0 100644 --- a/modDbImpl/src/org/aion/db/impl/h2/H2MVMap.java +++ b/modDbImpl/src/org/aion/db/impl/h2/H2MVMap.java @@ -36,10 +36,9 @@ import java.io.File; import java.util.Collection; -import java.util.HashSet; +import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import org.aion.base.util.ByteArrayWrapper; import org.aion.db.impl.AbstractDB; import org.h2.mvstore.FileStore; @@ -238,15 +237,18 @@ public boolean isEmpty() { } @Override - public Set keys() { - - Set keys = new HashSet<>(); - + public Iterator keys() { check(); - keys.addAll(map.keySet()); + // get current version + long version = store.getCurrentVersion(); + // making the version read-only + store.commit(); + + // get snapshot of version + MVMap snapshot = map.openVersion(version); - return keys; + return snapshot.keySet().iterator(); } @Override diff --git a/modDbImpl/src/org/aion/db/impl/leveldb/LevelDB.java b/modDbImpl/src/org/aion/db/impl/leveldb/LevelDB.java index ef00e1475f..d715aea272 100644 --- a/modDbImpl/src/org/aion/db/impl/leveldb/LevelDB.java +++ b/modDbImpl/src/org/aion/db/impl/leveldb/LevelDB.java @@ -37,9 +37,9 @@ import java.io.File; import java.io.IOException; import java.util.Collection; -import java.util.HashSet; +import java.util.Collections; +import java.util.Iterator; import java.util.Map; -import java.util.Set; import org.aion.base.util.ByteArrayWrapper; import org.aion.db.impl.AbstractDB; import org.fusesource.leveldbjni.JniDBFactory; @@ -48,6 +48,7 @@ import org.iq80.leveldb.DBException; import org.iq80.leveldb.DBIterator; import org.iq80.leveldb.Options; +import org.iq80.leveldb.ReadOptions; import org.iq80.leveldb.WriteBatch; /** @@ -285,22 +286,70 @@ public boolean isEmpty() { } @Override - public Set keys() { - Set set = new HashSet<>(); - + public Iterator keys() { check(); - try (DBIterator itr = db.iterator()) { - // extract keys - for (itr.seekToFirst(); itr.hasNext(); itr.next()) { - set.add(itr.peekNext().getKey()); - } + try { + ReadOptions readOptions = new ReadOptions(); + readOptions.snapshot(db.getSnapshot()); + return new LevelDBIteratorWrapper(readOptions, db.iterator(readOptions)); } catch (Exception e) { LOG.error("Unable to extract keys from database " + this.toString() + ".", e); } // empty when retrieval failed - return set; + return Collections.emptyIterator(); + } + + /** + * A wrapper for the {@link DBIterator} conforming to the {@link Iterator} interface. + * + * @author Alexandra Roatis + */ + private static class LevelDBIteratorWrapper implements Iterator { + private final DBIterator iterator; + private final ReadOptions readOptions; + private boolean closed; + + /** + * @implNote Building two wrappers for the same {@link DBIterator} will lead to inconsistent + * behavior. + */ + LevelDBIteratorWrapper(final ReadOptions readOptions, final DBIterator iterator) { + this.readOptions = readOptions; + this.iterator = iterator; + iterator.seekToFirst(); + closed = false; + } + + @Override + public boolean hasNext() { + if (!closed) { + boolean hasNext = iterator.hasNext(); + + // close iterator after last entry + if (!hasNext) { + try { + iterator.close(); + readOptions.snapshot().close(); + } catch (IOException e) { + LOG.error("Unable to close iterator object.", e); + } + closed = true; + } + + return hasNext; + } else { + return false; + } + } + + @Override + public byte[] next() { + byte[] key = iterator.peekNext().getKey(); + iterator.next(); + return key; + } } @Override @@ -365,7 +414,7 @@ public void putBatch(Map inputMap) { } } - WriteBatch batch = null; + private WriteBatch batch = null; @Override public void putToBatch(byte[] key, byte[] value) { diff --git a/modDbImpl/src/org/aion/db/impl/mockdb/MockDB.java b/modDbImpl/src/org/aion/db/impl/mockdb/MockDB.java index 0d6c96eea5..25088f43d7 100644 --- a/modDbImpl/src/org/aion/db/impl/mockdb/MockDB.java +++ b/modDbImpl/src/org/aion/db/impl/mockdb/MockDB.java @@ -26,6 +26,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Map; import java.util.Set; import org.aion.base.util.ByteArrayWrapper; @@ -105,7 +106,7 @@ public boolean isEmpty() { } @Override - public Set keys() { + public Iterator keys() { Set set = new HashSet<>(); check(); @@ -113,7 +114,7 @@ public Set keys() { kv.keySet().forEach(k -> set.add(k.getData())); // empty when retrieval failed - return set; + return set.iterator(); } @Override diff --git a/modDbImpl/src/org/aion/db/impl/rocksdb/RocksDBWrapper.java b/modDbImpl/src/org/aion/db/impl/rocksdb/RocksDBWrapper.java index e80eacfb6f..4bec8b44e1 100644 --- a/modDbImpl/src/org/aion/db/impl/rocksdb/RocksDBWrapper.java +++ b/modDbImpl/src/org/aion/db/impl/rocksdb/RocksDBWrapper.java @@ -25,14 +25,15 @@ import java.io.File; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; +import java.util.Collections; +import java.util.Iterator; import java.util.Map; -import java.util.Set; import org.aion.base.util.ByteArrayWrapper; import org.aion.db.impl.AbstractDB; import org.rocksdb.BlockBasedTableConfig; import org.rocksdb.CompressionType; import org.rocksdb.Options; +import org.rocksdb.ReadOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; @@ -220,24 +221,66 @@ public boolean isEmpty() { } @Override - public Set keys() { - Set set = new HashSet<>(); - + public Iterator keys() { check(); - try (RocksIterator itr = db.newIterator()) { - itr.seekToFirst(); - // extract keys - while (itr.isValid()) { - set.add(itr.key()); - itr.next(); - } + try { + ReadOptions readOptions = new ReadOptions(); + readOptions.setSnapshot(db.getSnapshot()); + return new RocksDBIteratorWrapper(readOptions, db.newIterator(readOptions)); } catch (Exception e) { LOG.error("Unable to extract keys from database " + this.toString() + ".", e); } // empty when retrieval failed - return set; + return Collections.emptyIterator(); + } + + /** + * A wrapper for the {@link RocksIterator} conforming to the {@link Iterator} interface. + * + * @author Alexandra Roatis + */ + private static class RocksDBIteratorWrapper implements Iterator { + private final RocksIterator iterator; + private final ReadOptions readOptions; + private boolean closed; + + /** + * @implNote Building two wrappers for the same {@link RocksIterator} will lead to + * inconsistent behavior. + */ + RocksDBIteratorWrapper(final ReadOptions readOptions, final RocksIterator iterator) { + this.readOptions = readOptions; + this.iterator = iterator; + iterator.seekToFirst(); + closed = false; + } + + @Override + public boolean hasNext() { + if (!closed) { + boolean isValid = iterator.isValid(); + + // close iterator after last entry + if (!isValid) { + iterator.close(); + readOptions.close(); + closed = true; + } + + return isValid; + } else { + return false; + } + } + + @Override + public byte[] next() { + byte[] key = iterator.key(); + iterator.next(); + return key; + } } @Override @@ -282,7 +325,7 @@ public void delete(byte[] k) { } } - WriteBatch batch = null; + private WriteBatch batch = null; @Override public void putToBatch(byte[] key, byte[] value) { diff --git a/modDbImpl/test/org/aion/db/impl/ConcurrencyTest.java b/modDbImpl/test/org/aion/db/impl/ConcurrencyTest.java index 6c949dcb7b..baaef5b1d6 100644 --- a/modDbImpl/test/org/aion/db/impl/ConcurrencyTest.java +++ b/modDbImpl/test/org/aion/db/impl/ConcurrencyTest.java @@ -31,10 +31,10 @@ import com.google.common.truth.Truth; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Set; import junitparams.JUnitParamsRunner; import junitparams.Parameters; import org.aion.base.db.IByteArrayKeyValueDatabase; @@ -90,6 +90,15 @@ private Object databaseInstanceDefinitions() { return DatabaseTestUtils.unlockedDatabaseInstanceDefinitions(); } + private static int count(Iterator keys) { + int size = 0; + while (keys.hasNext()) { + size++; + keys.next(); + } + return size; + } + private void addThread4IsEmpty(List threads, IByteArrayKeyValueDatabase db) { threads.add( () -> { @@ -106,10 +115,10 @@ private void addThread4IsEmpty(List threads, IByteArrayKeyValueDatabas private void addThread4Keys(List threads, IByteArrayKeyValueDatabase db) { threads.add( () -> { - Set keys = db.keys(); + Iterator keys = db.keys(); if (DISPLAY_MESSAGES) { System.out.println( - Thread.currentThread().getName() + ": #keys = " + keys.size()); + Thread.currentThread().getName() + ": #keys = " + count(keys)); } }); } @@ -304,7 +313,7 @@ public void testConcurrentPut(Properties dbDef) throws InterruptedException { assertConcurrent("Testing put(...) ", threads, TIME_OUT); // check that all values were added - assertThat(db.keys().size()).isEqualTo(CONCURRENT_THREADS); + assertThat(count(db.keys())).isEqualTo(CONCURRENT_THREADS); // ensuring close db.close(); @@ -330,7 +339,7 @@ public void testConcurrentPutBatch(Properties dbDef) throws InterruptedException assertConcurrent("Testing putBatch(...) ", threads, TIME_OUT); // check that all values were added - assertThat(db.keys().size()).isEqualTo(3 * CONCURRENT_THREADS); + assertThat(count(db.keys())).isEqualTo(3 * CONCURRENT_THREADS); // ensuring close db.close(); diff --git a/modDbImpl/test/org/aion/db/impl/DriverBaseTest.java b/modDbImpl/test/org/aion/db/impl/DriverBaseTest.java index 307bf376a6..a70b13c979 100644 --- a/modDbImpl/test/org/aion/db/impl/DriverBaseTest.java +++ b/modDbImpl/test/org/aion/db/impl/DriverBaseTest.java @@ -43,8 +43,9 @@ import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; -import java.util.Set; import org.aion.base.db.IByteArrayKeyValueDatabase; import org.aion.db.generic.DatabaseWithCache; import org.aion.db.generic.LockedDatabase; @@ -412,6 +413,15 @@ public void close() { assertThat(db.getName().get()).isEqualTo(dbName); } + private static int count(Iterator keys) { + int size = 0; + while (keys.hasNext()) { + size++; + keys.next(); + } + return size; + } + @Test public void testConcurrentAccess() { // TODO: import test from legacy test case @@ -455,7 +465,7 @@ public void testPersistence() throws InterruptedException { // ensure persistence assertThat(db.get(k1).get()).isEqualTo(v1); assertThat(db.isEmpty()).isFalse(); - assertThat(db.keys().size()).isEqualTo(1); + assertThat(count(db.keys())).isEqualTo(1); assertThat(db.isLocked()).isFalse(); // deleting data @@ -477,7 +487,7 @@ public void testPersistence() throws InterruptedException { // ensure absence assertThat(db.get(k1).isPresent()).isFalse(); assertThat(db.isEmpty()).isTrue(); - assertThat(db.keys().size()).isEqualTo(0); + assertThat(db.keys().hasNext()).isFalse(); assertThat(db.isLocked()).isFalse(); } } @@ -515,7 +525,7 @@ public void testBatchPersistence() throws InterruptedException { assertThat(db.get(k2).get()).isEqualTo(v2); assertThat(db.get(k3).get()).isEqualTo(v3); assertThat(db.isEmpty()).isFalse(); - assertThat(db.keys().size()).isEqualTo(3); + assertThat(count(db.keys())).isEqualTo(3); assertThat(db.isLocked()).isFalse(); // updating data @@ -544,7 +554,7 @@ public void testBatchPersistence() throws InterruptedException { assertThat(db.get(k2).get()).isEqualTo(v3); assertThat(db.get(k3).isPresent()).isFalse(); assertThat(db.isEmpty()).isFalse(); - assertThat(db.keys().size()).isEqualTo(2); + assertThat(count(db.keys())).isEqualTo(2); assertThat(db.isLocked()).isFalse(); // deleting data @@ -569,7 +579,7 @@ public void testBatchPersistence() throws InterruptedException { assertThat(db.get(k2).isPresent()).isFalse(); assertThat(db.get(k3).isPresent()).isFalse(); assertThat(db.isEmpty()).isTrue(); - assertThat(db.keys().size()).isEqualTo(0); + assertThat(db.keys().hasNext()).isFalse(); assertThat(db.isLocked()).isFalse(); } } @@ -744,11 +754,11 @@ public void testApproximateDBSize() { @Test public void testKeys() { // keys shouldn't be null even when empty - Set keys = db.keys(); + Iterator keys = db.keys(); assertThat(db.isLocked()).isFalse(); assertThat(db.isEmpty()).isTrue(); assertThat(keys).isNotNull(); - assertThat(keys.size()).isEqualTo(0); + assertThat(keys.hasNext()).isFalse(); // checking after put db.put(k1, v1); @@ -758,16 +768,19 @@ public void testKeys() { keys = db.keys(); assertThat(db.isLocked()).isFalse(); - assertThat(keys.size()).isEqualTo(2); // because of byte[], set.contains() does not work as expected - int count = 0; - for (byte[] k : keys) { + int countIn = 0, countOut = 0; + while (keys.hasNext()) { + byte[] k = keys.next(); if (Arrays.equals(k1, k) || Arrays.equals(k2, k)) { - count++; + countIn++; + } else { + countOut++; } } - assertThat(count).isEqualTo(2); + assertThat(countIn).isEqualTo(2); + assertThat(countOut).isEqualTo(0); // checking after delete db.delete(k2); @@ -775,15 +788,19 @@ public void testKeys() { keys = db.keys(); assertThat(db.isLocked()).isFalse(); - assertThat(keys.size()).isEqualTo(1); - count = 0; - for (byte[] k : keys) { + countIn = 0; + countOut = 0; + while (keys.hasNext()) { + byte[] k = keys.next(); if (Arrays.equals(k1, k)) { - count++; + countIn++; + } else { + countOut++; } } - assertThat(count).isEqualTo(1); + assertThat(countIn).isEqualTo(1); + assertThat(countOut).isEqualTo(0); // checking after putBatch Map ops = new HashMap<>(); @@ -794,22 +811,26 @@ public void testKeys() { keys = db.keys(); assertThat(db.isLocked()).isFalse(); - assertThat(keys.size()).isEqualTo(2); - count = 0; - for (byte[] k : keys) { + countIn = 0; + countOut = 0; + while (keys.hasNext()) { + byte[] k = keys.next(); if (Arrays.equals(k2, k) || Arrays.equals(k3, k)) { - count++; + countIn++; + } else { + countOut++; } } - assertThat(count).isEqualTo(2); + assertThat(countIn).isEqualTo(2); + assertThat(countOut).isEqualTo(0); // checking after deleteBatch db.deleteBatch(ops.keySet()); keys = db.keys(); assertThat(db.isLocked()).isFalse(); - assertThat(keys.size()).isEqualTo(0); + assertThat(keys.hasNext()).isFalse(); } @Test @@ -874,7 +895,7 @@ public void testAutoCommitDisabled() throws InterruptedException { // ensure lack of persistence assertThat(db.get(k1).isPresent()).isFalse(); assertThat(db.isEmpty()).isTrue(); - assertThat(db.keys().size()).isEqualTo(0); + assertThat(db.keys().hasNext()).isFalse(); assertThat(db.isLocked()).isFalse(); // deleting data @@ -895,7 +916,7 @@ public void testAutoCommitDisabled() throws InterruptedException { // ensure lack of persistence of delete assertThat(db.get(k1).get()).isEqualTo(v1); assertThat(db.isEmpty()).isFalse(); - assertThat(db.keys().size()).isEqualTo(1); + assertThat(count(db.keys())).isEqualTo(1); assertThat(db.isLocked()).isFalse(); // batch update @@ -926,7 +947,7 @@ public void testAutoCommitDisabled() throws InterruptedException { assertThat(db.get(k2).get()).isEqualTo(v2); assertThat(db.get(k3).get()).isEqualTo(v3); assertThat(db.isEmpty()).isFalse(); - assertThat(db.keys().size()).isEqualTo(2); + assertThat(count(db.keys())).isEqualTo(2); assertThat(db.isLocked()).isFalse(); // batch delete @@ -945,7 +966,7 @@ public void testAutoCommitDisabled() throws InterruptedException { assertThat(db.get(k2).get()).isEqualTo(v2); assertThat(db.get(k3).get()).isEqualTo(v3); assertThat(db.isEmpty()).isFalse(); - assertThat(db.keys().size()).isEqualTo(2); + assertThat(count(db.keys())).isEqualTo(2); assertThat(db.isLocked()).isFalse(); } } diff --git a/modMcf/src/org/aion/mcf/db/ContractDetailsCacheImpl.java b/modMcf/src/org/aion/mcf/db/ContractDetailsCacheImpl.java index 0cb67c7d56..980874dea3 100644 --- a/modMcf/src/org/aion/mcf/db/ContractDetailsCacheImpl.java +++ b/modMcf/src/org/aion/mcf/db/ContractDetailsCacheImpl.java @@ -117,6 +117,12 @@ public void decode(byte[] rlpCode) { throw new RuntimeException("Not supported by this implementation."); } + /** This method is not supported. */ + @Override + public void decode(byte[] rlpCode, boolean fastCheck) { + throw new RuntimeException("Not supported by this implementation."); + } + /** This method is not supported. */ @Override public byte[] getEncoded() { @@ -245,4 +251,5 @@ public IContractDetails getSnapshotTo(byte[] hash) { public void setDataSource(IByteArrayKeyValueStore dataSource) { throw new UnsupportedOperationException("Can't set datasource in cache implementation."); } + } diff --git a/modMcf/src/org/aion/mcf/db/DetailsDataStore.java b/modMcf/src/org/aion/mcf/db/DetailsDataStore.java index e9816f59fa..4dee2520df 100644 --- a/modMcf/src/org/aion/mcf/db/DetailsDataStore.java +++ b/modMcf/src/org/aion/mcf/db/DetailsDataStore.java @@ -36,10 +36,8 @@ import static org.aion.base.util.ByteArrayWrapper.wrap; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; -import java.util.List; +import java.util.Iterator; import java.util.Optional; import java.util.Set; import org.aion.base.db.IByteArrayKeyValueDatabase; @@ -99,9 +97,7 @@ public synchronized IContractDetails get(byte[] key) { // Check to see if we have to remove it. // If it isn't in removes set, we add it to removes set. - if (!removes.contains(wrappedKey)) { - removes.add(wrappedKey); - } + removes.add(wrappedKey); return null; } @@ -146,8 +142,9 @@ private long flushInternal() { syncLargeStorage(); // Get everything from the cache and calculate the size. - Set keysFromSource = detailsSrc.keys(); - for (byte[] keyInSource : keysFromSource) { + Iterator keysFromSource = detailsSrc.keys(); + while (keysFromSource.hasNext()) { + byte[] keyInSource = keysFromSource.next(); // Fetch the value given the keys. Optional valFromKey = detailsSrc.get(keyInSource); @@ -164,8 +161,9 @@ private long flushInternal() { public void syncLargeStorage() { - Set keysFromSource = detailsSrc.keys(); - for (byte[] keyInSource : keysFromSource) { + Iterator keysFromSource = detailsSrc.keys(); + while (keysFromSource.hasNext()) { + byte[] keyInSource = keysFromSource.next(); // Fetch the value given the keys. Optional rawDetails = detailsSrc.get(keyInSource); @@ -178,8 +176,8 @@ public void syncLargeStorage() { // Decode the details. IContractDetails detailsImpl = repoConfig.contractDetailsImpl(); detailsImpl.setDataSource(storageDSPrune); - detailsImpl.decode(rawDetails.get()); // We can safely get as we - // checked if it is present. + detailsImpl.decode(rawDetails.get(), true); + // We can safely get as we checked if it is present. // IContractDetails details = entry.getValue(); detailsImpl.syncStorage(); @@ -190,13 +188,36 @@ public JournalPruneDataSource getStorageDSPrune() { return storageDSPrune; } - public synchronized Set keys() { - // TODO - @yao do we wanted a sorted set? - Set keys = new HashSet<>(); - for (byte[] key : detailsSrc.keys()) { - keys.add(wrap(key)); + public synchronized Iterator keys() { + return new DetailsIteratorWrapper(detailsSrc.keys()); + } + + /** + * A wrapper for the iterator needed by {@link DetailsDataStore} conforming to the {@link + * Iterator} interface. + * + * @author Alexandra Roatis + */ + private class DetailsIteratorWrapper implements Iterator { + private Iterator sourceIterator; + + /** + * @implNote Building two wrappers for the same {@link Iterator} will lead to inconsistent + * behavior. + */ + DetailsIteratorWrapper(final Iterator sourceIterator) { + this.sourceIterator = sourceIterator; + } + + @Override + public boolean hasNext() { + return sourceIterator.hasNext(); + } + + @Override + public ByteArrayWrapper next() { + return wrap(sourceIterator.next()); } - return keys; } public synchronized void close() { @@ -207,15 +228,4 @@ public synchronized void close() { throw new RuntimeException("error closing db"); } } - - public static List dumpKeys(IByteArrayKeyValueDatabase ds) { - ArrayList keys = new ArrayList<>(); - - for (byte[] key : ds.keys()) { - keys.add(wrap(key)); - } - - Collections.sort(keys); - return keys; - } } diff --git a/modMcf/src/org/aion/mcf/ds/ArchivedDataSource.java b/modMcf/src/org/aion/mcf/ds/ArchivedDataSource.java index d83b13bbfc..2069beaecc 100644 --- a/modMcf/src/org/aion/mcf/ds/ArchivedDataSource.java +++ b/modMcf/src/org/aion/mcf/ds/ArchivedDataSource.java @@ -24,9 +24,9 @@ package org.aion.mcf.ds; import java.util.Collection; +import java.util.Iterator; import java.util.Map; import java.util.Optional; -import java.util.Set; import org.aion.base.db.IByteArrayKeyValueDatabase; import org.aion.base.db.IByteArrayKeyValueStore; @@ -50,7 +50,7 @@ public boolean isEmpty() { } @Override - public Set keys() { + public Iterator keys() { return data.keys(); } diff --git a/modMcf/src/org/aion/mcf/ds/XorDataSource.java b/modMcf/src/org/aion/mcf/ds/XorDataSource.java index 4526259a09..a2259e3439 100644 --- a/modMcf/src/org/aion/mcf/ds/XorDataSource.java +++ b/modMcf/src/org/aion/mcf/ds/XorDataSource.java @@ -36,17 +36,16 @@ import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; +import java.util.Iterator; import java.util.Map; import java.util.Optional; -import java.util.Set; import org.aion.base.db.IByteArrayKeyValueStore; import org.aion.base.util.ByteArrayWrapper; import org.aion.base.util.ByteUtil; public class XorDataSource implements IByteArrayKeyValueStore { - IByteArrayKeyValueStore source; - byte[] subKey; + private final IByteArrayKeyValueStore source; + private final byte[] subKey; public XorDataSource(IByteArrayKeyValueStore source, byte[] subKey) { this.source = source; @@ -73,13 +72,36 @@ public void delete(byte[] key) { } @Override - public Set keys() { - Set keys = source.keys(); - Set ret = new HashSet<>(keys.size()); - for (byte[] key : keys) { - ret.add(convertKey(key)); + public Iterator keys() { + return new XorDSIteratorWrapper(source.keys()); + } + + /** + * A wrapper for the iterator needed by {@link XorDataSource} conforming to the {@link Iterator} + * interface. + * + * @author Alexandra Roatis + */ + private class XorDSIteratorWrapper implements Iterator { + final Iterator sourceIterator; + + /** + * @implNote Building two wrappers for the same {@link Iterator} will lead to inconsistent + * behavior. + */ + XorDSIteratorWrapper(final Iterator sourceIterator) { + this.sourceIterator = sourceIterator; + } + + @Override + public boolean hasNext() { + return sourceIterator.hasNext(); + } + + @Override + public byte[] next() { + return convertKey(sourceIterator.next()); } - return ret; } @Override diff --git a/modMcf/src/org/aion/mcf/trie/Cache.java b/modMcf/src/org/aion/mcf/trie/Cache.java index 42a4286a35..a85e254f7c 100644 --- a/modMcf/src/org/aion/mcf/trie/Cache.java +++ b/modMcf/src/org/aion/mcf/trie/Cache.java @@ -229,7 +229,9 @@ public synchronized void setDB(IByteArrayKeyValueStore kvds) { } } } else { - for (byte[] key : this.dataSource.keys()) { + Iterator iterator = dataSource.keys(); + while (iterator.hasNext()) { + byte[] key = iterator.next(); rows.put(key, this.dataSource.get(key).get()); } diff --git a/modMcf/src/org/aion/mcf/trie/JournalPruneDataSource.java b/modMcf/src/org/aion/mcf/trie/JournalPruneDataSource.java index 4463bae7d5..081029ecf3 100644 --- a/modMcf/src/org/aion/mcf/trie/JournalPruneDataSource.java +++ b/modMcf/src/org/aion/mcf/trie/JournalPruneDataSource.java @@ -38,6 +38,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -348,7 +349,7 @@ public Optional get(byte[] key) { } } - public Set keys() { + public Iterator keys() { lock.readLock().lock(); try { return src.keys(); diff --git a/modMcf/test/org/aion/mcf/trie/JournalPruneDataSourceTest.java b/modMcf/test/org/aion/mcf/trie/JournalPruneDataSourceTest.java index ca516300c3..f86c36d2f7 100644 --- a/modMcf/test/org/aion/mcf/trie/JournalPruneDataSourceTest.java +++ b/modMcf/test/org/aion/mcf/trie/JournalPruneDataSourceTest.java @@ -29,10 +29,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; -import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -49,7 +49,7 @@ public class JournalPruneDataSourceTest { private static final String dbName = "TestDB"; - private static IByteArrayKeyValueDatabase source_db = DatabaseFactory.connect(dbName); + private static final IByteArrayKeyValueDatabase source_db = DatabaseFactory.connect(dbName); private static JournalPruneDataSource db; private static final byte[] k1 = "key1".getBytes(); @@ -240,10 +240,10 @@ public void testKeys_woPrune() { db.setPruneEnabled(false); // keys shouldn't be null even when empty - Set keys = db.keys(); + Iterator keys = db.keys(); assertThat(db.isEmpty()).isTrue(); assertThat(keys).isNotNull(); - assertThat(keys.size()).isEqualTo(0); + assertThat(count(keys)).isEqualTo(0); // checking after put db.put(k1, v1); @@ -251,15 +251,13 @@ public void testKeys_woPrune() { assertThat(db.get(k1).get()).isEqualTo(v1); assertThat(db.get(k2).get()).isEqualTo(v2); - keys = db.keys(); - assertThat(keys.size()).isEqualTo(2); + assertThat(count(db.keys())).isEqualTo(2); // checking after delete db.delete(k2); assertThat(db.get(k2).isPresent()).isTrue(); - keys = db.keys(); - assertThat(keys.size()).isEqualTo(2); + assertThat(count(db.keys())).isEqualTo(2); // checking after putBatch Map ops = new HashMap<>(); @@ -268,20 +266,31 @@ public void testKeys_woPrune() { ops.put(k3, v3); db.putBatch(ops); - keys = db.keys(); - assertThat(keys.size()).isEqualTo(3); + List del = new ArrayList<>(); + del.add(k1); + db.deleteBatch(del); + + assertThat(count(db.keys())).isEqualTo(3); // checking after deleteBatch db.deleteBatch(ops.keySet()); - keys = db.keys(); - assertThat(keys.size()).isEqualTo(3); + assertThat(count(db.keys())).isEqualTo(3); // ensure no cached values assertThat(db.getInsertedKeysCount()).isEqualTo(0); assertThat(db.getDeletedKeysCount()).isEqualTo(0); } + private static int count(Iterator keys) { + int size = 0; + while (keys.hasNext()) { + size++; + keys.next(); + } + return size; + } + @Test public void testIsEmpty_woPrune() { db.setPruneEnabled(false); @@ -512,10 +521,10 @@ public void testKeys_wPrune() { db.setPruneEnabled(true); // keys shouldn't be null even when empty - Set keys = db.keys(); + Iterator keys = db.keys(); assertThat(db.isEmpty()).isTrue(); assertThat(keys).isNotNull(); - assertThat(keys.size()).isEqualTo(0); + assertThat(count(keys)).isEqualTo(0); // checking after put db.put(k1, v1); @@ -523,15 +532,13 @@ public void testKeys_wPrune() { assertThat(db.get(k1).get()).isEqualTo(v1); assertThat(db.get(k2).get()).isEqualTo(v2); - keys = db.keys(); - assertThat(keys.size()).isEqualTo(2); + assertThat(count(db.keys())).isEqualTo(2); // checking after delete db.delete(k2); assertThat(db.get(k2).isPresent()).isTrue(); - keys = db.keys(); - assertThat(keys.size()).isEqualTo(2); + assertThat(count(db.keys())).isEqualTo(2); // checking after putBatch Map ops = new HashMap<>(); @@ -540,14 +547,16 @@ public void testKeys_wPrune() { ops.put(k3, v3); db.putBatch(ops); - keys = db.keys(); - assertThat(keys.size()).isEqualTo(3); + List del = new ArrayList<>(); + del.add(k1); + db.deleteBatch(del); + + assertThat(count(db.keys())).isEqualTo(3); // checking after deleteBatch db.deleteBatch(ops.keySet()); - keys = db.keys(); - assertThat(keys.size()).isEqualTo(3); + assertThat(count(db.keys())).isEqualTo(3); // ensure no cached values assertThat(db.getInsertedKeysCount()).isEqualTo(3); @@ -776,7 +785,7 @@ public void testDeleteBatch_wNullKey() { db.deleteBatch(list); } - public static byte[] randomBytes(int length) { + private static byte[] randomBytes(int length) { byte[] result = new byte[length]; new Random().nextBytes(result); return result; @@ -804,10 +813,10 @@ private void addThread_IsEmpty(List threads, JournalPruneDataSource db private void addThread_Keys(List threads, JournalPruneDataSource db) { threads.add( () -> { - Set keys = db.keys(); + Iterator keys = db.keys(); if (DISPLAY_MESSAGES) { System.out.println( - Thread.currentThread().getName() + ": #keys = " + keys.size()); + Thread.currentThread().getName() + ": #keys = " + count(keys)); } }); } @@ -1000,7 +1009,7 @@ public void testConcurrentPut() throws InterruptedException { assertConcurrent("Testing put(...) ", threads, TIME_OUT); // check that all values were added - assertThat(db.keys().size()).isEqualTo(CONCURRENT_THREADS); + assertThat(count(db.keys())).isEqualTo(CONCURRENT_THREADS); // ensuring close db.close(); @@ -1023,7 +1032,7 @@ public void testConcurrentPutBatch() throws InterruptedException { assertConcurrent("Testing putBatch(...) ", threads, TIME_OUT); // check that all values were added - assertThat(db.keys().size()).isEqualTo(3 * CONCURRENT_THREADS); + assertThat(count(db.keys())).isEqualTo(3 * CONCURRENT_THREADS); // ensuring close db.close(); @@ -1082,7 +1091,7 @@ public void testConcurrentUpdate() throws InterruptedException { * href="https://github.com/junit-team/junit4/wiki/multithreaded-code-and-concurrency">JUnit * Wiki on multithreaded code and concurrency */ - public static void assertConcurrent( + private static void assertConcurrent( final String message, final List runnables, final int maxTimeoutSeconds) @@ -1397,7 +1406,7 @@ public void pruningTest_wFork_onCurrentLevel() { db.storeBlockChanges(b0, 0); assertThat(db.getBlockUpdates().size()).isEqualTo(1); - assertThat(source_db.keys().size()).isEqualTo(3); + assertThat(count(source_db.keys())).isEqualTo(3); assertThat(source_db.get(k1).get()).isEqualTo(v1); assertThat(source_db.get(k2).get()).isEqualTo(v2); assertThat(source_db.get(k3).get()).isEqualTo(v3); @@ -1409,7 +1418,7 @@ public void pruningTest_wFork_onCurrentLevel() { db.storeBlockChanges(b1, 1); assertThat(db.getBlockUpdates().size()).isEqualTo(2); - assertThat(source_db.keys().size()).isEqualTo(4); + assertThat(count(source_db.keys())).isEqualTo(4); assertThat(source_db.get(k1).get()).isEqualTo(v2); assertThat(source_db.get(k2).get()).isEqualTo(v2); assertThat(source_db.get(k3).get()).isEqualTo(v3); @@ -1424,7 +1433,7 @@ public void pruningTest_wFork_onCurrentLevel() { db.storeBlockChanges(b2, 2); assertThat(db.getBlockUpdates().size()).isEqualTo(3); - assertThat(source_db.keys().size()).isEqualTo(5); + assertThat(count(source_db.keys())).isEqualTo(5); assertThat(source_db.get(k1).get()).isEqualTo(v4); assertThat(source_db.get(k2).get()).isEqualTo(v3); assertThat(source_db.get(k3).get()).isEqualTo(v3); @@ -1439,7 +1448,7 @@ public void pruningTest_wFork_onCurrentLevel() { db.storeBlockChanges(b3, 2); assertThat(db.getBlockUpdates().size()).isEqualTo(4); - assertThat(source_db.keys().size()).isEqualTo(6); + assertThat(count(source_db.keys())).isEqualTo(6); assertThat(source_db.get(k1).get()).isEqualTo(v3); assertThat(source_db.get(k2).get()).isEqualTo(v4); assertThat(source_db.get(k3).get()).isEqualTo(v3); @@ -1450,19 +1459,19 @@ public void pruningTest_wFork_onCurrentLevel() { // prune block b0 db.prune(b0, 0); assertThat(db.getBlockUpdates().size()).isEqualTo(3); - assertThat(source_db.keys().size()).isEqualTo(6); + assertThat(count(source_db.keys())).isEqualTo(6); // prune block b1 db.prune(b1, 1); assertThat(db.getBlockUpdates().size()).isEqualTo(2); - assertThat(source_db.keys().size()).isEqualTo(6); + assertThat(count(source_db.keys())).isEqualTo(6); // prune block b3 at level 2 (should be called for main chain block) db.prune(b3, 2); // also removed the updates for block b2 assertThat(db.getBlockUpdates().size()).isEqualTo(0); - assertThat(source_db.keys().size()).isEqualTo(4); + assertThat(count(source_db.keys())).isEqualTo(4); assertThat(source_db.get(k1).get()).isEqualTo(v3); assertThat(source_db.get(k2).get()).isEqualTo(v4); assertThat(source_db.get(k3).get()).isEqualTo(v3); @@ -1482,7 +1491,7 @@ public void pruningTest_wFork_onPastLevel() { db.storeBlockChanges(b0, 0); assertThat(db.getBlockUpdates().size()).isEqualTo(1); - assertThat(source_db.keys().size()).isEqualTo(3); + assertThat(count(source_db.keys())).isEqualTo(3); assertThat(source_db.get(k1).get()).isEqualTo(v1); assertThat(source_db.get(k2).get()).isEqualTo(v2); assertThat(source_db.get(k3).get()).isEqualTo(v3); @@ -1494,7 +1503,7 @@ public void pruningTest_wFork_onPastLevel() { db.storeBlockChanges(b1, 1); assertThat(db.getBlockUpdates().size()).isEqualTo(2); - assertThat(source_db.keys().size()).isEqualTo(4); + assertThat(count(source_db.keys())).isEqualTo(4); assertThat(source_db.get(k1).get()).isEqualTo(v2); assertThat(source_db.get(k2).get()).isEqualTo(v2); assertThat(source_db.get(k3).get()).isEqualTo(v3); @@ -1508,7 +1517,7 @@ public void pruningTest_wFork_onPastLevel() { db.storeBlockChanges(b2, 1); assertThat(db.getBlockUpdates().size()).isEqualTo(3); - assertThat(source_db.keys().size()).isEqualTo(5); + assertThat(count(source_db.keys())).isEqualTo(5); assertThat(source_db.get(k1).get()).isEqualTo(v4); assertThat(source_db.get(k2).get()).isEqualTo(v3); assertThat(source_db.get(k3).get()).isEqualTo(v3); @@ -1522,7 +1531,7 @@ public void pruningTest_wFork_onPastLevel() { db.storeBlockChanges(b3, 2); assertThat(db.getBlockUpdates().size()).isEqualTo(4); - assertThat(source_db.keys().size()).isEqualTo(6); + assertThat(count(source_db.keys())).isEqualTo(6); assertThat(source_db.get(k1).get()).isEqualTo(v3); assertThat(source_db.get(k2).get()).isEqualTo(v4); assertThat(source_db.get(k3).get()).isEqualTo(v3); @@ -1533,12 +1542,12 @@ public void pruningTest_wFork_onPastLevel() { // prune block b0 db.prune(b0, 0); assertThat(db.getBlockUpdates().size()).isEqualTo(3); - assertThat(source_db.keys().size()).isEqualTo(6); + assertThat(count(source_db.keys())).isEqualTo(6); // prune block b2 at level 1 : (should be called for main chain block) db.prune(b2, 1); assertThat(db.getBlockUpdates().size()).isEqualTo(1); - assertThat(source_db.keys().size()).isEqualTo(4); + assertThat(count(source_db.keys())).isEqualTo(4); assertThat(source_db.get(k1).get()).isEqualTo(v3); assertThat(source_db.get(k2).get()).isEqualTo(v4); assertThat(source_db.get(k3).isPresent()).isFalse(); @@ -1551,7 +1560,7 @@ public void pruningTest_wFork_onPastLevel() { // also removed the updates for block b2 assertThat(db.getBlockUpdates().size()).isEqualTo(0); - assertThat(source_db.keys().size()).isEqualTo(4); + assertThat(count(source_db.keys())).isEqualTo(4); assertThat(source_db.get(k1).get()).isEqualTo(v3); assertThat(source_db.get(k2).get()).isEqualTo(v4); assertThat(source_db.get(k3).isPresent()).isFalse();