From 7c6235f3e7b3f16d553b00b4bc06735e8e31d343 Mon Sep 17 00:00:00 2001 From: halibobo1205 Date: Wed, 30 Aug 2023 20:43:46 +0800 Subject: [PATCH] fix getting last account address --- .../core/db/common/iterator/DBIterator.java | 54 +++++++++ .../db/common/iterator/RockStoreIterator.java | 50 ++++++-- .../db/common/iterator/StoreIterator.java | 47 ++++++-- .../tron/core/service/RewardCalService.java | 18 ++- .../java/org/tron/core/db/DBIteratorTest.java | 110 ++++++++++++------ 5 files changed, 216 insertions(+), 63 deletions(-) diff --git a/chainbase/src/main/java/org/tron/core/db/common/iterator/DBIterator.java b/chainbase/src/main/java/org/tron/core/db/common/iterator/DBIterator.java index 593c8d460c3..ec8d7fd85be 100755 --- a/chainbase/src/main/java/org/tron/core/db/common/iterator/DBIterator.java +++ b/chainbase/src/main/java/org/tron/core/db/common/iterator/DBIterator.java @@ -6,6 +6,7 @@ import java.io.Closeable; import java.util.Iterator; import java.util.Map.Entry; +import java.util.NoSuchElementException; public interface DBIterator extends Iterator>, AutoCloseable, Closeable { @@ -20,4 +21,57 @@ public interface DBIterator extends Iterator>, AutoCloseab this.seek(afterThat == null ? key : afterThat); return Iterators.filter(this, entry -> Bytes.indexOf(entry.getKey(), key) == 0); } + + /** + * An iterator is either positioned at a key/value pair, or + * not valid. This method returns true iff the iterator is valid. + * + * REQUIRES: iterator not closed + * + * @throws IllegalStateException if the iterator is closed. + * @return an iterator is either positioned at a key/value pair + */ + boolean valid(); + + /** + * The underlying storage for + * the returned slice is valid only until the next modification of + * the iterator. + * + * REQUIRES: valid() && !closed + * + * @throws IllegalStateException if the iterator is closed. + * @throws NoSuchElementException if the iterator is not valid. + * + * @return the key for the current entry + */ + byte[] getKey(); + + /** + * The underlying storage for + * the returned slice is valid only until the next modification of + * the iterator. + * + * REQUIRES: valid() && !closed + * + * @throws IllegalStateException if the iterator is closed. + * @throws NoSuchElementException if the iterator is not valid. + * + * @return the value for the current entry + */ + byte[] getValue(); + + /** + * @throws IllegalStateException if the iterator is closed. + */ + void checkState(); + + /** + * @throws NoSuchElementException if the iterator is not valid. + */ + default void checkValid() { + if (!valid()) { + throw new NoSuchElementException(); + } + } } diff --git a/chainbase/src/main/java/org/tron/core/db/common/iterator/RockStoreIterator.java b/chainbase/src/main/java/org/tron/core/db/common/iterator/RockStoreIterator.java index 387848cf669..1438b9247b5 100644 --- a/chainbase/src/main/java/org/tron/core/db/common/iterator/RockStoreIterator.java +++ b/chainbase/src/main/java/org/tron/core/db/common/iterator/RockStoreIterator.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.Map.Entry; import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; import lombok.extern.slf4j.Slf4j; import org.rocksdb.RocksIterator; @@ -13,7 +14,7 @@ public final class RockStoreIterator implements DBIterator { private final RocksIterator dbIterator; private boolean first = true; - private boolean valid = true; + private final AtomicBoolean close = new AtomicBoolean(false); public RockStoreIterator(RocksIterator dbIterator) { this.dbIterator = dbIterator; @@ -21,15 +22,14 @@ public RockStoreIterator(RocksIterator dbIterator) { @Override public void close() throws IOException { - if (valid) { + if (close.compareAndSet(false, true)) { dbIterator.close(); - valid = false; } } @Override public boolean hasNext() { - if (!valid) { + if (close.get()) { return false; } boolean hasNext = false; @@ -40,13 +40,12 @@ public boolean hasNext() { first = false; } if (!(hasNext = dbIterator.isValid())) { // false is last item - dbIterator.close(); - valid = false; + close(); } } catch (Exception e) { logger.error(e.getMessage(), e); try { - dbIterator.close(); + close(); } catch (Exception e1) { logger.error(e.getMessage(), e); } @@ -56,7 +55,7 @@ public boolean hasNext() { @Override public Entry next() { - if (!valid) { + if (close.get()) { throw new NoSuchElementException(); } byte[] key = dbIterator.key(); @@ -82,16 +81,49 @@ public byte[] setValue(byte[] value) { @Override public void seek(byte[] key) { + checkState(); dbIterator.seek(key); + this.first = false; } @Override public void seekToFirst() { + checkState(); dbIterator.seekToFirst(); + this.first = false; } @Override public void seekToLast() { + checkState(); dbIterator.seekToLast(); + this.first = false; } -} \ No newline at end of file + + @Override + public boolean valid() { + checkState(); + return dbIterator.isValid(); + } + + @Override + public byte[] getKey() { + checkState(); + checkValid(); + return dbIterator.key(); + } + + @Override + public byte[] getValue() { + checkState(); + checkValid(); + return dbIterator.value(); + } + + @Override + public void checkState() { + if (close.get()) { + throw new IllegalStateException("iterator has been closed"); + } + } +} diff --git a/chainbase/src/main/java/org/tron/core/db/common/iterator/StoreIterator.java b/chainbase/src/main/java/org/tron/core/db/common/iterator/StoreIterator.java index 80fd3ca8191..4c635660ea6 100755 --- a/chainbase/src/main/java/org/tron/core/db/common/iterator/StoreIterator.java +++ b/chainbase/src/main/java/org/tron/core/db/common/iterator/StoreIterator.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.Map.Entry; import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; import lombok.extern.slf4j.Slf4j; import org.iq80.leveldb.DBIterator; @@ -13,7 +14,7 @@ public final class StoreIterator implements org.tron.core.db.common.iterator.DBI private final DBIterator dbIterator; private boolean first = true; - private boolean valid = true; + private final AtomicBoolean close = new AtomicBoolean(false); public StoreIterator(DBIterator dbIterator) { this.dbIterator = dbIterator; @@ -21,15 +22,14 @@ public StoreIterator(DBIterator dbIterator) { @Override public void close() throws IOException { - if (valid) { + if (close.compareAndSet(false, true)) { dbIterator.close(); - valid = false; } } @Override public boolean hasNext() { - if (!valid) { + if (close.get()) { return false; } @@ -42,8 +42,7 @@ public boolean hasNext() { } if (!(hasNext = dbIterator.hasNext())) { // false is last item - dbIterator.close(); - valid = false; + close(); } } catch (Exception e) { logger.error(e.getMessage(), e); @@ -54,7 +53,7 @@ public boolean hasNext() { @Override public Entry next() { - if (!valid) { + if (close.get()) { throw new NoSuchElementException(); } return dbIterator.next(); @@ -67,16 +66,50 @@ public void remove() { @Override public void seek(byte[] key) { + checkState(); dbIterator.seek(key); + this.first = false; } @Override public void seekToFirst() { + checkState(); dbIterator.seekToFirst(); + this.first = false; } @Override public void seekToLast() { + checkState(); dbIterator.seekToLast(); + this.first = false; + } + + @Override + public boolean valid() { + checkState(); + return dbIterator.hasNext(); + } + + @Override + public byte[] getKey() { + checkState(); + checkValid(); + return dbIterator.peekNext().getKey(); + } + + @Override + public byte[] getValue() { + checkState(); + checkValid(); + return dbIterator.peekNext().getValue(); + } + + @Override + public void checkState() { + if (close.get()) { + throw new IllegalStateException("iterator has been closed"); + } } } + diff --git a/chainbase/src/main/java/org/tron/core/service/RewardCalService.java b/chainbase/src/main/java/org/tron/core/service/RewardCalService.java index b3f5164e5bf..12507ee5bad 100644 --- a/chainbase/src/main/java/org/tron/core/service/RewardCalService.java +++ b/chainbase/src/main/java/org/tron/core/service/RewardCalService.java @@ -75,13 +75,7 @@ private void destroy() { } public void calReward() throws IOException { - try (DBIterator iterator = rewardCacheStore.iterator()) { - iterator.seekToLast(); - if (iterator.hasNext()) { - byte[] key = iterator.next().getKey(); - System.arraycopy(key, 0, lastAccount, 0, ADDRESS_SIZE); - } - } + initLastAccount(); es.submit(this::startRewardCal); } @@ -93,14 +87,18 @@ public void calRewardForTest() throws IOException { return; } accountIterator = (DBIterator) accountStore.getDb().iterator(); + initLastAccount(); + startRewardCal(); + } + + private void initLastAccount() throws IOException { try (DBIterator iterator = rewardCacheStore.iterator()) { iterator.seekToLast(); - if (iterator.hasNext()) { - byte[] key = iterator.next().getKey(); + if (iterator.valid()) { + byte[] key = iterator.getKey(); System.arraycopy(key, 0, lastAccount, 0, ADDRESS_SIZE); } } - startRewardCal(); } diff --git a/framework/src/test/java/org/tron/core/db/DBIteratorTest.java b/framework/src/test/java/org/tron/core/db/DBIteratorTest.java index a55d1b04d4d..b4f7ca424c0 100644 --- a/framework/src/test/java/org/tron/core/db/DBIteratorTest.java +++ b/framework/src/test/java/org/tron/core/db/DBIteratorTest.java @@ -5,86 +5,122 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; import java.util.NoSuchElementException; import org.iq80.leveldb.DB; import org.iq80.leveldb.Options; -import org.junit.AfterClass; import org.junit.Assert; -import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; -import org.tron.common.utils.FileUtil; import org.tron.core.db.common.iterator.RockStoreIterator; import org.tron.core.db.common.iterator.StoreIterator; public class DBIteratorTest { - @BeforeClass - public static void init() { - File file = Paths.get("database-iterator").toFile(); - if (!file.exists()) { - file.mkdirs(); - } - } + @ClassRule + public static final TemporaryFolder temporaryFolder = new TemporaryFolder(); - @AfterClass - public static void clear() { - File file = Paths.get("database-iterator").toFile(); - if (file.exists()) { - FileUtil.deleteDir(Paths.get("database-iterator").toFile()); - } - } + @Rule + public final ExpectedException thrown = ExpectedException.none(); @Test public void testLevelDb() throws IOException { - File file = new File("database-iterator/testLevelDb"); - try { - DB db = factory.open(file, new Options().createIfMissing(true)); + File file = temporaryFolder.newFolder(); + try (DB db = factory.open(file, new Options().createIfMissing(true))) { db.put("1".getBytes(StandardCharsets.UTF_8), "1".getBytes(StandardCharsets.UTF_8)); db.put("2".getBytes(StandardCharsets.UTF_8), "2".getBytes(StandardCharsets.UTF_8)); - StoreIterator iterator = new StoreIterator(db.iterator()); + StoreIterator iterator = new StoreIterator(db.iterator()); + iterator.seekToFirst(); + Assert.assertArrayEquals("1".getBytes(StandardCharsets.UTF_8), iterator.getKey()); + Assert.assertArrayEquals("1".getBytes(StandardCharsets.UTF_8), iterator.next().getValue()); + Assert.assertTrue(iterator.hasNext()); + + Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.getValue()); + Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.next().getKey()); + Assert.assertFalse(iterator.hasNext()); + + try { + iterator.seekToLast(); + } catch (Exception e) { + Assert.assertTrue(e instanceof IllegalStateException); + } + + iterator = new StoreIterator(db.iterator()); + iterator.seekToLast(); + Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.getKey()); + Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.getValue()); + iterator.seekToFirst(); while (iterator.hasNext()) { iterator.next(); } Assert.assertFalse(iterator.hasNext()); try { - iterator.next(); + iterator.getKey(); } catch (Exception e) { - Assert.assertTrue(e instanceof NoSuchElementException); + Assert.assertTrue(e instanceof IllegalStateException); } - db.close(); - } finally { - factory.destroy(file, new Options()); + try { + iterator.getValue(); + } catch (Exception e) { + Assert.assertTrue(e instanceof IllegalStateException); + } + thrown.expect(NoSuchElementException.class); + iterator.next(); } } @Test - public void testRocksDb() throws RocksDBException { - File file = new File("database-iterator/testRocksDb"); - try (org.rocksdb.Options options = new org.rocksdb.Options().setCreateIfMissing(true)) { - RocksDB db = RocksDB.open(options, file.toString()); + public void testRocksDb() throws RocksDBException, IOException { + File file = temporaryFolder.newFolder(); + try (org.rocksdb.Options options = new org.rocksdb.Options().setCreateIfMissing(true); + RocksDB db = RocksDB.open(options, file.toString())) { db.put("1".getBytes(StandardCharsets.UTF_8), "1".getBytes(StandardCharsets.UTF_8)); db.put("2".getBytes(StandardCharsets.UTF_8), "2".getBytes(StandardCharsets.UTF_8)); RockStoreIterator iterator = new RockStoreIterator(db.newIterator()); + iterator.seekToFirst(); + Assert.assertArrayEquals("1".getBytes(StandardCharsets.UTF_8), iterator.getKey()); + Assert.assertArrayEquals("1".getBytes(StandardCharsets.UTF_8), iterator.next().getValue()); + Assert.assertTrue(iterator.hasNext()); + + Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.getValue()); + Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.next().getKey()); + Assert.assertFalse(iterator.hasNext()); + + try { + iterator.seekToLast(); + } catch (Exception e) { + Assert.assertTrue(e instanceof IllegalStateException); + } + + iterator = new RockStoreIterator(db.newIterator()); + iterator.seekToLast(); + Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.getKey()); + Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.getValue()); + iterator.seekToFirst(); while (iterator.hasNext()) { iterator.next(); } Assert.assertFalse(iterator.hasNext()); try { - iterator.next(); + iterator.getKey(); + } catch (Exception e) { + Assert.assertTrue(e instanceof IllegalStateException); + } + try { + iterator.getValue(); } catch (Exception e) { - Assert.assertTrue(e instanceof NoSuchElementException); + Assert.assertTrue(e instanceof IllegalStateException); } - db.close(); - } finally { - RocksDB.destroyDB(file.toString(), new org.rocksdb.Options()); + thrown.expect(NoSuchElementException.class); + iterator.next(); } - } }