Merge pull request #7185 from HenrikJannsen/fix-missing-snapshot-creation

Add missing snapshot creation in full DAO mode
This commit is contained in:
Alejandro García 2024-07-19 00:12:28 +00:00 committed by GitHub
commit 4ce102e372
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 106 additions and 51 deletions

View file

@ -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),

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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) {

View file

@ -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() {

View file

@ -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();
}
}

View file

@ -41,7 +41,8 @@ public class DaoStateSnapshotServiceTest {
null,
null,
null,
null);
null,
true);
}
@Test

View file

@ -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")

View file

@ -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));
}