mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 07:07:43 +01:00
Merge pull request #2532 from ManfredKarrer/add-hash-of-dao-state
Add hash of dao state
This commit is contained in:
commit
545eb8c4e7
95 changed files with 5397 additions and 160 deletions
|
@ -31,5 +31,6 @@ public enum Capability {
|
|||
PROPOSAL,
|
||||
BLIND_VOTE,
|
||||
ACK_MSG,
|
||||
BSQ_BLOCK
|
||||
BSQ_BLOCK,
|
||||
DAO_STATE
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ public class Utilities {
|
|||
public static boolean isMacMenuBarDarkMode() {
|
||||
try {
|
||||
// check for exit status only. Once there are more modes than "dark" and "default", we might need to analyze string contents..
|
||||
final Process process = Runtime.getRuntime().exec(new String[] {"defaults", "read", "-g", "AppleInterfaceStyle"});
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"defaults", "read", "-g", "AppleInterfaceStyle"});
|
||||
process.waitFor(100, TimeUnit.MILLISECONDS);
|
||||
return process.exitValue() == 0;
|
||||
} catch (IOException | InterruptedException | IllegalThreadStateException ex) {
|
||||
|
@ -512,15 +512,29 @@ public class Utilities {
|
|||
throw new LimitedKeyStrengthException();
|
||||
}
|
||||
|
||||
public static String toTruncatedString(Object message, int maxLength) {
|
||||
if (message != null) {
|
||||
return StringUtils.abbreviate(message.toString(), maxLength).replace("\n", "");
|
||||
}
|
||||
return "null";
|
||||
public static String toTruncatedString(Object message) {
|
||||
return toTruncatedString(message, 200, true);
|
||||
}
|
||||
|
||||
public static String toTruncatedString(Object message) {
|
||||
return toTruncatedString(message, 200);
|
||||
public static String toTruncatedString(Object message, int maxLength) {
|
||||
return toTruncatedString(message, maxLength, true);
|
||||
}
|
||||
|
||||
public static String toTruncatedString(Object message, boolean removeLinebreaks) {
|
||||
return toTruncatedString(message, 200, removeLinebreaks);
|
||||
}
|
||||
|
||||
public static String toTruncatedString(Object message, int maxLength, boolean removeLinebreaks) {
|
||||
if (message == null)
|
||||
return "null";
|
||||
|
||||
|
||||
String result = StringUtils.abbreviate(message.toString(), maxLength);
|
||||
if (removeLinebreaks)
|
||||
return result.replace("\n", "");
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public static String getRandomPrefix(int minLength, int maxLength) {
|
||||
|
|
|
@ -57,6 +57,15 @@ message NetworkEnvelope {
|
|||
AddPersistableNetworkPayloadMessage add_persistable_network_payload_message = 31;
|
||||
AckMessage ack_message = 32;
|
||||
RepublishGovernanceDataRequest republish_governance_data_request = 33;
|
||||
NewDaoStateHashMessage new_dao_state_hash_message = 34;
|
||||
GetDaoStateHashesRequest get_dao_state_hashes_request = 35;
|
||||
GetDaoStateHashesResponse get_dao_state_hashes_response = 36;
|
||||
NewProposalStateHashMessage new_proposal_state_hash_message = 37;
|
||||
GetProposalStateHashesRequest get_proposal_state_hashes_request = 38;
|
||||
GetProposalStateHashesResponse get_proposal_state_hashes_response = 39;
|
||||
NewBlindVoteStateHashMessage new_blind_vote_state_hash_message = 40;
|
||||
GetBlindVoteStateHashesRequest get_blind_vote_state_hashes_request = 41;
|
||||
GetBlindVoteStateHashesResponse get_blind_vote_state_hashes_response = 42;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,6 +333,48 @@ message NewBlockBroadcastMessage {
|
|||
message RepublishGovernanceDataRequest {
|
||||
}
|
||||
|
||||
message NewDaoStateHashMessage {
|
||||
DaoStateHash state_hash = 1;
|
||||
}
|
||||
|
||||
message NewProposalStateHashMessage {
|
||||
ProposalStateHash state_hash = 1;
|
||||
}
|
||||
|
||||
message NewBlindVoteStateHashMessage {
|
||||
BlindVoteStateHash state_hash = 1;
|
||||
}
|
||||
|
||||
message GetDaoStateHashesRequest {
|
||||
int32 height = 1;
|
||||
int32 nonce = 2;
|
||||
}
|
||||
|
||||
message GetProposalStateHashesRequest {
|
||||
int32 height = 1;
|
||||
int32 nonce = 2;
|
||||
}
|
||||
|
||||
message GetBlindVoteStateHashesRequest {
|
||||
int32 height = 1;
|
||||
int32 nonce = 2;
|
||||
}
|
||||
|
||||
message GetDaoStateHashesResponse {
|
||||
repeated DaoStateHash state_hashes = 1;
|
||||
int32 request_nonce = 2;
|
||||
}
|
||||
|
||||
message GetProposalStateHashesResponse {
|
||||
repeated ProposalStateHash state_hashes = 1;
|
||||
int32 request_nonce = 2;
|
||||
}
|
||||
|
||||
message GetBlindVoteStateHashesResponse {
|
||||
repeated BlindVoteStateHash state_hashes = 1;
|
||||
int32 request_nonce = 2;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Payload
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -915,7 +966,6 @@ message AdvancedCashAccountPayload {
|
|||
string account_nr = 1;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PersistableEnvelope
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1713,6 +1763,27 @@ message DecryptedBallotsWithMerits {
|
|||
|
||||
message DaoStateStore {
|
||||
BsqState bsq_state = 1;
|
||||
repeated DaoStateHash dao_state_hash = 2;
|
||||
}
|
||||
|
||||
message DaoStateHash {
|
||||
int32 height = 1;
|
||||
bytes hash = 2;
|
||||
bytes prev_hash = 3;
|
||||
}
|
||||
|
||||
message ProposalStateHash {
|
||||
int32 height = 1;
|
||||
bytes hash = 2;
|
||||
bytes prev_hash = 3;
|
||||
int32 num_proposals = 4;
|
||||
}
|
||||
|
||||
message BlindVoteStateHash {
|
||||
int32 height = 1;
|
||||
bytes hash = 2;
|
||||
bytes prev_hash = 3;
|
||||
int32 num_blind_votes = 4;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -56,9 +56,12 @@ public class BitcoinModule extends AppModule {
|
|||
protected void configure() {
|
||||
// We we have selected BTC_DAO_TESTNET we use our master regtest node, otherwise the specified host or default
|
||||
// (localhost)
|
||||
String regTestHost = BisqEnvironment.getBaseCurrencyNetwork().isDaoTestNet() ?
|
||||
"104.248.31.39" :
|
||||
environment.getProperty(BtcOptionKeys.REG_TEST_HOST, String.class, RegTestHost.DEFAULT_HOST);
|
||||
String regTestHost = environment.getProperty(BtcOptionKeys.REG_TEST_HOST, String.class, "");
|
||||
if (regTestHost.isEmpty()) {
|
||||
regTestHost = BisqEnvironment.getBaseCurrencyNetwork().isDaoTestNet() ?
|
||||
"104.248.31.39" :
|
||||
RegTestHost.DEFAULT_HOST;
|
||||
}
|
||||
|
||||
RegTestHost.HOST = regTestHost;
|
||||
if (Arrays.asList("localhost", "127.0.0.1").contains(regTestHost)) {
|
||||
|
|
69
core/src/main/java/bisq/core/dao/DaoEventCoordinator.java
Normal file
69
core/src/main/java/bisq/core/dao/DaoEventCoordinator.java
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -64,6 +64,12 @@ import bisq.core.dao.governance.voteresult.MissingDataRequestService;
|
|||
import bisq.core.dao.governance.voteresult.VoteResultService;
|
||||
import bisq.core.dao.governance.voteresult.issuance.IssuanceService;
|
||||
import bisq.core.dao.governance.votereveal.VoteRevealService;
|
||||
import bisq.core.dao.monitoring.BlindVoteStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.ProposalStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.network.BlindVoteStateNetworkService;
|
||||
import bisq.core.dao.monitoring.network.DaoStateNetworkService;
|
||||
import bisq.core.dao.monitoring.network.ProposalStateNetworkService;
|
||||
import bisq.core.dao.node.BsqNodeProvider;
|
||||
import bisq.core.dao.node.explorer.ExportJsonFilesService;
|
||||
import bisq.core.dao.node.full.FullNode;
|
||||
|
@ -99,6 +105,7 @@ 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
|
||||
|
@ -116,6 +123,12 @@ public class DaoModule extends AppModule {
|
|||
bind(DaoStateService.class).in(Singleton.class);
|
||||
bind(DaoStateSnapshotService.class).in(Singleton.class);
|
||||
bind(DaoStateStorageService.class).in(Singleton.class);
|
||||
bind(DaoStateMonitoringService.class).in(Singleton.class);
|
||||
bind(DaoStateNetworkService.class).in(Singleton.class);
|
||||
bind(ProposalStateMonitoringService.class).in(Singleton.class);
|
||||
bind(ProposalStateNetworkService.class).in(Singleton.class);
|
||||
bind(BlindVoteStateMonitoringService.class).in(Singleton.class);
|
||||
bind(BlindVoteStateNetworkService.class).in(Singleton.class);
|
||||
bind(UnconfirmedBsqChangeOutputListService.class).in(Singleton.class);
|
||||
|
||||
bind(ExportJsonFilesService.class).in(Singleton.class);
|
||||
|
|
|
@ -31,6 +31,9 @@ import bisq.core.dao.governance.proposal.ProposalService;
|
|||
import bisq.core.dao.governance.voteresult.MissingDataRequestService;
|
||||
import bisq.core.dao.governance.voteresult.VoteResultService;
|
||||
import bisq.core.dao.governance.votereveal.VoteRevealService;
|
||||
import bisq.core.dao.monitoring.BlindVoteStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.ProposalStateMonitoringService;
|
||||
import bisq.core.dao.node.BsqNode;
|
||||
import bisq.core.dao.node.BsqNodeProvider;
|
||||
import bisq.core.dao.node.explorer.ExportJsonFilesService;
|
||||
|
@ -69,11 +72,20 @@ public class DaoSetup {
|
|||
ProofOfBurnService proofOfBurnService,
|
||||
DaoFacade daoFacade,
|
||||
ExportJsonFilesService exportJsonFilesService,
|
||||
DaoKillSwitch daoKillSwitch) {
|
||||
DaoKillSwitch daoKillSwitch,
|
||||
DaoStateMonitoringService daoStateMonitoringService,
|
||||
ProposalStateMonitoringService proposalStateMonitoringService,
|
||||
BlindVoteStateMonitoringService blindVoteStateMonitoringService,
|
||||
DaoEventCoordinator daoEventCoordinator) {
|
||||
|
||||
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);
|
||||
|
@ -92,6 +104,10 @@ public class DaoSetup {
|
|||
daoSetupServices.add(daoFacade);
|
||||
daoSetupServices.add(exportJsonFilesService);
|
||||
daoSetupServices.add(daoKillSwitch);
|
||||
daoSetupServices.add(daoStateMonitoringService);
|
||||
daoSetupServices.add(proposalStateMonitoringService);
|
||||
daoSetupServices.add(blindVoteStateMonitoringService);
|
||||
|
||||
daoSetupServices.add(bsqNodeProvider.getBsqNode());
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ public class BlindVoteListService implements AppendOnlyDataStoreListener, DaoSta
|
|||
|
||||
@Override
|
||||
public void onAdded(PersistableNetworkPayload payload) {
|
||||
onAppendOnlyDataAdded(payload);
|
||||
onAppendOnlyDataAdded(payload, true);
|
||||
}
|
||||
|
||||
|
||||
|
@ -120,16 +120,23 @@ public class BlindVoteListService implements AppendOnlyDataStoreListener, DaoSta
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<BlindVote> getConfirmedBlindVotes() {
|
||||
return blindVotePayloads.stream()
|
||||
.filter(blindVotePayload -> blindVoteValidator.areDataFieldsValidAndTxConfirmed(blindVotePayload.getBlindVote()))
|
||||
.map(BlindVotePayload::getBlindVote)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void fillListFromAppendOnlyDataStore() {
|
||||
p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().forEach(this::onAppendOnlyDataAdded);
|
||||
p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().forEach(e -> onAppendOnlyDataAdded(e, false));
|
||||
}
|
||||
|
||||
private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload) {
|
||||
private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload, boolean doLog) {
|
||||
if (persistableNetworkPayload instanceof BlindVotePayload) {
|
||||
BlindVotePayload blindVotePayload = (BlindVotePayload) persistableNetworkPayload;
|
||||
if (!blindVotePayloads.contains(blindVotePayload)) {
|
||||
|
@ -140,7 +147,9 @@ public class BlindVoteListService implements AppendOnlyDataStoreListener, DaoSta
|
|||
if (blindVoteValidator.areDataFieldsValid(blindVote)) {
|
||||
// We don't validate as we might receive blindVotes from other cycles or phases at startup.
|
||||
blindVotePayloads.add(blindVotePayload);
|
||||
log.info("We received a blindVotePayload. blindVoteTxId={}", txId);
|
||||
if (doLog) {
|
||||
log.info("We received a blindVotePayload. blindVoteTxId={}", txId);
|
||||
}
|
||||
} else {
|
||||
log.warn("We received an invalid blindVotePayload. blindVoteTxId={}", txId);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ public class MyBlindVoteList extends PersistableList<BlindVote> implements Conse
|
|||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private MyBlindVoteList(List<BlindVote> list) {
|
||||
public MyBlindVoteList(List<BlindVote> list) {
|
||||
super(list);
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
|
|||
@Override
|
||||
public void addListeners() {
|
||||
daoStateService.addDaoStateListener(this);
|
||||
p2PService.getNumConnectedPeers().addListener(numConnectedPeersListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -79,7 +79,7 @@ public class CycleService implements DaoStateListener, DaoSetupService {
|
|||
public void onNewBlockHeight(int blockHeight) {
|
||||
if (blockHeight != genesisBlockHeight)
|
||||
maybeCreateNewCycle(blockHeight, daoStateService.getCycles())
|
||||
.ifPresent(daoStateService.getCycles()::add);
|
||||
.ifPresent(daoStateService::addCycle);
|
||||
}
|
||||
|
||||
|
||||
|
@ -88,7 +88,7 @@ public class CycleService implements DaoStateListener, DaoSetupService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addFirstCycle() {
|
||||
daoStateService.getCycles().add(getFirstCycle());
|
||||
daoStateService.addCycle(getFirstCycle());
|
||||
}
|
||||
|
||||
public int getCycleIndex(Cycle cycle) {
|
||||
|
|
|
@ -85,7 +85,7 @@ public final class PeriodService {
|
|||
.isPresent();
|
||||
}
|
||||
|
||||
private Optional<Cycle> getCycle(int height) {
|
||||
public Optional<Cycle> getCycle(int height) {
|
||||
return daoStateService.getCycle(height);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,12 +31,12 @@ import java.util.stream.Collectors;
|
|||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* PersistableEnvelope wrapper for list of ballots. Used in vote consensus, so changes can break consensus!
|
||||
* PersistableEnvelope wrapper for list of proposals. Used in vote consensus, so changes can break consensus!
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MyProposalList extends PersistableList<Proposal> implements ConsensusCritical {
|
||||
|
||||
private MyProposalList(List<Proposal> list) {
|
||||
public MyProposalList(List<Proposal> list) {
|
||||
super(list);
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ public class MyProposalListService implements PersistedDataHost, DaoStateListene
|
|||
|
||||
numConnectedPeersListener = (observable, oldValue, newValue) -> rePublishOnceWellConnected();
|
||||
daoStateService.addDaoStateListener(this);
|
||||
p2PService.getNumConnectedPeers().addListener(numConnectedPeersListener);
|
||||
}
|
||||
|
||||
|
||||
|
@ -130,9 +131,6 @@ public class MyProposalListService implements PersistedDataHost, DaoStateListene
|
|||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void start() {
|
||||
}
|
||||
|
||||
// Broadcast tx and publish proposal to P2P network
|
||||
public void publishTxAndPayload(Proposal proposal, Transaction transaction, ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
|
|
|
@ -131,7 +131,7 @@ public class ProposalService implements HashMapChangedListener, AppendOnlyDataSt
|
|||
|
||||
@Override
|
||||
public void onAdded(ProtectedStorageEntry entry) {
|
||||
onProtectedDataAdded(entry);
|
||||
onProtectedDataAdded(entry, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -146,7 +146,7 @@ public class ProposalService implements HashMapChangedListener, AppendOnlyDataSt
|
|||
|
||||
@Override
|
||||
public void onAdded(PersistableNetworkPayload payload) {
|
||||
onAppendOnlyDataAdded(payload);
|
||||
onAppendOnlyDataAdded(payload, true);
|
||||
}
|
||||
|
||||
|
||||
|
@ -190,11 +190,11 @@ public class ProposalService implements HashMapChangedListener, AppendOnlyDataSt
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void fillListFromProtectedStore() {
|
||||
p2PService.getDataMap().values().forEach(this::onProtectedDataAdded);
|
||||
p2PService.getDataMap().values().forEach(e -> onProtectedDataAdded(e, false));
|
||||
}
|
||||
|
||||
private void fillListFromAppendOnlyDataStore() {
|
||||
p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().forEach(this::onAppendOnlyDataAdded);
|
||||
p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().forEach(e -> onAppendOnlyDataAdded(e, false));
|
||||
}
|
||||
|
||||
private void publishToAppendOnlyDataStore() {
|
||||
|
@ -211,7 +211,7 @@ public class ProposalService implements HashMapChangedListener, AppendOnlyDataSt
|
|||
});
|
||||
}
|
||||
|
||||
private void onProtectedDataAdded(ProtectedStorageEntry entry) {
|
||||
private void onProtectedDataAdded(ProtectedStorageEntry entry, boolean doLog) {
|
||||
ProtectedStoragePayload protectedStoragePayload = entry.getProtectedStoragePayload();
|
||||
if (protectedStoragePayload instanceof TempProposalPayload) {
|
||||
Proposal proposal = ((TempProposalPayload) protectedStoragePayload).getProposal();
|
||||
|
@ -219,8 +219,10 @@ public class ProposalService implements HashMapChangedListener, AppendOnlyDataSt
|
|||
// available/confirmed. But we check if we are in the proposal phase.
|
||||
if (!tempProposals.contains(proposal)) {
|
||||
if (proposalValidator.isValidOrUnconfirmed(proposal)) {
|
||||
log.info("We received a TempProposalPayload and store it to our protectedStoreList. proposalTxId={}",
|
||||
proposal.getTxId());
|
||||
if (doLog) {
|
||||
log.info("We received a TempProposalPayload and store it to our protectedStoreList. proposalTxId={}",
|
||||
proposal.getTxId());
|
||||
}
|
||||
tempProposals.add(proposal);
|
||||
} else {
|
||||
log.debug("We received an invalid proposal from the P2P network. Proposal.txId={}, blockHeight={}",
|
||||
|
@ -256,14 +258,16 @@ public class ProposalService implements HashMapChangedListener, AppendOnlyDataSt
|
|||
}
|
||||
}
|
||||
|
||||
private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload) {
|
||||
private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload, boolean doLog) {
|
||||
if (persistableNetworkPayload instanceof ProposalPayload) {
|
||||
ProposalPayload proposalPayload = (ProposalPayload) persistableNetworkPayload;
|
||||
if (!proposalPayloads.contains(proposalPayload)) {
|
||||
Proposal proposal = proposalPayload.getProposal();
|
||||
if (proposalValidator.areDataFieldsValid(proposal)) {
|
||||
log.info("We received a ProposalPayload and store it to our appendOnlyStoreList. proposalTxId={}",
|
||||
proposal.getTxId());
|
||||
if (doLog) {
|
||||
log.info("We received a ProposalPayload and store it to our appendOnlyStoreList. proposalTxId={}",
|
||||
proposal.getTxId());
|
||||
}
|
||||
proposalPayloads.add(proposalPayload);
|
||||
} else {
|
||||
log.warn("We received a invalid append-only proposal from the P2P network. " +
|
||||
|
|
|
@ -235,6 +235,7 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
|
|||
}
|
||||
|
||||
// Those which did not get accepted will be added to the nonBsq map
|
||||
// FIXME add check for cycle as now we call addNonBsqTxOutput for past rejected comp requests as well
|
||||
daoStateService.getIssuanceCandidateTxOutputs().stream()
|
||||
.filter(txOutput -> !daoStateService.isIssuanceTx(txOutput.getTxId()))
|
||||
.forEach(daoStateService::addNonBsqTxOutput);
|
||||
|
@ -304,6 +305,8 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
|
|||
return getDecryptedBallotsWithMerits(voteRevealTxId, currentCycle, voteRevealOpReturnData,
|
||||
blindVoteTxId, hashOfBlindVoteList, blindVoteStake, optionalBlindVote.get());
|
||||
}
|
||||
|
||||
// We are missing P2P network data
|
||||
return getEmptyDecryptedBallotsWithMerits(voteRevealTxId, blindVoteTxId, hashOfBlindVoteList,
|
||||
blindVoteStake);
|
||||
} catch (Throwable e) {
|
||||
|
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* 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.monitoring;
|
||||
|
||||
import bisq.core.dao.DaoSetupService;
|
||||
import bisq.core.dao.governance.blindvote.BlindVote;
|
||||
import bisq.core.dao.governance.blindvote.BlindVoteListService;
|
||||
import bisq.core.dao.governance.blindvote.MyBlindVoteList;
|
||||
import bisq.core.dao.governance.period.PeriodService;
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateBlock;
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
|
||||
import bisq.core.dao.monitoring.network.BlindVoteStateNetworkService;
|
||||
import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.NewBlindVoteStateHashMessage;
|
||||
import bisq.core.dao.state.DaoStateListener;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.GenesisTxInfo;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
import bisq.core.dao.state.model.governance.Cycle;
|
||||
import bisq.core.dao.state.model.governance.DaoPhase;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.crypto.Hash;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
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.checkNotNull;
|
||||
|
||||
/**
|
||||
* Monitors the BlindVote P2P network payloads with using a hash of a sorted list of BlindVotes from one cycle and
|
||||
* make it accessible to the network so we can detect quickly if any consensus issue arise.
|
||||
* We create that hash at the first block of the VoteReveal phase. There is one hash created per cycle.
|
||||
* The hash contains the hash of the previous block so we can ensure the validity of the whole history by
|
||||
* comparing the last block.
|
||||
*
|
||||
* We request the state from the connected seed nodes after batch processing of BSQ is complete as well as we start
|
||||
* to listen for broadcast messages from our peers about dao state of new blocks.
|
||||
*
|
||||
* We do NOT persist that chain of hashes as there is only one per cycle and the performance costs are very low.
|
||||
*/
|
||||
@Slf4j
|
||||
public class BlindVoteStateMonitoringService implements DaoSetupService, DaoStateListener, BlindVoteStateNetworkService.Listener<NewBlindVoteStateHashMessage, GetBlindVoteStateHashesRequest, BlindVoteStateHash> {
|
||||
public interface Listener {
|
||||
void onBlindVoteStateBlockChainChanged();
|
||||
}
|
||||
|
||||
private final DaoStateService daoStateService;
|
||||
private final BlindVoteStateNetworkService blindVoteStateNetworkService;
|
||||
private final GenesisTxInfo genesisTxInfo;
|
||||
private final PeriodService periodService;
|
||||
private final BlindVoteListService blindVoteListService;
|
||||
|
||||
@Getter
|
||||
private final LinkedList<BlindVoteStateBlock> blindVoteStateBlockChain = new LinkedList<>();
|
||||
@Getter
|
||||
private final LinkedList<BlindVoteStateHash> blindVoteStateHashChain = new LinkedList<>();
|
||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
@Getter
|
||||
private boolean isInConflict;
|
||||
private boolean parseBlockChainComplete;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public BlindVoteStateMonitoringService(DaoStateService daoStateService,
|
||||
BlindVoteStateNetworkService blindVoteStateNetworkService,
|
||||
GenesisTxInfo genesisTxInfo,
|
||||
PeriodService periodService,
|
||||
BlindVoteListService blindVoteListService) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.blindVoteStateNetworkService = blindVoteStateNetworkService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
this.periodService = periodService;
|
||||
this.blindVoteListService = blindVoteListService;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoSetupService
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void addListeners() {
|
||||
daoStateService.addDaoStateListener(this);
|
||||
blindVoteStateNetworkService.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public void onDaoStateChanged(Block block) {
|
||||
int blockHeight = block.getHeight();
|
||||
|
||||
int genesisBlockHeight = genesisTxInfo.getGenesisBlockHeight();
|
||||
|
||||
if (blindVoteStateBlockChain.isEmpty() && blockHeight > genesisBlockHeight) {
|
||||
// Takes about 150 ms for dao testnet data
|
||||
long ts = System.currentTimeMillis();
|
||||
for (int i = genesisBlockHeight; i < blockHeight; i++) {
|
||||
maybeUpdateHashChain(i);
|
||||
}
|
||||
log.info("updateHashChain for {} items took {} ms",
|
||||
blockHeight - genesisBlockHeight,
|
||||
System.currentTimeMillis() - ts);
|
||||
}
|
||||
maybeUpdateHashChain(blockHeight);
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public void onParseBlockChainComplete() {
|
||||
parseBlockChainComplete = true;
|
||||
blindVoteStateNetworkService.addListeners();
|
||||
|
||||
// We wait for processing messages until we have completed batch processing
|
||||
|
||||
// We request data from last 5 cycles. We ignore possible duration changes done by voting as that request
|
||||
// period is arbitrary anyway...
|
||||
Cycle currentCycle = periodService.getCurrentCycle();
|
||||
checkNotNull(currentCycle, "currentCycle must not be null");
|
||||
int fromHeight = Math.max(genesisTxInfo.getGenesisBlockHeight(), daoStateService.getChainHeight() - currentCycle.getDuration() * 5);
|
||||
|
||||
blindVoteStateNetworkService.requestHashesFromAllConnectedSeedNodes(fromHeight);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// StateNetworkService.Listener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onNewStateHashMessage(NewBlindVoteStateHashMessage newStateHashMessage, Connection connection) {
|
||||
if (newStateHashMessage.getStateHash().getHeight() <= daoStateService.getChainHeight()) {
|
||||
processPeersBlindVoteStateHash(newStateHashMessage.getStateHash(), connection.getPeersNodeAddressOptional(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetStateHashRequest(Connection connection, GetBlindVoteStateHashesRequest getStateHashRequest) {
|
||||
int fromHeight = getStateHashRequest.getHeight();
|
||||
List<BlindVoteStateHash> blindVoteStateHashes = blindVoteStateBlockChain.stream()
|
||||
.filter(e -> e.getHeight() >= fromHeight)
|
||||
.map(BlindVoteStateBlock::getMyStateHash)
|
||||
.collect(Collectors.toList());
|
||||
blindVoteStateNetworkService.sendGetStateHashesResponse(connection, getStateHashRequest.getNonce(), blindVoteStateHashes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPeersStateHashes(List<BlindVoteStateHash> stateHashes, Optional<NodeAddress> peersNodeAddress) {
|
||||
AtomicBoolean hasChanged = new AtomicBoolean(false);
|
||||
stateHashes.forEach(daoStateHash -> {
|
||||
boolean changed = processPeersBlindVoteStateHash(daoStateHash, peersNodeAddress, false);
|
||||
if (changed) {
|
||||
hasChanged.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasChanged.get()) {
|
||||
listeners.forEach(Listener::onBlindVoteStateBlockChainChanged);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void requestHashesFromGenesisBlockHeight(String peersAddress) {
|
||||
blindVoteStateNetworkService.requestHashes(genesisTxInfo.getGenesisBlockHeight(), peersAddress);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listeners
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void maybeUpdateHashChain(int blockHeight) {
|
||||
// We use first block in blind vote phase to create the hash of our blindVotes. We prefer to wait as long as
|
||||
// possible to increase the chance that we have received all blindVotes.
|
||||
if (!isFirstBlockOfBlindVotePhase(blockHeight)) {
|
||||
return;
|
||||
}
|
||||
|
||||
periodService.getCycle(blockHeight).ifPresent(cycle -> {
|
||||
List<BlindVote> blindVotes = blindVoteListService.getConfirmedBlindVotes().stream()
|
||||
.filter(e -> periodService.isTxInCorrectCycle(e.getTxId(), blockHeight))
|
||||
.sorted(Comparator.comparing(BlindVote::getTxId)).collect(Collectors.toList());
|
||||
|
||||
// We use MyBlindVoteList to get the serialized bytes from the blindVotes list
|
||||
byte[] serializedBlindVotes = new MyBlindVoteList(blindVotes).toProtoMessage().toByteArray();
|
||||
|
||||
byte[] prevHash;
|
||||
if (blindVoteStateBlockChain.isEmpty()) {
|
||||
prevHash = new byte[0];
|
||||
} else {
|
||||
prevHash = blindVoteStateBlockChain.getLast().getHash();
|
||||
}
|
||||
byte[] combined = ArrayUtils.addAll(prevHash, serializedBlindVotes);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(combined);
|
||||
|
||||
|
||||
BlindVoteStateHash myBlindVoteStateHash = new BlindVoteStateHash(blockHeight, hash, prevHash, blindVotes.size());
|
||||
BlindVoteStateBlock blindVoteStateBlock = new BlindVoteStateBlock(myBlindVoteStateHash);
|
||||
blindVoteStateBlockChain.add(blindVoteStateBlock);
|
||||
blindVoteStateHashChain.add(myBlindVoteStateHash);
|
||||
|
||||
// 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::onBlindVoteStateBlockChainChanged);
|
||||
|
||||
// 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);
|
||||
UserThread.runAfter(() -> blindVoteStateNetworkService.broadcastMyStateHash(myBlindVoteStateHash), delayInSec);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean processPeersBlindVoteStateHash(BlindVoteStateHash blindVoteStateHash, Optional<NodeAddress> peersNodeAddress, boolean notifyListeners) {
|
||||
AtomicBoolean changed = new AtomicBoolean(false);
|
||||
AtomicBoolean isInConflict = new AtomicBoolean(this.isInConflict);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
blindVoteStateBlockChain.stream()
|
||||
.filter(e -> e.getHeight() == blindVoteStateHash.getHeight()).findAny()
|
||||
.ifPresent(daoStateBlock -> {
|
||||
String peersNodeAddressAsString = peersNodeAddress.map(NodeAddress::getFullAddress)
|
||||
.orElseGet(() -> "Unknown peer " + new Random().nextInt(10000));
|
||||
daoStateBlock.putInPeersMap(peersNodeAddressAsString, blindVoteStateHash);
|
||||
if (!daoStateBlock.getMyStateHash().hasEqualHash(blindVoteStateHash)) {
|
||||
daoStateBlock.putInConflictMap(peersNodeAddressAsString, blindVoteStateHash);
|
||||
isInConflict.set(true);
|
||||
sb.append("We received a block hash from peer ")
|
||||
.append(peersNodeAddressAsString)
|
||||
.append(" which conflicts with our block hash.\n")
|
||||
.append("my blindVoteStateHash=")
|
||||
.append(daoStateBlock.getMyStateHash())
|
||||
.append("\npeers blindVoteStateHash=")
|
||||
.append(blindVoteStateHash);
|
||||
}
|
||||
changed.set(true);
|
||||
});
|
||||
|
||||
this.isInConflict = isInConflict.get();
|
||||
|
||||
String conflictMsg = sb.toString();
|
||||
if (this.isInConflict && !conflictMsg.isEmpty()) {
|
||||
log.warn(conflictMsg);
|
||||
}
|
||||
|
||||
if (notifyListeners && changed.get()) {
|
||||
listeners.forEach(Listener::onBlindVoteStateBlockChainChanged);
|
||||
}
|
||||
|
||||
return changed.get();
|
||||
}
|
||||
|
||||
private boolean isFirstBlockOfBlindVotePhase(int blockHeight) {
|
||||
return blockHeight == periodService.getFirstBlockOfPhase(blockHeight, DaoPhase.Phase.VOTE_REVEAL);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* 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.monitoring;
|
||||
|
||||
import bisq.core.dao.DaoSetupService;
|
||||
import bisq.core.dao.monitoring.model.DaoStateBlock;
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
import bisq.core.dao.monitoring.network.DaoStateNetworkService;
|
||||
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.NewDaoStateHashMessage;
|
||||
import bisq.core.dao.state.DaoStateListener;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.GenesisTxInfo;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.crypto.Hash;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Monitors the DaoState with using a hash fo the complete daoState and make it accessible to the network
|
||||
* so we can detect quickly if any consensus issue arise.
|
||||
* We create that hash after parsing and processing of a block is completed. There is one hash created per block.
|
||||
* The hash contains the hash of the previous block so we can ensure the validity of the whole history by
|
||||
* comparing the last block.
|
||||
*
|
||||
* We request the state from the connected seed nodes after batch processing of BSQ is complete as well as we start
|
||||
* to listen for broadcast messages from our peers about dao state of new blocks. It could be that the received dao
|
||||
* state from the peers is already covering the next block we have not received yet. So we only take data in account
|
||||
* which are inside the block height we have already. To avoid such race conditions we delay the broadcasting of our
|
||||
* state to the peers to not get ignored it in case they have not received the block yet.
|
||||
*
|
||||
* We do persist that chain of hashes with the snapshot.
|
||||
*/
|
||||
@Slf4j
|
||||
public class DaoStateMonitoringService implements DaoSetupService, DaoStateListener,
|
||||
DaoStateNetworkService.Listener<NewDaoStateHashMessage, GetDaoStateHashesRequest, DaoStateHash> {
|
||||
|
||||
public interface Listener {
|
||||
void onChangeAfterBatchProcessing();
|
||||
}
|
||||
|
||||
private final DaoStateService daoStateService;
|
||||
private final DaoStateNetworkService daoStateNetworkService;
|
||||
private final GenesisTxInfo genesisTxInfo;
|
||||
|
||||
@Getter
|
||||
private final LinkedList<DaoStateBlock> daoStateBlockChain = new LinkedList<>();
|
||||
@Getter
|
||||
private final LinkedList<DaoStateHash> daoStateHashChain = new LinkedList<>();
|
||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
private boolean parseBlockChainComplete;
|
||||
@Getter
|
||||
private boolean isInConflict;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public DaoStateMonitoringService(DaoStateService daoStateService,
|
||||
DaoStateNetworkService daoStateNetworkService,
|
||||
GenesisTxInfo genesisTxInfo) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.daoStateNetworkService = daoStateNetworkService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoSetupService
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void addListeners() {
|
||||
daoStateService.addDaoStateListener(this);
|
||||
daoStateNetworkService.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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;
|
||||
daoStateNetworkService.addListeners();
|
||||
|
||||
// We wait for processing messages until we have completed batch processing
|
||||
int fromHeight = daoStateService.getChainHeight() - 10;
|
||||
daoStateNetworkService.requestHashesFromAllConnectedSeedNodes(fromHeight);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// StateNetworkService.Listener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onNewStateHashMessage(NewDaoStateHashMessage newStateHashMessage, Connection connection) {
|
||||
if (newStateHashMessage.getStateHash().getHeight() <= daoStateService.getChainHeight()) {
|
||||
processPeersDaoStateHash(newStateHashMessage.getStateHash(), connection.getPeersNodeAddressOptional(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetStateHashRequest(Connection connection, GetDaoStateHashesRequest getStateHashRequest) {
|
||||
int fromHeight = getStateHashRequest.getHeight();
|
||||
List<DaoStateHash> daoStateHashes = daoStateBlockChain.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);
|
||||
}
|
||||
|
||||
public void requestHashesFromGenesisBlockHeight(String peersAddress) {
|
||||
daoStateNetworkService.requestHashes(genesisTxInfo.getGenesisBlockHeight(), peersAddress);
|
||||
}
|
||||
|
||||
public void applySnapshot(LinkedList<DaoStateHash> persistedDaoStateHashChain) {
|
||||
// We could got a reset from a reorg, so we clear all and start over from the genesis block.
|
||||
daoStateHashChain.clear();
|
||||
daoStateBlockChain.clear();
|
||||
daoStateNetworkService.reset();
|
||||
|
||||
if (!persistedDaoStateHashChain.isEmpty()) {
|
||||
log.info("Apply snapshot with {} daoStateHashes. Last daoStateHash={}",
|
||||
persistedDaoStateHashChain.size(), persistedDaoStateHashChain.getLast());
|
||||
}
|
||||
daoStateHashChain.addAll(persistedDaoStateHashChain);
|
||||
daoStateHashChain.forEach(e -> daoStateBlockChain.add(new DaoStateBlock(e)));
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listeners
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void updateHashChain(Block block) {
|
||||
byte[] prevHash;
|
||||
int height = block.getHeight();
|
||||
if (daoStateBlockChain.isEmpty()) {
|
||||
// Only at genesis we allow an empty prevHash
|
||||
if (height == genesisTxInfo.getGenesisBlockHeight()) {
|
||||
prevHash = new byte[0];
|
||||
} else {
|
||||
log.warn("DaoStateBlockchain is empty but we received the block which was not the genesis block. " +
|
||||
"We stop execution here.");
|
||||
return;
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
byte[] stateHash = daoStateService.getSerializedDaoState();
|
||||
// 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, stateHash);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(combined);
|
||||
|
||||
DaoStateHash myDaoStateHash = new DaoStateHash(height, hash, prevHash);
|
||||
DaoStateBlock daoStateBlock = new DaoStateBlock(myDaoStateHash);
|
||||
daoStateBlockChain.add(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);
|
||||
UserThread.runAfter(() -> daoStateNetworkService.broadcastMyStateHash(myDaoStateHash), delayInSec);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processPeersDaoStateHash(DaoStateHash daoStateHash, Optional<NodeAddress> peersNodeAddress, boolean notifyListeners) {
|
||||
AtomicBoolean changed = new AtomicBoolean(false);
|
||||
AtomicBoolean isInConflict = new AtomicBoolean(this.isInConflict);
|
||||
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);
|
||||
isInConflict.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);
|
||||
});
|
||||
|
||||
this.isInConflict = isInConflict.get();
|
||||
|
||||
String conflictMsg = sb.toString();
|
||||
if (this.isInConflict && !conflictMsg.isEmpty()) {
|
||||
log.warn(conflictMsg);
|
||||
}
|
||||
|
||||
if (notifyListeners && changed.get()) {
|
||||
listeners.forEach(Listener::onChangeAfterBatchProcessing);
|
||||
}
|
||||
|
||||
return changed.get();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
/*
|
||||
* 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.monitoring;
|
||||
|
||||
import bisq.core.dao.DaoSetupService;
|
||||
import bisq.core.dao.governance.period.PeriodService;
|
||||
import bisq.core.dao.governance.proposal.MyProposalList;
|
||||
import bisq.core.dao.governance.proposal.ProposalService;
|
||||
import bisq.core.dao.monitoring.model.ProposalStateBlock;
|
||||
import bisq.core.dao.monitoring.model.ProposalStateHash;
|
||||
import bisq.core.dao.monitoring.network.ProposalStateNetworkService;
|
||||
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.NewProposalStateHashMessage;
|
||||
import bisq.core.dao.state.DaoStateListener;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.GenesisTxInfo;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
import bisq.core.dao.state.model.governance.Cycle;
|
||||
import bisq.core.dao.state.model.governance.DaoPhase;
|
||||
import bisq.core.dao.state.model.governance.Proposal;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.crypto.Hash;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
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.checkNotNull;
|
||||
|
||||
/**
|
||||
* Monitors the Proposal P2P network payloads with using a hash of a sorted list of Proposals from one cycle and
|
||||
* make it accessible to the network so we can detect quickly if any consensus issue arise.
|
||||
* We create that hash at the first block of the BlindVote phase. There is one hash created per cycle.
|
||||
* The hash contains the hash of the previous block so we can ensure the validity of the whole history by
|
||||
* comparing the last block.
|
||||
*
|
||||
* We request the state from the connected seed nodes after batch processing of BSQ is complete as well as we start
|
||||
* to listen for broadcast messages from our peers about dao state of new blocks.
|
||||
*
|
||||
* We do NOT persist that chain of hashes as there is only one per cycle and the performance costs are very low.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProposalStateMonitoringService implements DaoSetupService, DaoStateListener, ProposalStateNetworkService.Listener<NewProposalStateHashMessage, GetProposalStateHashesRequest, ProposalStateHash> {
|
||||
public interface Listener {
|
||||
void onProposalStateBlockChainChanged();
|
||||
}
|
||||
|
||||
private final DaoStateService daoStateService;
|
||||
private final ProposalStateNetworkService proposalStateNetworkService;
|
||||
private final GenesisTxInfo genesisTxInfo;
|
||||
private final PeriodService periodService;
|
||||
private final ProposalService proposalService;
|
||||
|
||||
@Getter
|
||||
private final LinkedList<ProposalStateBlock> proposalStateBlockChain = new LinkedList<>();
|
||||
@Getter
|
||||
private final LinkedList<ProposalStateHash> proposalStateHashChain = new LinkedList<>();
|
||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
@Getter
|
||||
private boolean isInConflict;
|
||||
private boolean parseBlockChainComplete;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public ProposalStateMonitoringService(DaoStateService daoStateService,
|
||||
ProposalStateNetworkService proposalStateNetworkService,
|
||||
GenesisTxInfo genesisTxInfo,
|
||||
PeriodService periodService,
|
||||
ProposalService proposalService) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.proposalStateNetworkService = proposalStateNetworkService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
this.periodService = periodService;
|
||||
this.proposalService = proposalService;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoSetupService
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void addListeners() {
|
||||
daoStateService.addDaoStateListener(this);
|
||||
proposalStateNetworkService.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public void onDaoStateChanged(Block block) {
|
||||
int blockHeight = block.getHeight();
|
||||
int genesisBlockHeight = genesisTxInfo.getGenesisBlockHeight();
|
||||
|
||||
if (proposalStateBlockChain.isEmpty() && blockHeight > genesisBlockHeight) {
|
||||
// Takes about 150 ms for dao testnet data
|
||||
long ts = System.currentTimeMillis();
|
||||
for (int i = genesisBlockHeight; i < blockHeight; i++) {
|
||||
maybeUpdateHashChain(i);
|
||||
}
|
||||
log.info("updateHashChain for {} items took {} ms",
|
||||
blockHeight - genesisBlockHeight,
|
||||
System.currentTimeMillis() - ts);
|
||||
}
|
||||
maybeUpdateHashChain(blockHeight);
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public void onParseBlockChainComplete() {
|
||||
parseBlockChainComplete = true;
|
||||
proposalStateNetworkService.addListeners();
|
||||
|
||||
// We wait for processing messages until we have completed batch processing
|
||||
|
||||
// We request data from last 5 cycles. We ignore possible duration changes done by voting as that request
|
||||
// period is arbitrary anyway...
|
||||
Cycle currentCycle = periodService.getCurrentCycle();
|
||||
checkNotNull(currentCycle, "currentCycle must not be null");
|
||||
int fromHeight = Math.max(genesisTxInfo.getGenesisBlockHeight(), daoStateService.getChainHeight() - currentCycle.getDuration() * 5);
|
||||
|
||||
proposalStateNetworkService.requestHashesFromAllConnectedSeedNodes(fromHeight);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// StateNetworkService.Listener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onNewStateHashMessage(NewProposalStateHashMessage newStateHashMessage, Connection connection) {
|
||||
if (newStateHashMessage.getStateHash().getHeight() <= daoStateService.getChainHeight()) {
|
||||
processPeersProposalStateHash(newStateHashMessage.getStateHash(), connection.getPeersNodeAddressOptional(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetStateHashRequest(Connection connection, GetProposalStateHashesRequest getStateHashRequest) {
|
||||
int fromHeight = getStateHashRequest.getHeight();
|
||||
List<ProposalStateHash> proposalStateHashes = proposalStateBlockChain.stream()
|
||||
.filter(e -> e.getHeight() >= fromHeight)
|
||||
.map(ProposalStateBlock::getMyStateHash)
|
||||
.collect(Collectors.toList());
|
||||
proposalStateNetworkService.sendGetStateHashesResponse(connection, getStateHashRequest.getNonce(), proposalStateHashes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPeersStateHashes(List<ProposalStateHash> stateHashes, Optional<NodeAddress> peersNodeAddress) {
|
||||
AtomicBoolean hasChanged = new AtomicBoolean(false);
|
||||
stateHashes.forEach(daoStateHash -> {
|
||||
boolean changed = processPeersProposalStateHash(daoStateHash, peersNodeAddress, false);
|
||||
if (changed) {
|
||||
hasChanged.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasChanged.get()) {
|
||||
listeners.forEach(Listener::onProposalStateBlockChainChanged);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void requestHashesFromGenesisBlockHeight(String peersAddress) {
|
||||
proposalStateNetworkService.requestHashes(genesisTxInfo.getGenesisBlockHeight(), peersAddress);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listeners
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void maybeUpdateHashChain(int blockHeight) {
|
||||
// We use first block in blind vote phase to create the hash of our proposals. We prefer to wait as long as
|
||||
// possible to increase the chance that we have received all proposals.
|
||||
if (!isFirstBlockOfBlindVotePhase(blockHeight)) {
|
||||
return;
|
||||
}
|
||||
|
||||
periodService.getCycle(blockHeight).ifPresent(cycle -> {
|
||||
List<Proposal> proposals = proposalService.getValidatedProposals().stream()
|
||||
.filter(e -> periodService.isTxInPhaseAndCycle(e.getTxId(), DaoPhase.Phase.PROPOSAL, blockHeight))
|
||||
.sorted(Comparator.comparing(Proposal::getTxId)).collect(Collectors.toList());
|
||||
|
||||
// We use MyProposalList to get the serialized bytes from the proposals list
|
||||
byte[] serializedProposals = new MyProposalList(proposals).toProtoMessage().toByteArray();
|
||||
|
||||
byte[] prevHash;
|
||||
if (proposalStateBlockChain.isEmpty()) {
|
||||
prevHash = new byte[0];
|
||||
} else {
|
||||
prevHash = proposalStateBlockChain.getLast().getHash();
|
||||
}
|
||||
byte[] combined = ArrayUtils.addAll(prevHash, serializedProposals);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(combined);
|
||||
ProposalStateHash myProposalStateHash = new ProposalStateHash(blockHeight, hash, prevHash, proposals.size());
|
||||
ProposalStateBlock proposalStateBlock = new ProposalStateBlock(myProposalStateHash);
|
||||
proposalStateBlockChain.add(proposalStateBlock);
|
||||
proposalStateHashChain.add(myProposalStateHash);
|
||||
|
||||
// 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::onProposalStateBlockChainChanged);
|
||||
|
||||
// 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);
|
||||
UserThread.runAfter(() -> proposalStateNetworkService.broadcastMyStateHash(myProposalStateHash), delayInSec);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean processPeersProposalStateHash(ProposalStateHash proposalStateHash, Optional<NodeAddress> peersNodeAddress, boolean notifyListeners) {
|
||||
AtomicBoolean changed = new AtomicBoolean(false);
|
||||
AtomicBoolean isInConflict = new AtomicBoolean(this.isInConflict);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
proposalStateBlockChain.stream()
|
||||
.filter(e -> e.getHeight() == proposalStateHash.getHeight()).findAny()
|
||||
.ifPresent(daoStateBlock -> {
|
||||
String peersNodeAddressAsString = peersNodeAddress.map(NodeAddress::getFullAddress)
|
||||
.orElseGet(() -> "Unknown peer " + new Random().nextInt(10000));
|
||||
daoStateBlock.putInPeersMap(peersNodeAddressAsString, proposalStateHash);
|
||||
if (!daoStateBlock.getMyStateHash().hasEqualHash(proposalStateHash)) {
|
||||
daoStateBlock.putInConflictMap(peersNodeAddressAsString, proposalStateHash);
|
||||
isInConflict.set(true);
|
||||
sb.append("We received a block hash from peer ")
|
||||
.append(peersNodeAddressAsString)
|
||||
.append(" which conflicts with our block hash.\n")
|
||||
.append("my proposalStateHash=")
|
||||
.append(daoStateBlock.getMyStateHash())
|
||||
.append("\npeers proposalStateHash=")
|
||||
.append(proposalStateHash);
|
||||
}
|
||||
changed.set(true);
|
||||
});
|
||||
|
||||
this.isInConflict = isInConflict.get();
|
||||
|
||||
String conflictMsg = sb.toString();
|
||||
if (this.isInConflict && !conflictMsg.isEmpty()) {
|
||||
log.warn(conflictMsg);
|
||||
}
|
||||
|
||||
if (notifyListeners && changed.get()) {
|
||||
listeners.forEach(Listener::onProposalStateBlockChainChanged);
|
||||
}
|
||||
|
||||
return changed.get();
|
||||
}
|
||||
|
||||
private boolean isFirstBlockOfBlindVotePhase(int blockHeight) {
|
||||
return blockHeight == periodService.getFirstBlockOfPhase(blockHeight, DaoPhase.Phase.BLIND_VOTE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.monitoring.model;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BlindVoteStateBlock extends StateBlock<BlindVoteStateHash> {
|
||||
public BlindVoteStateBlock(BlindVoteStateHash myBlindVoteStateHash) {
|
||||
super(myBlindVoteStateHash);
|
||||
}
|
||||
|
||||
public int getNumBlindVotes() {
|
||||
return myStateHash.getNumBlindVotes();
|
||||
}
|
||||
}
|
|
@ -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.core.dao.monitoring.model;
|
||||
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
|
||||
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);
|
||||
this.numBlindVotes = numBlindVotes;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public PB.BlindVoteStateHash toProtoMessage() {
|
||||
return PB.BlindVoteStateHash.newBuilder()
|
||||
.setHeight(height)
|
||||
.setHash(ByteString.copyFrom(hash))
|
||||
.setPrevHash(ByteString.copyFrom(prevHash))
|
||||
.setNumBlindVotes(numBlindVotes).build();
|
||||
}
|
||||
|
||||
public static BlindVoteStateHash fromProto(PB.BlindVoteStateHash proto) {
|
||||
return new BlindVoteStateHash(proto.getHeight(),
|
||||
proto.getHash().toByteArray(),
|
||||
proto.getPrevHash().toByteArray(),
|
||||
proto.getNumBlindVotes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlindVoteStateHash{" +
|
||||
"\n numBlindVotes=" + numBlindVotes +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.monitoring.model;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DaoStateBlock extends StateBlock<DaoStateHash> {
|
||||
public DaoStateBlock(DaoStateHash myDaoStateHash) {
|
||||
super(myDaoStateHash);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.monitoring.model;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class DaoStateHash extends StateHash {
|
||||
public DaoStateHash(int height, byte[] hash, byte[] prevHash) {
|
||||
super(height, hash, prevHash);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public PB.DaoStateHash toProtoMessage() {
|
||||
return PB.DaoStateHash.newBuilder()
|
||||
.setHeight(height)
|
||||
.setHash(ByteString.copyFrom(hash))
|
||||
.setPrevHash(ByteString.copyFrom(prevHash)).build();
|
||||
}
|
||||
|
||||
public static DaoStateHash fromProto(PB.DaoStateHash proto) {
|
||||
return new DaoStateHash(proto.getHeight(),
|
||||
proto.getHash().toByteArray(),
|
||||
proto.getPrevHash().toByteArray());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.monitoring.model;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProposalStateBlock extends StateBlock<ProposalStateHash> {
|
||||
public ProposalStateBlock(ProposalStateHash myProposalStateHash) {
|
||||
super(myProposalStateHash);
|
||||
}
|
||||
|
||||
public int getNumProposals() {
|
||||
return myStateHash.getNumProposals();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.monitoring.model;
|
||||
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
|
||||
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);
|
||||
this.numProposals = numProposals;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public PB.ProposalStateHash toProtoMessage() {
|
||||
return PB.ProposalStateHash.newBuilder()
|
||||
.setHeight(height)
|
||||
.setHash(ByteString.copyFrom(hash))
|
||||
.setPrevHash(ByteString.copyFrom(prevHash))
|
||||
.setNumProposals(numProposals).build();
|
||||
}
|
||||
|
||||
public static ProposalStateHash fromProto(PB.ProposalStateHash proto) {
|
||||
return new ProposalStateHash(proto.getHeight(),
|
||||
proto.getHash().toByteArray(),
|
||||
proto.getPrevHash().toByteArray(),
|
||||
proto.getNumProposals());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProposalStateHash{" +
|
||||
"\n numProposals=" + numProposals +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.monitoring.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Contains my StateHash at a particular block height and the received stateHash from our peers.
|
||||
* The maps get updated over time, this is not an immutable class.
|
||||
*/
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public abstract class StateBlock<T extends StateHash> {
|
||||
protected final T myStateHash;
|
||||
|
||||
private final Map<String, T> peersMap = new HashMap<>();
|
||||
private final Map<String, T> inConflictMap = new HashMap<>();
|
||||
|
||||
StateBlock(T myStateHash) {
|
||||
this.myStateHash = myStateHash;
|
||||
}
|
||||
|
||||
public void putInPeersMap(String peersNodeAddress, T stateHash) {
|
||||
peersMap.putIfAbsent(peersNodeAddress, stateHash);
|
||||
}
|
||||
|
||||
public void putInConflictMap(String peersNodeAddress, T stateHash) {
|
||||
inConflictMap.putIfAbsent(peersNodeAddress, stateHash);
|
||||
}
|
||||
|
||||
// Delegates
|
||||
public int getHeight() {
|
||||
return myStateHash.getHeight();
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
return myStateHash.getHash();
|
||||
}
|
||||
|
||||
public byte[] getPrevHash() {
|
||||
return myStateHash.getPrevHash();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StateBlock{" +
|
||||
"\n myStateHash=" + myStateHash +
|
||||
",\n peersMap=" + peersMap +
|
||||
",\n inConflictMap=" + inConflictMap +
|
||||
"\n}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.monitoring.model;
|
||||
|
||||
|
||||
import bisq.common.proto.network.NetworkPayload;
|
||||
import bisq.common.proto.persistable.PersistablePayload;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Contains the blockHeight, the hash and the previous hash of the state.
|
||||
* As the hash is created from the state at the particular height including the previous hash we get the history of
|
||||
* the full chain included and we know if the hash matches at a particular height that all the past blocks need to match
|
||||
* as well.
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@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) {
|
||||
this.height = height;
|
||||
this.hash = hash;
|
||||
this.prevHash = prevHash;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean hasEqualHash(StateHash other) {
|
||||
return Arrays.equals(hash, other.getHash());
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StateHash{" +
|
||||
"\n height=" + height +
|
||||
",\n hash=" + Utilities.bytesAsHexString(hash) +
|
||||
",\n prevHash=" + Utilities.bytesAsHexString(prevHash) +
|
||||
"\n}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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.monitoring.network;
|
||||
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
|
||||
import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesResponse;
|
||||
import bisq.core.dao.monitoring.network.messages.NewBlindVoteStateHashMessage;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.Broadcaster;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class BlindVoteStateNetworkService extends StateNetworkService<NewBlindVoteStateHashMessage,
|
||||
GetBlindVoteStateHashesRequest,
|
||||
GetBlindVoteStateHashesResponse,
|
||||
RequestBlindVoteStateHashesHandler,
|
||||
BlindVoteStateHash> {
|
||||
@Inject
|
||||
public BlindVoteStateNetworkService(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
Broadcaster broadcaster) {
|
||||
super(networkNode, peerManager, broadcaster);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetBlindVoteStateHashesRequest castToGetStateHashRequest(NetworkEnvelope networkEnvelope) {
|
||||
return (GetBlindVoteStateHashesRequest) networkEnvelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGetStateHashesRequest(NetworkEnvelope networkEnvelope) {
|
||||
return networkEnvelope instanceof GetBlindVoteStateHashesRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NewBlindVoteStateHashMessage castToNewStateHashMessage(NetworkEnvelope networkEnvelope) {
|
||||
return (NewBlindVoteStateHashMessage) networkEnvelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNewStateHashMessage(NetworkEnvelope networkEnvelope) {
|
||||
return networkEnvelope instanceof NewBlindVoteStateHashMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetBlindVoteStateHashesResponse getGetStateHashesResponse(int nonce, List<BlindVoteStateHash> stateHashes) {
|
||||
return new GetBlindVoteStateHashesResponse(stateHashes, nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NewBlindVoteStateHashMessage getNewStateHashMessage(BlindVoteStateHash myStateHash) {
|
||||
return new NewBlindVoteStateHashMessage(myStateHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestBlindVoteStateHashesHandler getRequestStateHashesHandler(NodeAddress nodeAddress, RequestStateHashesHandler.Listener<GetBlindVoteStateHashesResponse> listener) {
|
||||
return new RequestBlindVoteStateHashesHandler(networkNode, peerManager, nodeAddress, listener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.monitoring.network;
|
||||
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesResponse;
|
||||
import bisq.core.dao.monitoring.network.messages.NewDaoStateHashMessage;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.Broadcaster;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class DaoStateNetworkService extends StateNetworkService<NewDaoStateHashMessage,
|
||||
GetDaoStateHashesRequest,
|
||||
GetDaoStateHashesResponse,
|
||||
RequestDaoStateHashesHandler,
|
||||
DaoStateHash> {
|
||||
@Inject
|
||||
public DaoStateNetworkService(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
Broadcaster broadcaster) {
|
||||
super(networkNode, peerManager, broadcaster);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetDaoStateHashesRequest castToGetStateHashRequest(NetworkEnvelope networkEnvelope) {
|
||||
return (GetDaoStateHashesRequest) networkEnvelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGetStateHashesRequest(NetworkEnvelope networkEnvelope) {
|
||||
return networkEnvelope instanceof GetDaoStateHashesRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NewDaoStateHashMessage castToNewStateHashMessage(NetworkEnvelope networkEnvelope) {
|
||||
return (NewDaoStateHashMessage) networkEnvelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNewStateHashMessage(NetworkEnvelope networkEnvelope) {
|
||||
return networkEnvelope instanceof NewDaoStateHashMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetDaoStateHashesResponse getGetStateHashesResponse(int nonce, List<DaoStateHash> stateHashes) {
|
||||
return new GetDaoStateHashesResponse(stateHashes, nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NewDaoStateHashMessage getNewStateHashMessage(DaoStateHash myStateHash) {
|
||||
return new NewDaoStateHashMessage(myStateHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestDaoStateHashesHandler getRequestStateHashesHandler(NodeAddress nodeAddress, RequestStateHashesHandler.Listener<GetDaoStateHashesResponse> listener) {
|
||||
return new RequestDaoStateHashesHandler(networkNode, peerManager, nodeAddress, listener);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.monitoring.network;
|
||||
|
||||
import bisq.core.dao.monitoring.model.ProposalStateHash;
|
||||
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesResponse;
|
||||
import bisq.core.dao.monitoring.network.messages.NewProposalStateHashMessage;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.Broadcaster;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ProposalStateNetworkService extends StateNetworkService<NewProposalStateHashMessage,
|
||||
GetProposalStateHashesRequest,
|
||||
GetProposalStateHashesResponse,
|
||||
RequestProposalStateHashesHandler,
|
||||
ProposalStateHash> {
|
||||
@Inject
|
||||
public ProposalStateNetworkService(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
Broadcaster broadcaster) {
|
||||
super(networkNode, peerManager, broadcaster);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetProposalStateHashesRequest castToGetStateHashRequest(NetworkEnvelope networkEnvelope) {
|
||||
return (GetProposalStateHashesRequest) networkEnvelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGetStateHashesRequest(NetworkEnvelope networkEnvelope) {
|
||||
return networkEnvelope instanceof GetProposalStateHashesRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NewProposalStateHashMessage castToNewStateHashMessage(NetworkEnvelope networkEnvelope) {
|
||||
return (NewProposalStateHashMessage) networkEnvelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNewStateHashMessage(NetworkEnvelope networkEnvelope) {
|
||||
return networkEnvelope instanceof NewProposalStateHashMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetProposalStateHashesResponse getGetStateHashesResponse(int nonce, List<ProposalStateHash> stateHashes) {
|
||||
return new GetProposalStateHashesResponse(stateHashes, nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NewProposalStateHashMessage getNewStateHashMessage(ProposalStateHash myStateHash) {
|
||||
return new NewProposalStateHashMessage(myStateHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestProposalStateHashesHandler getRequestStateHashesHandler(NodeAddress nodeAddress, RequestStateHashesHandler.Listener<GetProposalStateHashesResponse> listener) {
|
||||
return new RequestProposalStateHashesHandler(networkNode, peerManager, nodeAddress, listener);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.monitoring.network;
|
||||
|
||||
import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesResponse;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class RequestBlindVoteStateHashesHandler extends RequestStateHashesHandler<GetBlindVoteStateHashesRequest, GetBlindVoteStateHashesResponse> {
|
||||
RequestBlindVoteStateHashesHandler(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
NodeAddress nodeAddress,
|
||||
Listener<GetBlindVoteStateHashesResponse> listener) {
|
||||
super(networkNode, peerManager, nodeAddress, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetBlindVoteStateHashesRequest getGetStateHashesRequest(int fromHeight) {
|
||||
return new GetBlindVoteStateHashesRequest(fromHeight, nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetBlindVoteStateHashesResponse castToGetStateHashesResponse(NetworkEnvelope networkEnvelope) {
|
||||
return (GetBlindVoteStateHashesResponse) networkEnvelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGetStateHashesResponse(NetworkEnvelope networkEnvelope) {
|
||||
return networkEnvelope instanceof GetBlindVoteStateHashesResponse;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.monitoring.network;
|
||||
|
||||
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesResponse;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class RequestDaoStateHashesHandler extends RequestStateHashesHandler<GetDaoStateHashesRequest, GetDaoStateHashesResponse> {
|
||||
RequestDaoStateHashesHandler(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
NodeAddress nodeAddress,
|
||||
Listener<GetDaoStateHashesResponse> listener) {
|
||||
super(networkNode, peerManager, nodeAddress, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetDaoStateHashesRequest getGetStateHashesRequest(int fromHeight) {
|
||||
return new GetDaoStateHashesRequest(fromHeight, nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetDaoStateHashesResponse castToGetStateHashesResponse(NetworkEnvelope networkEnvelope) {
|
||||
return (GetDaoStateHashesResponse) networkEnvelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGetStateHashesResponse(NetworkEnvelope networkEnvelope) {
|
||||
return networkEnvelope instanceof GetDaoStateHashesResponse;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.monitoring.network;
|
||||
|
||||
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesResponse;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class RequestProposalStateHashesHandler extends RequestStateHashesHandler<GetProposalStateHashesRequest, GetProposalStateHashesResponse> {
|
||||
RequestProposalStateHashesHandler(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
NodeAddress nodeAddress,
|
||||
Listener<GetProposalStateHashesResponse> listener) {
|
||||
super(networkNode, peerManager, nodeAddress, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetProposalStateHashesRequest getGetStateHashesRequest(int fromHeight) {
|
||||
return new GetProposalStateHashesRequest(fromHeight, nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetProposalStateHashesResponse castToGetStateHashesResponse(NetworkEnvelope networkEnvelope) {
|
||||
return (GetProposalStateHashesResponse) networkEnvelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGetStateHashesResponse(NetworkEnvelope networkEnvelope) {
|
||||
return networkEnvelope instanceof GetProposalStateHashesResponse;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* 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.monitoring.network;
|
||||
|
||||
import bisq.core.dao.monitoring.network.messages.GetStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetStateHashesResponse;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.CloseConnectionReason;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.network.MessageListener;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Slf4j
|
||||
abstract class RequestStateHashesHandler<Req extends GetStateHashesRequest, Res extends GetStateHashesResponse> implements MessageListener {
|
||||
private static final long TIMEOUT = 120;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public interface Listener<Res extends GetStateHashesResponse> {
|
||||
void onComplete(Res getStateHashesResponse, Optional<NodeAddress> peersNodeAddress);
|
||||
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
void onFault(String errorMessage, @SuppressWarnings("SameParameterValue") @Nullable Connection connection);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Class fields
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private final NetworkNode networkNode;
|
||||
private final PeerManager peerManager;
|
||||
private final NodeAddress nodeAddress;
|
||||
private final Listener<Res> listener;
|
||||
private Timer timeoutTimer;
|
||||
final int nonce = new Random().nextInt();
|
||||
private boolean stopped;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
RequestStateHashesHandler(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
NodeAddress nodeAddress,
|
||||
Listener<Res> listener) {
|
||||
this.networkNode = networkNode;
|
||||
this.peerManager = peerManager;
|
||||
this.nodeAddress = nodeAddress;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Abstract
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected abstract Req getGetStateHashesRequest(int fromHeight);
|
||||
|
||||
protected abstract Res castToGetStateHashesResponse(NetworkEnvelope networkEnvelope);
|
||||
|
||||
protected abstract boolean isGetStateHashesResponse(NetworkEnvelope networkEnvelope);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void requestStateHashes(int fromHeight) {
|
||||
if (!stopped) {
|
||||
Req getStateHashesRequest = getGetStateHashesRequest(fromHeight);
|
||||
if (timeoutTimer == null) {
|
||||
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
||||
if (!stopped) {
|
||||
String errorMessage = "A timeout occurred at sending getStateHashesRequest:" + getStateHashesRequest +
|
||||
" on peersNodeAddress:" + nodeAddress;
|
||||
log.debug(errorMessage + " / RequestStateHashesHandler=" + RequestStateHashesHandler.this);
|
||||
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_TIMEOUT);
|
||||
} else {
|
||||
log.trace("We have stopped already. We ignore that timeoutTimer.run call. " +
|
||||
"Might be caused by an previous networkNode.sendMessage.onFailure.");
|
||||
}
|
||||
},
|
||||
TIMEOUT);
|
||||
}
|
||||
|
||||
log.info("We send to peer {} a {}.", nodeAddress, getStateHashesRequest);
|
||||
networkNode.addMessageListener(this);
|
||||
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getStateHashesRequest);
|
||||
Futures.addCallback(future, new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(Connection connection) {
|
||||
if (!stopped) {
|
||||
log.info("Sending of {} message to peer {} succeeded.",
|
||||
getStateHashesRequest.getClass().getSimpleName(),
|
||||
nodeAddress.getFullAddress());
|
||||
} else {
|
||||
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onSuccess call." +
|
||||
"Might be caused by an previous timeout.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
if (!stopped) {
|
||||
String errorMessage = "Sending getStateHashesRequest to " + nodeAddress +
|
||||
" failed. That is expected if the peer is offline.\n\t" +
|
||||
"getStateHashesRequest=" + getStateHashesRequest + "." +
|
||||
"\n\tException=" + throwable.getMessage();
|
||||
log.error(errorMessage);
|
||||
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
|
||||
} else {
|
||||
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onFailure call. " +
|
||||
"Might be caused by an previous timeout.");
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.warn("We have stopped already. We ignore that requestProposalsHash call.");
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// MessageListener implementation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||
if (isGetStateHashesResponse(networkEnvelope)) {
|
||||
if (connection.getPeersNodeAddressOptional().isPresent() && connection.getPeersNodeAddressOptional().get().equals(nodeAddress)) {
|
||||
if (!stopped) {
|
||||
Res getStateHashesResponse = castToGetStateHashesResponse(networkEnvelope);
|
||||
if (getStateHashesResponse.getRequestNonce() == nonce) {
|
||||
stopTimeoutTimer();
|
||||
cleanup();
|
||||
log.info("We received from peer {} a {} with {} stateHashes",
|
||||
nodeAddress.getFullAddress(), getStateHashesResponse.getClass().getSimpleName(),
|
||||
getStateHashesResponse.getStateHashes().size());
|
||||
listener.onComplete(getStateHashesResponse, connection.getPeersNodeAddressOptional());
|
||||
} else {
|
||||
log.warn("Nonce not matching. That can happen rarely if we get a response after a canceled " +
|
||||
"handshake (timeout causes connection close but peer might have sent a msg before " +
|
||||
"connection was closed).\n\t" +
|
||||
"We drop that message. nonce={} / requestNonce={}",
|
||||
nonce, getStateHashesResponse.getRequestNonce());
|
||||
}
|
||||
} else {
|
||||
log.warn("We have stopped already.");
|
||||
}
|
||||
} else if (connection.getPeersNodeAddressOptional().isPresent()) {
|
||||
log.debug("{}: We got a message from another node. We ignore that.",
|
||||
this.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
private void handleFault(String errorMessage, NodeAddress nodeAddress, CloseConnectionReason closeConnectionReason) {
|
||||
cleanup();
|
||||
peerManager.handleConnectionFault(nodeAddress);
|
||||
listener.onFault(errorMessage, null);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
stopped = true;
|
||||
networkNode.removeMessageListener(this);
|
||||
stopTimeoutTimer();
|
||||
}
|
||||
|
||||
private void stopTimeoutTimer() {
|
||||
if (timeoutTimer != null) {
|
||||
timeoutTimer.stop();
|
||||
timeoutTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* 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.monitoring.network;
|
||||
|
||||
import bisq.core.dao.monitoring.model.StateHash;
|
||||
import bisq.core.dao.monitoring.network.messages.GetStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetStateHashesResponse;
|
||||
import bisq.core.dao.monitoring.network.messages.NewStateHashMessage;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.network.MessageListener;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.Broadcaster;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Slf4j
|
||||
public abstract class StateNetworkService<Msg extends NewStateHashMessage,
|
||||
Req extends GetStateHashesRequest,
|
||||
Res extends GetStateHashesResponse<StH>,
|
||||
Han extends RequestStateHashesHandler,
|
||||
StH extends StateHash> implements MessageListener {
|
||||
|
||||
public interface Listener<Msg extends NewStateHashMessage, Req extends GetStateHashesRequest, StH extends StateHash> {
|
||||
void onNewStateHashMessage(Msg newStateHashMessage, Connection connection);
|
||||
|
||||
void onGetStateHashRequest(Connection connection, Req getStateHashRequest);
|
||||
|
||||
void onPeersStateHashes(List<StH> stateHashes, Optional<NodeAddress> peersNodeAddress);
|
||||
}
|
||||
|
||||
protected final NetworkNode networkNode;
|
||||
protected final PeerManager peerManager;
|
||||
private final Broadcaster broadcaster;
|
||||
|
||||
@Getter
|
||||
private final Map<NodeAddress, Han> requestStateHashHandlerMap = new HashMap<>();
|
||||
private final List<Listener<Msg, Req, StH>> listeners = new CopyOnWriteArrayList<>();
|
||||
private boolean messageListenerAdded;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public StateNetworkService(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
Broadcaster broadcaster) {
|
||||
this.networkNode = networkNode;
|
||||
this.peerManager = peerManager;
|
||||
this.broadcaster = broadcaster;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Abstract
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected abstract Req castToGetStateHashRequest(NetworkEnvelope networkEnvelope);
|
||||
|
||||
|
||||
protected abstract boolean isGetStateHashesRequest(NetworkEnvelope networkEnvelope);
|
||||
|
||||
|
||||
protected abstract Msg castToNewStateHashMessage(NetworkEnvelope networkEnvelope);
|
||||
|
||||
|
||||
protected abstract boolean isNewStateHashMessage(NetworkEnvelope networkEnvelope);
|
||||
|
||||
protected abstract Res getGetStateHashesResponse(int nonce, List<StH> stateHashes);
|
||||
|
||||
protected abstract Msg getNewStateHashMessage(StH myStateHash);
|
||||
|
||||
protected abstract Han getRequestStateHashesHandler(NodeAddress nodeAddress, RequestStateHashesHandler.Listener<Res> listener);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// MessageListener implementation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||
if (isNewStateHashMessage(networkEnvelope)) {
|
||||
Msg newStateHashMessage = castToNewStateHashMessage(networkEnvelope);
|
||||
log.info("We received a {} from peer {} with stateHash={} ",
|
||||
newStateHashMessage.getClass().getSimpleName(),
|
||||
connection.getPeersNodeAddressOptional(),
|
||||
newStateHashMessage.getStateHash());
|
||||
listeners.forEach(e -> e.onNewStateHashMessage(newStateHashMessage, connection));
|
||||
} else if (isGetStateHashesRequest(networkEnvelope)) {
|
||||
Req getStateHashRequest = castToGetStateHashRequest(networkEnvelope);
|
||||
log.info("We received a {} from peer {} for height={} ",
|
||||
getStateHashRequest.getClass().getSimpleName(),
|
||||
connection.getPeersNodeAddressOptional(),
|
||||
getStateHashRequest.getHeight());
|
||||
listeners.forEach(e -> e.onGetStateHashRequest(connection, getStateHashRequest));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addListeners() {
|
||||
if (!messageListenerAdded) {
|
||||
networkNode.addMessageListener(this);
|
||||
messageListenerAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void sendGetStateHashesResponse(Connection connection, int nonce, List<StH> stateHashes) {
|
||||
Res getStateHashesResponse = getGetStateHashesResponse(nonce, stateHashes);
|
||||
log.info("Send {} with {} stateHashes to peer {}", getStateHashesResponse.getClass().getSimpleName(),
|
||||
stateHashes.size(), connection.getPeersNodeAddressOptional());
|
||||
connection.sendMessage(getStateHashesResponse);
|
||||
}
|
||||
|
||||
public void requestHashesFromAllConnectedSeedNodes(int fromHeight) {
|
||||
networkNode.getConfirmedConnections().stream()
|
||||
.filter(peerManager::isSeedNode)
|
||||
.forEach(connection -> connection.getPeersNodeAddressOptional()
|
||||
.ifPresent(e -> requestHashesFromSeedNode(fromHeight, e)));
|
||||
}
|
||||
|
||||
public void broadcastMyStateHash(StH myStateHash) {
|
||||
NewStateHashMessage newStateHashMessage = getNewStateHashMessage(myStateHash);
|
||||
broadcaster.broadcast(newStateHashMessage, networkNode.getNodeAddress(), null, true);
|
||||
}
|
||||
|
||||
public void requestHashes(int fromHeight, String peersAddress) {
|
||||
requestHashesFromSeedNode(fromHeight, new NodeAddress(peersAddress));
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
requestStateHashHandlerMap.clear();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listeners
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addListener(Listener<Msg, Req, StH> listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener<Msg, Req, StH> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void requestHashesFromSeedNode(int fromHeight, NodeAddress nodeAddress) {
|
||||
RequestStateHashesHandler.Listener<Res> listener = new RequestStateHashesHandler.Listener<>() {
|
||||
@Override
|
||||
public void onComplete(Res getStateHashesResponse, Optional<NodeAddress> peersNodeAddress) {
|
||||
requestStateHashHandlerMap.remove(nodeAddress);
|
||||
List<StH> stateHashes = getStateHashesResponse.getStateHashes();
|
||||
listeners.forEach(e -> e.onPeersStateHashes(stateHashes, peersNodeAddress));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage, @Nullable Connection connection) {
|
||||
log.warn("requestDaoStateHashesHandler with outbound connection failed.\n\tnodeAddress={}\n\t" +
|
||||
"ErrorMessage={}", nodeAddress, errorMessage);
|
||||
requestStateHashHandlerMap.remove(nodeAddress);
|
||||
}
|
||||
};
|
||||
Han requestStateHashesHandler = getRequestStateHashesHandler(nodeAddress, listener);
|
||||
requestStateHashHandlerMap.put(nodeAddress, requestStateHashesHandler);
|
||||
requestStateHashesHandler.requestStateHashes(fromHeight);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public final class GetBlindVoteStateHashesRequest extends GetStateHashesRequest {
|
||||
public GetBlindVoteStateHashesRequest(int fromCycleStartHeight, int nonce) {
|
||||
super(fromCycleStartHeight, nonce, Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private GetBlindVoteStateHashesRequest(int height, int nonce, int messageVersion) {
|
||||
super(height, nonce, messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setGetBlindVoteStateHashesRequest(PB.GetBlindVoteStateHashesRequest.newBuilder()
|
||||
.setHeight(height)
|
||||
.setNonce(nonce))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(PB.GetBlindVoteStateHashesRequest proto, int messageVersion) {
|
||||
return new GetBlindVoteStateHashesRequest(proto.getHeight(), proto.getNonce(), messageVersion);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public final class GetBlindVoteStateHashesResponse extends GetStateHashesResponse<BlindVoteStateHash> {
|
||||
public GetBlindVoteStateHashesResponse(List<BlindVoteStateHash> stateHashes, int requestNonce) {
|
||||
super(stateHashes, requestNonce, Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private GetBlindVoteStateHashesResponse(List<BlindVoteStateHash> stateHashes,
|
||||
int requestNonce,
|
||||
int messageVersion) {
|
||||
super(stateHashes, requestNonce, messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setGetBlindVoteStateHashesResponse(PB.GetBlindVoteStateHashesResponse.newBuilder()
|
||||
.addAllStateHashes(stateHashes.stream()
|
||||
.map(BlindVoteStateHash::toProtoMessage)
|
||||
.collect(Collectors.toList()))
|
||||
.setRequestNonce(requestNonce))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(PB.GetBlindVoteStateHashesResponse proto, int messageVersion) {
|
||||
return new GetBlindVoteStateHashesResponse(proto.getStateHashesList().isEmpty() ?
|
||||
new ArrayList<>() :
|
||||
proto.getStateHashesList().stream()
|
||||
.map(BlindVoteStateHash::fromProto)
|
||||
.collect(Collectors.toList()),
|
||||
proto.getRequestNonce(),
|
||||
messageVersion);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public final class GetDaoStateHashesRequest extends GetStateHashesRequest {
|
||||
public GetDaoStateHashesRequest(int height, int nonce) {
|
||||
super(height, nonce, Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private GetDaoStateHashesRequest(int height, int nonce, int messageVersion) {
|
||||
super(height, nonce, messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setGetDaoStateHashesRequest(PB.GetDaoStateHashesRequest.newBuilder()
|
||||
.setHeight(height)
|
||||
.setNonce(nonce))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(PB.GetDaoStateHashesRequest proto, int messageVersion) {
|
||||
return new GetDaoStateHashesRequest(proto.getHeight(), proto.getNonce(), messageVersion);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public final class GetDaoStateHashesResponse extends GetStateHashesResponse<DaoStateHash> {
|
||||
public GetDaoStateHashesResponse(List<DaoStateHash> daoStateHashes, int requestNonce) {
|
||||
super(daoStateHashes, requestNonce, Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private GetDaoStateHashesResponse(List<DaoStateHash> daoStateHashes,
|
||||
int requestNonce,
|
||||
int messageVersion) {
|
||||
super(daoStateHashes, requestNonce, messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setGetDaoStateHashesResponse(PB.GetDaoStateHashesResponse.newBuilder()
|
||||
.addAllStateHashes(stateHashes.stream()
|
||||
.map(DaoStateHash::toProtoMessage)
|
||||
.collect(Collectors.toList()))
|
||||
.setRequestNonce(requestNonce))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(PB.GetDaoStateHashesResponse proto, int messageVersion) {
|
||||
return new GetDaoStateHashesResponse(proto.getStateHashesList().isEmpty() ?
|
||||
new ArrayList<>() :
|
||||
proto.getStateHashesList().stream()
|
||||
.map(DaoStateHash::fromProto)
|
||||
.collect(Collectors.toList()),
|
||||
proto.getRequestNonce(),
|
||||
messageVersion);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public final class GetProposalStateHashesRequest extends GetStateHashesRequest {
|
||||
public GetProposalStateHashesRequest(int fromCycleStartHeight, int nonce) {
|
||||
super(fromCycleStartHeight, nonce, Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private GetProposalStateHashesRequest(int height, int nonce, int messageVersion) {
|
||||
super(height, nonce, messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setGetProposalStateHashesRequest(PB.GetProposalStateHashesRequest.newBuilder()
|
||||
.setHeight(height)
|
||||
.setNonce(nonce))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(PB.GetProposalStateHashesRequest proto, int messageVersion) {
|
||||
return new GetProposalStateHashesRequest(proto.getHeight(), proto.getNonce(), messageVersion);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.core.dao.monitoring.model.ProposalStateHash;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public final class GetProposalStateHashesResponse extends GetStateHashesResponse<ProposalStateHash> {
|
||||
public GetProposalStateHashesResponse(List<ProposalStateHash> proposalStateHashes, int requestNonce) {
|
||||
super(proposalStateHashes, requestNonce, Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private GetProposalStateHashesResponse(List<ProposalStateHash> proposalStateHashes,
|
||||
int requestNonce,
|
||||
int messageVersion) {
|
||||
super(proposalStateHashes, requestNonce, messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setGetProposalStateHashesResponse(PB.GetProposalStateHashesResponse.newBuilder()
|
||||
.addAllStateHashes(stateHashes.stream()
|
||||
.map(ProposalStateHash::toProtoMessage)
|
||||
.collect(Collectors.toList()))
|
||||
.setRequestNonce(requestNonce))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(PB.GetProposalStateHashesResponse proto, int messageVersion) {
|
||||
return new GetProposalStateHashesResponse(proto.getStateHashesList().isEmpty() ?
|
||||
new ArrayList<>() :
|
||||
proto.getStateHashesList().stream()
|
||||
.map(ProposalStateHash::fromProto)
|
||||
.collect(Collectors.toList()),
|
||||
proto.getRequestNonce(),
|
||||
messageVersion);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.network.p2p.DirectMessage;
|
||||
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public abstract class GetStateHashesRequest extends NetworkEnvelope implements DirectMessage, CapabilityRequiringPayload {
|
||||
protected final int height;
|
||||
protected final int nonce;
|
||||
|
||||
protected GetStateHashesRequest(int height, int nonce, int messageVersion) {
|
||||
super(messageVersion);
|
||||
this.height = height;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Capabilities getRequiredCapabilities() {
|
||||
return new Capabilities(Capability.DAO_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GetStateHashesRequest{" +
|
||||
",\n height=" + height +
|
||||
",\n nonce=" + nonce +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.core.dao.monitoring.model.StateHash;
|
||||
|
||||
import bisq.network.p2p.DirectMessage;
|
||||
import bisq.network.p2p.ExtendedDataSizePermission;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public abstract class GetStateHashesResponse<T extends StateHash> extends NetworkEnvelope implements DirectMessage, ExtendedDataSizePermission {
|
||||
protected final List<T> stateHashes;
|
||||
protected final int requestNonce;
|
||||
|
||||
protected GetStateHashesResponse(List<T> stateHashes,
|
||||
int requestNonce,
|
||||
int messageVersion) {
|
||||
super(messageVersion);
|
||||
this.stateHashes = stateHashes;
|
||||
this.requestNonce = requestNonce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GetStateHashesResponse{" +
|
||||
"\n stateHashes=" + stateHashes +
|
||||
",\n requestNonce=" + requestNonce +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public final class NewBlindVoteStateHashMessage extends NewStateHashMessage<BlindVoteStateHash> {
|
||||
public NewBlindVoteStateHashMessage(BlindVoteStateHash stateHash) {
|
||||
super(stateHash, Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private NewBlindVoteStateHashMessage(BlindVoteStateHash stateHash, int messageVersion) {
|
||||
super(stateHash, messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setNewBlindVoteStateHashMessage(PB.NewBlindVoteStateHashMessage.newBuilder()
|
||||
.setStateHash(stateHash.toProtoMessage()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(PB.NewBlindVoteStateHashMessage proto, int messageVersion) {
|
||||
return new NewBlindVoteStateHashMessage(BlindVoteStateHash.fromProto(proto.getStateHash()), messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Capabilities getRequiredCapabilities() {
|
||||
return new Capabilities(Capability.DAO_STATE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public final class NewDaoStateHashMessage extends NewStateHashMessage<DaoStateHash> {
|
||||
public NewDaoStateHashMessage(DaoStateHash daoStateHash) {
|
||||
super(daoStateHash, Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private NewDaoStateHashMessage(DaoStateHash daoStateHash, int messageVersion) {
|
||||
super(daoStateHash, messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setNewDaoStateHashMessage(PB.NewDaoStateHashMessage.newBuilder()
|
||||
.setStateHash(stateHash.toProtoMessage()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(PB.NewDaoStateHashMessage proto, int messageVersion) {
|
||||
return new NewDaoStateHashMessage(DaoStateHash.fromProto(proto.getStateHash()), messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Capabilities getRequiredCapabilities() {
|
||||
return new Capabilities(Capability.DAO_STATE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.core.dao.monitoring.model.ProposalStateHash;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public final class NewProposalStateHashMessage extends NewStateHashMessage<ProposalStateHash> {
|
||||
public NewProposalStateHashMessage(ProposalStateHash proposalStateHash) {
|
||||
super(proposalStateHash, Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private NewProposalStateHashMessage(ProposalStateHash proposalStateHash, int messageVersion) {
|
||||
super(proposalStateHash, messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setNewProposalStateHashMessage(PB.NewProposalStateHashMessage.newBuilder()
|
||||
.setStateHash(stateHash.toProtoMessage()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(PB.NewProposalStateHashMessage proto, int messageVersion) {
|
||||
return new NewProposalStateHashMessage(ProposalStateHash.fromProto(proto.getStateHash()), messageVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Capabilities getRequiredCapabilities() {
|
||||
return new Capabilities(Capability.DAO_STATE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.monitoring.network.messages;
|
||||
|
||||
import bisq.core.dao.monitoring.model.StateHash;
|
||||
|
||||
import bisq.network.p2p.storage.messages.BroadcastMessage;
|
||||
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public abstract class NewStateHashMessage<T extends StateHash> extends BroadcastMessage implements CapabilityRequiringPayload {
|
||||
protected final T stateHash;
|
||||
|
||||
protected NewStateHashMessage(T stateHash, int messageVersion) {
|
||||
super(messageVersion);
|
||||
this.stateHash = stateHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Capabilities getRequiredCapabilities() {
|
||||
return new Capabilities(Capability.DAO_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NewStateHashMessage{" +
|
||||
"\n stateHash=" + stateHash +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
|
@ -185,7 +185,9 @@ public class FullNode extends BsqNode {
|
|||
} else {
|
||||
log.info("parseBlocksIfNewBlockAvailable did not result in a new block, so we complete.");
|
||||
log.info("parse {} blocks took {} seconds", blocksToParseInBatch, (System.currentTimeMillis() - parseInBatchStartTime) / 1000d);
|
||||
onParseBlockChainComplete();
|
||||
if (!parseBlockchainComplete) {
|
||||
onParseBlockChainComplete();
|
||||
}
|
||||
}
|
||||
},
|
||||
this::handleError);
|
||||
|
|
|
@ -74,7 +74,6 @@ public class RpcService {
|
|||
private final String rpcPassword;
|
||||
private final String rpcPort;
|
||||
private final String rpcBlockPort;
|
||||
private final boolean dumpBlockchainData;
|
||||
|
||||
private BtcdClient client;
|
||||
private BtcdDaemon daemon;
|
||||
|
@ -92,8 +91,7 @@ public class RpcService {
|
|||
@Inject
|
||||
public RpcService(Preferences preferences,
|
||||
@Named(DaoOptionKeys.RPC_PORT) String rpcPort,
|
||||
@Named(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT) String rpcBlockPort,
|
||||
@Named(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA) boolean dumpBlockchainData) {
|
||||
@Named(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT) String rpcBlockPort) {
|
||||
this.rpcUser = preferences.getRpcUser();
|
||||
this.rpcPassword = preferences.getRpcPw();
|
||||
|
||||
|
@ -109,8 +107,6 @@ public class RpcService {
|
|||
"18443"; // regtest
|
||||
this.rpcBlockPort = rpcBlockPort != null && !rpcBlockPort.isEmpty() ? rpcBlockPort : "5125";
|
||||
|
||||
this.dumpBlockchainData = dumpBlockchainData;
|
||||
|
||||
log.info("Version of btcd-cli4j library: {}", BtcdCli4jVersion.VERSION);
|
||||
}
|
||||
|
||||
|
@ -305,7 +301,7 @@ public class RpcService {
|
|||
// We don't support raw MS which are the only case where scriptPubKey.getAddresses()>1
|
||||
String address = scriptPubKey.getAddresses() != null &&
|
||||
scriptPubKey.getAddresses().size() == 1 ? scriptPubKey.getAddresses().get(0) : null;
|
||||
PubKeyScript pubKeyScript = dumpBlockchainData ? new PubKeyScript(scriptPubKey) : null;
|
||||
PubKeyScript pubKeyScript = new PubKeyScript(scriptPubKey);
|
||||
return new RawTxOutput(rawBtcTxOutput.getN(),
|
||||
rawBtcTxOutput.getValue().movePointRight(8).longValue(),
|
||||
rawBtcTx.getTxId(),
|
||||
|
|
|
@ -125,7 +125,7 @@ public class RequestBlocksHandler implements MessageListener {
|
|||
TIMEOUT);
|
||||
}
|
||||
|
||||
log.info("We send to peer {} a {}.", nodeAddress, getBlocksRequest);
|
||||
log.info("We request blocks from peer {} from block height {}.", nodeAddress, getBlocksRequest.getFromBlockHeight());
|
||||
networkNode.addMessageListener(this);
|
||||
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getBlocksRequest);
|
||||
Futures.addCallback(future, new FutureCallback<Connection>() {
|
||||
|
@ -190,7 +190,7 @@ public class RequestBlocksHandler implements MessageListener {
|
|||
log.warn("We have stopped already. We ignore that onDataRequest call.");
|
||||
}
|
||||
} else {
|
||||
log.warn("We got a message from another connection and ignore it. That should never happen.");
|
||||
log.warn("We got a message from ourselves. That should never happen.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,4 +32,9 @@ public interface DaoStateListener {
|
|||
|
||||
default void onParseBlockCompleteAfterBatchProcessing(Block block) {
|
||||
}
|
||||
|
||||
// Called after the parsing of a block is complete and we do not allow any change in the daoState until the next
|
||||
// block arrives.
|
||||
default void onDaoStateChanged(Block block) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,9 +46,9 @@ import java.util.Comparator;
|
|||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -71,6 +71,7 @@ public class DaoStateService implements DaoSetupService {
|
|||
private final List<DaoStateListener> daoStateListeners = new CopyOnWriteArrayList<>();
|
||||
@Getter
|
||||
private boolean parseBlockChainComplete;
|
||||
private boolean allowDaoStateChange;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -95,6 +96,8 @@ public class DaoStateService implements DaoSetupService {
|
|||
|
||||
@Override
|
||||
public void start() {
|
||||
allowDaoStateChange = true;
|
||||
assertDaoStateChange();
|
||||
daoState.setChainHeight(genesisTxInfo.getGenesisBlockHeight());
|
||||
}
|
||||
|
||||
|
@ -104,6 +107,9 @@ public class DaoStateService implements DaoSetupService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void applySnapshot(DaoState snapshot) {
|
||||
allowDaoStateChange = true;
|
||||
assertDaoStateChange();
|
||||
|
||||
log.info("Apply snapshot with chain height {}", snapshot.getChainHeight());
|
||||
|
||||
daoState.setChainHeight(snapshot.getChainHeight());
|
||||
|
@ -117,15 +123,18 @@ public class DaoStateService implements DaoSetupService {
|
|||
daoState.getUnspentTxOutputMap().clear();
|
||||
daoState.getUnspentTxOutputMap().putAll(snapshot.getUnspentTxOutputMap());
|
||||
|
||||
daoState.getNonBsqTxOutputMap().clear();
|
||||
daoState.getNonBsqTxOutputMap().putAll(snapshot.getNonBsqTxOutputMap());
|
||||
|
||||
daoState.getSpentInfoMap().clear();
|
||||
daoState.getSpentInfoMap().putAll(snapshot.getSpentInfoMap());
|
||||
|
||||
daoState.getConfiscatedLockupTxList().clear();
|
||||
daoState.getConfiscatedLockupTxList().addAll(snapshot.getConfiscatedLockupTxList());
|
||||
|
||||
daoState.getIssuanceMap().clear();
|
||||
daoState.getIssuanceMap().putAll(snapshot.getIssuanceMap());
|
||||
|
||||
daoState.getSpentInfoMap().clear();
|
||||
daoState.getSpentInfoMap().putAll(snapshot.getSpentInfoMap());
|
||||
|
||||
daoState.getParamChangeList().clear();
|
||||
daoState.getParamChangeList().addAll(snapshot.getParamChangeList());
|
||||
|
||||
|
@ -144,6 +153,10 @@ public class DaoStateService implements DaoSetupService {
|
|||
return DaoState.getClone(snapshotCandidate);
|
||||
}
|
||||
|
||||
public byte[] getSerializedDaoState() {
|
||||
return daoState.toProtoMessage().toByteArray();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ChainHeight
|
||||
|
@ -162,6 +175,11 @@ public class DaoStateService implements DaoSetupService {
|
|||
return daoState.getCycles();
|
||||
}
|
||||
|
||||
public void addCycle(Cycle cycle) {
|
||||
assertDaoStateChange();
|
||||
getCycles().add(cycle);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Cycle getCurrentCycle() {
|
||||
return !getCycles().isEmpty() ? getCycles().getLast() : null;
|
||||
|
@ -190,12 +208,14 @@ public class DaoStateService implements DaoSetupService {
|
|||
|
||||
// First we get the blockHeight set
|
||||
public void onNewBlockHeight(int blockHeight) {
|
||||
allowDaoStateChange = true;
|
||||
daoState.setChainHeight(blockHeight);
|
||||
daoStateListeners.forEach(listener -> listener.onNewBlockHeight(blockHeight));
|
||||
}
|
||||
|
||||
// Second we get the block added with empty txs
|
||||
public void onNewBlockWithEmptyTxs(Block block) {
|
||||
assertDaoStateChange();
|
||||
if (daoState.getBlocks().isEmpty() && block.getHeight() != getGenesisBlockHeight()) {
|
||||
log.warn("We don't have any blocks yet and we received a block which is not the genesis block. " +
|
||||
"We ignore that block as the first block need to be the genesis block. " +
|
||||
|
@ -215,14 +235,19 @@ public class DaoStateService implements DaoSetupService {
|
|||
// VoteResult and other listeners like balances usually listen on onParseTxsCompleteAfterBatchProcessing
|
||||
// so we need to make sure that vote result calculation is completed before (e.g. for comp. request to
|
||||
// update balance).
|
||||
// TODO the dependency on ordering is nto good here.... Listeners should not depend on order of execution.
|
||||
daoStateListeners.forEach(l -> l.onParseBlockComplete(block));
|
||||
|
||||
// We use 2 different handlers as we don't want to update domain listeners during batch processing of all
|
||||
// blocks as that cause performance issues. In earlier versions when we updated at each block it took
|
||||
// 50 sec. for 4000 blocks, after that change it was about 4 sec.
|
||||
// Clients
|
||||
if (parseBlockChainComplete)
|
||||
daoStateListeners.forEach(l -> l.onParseBlockCompleteAfterBatchProcessing(block));
|
||||
|
||||
// Here listeners must not trigger any state change in the DAO as we trigger the validation service to
|
||||
// generate a hash of the state.
|
||||
allowDaoStateChange = false;
|
||||
daoStateListeners.forEach(l -> l.onDaoStateChanged(block));
|
||||
}
|
||||
|
||||
// Called after parsing of all pending blocks is completed
|
||||
|
@ -321,8 +346,8 @@ public class DaoStateService implements DaoSetupService {
|
|||
.flatMap(block -> block.getTxs().stream());
|
||||
}
|
||||
|
||||
public Map<String, Tx> getTxMap() {
|
||||
return getTxStream().collect(Collectors.toMap(Tx::getId, tx -> tx));
|
||||
public TreeMap<String, Tx> getTxMap() {
|
||||
return new TreeMap<>(getTxStream().collect(Collectors.toMap(Tx::getId, tx -> tx)));
|
||||
}
|
||||
|
||||
public Set<Tx> getTxs() {
|
||||
|
@ -405,15 +430,17 @@ public class DaoStateService implements DaoSetupService {
|
|||
// UnspentTxOutput
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Map<TxOutputKey, TxOutput> getUnspentTxOutputMap() {
|
||||
public TreeMap<TxOutputKey, TxOutput> getUnspentTxOutputMap() {
|
||||
return daoState.getUnspentTxOutputMap();
|
||||
}
|
||||
|
||||
public void addUnspentTxOutput(TxOutput txOutput) {
|
||||
assertDaoStateChange();
|
||||
getUnspentTxOutputMap().put(txOutput.getKey(), txOutput);
|
||||
}
|
||||
|
||||
public void removeUnspentTxOutput(TxOutput txOutput) {
|
||||
assertDaoStateChange();
|
||||
getUnspentTxOutputMap().remove(txOutput.getKey());
|
||||
}
|
||||
|
||||
|
@ -547,6 +574,7 @@ public class DaoStateService implements DaoSetupService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addIssuance(Issuance issuance) {
|
||||
assertDaoStateChange();
|
||||
daoState.getIssuanceMap().put(issuance.getTxId(), issuance);
|
||||
}
|
||||
|
||||
|
@ -595,16 +623,18 @@ public class DaoStateService implements DaoSetupService {
|
|||
// Non-BSQ
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//TODO we never remove NonBsqTxOutput!
|
||||
//FIXME called at result phase even if there is not a new one (passed txo from prev. cycle which was already added)
|
||||
public void addNonBsqTxOutput(TxOutput txOutput) {
|
||||
assertDaoStateChange();
|
||||
checkArgument(txOutput.getTxOutputType() == TxOutputType.ISSUANCE_CANDIDATE_OUTPUT,
|
||||
"txOutput must be type ISSUANCE_CANDIDATE_OUTPUT");
|
||||
log.info("addNonBsqTxOutput: txOutput={}", txOutput);
|
||||
daoState.getNonBsqTxOutputMap().put(txOutput.getKey(), txOutput);
|
||||
}
|
||||
|
||||
public Optional<TxOutput> getBtcTxOutput(TxOutputKey key) {
|
||||
// Issuance candidates which did not got accepted in voting are covered here
|
||||
Map<TxOutputKey, TxOutput> nonBsqTxOutputMap = daoState.getNonBsqTxOutputMap();
|
||||
TreeMap<TxOutputKey, TxOutput> nonBsqTxOutputMap = daoState.getNonBsqTxOutputMap();
|
||||
if (nonBsqTxOutputMap.containsKey(key))
|
||||
return Optional.of(nonBsqTxOutputMap.get(key));
|
||||
|
||||
|
@ -827,6 +857,7 @@ public class DaoStateService implements DaoSetupService {
|
|||
}
|
||||
|
||||
private void doConfiscateBond(String lockupTxId) {
|
||||
assertDaoStateChange();
|
||||
log.warn("TxId {} added to confiscatedLockupTxIdList.", lockupTxId);
|
||||
daoState.getConfiscatedLockupTxList().add(lockupTxId);
|
||||
}
|
||||
|
@ -855,6 +886,7 @@ public class DaoStateService implements DaoSetupService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void setNewParam(int blockHeight, Param param, String paramValue) {
|
||||
assertDaoStateChange();
|
||||
List<ParamChange> paramChangeList = daoState.getParamChangeList();
|
||||
getStartHeightOfNextCycle(blockHeight)
|
||||
.ifPresent(heightOfNewCycle -> {
|
||||
|
@ -903,6 +935,7 @@ public class DaoStateService implements DaoSetupService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void setSpentInfo(TxOutputKey txOutputKey, SpentInfo spentInfo) {
|
||||
assertDaoStateChange();
|
||||
daoState.getSpentInfoMap().put(txOutputKey, spentInfo);
|
||||
}
|
||||
|
||||
|
@ -920,9 +953,14 @@ public class DaoStateService implements DaoSetupService {
|
|||
}
|
||||
|
||||
public void addEvaluatedProposalSet(Set<EvaluatedProposal> evaluatedProposals) {
|
||||
assertDaoStateChange();
|
||||
|
||||
evaluatedProposals.stream()
|
||||
.filter(e -> !daoState.getEvaluatedProposalList().contains(e))
|
||||
.forEach(daoState.getEvaluatedProposalList()::add);
|
||||
|
||||
// We need deterministic order for the hash chain
|
||||
daoState.getEvaluatedProposalList().sort(Comparator.comparing(EvaluatedProposal::getProposalTxId));
|
||||
}
|
||||
|
||||
public List<DecryptedBallotsWithMerits> getDecryptedBallotsWithMeritsList() {
|
||||
|
@ -930,9 +968,14 @@ public class DaoStateService implements DaoSetupService {
|
|||
}
|
||||
|
||||
public void addDecryptedBallotsWithMeritsSet(Set<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsSet) {
|
||||
assertDaoStateChange();
|
||||
|
||||
decryptedBallotsWithMeritsSet.stream()
|
||||
.filter(e -> !daoState.getDecryptedBallotsWithMeritsList().contains(e))
|
||||
.forEach(daoState.getDecryptedBallotsWithMeritsList()::add);
|
||||
|
||||
// We need deterministic order for the hash chain
|
||||
daoState.getDecryptedBallotsWithMeritsList().sort(Comparator.comparing(DecryptedBallotsWithMerits::getBlindVoteTxId));
|
||||
}
|
||||
|
||||
|
||||
|
@ -964,5 +1007,24 @@ public class DaoStateService implements DaoSetupService {
|
|||
public void removeDaoStateListener(DaoStateListener listener) {
|
||||
daoStateListeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public String daoStateToString() {
|
||||
return daoState.toString();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void assertDaoStateChange() {
|
||||
if (!allowDaoStateChange)
|
||||
throw new RuntimeException("We got a call which would change the daoState outside of the allowed event phase");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.core.dao.governance.period.CycleService;
|
||||
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;
|
||||
|
||||
|
@ -37,17 +39,20 @@ import lombok.extern.slf4j.Slf4j;
|
|||
* SNAPSHOT_GRID old not less than 2 times the SNAPSHOT_GRID old.
|
||||
*/
|
||||
@Slf4j
|
||||
public class DaoStateSnapshotService implements DaoStateListener {
|
||||
public class DaoStateSnapshotService {
|
||||
private static final int SNAPSHOT_GRID = 20;
|
||||
|
||||
private final DaoStateService daoStateService;
|
||||
private final GenesisTxInfo genesisTxInfo;
|
||||
private final CycleService cycleService;
|
||||
private final DaoStateStorageService daoStateStorageService;
|
||||
private final DaoStateMonitoringService daoStateMonitoringService;
|
||||
|
||||
private DaoState snapshotCandidate;
|
||||
private DaoState daoStateSnapshotCandidate;
|
||||
private LinkedList<DaoStateHash> daoStateHashChainSnapshotCandidate = new LinkedList<>();
|
||||
private int chainHeightOfLastApplySnapshot;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -56,47 +61,13 @@ public class DaoStateSnapshotService implements DaoStateListener {
|
|||
public DaoStateSnapshotService(DaoStateService daoStateService,
|
||||
GenesisTxInfo genesisTxInfo,
|
||||
CycleService cycleService,
|
||||
DaoStateStorageService daoStateStorageService) {
|
||||
DaoStateStorageService daoStateStorageService,
|
||||
DaoStateMonitoringService daoStateMonitoringService) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
this.cycleService = cycleService;
|
||||
this.daoStateStorageService = daoStateStorageService;
|
||||
|
||||
this.daoStateService.addDaoStateListener(this);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We listen to each ParseTxsComplete event even if the batch processing of all blocks at startup is not completed
|
||||
// as we need to write snapshots during that batch processing.
|
||||
@Override
|
||||
public void onParseBlockComplete(Block block) {
|
||||
int chainHeight = block.getHeight();
|
||||
|
||||
// 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 = snapshotCandidate == null || snapshotCandidate.getChainHeight() != chainHeight;
|
||||
if (isSnapshotHeight(chainHeight) &&
|
||||
!daoStateService.getBlocks().isEmpty() &&
|
||||
isValidHeight(daoStateService.getBlocks().getLast().getHeight()) &&
|
||||
noSnapshotCandidateOrDifferentHeight) {
|
||||
// At trigger event we store the latest snapshotCandidate to disc
|
||||
if (snapshotCandidate != null) {
|
||||
// We clone because storage is in a threaded context and we set the snapshotCandidate to our current
|
||||
// state in the next step
|
||||
DaoState cloned = daoStateService.getClone(snapshotCandidate);
|
||||
daoStateStorageService.persist(cloned);
|
||||
log.info("Saved snapshotCandidate with height {} to Disc at height {} ",
|
||||
snapshotCandidate.getChainHeight(), chainHeight);
|
||||
}
|
||||
|
||||
// Now we clone and keep it in memory for the next trigger event
|
||||
snapshotCandidate = daoStateService.getClone();
|
||||
log.info("Cloned new snapshotCandidate at height " + chainHeight);
|
||||
}
|
||||
this.daoStateMonitoringService = daoStateMonitoringService;
|
||||
}
|
||||
|
||||
|
||||
|
@ -104,31 +75,66 @@ public class DaoStateSnapshotService implements DaoStateListener {
|
|||
// 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();
|
||||
|
||||
// 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 = daoStateSnapshotCandidate == null ||
|
||||
daoStateSnapshotCandidate.getChainHeight() != chainHeight;
|
||||
if (isSnapshotHeight(chainHeight) &&
|
||||
!daoStateService.getBlocks().isEmpty() &&
|
||||
isValidHeight(daoStateService.getBlocks().getLast().getHeight()) &&
|
||||
noSnapshotCandidateOrDifferentHeight) {
|
||||
// At trigger event we store the latest snapshotCandidate to disc
|
||||
if (daoStateSnapshotCandidate != null) {
|
||||
// We clone because storage is in a threaded context and we set the snapshotCandidate to our current
|
||||
// state in the next step
|
||||
DaoState clonedDaoState = daoStateService.getClone(daoStateSnapshotCandidate);
|
||||
LinkedList<DaoStateHash> clonedDaoStateHashChain = new LinkedList<>(daoStateHashChainSnapshotCandidate);
|
||||
daoStateStorageService.persist(clonedDaoState, clonedDaoStateHashChain);
|
||||
|
||||
log.info("Saved snapshotCandidate with height {} to Disc at height {} ",
|
||||
daoStateSnapshotCandidate.getChainHeight(), chainHeight);
|
||||
}
|
||||
|
||||
// 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 " + chainHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void applySnapshot(boolean fromReorg) {
|
||||
DaoState persisted = daoStateStorageService.getPersistedBsqState();
|
||||
if (persisted != null) {
|
||||
LinkedList<Block> blocks = persisted.getBlocks();
|
||||
int chainHeightOfPersisted = persisted.getChainHeight();
|
||||
DaoState persistedBsqState = daoStateStorageService.getPersistedBsqState();
|
||||
LinkedList<DaoStateHash> persistedDaoStateHashChain = daoStateStorageService.getPersistedDaoStateHashChain();
|
||||
if (persistedBsqState != null) {
|
||||
LinkedList<Block> blocks = persistedBsqState.getBlocks();
|
||||
int chainHeightOfPersisted = persistedBsqState.getChainHeight();
|
||||
if (!blocks.isEmpty()) {
|
||||
int heightOfLastBlock = blocks.getLast().getHeight();
|
||||
log.info("applySnapshot from persisted daoState with height of last block {}", heightOfLastBlock);
|
||||
log.info("applySnapshot from persistedBsqState daoState with height of last block {}", heightOfLastBlock);
|
||||
if (isValidHeight(heightOfLastBlock)) {
|
||||
if (chainHeightOfLastApplySnapshot != chainHeightOfPersisted) {
|
||||
chainHeightOfLastApplySnapshot = chainHeightOfPersisted;
|
||||
daoStateService.applySnapshot(persisted);
|
||||
daoStateService.applySnapshot(persistedBsqState);
|
||||
daoStateMonitoringService.applySnapshot(persistedDaoStateHashChain);
|
||||
} else {
|
||||
// The reorg might have been caused by the previous parsing which might contains a range of
|
||||
// blocks.
|
||||
log.warn("We applied already a snapshot with chainHeight {}. We will reset the daoState and " +
|
||||
"start over from the genesis transaction again.", chainHeightOfLastApplySnapshot);
|
||||
persisted = new DaoState();
|
||||
applyEmptySnapshot(persisted);
|
||||
applyEmptySnapshot();
|
||||
}
|
||||
}
|
||||
} else if (fromReorg) {
|
||||
log.info("We got a reorg and we want to apply the snapshot but it is empty. That is expected in the first blocks until the " +
|
||||
"first snapshot has been created. We use our applySnapshot method and restart from the genesis tx");
|
||||
applyEmptySnapshot(persisted);
|
||||
applyEmptySnapshot();
|
||||
}
|
||||
} else {
|
||||
log.info("Try to apply snapshot but no stored snapshot available. That is expected at first blocks.");
|
||||
|
@ -144,13 +150,16 @@ public class DaoStateSnapshotService implements DaoStateListener {
|
|||
return heightOfLastBlock >= genesisTxInfo.getGenesisBlockHeight();
|
||||
}
|
||||
|
||||
private void applyEmptySnapshot(DaoState persisted) {
|
||||
private void applyEmptySnapshot() {
|
||||
DaoState emptyDaoState = new DaoState();
|
||||
int genesisBlockHeight = genesisTxInfo.getGenesisBlockHeight();
|
||||
persisted.setChainHeight(genesisBlockHeight);
|
||||
emptyDaoState.setChainHeight(genesisBlockHeight);
|
||||
chainHeightOfLastApplySnapshot = genesisBlockHeight;
|
||||
daoStateService.applySnapshot(persisted);
|
||||
daoStateService.applySnapshot(emptyDaoState);
|
||||
// In case we apply an empty snapshot we need to trigger the cycleService.addFirstCycle method
|
||||
cycleService.addFirstCycle();
|
||||
|
||||
daoStateMonitoringService.applySnapshot(new LinkedList<>());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
import bisq.core.dao.state.model.DaoState;
|
||||
|
||||
import bisq.network.p2p.storage.persistence.ResourceDataStoreService;
|
||||
|
@ -30,6 +32,7 @@ import javax.inject.Named;
|
|||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -39,9 +42,13 @@ import lombok.extern.slf4j.Slf4j;
|
|||
*/
|
||||
@Slf4j
|
||||
public class DaoStateStorageService extends StoreService<DaoStateStore> {
|
||||
private static final String FILE_NAME = "DaoStateStore";
|
||||
//TODO We need to rename as we have a new file structure with the hashchain feature and need to enforce the
|
||||
// new file to be used.
|
||||
// We can rename to DaoStateStore before mainnet launch again.
|
||||
private static final String FILE_NAME = "DaoStateStore2";
|
||||
|
||||
private DaoState daoState;
|
||||
private final DaoState daoState;
|
||||
private final DaoStateMonitoringService daoStateMonitoringService;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -51,10 +58,12 @@ public class DaoStateStorageService extends StoreService<DaoStateStore> {
|
|||
@Inject
|
||||
public DaoStateStorageService(ResourceDataStoreService resourceDataStoreService,
|
||||
DaoState daoState,
|
||||
DaoStateMonitoringService daoStateMonitoringService,
|
||||
@Named(Storage.STORAGE_DIR) File storageDir,
|
||||
Storage<DaoStateStore> daoSnapshotStorage) {
|
||||
super(storageDir, daoSnapshotStorage);
|
||||
this.daoState = daoState;
|
||||
this.daoStateMonitoringService = daoStateMonitoringService;
|
||||
|
||||
resourceDataStoreService.addService(this);
|
||||
}
|
||||
|
@ -69,12 +78,13 @@ public class DaoStateStorageService extends StoreService<DaoStateStore> {
|
|||
return FILE_NAME;
|
||||
}
|
||||
|
||||
public void persist(DaoState daoState) {
|
||||
persist(daoState, 200);
|
||||
public void persist(DaoState daoState, LinkedList<DaoStateHash> daoStateHashChain) {
|
||||
persist(daoState, daoStateHashChain, 200);
|
||||
}
|
||||
|
||||
public void persist(DaoState daoState, long delayInMilli) {
|
||||
private void persist(DaoState daoState, LinkedList<DaoStateHash> daoStateHashChain, long delayInMilli) {
|
||||
store.setDaoState(daoState);
|
||||
store.setDaoStateHashChain(daoStateHashChain);
|
||||
storage.queueUpForSave(store, delayInMilli);
|
||||
}
|
||||
|
||||
|
@ -82,8 +92,12 @@ public class DaoStateStorageService extends StoreService<DaoStateStore> {
|
|||
return store.getDaoState();
|
||||
}
|
||||
|
||||
public LinkedList<DaoStateHash> getPersistedDaoStateHashChain() {
|
||||
return store.getDaoStateHashChain();
|
||||
}
|
||||
|
||||
public void resetDaoState(Runnable resultHandler) {
|
||||
persist(new DaoState(), 1);
|
||||
persist(new DaoState(), new LinkedList<>(), 1);
|
||||
UserThread.runAfter(resultHandler, 300, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
@ -94,6 +108,6 @@ public class DaoStateStorageService extends StoreService<DaoStateStore> {
|
|||
|
||||
@Override
|
||||
protected DaoStateStore createStore() {
|
||||
return new DaoStateStore(DaoState.getClone(daoState));
|
||||
return new DaoStateStore(DaoState.getClone(daoState), new LinkedList<>(daoStateMonitoringService.getDaoStateHashChain()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
import bisq.core.dao.state.model.DaoState;
|
||||
|
||||
import bisq.common.proto.persistable.PersistableEnvelope;
|
||||
|
@ -25,12 +26,13 @@ import io.bisq.generated.protobuffer.PB;
|
|||
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
@ -38,13 +40,16 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
public class DaoStateStore implements PersistableEnvelope {
|
||||
// DaoState is always a clone and must not be used for read access beside initial read from disc when we apply
|
||||
// the snapshot!
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
DaoState daoState;
|
||||
private DaoState daoState;
|
||||
@Getter
|
||||
@Setter
|
||||
private LinkedList<DaoStateHash> daoStateHashChain;
|
||||
|
||||
DaoStateStore(DaoState daoState) {
|
||||
DaoStateStore(DaoState daoState, LinkedList<DaoStateHash> daoStateHashChain) {
|
||||
this.daoState = daoState;
|
||||
this.daoStateHashChain = daoStateHashChain;
|
||||
}
|
||||
|
||||
|
||||
|
@ -55,13 +60,21 @@ public class DaoStateStore implements PersistableEnvelope {
|
|||
public Message toProtoMessage() {
|
||||
checkNotNull(daoState, "daoState must not be null when toProtoMessage is invoked");
|
||||
PB.DaoStateStore.Builder builder = PB.DaoStateStore.newBuilder()
|
||||
.setBsqState(daoState.getBsqStateBuilder());
|
||||
.setBsqState(daoState.getBsqStateBuilder())
|
||||
.addAllDaoStateHash(daoStateHashChain.stream()
|
||||
.map(DaoStateHash::toProtoMessage)
|
||||
.collect(Collectors.toList()));
|
||||
return PB.PersistableEnvelope.newBuilder()
|
||||
.setDaoStateStore(builder)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static PersistableEnvelope fromProto(PB.DaoStateStore proto) {
|
||||
return new DaoStateStore(DaoState.fromProto(proto.getBsqState()));
|
||||
LinkedList<DaoStateHash> daoStateHashList = proto.getDaoStateHashList().isEmpty() ?
|
||||
new LinkedList<>() :
|
||||
new LinkedList<>(proto.getDaoStateHashList().stream()
|
||||
.map(DaoStateHash::fromProto)
|
||||
.collect(Collectors.toList()));
|
||||
return new DaoStateStore(DaoState.fromProto(proto.getBsqState()), daoStateHashList);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,10 +36,10 @@ import com.google.protobuf.Message;
|
|||
import javax.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
|
@ -51,6 +51,9 @@ import lombok.extern.slf4j.Slf4j;
|
|||
* Holds both blockchain data as well as data derived from the governance process (voting).
|
||||
* <p>
|
||||
* One BSQ block with empty txs adds 152 bytes which results in about 8 MB/year
|
||||
*
|
||||
* For supporting the hashChain we need to ensure deterministic sorting behaviour of all collections so we use a
|
||||
* TreeMap which is sorted by the key.
|
||||
*/
|
||||
@Slf4j
|
||||
public class DaoState implements PersistablePayload {
|
||||
|
@ -77,17 +80,17 @@ public class DaoState implements PersistablePayload {
|
|||
|
||||
// These maps represent mutual data which can get changed at parsing a transaction
|
||||
@Getter
|
||||
private final Map<TxOutputKey, TxOutput> unspentTxOutputMap;
|
||||
private final TreeMap<TxOutputKey, TxOutput> unspentTxOutputMap;
|
||||
@Getter
|
||||
private final Map<TxOutputKey, TxOutput> nonBsqTxOutputMap;
|
||||
private final TreeMap<TxOutputKey, TxOutput> nonBsqTxOutputMap;
|
||||
@Getter
|
||||
private final Map<TxOutputKey, SpentInfo> spentInfoMap;
|
||||
private final TreeMap<TxOutputKey, SpentInfo> spentInfoMap;
|
||||
|
||||
// These maps are related to state change triggered by voting
|
||||
@Getter
|
||||
private final List<String> confiscatedLockupTxList;
|
||||
@Getter
|
||||
private final Map<String, Issuance> issuanceMap; // key is txId
|
||||
private final TreeMap<String, Issuance> issuanceMap; // key is txId
|
||||
@Getter
|
||||
private final List<ParamChange> paramChangeList;
|
||||
|
||||
|
@ -109,11 +112,11 @@ public class DaoState implements PersistablePayload {
|
|||
this(0,
|
||||
new LinkedList<>(),
|
||||
new LinkedList<>(),
|
||||
new HashMap<>(),
|
||||
new HashMap<>(),
|
||||
new HashMap<>(),
|
||||
new TreeMap<>(),
|
||||
new TreeMap<>(),
|
||||
new TreeMap<>(),
|
||||
new ArrayList<>(),
|
||||
new HashMap<>(),
|
||||
new TreeMap<>(),
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>()
|
||||
|
@ -128,11 +131,11 @@ public class DaoState implements PersistablePayload {
|
|||
private DaoState(int chainHeight,
|
||||
LinkedList<Block> blocks,
|
||||
LinkedList<Cycle> cycles,
|
||||
Map<TxOutputKey, TxOutput> unspentTxOutputMap,
|
||||
Map<TxOutputKey, TxOutput> nonBsqTxOutputMap,
|
||||
Map<TxOutputKey, SpentInfo> spentInfoMap,
|
||||
TreeMap<TxOutputKey, TxOutput> unspentTxOutputMap,
|
||||
TreeMap<TxOutputKey, TxOutput> nonBsqTxOutputMap,
|
||||
TreeMap<TxOutputKey, SpentInfo> spentInfoMap,
|
||||
List<String> confiscatedLockupTxList,
|
||||
Map<String, Issuance> issuanceMap,
|
||||
TreeMap<String, Issuance> issuanceMap,
|
||||
List<ParamChange> paramChangeList,
|
||||
List<EvaluatedProposal> evaluatedProposalList,
|
||||
List<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsList) {
|
||||
|
@ -182,15 +185,15 @@ public class DaoState implements PersistablePayload {
|
|||
.collect(Collectors.toCollection(LinkedList::new));
|
||||
LinkedList<Cycle> cycles = proto.getCyclesList().stream()
|
||||
.map(Cycle::fromProto).collect(Collectors.toCollection(LinkedList::new));
|
||||
Map<TxOutputKey, TxOutput> unspentTxOutputMap = proto.getUnspentTxOutputMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue())));
|
||||
Map<TxOutputKey, TxOutput> nonBsqTxOutputMap = proto.getNonBsqTxOutputMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue())));
|
||||
Map<TxOutputKey, SpentInfo> spentInfoMap = proto.getSpentInfoMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> SpentInfo.fromProto(e.getValue())));
|
||||
TreeMap<TxOutputKey, TxOutput> unspentTxOutputMap = new TreeMap<>(proto.getUnspentTxOutputMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue()))));
|
||||
TreeMap<TxOutputKey, TxOutput> nonBsqTxOutputMap = new TreeMap<>(proto.getNonBsqTxOutputMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue()))));
|
||||
TreeMap<TxOutputKey, SpentInfo> spentInfoMap = new TreeMap<>(proto.getSpentInfoMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> SpentInfo.fromProto(e.getValue()))));
|
||||
List<String> confiscatedLockupTxList = new ArrayList<>(proto.getConfiscatedLockupTxListList());
|
||||
Map<String, Issuance> issuanceMap = proto.getIssuanceMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> Issuance.fromProto(e.getValue())));
|
||||
TreeMap<String, Issuance> issuanceMap = new TreeMap<>(proto.getIssuanceMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> Issuance.fromProto(e.getValue()))));
|
||||
List<ParamChange> paramChangeList = proto.getParamChangeListList().stream()
|
||||
.map(ParamChange::fromProto).collect(Collectors.toCollection(ArrayList::new));
|
||||
List<EvaluatedProposal> evaluatedProposalList = proto.getEvaluatedProposalListList().stream()
|
||||
|
@ -218,4 +221,21 @@ public class DaoState implements PersistablePayload {
|
|||
public void setChainHeight(int chainHeight) {
|
||||
this.chainHeight = chainHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DaoState{" +
|
||||
"\n chainHeight=" + chainHeight +
|
||||
",\n blocks=" + blocks +
|
||||
",\n cycles=" + cycles +
|
||||
",\n unspentTxOutputMap=" + unspentTxOutputMap +
|
||||
",\n nonBsqTxOutputMap=" + nonBsqTxOutputMap +
|
||||
",\n spentInfoMap=" + spentInfoMap +
|
||||
",\n confiscatedLockupTxList=" + confiscatedLockupTxList +
|
||||
",\n issuanceMap=" + issuanceMap +
|
||||
",\n paramChangeList=" + paramChangeList +
|
||||
",\n evaluatedProposalList=" + evaluatedProposalList +
|
||||
",\n decryptedBallotsWithMeritsList=" + decryptedBallotsWithMeritsList +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,9 +45,10 @@ public abstract class BaseTxOutput implements ImmutableDaoStateModel {
|
|||
protected final long value;
|
||||
protected final String txId;
|
||||
|
||||
// Only set if dumpBlockchainData is true
|
||||
// Before v0.9.6 it was only set if dumpBlockchainData was set to true but we changed that with 0.9.6
|
||||
// so that is is always set. We still need to support it because of backward compatibility.
|
||||
@Nullable
|
||||
protected final PubKeyScript pubKeyScript;
|
||||
protected final PubKeyScript pubKeyScript; // Has about 50 bytes, total size of TxOutput is about 300 bytes.
|
||||
@Nullable
|
||||
protected final String address;
|
||||
@Nullable
|
||||
|
@ -69,7 +70,6 @@ public abstract class BaseTxOutput implements ImmutableDaoStateModel {
|
|||
this.address = address;
|
||||
this.opReturnData = opReturnData;
|
||||
this.blockHeight = blockHeight;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ import bisq.core.dao.state.model.ImmutableDaoStateModel;
|
|||
|
||||
import lombok.Value;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
|
@ -29,7 +31,7 @@ import javax.annotation.concurrent.Immutable;
|
|||
*/
|
||||
@Immutable
|
||||
@Value
|
||||
public final class TxOutputKey implements ImmutableDaoStateModel {
|
||||
public final class TxOutputKey implements ImmutableDaoStateModel, Comparable {
|
||||
private final String txId;
|
||||
private final int index;
|
||||
|
||||
|
@ -47,4 +49,9 @@ public final class TxOutputKey implements ImmutableDaoStateModel {
|
|||
final String[] tokens = keyAsString.split(":");
|
||||
return new TxOutputKey(tokens[0], Integer.valueOf(tokens[1]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Object o) {
|
||||
return toString().compareTo(o.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,15 @@ import bisq.core.arbitration.messages.PeerOpenedDisputeMessage;
|
|||
import bisq.core.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
|
||||
import bisq.core.dao.governance.blindvote.network.messages.RepublishGovernanceDataRequest;
|
||||
import bisq.core.dao.governance.proposal.storage.temp.TempProposalPayload;
|
||||
import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesResponse;
|
||||
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesResponse;
|
||||
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesResponse;
|
||||
import bisq.core.dao.monitoring.network.messages.NewBlindVoteStateHashMessage;
|
||||
import bisq.core.dao.monitoring.network.messages.NewDaoStateHashMessage;
|
||||
import bisq.core.dao.monitoring.network.messages.NewProposalStateHashMessage;
|
||||
import bisq.core.dao.node.messages.GetBlocksRequest;
|
||||
import bisq.core.dao.node.messages.GetBlocksResponse;
|
||||
import bisq.core.dao.node.messages.NewBlockBroadcastMessage;
|
||||
|
@ -153,7 +162,6 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
|||
return GetBlocksResponse.fromProto(proto.getGetBlocksResponse(), messageVersion);
|
||||
case NEW_BLOCK_BROADCAST_MESSAGE:
|
||||
return NewBlockBroadcastMessage.fromProto(proto.getNewBlockBroadcastMessage(), messageVersion);
|
||||
|
||||
case ADD_PERSISTABLE_NETWORK_PAYLOAD_MESSAGE:
|
||||
return AddPersistableNetworkPayloadMessage.fromProto(proto.getAddPersistableNetworkPayloadMessage(), this, messageVersion);
|
||||
case ACK_MESSAGE:
|
||||
|
@ -161,6 +169,27 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
|||
case REPUBLISH_GOVERNANCE_DATA_REQUEST:
|
||||
return RepublishGovernanceDataRequest.fromProto(proto.getRepublishGovernanceDataRequest(), messageVersion);
|
||||
|
||||
case NEW_DAO_STATE_HASH_MESSAGE:
|
||||
return NewDaoStateHashMessage.fromProto(proto.getNewDaoStateHashMessage(), messageVersion);
|
||||
case GET_DAO_STATE_HASHES_REQUEST:
|
||||
return GetDaoStateHashesRequest.fromProto(proto.getGetDaoStateHashesRequest(), messageVersion);
|
||||
case GET_DAO_STATE_HASHES_RESPONSE:
|
||||
return GetDaoStateHashesResponse.fromProto(proto.getGetDaoStateHashesResponse(), messageVersion);
|
||||
|
||||
case NEW_PROPOSAL_STATE_HASH_MESSAGE:
|
||||
return NewProposalStateHashMessage.fromProto(proto.getNewProposalStateHashMessage(), messageVersion);
|
||||
case GET_PROPOSAL_STATE_HASHES_REQUEST:
|
||||
return GetProposalStateHashesRequest.fromProto(proto.getGetProposalStateHashesRequest(), messageVersion);
|
||||
case GET_PROPOSAL_STATE_HASHES_RESPONSE:
|
||||
return GetProposalStateHashesResponse.fromProto(proto.getGetProposalStateHashesResponse(), messageVersion);
|
||||
|
||||
case NEW_BLIND_VOTE_STATE_HASH_MESSAGE:
|
||||
return NewBlindVoteStateHashMessage.fromProto(proto.getNewBlindVoteStateHashMessage(), messageVersion);
|
||||
case GET_BLIND_VOTE_STATE_HASHES_REQUEST:
|
||||
return GetBlindVoteStateHashesRequest.fromProto(proto.getGetBlindVoteStateHashesRequest(), messageVersion);
|
||||
case GET_BLIND_VOTE_STATE_HASHES_RESPONSE:
|
||||
return GetBlindVoteStateHashesResponse.fromProto(proto.getGetBlindVoteStateHashesResponse(), messageVersion);
|
||||
|
||||
default:
|
||||
throw new ProtobufferException("Unknown proto message case (PB.NetworkEnvelope). messageCase=" +
|
||||
proto.getMessageCase() + "; proto raw data=" + proto.toString());
|
||||
|
|
|
@ -39,6 +39,7 @@ public class CoreNetworkCapabilities {
|
|||
supportedCapabilities.add(Capability.PROPOSAL);
|
||||
supportedCapabilities.add(Capability.BLIND_VOTE);
|
||||
supportedCapabilities.add(Capability.BSQ_BLOCK);
|
||||
supportedCapabilities.add(Capability.DAO_STATE);
|
||||
|
||||
String isFullDaoNode = bisqEnvironment.getProperty(DaoOptionKeys.FULL_DAO_NODE, String.class, "");
|
||||
if (isFullDaoNode != null && !isFullDaoNode.isEmpty())
|
||||
|
|
|
@ -1260,6 +1260,7 @@ dao.tab.bsqWallet=BSQ wallet
|
|||
dao.tab.proposals=Governance
|
||||
dao.tab.bonding=Bonding
|
||||
dao.tab.proofOfBurn=Asset listing fee/Proof of burn
|
||||
dao.tab.monitor=Network monitor
|
||||
dao.tab.news=News
|
||||
|
||||
dao.paidWithBsq=paid with BSQ
|
||||
|
@ -1311,6 +1312,7 @@ dao.results.proposals.table.header.result=Vote result
|
|||
dao.results.proposals.voting.detail.header=Vote results for selected proposal
|
||||
|
||||
dao.results.exceptions=Vote result exception(s)
|
||||
|
||||
# suppress inspection "UnusedProperty"
|
||||
dao.param.UNDEFINED=Undefined
|
||||
|
||||
|
@ -1742,7 +1744,6 @@ dao.wallet.dashboard.burntTx=No. of all fee payments transactions
|
|||
dao.wallet.dashboard.price=Latest BSQ/BTC trade price (in Bisq)
|
||||
dao.wallet.dashboard.marketCap=Market capitalisation (based on trade price)
|
||||
|
||||
|
||||
dao.wallet.receive.fundYourWallet=Your BSQ receive address
|
||||
dao.wallet.receive.bsqAddress=BSQ wallet address (Fresh unused address)
|
||||
|
||||
|
@ -1857,6 +1858,51 @@ dao.news.DAOOnTestnet.fourthSection.title=4. Explore a BSQ Block Explorer
|
|||
dao.news.DAOOnTestnet.fourthSection.content=Since BSQ is just bitcoin, you can see BSQ transactions on our bitcoin block explorer.
|
||||
dao.news.DAOOnTestnet.readMoreLink=Read the full documentation
|
||||
|
||||
dao.monitor.daoState=DAO state
|
||||
dao.monitor.proposals=Proposals state
|
||||
dao.monitor.blindVotes=Blind votes state
|
||||
|
||||
dao.monitor.table.peers=Peers
|
||||
dao.monitor.table.conflicts=Conflicts
|
||||
dao.monitor.state=Status
|
||||
dao.monitor.requestAlHashes=Request all hashes
|
||||
dao.monitor.resync=Resync DAO state
|
||||
dao.monitor.table.header.cycleBlockHeight=Cycle / block height
|
||||
dao.monitor.table.cycleBlockHeight=Cycle {0} / block {1}
|
||||
|
||||
dao.monitor.daoState.headline=DAO state
|
||||
dao.monitor.daoState.daoStateInSync=Your local DAO state is in consensus with the network
|
||||
dao.monitor.daoState.daoStateNotInSync=Your local DAO state is not in consensus with the network. Please resync your \
|
||||
DAO state.
|
||||
dao.monitor.daoState.table.headline=Chain of DAO state hashes
|
||||
dao.monitor.daoState.table.blockHeight=Block height
|
||||
dao.monitor.daoState.table.hash=Hash of DAO state
|
||||
dao.monitor.daoState.table.prev=Previous hash
|
||||
dao.monitor.daoState.conflictTable.headline=DAO state hashes from peers in conflict
|
||||
|
||||
dao.monitor.proposal.headline=Proposals state
|
||||
dao.monitor.proposal.daoStateInSync=Your local proposals state is in consensus with the network
|
||||
dao.monitor.proposal.daoStateNotInSync=Your local proposals state is not in consensus with the network. Please restart your \
|
||||
application.
|
||||
dao.monitor.proposal.table.headline=Chain of proposal state hashes
|
||||
dao.monitor.proposal.conflictTable.headline=Proposal state hashes from peers in conflict
|
||||
|
||||
dao.monitor.proposal.table.hash=Hash of proposal state
|
||||
dao.monitor.proposal.table.prev=Previous hash
|
||||
dao.monitor.proposal.table.numProposals=No. proposals
|
||||
|
||||
|
||||
dao.monitor.blindVote.headline=Blind votes state
|
||||
dao.monitor.blindVote.daoStateInSync=Your local blind votes state is in consensus with the network
|
||||
dao.monitor.blindVote.daoStateNotInSync=Your local blind votes state is not in consensus with the network. Please restart your \
|
||||
application.
|
||||
dao.monitor.blindVote.table.headline=Chain of blind vote state hashes
|
||||
dao.monitor.blindVote.conflictTable.headline=Blind vote state hashes from peers in conflict
|
||||
dao.monitor.blindVote.table.hash=Hash of blind vote state
|
||||
dao.monitor.blindVote.table.prev=Previous hash
|
||||
dao.monitor.blindVote.table.numBlindVotes=No. blind votes
|
||||
|
||||
|
||||
####################################################################
|
||||
# Windows
|
||||
####################################################################
|
||||
|
|
|
@ -38,6 +38,7 @@ public class DaoStateServiceTest {
|
|||
);
|
||||
|
||||
Block block = new Block(0, 1534800000, "fakeblockhash0", null);
|
||||
stateService.onNewBlockHeight(0);
|
||||
stateService.onNewBlockWithEmptyTxs(block);
|
||||
Assert.assertEquals(
|
||||
"Block has to be genesis block to get added.",
|
||||
|
@ -52,10 +53,13 @@ public class DaoStateServiceTest {
|
|||
);
|
||||
|
||||
block = new Block(1, 1534800001, "fakeblockhash1", null);
|
||||
stateService.onNewBlockHeight(1);
|
||||
stateService.onNewBlockWithEmptyTxs(block);
|
||||
block = new Block(2, 1534800002, "fakeblockhash2", null);
|
||||
stateService.onNewBlockHeight(2);
|
||||
stateService.onNewBlockWithEmptyTxs(block);
|
||||
block = new Block(3, 1534800003, "fakeblockhash3", null);
|
||||
stateService.onNewBlockHeight(3);
|
||||
stateService.onNewBlockWithEmptyTxs(block);
|
||||
Assert.assertEquals(
|
||||
"Block that was never added should still not exist after adding more blocks.",
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.core.dao.governance.period.CycleService;
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
|
@ -33,7 +34,7 @@ import static org.junit.Assert.assertTrue;
|
|||
import static org.powermock.api.mockito.PowerMockito.mock;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({DaoStateService.class, GenesisTxInfo.class, CycleService.class, DaoStateStorageService.class})
|
||||
@PrepareForTest({DaoStateService.class, GenesisTxInfo.class, CycleService.class, DaoStateStorageService.class, DaoStateMonitoringService.class})
|
||||
@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"})
|
||||
public class DaoStateSnapshotServiceTest {
|
||||
|
||||
|
@ -44,7 +45,8 @@ public class DaoStateSnapshotServiceTest {
|
|||
daoStateSnapshotService = new DaoStateSnapshotService(mock(DaoStateService.class),
|
||||
mock(GenesisTxInfo.class),
|
||||
mock(CycleService.class),
|
||||
mock(DaoStateStorageService.class));
|
||||
mock(DaoStateStorageService.class),
|
||||
mock(DaoStateMonitoringService.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -2049,6 +2049,14 @@ textfield */
|
|||
-fx-fill: -fx-accent;
|
||||
}
|
||||
|
||||
.dao-inSync {
|
||||
-fx-text-fill: -bs-rd-green;
|
||||
}
|
||||
|
||||
.dao-inConflict {
|
||||
-fx-text-fill: -bs-rd-error-red;
|
||||
}
|
||||
|
||||
/********************************************************************************************************************
|
||||
* *
|
||||
* Notifications *
|
||||
|
|
|
@ -28,6 +28,7 @@ import bisq.desktop.main.MainView;
|
|||
import bisq.desktop.main.dao.bonding.BondingView;
|
||||
import bisq.desktop.main.dao.burnbsq.BurnBsqView;
|
||||
import bisq.desktop.main.dao.governance.GovernanceView;
|
||||
import bisq.desktop.main.dao.monitor.MonitorView;
|
||||
import bisq.desktop.main.dao.news.NewsView;
|
||||
import bisq.desktop.main.dao.wallet.BsqWalletView;
|
||||
import bisq.desktop.main.dao.wallet.dashboard.BsqDashboardView;
|
||||
|
@ -52,7 +53,7 @@ import javafx.beans.value.ChangeListener;
|
|||
public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
|
||||
|
||||
@FXML
|
||||
private Tab bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, daoNewsTab;
|
||||
private Tab bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, daoNewsTab, monitor;
|
||||
|
||||
private Navigation.Listener navigationListener;
|
||||
private ChangeListener<Tab> tabChangeListener;
|
||||
|
@ -80,23 +81,26 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
proposalsTab = new Tab(Res.get("dao.tab.proposals").toUpperCase());
|
||||
bondingTab = new Tab(Res.get("dao.tab.bonding").toUpperCase());
|
||||
burnBsqTab = new Tab(Res.get("dao.tab.proofOfBurn").toUpperCase());
|
||||
monitor = new Tab(Res.get("dao.tab.monitor").toUpperCase());
|
||||
|
||||
bsqWalletTab.setClosable(false);
|
||||
proposalsTab.setClosable(false);
|
||||
bondingTab.setClosable(false);
|
||||
burnBsqTab.setClosable(false);
|
||||
monitor.setClosable(false);
|
||||
|
||||
if (!DevEnv.isDaoActivated()) {
|
||||
bsqWalletTab.setDisable(true);
|
||||
proposalsTab.setDisable(true);
|
||||
bondingTab.setDisable(true);
|
||||
burnBsqTab.setDisable(true);
|
||||
bsqWalletTab.setDisable(true);
|
||||
monitor.setDisable(true);
|
||||
|
||||
daoNewsTab = new Tab(Res.get("dao.tab.news").toUpperCase());
|
||||
|
||||
root.getTabs().add(daoNewsTab);
|
||||
} else {
|
||||
root.getTabs().addAll(bsqWalletTab, proposalsTab, bondingTab, burnBsqTab);
|
||||
root.getTabs().addAll(bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, monitor);
|
||||
}
|
||||
|
||||
navigationListener = viewPath -> {
|
||||
|
@ -121,6 +125,8 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
navigation.navigateTo(MainView.class, DaoView.class, BondingView.class);
|
||||
} else if (newValue == burnBsqTab) {
|
||||
navigation.navigateTo(MainView.class, DaoView.class, BurnBsqView.class);
|
||||
} else if (newValue == monitor) {
|
||||
navigation.navigateTo(MainView.class, DaoView.class, MonitorView.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -141,6 +147,8 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
navigation.navigateTo(MainView.class, DaoView.class, BondingView.class);
|
||||
else if (selectedItem == burnBsqTab)
|
||||
navigation.navigateTo(MainView.class, DaoView.class, BurnBsqView.class);
|
||||
else if (selectedItem == monitor)
|
||||
navigation.navigateTo(MainView.class, DaoView.class, MonitorView.class);
|
||||
}
|
||||
} else {
|
||||
loadView(NewsView.class);
|
||||
|
@ -173,6 +181,8 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
selectedTab = bondingTab;
|
||||
} else if (view instanceof BurnBsqView) {
|
||||
selectedTab = burnBsqTab;
|
||||
} else if (view instanceof MonitorView) {
|
||||
selectedTab = monitor;
|
||||
} else if (view instanceof NewsView) {
|
||||
selectedTab = daoNewsTab;
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ public class BondsView extends ActivatableView<GridPane, Void> {
|
|||
bondedReputationRepository.getBonds().addListener(bondedReputationListener);
|
||||
bondedRolesRepository.getBonds().addListener(bondedRolesListener);
|
||||
updateList();
|
||||
GUIUtil.setFitToRowsForTableView(tableView, 37, 28, 2, 30);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -189,6 +189,7 @@ public class MyReputationView extends ActivatableView<GridPane, Void> implements
|
|||
setNewRandomSalt();
|
||||
|
||||
updateList();
|
||||
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 30);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -103,6 +103,7 @@ public class RolesView extends ActivatableView<GridPane, Void> {
|
|||
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
|
||||
daoFacade.getBondedRoles().addListener(bondedRoleListChangeListener);
|
||||
updateList();
|
||||
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 30);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -191,6 +191,7 @@ public class AssetFeeView extends ActivatableView<GridPane, Void> implements Bsq
|
|||
});
|
||||
|
||||
updateList();
|
||||
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 10);
|
||||
updateButtonState();
|
||||
|
||||
feeAmountInputTextField.resetValidation();
|
||||
|
|
|
@ -187,6 +187,8 @@ public class ProofOfBurnView extends ActivatableView<GridPane, Void> implements
|
|||
preImageTextField.setValidator(new InputValidator());
|
||||
|
||||
updateList();
|
||||
GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 2, 4);
|
||||
GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 10);
|
||||
updateButtonState();
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,6 @@ import java.util.List;
|
|||
|
||||
@FxmlView
|
||||
public class GovernanceView extends ActivatableViewAndModel {
|
||||
|
||||
private final ViewLoader viewLoader;
|
||||
private final Navigation navigation;
|
||||
private final DaoFacade daoFacade;
|
||||
|
@ -102,6 +101,7 @@ public class GovernanceView extends ActivatableViewAndModel {
|
|||
ProposalsView.class, baseNavPath);
|
||||
result = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.result"),
|
||||
VoteResultView.class, baseNavPath);
|
||||
|
||||
leftVBox.getChildren().addAll(dashboard, make, open, result);
|
||||
}
|
||||
|
||||
|
|
|
@ -184,8 +184,6 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
|
|||
public void initialize() {
|
||||
super.initialize();
|
||||
|
||||
root.getStyleClass().add("vote-root");
|
||||
|
||||
gridRow = phasesView.addGroup(root, gridRow);
|
||||
|
||||
proposalDisplayGridPane = new GridPane();
|
||||
|
@ -226,6 +224,7 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
|
|||
bsqWalletService.getUnlockingBondsBalance());
|
||||
|
||||
updateListItems();
|
||||
GUIUtil.setFitToRowsForTableView(tableView, 38, 28, 2, 6);
|
||||
updateViews();
|
||||
}
|
||||
|
||||
|
@ -334,8 +333,6 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
|
|||
}
|
||||
|
||||
GUIUtil.setFitToRowsForTableView(tableView, 38, 28, 2, 6);
|
||||
tableView.layout();
|
||||
root.layout();
|
||||
}
|
||||
|
||||
private void createAllFieldsOnProposalDisplay(Proposal proposal, @Nullable Ballot ballot,
|
||||
|
|
|
@ -149,6 +149,7 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
|
|||
private ChangeListener<CycleListItem> selectedVoteResultListItemListener;
|
||||
private ResultsOfCycle resultsOfCycle;
|
||||
private ProposalListItem selectedProposalListItem;
|
||||
private TableView<VoteListItem> votesTableView;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -211,6 +212,13 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
|
|||
JsonElement cyclesJsonArray = getVotingHistoryJson();
|
||||
GUIUtil.exportJSON("voteResultsHistory.json", cyclesJsonArray, (Stage) root.getScene().getWindow());
|
||||
});
|
||||
if (proposalsTableView != null) {
|
||||
GUIUtil.setFitToRowsForTableView(proposalsTableView, 25, 28, 2, 4);
|
||||
}
|
||||
if (votesTableView != null) {
|
||||
GUIUtil.setFitToRowsForTableView(votesTableView, 25, 28, 2, 4);
|
||||
}
|
||||
GUIUtil.setFitToRowsForTableView(cyclesTableView, 25, 28, 2, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -513,7 +521,7 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
|
|||
GridPane.setColumnSpan(votesTableHeader, 2);
|
||||
root.getChildren().add(votesTableHeader);
|
||||
|
||||
TableView<VoteListItem> votesTableView = new TableView<>();
|
||||
votesTableView = new TableView<>();
|
||||
votesTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
|
||||
votesTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.monitor.MonitorView"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<VBox fx:id="leftVBox" prefWidth="240" spacing="5" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
|
||||
AnchorPane.topAnchor="15"/>
|
||||
|
||||
<ScrollPane fitToWidth="true" hbarPolicy="NEVER"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="270.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
|
||||
</ScrollPane>
|
||||
|
||||
</AnchorPane>
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor;
|
||||
|
||||
import bisq.desktop.Navigation;
|
||||
import bisq.desktop.common.view.ActivatableViewAndModel;
|
||||
import bisq.desktop.common.view.CachingViewLoader;
|
||||
import bisq.desktop.common.view.FxmlView;
|
||||
import bisq.desktop.common.view.View;
|
||||
import bisq.desktop.common.view.ViewLoader;
|
||||
import bisq.desktop.common.view.ViewPath;
|
||||
import bisq.desktop.components.MenuItem;
|
||||
import bisq.desktop.main.MainView;
|
||||
import bisq.desktop.main.dao.DaoView;
|
||||
import bisq.desktop.main.dao.monitor.blindvotes.BlindVoteStateMonitorView;
|
||||
import bisq.desktop.main.dao.monitor.daostate.DaoStateMonitorView;
|
||||
import bisq.desktop.main.dao.monitor.proposals.ProposalStateMonitorView;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@FxmlView
|
||||
public class MonitorView extends ActivatableViewAndModel {
|
||||
private final ViewLoader viewLoader;
|
||||
private final Navigation navigation;
|
||||
|
||||
private MenuItem daoState, proposals, blindVotes;
|
||||
private Navigation.Listener navigationListener;
|
||||
|
||||
@FXML
|
||||
private VBox leftVBox;
|
||||
@FXML
|
||||
private AnchorPane content;
|
||||
private Class<? extends View> selectedViewClass;
|
||||
private ToggleGroup toggleGroup;
|
||||
|
||||
@Inject
|
||||
private MonitorView(CachingViewLoader viewLoader, Navigation navigation) {
|
||||
this.viewLoader = viewLoader;
|
||||
this.navigation = navigation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
navigationListener = viewPath -> {
|
||||
if (viewPath.size() != 4 || viewPath.indexOf(MonitorView.class) != 2)
|
||||
return;
|
||||
|
||||
selectedViewClass = viewPath.tip();
|
||||
loadView(selectedViewClass);
|
||||
};
|
||||
|
||||
toggleGroup = new ToggleGroup();
|
||||
List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, MonitorView.class);
|
||||
daoState = new MenuItem(navigation, toggleGroup, Res.get("dao.monitor.daoState"),
|
||||
DaoStateMonitorView.class, baseNavPath);
|
||||
proposals = new MenuItem(navigation, toggleGroup, Res.get("dao.monitor.proposals"),
|
||||
ProposalStateMonitorView.class, baseNavPath);
|
||||
blindVotes = new MenuItem(navigation, toggleGroup, Res.get("dao.monitor.blindVotes"),
|
||||
BlindVoteStateMonitorView.class, baseNavPath);
|
||||
|
||||
leftVBox.getChildren().addAll(daoState, proposals, blindVotes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
proposals.activate();
|
||||
blindVotes.activate();
|
||||
daoState.activate();
|
||||
|
||||
navigation.addListener(navigationListener);
|
||||
ViewPath viewPath = navigation.getCurrentPath();
|
||||
if (viewPath.size() == 3 && viewPath.indexOf(MonitorView.class) == 2 ||
|
||||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
|
||||
if (selectedViewClass == null)
|
||||
selectedViewClass = DaoStateMonitorView.class;
|
||||
|
||||
loadView(selectedViewClass);
|
||||
|
||||
} else if (viewPath.size() == 4 && viewPath.indexOf(MonitorView.class) == 2) {
|
||||
selectedViewClass = viewPath.get(3);
|
||||
loadView(selectedViewClass);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
navigation.removeListener(navigationListener);
|
||||
|
||||
proposals.deactivate();
|
||||
blindVotes.deactivate();
|
||||
daoState.deactivate();
|
||||
}
|
||||
|
||||
private void loadView(Class<? extends View> viewClass) {
|
||||
View view = viewLoader.load(viewClass);
|
||||
content.getChildren().setAll(view.getRoot());
|
||||
|
||||
if (view instanceof DaoStateMonitorView) toggleGroup.selectToggle(daoState);
|
||||
else if (view instanceof ProposalStateMonitorView) toggleGroup.selectToggle(proposals);
|
||||
else if (view instanceof BlindVoteStateMonitorView) toggleGroup.selectToggle(blindVotes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor;
|
||||
|
||||
import bisq.core.dao.monitoring.model.StateBlock;
|
||||
import bisq.core.dao.monitoring.model.StateHash;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public abstract class StateBlockListItem<StH extends StateHash, StB extends StateBlock<StH>> {
|
||||
protected final StateBlock<StH> stateBlock;
|
||||
protected final String height;
|
||||
protected final String hash;
|
||||
protected final String prevHash;
|
||||
protected final String numNetworkMessages;
|
||||
protected final String numMisMatches;
|
||||
protected final boolean isInSync;
|
||||
|
||||
protected StateBlockListItem(StB stateBlock, int cycleIndex) {
|
||||
this.stateBlock = stateBlock;
|
||||
height = Res.get("dao.monitor.table.cycleBlockHeight", cycleIndex + 1, String.valueOf(stateBlock.getHeight()));
|
||||
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);
|
||||
isInSync = size == 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor;
|
||||
|
||||
import bisq.core.dao.monitoring.model.StateHash;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public abstract class StateInConflictListItem<T extends StateHash> {
|
||||
private final String peerAddress;
|
||||
private final String height;
|
||||
private final String hash;
|
||||
private final String prevHash;
|
||||
private final T stateHash;
|
||||
|
||||
protected StateInConflictListItem(String peerAddress, T stateHash, int cycleIndex) {
|
||||
this.stateHash = stateHash;
|
||||
this.peerAddress = peerAddress;
|
||||
height = Res.get("dao.monitor.table.cycleBlockHeight", cycleIndex + 1, String.valueOf(stateHash.getHeight()));
|
||||
hash = Utilities.bytesAsHexString(stateHash.getHash());
|
||||
prevHash = stateHash.getPrevHash().length > 0 ?
|
||||
Utilities.bytesAsHexString(stateHash.getPrevHash()) : "-";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,563 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor;
|
||||
|
||||
import bisq.desktop.common.view.ActivatableView;
|
||||
import bisq.desktop.common.view.FxmlView;
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.AutoTooltipLabel;
|
||||
import bisq.desktop.components.AutoTooltipTableColumn;
|
||||
import bisq.desktop.components.TableGroupHeadline;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
import bisq.desktop.util.Layout;
|
||||
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.dao.governance.period.CycleService;
|
||||
import bisq.core.dao.governance.period.PeriodService;
|
||||
import bisq.core.dao.monitoring.model.StateBlock;
|
||||
import bisq.core.dao.monitoring.model.StateHash;
|
||||
import bisq.core.dao.state.DaoStateListener;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@FxmlView
|
||||
public abstract class StateMonitorView<StH extends StateHash,
|
||||
StB extends StateBlock<StH>,
|
||||
BLI extends StateBlockListItem<StH, StB>,
|
||||
CLI extends StateInConflictListItem<StH>>
|
||||
extends ActivatableView<GridPane, Void> implements DaoStateListener {
|
||||
protected final DaoStateService daoStateService;
|
||||
protected final DaoFacade daoFacade;
|
||||
protected final CycleService cycleService;
|
||||
protected final PeriodService periodService;
|
||||
|
||||
protected TextField statusTextField;
|
||||
protected Button resyncButton;
|
||||
protected TableView<BLI> tableView;
|
||||
protected TableView<CLI> conflictTableView;
|
||||
|
||||
protected final ObservableList<BLI> listItems = FXCollections.observableArrayList();
|
||||
private final SortedList<BLI> sortedList = new SortedList<>(listItems);
|
||||
private final ObservableList<CLI> conflictListItems = FXCollections.observableArrayList();
|
||||
private final SortedList<CLI> sortedConflictList = new SortedList<>(conflictListItems);
|
||||
|
||||
protected int gridRow = 0;
|
||||
private Subscription selectedItemSubscription;
|
||||
protected final BooleanProperty isInConflict = new SimpleBooleanProperty();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public StateMonitorView(DaoStateService daoStateService,
|
||||
DaoFacade daoFacade,
|
||||
CycleService cycleService,
|
||||
PeriodService periodService) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.daoFacade = daoFacade;
|
||||
this.cycleService = cycleService;
|
||||
this.periodService = periodService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
createTableView();
|
||||
createDetailsView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
selectedItemSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectItem);
|
||||
|
||||
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
|
||||
sortedConflictList.comparatorProperty().bind(conflictTableView.comparatorProperty());
|
||||
|
||||
daoStateService.addDaoStateListener(this);
|
||||
|
||||
resyncButton.visibleProperty().bind(isInConflict);
|
||||
resyncButton.managedProperty().bind(isInConflict);
|
||||
|
||||
if (daoStateService.isParseBlockChainComplete()) {
|
||||
onDataUpdate();
|
||||
}
|
||||
|
||||
GUIUtil.setFitToRowsForTableView(tableView, 25, 28, 2, 5);
|
||||
GUIUtil.setFitToRowsForTableView(conflictTableView, 38, 28, 2, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
selectedItemSubscription.unsubscribe();
|
||||
|
||||
sortedList.comparatorProperty().unbind();
|
||||
sortedConflictList.comparatorProperty().unbind();
|
||||
|
||||
daoStateService.removeDaoStateListener(this);
|
||||
|
||||
resyncButton.visibleProperty().unbind();
|
||||
resyncButton.managedProperty().unbind();
|
||||
|
||||
resyncButton.setOnAction(null);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Abstract
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected abstract BLI getStateBlockListItem(StB e);
|
||||
|
||||
protected abstract CLI getStateInConflictListItem(Map.Entry<String, StH> mapEntry);
|
||||
|
||||
protected abstract void requestHashesFromGenesisBlockHeight(String peerAddress);
|
||||
|
||||
protected abstract String getConflictsTableHeader();
|
||||
|
||||
protected abstract String getPeersTableHeader();
|
||||
|
||||
protected abstract String getPrevHashTableHeader();
|
||||
|
||||
protected abstract String getHashTableHeader();
|
||||
|
||||
protected abstract String getBlockHeightTableHeader();
|
||||
|
||||
protected abstract String getRequestHashes();
|
||||
|
||||
protected abstract String getTableHeadLine();
|
||||
|
||||
protected abstract String getConflictTableHeadLine();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onParseBlockChainComplete() {
|
||||
onDataUpdate();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Create table views
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void createTableView() {
|
||||
TableGroupHeadline headline = new TableGroupHeadline(getTableHeadLine());
|
||||
GridPane.setRowIndex(headline, ++gridRow);
|
||||
GridPane.setMargin(headline, new Insets(Layout.GROUP_DISTANCE, -10, -10, -10));
|
||||
root.getChildren().add(headline);
|
||||
|
||||
tableView = new TableView<>();
|
||||
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
|
||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
tableView.setPrefHeight(100);
|
||||
|
||||
createColumns();
|
||||
GridPane.setRowIndex(tableView, gridRow);
|
||||
GridPane.setHgrow(tableView, Priority.ALWAYS);
|
||||
GridPane.setMargin(tableView, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, -10, -25, -10));
|
||||
root.getChildren().add(tableView);
|
||||
|
||||
tableView.setItems(sortedList);
|
||||
}
|
||||
|
||||
private void createDetailsView() {
|
||||
TableGroupHeadline conflictTableHeadline = new TableGroupHeadline(getConflictTableHeadLine());
|
||||
GridPane.setRowIndex(conflictTableHeadline, ++gridRow);
|
||||
GridPane.setMargin(conflictTableHeadline, new Insets(Layout.GROUP_DISTANCE, -10, -10, -10));
|
||||
root.getChildren().add(conflictTableHeadline);
|
||||
|
||||
conflictTableView = new TableView<>();
|
||||
conflictTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
|
||||
conflictTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
conflictTableView.setPrefHeight(100);
|
||||
|
||||
createConflictColumns();
|
||||
GridPane.setRowIndex(conflictTableView, gridRow);
|
||||
GridPane.setHgrow(conflictTableView, Priority.ALWAYS);
|
||||
GridPane.setMargin(conflictTableView, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, -10, -25, -10));
|
||||
root.getChildren().add(conflictTableView);
|
||||
|
||||
conflictTableView.setItems(sortedConflictList);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Handler
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void onSelectItem(BLI item) {
|
||||
if (item != null) {
|
||||
conflictListItems.setAll(item.getStateBlock().getInConflictMap().entrySet().stream()
|
||||
.map(this::getStateInConflictListItem).collect(Collectors.toList()));
|
||||
GUIUtil.setFitToRowsForTableView(conflictTableView, 38, 28, 2, 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void onDataUpdate() {
|
||||
GUIUtil.setFitToRowsForTableView(tableView, 25, 28, 2, 5);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// TableColumns
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void createColumns() {
|
||||
TableColumn<BLI, BLI> column;
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getBlockHeightTableHeader());
|
||||
column.setMinWidth(120);
|
||||
column.getStyleClass().add("first-column");
|
||||
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.getHeight());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getHeight()));
|
||||
column.setSortType(TableColumn.SortType.DESCENDING);
|
||||
tableView.getSortOrder().add(column);
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getHashTableHeader());
|
||||
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.getHash());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(BLI::getHash));
|
||||
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());
|
||||
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.getNumNetworkMessages());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getPeersMap().size()));
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getConflictsTableHeader());
|
||||
column.setMinWidth(80);
|
||||
column.setMaxWidth(column.getMinWidth());
|
||||
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.getNumMisMatches());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getInConflictMap().size()));
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
column = new AutoTooltipTableColumn<>("");
|
||||
column.setMinWidth(40);
|
||||
column.setMaxWidth(column.getMinWidth());
|
||||
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 && !empty) {
|
||||
Label icon;
|
||||
if (!item.getStateBlock().getPeersMap().isEmpty()) {
|
||||
if (item.isInSync()) {
|
||||
icon = FormBuilder.getIcon(AwesomeIcon.OK_CIRCLE);
|
||||
icon.getStyleClass().addAll("icon", "dao-inSync");
|
||||
} else {
|
||||
icon = FormBuilder.getIcon(AwesomeIcon.REMOVE_CIRCLE);
|
||||
icon.getStyleClass().addAll("icon", "dao-inConflict");
|
||||
}
|
||||
setGraphic(icon);
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setSortable(false);
|
||||
tableView.getColumns().add(column);
|
||||
}
|
||||
|
||||
|
||||
protected void createConflictColumns() {
|
||||
TableColumn<CLI, CLI> column;
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getBlockHeightTableHeader());
|
||||
column.setMinWidth(120);
|
||||
column.getStyleClass().add("first-column");
|
||||
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.getHeight());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateHash().getHeight()));
|
||||
column.setSortType(TableColumn.SortType.DESCENDING);
|
||||
conflictTableView.getColumns().add(column);
|
||||
conflictTableView.getSortOrder().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getPeersTableHeader());
|
||||
column.setMinWidth(80);
|
||||
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.getPeerAddress());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(CLI::getPeerAddress));
|
||||
conflictTableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getHashTableHeader());
|
||||
column.setMinWidth(150);
|
||||
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.getHash());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(CLI::getHash));
|
||||
conflictTableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getPrevHashTableHeader());
|
||||
column.setMinWidth(150);
|
||||
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(100);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<CLI, CLI> call(
|
||||
TableColumn<CLI, CLI> column) {
|
||||
return new TableCell<>() {
|
||||
Button button;
|
||||
|
||||
@Override
|
||||
public void updateItem(final CLI item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
if (button == null) {
|
||||
button = new AutoTooltipButton(getRequestHashes());
|
||||
setGraphic(button);
|
||||
}
|
||||
button.setOnAction(e -> requestHashesFromGenesisBlockHeight(item.getPeerAddress()));
|
||||
} else {
|
||||
setGraphic(null);
|
||||
if (button != null) {
|
||||
button.setOnAction(null);
|
||||
button = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setSortable(false);
|
||||
conflictTableView.getColumns().add(column);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor.blindvotes;
|
||||
|
||||
import bisq.desktop.main.dao.monitor.StateBlockListItem;
|
||||
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateBlock;
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
class BlindVoteStateBlockListItem extends StateBlockListItem<BlindVoteStateHash, BlindVoteStateBlock> {
|
||||
private final String numBlindVotes;
|
||||
|
||||
BlindVoteStateBlockListItem(BlindVoteStateBlock stateBlock, int cycleIndex) {
|
||||
super(stateBlock, cycleIndex);
|
||||
|
||||
numBlindVotes = String.valueOf(stateBlock.getNumBlindVotes());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor.blindvotes;
|
||||
|
||||
import bisq.desktop.main.dao.monitor.StateInConflictListItem;
|
||||
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
class BlindVoteStateInConflictListItem extends StateInConflictListItem<BlindVoteStateHash> {
|
||||
private final String numBlindVotes;
|
||||
|
||||
BlindVoteStateInConflictListItem(String peerAddress, BlindVoteStateHash stateHash, int cycleIndex) {
|
||||
super(peerAddress, stateHash, cycleIndex);
|
||||
|
||||
numBlindVotes = String.valueOf(stateHash.getNumBlindVotes());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.monitor.blindvotes.BlindVoteStateMonitorView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
|
||||
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="100"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor.blindvotes;
|
||||
|
||||
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;
|
||||
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.dao.governance.period.CycleService;
|
||||
import bisq.core.dao.governance.period.PeriodService;
|
||||
import bisq.core.dao.monitoring.BlindVoteStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateBlock;
|
||||
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@FxmlView
|
||||
public class BlindVoteStateMonitorView extends StateMonitorView<BlindVoteStateHash, BlindVoteStateBlock, BlindVoteStateBlockListItem, BlindVoteStateInConflictListItem>
|
||||
implements BlindVoteStateMonitoringService.Listener {
|
||||
private final BlindVoteStateMonitoringService blindVoteStateMonitoringService;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@Inject
|
||||
private BlindVoteStateMonitorView(DaoStateService daoStateService,
|
||||
DaoFacade daoFacade,
|
||||
BlindVoteStateMonitoringService blindVoteStateMonitoringService,
|
||||
CycleService cycleService,
|
||||
PeriodService periodService) {
|
||||
super(daoStateService, daoFacade, cycleService, periodService);
|
||||
|
||||
this.blindVoteStateMonitoringService = blindVoteStateMonitoringService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.blindVote.headline"));
|
||||
|
||||
statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow,
|
||||
Res.get("dao.monitor.state")).second;
|
||||
resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10);
|
||||
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
super.activate();
|
||||
blindVoteStateMonitoringService.addListener(this);
|
||||
|
||||
resyncButton.setOnAction(e -> daoFacade.resyncDao(() ->
|
||||
new Popup<>().attention(Res.get("setting.preferences.dao.resync.popup"))
|
||||
.useShutDownButton()
|
||||
.hideCloseButton()
|
||||
.show())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
super.deactivate();
|
||||
blindVoteStateMonitoringService.removeListener(this);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// BlindVoteStateMonitoringService.Listener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onBlindVoteStateBlockChainChanged() {
|
||||
if (daoStateService.isParseBlockChainComplete()) {
|
||||
onDataUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation abstract methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected BlindVoteStateBlockListItem getStateBlockListItem(BlindVoteStateBlock daoStateBlock) {
|
||||
int cycleIndex = periodService.getCycle(daoStateBlock.getHeight()).map(cycleService::getCycleIndex).orElse(0);
|
||||
return new BlindVoteStateBlockListItem(daoStateBlock, cycleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlindVoteStateInConflictListItem getStateInConflictListItem(Map.Entry<String, BlindVoteStateHash> mapEntry) {
|
||||
BlindVoteStateHash blindVoteStateHash = mapEntry.getValue();
|
||||
int cycleIndex = periodService.getCycle(blindVoteStateHash.getHeight()).map(cycleService::getCycleIndex).orElse(0);
|
||||
return new BlindVoteStateInConflictListItem(mapEntry.getKey(), mapEntry.getValue(), cycleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableHeadLine() {
|
||||
return Res.get("dao.monitor.blindVote.table.headline");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConflictTableHeadLine() {
|
||||
return Res.get("dao.monitor.blindVote.conflictTable.headline");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConflictsTableHeader() {
|
||||
return Res.get("dao.monitor.table.conflicts");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPeersTableHeader() {
|
||||
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");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBlockHeightTableHeader() {
|
||||
return Res.get("dao.monitor.table.header.cycleBlockHeight");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRequestHashes() {
|
||||
return Res.get("dao.monitor.requestAlHashes");
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Override
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void onDataUpdate() {
|
||||
isInConflict.set(blindVoteStateMonitoringService.isInConflict());
|
||||
|
||||
if (isInConflict.get()) {
|
||||
statusTextField.setText(Res.get("dao.monitor.blindVote.daoStateNotInSync"));
|
||||
statusTextField.getStyleClass().add("dao-inConflict");
|
||||
} else {
|
||||
statusTextField.setText(Res.get("dao.monitor.blindVote.daoStateInSync"));
|
||||
statusTextField.getStyleClass().remove("dao-inConflict");
|
||||
}
|
||||
|
||||
listItems.setAll(blindVoteStateMonitoringService.getBlindVoteStateBlockChain().stream()
|
||||
.map(this::getStateBlockListItem)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
super.onDataUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestHashesFromGenesisBlockHeight(String peerAddress) {
|
||||
blindVoteStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createColumns() {
|
||||
super.createColumns();
|
||||
|
||||
TableColumn<BlindVoteStateBlockListItem, BlindVoteStateBlockListItem> column;
|
||||
|
||||
column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.blindVote.table.numBlindVotes"));
|
||||
column.setMinWidth(110);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<BlindVoteStateBlockListItem, BlindVoteStateBlockListItem> call(
|
||||
TableColumn<BlindVoteStateBlockListItem, BlindVoteStateBlockListItem> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(BlindVoteStateBlockListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.getNumBlindVotes());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getMyStateHash().getNumBlindVotes()));
|
||||
tableView.getColumns().add(1, column);
|
||||
}
|
||||
|
||||
|
||||
protected void createConflictColumns() {
|
||||
super.createConflictColumns();
|
||||
|
||||
TableColumn<BlindVoteStateInConflictListItem, BlindVoteStateInConflictListItem> column;
|
||||
|
||||
column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.blindVote.table.numBlindVotes"));
|
||||
column.setMinWidth(110);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<BlindVoteStateInConflictListItem, BlindVoteStateInConflictListItem> call(
|
||||
TableColumn<BlindVoteStateInConflictListItem, BlindVoteStateInConflictListItem> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(BlindVoteStateInConflictListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.getNumBlindVotes());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateHash().getNumBlindVotes()));
|
||||
conflictTableView.getColumns().add(1, column);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor.daostate;
|
||||
|
||||
import bisq.desktop.main.dao.monitor.StateBlockListItem;
|
||||
|
||||
import bisq.core.dao.monitoring.model.DaoStateBlock;
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
class DaoStateBlockListItem extends StateBlockListItem<DaoStateHash, DaoStateBlock> {
|
||||
DaoStateBlockListItem(DaoStateBlock stateBlock, int cycleIndex) {
|
||||
super(stateBlock, cycleIndex);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor.daostate;
|
||||
|
||||
import bisq.desktop.main.dao.monitor.StateInConflictListItem;
|
||||
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
class DaoStateInConflictListItem extends StateInConflictListItem<DaoStateHash> {
|
||||
DaoStateInConflictListItem(String peerAddress, DaoStateHash stateHash, int cycleIndex) {
|
||||
super(peerAddress, stateHash, cycleIndex);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.monitor.daostate.DaoStateMonitorView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
|
||||
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="100"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor.daostate;
|
||||
|
||||
import bisq.desktop.common.view.FxmlView;
|
||||
import bisq.desktop.main.dao.monitor.StateMonitorView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.dao.governance.period.CycleService;
|
||||
import bisq.core.dao.governance.period.PeriodService;
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.model.DaoStateBlock;
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@FxmlView
|
||||
public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoStateBlock, DaoStateBlockListItem, DaoStateInConflictListItem>
|
||||
implements DaoStateMonitoringService.Listener {
|
||||
private final DaoStateMonitoringService daoStateMonitoringService;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@Inject
|
||||
private DaoStateMonitorView(DaoStateService daoStateService,
|
||||
DaoFacade daoFacade,
|
||||
DaoStateMonitoringService daoStateMonitoringService,
|
||||
CycleService cycleService,
|
||||
PeriodService periodService) {
|
||||
super(daoStateService, daoFacade, cycleService, periodService);
|
||||
|
||||
this.daoStateMonitoringService = daoStateMonitoringService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.daoState.headline"));
|
||||
|
||||
statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow,
|
||||
Res.get("dao.monitor.state")).second;
|
||||
resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10);
|
||||
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
super.activate();
|
||||
daoStateMonitoringService.addListener(this);
|
||||
|
||||
resyncButton.setOnAction(e -> daoFacade.resyncDao(() ->
|
||||
new Popup<>().attention(Res.get("setting.preferences.dao.resync.popup"))
|
||||
.useShutDownButton()
|
||||
.hideCloseButton()
|
||||
.show())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
super.deactivate();
|
||||
daoStateMonitoringService.removeListener(this);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateMonitoringService.Listener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onChangeAfterBatchProcessing() {
|
||||
if (daoStateService.isParseBlockChainComplete()) {
|
||||
onDataUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation abstract methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected DaoStateBlockListItem getStateBlockListItem(DaoStateBlock daoStateBlock) {
|
||||
int cycleIndex = periodService.getCycle(daoStateBlock.getHeight()).map(cycleService::getCycleIndex).orElse(0);
|
||||
return new DaoStateBlockListItem(daoStateBlock, cycleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DaoStateInConflictListItem getStateInConflictListItem(Map.Entry<String, DaoStateHash> mapEntry) {
|
||||
DaoStateHash daoStateHash = mapEntry.getValue();
|
||||
int cycleIndex = periodService.getCycle(daoStateHash.getHeight()).map(cycleService::getCycleIndex).orElse(0);
|
||||
return new DaoStateInConflictListItem(mapEntry.getKey(), daoStateHash, cycleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableHeadLine() {
|
||||
return Res.get("dao.monitor.daoState.table.headline");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConflictTableHeadLine() {
|
||||
return Res.get("dao.monitor.daoState.conflictTable.headline");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConflictsTableHeader() {
|
||||
return Res.get("dao.monitor.table.conflicts");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPeersTableHeader() {
|
||||
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");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBlockHeightTableHeader() {
|
||||
return Res.get("dao.monitor.daoState.table.blockHeight");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRequestHashes() {
|
||||
return Res.get("dao.monitor.requestAlHashes");
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Override
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void onDataUpdate() {
|
||||
isInConflict.set(daoStateMonitoringService.isInConflict());
|
||||
|
||||
if (isInConflict.get()) {
|
||||
statusTextField.setText(Res.get("dao.monitor.daoState.daoStateNotInSync"));
|
||||
statusTextField.getStyleClass().add("dao-inConflict");
|
||||
} else {
|
||||
statusTextField.setText(Res.get("dao.monitor.daoState.daoStateInSync"));
|
||||
statusTextField.getStyleClass().remove("dao-inConflict");
|
||||
}
|
||||
|
||||
listItems.setAll(daoStateMonitoringService.getDaoStateBlockChain().stream()
|
||||
.map(this::getStateBlockListItem)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
super.onDataUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestHashesFromGenesisBlockHeight(String peerAddress) {
|
||||
daoStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor.proposals;
|
||||
|
||||
import bisq.desktop.main.dao.monitor.StateBlockListItem;
|
||||
|
||||
import bisq.core.dao.monitoring.model.ProposalStateBlock;
|
||||
import bisq.core.dao.monitoring.model.ProposalStateHash;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
class ProposalStateBlockListItem extends StateBlockListItem<ProposalStateHash, ProposalStateBlock> {
|
||||
private final String numProposals;
|
||||
|
||||
ProposalStateBlockListItem(ProposalStateBlock stateBlock, int cycleIndex) {
|
||||
super(stateBlock, cycleIndex);
|
||||
|
||||
numProposals = String.valueOf(stateBlock.getNumProposals());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor.proposals;
|
||||
|
||||
import bisq.desktop.main.dao.monitor.StateInConflictListItem;
|
||||
|
||||
import bisq.core.dao.monitoring.model.ProposalStateHash;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
class ProposalStateInConflictListItem extends StateInConflictListItem<ProposalStateHash> {
|
||||
private final String numProposals;
|
||||
|
||||
ProposalStateInConflictListItem(String peerAddress, ProposalStateHash stateHash, int cycleIndex) {
|
||||
super(peerAddress, stateHash, cycleIndex);
|
||||
|
||||
numProposals = String.valueOf(stateHash.getNumProposals());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.monitor.proposals.ProposalStateMonitorView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
|
||||
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="100"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* 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.desktop.main.dao.monitor.proposals;
|
||||
|
||||
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;
|
||||
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.dao.governance.period.CycleService;
|
||||
import bisq.core.dao.governance.period.PeriodService;
|
||||
import bisq.core.dao.monitoring.ProposalStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.model.ProposalStateBlock;
|
||||
import bisq.core.dao.monitoring.model.ProposalStateHash;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@FxmlView
|
||||
public class ProposalStateMonitorView extends StateMonitorView<ProposalStateHash, ProposalStateBlock, ProposalStateBlockListItem, ProposalStateInConflictListItem>
|
||||
implements ProposalStateMonitoringService.Listener {
|
||||
private final ProposalStateMonitoringService proposalStateMonitoringService;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@Inject
|
||||
private ProposalStateMonitorView(DaoStateService daoStateService,
|
||||
DaoFacade daoFacade,
|
||||
ProposalStateMonitoringService proposalStateMonitoringService,
|
||||
CycleService cycleService,
|
||||
PeriodService periodService) {
|
||||
super(daoStateService, daoFacade, cycleService, periodService);
|
||||
|
||||
this.proposalStateMonitoringService = proposalStateMonitoringService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.proposal.headline"));
|
||||
|
||||
statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow,
|
||||
Res.get("dao.monitor.state")).second;
|
||||
resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10);
|
||||
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
super.activate();
|
||||
proposalStateMonitoringService.addListener(this);
|
||||
|
||||
resyncButton.setOnAction(e -> daoFacade.resyncDao(() ->
|
||||
new Popup<>().attention(Res.get("setting.preferences.dao.resync.popup"))
|
||||
.useShutDownButton()
|
||||
.hideCloseButton()
|
||||
.show())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
super.deactivate();
|
||||
proposalStateMonitoringService.removeListener(this);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ProposalStateMonitoringService.Listener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onProposalStateBlockChainChanged() {
|
||||
if (daoStateService.isParseBlockChainComplete()) {
|
||||
onDataUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation abstract methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected ProposalStateBlockListItem getStateBlockListItem(ProposalStateBlock daoStateBlock) {
|
||||
int cycleIndex = periodService.getCycle(daoStateBlock.getHeight()).map(cycleService::getCycleIndex).orElse(0);
|
||||
return new ProposalStateBlockListItem(daoStateBlock, cycleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProposalStateInConflictListItem getStateInConflictListItem(Map.Entry<String, ProposalStateHash> mapEntry) {
|
||||
ProposalStateHash proposalStateHash = mapEntry.getValue();
|
||||
int cycleIndex = periodService.getCycle(proposalStateHash.getHeight()).map(cycleService::getCycleIndex).orElse(0);
|
||||
return new ProposalStateInConflictListItem(mapEntry.getKey(), mapEntry.getValue(), cycleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableHeadLine() {
|
||||
return Res.get("dao.monitor.proposal.table.headline");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConflictTableHeadLine() {
|
||||
return Res.get("dao.monitor.proposal.conflictTable.headline");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConflictsTableHeader() {
|
||||
return Res.get("dao.monitor.table.conflicts");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPeersTableHeader() {
|
||||
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");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBlockHeightTableHeader() {
|
||||
return Res.get("dao.monitor.table.header.cycleBlockHeight");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRequestHashes() {
|
||||
return Res.get("dao.monitor.requestAlHashes");
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Override
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void onDataUpdate() {
|
||||
isInConflict.set(proposalStateMonitoringService.isInConflict());
|
||||
|
||||
if (isInConflict.get()) {
|
||||
statusTextField.setText(Res.get("dao.monitor.proposal.daoStateNotInSync"));
|
||||
statusTextField.getStyleClass().add("dao-inConflict");
|
||||
} else {
|
||||
statusTextField.setText(Res.get("dao.monitor.proposal.daoStateInSync"));
|
||||
statusTextField.getStyleClass().remove("dao-inConflict");
|
||||
}
|
||||
|
||||
listItems.setAll(proposalStateMonitoringService.getProposalStateBlockChain().stream()
|
||||
.map(this::getStateBlockListItem)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
super.onDataUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestHashesFromGenesisBlockHeight(String peerAddress) {
|
||||
proposalStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createColumns() {
|
||||
super.createColumns();
|
||||
|
||||
TableColumn<ProposalStateBlockListItem, ProposalStateBlockListItem> column;
|
||||
|
||||
column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.proposal.table.numProposals"));
|
||||
column.setMinWidth(110);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<ProposalStateBlockListItem, ProposalStateBlockListItem> call(
|
||||
TableColumn<ProposalStateBlockListItem, ProposalStateBlockListItem> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(ProposalStateBlockListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.getNumProposals());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getMyStateHash().getNumProposals()));
|
||||
tableView.getColumns().add(1, column);
|
||||
}
|
||||
|
||||
|
||||
protected void createConflictColumns() {
|
||||
super.createConflictColumns();
|
||||
|
||||
TableColumn<ProposalStateInConflictListItem, ProposalStateInConflictListItem> column;
|
||||
|
||||
column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.proposal.table.numProposals"));
|
||||
column.setMinWidth(110);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<ProposalStateInConflictListItem, ProposalStateInConflictListItem> call(
|
||||
TableColumn<ProposalStateInConflictListItem, ProposalStateInConflictListItem> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(ProposalStateInConflictListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.getNumProposals());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateHash().getNumProposals()));
|
||||
conflictTableView.getColumns().add(1, column);
|
||||
}
|
||||
}
|
|
@ -297,7 +297,9 @@ public class Connection extends Capabilities implements Runnable, MessageListene
|
|||
}
|
||||
|
||||
if (!result)
|
||||
log.info("We did not send the message because the peer does not support our required capabilities. message={}, peers supportedCapabilities={}", msg, capabilities);
|
||||
log.info("We did not send the message because the peer does not support our required capabilities. " +
|
||||
"message={}, peer={}, peers supportedCapabilities={}",
|
||||
msg, peersNodeAddressOptional, capabilities);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
Binary file not shown.
BIN
p2p/src/main/resources/DaoStateStore2_BTC_DAO_TESTNET
Normal file
BIN
p2p/src/main/resources/DaoStateStore2_BTC_DAO_TESTNET
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Add table
Reference in a new issue