From 6400f4b7bf975dba6971bd51dfc76a3e20b0fb49 Mon Sep 17 00:00:00 2001 From: Luca Moser Date: Wed, 5 Jun 2019 15:13:03 +0200 Subject: [PATCH] adds QuickTransactionSolidifier --- src/main/java/com/iota/iri/Iota.java | 5 + .../com/iota/iri/TransactionValidator.java | 2 +- .../com/iota/iri/conf/BaseIotaConfig.java | 39 +++++- .../iota/iri/conf/SolidificationConfig.java | 18 ++- .../solidifier/TransactionSolidifier.java | 23 ++++ .../impl/QuickTransactionSolidifier.java | 121 ++++++++++++++++++ 6 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/iota/iri/service/solidifier/TransactionSolidifier.java create mode 100644 src/main/java/com/iota/iri/service/solidifier/impl/QuickTransactionSolidifier.java diff --git a/src/main/java/com/iota/iri/Iota.java b/src/main/java/com/iota/iri/Iota.java index 257b0e0ace..d95f1a581f 100644 --- a/src/main/java/com/iota/iri/Iota.java +++ b/src/main/java/com/iota/iri/Iota.java @@ -16,6 +16,7 @@ import com.iota.iri.service.snapshot.impl.LocalSnapshotManagerImpl; import com.iota.iri.service.snapshot.impl.SnapshotProviderImpl; import com.iota.iri.service.snapshot.impl.SnapshotServiceImpl; +import com.iota.iri.service.solidifier.impl.QuickTransactionSolidifier; import com.iota.iri.service.spentaddresses.SpentAddressesException; import com.iota.iri.service.spentaddresses.impl.SpentAddressesProviderImpl; import com.iota.iri.service.spentaddresses.impl.SpentAddressesServiceImpl; @@ -101,6 +102,7 @@ public class Iota { public final Tangle tangle; public final TransactionValidator transactionValidator; public final TipsSolidifier tipsSolidifier; + public final QuickTransactionSolidifier quickTransactionSolidifier; public final TransactionRequester transactionRequester; public final Node node; public final UDPReceiver udpReceiver; @@ -137,6 +139,7 @@ public Iota(IotaConfig configuration) throws TransactionPruningException, Snapsh ? new AsyncTransactionPruner() : null; transactionRequesterWorker = new TransactionRequesterWorkerImpl(); + quickTransactionSolidifier = new QuickTransactionSolidifier(); // legacy code bundleValidator = new BundleValidator(); @@ -190,6 +193,7 @@ public void init() throws Exception { seenMilestonesRetriever.start(); milestoneSolidifier.start(); transactionRequesterWorker.start(); + quickTransactionSolidifier.start(); if (localSnapshotManager != null) { localSnapshotManager.start(latestMilestoneTracker); @@ -222,6 +226,7 @@ private void injectDependencies() throws SnapshotException, TransactionPruningEx transactionPruner.init(tangle, snapshotProvider, spentAddressesService, spentAddressesProvider, tipsViewModel, configuration); } transactionRequesterWorker.init(tangle, transactionRequester, tipsViewModel, node); + quickTransactionSolidifier.init(configuration, tangle, snapshotProvider, latestMilestoneTracker, transactionValidator); } private void rescanDb() throws Exception { diff --git a/src/main/java/com/iota/iri/TransactionValidator.java b/src/main/java/com/iota/iri/TransactionValidator.java index 022e5c5d23..0cee116e35 100644 --- a/src/main/java/com/iota/iri/TransactionValidator.java +++ b/src/main/java/com/iota/iri/TransactionValidator.java @@ -396,7 +396,7 @@ public void updateStatus(TransactionViewModel transactionViewModel) throws Excep * @param transactionViewModel transaction we try to solidify. * @return true if we managed to solidify, else false. */ - private boolean quietQuickSetSolid(TransactionViewModel transactionViewModel) { + public boolean quietQuickSetSolid(TransactionViewModel transactionViewModel) { try { return quickSetSolid(transactionViewModel); } catch (Exception e) { diff --git a/src/main/java/com/iota/iri/conf/BaseIotaConfig.java b/src/main/java/com/iota/iri/conf/BaseIotaConfig.java index 7a23de32c3..214eb66d8f 100644 --- a/src/main/java/com/iota/iri/conf/BaseIotaConfig.java +++ b/src/main/java/com/iota/iri/conf/BaseIotaConfig.java @@ -5,7 +5,6 @@ import com.beust.jcommander.ParameterException; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.iota.iri.IRI; import com.iota.iri.crypto.SpongeFactory; import com.iota.iri.model.Hash; import com.iota.iri.model.HashFactory; @@ -30,7 +29,7 @@ public abstract class BaseIotaConfig implements IotaConfig { private boolean help; private boolean testnet = false; - + //API protected int port = Defaults.API_PORT; protected String apiHost = Defaults.API_HOST; @@ -41,7 +40,7 @@ public abstract class BaseIotaConfig implements IotaConfig { protected int maxGetTrytes = Defaults.MAX_GET_TRYTES; protected int maxBodyLength = Defaults.MAX_BODY_LENGTH; protected String remoteAuth = Defaults.REMOTE_AUTH; - + //We don't have a REMOTE config but we have a remote flag. We must add a field for JCommander private boolean remote; @@ -98,6 +97,8 @@ public abstract class BaseIotaConfig implements IotaConfig { //Tip Solidification protected boolean tipSolidifierEnabled = Defaults.TIP_SOLIDIFIER_ENABLED; + protected int solidifierDepth = Defaults.SOLIDIFIER_DEPTH; + protected int solidifierIntervalMillisec = Defaults.SOLIDIFIER_INTERVAL_MILLISEC; //PearlDiver protected int powThreads = Defaults.POW_THREADS; @@ -138,12 +139,12 @@ public JCommander parseConfigFromArgs(String[] args) throws ParameterException { public boolean isHelp() { return help; } - + @Override public boolean isTestnet() { return testnet; } - + @JsonIgnore @Parameter(names = {Config.TESTNET_FLAG}, description = Config.Descriptions.TESTNET, arity = 1) protected void setTestnet(boolean testnet) { @@ -172,7 +173,7 @@ public String getApiHost() { if (remote) { return "0.0.0.0"; } - + return apiHost; } @@ -852,13 +853,35 @@ protected void setTipSolidifierEnabled(boolean tipSolidifierEnabled) { this.tipSolidifierEnabled = tipSolidifierEnabled; } + @Override + public int getSolidifierDepth() { + return solidifierDepth; + } + + @JsonProperty + @Parameter(names = {"--solidifier-depth"}, description = SolidificationConfig.Descriptions.SOLIDIFIER_DEPTH) + protected void setSolidifierDepth(int solidifierDepth) { + this.solidifierDepth = solidifierDepth; + } + + @Override + public int getSolidifierIntervalMillisec() { + return solidifierIntervalMillisec; + } + + @JsonProperty + @Parameter(names = {"--solidifier-interval-millisec"}, description = SolidificationConfig.Descriptions.SOLIDIFIER_INTERVAL_MILLISEC) + protected void setSolidifierIntervalMillisec(int solidifierIntervalMillisec) { + this.solidifierIntervalMillisec = solidifierIntervalMillisec; + } + @Override public int getBelowMaxDepthTransactionLimit() { return maxAnalyzedTransactions; } @JsonProperty - @Parameter(names = "--max-analyzed-transactions", + @Parameter(names = "--max-analyzed-transactions", description = TipSelConfig.Descriptions.BELOW_MAX_DEPTH_TRANSACTION_LIMIT) protected void setBelowMaxDepthTransactionLimit(int maxAnalyzedTransactions) { this.maxAnalyzedTransactions = maxAnalyzedTransactions; @@ -940,6 +963,8 @@ public interface Defaults { //Tip solidification boolean TIP_SOLIDIFIER_ENABLED = true; + int SOLIDIFIER_DEPTH = 3; + int SOLIDIFIER_INTERVAL_MILLISEC = 10000; //PearlDiver int POW_THREADS = 0; diff --git a/src/main/java/com/iota/iri/conf/SolidificationConfig.java b/src/main/java/com/iota/iri/conf/SolidificationConfig.java index 605fd11514..9888945326 100644 --- a/src/main/java/com/iota/iri/conf/SolidificationConfig.java +++ b/src/main/java/com/iota/iri/conf/SolidificationConfig.java @@ -13,12 +13,28 @@ public interface SolidificationConfig extends Config { * @return {@value SolidificationConfig.Descriptions#TIP_SOLIDIFIER} */ boolean isTipSolidifierEnabled(); - + + /** + * The depth the solidifier will use. + * + * @return the depth the solidifier will use. + */ + int getSolidifierDepth(); + + /** + * Returns the interval at which the solidifer will run. + * + * @return the interval at which the solidifer will run + */ + int getSolidifierIntervalMillisec(); + /** * Field descriptions */ interface Descriptions { String TIP_SOLIDIFIER = "Scan the current tips and attempt to mark them as solid"; + String SOLIDIFIER_DEPTH = "The depth at which the solidifer will start"; + String SOLIDIFIER_INTERVAL_MILLISEC = "The interval at which the solidifier will run"; } } diff --git a/src/main/java/com/iota/iri/service/solidifier/TransactionSolidifier.java b/src/main/java/com/iota/iri/service/solidifier/TransactionSolidifier.java new file mode 100644 index 0000000000..f2f5e45acd --- /dev/null +++ b/src/main/java/com/iota/iri/service/solidifier/TransactionSolidifier.java @@ -0,0 +1,23 @@ +package com.iota.iri.service.solidifier; + +/** + * The {@link TransactionSolidifier} continuously tries to solidify transactions. + */ +public interface TransactionSolidifier { + + /** + * Starts the background worker that continuously tries to solidify transactions. + */ + void start(); + + /** + * Solidifies transactions. Which transactions and how are getting solidified is an implementation detail. + */ + void solidify(); + + /** + * Stops the background worker that continuously solidifies transactions. + */ + void shutdown(); + +} diff --git a/src/main/java/com/iota/iri/service/solidifier/impl/QuickTransactionSolidifier.java b/src/main/java/com/iota/iri/service/solidifier/impl/QuickTransactionSolidifier.java new file mode 100644 index 0000000000..fdbc977175 --- /dev/null +++ b/src/main/java/com/iota/iri/service/solidifier/impl/QuickTransactionSolidifier.java @@ -0,0 +1,121 @@ +package com.iota.iri.service.solidifier.impl; + +import com.iota.iri.TransactionValidator; +import com.iota.iri.conf.SolidificationConfig; +import com.iota.iri.controllers.MilestoneViewModel; +import com.iota.iri.controllers.TransactionViewModel; +import com.iota.iri.service.milestone.LatestMilestoneTracker; +import com.iota.iri.service.snapshot.SnapshotProvider; +import com.iota.iri.service.solidifier.TransactionSolidifier; +import com.iota.iri.storage.Tangle; +import com.iota.iri.utils.dag.DAGHelper; +import com.iota.iri.utils.thread.DedicatedScheduledExecutorService; +import com.iota.iri.utils.thread.SilentScheduledExecutorService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The {@link QuickTransactionSolidifier} starts at the defined depth parameter to walk up the graph towards the tips + * and executes the {@link TransactionValidator#quietQuickSetSolid(TransactionViewModel)} on each traversed transaction. + */ +public class QuickTransactionSolidifier implements TransactionSolidifier { + + private static final Logger log = LoggerFactory.getLogger(QuickTransactionSolidifier.class); + + private final SilentScheduledExecutorService executorService = new DedicatedScheduledExecutorService( + "Quick Transaction Solidifier", log); + + // external + private SolidificationConfig solidificationConfig; + private Tangle tangle; + private SnapshotProvider snapshotProvider; + private LatestMilestoneTracker latestMilestoneTracker; + private TransactionValidator transactionValidator; + + /** + *

+ * This method initializes the instance and registers its dependencies. + *

+ *

+ * It simply stores the passed in values in their corresponding private properties. + *

+ *

+ * Note: Instead of handing over the dependencies in the constructor, we register them lazy. This allows us to have + * circular dependencies because the instantiation is separated from the dependency injection. To reduce the + * amount of code that is necessary to correctly instantiate this class, we return the instance itself which + * allows us to still instantiate, initialize and assign in one line - see Example: + *

+ * {@code quickTransactionSolidifier = new QuickTransactionSolidifier().init(...);} + * + * @param solidificationConfig the configuration holding parameters for solidification + * @param tangle Tangle object which acts as a database interface + * @param snapshotProvider data provider for the snapshots that are relevant for the node + * @param latestMilestoneTracker the tracker holding the latest known milestone index + * @param transactionValidator the TransactionValidator which holds the logic for checking quick solidification + * @return the initialized instance itself to allow chaining + */ + public QuickTransactionSolidifier init(SolidificationConfig solidificationConfig, Tangle tangle, SnapshotProvider snapshotProvider, + LatestMilestoneTracker latestMilestoneTracker, TransactionValidator transactionValidator) { + this.solidificationConfig = solidificationConfig; + this.tangle = tangle; + this.snapshotProvider = snapshotProvider; + this.latestMilestoneTracker = latestMilestoneTracker; + this.transactionValidator = transactionValidator; + return this; + } + + @Override + public void start() { + executorService.silentScheduleWithFixedDelay(this::solidify, 0, + solidificationConfig.getSolidifierIntervalMillisec(), TimeUnit.MILLISECONDS); + } + + @Override + public void solidify() { + // we only try to quick solidify transactions when we are not synchronized and below the depth of the + // quick solidification parameter + int latestMilestoneIndex = latestMilestoneTracker.getLatestMilestoneIndex(); + int depth = solidificationConfig.getSolidifierDepth(); + if (latestMilestoneIndex - depth <= 0 + || snapshotProvider.getLatestSnapshot().getIndex() < latestMilestoneIndex - depth) { + return; + } + + DAGHelper dagHelper = DAGHelper.get(tangle); + long start = System.currentTimeMillis(); + try { + MilestoneViewModel milestone = MilestoneViewModel.get(tangle, latestMilestoneIndex - depth); + // if we don't have the milestone which we wanted to use as a starting point, we simply don't run. + if (milestone == null) { + return; + } + AtomicInteger updated = new AtomicInteger(); + AtomicInteger traversed = new AtomicInteger(); + dagHelper.traverseApprovers(milestone.getHash(), tvm -> !Thread.currentThread().isInterrupted(), tvm -> { + try { + if (transactionValidator.quietQuickSetSolid(tvm)) { + tvm.update(tangle, snapshotProvider.getInitialSnapshot(), "solid|height"); + updated.incrementAndGet(); + } + traversed.incrementAndGet(); + } catch (Exception e) { + log.error("error while trying to quick set solid transaction {}. reason: {}", tvm.getHash(), + e.getMessage()); + } + }); + + log.info("updated {} and traversed {} transactions, took {} ms", updated.get(), traversed.get(), + System.currentTimeMillis() - start); + } catch (Exception e) { + log.error("error occurred during quick solidification run: {}", e.getMessage()); + } + } + + @Override + public void shutdown() { + executorService.shutdownNow(); + } +}