mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 15:10:44 +01:00
Merge pull request #7185 from HenrikJannsen/fix-missing-snapshot-creation
Add missing snapshot creation in full DAO mode
This commit is contained in:
commit
4ce102e372
9 changed files with 106 additions and 51 deletions
|
@ -139,6 +139,7 @@ public class Config {
|
|||
public static final String BM_ORACLE_NODE_PRIV_KEY = "bmOracleNodePrivKey";
|
||||
public static final String SEED_NODE_REPORTING_SERVER_URL = "seedNodeReportingServerUrl";
|
||||
public static final String USE_TOR_FOR_BTC_MONITOR = "useTorForBtcMonitor";
|
||||
public static final String USE_FULL_MODE_DAO_MONITOR = "useFullModeDaoMonitor";
|
||||
|
||||
// Default values for certain options
|
||||
public static final int UNSPECIFIED_PORT = -1;
|
||||
|
@ -239,6 +240,8 @@ public class Config {
|
|||
public final String bmOracleNodePrivKey;
|
||||
public final String seedNodeReportingServerUrl;
|
||||
public final boolean useTorForBtcMonitor;
|
||||
public final boolean useFullModeDaoMonitor;
|
||||
public final boolean useFullModeDaoMonitorSetExplicitly;
|
||||
|
||||
// Properties derived from options but not exposed as options themselves
|
||||
public final File torDir;
|
||||
|
@ -716,7 +719,7 @@ public class Config {
|
|||
parser.accepts(DAO_NODE_API_PORT, "Dao node API port")
|
||||
.withRequiredArg()
|
||||
.ofType(Integer.class)
|
||||
.defaultsTo(8082);
|
||||
.defaultsTo(8081);
|
||||
|
||||
ArgumentAcceptingOptionSpec<Boolean> isBmFullNode =
|
||||
parser.accepts(IS_BM_FULL_NODE, "Run as Burningman full node")
|
||||
|
@ -746,6 +749,14 @@ public class Config {
|
|||
.ofType(Boolean.class)
|
||||
.defaultsTo(true);
|
||||
|
||||
ArgumentAcceptingOptionSpec<Boolean> useFullModeDaoMonitorOpt =
|
||||
parser.accepts(USE_FULL_MODE_DAO_MONITOR, "If set to true full mode DAO monitor is activated. " +
|
||||
"By that at each block during parsing the dao state hash is created, " +
|
||||
"otherwise only after block parsing is complete and on new blocks.")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
try {
|
||||
CompositeOptionSet options = new CompositeOptionSet();
|
||||
|
||||
|
@ -873,6 +884,8 @@ public class Config {
|
|||
this.bmOracleNodePrivKey = options.valueOf(bmOracleNodePrivKey);
|
||||
this.seedNodeReportingServerUrl = options.valueOf(seedNodeReportingServerUrlOpt);
|
||||
this.useTorForBtcMonitor = options.valueOf(useTorForBtcMonitorOpt);
|
||||
this.useFullModeDaoMonitor = options.valueOf(useFullModeDaoMonitorOpt);
|
||||
this.useFullModeDaoMonitorSetExplicitly = options.has(useFullModeDaoMonitorOpt);
|
||||
} catch (OptionException ex) {
|
||||
throw new ConfigException("problem parsing option '%s': %s",
|
||||
ex.options().get(0),
|
||||
|
|
|
@ -225,6 +225,7 @@ public class DaoModule extends AppModule {
|
|||
bindConstant().annotatedWith(named(Config.IS_BM_FULL_NODE)).to(config.isBmFullNode);
|
||||
bindConstant().annotatedWith(named(Config.BM_ORACLE_NODE_PUB_KEY)).to(config.bmOracleNodePubKey);
|
||||
bindConstant().annotatedWith(named(Config.BM_ORACLE_NODE_PRIV_KEY)).to(config.bmOracleNodePrivKey);
|
||||
bindConstant().annotatedWith(named(Config.USE_FULL_MODE_DAO_MONITOR)).to(config.useFullModeDaoMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -203,7 +203,7 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
verifyCheckpoints();
|
||||
}
|
||||
|
||||
log.info("ParseBlockChainComplete: Accumulated updateHashChain() calls for {} block took {} ms " +
|
||||
log.info("ParseBlockChainComplete: Accumulated updateHashChain() calls for {} blocks took {} ms " +
|
||||
"({} ms in average / block)",
|
||||
numCalls,
|
||||
accumulatedDuration,
|
||||
|
@ -370,12 +370,19 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
UserThread.runAfter(() -> daoStateNetworkService.broadcastMyStateHash(myDaoStateHash), delayInSec);
|
||||
}
|
||||
long duration = System.currentTimeMillis() - ts;
|
||||
// We don't want to spam the output. We log accumulated time after parsing is completed.
|
||||
log.trace("updateHashChain for block {} took {} ms",
|
||||
block.getHeight(),
|
||||
duration);
|
||||
accumulatedDuration += duration;
|
||||
numCalls++;
|
||||
|
||||
if (numCalls % 10 == 0) {
|
||||
log.info("Accumulated updateHashChain() calls for {} blocks took {} ms " +
|
||||
"({} ms in average / block)",
|
||||
numCalls,
|
||||
accumulatedDuration,
|
||||
(int) ((double) accumulatedDuration / (double) numCalls));
|
||||
}
|
||||
listeners.forEach(Listener::onDaoStateBlockCreated);
|
||||
return Optional.of(daoStateBlock);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import lombok.Getter;
|
|||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class DaoStateHash extends StateHash {
|
||||
// If we have built the hash by ourself opposed to that we got delivered the hash from seed nodes or resources
|
||||
// If we have built the hash by ourselves opposed to that we got delivered the hash from seed nodes or resources
|
||||
private final boolean isSelfCreated;
|
||||
|
||||
public DaoStateHash(int height, byte[] hash, boolean isSelfCreated) {
|
||||
|
|
|
@ -34,6 +34,7 @@ import bisq.common.config.Config;
|
|||
import bisq.common.util.GcUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
|
@ -50,7 +51,7 @@ import javax.annotation.Nullable;
|
|||
|
||||
/**
|
||||
* Manages periodical snapshots of the DaoState.
|
||||
* At startup we apply a snapshot if available.
|
||||
* At startup, we apply a snapshot if available.
|
||||
* At each trigger height we persist the latest snapshot candidate and set the current daoState as new candidate.
|
||||
* The trigger height is determined by the SNAPSHOT_GRID. The latest persisted snapshot is min. the height of
|
||||
* SNAPSHOT_GRID old not less than 2 times the SNAPSHOT_GRID old.
|
||||
|
@ -67,6 +68,7 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
private final BsqWalletService bsqWalletService;
|
||||
private final Preferences preferences;
|
||||
private final Config config;
|
||||
private final boolean fullDaoNode;
|
||||
|
||||
private protobuf.DaoState daoStateCandidate;
|
||||
private LinkedList<DaoStateHash> hashChainCandidate = new LinkedList<>();
|
||||
|
@ -77,7 +79,7 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
@Nullable
|
||||
private Runnable resyncDaoStateFromResourcesHandler;
|
||||
private int daoRequiresRestartHandlerAttempts = 0;
|
||||
private boolean readyForPersisting = true;
|
||||
private boolean persistingBlockInProgress;
|
||||
private boolean isParseBlockChainComplete;
|
||||
private final List<Integer> heightsOfLastAppliedSnapshots = new ArrayList<>();
|
||||
|
||||
|
@ -93,7 +95,8 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
WalletsSetup walletsSetup,
|
||||
BsqWalletService bsqWalletService,
|
||||
Preferences preferences,
|
||||
Config config) {
|
||||
Config config,
|
||||
@Named(Config.FULL_DAO_NODE) boolean fullDaoNode) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
this.daoStateStorageService = daoStateStorageService;
|
||||
|
@ -102,6 +105,7 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
this.bsqWalletService = bsqWalletService;
|
||||
this.preferences = preferences;
|
||||
this.config = config;
|
||||
this.fullDaoNode = fullDaoNode;
|
||||
}
|
||||
|
||||
|
||||
|
@ -122,6 +126,7 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
daoStateStorageService.shutDown();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -147,13 +152,16 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
@Override
|
||||
public void onDaoStateChanged(Block block) {
|
||||
// If we have isUseDaoMonitor activated we apply the hash and snapshots at each new block during initial parsing.
|
||||
// Otherwise we do it only after the initial blockchain parsing is completed to not delay the parsing.
|
||||
// Otherwise, we do it only after the initial blockchain parsing is completed to not delay the parsing.
|
||||
// In that case we get the missing hashes from the seed nodes. At any new block we do the hash calculation
|
||||
// ourself and therefore get back confidence that our DAO state is in sync with the network.
|
||||
// ourselves and therefore get back confidence that our DAO state is in sync with the network.
|
||||
if (preferences.isUseFullModeDaoMonitor() || isParseBlockChainComplete) {
|
||||
// We need to execute first the daoStateMonitoringService.createHashFromBlock to get the hash created
|
||||
daoStateMonitoringService.createHashFromBlock(block);
|
||||
maybeCreateSnapshot(block);
|
||||
} else if (fullDaoNode) {
|
||||
// If we run as full DAO node we want to create a snapshot at each trigger block.
|
||||
maybeCreateSnapshot(block);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +169,7 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
public void onParseBlockChainComplete() {
|
||||
isParseBlockChainComplete = true;
|
||||
|
||||
// In case we have dao monitoring deactivated we create the snapshot after we are completed with parsing
|
||||
// In case we have dao monitoring deactivated we create the snapshot after we are completed with parsing,
|
||||
// and we got called back from daoStateMonitoringService once the hashes are created from peers data.
|
||||
if (!preferences.isUseFullModeDaoMonitor()) {
|
||||
// We register a callback handler once the daoStateMonitoringService has received the missing hashes from
|
||||
|
@ -204,44 +212,51 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
// We need to process during batch processing as well to write snapshots during that process.
|
||||
public void maybeCreateSnapshot(Block block) {
|
||||
int chainHeight = block.getHeight();
|
||||
if (!isSnapshotHeight(chainHeight)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Either we don't have a snapshot candidate yet, or if we have one the height at that snapshot candidate must be
|
||||
// different to our current height.
|
||||
boolean noSnapshotCandidateOrDifferentHeight = daoStateCandidate == null ||
|
||||
snapshotHeight != chainHeight;
|
||||
if (isSnapshotHeight(chainHeight) &&
|
||||
!daoStateService.getBlocks().isEmpty() &&
|
||||
isHeightAtLeastGenesisHeight(daoStateService.getBlockHeightOfLastBlock()) &&
|
||||
noSnapshotCandidateOrDifferentHeight) {
|
||||
if (isHeightBelowGenesisHeight(daoStateService.getBlockHeightOfLastBlock())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We protect to get called while we are not completed with persisting the daoState. This can take about
|
||||
// 20 seconds and it is not expected that we get triggered another snapshot event in that period, but this
|
||||
// check guards that we would skip such calls..
|
||||
if (!readyForPersisting) {
|
||||
if (preferences.isUseFullModeDaoMonitor()) {
|
||||
// In case we dont use isUseFullModeDaoMonitor we might called here too often as the parsing is much
|
||||
// faster than the persistence and we likely create only 1 snapshot during initial parsing, so
|
||||
// we log only if isUseFullModeDaoMonitor is true as then parsing is likely slower and we would
|
||||
// expect that we do a snapshot at each trigger block.
|
||||
log.info("We try to persist a daoState but the previous call has not completed yet. " +
|
||||
"We ignore that call and skip that snapshot. " +
|
||||
"Snapshot will be created at next snapshot height again. This is not to be expected with live " +
|
||||
"blockchain data.");
|
||||
}
|
||||
return;
|
||||
if (daoStateService.getBlocks().isEmpty()) {
|
||||
log.error("No snapshot to be created as blocks are empty. This should never happen.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (daoStateCandidate != null && snapshotHeight == chainHeight) {
|
||||
log.error("snapshotHeight is same as chainHeight. This should never happen. chainHeight={}", chainHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
// We protect to get called while we are not completed with persisting the daoState. This can take about
|
||||
// 20 seconds, and it is not expected that we get triggered another snapshot event in that period, but this
|
||||
// check guards that we would skip such calls.
|
||||
if (persistingBlockInProgress) {
|
||||
if (preferences.isUseFullModeDaoMonitor()) {
|
||||
// In case we don't use isUseFullModeDaoMonitor we might get called here too often as the parsing is much
|
||||
// faster than the persistence, and we likely create only 1 snapshot during initial parsing, so
|
||||
// we log only if isUseFullModeDaoMonitor is true as then parsing is likely slower, and we would
|
||||
// expect that we do a snapshot at each trigger block.
|
||||
log.info("We try to persist a daoState but the previous call has not completed yet. " +
|
||||
"We ignore that call and skip that snapshot. " +
|
||||
"Snapshot will be created at next snapshot height again. This is not to be expected with live " +
|
||||
"blockchain data.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (daoStateCandidate != null) {
|
||||
persist();
|
||||
} else {
|
||||
createSnapshot();
|
||||
}
|
||||
if (daoStateCandidate != null) {
|
||||
persist();
|
||||
} else {
|
||||
createSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
private void persist() {
|
||||
long ts = System.currentTimeMillis();
|
||||
readyForPersisting = false;
|
||||
persistingBlockInProgress = true;
|
||||
daoStateStorageService.requestPersistence(daoStateCandidate,
|
||||
blocksCandidate,
|
||||
hashChainCandidate,
|
||||
|
@ -250,7 +265,7 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
snapshotHeight, System.currentTimeMillis() - ts);
|
||||
|
||||
createSnapshot();
|
||||
readyForPersisting = true;
|
||||
persistingBlockInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -315,8 +330,7 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
return;
|
||||
}
|
||||
|
||||
if (!isHeightAtLeastGenesisHeight(chainHeightOfPersistedDaoState)) {
|
||||
log.error("heightOfPersistedLastBlock is below genesis height. This should never happen.");
|
||||
if (isHeightBelowGenesisHeight(chainHeightOfPersistedDaoState)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -343,8 +357,12 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
|
|||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private boolean isHeightAtLeastGenesisHeight(int heightOfLastBlock) {
|
||||
return heightOfLastBlock >= genesisTxInfo.getGenesisBlockHeight();
|
||||
private boolean isHeightBelowGenesisHeight(int height) {
|
||||
boolean isHeightBelowGenesisHeight = height < genesisTxInfo.getGenesisBlockHeight();
|
||||
if (isHeightBelowGenesisHeight) {
|
||||
log.error("height is below genesis height. This should never happen. height={}", height);
|
||||
}
|
||||
return isHeightBelowGenesisHeight;
|
||||
}
|
||||
|
||||
private void resyncDaoStateFromResources() {
|
||||
|
|
|
@ -171,7 +171,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
private final String btcNodesFromOptions, referralIdFromOptions,
|
||||
rpcUserFromOptions, rpcPwFromOptions;
|
||||
private final int blockNotifyPortFromOptions;
|
||||
private final boolean fullDaoNodeFromOptions, fullAccountingNodeFromOptions;
|
||||
private final boolean fullDaoNodeFromOptions, fullAccountingNodeFromOptions, useFullModeDaoMonitorFromOptions;
|
||||
@Getter
|
||||
private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode());
|
||||
|
||||
|
@ -191,7 +191,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
@Named(Config.IS_BM_FULL_NODE) boolean fullAccountingNode,
|
||||
@Named(Config.RPC_USER) String rpcUser,
|
||||
@Named(Config.RPC_PASSWORD) String rpcPassword,
|
||||
@Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockNotificationPort) {
|
||||
@Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockNotificationPort,
|
||||
@Named(Config.USE_FULL_MODE_DAO_MONITOR) boolean useFullModeDaoMonitor) {
|
||||
|
||||
this.persistenceManager = persistenceManager;
|
||||
this.config = config;
|
||||
|
@ -204,6 +205,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
this.rpcUserFromOptions = rpcUser;
|
||||
this.rpcPwFromOptions = rpcPassword;
|
||||
this.blockNotifyPortFromOptions = rpcBlockNotificationPort;
|
||||
this.useFullModeDaoMonitorFromOptions = useFullModeDaoMonitor;
|
||||
|
||||
useAnimationsProperty.addListener((ov) -> {
|
||||
prefPayload.setUseAnimations(useAnimationsProperty.get());
|
||||
|
@ -828,8 +830,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
}
|
||||
|
||||
public void setUseFullModeDaoMonitor(boolean value) {
|
||||
prefPayload.setUseFullModeDaoMonitor(value);
|
||||
requestPersistence();
|
||||
// We only persist if we have not set the program argument
|
||||
if (!config.useFullModeDaoMonitorSetExplicitly) {
|
||||
prefPayload.setUseFullModeDaoMonitor(value);
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void setUseBitcoinUrisInQrCodes(boolean value) {
|
||||
|
@ -1031,6 +1036,14 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
return prefPayload.isFullBMAccountingNode();
|
||||
}
|
||||
|
||||
public boolean isUseFullModeDaoMonitor() {
|
||||
if (config.useFullModeDaoMonitorSetExplicitly) {
|
||||
return useFullModeDaoMonitorFromOptions;
|
||||
} else {
|
||||
return prefPayload.isUseFullModeDaoMonitor();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
@ -1202,5 +1215,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
void setProcessBurningManAccountingData(boolean processBurningManAccountingData);
|
||||
|
||||
void setFullBMAccountingNode(boolean isFullBMAccountingNode);
|
||||
|
||||
boolean isUseFullModeDaoMonitor();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,8 @@ public class DaoStateSnapshotServiceTest {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
null,
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -69,7 +69,7 @@ public class PreferencesTest {
|
|||
LocalBitcoinNode localBitcoinNode = new LocalBitcoinNode(config);
|
||||
preferences = new Preferences(
|
||||
persistenceManager, config, null, localBitcoinNode, null, null, Config.DEFAULT_FULL_DAO_NODE,
|
||||
false, null, null, Config.UNSPECIFIED_PORT);
|
||||
false, null, null, Config.UNSPECIFIED_PORT, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -47,7 +47,7 @@ public class PreferenceMakers {
|
|||
lookup.valueOf(localBitcoinNode, new SameValueDonor<>(null)),
|
||||
lookup.valueOf(useTorFlagFromOptions, new SameValueDonor<>(null)),
|
||||
lookup.valueOf(referralID, new SameValueDonor<>(null)),
|
||||
Config.DEFAULT_FULL_DAO_NODE, false, null, null, Config.UNSPECIFIED_PORT);
|
||||
Config.DEFAULT_FULL_DAO_NODE, false, null, null, Config.UNSPECIFIED_PORT, false);
|
||||
|
||||
public static final Preferences empty = make(a(Preferences));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue