mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Merge pull request #5782 from chimp1984/redesign-dao-state-monitoring
Redesign dao state monitoring [4]
This commit is contained in:
commit
65c308f5ed
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
|
||||
// @Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class BsqSwapTradeTestLoop extends AbstractOfferTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
createBsqSwapBsqPaymentAccounts();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetBalancesBeforeTrade() {
|
||||
BsqSwapTradeTest test = new BsqSwapTradeTest();
|
||||
runTradeLoop(test);
|
||||
}
|
||||
|
||||
private void runTradeLoop(BsqSwapTradeTest test) {
|
||||
// TODO Fix wallet inconsistency bugs after 2nd trades.
|
||||
for (int tradeCount = 1; tradeCount <= 2; tradeCount++) {
|
||||
log.warn("================================ Trade # {} ================================", tradeCount);
|
||||
test.testGetBalancesBeforeTrade();
|
||||
|
||||
test.testAliceCreateBsqSwapBuyOffer();
|
||||
genBtcBlocksThenWait(1, 8000);
|
||||
|
||||
test.testBobTakesBsqSwapOffer();
|
||||
genBtcBlocksThenWait(1, 8000);
|
||||
|
||||
test.testGetBalancesAfterTrade();
|
||||
}
|
||||
}
|
||||
}
|
@ -501,8 +501,6 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
||||
if (completeHandler != null) {
|
||||
UserThread.execute(completeHandler);
|
||||
}
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.common.util;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.DevEnv;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -47,18 +48,31 @@ public class GcUtil {
|
||||
* @param trigger Threshold for free memory in MB when we invoke the garbage collector
|
||||
*/
|
||||
private static void autoReleaseMemory(long trigger) {
|
||||
UserThread.runPeriodically(() -> maybeReleaseMemory(trigger), 60);
|
||||
UserThread.runPeriodically(() -> maybeReleaseMemory(trigger), 120);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trigger Threshold for free memory in MB when we invoke the garbage collector
|
||||
*/
|
||||
private static void maybeReleaseMemory(long trigger) {
|
||||
long totalMemory = Runtime.getRuntime().totalMemory();
|
||||
if (totalMemory > trigger * 1024 * 1024) {
|
||||
log.info("Invoke garbage collector. Total memory: {} {} {}", Utilities.readableFileSize(totalMemory), totalMemory, trigger * 1024 * 1024);
|
||||
long ts = System.currentTimeMillis();
|
||||
long preGcMemory = Runtime.getRuntime().totalMemory();
|
||||
if (preGcMemory > trigger * 1024 * 1024) {
|
||||
System.gc();
|
||||
log.info("Total memory after gc() call: {}", Utilities.readableFileSize(Runtime.getRuntime().totalMemory()));
|
||||
long postGcMemory = Runtime.getRuntime().totalMemory();
|
||||
log.info("GC reduced memory by {}. Total memory before/after: {}/{}. Took {} ms.",
|
||||
Utilities.readableFileSize(preGcMemory - postGcMemory),
|
||||
Utilities.readableFileSize(preGcMemory),
|
||||
Utilities.readableFileSize(postGcMemory),
|
||||
System.currentTimeMillis() - ts);
|
||||
if (DevEnv.isDevMode()) {
|
||||
try {
|
||||
// To see from where we got called
|
||||
throw new RuntimeException("Dummy Exception for print stacktrace at maybeReleaseMemory");
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import bisq.core.dao.governance.proofofburn.MyProofOfBurnListService;
|
||||
import bisq.core.dao.governance.proposal.MyProposalListService;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
@ -42,6 +43,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
private final DaoSetup daoSetup;
|
||||
private final Preferences preferences;
|
||||
|
||||
@Inject
|
||||
public AppSetupWithP2PAndDAO(P2PService p2PService,
|
||||
@ -58,6 +60,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
MyProposalListService myProposalListService,
|
||||
MyReputationListService myReputationListService,
|
||||
MyProofOfBurnListService myProofOfBurnListService,
|
||||
Preferences preferences,
|
||||
Config config) {
|
||||
super(p2PService,
|
||||
p2PDataStorage,
|
||||
@ -69,6 +72,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
config);
|
||||
|
||||
this.daoSetup = daoSetup;
|
||||
this.preferences = preferences;
|
||||
|
||||
// TODO Should be refactored/removed. In the meantime keep in sync with CorePersistedDataHost
|
||||
if (config.daoActivated) {
|
||||
@ -86,5 +90,8 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
super.onBasicServicesInitialized();
|
||||
|
||||
daoSetup.onAllServicesInitialized(log::error, log::warn);
|
||||
|
||||
// For seed nodes we need to set default value to true
|
||||
preferences.setUseFullModeDaoMonitor(true);
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.dao;
|
||||
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
import bisq.core.dao.state.DaoStateListener;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class DaoEventCoordinator implements DaoSetupService, DaoStateListener {
|
||||
private final DaoStateService daoStateService;
|
||||
private final DaoStateSnapshotService daoStateSnapshotService;
|
||||
private final DaoStateMonitoringService daoStateMonitoringService;
|
||||
|
||||
@Inject
|
||||
public DaoEventCoordinator(DaoStateService daoStateService,
|
||||
DaoStateSnapshotService daoStateSnapshotService,
|
||||
DaoStateMonitoringService daoStateMonitoringService) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.daoStateSnapshotService = daoStateSnapshotService;
|
||||
this.daoStateMonitoringService = daoStateMonitoringService;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoSetupService
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void addListeners() {
|
||||
this.daoStateService.addDaoStateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We listen onDaoStateChanged to ensure the dao state has been processed from listener clients after parsing.
|
||||
// We need to listen during batch processing as well to write snapshots during that process.
|
||||
@Override
|
||||
public void onDaoStateChanged(Block block) {
|
||||
// We need to execute first the daoStateMonitoringService
|
||||
daoStateMonitoringService.createHashFromBlock(block);
|
||||
daoStateSnapshotService.maybeCreateSnapshot(block);
|
||||
}
|
||||
}
|
@ -102,7 +102,6 @@ public class DaoModule extends AppModule {
|
||||
protected void configure() {
|
||||
bind(DaoSetup.class).in(Singleton.class);
|
||||
bind(DaoFacade.class).in(Singleton.class);
|
||||
bind(DaoEventCoordinator.class).in(Singleton.class);
|
||||
bind(DaoKillSwitch.class).in(Singleton.class);
|
||||
|
||||
// Node, parser
|
||||
|
@ -39,6 +39,7 @@ import bisq.core.dao.node.BsqNode;
|
||||
import bisq.core.dao.node.BsqNodeProvider;
|
||||
import bisq.core.dao.node.explorer.ExportJsonFilesService;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@ -78,16 +79,11 @@ public class DaoSetup {
|
||||
DaoStateMonitoringService daoStateMonitoringService,
|
||||
ProposalStateMonitoringService proposalStateMonitoringService,
|
||||
BlindVoteStateMonitoringService blindVoteStateMonitoringService,
|
||||
DaoEventCoordinator daoEventCoordinator) {
|
||||
DaoStateSnapshotService daoStateSnapshotService) {
|
||||
|
||||
bsqNode = bsqNodeProvider.getBsqNode();
|
||||
|
||||
// We need to take care of order of execution.
|
||||
|
||||
// For order critical event flow we use the daoEventCoordinator to delegate the calls from anonymous listeners
|
||||
// to concrete clients.
|
||||
daoSetupServices.add(daoEventCoordinator);
|
||||
|
||||
daoSetupServices.add(daoStateService);
|
||||
daoSetupServices.add(cycleService);
|
||||
daoSetupServices.add(ballotListService);
|
||||
@ -110,6 +106,7 @@ public class DaoSetup {
|
||||
daoSetupServices.add(daoStateMonitoringService);
|
||||
daoSetupServices.add(proposalStateMonitoringService);
|
||||
daoSetupServices.add(blindVoteStateMonitoringService);
|
||||
daoSetupServices.add(daoStateSnapshotService);
|
||||
|
||||
daoSetupServices.add(bsqNodeProvider.getBsqNode());
|
||||
}
|
||||
|
@ -90,8 +90,7 @@ public class CycleService implements DaoStateListener, DaoSetupService {
|
||||
}
|
||||
|
||||
public int getCycleIndex(Cycle cycle) {
|
||||
Optional<Cycle> previousCycle = getCycle(cycle.getHeightOfFirstBlock() - 1, daoStateService.getCycles());
|
||||
return previousCycle.map(cycle1 -> getCycleIndex(cycle1) + 1).orElse(0);
|
||||
return daoStateService.getCycles().indexOf(cycle);
|
||||
}
|
||||
|
||||
public boolean isTxInCycle(Cycle cycle, String txId) {
|
||||
|
@ -274,7 +274,7 @@ public class BlindVoteStateMonitoringService implements DaoSetupService, DaoStat
|
||||
byte[] combined = ArrayUtils.addAll(prevHash, serializedBlindVotes);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(combined);
|
||||
|
||||
BlindVoteStateHash myBlindVoteStateHash = new BlindVoteStateHash(blockHeight, hash, prevHash, blindVotes.size());
|
||||
BlindVoteStateHash myBlindVoteStateHash = new BlindVoteStateHash(blockHeight, hash, blindVotes.size());
|
||||
BlindVoteStateBlock blindVoteStateBlock = new BlindVoteStateBlock(myBlindVoteStateHash);
|
||||
blindVoteStateBlockChain.add(blindVoteStateBlock);
|
||||
blindVoteStateHashChain.add(myBlindVoteStateHash);
|
||||
|
@ -31,6 +31,7 @@ import bisq.core.dao.state.GenesisTxInfo;
|
||||
import bisq.core.dao.state.model.blockchain.BaseTxOutput;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
import bisq.core.dao.state.model.governance.IssuanceType;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
@ -40,7 +41,6 @@ import bisq.common.UserThread;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.file.FileUtil;
|
||||
import bisq.common.util.GcUtil;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -54,19 +54,20 @@ import javafx.collections.ObservableList;
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Monitors the DaoState by using a hash for the complete daoState and make it accessible to the network
|
||||
@ -88,7 +89,7 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
DaoStateNetworkService.Listener<NewDaoStateHashMessage, GetDaoStateHashesRequest, DaoStateHash> {
|
||||
|
||||
public interface Listener {
|
||||
void onChangeAfterBatchProcessing();
|
||||
void onDaoStateHashesChanged();
|
||||
|
||||
void onCheckpointFail();
|
||||
}
|
||||
@ -98,7 +99,6 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
private final GenesisTxInfo genesisTxInfo;
|
||||
private final Set<String> seedNodeAddresses;
|
||||
|
||||
|
||||
@Getter
|
||||
private final LinkedList<DaoStateBlock> daoStateBlockChain = new LinkedList<>();
|
||||
@Getter
|
||||
@ -110,6 +110,8 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
@Getter
|
||||
private boolean isInConflictWithSeedNode;
|
||||
@Getter
|
||||
private boolean daoStateBlockChainNotConnecting;
|
||||
@Getter
|
||||
private final ObservableList<UtxoMismatch> utxoMismatches = FXCollections.observableArrayList();
|
||||
|
||||
private final List<Checkpoint> checkpoints = Arrays.asList(
|
||||
@ -120,7 +122,13 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
private int numCalls;
|
||||
private long accumulatedDuration;
|
||||
|
||||
private final Preferences preferences;
|
||||
private final File storageDir;
|
||||
@Nullable
|
||||
private Runnable createSnapshotHandler;
|
||||
// Lookup map
|
||||
private Map<Integer, DaoStateBlock> daoStateBlockByHeight = new HashMap<>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -131,11 +139,13 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
DaoStateNetworkService daoStateNetworkService,
|
||||
GenesisTxInfo genesisTxInfo,
|
||||
SeedNodeRepository seedNodeRepository,
|
||||
Preferences preferences,
|
||||
@Named(Config.STORAGE_DIR) File storageDir,
|
||||
@Named(Config.IGNORE_DEV_MSG) boolean ignoreDevMsg) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.daoStateNetworkService = daoStateNetworkService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
this.preferences = preferences;
|
||||
this.storageDir = storageDir;
|
||||
this.ignoreDevMsg = ignoreDevMsg;
|
||||
seedNodeAddresses = seedNodeRepository.getSeedNodeAddresses().stream()
|
||||
@ -163,16 +173,19 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We do not use onDaoStateChanged but let the DaoEventCoordinator call createHashFromBlock to ensure the
|
||||
// correct order of execution.
|
||||
|
||||
@Override
|
||||
public void onParseBlockChainComplete() {
|
||||
parseBlockChainComplete = true;
|
||||
daoStateService.getLastBlock().ifPresent(this::checkUtxos);
|
||||
|
||||
daoStateNetworkService.addListeners();
|
||||
|
||||
// We wait for processing messages until we have completed batch processing
|
||||
int fromHeight = daoStateService.getChainHeight() - 10;
|
||||
// We take either the height of the previous hashBlock we have or 10 blocks below the chain tip.
|
||||
int nextBlockHeight = daoStateBlockChain.isEmpty() ?
|
||||
genesisTxInfo.getGenesisBlockHeight() :
|
||||
daoStateBlockChain.getLast().getHeight() + 1;
|
||||
int past10 = daoStateService.getChainHeight() - 10;
|
||||
int fromHeight = Math.min(nextBlockHeight, past10);
|
||||
daoStateNetworkService.requestHashesFromAllConnectedSeedNodes(fromHeight);
|
||||
|
||||
if (!ignoreDevMsg) {
|
||||
@ -188,16 +201,9 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
|
||||
@Override
|
||||
public void onDaoStateChanged(Block block) {
|
||||
long genesisTotalSupply = daoStateService.getGenesisTotalSupply().value;
|
||||
long compensationIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.COMPENSATION);
|
||||
long reimbursementIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT);
|
||||
long totalAmountOfBurntBsq = daoStateService.getTotalAmountOfBurntBsq();
|
||||
// confiscated funds are still in the utxo set
|
||||
long sumUtxo = daoStateService.getUnspentTxOutputMap().values().stream().mapToLong(BaseTxOutput::getValue).sum();
|
||||
long sumBsq = genesisTotalSupply + compensationIssuance + reimbursementIssuance - totalAmountOfBurntBsq;
|
||||
|
||||
if (sumBsq != sumUtxo) {
|
||||
utxoMismatches.add(new UtxoMismatch(block.getHeight(), sumUtxo, sumBsq));
|
||||
// During syncing we do not call checkUtxos as its a bit slow (about 4 ms)
|
||||
if (parseBlockChainComplete) {
|
||||
checkUtxos(block);
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,44 +214,47 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
|
||||
@Override
|
||||
public void onNewStateHashMessage(NewDaoStateHashMessage newStateHashMessage, Connection connection) {
|
||||
if (newStateHashMessage.getStateHash().getHeight() <= daoStateService.getChainHeight()) {
|
||||
processPeersDaoStateHash(newStateHashMessage.getStateHash(), connection.getPeersNodeAddressOptional(), true);
|
||||
// Called when receiving NewDaoStateHashMessages from peers after a new block
|
||||
DaoStateHash peersDaoStateHash = newStateHashMessage.getStateHash();
|
||||
if (peersDaoStateHash.getHeight() <= daoStateService.getChainHeight()) {
|
||||
putInPeersMapAndCheckForConflicts(getPeersAddress(connection.getPeersNodeAddressOptional()), peersDaoStateHash);
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPeersStateHashes(List<DaoStateHash> stateHashes, Optional<NodeAddress> peersNodeAddress) {
|
||||
// Called when receiving GetDaoStateHashesResponse from seed nodes
|
||||
processPeersDaoStateHashes(stateHashes, peersNodeAddress);
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
if (createSnapshotHandler != null) {
|
||||
createSnapshotHandler.run();
|
||||
// As we get called multiple times from hashes of diff. seed nodes we want to avoid to
|
||||
// call our handler multiple times.
|
||||
createSnapshotHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetStateHashRequest(Connection connection, GetDaoStateHashesRequest getStateHashRequest) {
|
||||
int fromHeight = getStateHashRequest.getHeight();
|
||||
List<DaoStateHash> daoStateHashes = daoStateBlockChain.stream()
|
||||
List<DaoStateHash> daoStateHashes = daoStateHashChain.stream()
|
||||
.filter(e -> e.getHeight() >= fromHeight)
|
||||
.map(DaoStateBlock::getMyStateHash)
|
||||
.collect(Collectors.toList());
|
||||
daoStateNetworkService.sendGetStateHashesResponse(connection, getStateHashRequest.getNonce(), daoStateHashes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPeersStateHashes(List<DaoStateHash> stateHashes, Optional<NodeAddress> peersNodeAddress) {
|
||||
AtomicBoolean hasChanged = new AtomicBoolean(false);
|
||||
|
||||
stateHashes.forEach(daoStateHash -> {
|
||||
boolean changed = processPeersDaoStateHash(daoStateHash, peersNodeAddress, false);
|
||||
if (changed) {
|
||||
hasChanged.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasChanged.get()) {
|
||||
listeners.forEach(Listener::onChangeAfterBatchProcessing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void createHashFromBlock(Block block) {
|
||||
updateHashChain(block);
|
||||
createDaoStateBlock(block);
|
||||
if (parseBlockChainComplete) {
|
||||
// We notify listeners only after batch processing to avoid performance issues at UI code
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestHashesFromGenesisBlockHeight(String peersAddress) {
|
||||
@ -256,6 +265,7 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
// We could get a reset from a reorg, so we clear all and start over from the genesis block.
|
||||
daoStateHashChain.clear();
|
||||
daoStateBlockChain.clear();
|
||||
daoStateBlockByHeight.clear();
|
||||
daoStateNetworkService.reset();
|
||||
|
||||
if (!persistedDaoStateHashChain.isEmpty()) {
|
||||
@ -263,7 +273,15 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
persistedDaoStateHashChain.size(), persistedDaoStateHashChain.getLast());
|
||||
}
|
||||
daoStateHashChain.addAll(persistedDaoStateHashChain);
|
||||
daoStateHashChain.forEach(e -> daoStateBlockChain.add(new DaoStateBlock(e)));
|
||||
daoStateHashChain.forEach(daoStateHash -> {
|
||||
DaoStateBlock daoStateBlock = new DaoStateBlock(daoStateHash);
|
||||
daoStateBlockChain.add(daoStateBlock);
|
||||
daoStateBlockByHeight.put(daoStateHash.getHeight(), daoStateBlock);
|
||||
});
|
||||
}
|
||||
|
||||
public void setCreateSnapshotHandler(Runnable handler) {
|
||||
createSnapshotHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
@ -284,7 +302,7 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void updateHashChain(Block block) {
|
||||
private Optional<DaoStateBlock> createDaoStateBlock(Block block) {
|
||||
long ts = System.currentTimeMillis();
|
||||
byte[] prevHash;
|
||||
int height = block.getHeight();
|
||||
@ -295,34 +313,45 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
} else {
|
||||
log.warn("DaoStateBlockchain is empty but we received the block which was not the genesis block. " +
|
||||
"We stop execution here.");
|
||||
return;
|
||||
daoStateBlockChainNotConnecting = true;
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
checkArgument(height == daoStateBlockChain.getLast().getHeight() + 1,
|
||||
"New block must be 1 block above previous block. height={}, " +
|
||||
"daoStateBlockChain.getLast().getHeight()={}",
|
||||
height, daoStateBlockChain.getLast().getHeight());
|
||||
prevHash = daoStateBlockChain.getLast().getHash();
|
||||
DaoStateBlock last = daoStateBlockChain.getLast();
|
||||
int heightOfLastBlock = last.getHeight();
|
||||
if (height == heightOfLastBlock + 1) {
|
||||
prevHash = last.getHash();
|
||||
} else {
|
||||
log.warn("New block must be 1 block above previous block. height={}, " +
|
||||
"daoStateBlockChain.getLast().getHeight()={}",
|
||||
height, heightOfLastBlock);
|
||||
daoStateBlockChainNotConnecting = true;
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] stateAsBytes = daoStateService.getSerializedStateForHashChain();
|
||||
// We include the prev. hash in our new hash so we can be sure that if one hash is matching all the past would
|
||||
// match as well.
|
||||
byte[] combined = ArrayUtils.addAll(prevHash, stateAsBytes);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(combined);
|
||||
|
||||
DaoStateHash myDaoStateHash = new DaoStateHash(height, hash, prevHash);
|
||||
DaoStateHash myDaoStateHash = new DaoStateHash(height, hash, true);
|
||||
DaoStateBlock daoStateBlock = new DaoStateBlock(myDaoStateHash);
|
||||
daoStateBlockChain.add(daoStateBlock);
|
||||
daoStateBlockByHeight.put(height, daoStateBlock);
|
||||
daoStateHashChain.add(myDaoStateHash);
|
||||
|
||||
// We only broadcast after parsing of blockchain is complete
|
||||
if (parseBlockChainComplete) {
|
||||
// We notify listeners only after batch processing to avoid performance issues at UI code
|
||||
listeners.forEach(Listener::onChangeAfterBatchProcessing);
|
||||
|
||||
// We delay broadcast to give peers enough time to have received the block.
|
||||
// Otherwise they would ignore our data if received block is in future to their local blockchain.
|
||||
int delayInSec = 5 + new Random().nextInt(10);
|
||||
if (Config.baseCurrencyNetwork().isRegtest()) {
|
||||
delayInSec = 1;
|
||||
}
|
||||
UserThread.runAfter(() -> daoStateNetworkService.broadcastMyStateHash(myDaoStateHash), delayInSec);
|
||||
}
|
||||
long duration = System.currentTimeMillis() - ts;
|
||||
@ -332,59 +361,93 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
duration);
|
||||
accumulatedDuration += duration;
|
||||
numCalls++;
|
||||
return Optional.of(daoStateBlock);
|
||||
}
|
||||
|
||||
private boolean processPeersDaoStateHash(DaoStateHash daoStateHash,
|
||||
Optional<NodeAddress> peersNodeAddress,
|
||||
boolean notifyListeners) {
|
||||
GcUtil.maybeReleaseMemory();
|
||||
private void processPeersDaoStateHashes(List<DaoStateHash> stateHashes, Optional<NodeAddress> peersNodeAddress) {
|
||||
boolean useDaoMonitor = preferences.isUseFullModeDaoMonitor();
|
||||
stateHashes.forEach(peersHash -> {
|
||||
Optional<DaoStateBlock> optionalDaoStateBlock;
|
||||
// If we do not add own hashes during initial parsing we fill the missing hashes from the peer and create
|
||||
// at the last block our own hash.
|
||||
int height = peersHash.getHeight();
|
||||
if (!useDaoMonitor &&
|
||||
!findDaoStateBlock(height).isPresent()) {
|
||||
if (daoStateService.getChainHeight() == height) {
|
||||
// At the most recent block we create our own hash
|
||||
optionalDaoStateBlock = daoStateService.getLastBlock()
|
||||
.map(this::createDaoStateBlock)
|
||||
.orElse(findDaoStateBlock(height));
|
||||
} else {
|
||||
// Otherwise we create a block from the peers daoStateHash
|
||||
DaoStateHash daoStateHash = new DaoStateHash(height, peersHash.getHash(), false);
|
||||
DaoStateBlock daoStateBlock = new DaoStateBlock(daoStateHash);
|
||||
daoStateBlockChain.add(daoStateBlock);
|
||||
daoStateBlockByHeight.put(height, daoStateBlock);
|
||||
daoStateHashChain.add(daoStateHash);
|
||||
optionalDaoStateBlock = Optional.of(daoStateBlock);
|
||||
}
|
||||
} else {
|
||||
optionalDaoStateBlock = findDaoStateBlock(height);
|
||||
}
|
||||
|
||||
AtomicBoolean changed = new AtomicBoolean(false);
|
||||
AtomicBoolean inConflictWithNonSeedNode = new AtomicBoolean(this.isInConflictWithNonSeedNode);
|
||||
AtomicBoolean inConflictWithSeedNode = new AtomicBoolean(this.isInConflictWithSeedNode);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
daoStateBlockChain.stream()
|
||||
.filter(e -> e.getHeight() == daoStateHash.getHeight()).findAny()
|
||||
.ifPresent(daoStateBlock -> {
|
||||
String peersNodeAddressAsString = peersNodeAddress.map(NodeAddress::getFullAddress)
|
||||
.orElseGet(() -> "Unknown peer " + new Random().nextInt(10000));
|
||||
daoStateBlock.putInPeersMap(peersNodeAddressAsString, daoStateHash);
|
||||
if (!daoStateBlock.getMyStateHash().hasEqualHash(daoStateHash)) {
|
||||
daoStateBlock.putInConflictMap(peersNodeAddressAsString, daoStateHash);
|
||||
if (seedNodeAddresses.contains(peersNodeAddressAsString)) {
|
||||
inConflictWithSeedNode.set(true);
|
||||
} else {
|
||||
inConflictWithNonSeedNode.set(true);
|
||||
}
|
||||
sb.append("We received a block hash from peer ")
|
||||
.append(peersNodeAddressAsString)
|
||||
.append(" which conflicts with our block hash.\n")
|
||||
.append("my daoStateHash=")
|
||||
.append(daoStateBlock.getMyStateHash())
|
||||
.append("\npeers daoStateHash=")
|
||||
.append(daoStateHash);
|
||||
}
|
||||
changed.set(true);
|
||||
});
|
||||
// In any case we add the peer to our peersMap and check for conflicts on the relevant daoStateBlock
|
||||
putInPeersMapAndCheckForConflicts(optionalDaoStateBlock, getPeersAddress(peersNodeAddress), peersHash);
|
||||
});
|
||||
}
|
||||
|
||||
this.isInConflictWithNonSeedNode = inConflictWithNonSeedNode.get();
|
||||
this.isInConflictWithSeedNode = inConflictWithSeedNode.get();
|
||||
private void putInPeersMapAndCheckForConflicts(String peersAddress, DaoStateHash peersHash) {
|
||||
putInPeersMapAndCheckForConflicts(findDaoStateBlock(peersHash.getHeight()), peersAddress, peersHash);
|
||||
}
|
||||
|
||||
String conflictMsg = sb.toString();
|
||||
if (!conflictMsg.isEmpty()) {
|
||||
if (this.isInConflictWithSeedNode)
|
||||
log.warn("Conflict with seed nodes: {}", conflictMsg);
|
||||
else if (this.isInConflictWithNonSeedNode)
|
||||
log.debug("Conflict with non-seed nodes: {}", conflictMsg);
|
||||
private void putInPeersMapAndCheckForConflicts(Optional<DaoStateBlock> optionalDaoStateBlock,
|
||||
String peersAddress,
|
||||
DaoStateHash peersHash) {
|
||||
optionalDaoStateBlock.ifPresent(daoStateBlock -> {
|
||||
daoStateBlock.putInPeersMap(peersAddress, peersHash);
|
||||
checkForHashConflicts(peersHash, peersAddress, daoStateBlock);
|
||||
});
|
||||
}
|
||||
|
||||
private void checkForHashConflicts(DaoStateHash peersDaoStateHash,
|
||||
String peersNodeAddress,
|
||||
DaoStateBlock daoStateBlock) {
|
||||
if (daoStateBlock.getMyStateHash().hasEqualHash(peersDaoStateHash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (notifyListeners && changed.get()) {
|
||||
listeners.forEach(Listener::onChangeAfterBatchProcessing);
|
||||
daoStateBlock.putInConflictMap(peersNodeAddress, peersDaoStateHash);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("We received a block hash from peer ")
|
||||
.append(peersNodeAddress)
|
||||
.append(" which conflicts with our block hash.\n")
|
||||
.append("my peersDaoStateHash=")
|
||||
.append(daoStateBlock.getMyStateHash())
|
||||
.append("\npeers peersDaoStateHash=")
|
||||
.append(peersDaoStateHash);
|
||||
String conflictMsg = stringBuilder.toString();
|
||||
|
||||
if (isSeedNode(peersNodeAddress)) {
|
||||
isInConflictWithSeedNode = true;
|
||||
log.warn("Conflict with seed nodes: {}", conflictMsg);
|
||||
} else {
|
||||
isInConflictWithNonSeedNode = true;
|
||||
log.debug("Conflict with non-seed nodes: {}", conflictMsg);
|
||||
}
|
||||
}
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
private void checkUtxos(Block block) {
|
||||
long genesisTotalSupply = daoStateService.getGenesisTotalSupply().value;
|
||||
long compensationIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.COMPENSATION);
|
||||
long reimbursementIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT);
|
||||
long totalAmountOfBurntBsq = daoStateService.getTotalAmountOfBurntBsq();
|
||||
// confiscated funds are still in the utxo set
|
||||
long sumUtxo = daoStateService.getUnspentTxOutputMap().values().stream().mapToLong(BaseTxOutput::getValue).sum();
|
||||
long sumBsq = genesisTotalSupply + compensationIssuance + reimbursementIssuance - totalAmountOfBurntBsq;
|
||||
|
||||
return changed.get();
|
||||
if (sumBsq != sumUtxo) {
|
||||
utxoMismatches.add(new UtxoMismatch(block.getHeight(), sumUtxo, sumBsq));
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyCheckpoints() {
|
||||
@ -431,4 +494,17 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
||||
log.error(t.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSeedNode(String peersNodeAddress) {
|
||||
return seedNodeAddresses.contains(peersNodeAddress);
|
||||
}
|
||||
|
||||
private String getPeersAddress(Optional<NodeAddress> peersNodeAddress) {
|
||||
return peersNodeAddress.map(NodeAddress::getFullAddress)
|
||||
.orElseGet(() -> "Unknown peer " + new Random().nextInt(10000));
|
||||
}
|
||||
|
||||
private Optional<DaoStateBlock> findDaoStateBlock(int height) {
|
||||
return Optional.ofNullable(daoStateBlockByHeight.get(height));
|
||||
}
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ public class ProposalStateMonitoringService implements DaoSetupService, DaoState
|
||||
}
|
||||
byte[] combined = ArrayUtils.addAll(prevHash, serializedProposals);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(combined);
|
||||
ProposalStateHash myProposalStateHash = new ProposalStateHash(blockHeight, hash, prevHash, proposals.size());
|
||||
ProposalStateHash myProposalStateHash = new ProposalStateHash(blockHeight, hash, proposals.size());
|
||||
ProposalStateBlock proposalStateBlock = new ProposalStateBlock(myProposalStateHash);
|
||||
proposalStateBlockChain.add(proposalStateBlock);
|
||||
proposalStateHashChain.add(myProposalStateHash);
|
||||
|
@ -29,8 +29,8 @@ public final class BlindVoteStateHash extends StateHash {
|
||||
@Getter
|
||||
private final int numBlindVotes;
|
||||
|
||||
public BlindVoteStateHash(int cycleStartBlockHeight, byte[] hash, byte[] prevHash, int numBlindVotes) {
|
||||
super(cycleStartBlockHeight, hash, prevHash);
|
||||
public BlindVoteStateHash(int cycleStartBlockHeight, byte[] hash, int numBlindVotes) {
|
||||
super(cycleStartBlockHeight, hash);
|
||||
this.numBlindVotes = numBlindVotes;
|
||||
}
|
||||
|
||||
@ -43,14 +43,12 @@ public final class BlindVoteStateHash extends StateHash {
|
||||
return protobuf.BlindVoteStateHash.newBuilder()
|
||||
.setHeight(height)
|
||||
.setHash(ByteString.copyFrom(hash))
|
||||
.setPrevHash(ByteString.copyFrom(prevHash))
|
||||
.setNumBlindVotes(numBlindVotes).build();
|
||||
}
|
||||
|
||||
public static BlindVoteStateHash fromProto(protobuf.BlindVoteStateHash proto) {
|
||||
return new BlindVoteStateHash(proto.getHeight(),
|
||||
proto.getHash().toByteArray(),
|
||||
proto.getPrevHash().toByteArray(),
|
||||
proto.getNumBlindVotes());
|
||||
}
|
||||
|
||||
|
@ -18,12 +18,14 @@
|
||||
package bisq.core.dao.monitoring.model;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DaoStateBlock extends StateBlock<DaoStateHash> {
|
||||
public DaoStateBlock(DaoStateHash myDaoStateHash) {
|
||||
super(myDaoStateHash);
|
||||
}
|
||||
|
||||
public boolean isSelfCreated() {
|
||||
return myStateHash.isSelfCreated();
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,17 @@ package bisq.core.dao.monitoring.model;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class DaoStateHash extends StateHash {
|
||||
public DaoStateHash(int height, byte[] hash, byte[] prevHash) {
|
||||
super(height, hash, prevHash);
|
||||
// If we have built the hash by ourself 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) {
|
||||
super(height, hash);
|
||||
this.isSelfCreated = isSelfCreated;
|
||||
}
|
||||
|
||||
|
||||
@ -38,12 +44,18 @@ public final class DaoStateHash extends StateHash {
|
||||
return protobuf.DaoStateHash.newBuilder()
|
||||
.setHeight(height)
|
||||
.setHash(ByteString.copyFrom(hash))
|
||||
.setPrevHash(ByteString.copyFrom(prevHash)).build();
|
||||
.setIsSelfCreated(isSelfCreated)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DaoStateHash fromProto(protobuf.DaoStateHash proto) {
|
||||
return new DaoStateHash(proto.getHeight(),
|
||||
proto.getHash().toByteArray(),
|
||||
proto.getPrevHash().toByteArray());
|
||||
return new DaoStateHash(proto.getHeight(), proto.getHash().toByteArray(), proto.getIsSelfCreated());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DaoStateHash{" +
|
||||
"\r\n isSelfCreated=" + isSelfCreated +
|
||||
"\r\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ public final class ProposalStateHash extends StateHash {
|
||||
@Getter
|
||||
private final int numProposals;
|
||||
|
||||
public ProposalStateHash(int cycleStartBlockHeight, byte[] hash, byte[] prevHash, int numProposals) {
|
||||
super(cycleStartBlockHeight, hash, prevHash);
|
||||
public ProposalStateHash(int cycleStartBlockHeight, byte[] hash, int numProposals) {
|
||||
super(cycleStartBlockHeight, hash);
|
||||
this.numProposals = numProposals;
|
||||
}
|
||||
|
||||
@ -43,14 +43,12 @@ public final class ProposalStateHash extends StateHash {
|
||||
return protobuf.ProposalStateHash.newBuilder()
|
||||
.setHeight(height)
|
||||
.setHash(ByteString.copyFrom(hash))
|
||||
.setPrevHash(ByteString.copyFrom(prevHash))
|
||||
.setNumProposals(numProposals).build();
|
||||
}
|
||||
|
||||
public static ProposalStateHash fromProto(protobuf.ProposalStateHash proto) {
|
||||
return new ProposalStateHash(proto.getHeight(),
|
||||
proto.getHash().toByteArray(),
|
||||
proto.getPrevHash().toByteArray(),
|
||||
proto.getNumProposals());
|
||||
}
|
||||
|
||||
|
@ -56,10 +56,6 @@ public abstract class StateBlock<T extends StateHash> {
|
||||
return myStateHash.getHash();
|
||||
}
|
||||
|
||||
public byte[] getPrevHash() {
|
||||
return myStateHash.getPrevHash();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StateBlock{" +
|
||||
|
@ -40,13 +40,10 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public abstract class StateHash implements PersistablePayload, NetworkPayload {
|
||||
protected final int height;
|
||||
protected final byte[] hash;
|
||||
// For first block the prevHash is an empty byte array
|
||||
protected final byte[] prevHash;
|
||||
|
||||
StateHash(int height, byte[] hash, byte[] prevHash) {
|
||||
StateHash(int height, byte[] hash) {
|
||||
this.height = height;
|
||||
this.hash = hash;
|
||||
this.prevHash = prevHash;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -67,7 +64,6 @@ public abstract class StateHash implements PersistablePayload, NetworkPayload {
|
||||
return "StateHash{" +
|
||||
"\n height=" + height +
|
||||
",\n hash=" + Utilities.bytesAsHexString(hash) +
|
||||
",\n prevHash=" + Utilities.bytesAsHexString(prevHash) +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,8 @@ public abstract class StateNetworkService<Msg extends NewStateHashMessage,
|
||||
|
||||
protected abstract Msg getNewStateHashMessage(StH myStateHash);
|
||||
|
||||
protected abstract Han getRequestStateHashesHandler(NodeAddress nodeAddress, RequestStateHashesHandler.Listener<Res> listener);
|
||||
protected abstract Han getRequestStateHashesHandler(NodeAddress nodeAddress,
|
||||
RequestStateHashesHandler.Listener<Res> listener);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -155,8 +156,7 @@ public abstract class StateNetworkService<Msg extends NewStateHashMessage,
|
||||
}
|
||||
|
||||
public void broadcastMyStateHash(StH myStateHash) {
|
||||
NewStateHashMessage newStateHashMessage = getNewStateHashMessage(myStateHash);
|
||||
broadcaster.broadcast(newStateHashMessage, networkNode.getNodeAddress());
|
||||
broadcaster.broadcast(getNewStateHashMessage(myStateHash), networkNode.getNodeAddress());
|
||||
}
|
||||
|
||||
public void requestHashes(int fromHeight, String peersAddress) {
|
||||
@ -167,6 +167,10 @@ public abstract class StateNetworkService<Msg extends NewStateHashMessage,
|
||||
requestStateHashHandlerMap.clear();
|
||||
}
|
||||
|
||||
public boolean isSeedNode(NodeAddress nodeAddress) {
|
||||
return peerManager.isSeedNode(nodeAddress);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listeners
|
||||
|
@ -135,10 +135,11 @@ public class FullNode extends BsqNode {
|
||||
protected void onParseBlockChainComplete() {
|
||||
super.onParseBlockChainComplete();
|
||||
|
||||
if (p2pNetworkReady)
|
||||
if (p2pNetworkReady) {
|
||||
addBlockHandler();
|
||||
else
|
||||
} else {
|
||||
log.info("onParseBlockChainComplete but P2P network is not ready yet.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -222,7 +222,7 @@ public class LiteNode extends BsqNode {
|
||||
|
||||
runDelayedBatchProcessing(new ArrayList<>(blockList),
|
||||
() -> {
|
||||
log.info("runDelayedBatchProcessing Parsing {} blocks took {} seconds.", blockList.size(),
|
||||
log.info("Parsing {} blocks took {} seconds.", blockList.size(),
|
||||
(System.currentTimeMillis() - ts) / 1000d);
|
||||
// We only request again if wallet is synced, otherwise we would get repeated calls we want to avoid.
|
||||
// We deal with that case at the setupWalletBestBlockListener method above.
|
||||
|
@ -200,6 +200,7 @@ public class LiteNodeNetworkService implements MessageListener, ConnectionListen
|
||||
|
||||
@Override
|
||||
public void onAllConnectionsLost() {
|
||||
log.info("onAllConnectionsLost");
|
||||
closeAllHandlers();
|
||||
stopRetryTimer();
|
||||
stopped = true;
|
||||
@ -208,6 +209,7 @@ public class LiteNodeNetworkService implements MessageListener, ConnectionListen
|
||||
|
||||
@Override
|
||||
public void onNewConnectionAfterAllConnectionsLost() {
|
||||
log.info("onNewConnectionAfterAllConnectionsLost");
|
||||
closeAllHandlers();
|
||||
stopped = false;
|
||||
tryWithNewSeedNode(lastRequestedBlockHeight);
|
||||
|
@ -24,7 +24,6 @@ import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.util.GcUtil;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
@ -113,10 +112,12 @@ public class BlockParser {
|
||||
.ifPresent(tx -> daoStateService.onNewTxForLastBlock(block, tx)));
|
||||
|
||||
daoStateService.onParseBlockComplete(block);
|
||||
log.info("Parsing {} transactions at block height {} took {} ms", rawBlock.getRawTxs().size(),
|
||||
blockHeight, System.currentTimeMillis() - startTs);
|
||||
long duration = System.currentTimeMillis() - startTs;
|
||||
if (duration > 10) {
|
||||
log.info("Parsing {} transactions at block height {} took {} ms", rawBlock.getRawTxs().size(),
|
||||
blockHeight, duration);
|
||||
}
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
return block;
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,13 @@
|
||||
|
||||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.core.dao.DaoSetupService;
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
import bisq.core.dao.state.model.DaoState;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
import bisq.core.dao.state.storage.DaoStateStorageService;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.util.GcUtil;
|
||||
@ -49,13 +51,14 @@ import javax.annotation.Nullable;
|
||||
* SNAPSHOT_GRID old not less than 2 times the SNAPSHOT_GRID old.
|
||||
*/
|
||||
@Slf4j
|
||||
public class DaoStateSnapshotService {
|
||||
public class DaoStateSnapshotService implements DaoSetupService, DaoStateListener {
|
||||
private static final int SNAPSHOT_GRID = 20;
|
||||
|
||||
private final DaoStateService daoStateService;
|
||||
private final GenesisTxInfo genesisTxInfo;
|
||||
private final DaoStateStorageService daoStateStorageService;
|
||||
private final DaoStateMonitoringService daoStateMonitoringService;
|
||||
private final Preferences preferences;
|
||||
private final File storageDir;
|
||||
|
||||
private DaoState daoStateSnapshotCandidate;
|
||||
@ -64,7 +67,8 @@ public class DaoStateSnapshotService {
|
||||
@Setter
|
||||
@Nullable
|
||||
private Runnable daoRequiresRestartHandler;
|
||||
private boolean requestPersistenceCalled;
|
||||
private boolean readyForPersisting = true;
|
||||
private boolean isParseBlockChainComplete;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -76,21 +80,86 @@ public class DaoStateSnapshotService {
|
||||
GenesisTxInfo genesisTxInfo,
|
||||
DaoStateStorageService daoStateStorageService,
|
||||
DaoStateMonitoringService daoStateMonitoringService,
|
||||
Preferences preferences,
|
||||
@Named(Config.STORAGE_DIR) File storageDir) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
this.daoStateStorageService = daoStateStorageService;
|
||||
this.daoStateMonitoringService = daoStateMonitoringService;
|
||||
this.preferences = preferences;
|
||||
this.storageDir = storageDir;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoSetupService
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void addListeners() {
|
||||
daoStateService.addDaoStateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We listen onDaoStateChanged to ensure the dao state has been processed from listener clients after parsing.
|
||||
// We need to listen during batch processing as well to write snapshots during that process.
|
||||
@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.
|
||||
// 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.
|
||||
if (preferences.isUseFullModeDaoMonitor() || isParseBlockChainComplete) {
|
||||
// We need to execute first the daoStateMonitoringService.createHashFromBlock to get the hash created
|
||||
daoStateMonitoringService.createHashFromBlock(block);
|
||||
maybeCreateSnapshot(block);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onParseBlockChainComplete() {
|
||||
isParseBlockChainComplete = true;
|
||||
|
||||
// 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
|
||||
// the seed node and applied the latest hash. After that we are ready to make a snapshot and persist it.
|
||||
daoStateMonitoringService.setCreateSnapshotHandler(() -> {
|
||||
// As we did not have created any snapshots during initial parsing we create it now. We cannot use the past
|
||||
// snapshot height as we have not cloned a candidate (that would cause quite some delay during parsing).
|
||||
// The next snapshots will be created again according to the snapshot height grid (each 20 blocks).
|
||||
// This also comes with the improvement that the user does not need to load the past blocks back to the last
|
||||
// snapshot height. Though it comes also with the small risk that in case of re-orgs the user need to do
|
||||
// a resync in case the dao state would have been affected by that reorg.
|
||||
long ts = System.currentTimeMillis();
|
||||
// We do not keep a copy of the clone as we use it immediately for persistence.
|
||||
GcUtil.maybeReleaseMemory();
|
||||
log.info("Create snapshot at height {}", daoStateService.getChainHeight());
|
||||
daoStateStorageService.requestPersistence(daoStateService.getClone(),
|
||||
new LinkedList<>(daoStateMonitoringService.getDaoStateHashChain()),
|
||||
() -> {
|
||||
log.info("Persisted daoState after parsing completed at height {}. Took {} ms",
|
||||
daoStateService.getChainHeight(), System.currentTimeMillis() - ts);
|
||||
});
|
||||
GcUtil.maybeReleaseMemory();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We do not use DaoStateListener.onDaoStateChanged but let the DaoEventCoordinator call maybeCreateSnapshot to ensure the
|
||||
// correct order of execution.
|
||||
// We need to process during batch processing as well to write snapshots during that process.
|
||||
public void maybeCreateSnapshot(Block block) {
|
||||
int chainHeight = block.getHeight();
|
||||
@ -107,42 +176,68 @@ public class DaoStateSnapshotService {
|
||||
// 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 (requestPersistenceCalled) {
|
||||
log.warn("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.");
|
||||
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;
|
||||
}
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
|
||||
// At trigger event we store the latest snapshotCandidate to disc
|
||||
long ts = System.currentTimeMillis();
|
||||
requestPersistenceCalled = true;
|
||||
daoStateStorageService.requestPersistence(daoStateSnapshotCandidate,
|
||||
daoStateHashChainSnapshotCandidate,
|
||||
() -> {
|
||||
log.info("Serializing snapshotCandidate for writing to Disc with height {} at height {} took {} ms",
|
||||
daoStateSnapshotCandidate != null ? daoStateSnapshotCandidate.getChainHeight() : "N/A",
|
||||
chainHeight,
|
||||
System.currentTimeMillis() - ts);
|
||||
|
||||
long ts2 = System.currentTimeMillis();
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
|
||||
// Now we clone and keep it in memory for the next trigger event
|
||||
daoStateSnapshotCandidate = daoStateService.getClone();
|
||||
daoStateHashChainSnapshotCandidate = new LinkedList<>(daoStateMonitoringService.getDaoStateHashChain());
|
||||
|
||||
log.info("Cloned new snapshotCandidate at height {} took {} ms", chainHeight, System.currentTimeMillis() - ts2);
|
||||
requestPersistenceCalled = false;
|
||||
GcUtil.maybeReleaseMemory();
|
||||
});
|
||||
if (daoStateSnapshotCandidate != null) {
|
||||
persist();
|
||||
} else {
|
||||
createClones();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void persist() {
|
||||
long ts = System.currentTimeMillis();
|
||||
readyForPersisting = false;
|
||||
daoStateStorageService.requestPersistence(daoStateSnapshotCandidate,
|
||||
daoStateHashChainSnapshotCandidate,
|
||||
() -> {
|
||||
log.info("Serializing snapshotCandidate for writing to Disc at chainHeight {} took {} ms.\n" +
|
||||
"daoStateSnapshotCandidate.height={};\n" +
|
||||
"daoStateHashChainSnapshotCandidate.height={}",
|
||||
daoStateService.getChainHeight(),
|
||||
System.currentTimeMillis() - ts,
|
||||
daoStateSnapshotCandidate != null ? daoStateSnapshotCandidate.getChainHeight() : "N/A",
|
||||
daoStateHashChainSnapshotCandidate != null && !daoStateHashChainSnapshotCandidate.isEmpty() ?
|
||||
daoStateHashChainSnapshotCandidate.getLast().getHeight() : "N/A");
|
||||
|
||||
createClones();
|
||||
readyForPersisting = true;
|
||||
});
|
||||
}
|
||||
|
||||
private void createClones() {
|
||||
long ts = System.currentTimeMillis();
|
||||
// Now we clone and keep it in memory for the next trigger event
|
||||
// We do not fit into the target grid of 20 blocks as we get called here once persistence is
|
||||
// done from the write thread (mapped back to user thread).
|
||||
// As we want to prevent to maintain 2 clones we prefer that strategy. If we would do the clone
|
||||
// after the persist call we would keep an additional copy in memory.
|
||||
daoStateSnapshotCandidate = daoStateService.getClone();
|
||||
daoStateHashChainSnapshotCandidate = new LinkedList<>(daoStateMonitoringService.getDaoStateHashChain());
|
||||
GcUtil.maybeReleaseMemory();
|
||||
|
||||
log.info("Cloned new snapshotCandidate at chainHeight {} took {} ms.\n" +
|
||||
"daoStateSnapshotCandidate.height={};\n" +
|
||||
"daoStateHashChainSnapshotCandidate.height={}",
|
||||
daoStateService.getChainHeight(), System.currentTimeMillis() - ts,
|
||||
daoStateSnapshotCandidate != null ? daoStateSnapshotCandidate.getChainHeight() : "N/A",
|
||||
daoStateHashChainSnapshotCandidate != null && !daoStateHashChainSnapshotCandidate.isEmpty() ?
|
||||
daoStateHashChainSnapshotCandidate.getLast().getHeight() : "N/A");
|
||||
}
|
||||
|
||||
public void applySnapshot(boolean fromReorg) {
|
||||
DaoState persistedBsqState = daoStateStorageService.getPersistedBsqState();
|
||||
LinkedList<DaoStateHash> persistedDaoStateHashChain = daoStateStorageService.getPersistedDaoStateHashChain();
|
||||
@ -156,6 +251,7 @@ public class DaoStateSnapshotService {
|
||||
chainHeightOfLastApplySnapshot = chainHeightOfPersisted;
|
||||
daoStateService.applySnapshot(persistedBsqState);
|
||||
daoStateMonitoringService.applySnapshot(persistedDaoStateHashChain);
|
||||
daoStateStorageService.pruneStore();
|
||||
} else {
|
||||
// The reorg might have been caused by the previous parsing which might contains a range of
|
||||
// blocks.
|
||||
|
@ -27,6 +27,7 @@ import bisq.network.p2p.storage.persistence.StoreService;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.file.FileUtil;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.util.GcUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
@ -92,15 +93,20 @@ public class DaoStateStorageService extends StoreService<DaoStateStore> {
|
||||
new Thread(() -> {
|
||||
Thread.currentThread().setName("Serialize and write DaoState");
|
||||
persistenceManager.persistNow(() -> {
|
||||
// After we have written to disk we remove the the daoState in the store to avoid that it stays in
|
||||
// After we have written to disk we remove the daoState in the store to avoid that it stays in
|
||||
// memory there until the next persist call.
|
||||
store.setDaoState(null);
|
||||
|
||||
pruneStore();
|
||||
completeHandler.run();
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void pruneStore() {
|
||||
store.setDaoState(null);
|
||||
store.setDaoStateHashChain(null);
|
||||
GcUtil.maybeReleaseMemory();
|
||||
}
|
||||
|
||||
public DaoState getPersistedBsqState() {
|
||||
return store.getDaoState();
|
||||
}
|
||||
|
@ -82,7 +82,6 @@ public class TradeStatisticsManager {
|
||||
this.storageDir = storageDir;
|
||||
this.dumpStatistics = dumpStatistics;
|
||||
|
||||
|
||||
appendOnlyDataStoreService.addService(tradeStatistics3StorageService);
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
|
||||
|
||||
// We set serviceAddresses at request time. If user changes AutoConfirmSettings after request has started
|
||||
// it will have no impact on serviceAddresses and numRequiredSuccessResults.
|
||||
// Thought numRequiredConfirmations can be changed during request process and will be read from
|
||||
// Though numRequiredConfirmations can be changed during request process and will be read from
|
||||
// autoConfirmSettings at result parsing.
|
||||
List<String> serviceAddresses = autoConfirmSettings.getServiceAddresses();
|
||||
numRequiredSuccessResults = serviceAddresses.size();
|
||||
|
@ -800,6 +800,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
public void setUseFullModeDaoMonitor(boolean value) {
|
||||
prefPayload.setUseFullModeDaoMonitor(value);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getter
|
||||
@ -1115,5 +1120,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
||||
void setDenyApiTaker(boolean value);
|
||||
|
||||
void setNotifyOnPreRelease(boolean value);
|
||||
|
||||
void setUseFullModeDaoMonitor(boolean value);
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
||||
private boolean showOffersMatchingMyAccounts;
|
||||
private boolean denyApiTaker;
|
||||
private boolean notifyOnPreRelease;
|
||||
private boolean useFullModeDaoMonitor;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -201,7 +202,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
||||
.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods)
|
||||
.setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts)
|
||||
.setDenyApiTaker(denyApiTaker)
|
||||
.setNotifyOnPreRelease(notifyOnPreRelease);
|
||||
.setNotifyOnPreRelease(notifyOnPreRelease)
|
||||
.setUseFullModeDaoMonitor(useFullModeDaoMonitor);
|
||||
|
||||
Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory);
|
||||
Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage()));
|
||||
@ -299,7 +301,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
||||
proto.getHideNonAccountPaymentMethods(),
|
||||
proto.getShowOffersMatchingMyAccounts(),
|
||||
proto.getDenyApiTaker(),
|
||||
proto.getNotifyOnPreRelease()
|
||||
proto.getNotifyOnPreRelease(),
|
||||
proto.getUseFullModeDaoMonitor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1351,6 +1351,18 @@ setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdow
|
||||
setting.preferences.dao.isDaoFullNode=Run Bisq as DAO full node
|
||||
setting.preferences.dao.activated=DAO activated
|
||||
setting.preferences.dao.activated.popup=The change will be applied after a restart
|
||||
|
||||
setting.preferences.dao.fullModeDaoMonitor=Full-mode DAO state monitoring
|
||||
setting.preferences.dao.fullModeDaoMonitor.popup=If full-mode DAO state monitoring is activated the DAO state \
|
||||
hashes are created during parsing the BSQ blocks. This comes with considerable performance costs at the initial DAO sync.\n\n\
|
||||
For users who are regularily using Bisq this should not be an issue as there are not many blocks pending for parsing, though for \
|
||||
users who only use Bisq casually creating the DAO state hashes for 100s or 1000s of blocks degrades heavily the user experience.\n\n\
|
||||
In case full-mode is deactivated (default) the missing DAO state hashes are requested from network nodes and \
|
||||
the DAO state hash based on the most recent block will be created by the user. As all hashes are connected by \
|
||||
reference to the previous hash a correct hash at the chain tip means that all past hashes are correct as well. The \
|
||||
main functionality of the DAO state monitoring - to detect if the local DAO state is out of sync with the rest of the network - \
|
||||
is therefore still fulfilled.
|
||||
|
||||
setting.preferences.dao.rpcUser=RPC username
|
||||
setting.preferences.dao.rpcPw=RPC password
|
||||
setting.preferences.dao.blockNotifyPort=Block notify port
|
||||
@ -2499,6 +2511,9 @@ dao.monitor.proposals=Proposals state
|
||||
dao.monitor.blindVotes=Blind votes state
|
||||
|
||||
dao.monitor.table.peers=Peers
|
||||
dao.monitor.table.hashCreator=Hash creator
|
||||
dao.monitor.table.hashCreator.self=Self
|
||||
dao.monitor.table.hashCreator.peer=Peer
|
||||
dao.monitor.table.conflicts=Conflicts
|
||||
dao.monitor.state=Status
|
||||
dao.monitor.requestAlHashes=Request all hashes
|
||||
@ -2532,6 +2547,8 @@ dao.monitor.isInConflictWithSeedNode=Your local data is not in consensus with at
|
||||
Please resync the DAO state.
|
||||
dao.monitor.isInConflictWithNonSeedNode=One of your peers is not in consensus with the network but your node \
|
||||
is in sync with the seed nodes.
|
||||
dao.monitor.isDaoStateBlockChainNotConnecting=Your DAO state chain is not connecting with the new data. \
|
||||
Please resync the DAO state.
|
||||
dao.monitor.daoStateInSync=Your local node is in consensus with the network
|
||||
|
||||
dao.monitor.blindVote.headline=Blind votes state
|
||||
|
@ -38,6 +38,7 @@ public class DaoStateSnapshotServiceTest {
|
||||
mock(GenesisTxInfo.class),
|
||||
mock(DaoStateStorageService.class),
|
||||
mock(DaoStateMonitoringService.class),
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
@ -416,7 +416,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onChangeAfterBatchProcessing() {
|
||||
public void onDaoStateHashesChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -36,10 +36,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public abstract class StateBlockListItem<StH extends StateHash, StB extends StateBlock<StH>> {
|
||||
private final StateBlock<StH> stateBlock;
|
||||
protected final StateBlock<StH> stateBlock;
|
||||
private final Supplier<String> height;
|
||||
private final String hash;
|
||||
private final String prevHash;
|
||||
private final String numNetworkMessages;
|
||||
private final String numMisMatches;
|
||||
private final boolean isInSync;
|
||||
@ -58,7 +57,6 @@ public abstract class StateBlockListItem<StH extends StateHash, StB extends Stat
|
||||
Res.get("dao.monitor.table.cycleBlockHeight", cycleIndexSupplier.getAsInt() + 1,
|
||||
String.valueOf(stateBlock.getHeight())))::get;
|
||||
hash = Utilities.bytesAsHexString(stateBlock.getHash());
|
||||
prevHash = stateBlock.getPrevHash().length > 0 ? Utilities.bytesAsHexString(stateBlock.getPrevHash()) : "-";
|
||||
numNetworkMessages = String.valueOf(stateBlock.getPeersMap().size());
|
||||
int size = stateBlock.getInConflictMap().size();
|
||||
numMisMatches = String.valueOf(size);
|
||||
|
@ -38,7 +38,6 @@ public abstract class StateInConflictListItem<T extends StateHash> {
|
||||
private final String peerAddressString;
|
||||
private final String height;
|
||||
private final String hash;
|
||||
private final String prevHash;
|
||||
private final T stateHash;
|
||||
|
||||
protected StateInConflictListItem(String peerAddress, T stateHash, int cycleIndex,
|
||||
@ -52,7 +51,5 @@ public abstract class StateInConflictListItem<T extends StateHash> {
|
||||
cycleIndex + 1,
|
||||
String.valueOf(stateHash.getHeight()));
|
||||
hash = Utilities.bytesAsHexString(stateHash.getHash());
|
||||
prevHash = stateHash.getPrevHash().length > 0 ?
|
||||
Utilities.bytesAsHexString(stateHash.getPrevHash()) : "-";
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ public abstract class StateMonitorView<StH extends StateHash,
|
||||
private Subscription selectedItemSubscription;
|
||||
protected final BooleanProperty isInConflictWithNonSeedNode = new SimpleBooleanProperty();
|
||||
protected final BooleanProperty isInConflictWithSeedNode = new SimpleBooleanProperty();
|
||||
protected final BooleanProperty isDaoStateBlockChainNotConnecting = new SimpleBooleanProperty();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -134,8 +135,10 @@ public abstract class StateMonitorView<StH extends StateHash,
|
||||
|
||||
daoStateService.addDaoStateListener(this);
|
||||
|
||||
resyncButton.visibleProperty().bind(isInConflictWithSeedNode);
|
||||
resyncButton.managedProperty().bind(isInConflictWithSeedNode);
|
||||
resyncButton.visibleProperty().bind(isInConflictWithSeedNode
|
||||
.or(isDaoStateBlockChainNotConnecting));
|
||||
resyncButton.managedProperty().bind(isInConflictWithSeedNode
|
||||
.or(isDaoStateBlockChainNotConnecting));
|
||||
|
||||
resyncButton.setOnAction(ev -> resyncDaoState());
|
||||
|
||||
@ -177,8 +180,6 @@ public abstract class StateMonitorView<StH extends StateHash,
|
||||
|
||||
protected abstract String getPeersTableHeader();
|
||||
|
||||
protected abstract String getPrevHashTableHeader();
|
||||
|
||||
protected abstract String getHashTableHeader();
|
||||
|
||||
protected abstract String getBlockHeightTableHeader();
|
||||
@ -272,6 +273,9 @@ public abstract class StateMonitorView<StH extends StateHash,
|
||||
} else if (isInConflictWithNonSeedNode.get()) {
|
||||
statusTextField.setText(Res.get("dao.monitor.isInConflictWithNonSeedNode"));
|
||||
statusTextField.getStyleClass().remove("dao-inConflict");
|
||||
} else if (isDaoStateBlockChainNotConnecting.get()) {
|
||||
statusTextField.setText(Res.get("dao.monitor.isDaoStateBlockChainNotConnecting"));
|
||||
statusTextField.getStyleClass().add("dao-inConflict");
|
||||
} else {
|
||||
statusTextField.setText(Res.get("dao.monitor.daoStateInSync"));
|
||||
statusTextField.getStyleClass().remove("dao-inConflict");
|
||||
@ -328,7 +332,6 @@ public abstract class StateMonitorView<StH extends StateHash,
|
||||
tableView.getSortOrder().add(column);
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getHashTableHeader());
|
||||
column.setMinWidth(120);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
@ -353,31 +356,6 @@ public abstract class StateMonitorView<StH extends StateHash,
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getPrevHashTableHeader());
|
||||
column.setMinWidth(120);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public TableCell<BLI, BLI> call(TableColumn<BLI,
|
||||
BLI> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final BLI item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.getPrevHash());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(BLI::getPrevHash));
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getPeersTableHeader());
|
||||
column.setMinWidth(80);
|
||||
column.setMaxWidth(column.getMinWidth());
|
||||
@ -543,30 +521,6 @@ public abstract class StateMonitorView<StH extends StateHash,
|
||||
conflictTableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getPrevHashTableHeader());
|
||||
column.setMinWidth(120);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<CLI, CLI> call(
|
||||
TableColumn<CLI, CLI> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final CLI item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.getPrevHash());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(CLI::getPrevHash));
|
||||
conflictTableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>("");
|
||||
column.setMinWidth(120);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
|
@ -149,11 +149,6 @@ public class BlindVoteStateMonitorView extends StateMonitorView<BlindVoteStateHa
|
||||
return Res.get("dao.monitor.table.peers");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrevHashTableHeader() {
|
||||
return Res.get("dao.monitor.blindVote.table.prev");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHashTableHeader() {
|
||||
return Res.get("dao.monitor.blindVote.table.hash");
|
||||
@ -221,7 +216,6 @@ public class BlindVoteStateMonitorView extends StateMonitorView<BlindVoteStateHa
|
||||
tableView.getColumns().add(1, column);
|
||||
}
|
||||
|
||||
|
||||
protected void createConflictColumns() {
|
||||
super.createConflictColumns();
|
||||
|
||||
|
@ -21,6 +21,7 @@ import bisq.desktop.main.dao.monitor.StateBlockListItem;
|
||||
|
||||
import bisq.core.dao.monitoring.model.DaoStateBlock;
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
@ -35,4 +36,10 @@ class DaoStateBlockListItem extends StateBlockListItem<DaoStateHash, DaoStateBlo
|
||||
DaoStateBlockListItem(DaoStateBlock stateBlock, IntSupplier cycleIndexSupplier) {
|
||||
super(stateBlock, cycleIndexSupplier);
|
||||
}
|
||||
|
||||
String hashCreator() {
|
||||
return ((DaoStateBlock) stateBlock).isSelfCreated() ?
|
||||
Res.get("dao.monitor.table.hashCreator.self") :
|
||||
Res.get("dao.monitor.table.hashCreator.peer");
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.desktop.main.dao.monitor.daostate;
|
||||
|
||||
import bisq.desktop.common.view.FxmlView;
|
||||
import bisq.desktop.components.AutoTooltipTableColumn;
|
||||
import bisq.desktop.main.dao.monitor.StateMonitorView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
@ -40,10 +41,18 @@ import bisq.common.util.Utilities;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.stream.Collectors;
|
||||
@ -60,7 +69,6 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@Inject
|
||||
private DaoStateMonitorView(DaoStateService daoStateService,
|
||||
DaoFacade daoFacade,
|
||||
@ -111,7 +119,7 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onChangeAfterBatchProcessing() {
|
||||
public void onDaoStateHashesChanged() {
|
||||
if (daoStateService.isParseBlockChainComplete()) {
|
||||
onDataUpdate();
|
||||
}
|
||||
@ -160,11 +168,6 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
||||
return Res.get("dao.monitor.table.peers");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrevHashTableHeader() {
|
||||
return Res.get("dao.monitor.daoState.table.prev");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHashTableHeader() {
|
||||
return Res.get("dao.monitor.daoState.table.hash");
|
||||
@ -189,6 +192,7 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
||||
protected void onDataUpdate() {
|
||||
isInConflictWithSeedNode.set(daoStateMonitoringService.isInConflictWithSeedNode());
|
||||
isInConflictWithNonSeedNode.set(daoStateMonitoringService.isInConflictWithNonSeedNode());
|
||||
isDaoStateBlockChainNotConnecting.set(daoStateMonitoringService.isDaoStateBlockChainNotConnecting());
|
||||
|
||||
listItems.setAll(daoStateMonitoringService.getDaoStateBlockChain().stream()
|
||||
.map(this::getStateBlockListItem)
|
||||
@ -202,6 +206,35 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
||||
daoStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createColumns() {
|
||||
super.createColumns();
|
||||
|
||||
TableColumn<DaoStateBlockListItem, DaoStateBlockListItem> column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.table.hashCreator"));
|
||||
column.setMinWidth(90);
|
||||
column.setMaxWidth(column.getMinWidth());
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<DaoStateBlockListItem, DaoStateBlockListItem> call(
|
||||
TableColumn<DaoStateBlockListItem, DaoStateBlockListItem> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final DaoStateBlockListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.hashCreator());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getPeersMap().size()));
|
||||
tableView.getColumns().add(2, column);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
@ -147,11 +147,6 @@ public class ProposalStateMonitorView extends StateMonitorView<ProposalStateHash
|
||||
return Res.get("dao.monitor.table.peers");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrevHashTableHeader() {
|
||||
return Res.get("dao.monitor.proposal.table.prev");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHashTableHeader() {
|
||||
return Res.get("dao.monitor.proposal.table.hash");
|
||||
@ -219,7 +214,6 @@ public class ProposalStateMonitorView extends StateMonitorView<ProposalStateHash
|
||||
tableView.getColumns().add(1, column);
|
||||
}
|
||||
|
||||
|
||||
protected void createConflictColumns() {
|
||||
super.createConflictColumns();
|
||||
|
||||
|
@ -48,6 +48,7 @@ import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.user.DontShowAgainLookup;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
@ -122,13 +123,12 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
|
||||
private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically,
|
||||
avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle,
|
||||
notifyOnPreReleaseToggle;
|
||||
notifyOnPreReleaseToggle, isDaoFullNodeToggleButton, daoActivatedToggleButton, fullModeDaoMonitorToggleButton;
|
||||
private int gridRow = 0;
|
||||
private int displayCurrenciesGridRowIndex = 0;
|
||||
private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField,
|
||||
autoConfRequiredConfirmationsTf, autoConfServiceAddressTf, autoConfTradeLimitTf, /*referralIdInputTextField,*/
|
||||
autoConfRequiredConfirmationsTf, autoConfServiceAddressTf, autoConfTradeLimitTf,
|
||||
rpcUserTextField, blockNotifyPortTextField;
|
||||
private ToggleButton isDaoFullNodeToggleButton, daoActivatedToggleButton;
|
||||
private PasswordTextField rpcPwTextField;
|
||||
private TitledGroupBg daoOptionsTitledGroupBg;
|
||||
|
||||
@ -623,7 +623,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
}
|
||||
|
||||
private void initializeDaoOptions() {
|
||||
int rowSpan = DevEnv.isDaoActivated() ? 5 : 1;
|
||||
int rowSpan = DevEnv.isDaoActivated() ? 6 : 1;
|
||||
daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, rowSpan,
|
||||
Res.get("setting.preferences.daoOptions"), Layout.GROUP_DISTANCE);
|
||||
daoActivatedToggleButton = addSlideToggleButton(root, gridRow,
|
||||
@ -632,6 +632,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
return;
|
||||
}
|
||||
|
||||
fullModeDaoMonitorToggleButton = addSlideToggleButton(root, ++gridRow,
|
||||
Res.get("setting.preferences.dao.fullModeDaoMonitor"));
|
||||
|
||||
resyncDaoFromResourcesButton = addButton(root, ++gridRow, Res.get("setting.preferences.dao.resyncFromResources.label"));
|
||||
resyncDaoFromResourcesButton.setMaxWidth(Double.MAX_VALUE);
|
||||
GridPane.setHgrow(resyncDaoFromResourcesButton, Priority.ALWAYS);
|
||||
@ -1021,6 +1024,21 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
return;
|
||||
}
|
||||
|
||||
fullModeDaoMonitorToggleButton.setSelected(preferences.isUseFullModeDaoMonitor());
|
||||
fullModeDaoMonitorToggleButton.setOnAction(e -> {
|
||||
preferences.setUseFullModeDaoMonitor(fullModeDaoMonitorToggleButton.isSelected());
|
||||
if (fullModeDaoMonitorToggleButton.isSelected()) {
|
||||
String key = "fullModeDaoMonitor";
|
||||
if (DontShowAgainLookup.showAgain(key)) {
|
||||
new Popup().information(Res.get("setting.preferences.dao.fullModeDaoMonitor.popup"))
|
||||
.width(1000)
|
||||
.dontShowAgainId(key)
|
||||
.closeButtonText(Res.get("shared.iUnderstand"))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
boolean daoFullNode = preferences.isDaoFullNode();
|
||||
isDaoFullNodeToggleButton.setSelected(daoFullNode);
|
||||
|
||||
@ -1173,6 +1191,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
return;
|
||||
}
|
||||
|
||||
fullModeDaoMonitorToggleButton.setOnAction(null);
|
||||
resyncDaoFromResourcesButton.setOnAction(null);
|
||||
resyncDaoFromGenesisButton.setOnAction(null);
|
||||
bsqAverageTrimThresholdTextField.textProperty().removeListener(bsqAverageTrimThresholdListener);
|
||||
|
@ -41,7 +41,7 @@ public class DaoStateBlockListItemTest {
|
||||
|
||||
@Test
|
||||
public void testEqualsAndHashCode() {
|
||||
var block = new DaoStateBlock(new DaoStateHash(0, new byte[0], new byte[0]));
|
||||
var block = new DaoStateBlock(new DaoStateHash(0, new byte[0], true));
|
||||
var item1 = new DaoStateBlockListItem(block, newSupplier(1));
|
||||
var item2 = new DaoStateBlockListItem(block, newSupplier(2));
|
||||
var item3 = new DaoStateBlockListItem(block, newSupplier(1));
|
||||
|
@ -1945,6 +1945,7 @@ message PreferencesPayload {
|
||||
bool show_offers_matching_my_accounts = 59;
|
||||
bool deny_api_taker = 60;
|
||||
bool notify_on_pre_release = 61;
|
||||
bool use_full_mode_dao_monitor = 62;
|
||||
}
|
||||
|
||||
message AutoConfirmSettings {
|
||||
@ -2381,20 +2382,21 @@ message DaoStateStore {
|
||||
message DaoStateHash {
|
||||
int32 height = 1;
|
||||
bytes hash = 2;
|
||||
bytes prev_hash = 3;
|
||||
bytes prev_hash = 3 [deprecated = true];
|
||||
bool is_self_created = 4;
|
||||
}
|
||||
|
||||
message ProposalStateHash {
|
||||
int32 height = 1;
|
||||
bytes hash = 2;
|
||||
bytes prev_hash = 3;
|
||||
bytes prev_hash = 3 [deprecated = true];
|
||||
int32 num_proposals = 4;
|
||||
}
|
||||
|
||||
message BlindVoteStateHash {
|
||||
int32 height = 1;
|
||||
bytes hash = 2;
|
||||
bytes prev_hash = 3;
|
||||
bytes prev_hash = 3 [deprecated = true];
|
||||
int32 num_blind_votes = 4;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user