mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Add handling for missing blindVote data
This commit is contained in:
parent
58e6db0a25
commit
5141806509
@ -56,6 +56,7 @@ message NetworkEnvelope {
|
|||||||
|
|
||||||
AddPersistableNetworkPayloadMessage add_persistable_network_payload_message = 31;
|
AddPersistableNetworkPayloadMessage add_persistable_network_payload_message = 31;
|
||||||
AckMessage ack_message = 32;
|
AckMessage ack_message = 32;
|
||||||
|
RepublishBlindVotesRequest republish_blind_votes_request = 33;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,6 +320,9 @@ message NewBlockBroadcastMessage {
|
|||||||
BaseBlock raw_block = 1;
|
BaseBlock raw_block = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RepublishBlindVotesRequest {
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Payload
|
// Payload
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -25,6 +25,7 @@ import bisq.core.dao.governance.ballot.BallotListService;
|
|||||||
import bisq.core.dao.governance.blindvote.BlindVoteListService;
|
import bisq.core.dao.governance.blindvote.BlindVoteListService;
|
||||||
import bisq.core.dao.governance.blindvote.BlindVoteValidator;
|
import bisq.core.dao.governance.blindvote.BlindVoteValidator;
|
||||||
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
|
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
|
||||||
|
import bisq.core.dao.governance.blindvote.network.RepublishBlindVotesHandler;
|
||||||
import bisq.core.dao.governance.blindvote.storage.BlindVoteStorageService;
|
import bisq.core.dao.governance.blindvote.storage.BlindVoteStorageService;
|
||||||
import bisq.core.dao.governance.blindvote.storage.BlindVoteStore;
|
import bisq.core.dao.governance.blindvote.storage.BlindVoteStore;
|
||||||
import bisq.core.dao.governance.myvote.MyVoteListService;
|
import bisq.core.dao.governance.myvote.MyVoteListService;
|
||||||
@ -51,6 +52,7 @@ import bisq.core.dao.governance.proposal.storage.appendonly.ProposalStore;
|
|||||||
import bisq.core.dao.governance.proposal.storage.temp.TempProposalStorageService;
|
import bisq.core.dao.governance.proposal.storage.temp.TempProposalStorageService;
|
||||||
import bisq.core.dao.governance.proposal.storage.temp.TempProposalStore;
|
import bisq.core.dao.governance.proposal.storage.temp.TempProposalStore;
|
||||||
import bisq.core.dao.governance.role.BondedRolesService;
|
import bisq.core.dao.governance.role.BondedRolesService;
|
||||||
|
import bisq.core.dao.governance.voteresult.MissingDataRequestService;
|
||||||
import bisq.core.dao.governance.voteresult.VoteResultService;
|
import bisq.core.dao.governance.voteresult.VoteResultService;
|
||||||
import bisq.core.dao.governance.voteresult.issuance.IssuanceService;
|
import bisq.core.dao.governance.voteresult.issuance.IssuanceService;
|
||||||
import bisq.core.dao.governance.votereveal.VoteRevealService;
|
import bisq.core.dao.governance.votereveal.VoteRevealService;
|
||||||
@ -165,7 +167,9 @@ public class DaoModule extends AppModule {
|
|||||||
|
|
||||||
// VoteResult
|
// VoteResult
|
||||||
bind(VoteResultService.class).in(Singleton.class);
|
bind(VoteResultService.class).in(Singleton.class);
|
||||||
|
bind(MissingDataRequestService.class).in(Singleton.class);
|
||||||
bind(IssuanceService.class).in(Singleton.class);
|
bind(IssuanceService.class).in(Singleton.class);
|
||||||
|
bind(RepublishBlindVotesHandler.class).in(Singleton.class);
|
||||||
|
|
||||||
// Genesis
|
// Genesis
|
||||||
String genesisTxId = environment.getProperty(DaoOptionKeys.GENESIS_TX_ID, String.class, "");
|
String genesisTxId = environment.getProperty(DaoOptionKeys.GENESIS_TX_ID, String.class, "");
|
||||||
|
@ -21,6 +21,7 @@ import bisq.core.dao.governance.ballot.BallotListService;
|
|||||||
import bisq.core.dao.governance.blindvote.BlindVoteListService;
|
import bisq.core.dao.governance.blindvote.BlindVoteListService;
|
||||||
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
|
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
|
||||||
import bisq.core.dao.governance.proposal.ProposalService;
|
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.voteresult.VoteResultService;
|
||||||
import bisq.core.dao.governance.votereveal.VoteRevealService;
|
import bisq.core.dao.governance.votereveal.VoteRevealService;
|
||||||
import bisq.core.dao.node.BsqNode;
|
import bisq.core.dao.node.BsqNode;
|
||||||
@ -47,6 +48,7 @@ public class DaoSetup {
|
|||||||
private final VoteRevealService voteRevealService;
|
private final VoteRevealService voteRevealService;
|
||||||
private final VoteResultService voteResultService;
|
private final VoteResultService voteResultService;
|
||||||
private final BsqNode bsqNode;
|
private final BsqNode bsqNode;
|
||||||
|
private final MissingDataRequestService missingDataRequestService;
|
||||||
private final DaoFacade daoFacade;
|
private final DaoFacade daoFacade;
|
||||||
private final ExportJsonFilesService exportJsonFilesService;
|
private final ExportJsonFilesService exportJsonFilesService;
|
||||||
|
|
||||||
@ -60,6 +62,7 @@ public class DaoSetup {
|
|||||||
MyBlindVoteListService myBlindVoteListService,
|
MyBlindVoteListService myBlindVoteListService,
|
||||||
VoteRevealService voteRevealService,
|
VoteRevealService voteRevealService,
|
||||||
VoteResultService voteResultService,
|
VoteResultService voteResultService,
|
||||||
|
MissingDataRequestService missingDataRequestService,
|
||||||
DaoFacade daoFacade,
|
DaoFacade daoFacade,
|
||||||
ExportJsonFilesService exportJsonFilesService) {
|
ExportJsonFilesService exportJsonFilesService) {
|
||||||
this.bsqStateService = bsqStateService;
|
this.bsqStateService = bsqStateService;
|
||||||
@ -70,6 +73,7 @@ public class DaoSetup {
|
|||||||
this.myBlindVoteListService = myBlindVoteListService;
|
this.myBlindVoteListService = myBlindVoteListService;
|
||||||
this.voteRevealService = voteRevealService;
|
this.voteRevealService = voteRevealService;
|
||||||
this.voteResultService = voteResultService;
|
this.voteResultService = voteResultService;
|
||||||
|
this.missingDataRequestService = missingDataRequestService;
|
||||||
this.daoFacade = daoFacade;
|
this.daoFacade = daoFacade;
|
||||||
this.exportJsonFilesService = exportJsonFilesService;
|
this.exportJsonFilesService = exportJsonFilesService;
|
||||||
|
|
||||||
@ -87,8 +91,9 @@ public class DaoSetup {
|
|||||||
myBlindVoteListService.addListeners();
|
myBlindVoteListService.addListeners();
|
||||||
voteRevealService.addListeners();
|
voteRevealService.addListeners();
|
||||||
voteResultService.addListeners();
|
voteResultService.addListeners();
|
||||||
exportJsonFilesService.addListeners();
|
missingDataRequestService.addListeners();
|
||||||
daoFacade.addListeners();
|
daoFacade.addListeners();
|
||||||
|
exportJsonFilesService.addListeners();
|
||||||
|
|
||||||
bsqStateService.start();
|
bsqStateService.start();
|
||||||
cycleService.start();
|
cycleService.start();
|
||||||
@ -98,8 +103,9 @@ public class DaoSetup {
|
|||||||
myBlindVoteListService.start();
|
myBlindVoteListService.start();
|
||||||
voteRevealService.start();
|
voteRevealService.start();
|
||||||
voteResultService.start();
|
voteResultService.start();
|
||||||
exportJsonFilesService.start();
|
missingDataRequestService.start();
|
||||||
daoFacade.start();
|
daoFacade.start();
|
||||||
|
exportJsonFilesService.start();
|
||||||
|
|
||||||
bsqNode.setErrorMessageHandler(errorMessageHandler);
|
bsqNode.setErrorMessageHandler(errorMessageHandler);
|
||||||
bsqNode.start();
|
bsqNode.start();
|
||||||
|
@ -39,6 +39,7 @@ import javafx.collections.ObservableList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,7 +50,7 @@ public class BlindVoteListService implements AppendOnlyDataStoreListener, BsqSta
|
|||||||
private final BsqStateService bsqStateService;
|
private final BsqStateService bsqStateService;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final BlindVoteValidator blindVoteValidator;
|
private final BlindVoteValidator blindVoteValidator;
|
||||||
|
@Getter
|
||||||
private final ObservableList<BlindVotePayload> appendOnlyStoreList = FXCollections.observableArrayList();
|
private final ObservableList<BlindVotePayload> appendOnlyStoreList = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* 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.governance.blindvote.network;
|
||||||
|
|
||||||
|
import bisq.core.dao.governance.blindvote.network.messages.RepublishBlindVotesRequest;
|
||||||
|
|
||||||
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import bisq.network.p2p.network.Connection;
|
||||||
|
import bisq.network.p2p.network.NetworkNode;
|
||||||
|
import bisq.network.p2p.peers.PeerManager;
|
||||||
|
import bisq.network.p2p.peers.peerexchange.Peer;
|
||||||
|
import bisq.network.p2p.seed.SeedNodeRepository;
|
||||||
|
|
||||||
|
import bisq.common.Timer;
|
||||||
|
import bisq.common.UserThread;
|
||||||
|
import bisq.common.app.Capabilities;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for sending a RepublishBlindVotesRequest to full nodes.
|
||||||
|
* Processing of RepublishBlindVotesRequests at full nodes is done in the FullNodeNetworkService.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public final class RepublishBlindVotesHandler {
|
||||||
|
private static final long TIMEOUT = 120;
|
||||||
|
|
||||||
|
private final Collection<NodeAddress> seedNodeAddresses;
|
||||||
|
private final NetworkNode networkNode;
|
||||||
|
private final PeerManager peerManager;
|
||||||
|
|
||||||
|
private boolean stopped;
|
||||||
|
private Timer timeoutTimer;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RepublishBlindVotesHandler(NetworkNode networkNode,
|
||||||
|
PeerManager peerManager,
|
||||||
|
SeedNodeRepository seedNodesRepository) {
|
||||||
|
this.networkNode = networkNode;
|
||||||
|
this.peerManager = peerManager;
|
||||||
|
this.seedNodeAddresses = new HashSet<>(seedNodesRepository.getSeedNodeAddresses());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// API
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void requestBlindVotePayload() {
|
||||||
|
// First try if we have a seed node in our connections. All seed nodes are full nodes.
|
||||||
|
if (!stopped)
|
||||||
|
connectToNextNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void sendRepublishBlindVotesRequest(NodeAddress nodeAddress) {
|
||||||
|
RepublishBlindVotesRequest republishBlindVotesRequest = new RepublishBlindVotesRequest();
|
||||||
|
if (timeoutTimer == null) {
|
||||||
|
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
||||||
|
if (!stopped) {
|
||||||
|
String errorMessage = "A timeout occurred at sending republishBlindVotesRequest:" +
|
||||||
|
" to nodeAddress:" + nodeAddress;
|
||||||
|
log.warn(errorMessage);
|
||||||
|
connectToNextNode();
|
||||||
|
} else {
|
||||||
|
log.warn("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 republishBlindVotesRequest.", nodeAddress);
|
||||||
|
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, republishBlindVotesRequest);
|
||||||
|
Futures.addCallback(future, new FutureCallback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Connection connection) {
|
||||||
|
if (!stopped) {
|
||||||
|
log.info("Sending of RepublishBlindVotesRequest message to peer {} succeeded.", nodeAddress.getFullAddress());
|
||||||
|
stop();
|
||||||
|
} 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 republishBlindVotesRequest to " + nodeAddress +
|
||||||
|
" failed. That is expected if the peer is offline.\n\t" +
|
||||||
|
"\n\tException=" + throwable.getMessage();
|
||||||
|
log.info(errorMessage);
|
||||||
|
handleFault(nodeAddress);
|
||||||
|
connectToNextNode();
|
||||||
|
} else {
|
||||||
|
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onFailure call. " +
|
||||||
|
"Might be caused by an previous timeout.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectToNextNode() {
|
||||||
|
// First we try our connected seed nodes
|
||||||
|
Optional<Connection> connectionToSeedNodeOptional = networkNode.getConfirmedConnections().stream()
|
||||||
|
.filter(peerManager::isSeedNode)
|
||||||
|
.findAny();
|
||||||
|
if (connectionToSeedNodeOptional.isPresent() &&
|
||||||
|
connectionToSeedNodeOptional.get().getPeersNodeAddressOptional().isPresent()) {
|
||||||
|
NodeAddress nodeAddress = connectionToSeedNodeOptional.get().getPeersNodeAddressOptional().get();
|
||||||
|
sendRepublishBlindVotesRequest(nodeAddress);
|
||||||
|
} else {
|
||||||
|
// If connected seed nodes did not confirm receipt of message we try next seed node from seedNodeAddresses
|
||||||
|
List<NodeAddress> list = seedNodeAddresses.stream()
|
||||||
|
.filter(e -> peerManager.isSeedNode(e) && !peerManager.isSelf(e))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Collections.shuffle(list);
|
||||||
|
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
NodeAddress nodeAddress = list.get(0);
|
||||||
|
seedNodeAddresses.remove(nodeAddress);
|
||||||
|
sendRepublishBlindVotesRequest(nodeAddress);
|
||||||
|
} else {
|
||||||
|
log.warn("No more seed nodes available. We try any of our other peers.");
|
||||||
|
connectToAnyFullNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectToAnyFullNode() {
|
||||||
|
List<Integer> required = new ArrayList<>(Collections.singletonList(
|
||||||
|
Capabilities.Capability.DAO_FULL_NODE.ordinal()
|
||||||
|
));
|
||||||
|
|
||||||
|
List<Peer> list = peerManager.getLivePeers(null).stream()
|
||||||
|
.filter(peer -> Capabilities.isCapabilitySupported(required, peer.getSupportedCapabilities()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (list.isEmpty())
|
||||||
|
list = peerManager.getReportedPeers().stream()
|
||||||
|
.filter(peer -> Capabilities.isCapabilitySupported(required, peer.getSupportedCapabilities()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (list.isEmpty())
|
||||||
|
list = peerManager.getPersistedPeers().stream()
|
||||||
|
.filter(peer -> Capabilities.isCapabilitySupported(required, peer.getSupportedCapabilities()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
// We avoid the complexity to maintain the results of all our peers and to iterate all until we find a good peer,
|
||||||
|
// but we prefer simplicity with the risk that we don't get the data so we request from max 4 peers in parallel
|
||||||
|
// assuming that at least one will republish and therefore we should receive all data.
|
||||||
|
list = new ArrayList<>(list.subList(0, Math.min(list.size(), 4)));
|
||||||
|
list.stream()
|
||||||
|
.map(Peer::getNodeAddress)
|
||||||
|
.forEach(this::sendRepublishBlindVotesRequest);
|
||||||
|
} else {
|
||||||
|
log.warn("No other nodes found. We try again in 60 seconds.");
|
||||||
|
UserThread.runAfter(this::connectToNextNode, 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFault(NodeAddress nodeAddress) {
|
||||||
|
peerManager.handleConnectionFault(nodeAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop() {
|
||||||
|
stopped = true;
|
||||||
|
stopTimeoutTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopTimeoutTimer() {
|
||||||
|
if (timeoutTimer != null) {
|
||||||
|
timeoutTimer.stop();
|
||||||
|
timeoutTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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.governance.blindvote.network.messages;
|
||||||
|
|
||||||
|
import bisq.network.p2p.DirectMessage;
|
||||||
|
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
|
||||||
|
|
||||||
|
import bisq.common.app.Capabilities;
|
||||||
|
import bisq.common.app.Version;
|
||||||
|
import bisq.common.proto.network.NetworkEnvelope;
|
||||||
|
|
||||||
|
import io.bisq.generated.protobuffer.PB;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Getter
|
||||||
|
public final class RepublishBlindVotesRequest extends NetworkEnvelope implements DirectMessage, CapabilityRequiringPayload {
|
||||||
|
|
||||||
|
public RepublishBlindVotesRequest() {
|
||||||
|
this(Version.getP2PMessageVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// PROTO BUFFER
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private RepublishBlindVotesRequest(int messageVersion) {
|
||||||
|
super(messageVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PB.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||||
|
return getNetworkEnvelopeBuilder()
|
||||||
|
.setRepublishBlindVotesRequest(PB.RepublishBlindVotesRequest.newBuilder())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NetworkEnvelope fromProto(PB.RepublishBlindVotesRequest proto, int messageVersion) {
|
||||||
|
return new RepublishBlindVotesRequest(messageVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Integer> getRequiredCapabilities() {
|
||||||
|
return new ArrayList<>(Collections.singletonList(
|
||||||
|
Capabilities.Capability.DAO_FULL_NODE.ordinal()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "RepublishBlindVotesRequest{" +
|
||||||
|
"\n} " + super.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -41,12 +41,13 @@ public class MeritConsensus {
|
|||||||
// Value with 144 blocks a day and 365 days would be 52560. We take a close round number instead.
|
// Value with 144 blocks a day and 365 days would be 52560. We take a close round number instead.
|
||||||
private static final int BLOCKS_PER_YEAR = 50_000;
|
private static final int BLOCKS_PER_YEAR = 50_000;
|
||||||
|
|
||||||
public static MeritList decryptMeritList(byte[] encryptedMeritList, SecretKey secretKey) throws VoteResultException {
|
public static MeritList decryptMeritList(byte[] encryptedMeritList, SecretKey secretKey)
|
||||||
|
throws VoteResultException.DecryptionException {
|
||||||
try {
|
try {
|
||||||
final byte[] decrypted = Encryption.decrypt(encryptedMeritList, secretKey);
|
byte[] decrypted = Encryption.decrypt(encryptedMeritList, secretKey);
|
||||||
return MeritList.getMeritListFromBytes(decrypted);
|
return MeritList.getMeritListFromBytes(decrypted);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new VoteResultException(t);
|
throw new VoteResultException.DecryptionException(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.dao.governance.voteresult;
|
|
||||||
|
|
||||||
import bisq.core.dao.governance.ballot.Ballot;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Value
|
|
||||||
public class MissingBallotException extends Exception {
|
|
||||||
private List<Ballot> existingBallots;
|
|
||||||
private List<String> proposalTxIdsOfMissingBallots;
|
|
||||||
|
|
||||||
public MissingBallotException(List<Ballot> existingBallots, List<String> proposalTxIdsOfMissingBallots) {
|
|
||||||
this.existingBallots = existingBallots;
|
|
||||||
this.proposalTxIdsOfMissingBallots = proposalTxIdsOfMissingBallots;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.governance.voteresult;
|
||||||
|
|
||||||
|
import bisq.core.dao.DaoSetupService;
|
||||||
|
import bisq.core.dao.governance.blindvote.network.RepublishBlindVotesHandler;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public class MissingDataRequestService implements DaoSetupService {
|
||||||
|
private final RepublishBlindVotesHandler republishBlindVotesHandler;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ObservableList<VoteResultException> voteResultExceptions = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MissingDataRequestService(RepublishBlindVotesHandler republishBlindVotesHandler) {
|
||||||
|
this.republishBlindVotesHandler = republishBlindVotesHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// DaoSetupService
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListeners() {
|
||||||
|
voteResultExceptions.addListener((ListChangeListener<VoteResultException>) c -> {
|
||||||
|
c.next();
|
||||||
|
if (c.wasAdded()) {
|
||||||
|
c.getAddedSubList().stream().filter(e -> e instanceof VoteResultException.MissingBlindVoteDataException)
|
||||||
|
.map(e -> (VoteResultException.MissingBlindVoteDataException) e)
|
||||||
|
.forEach(e -> republishBlindVotesHandler.requestBlindVotePayload());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// API
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void addVoteResultException(VoteResultException voteResultException) {
|
||||||
|
this.voteResultExceptions.add(voteResultException);
|
||||||
|
}
|
||||||
|
}
|
@ -54,35 +54,41 @@ public class VoteResultConsensus {
|
|||||||
return Arrays.copyOfRange(opReturnData, 2, 22);
|
return Arrays.copyOfRange(opReturnData, 2, 22);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VoteWithProposalTxIdList decryptVotes(byte[] encryptedVotes, SecretKey secretKey) throws VoteResultException {
|
public static VoteWithProposalTxIdList decryptVotes(byte[] encryptedVotes, SecretKey secretKey)
|
||||||
|
throws VoteResultException.DecryptionException {
|
||||||
try {
|
try {
|
||||||
byte[] decrypted = Encryption.decrypt(encryptedVotes, secretKey);
|
byte[] decrypted = Encryption.decrypt(encryptedVotes, secretKey);
|
||||||
return VoteWithProposalTxIdList.getVoteWithProposalTxIdListFromBytes(decrypted);
|
return VoteWithProposalTxIdList.getVoteWithProposalTxIdListFromBytes(decrypted);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new VoteResultException(t);
|
throw new VoteResultException.DecryptionException(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We compare first by stake and in case we have multiple entries with same stake we use the
|
// We compare first by stake and in case we have multiple entries with same stake we use the
|
||||||
// hex encoded hashOfProposalList for comparision
|
// hex encoded hashOfProposalList for comparision
|
||||||
@Nullable
|
@Nullable
|
||||||
public static byte[] getMajorityHash(List<VoteResultService.HashWithStake> hashWithStakeList) throws VoteResultException {
|
public static byte[] getMajorityHash(List<VoteResultService.HashWithStake> hashWithStakeList)
|
||||||
checkArgument(!hashWithStakeList.isEmpty(), "hashWithStakeList must not be empty");
|
throws VoteResultException.ConsensusException, VoteResultException.ValidationException {
|
||||||
hashWithStakeList.sort(Comparator.comparingLong(VoteResultService.HashWithStake::getStake).reversed()
|
try {
|
||||||
.thenComparing(hashWithStake -> Utilities.encodeToHex(hashWithStake.getHash())));
|
checkArgument(!hashWithStakeList.isEmpty(), "hashWithStakeList must not be empty");
|
||||||
|
hashWithStakeList.sort(Comparator.comparingLong(VoteResultService.HashWithStake::getStake).reversed()
|
||||||
|
.thenComparing(hashWithStake -> Utilities.encodeToHex(hashWithStake.getHash())));
|
||||||
|
|
||||||
// If there are conflicting data views (multiple hashes) we only consider the voting round as valid if
|
// If there are conflicting data views (multiple hashes) we only consider the voting round as valid if
|
||||||
// the majority is a super majority with > 80%.
|
// the majority is a super majority with > 80%.
|
||||||
if (hashWithStakeList.size() > 1) {
|
if (hashWithStakeList.size() > 1) {
|
||||||
long stakeOfAll = hashWithStakeList.stream().mapToLong(VoteResultService.HashWithStake::getStake).sum();
|
long stakeOfAll = hashWithStakeList.stream().mapToLong(VoteResultService.HashWithStake::getStake).sum();
|
||||||
long stakeOfFirst = hashWithStakeList.get(0).getStake();
|
long stakeOfFirst = hashWithStakeList.get(0).getStake();
|
||||||
if ((double) stakeOfFirst / (double) stakeOfAll < 0.8) {
|
if ((double) stakeOfFirst / (double) stakeOfAll < 0.8) {
|
||||||
throw new VoteResultException("The winning data view has less then 80% of the total stake of " +
|
throw new VoteResultException.ConsensusException("The winning data view has less then 80% of the " +
|
||||||
"all data views. We consider the voting cycle as invalid if the winning data view does not " +
|
"total stake of all data views. We consider the voting cycle as invalid if the " +
|
||||||
"reach a super majority.");
|
"winning data view does not reach a super majority.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return hashWithStakeList.get(0).getHash();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new VoteResultException.ValidationException(t);
|
||||||
}
|
}
|
||||||
return hashWithStakeList.get(0).getHash();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key is stored after version and type bytes and list of Blind votes. It has 16 bytes
|
// Key is stored after version and type bytes and list of Blind votes. It has 16 bytes
|
||||||
@ -92,24 +98,24 @@ public class VoteResultConsensus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static TxOutput getConnectedBlindVoteStakeOutput(Tx voteRevealTx, BsqStateService bsqStateService)
|
public static TxOutput getConnectedBlindVoteStakeOutput(Tx voteRevealTx, BsqStateService bsqStateService)
|
||||||
throws VoteResultException {
|
throws VoteResultException.ValidationException {
|
||||||
try {
|
try {
|
||||||
// We use the stake output of the blind vote tx as first input
|
// We use the stake output of the blind vote tx as first input
|
||||||
final TxInput stakeTxInput = voteRevealTx.getTxInputs().get(0);
|
TxInput stakeTxInput = voteRevealTx.getTxInputs().get(0);
|
||||||
Optional<TxOutput> optionalBlindVoteStakeOutput = bsqStateService.getConnectedTxOutput(stakeTxInput);
|
Optional<TxOutput> optionalBlindVoteStakeOutput = bsqStateService.getConnectedTxOutput(stakeTxInput);
|
||||||
checkArgument(optionalBlindVoteStakeOutput.isPresent(), "blindVoteStakeOutput must be present");
|
checkArgument(optionalBlindVoteStakeOutput.isPresent(), "blindVoteStakeOutput must be present");
|
||||||
final TxOutput blindVoteStakeOutput = optionalBlindVoteStakeOutput.get();
|
TxOutput blindVoteStakeOutput = optionalBlindVoteStakeOutput.get();
|
||||||
checkArgument(blindVoteStakeOutput.getTxOutputType() == TxOutputType.BLIND_VOTE_LOCK_STAKE_OUTPUT,
|
checkArgument(blindVoteStakeOutput.getTxOutputType() == TxOutputType.BLIND_VOTE_LOCK_STAKE_OUTPUT,
|
||||||
"blindVoteStakeOutput must be of type BLIND_VOTE_LOCK_STAKE_OUTPUT");
|
"blindVoteStakeOutput must be of type BLIND_VOTE_LOCK_STAKE_OUTPUT");
|
||||||
return blindVoteStakeOutput;
|
return blindVoteStakeOutput;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new VoteResultException(t);
|
throw new VoteResultException.ValidationException(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Tx getBlindVoteTx(TxOutput blindVoteStakeOutput, BsqStateService bsqStateService,
|
public static Tx getBlindVoteTx(TxOutput blindVoteStakeOutput, BsqStateService bsqStateService,
|
||||||
PeriodService periodService, int chainHeight)
|
PeriodService periodService, int chainHeight)
|
||||||
throws VoteResultException {
|
throws VoteResultException.ValidationException {
|
||||||
try {
|
try {
|
||||||
String blindVoteTxId = blindVoteStakeOutput.getTxId();
|
String blindVoteTxId = blindVoteStakeOutput.getTxId();
|
||||||
Optional<Tx> optionalBlindVoteTx = bsqStateService.getTx(blindVoteTxId);
|
Optional<Tx> optionalBlindVoteTx = bsqStateService.getTx(blindVoteTxId);
|
||||||
@ -128,7 +134,7 @@ public class VoteResultConsensus {
|
|||||||
+ blindVoteTx.getBlockHeight());
|
+ blindVoteTx.getBlockHeight());
|
||||||
return blindVoteTx;
|
return blindVoteTx;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new VoteResultException(t);
|
throw new VoteResultException.ValidationException(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,35 +17,105 @@
|
|||||||
|
|
||||||
package bisq.core.dao.governance.voteresult;
|
package bisq.core.dao.governance.voteresult;
|
||||||
|
|
||||||
import bisq.core.dao.governance.blindvote.storage.BlindVotePayload;
|
import bisq.core.dao.governance.ballot.Ballot;
|
||||||
|
|
||||||
import lombok.Getter;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
public class VoteResultException extends Exception {
|
public class VoteResultException extends Exception {
|
||||||
@Getter
|
|
||||||
@Nullable
|
|
||||||
private BlindVotePayload blindVotePayload;
|
|
||||||
|
|
||||||
public VoteResultException(String message, Exception cause, BlindVotePayload blindVotePayload) {
|
VoteResultException(Throwable cause) {
|
||||||
super(message, cause);
|
super(cause);
|
||||||
this.blindVotePayload = blindVotePayload;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public VoteResultException(String message) {
|
private VoteResultException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VoteResultException(Throwable cause) {
|
private VoteResultException(String message, Throwable cause) {
|
||||||
super(cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "VoteResultException{" +
|
return "VoteResultException{" +
|
||||||
"\n blindVotePayload=" + blindVotePayload +
|
|
||||||
"\n cause=" + getCause() +
|
|
||||||
"\n} " + super.toString();
|
"\n} " + super.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class ConsensusException extends VoteResultException {
|
||||||
|
|
||||||
|
ConsensusException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ConsensusException{" +
|
||||||
|
"\n} " + super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class ValidationException extends VoteResultException {
|
||||||
|
|
||||||
|
ValidationException(Throwable cause) {
|
||||||
|
super("Validation of vote result failed.", cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VoteResultException{" +
|
||||||
|
"\n cause=" + getCause() +
|
||||||
|
"\n} " + super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Value
|
||||||
|
public static class MissingBlindVoteDataException extends VoteResultException {
|
||||||
|
private String blindVoteTxId;
|
||||||
|
|
||||||
|
MissingBlindVoteDataException(String blindVoteTxId) {
|
||||||
|
super("Blind vote tx ID " + blindVoteTxId + " is missing");
|
||||||
|
this.blindVoteTxId = blindVoteTxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MissingBlindVoteDataException{" +
|
||||||
|
"\n blindVoteTxId='" + blindVoteTxId + '\'' +
|
||||||
|
"\n} " + super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Value
|
||||||
|
public static class MissingBallotException extends VoteResultException {
|
||||||
|
private List<Ballot> existingBallots;
|
||||||
|
private List<String> proposalTxIdsOfMissingBallots;
|
||||||
|
|
||||||
|
MissingBallotException(List<Ballot> existingBallots, List<String> proposalTxIdsOfMissingBallots) {
|
||||||
|
super("Missing ballots. proposalTxIdsOfMissingBallots=" + proposalTxIdsOfMissingBallots);
|
||||||
|
this.existingBallots = existingBallots;
|
||||||
|
this.proposalTxIdsOfMissingBallots = proposalTxIdsOfMissingBallots;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Value
|
||||||
|
public static class DecryptionException extends VoteResultException {
|
||||||
|
public DecryptionException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DecryptionException{" +
|
||||||
|
"\n} " + super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,10 +105,12 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
|||||||
private final BondedRolesService bondedRolesService;
|
private final BondedRolesService bondedRolesService;
|
||||||
private final IssuanceService issuanceService;
|
private final IssuanceService issuanceService;
|
||||||
private final AssetService assetService;
|
private final AssetService assetService;
|
||||||
private final Storage<EvaluatedProposalList> evaluatedProposalStorage;
|
private final MissingDataRequestService missingDataRequestService;
|
||||||
private Storage<DecryptedBallotsWithMeritsList> decryptedBallotsWithMeritsStorage;
|
|
||||||
@Getter
|
@Getter
|
||||||
private final ObservableList<VoteResultException> voteResultExceptions = FXCollections.observableArrayList();
|
private final ObservableList<VoteResultException> voteResultExceptions = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
private final Storage<EvaluatedProposalList> evaluatedProposalStorage;
|
||||||
|
private Storage<DecryptedBallotsWithMeritsList> decryptedBallotsWithMeritsStorage;
|
||||||
@Getter
|
@Getter
|
||||||
private final EvaluatedProposalList evaluatedProposalList = new EvaluatedProposalList();
|
private final EvaluatedProposalList evaluatedProposalList = new EvaluatedProposalList();
|
||||||
@Getter
|
@Getter
|
||||||
@ -129,6 +131,7 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
|||||||
BondedRolesService bondedRolesService,
|
BondedRolesService bondedRolesService,
|
||||||
IssuanceService issuanceService,
|
IssuanceService issuanceService,
|
||||||
AssetService assetService,
|
AssetService assetService,
|
||||||
|
MissingDataRequestService missingDataRequestService,
|
||||||
Storage<EvaluatedProposalList> evaluatedProposalStorage,
|
Storage<EvaluatedProposalList> evaluatedProposalStorage,
|
||||||
Storage<DecryptedBallotsWithMeritsList> decryptedBallotsWithMeritsStorage) {
|
Storage<DecryptedBallotsWithMeritsList> decryptedBallotsWithMeritsStorage) {
|
||||||
this.voteRevealService = voteRevealService;
|
this.voteRevealService = voteRevealService;
|
||||||
@ -140,6 +143,7 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
|||||||
this.bondedRolesService = bondedRolesService;
|
this.bondedRolesService = bondedRolesService;
|
||||||
this.issuanceService = issuanceService;
|
this.issuanceService = issuanceService;
|
||||||
this.assetService = assetService;
|
this.assetService = assetService;
|
||||||
|
this.missingDataRequestService = missingDataRequestService;
|
||||||
this.evaluatedProposalStorage = evaluatedProposalStorage;
|
this.evaluatedProposalStorage = evaluatedProposalStorage;
|
||||||
this.decryptedBallotsWithMeritsStorage = decryptedBallotsWithMeritsStorage;
|
this.decryptedBallotsWithMeritsStorage = decryptedBallotsWithMeritsStorage;
|
||||||
}
|
}
|
||||||
@ -249,13 +253,19 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
|||||||
// TODO request missing blind votes
|
// TODO request missing blind votes
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (VoteResultException e) {
|
} catch (VoteResultException.ValidationException e) {
|
||||||
|
log.error(e.toString());
|
||||||
|
e.printStackTrace();
|
||||||
|
voteResultExceptions.add(e);
|
||||||
|
} catch (VoteResultException.ConsensusException e) {
|
||||||
log.error(e.toString());
|
log.error(e.toString());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
//TODO notify application of that case (e.g. add error handler)
|
//TODO notify application of that case (e.g. add error handler)
|
||||||
// The vote cycle is invalid as conflicting data views of the blind vote data exist and the winner
|
// The vote cycle is invalid as conflicting data views of the blind vote data exist and the winner
|
||||||
// did not reach super majority of 80%.
|
// did not reach super majority of 80%.
|
||||||
|
|
||||||
|
voteResultExceptions.add(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info("There have not been any votes in that cycle. chainHeight={}", chainHeight);
|
log.info("There have not been any votes in that cycle. chainHeight={}", chainHeight);
|
||||||
@ -301,28 +311,41 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
|||||||
.findAny();
|
.findAny();
|
||||||
if (optionalBlindVote.isPresent()) {
|
if (optionalBlindVote.isPresent()) {
|
||||||
BlindVote blindVote = optionalBlindVote.get();
|
BlindVote blindVote = optionalBlindVote.get();
|
||||||
VoteWithProposalTxIdList voteWithProposalTxIdList = VoteResultConsensus.decryptVotes(blindVote.getEncryptedVotes(), secretKey);
|
try {
|
||||||
MeritList meritList = MeritConsensus.decryptMeritList(blindVote.getEncryptedMeritList(), secretKey);
|
VoteWithProposalTxIdList voteWithProposalTxIdList = VoteResultConsensus.decryptVotes(blindVote.getEncryptedVotes(), secretKey);
|
||||||
|
MeritList meritList = MeritConsensus.decryptMeritList(blindVote.getEncryptedMeritList(), secretKey);
|
||||||
// We lookup for the proposals we have in our local list which match the txId from the
|
// We lookup for the proposals we have in our local list which match the txId from the
|
||||||
// voteWithProposalTxIdList and create a ballot list with the proposal and the vote from
|
// voteWithProposalTxIdList and create a ballot list with the proposal and the vote from
|
||||||
// the voteWithProposalTxIdList
|
// the voteWithProposalTxIdList
|
||||||
BallotList ballotList = createBallotList(voteWithProposalTxIdList);
|
BallotList ballotList = createBallotList(voteWithProposalTxIdList);
|
||||||
return new DecryptedBallotsWithMerits(hashOfBlindVoteList, voteRevealTxId, blindVoteTxId, blindVoteStake, ballotList, meritList);
|
return new DecryptedBallotsWithMerits(hashOfBlindVoteList, voteRevealTxId, blindVoteTxId, blindVoteStake, ballotList, meritList);
|
||||||
|
} catch (VoteResultException.MissingBallotException missingBallotException) {
|
||||||
|
//TODO handle case that we are missing proposals
|
||||||
|
log.warn("We are missing proposals to create the vote result: " + missingBallotException.toString());
|
||||||
|
missingDataRequestService.addVoteResultException(missingBallotException);
|
||||||
|
return null;
|
||||||
|
} catch (VoteResultException.DecryptionException decryptionException) {
|
||||||
|
log.error("Could not decrypt data: " + decryptionException.toString());
|
||||||
|
voteResultExceptions.add(decryptionException);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO handle recovering
|
//TODO handle recovering
|
||||||
log.warn("We have a blindVoteTx but we do not have the corresponding blindVote in our local list.\n" +
|
log.warn("We have a blindVoteTx but we do not have the corresponding blindVote in our local list.\n" +
|
||||||
"That can happen if the blindVote item was not properly broadcast. We will go on " +
|
"That can happen if the blindVote item was not properly broadcast. We will go on " +
|
||||||
"and see if that blindVote was part of the majority data view. If so we should " +
|
"and see if that blindVote was part of the majority data view. If so we should " +
|
||||||
"recover the missing blind vote by a request to our peers. blindVoteTxId={}", blindVoteTxId);
|
"recover the missing blind vote by a request to our peers. blindVoteTxId={}", blindVoteTxId);
|
||||||
|
|
||||||
|
missingDataRequestService.addVoteResultException(new VoteResultException.MissingBlindVoteDataException(blindVoteTxId));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (MissingBallotException e) {
|
} catch (VoteResultException.ValidationException e) {
|
||||||
//TODO handle case that we are missing proposals
|
log.error("Could not create DecryptedBallotsWithMerits because of voteResultValidationException: " + e.toString());
|
||||||
log.error("We are missing proposals to create the vote result: " + e.toString());
|
voteResultExceptions.add(e);
|
||||||
return null;
|
return null;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
log.error("Could not create DecryptedBallotsWithMerits: " + e.toString());
|
log.error("Could not create DecryptedBallotsWithMerits because of an unknown exception: " + e.toString());
|
||||||
|
voteResultExceptions.add(new VoteResultException(e));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -330,7 +353,8 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
|||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private BallotList createBallotList(VoteWithProposalTxIdList voteWithProposalTxIdList) throws MissingBallotException {
|
private BallotList createBallotList(VoteWithProposalTxIdList voteWithProposalTxIdList)
|
||||||
|
throws VoteResultException.MissingBallotException {
|
||||||
// We convert the list to a map with proposalTxId as key and the vote as value
|
// We convert the list to a map with proposalTxId as key and the vote as value
|
||||||
Map<String, Vote> voteByTxIdMap = voteWithProposalTxIdList.stream()
|
Map<String, Vote> voteByTxIdMap = voteWithProposalTxIdList.stream()
|
||||||
.filter(voteWithProposalTxId -> voteWithProposalTxId.getVote() != null)
|
.filter(voteWithProposalTxId -> voteWithProposalTxId.getVote() != null)
|
||||||
@ -366,7 +390,7 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (!missingBallots.isEmpty())
|
if (!missingBallots.isEmpty())
|
||||||
throw new MissingBallotException(ballots, missingBallots);
|
throw new VoteResultException.MissingBallotException(ballots, missingBallots);
|
||||||
|
|
||||||
// Let's keep the data more deterministic by sorting it by txId. Though we are not using the sorting.
|
// Let's keep the data more deterministic by sorting it by txId. Though we are not using the sorting.
|
||||||
ballots.sort(Comparator.comparing(Ballot::getTxId));
|
ballots.sort(Comparator.comparing(Ballot::getTxId));
|
||||||
@ -393,7 +417,8 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getMajorityBlindVoteListHash(Map<P2PDataStorage.ByteArray, Long> map) throws VoteResultException {
|
private byte[] getMajorityBlindVoteListHash(Map<P2PDataStorage.ByteArray, Long> map)
|
||||||
|
throws VoteResultException.ValidationException, VoteResultException.ConsensusException {
|
||||||
List<HashWithStake> list = map.entrySet().stream()
|
List<HashWithStake> list = map.entrySet().stream()
|
||||||
.map(entry -> new HashWithStake(entry.getKey().bytes, entry.getValue()))
|
.map(entry -> new HashWithStake(entry.getKey().bytes, entry.getValue()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
@ -80,6 +80,8 @@ public class FullNode extends BsqNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
|
fullNodeNetworkService.start();
|
||||||
|
|
||||||
rpcService.setup(() -> {
|
rpcService.setup(() -> {
|
||||||
super.onInitialized();
|
super.onInitialized();
|
||||||
startParseBlocks();
|
startParseBlocks();
|
||||||
|
@ -17,12 +17,16 @@
|
|||||||
|
|
||||||
package bisq.core.dao.node.full.network;
|
package bisq.core.dao.node.full.network;
|
||||||
|
|
||||||
|
import bisq.core.dao.governance.blindvote.BlindVoteListService;
|
||||||
|
import bisq.core.dao.governance.blindvote.network.messages.RepublishBlindVotesRequest;
|
||||||
|
import bisq.core.dao.governance.blindvote.storage.BlindVotePayload;
|
||||||
import bisq.core.dao.node.messages.GetBlocksRequest;
|
import bisq.core.dao.node.messages.GetBlocksRequest;
|
||||||
import bisq.core.dao.node.messages.NewBlockBroadcastMessage;
|
import bisq.core.dao.node.messages.NewBlockBroadcastMessage;
|
||||||
import bisq.core.dao.state.BsqStateService;
|
import bisq.core.dao.state.BsqStateService;
|
||||||
import bisq.core.dao.state.blockchain.Block;
|
import bisq.core.dao.state.blockchain.Block;
|
||||||
import bisq.core.dao.state.blockchain.RawBlock;
|
import bisq.core.dao.state.blockchain.RawBlock;
|
||||||
|
|
||||||
|
import bisq.network.p2p.P2PService;
|
||||||
import bisq.network.p2p.network.Connection;
|
import bisq.network.p2p.network.Connection;
|
||||||
import bisq.network.p2p.network.MessageListener;
|
import bisq.network.p2p.network.MessageListener;
|
||||||
import bisq.network.p2p.network.NetworkNode;
|
import bisq.network.p2p.network.NetworkNode;
|
||||||
@ -35,8 +39,12 @@ import bisq.common.proto.network.NetworkEnvelope;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -58,6 +66,8 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List
|
|||||||
private final NetworkNode networkNode;
|
private final NetworkNode networkNode;
|
||||||
private final PeerManager peerManager;
|
private final PeerManager peerManager;
|
||||||
private final Broadcaster broadcaster;
|
private final Broadcaster broadcaster;
|
||||||
|
private final BlindVoteListService blindVoteListService;
|
||||||
|
private final P2PService p2PService;
|
||||||
private final BsqStateService bsqStateService;
|
private final BsqStateService bsqStateService;
|
||||||
|
|
||||||
// Key is connection UID
|
// Key is connection UID
|
||||||
@ -73,20 +83,26 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List
|
|||||||
public FullNodeNetworkService(NetworkNode networkNode,
|
public FullNodeNetworkService(NetworkNode networkNode,
|
||||||
PeerManager peerManager,
|
PeerManager peerManager,
|
||||||
Broadcaster broadcaster,
|
Broadcaster broadcaster,
|
||||||
|
BlindVoteListService blindVoteListService,
|
||||||
|
P2PService p2PService,
|
||||||
BsqStateService bsqStateService) {
|
BsqStateService bsqStateService) {
|
||||||
this.networkNode = networkNode;
|
this.networkNode = networkNode;
|
||||||
this.peerManager = peerManager;
|
this.peerManager = peerManager;
|
||||||
this.broadcaster = broadcaster;
|
this.broadcaster = broadcaster;
|
||||||
|
this.blindVoteListService = blindVoteListService;
|
||||||
|
this.p2PService = p2PService;
|
||||||
this.bsqStateService = bsqStateService;
|
this.bsqStateService = bsqStateService;
|
||||||
|
|
||||||
networkNode.addMessageListener(this);
|
|
||||||
peerManager.addListener(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
networkNode.addMessageListener(this);
|
||||||
|
peerManager.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public void shutDown() {
|
public void shutDown() {
|
||||||
Log.traceCall();
|
Log.traceCall();
|
||||||
@ -173,6 +189,24 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List
|
|||||||
} else {
|
} else {
|
||||||
log.warn("We have stopped already. We ignore that onMessage call.");
|
log.warn("We have stopped already. We ignore that onMessage call.");
|
||||||
}
|
}
|
||||||
|
} else if (networkEnvelope instanceof RepublishBlindVotesRequest) {
|
||||||
|
ObservableList<BlindVotePayload> appendOnlyStoreList = blindVoteListService.getAppendOnlyStoreList();
|
||||||
|
appendOnlyStoreList
|
||||||
|
.forEach(blindVotePayload -> {
|
||||||
|
// We want a random delay between 0.1 and 30 sec. depending on the number of items
|
||||||
|
int delay = Math.max(100, Math.min(30_000, new Random().nextInt(appendOnlyStoreList.size() * 500)));
|
||||||
|
UserThread.runAfter(() -> {
|
||||||
|
boolean success = p2PService.addPersistableNetworkPayload(blindVotePayload, true);
|
||||||
|
String txId = blindVotePayload.getBlindVote().getTxId();
|
||||||
|
if (success) {
|
||||||
|
log.info("We received a RepublishBlindVotesRequest and re-published a blindVotePayload to " +
|
||||||
|
"the P2P network as append only data. blindVoteTxId={}",
|
||||||
|
txId);
|
||||||
|
} else {
|
||||||
|
log.error("Adding of blindVotePayload to P2P network failed. blindVoteTxId=" + txId);
|
||||||
|
}
|
||||||
|
}, delay, TimeUnit.MILLISECONDS);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,8 @@ class GetBlocksRequestHandler {
|
|||||||
connection.getPeersNodeAddressOptional(), getBlocksRequest.getFromBlockHeight());
|
connection.getPeersNodeAddressOptional(), getBlocksRequest.getFromBlockHeight());
|
||||||
if (timeoutTimer == null) {
|
if (timeoutTimer == null) {
|
||||||
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
||||||
String errorMessage = "A timeout occurred for getBlocksResponse:" + getBlocksResponse +
|
String errorMessage = "A timeout occurred for getBlocksResponse.requestNonce:" +
|
||||||
|
getBlocksResponse.getRequestNonce() +
|
||||||
" on connection:" + connection;
|
" on connection:" + connection;
|
||||||
handleFault(errorMessage, CloseConnectionReason.SEND_MSG_TIMEOUT, connection);
|
handleFault(errorMessage, CloseConnectionReason.SEND_MSG_TIMEOUT, connection);
|
||||||
},
|
},
|
||||||
@ -111,8 +112,8 @@ class GetBlocksRequestHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Connection connection) {
|
public void onSuccess(Connection connection) {
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
log.info("Send DataResponse to {} succeeded. getBlocksResponse={}",
|
log.info("Send DataResponse to {} succeeded. getBlocksResponse.getBlocks().size()={}",
|
||||||
connection.getPeersNodeAddressOptional(), getBlocksResponse);
|
connection.getPeersNodeAddressOptional(), getBlocksResponse.getBlocks().size());
|
||||||
cleanup();
|
cleanup();
|
||||||
listener.onComplete();
|
listener.onComplete();
|
||||||
} else {
|
} else {
|
||||||
|
@ -73,7 +73,7 @@ public class LiteNode extends BsqNode {
|
|||||||
public void start() {
|
public void start() {
|
||||||
super.onInitialized();
|
super.onInitialized();
|
||||||
|
|
||||||
liteNodeNetworkService.init();
|
liteNodeNetworkService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -117,7 +117,7 @@ public class LiteNodeNetworkService implements MessageListener, ConnectionListen
|
|||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void init() {
|
public void start() {
|
||||||
networkNode.addMessageListener(this);
|
networkNode.addMessageListener(this);
|
||||||
networkNode.addConnectionListener(this);
|
networkNode.addConnectionListener(this);
|
||||||
peerManager.addListener(this);
|
peerManager.addListener(this);
|
||||||
|
@ -26,6 +26,7 @@ import bisq.core.arbitration.messages.DisputeResultMessage;
|
|||||||
import bisq.core.arbitration.messages.OpenNewDisputeMessage;
|
import bisq.core.arbitration.messages.OpenNewDisputeMessage;
|
||||||
import bisq.core.arbitration.messages.PeerOpenedDisputeMessage;
|
import bisq.core.arbitration.messages.PeerOpenedDisputeMessage;
|
||||||
import bisq.core.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
|
import bisq.core.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
|
||||||
|
import bisq.core.dao.governance.blindvote.network.messages.RepublishBlindVotesRequest;
|
||||||
import bisq.core.dao.governance.proposal.storage.temp.TempProposalPayload;
|
import bisq.core.dao.governance.proposal.storage.temp.TempProposalPayload;
|
||||||
import bisq.core.dao.node.messages.GetBlocksRequest;
|
import bisq.core.dao.node.messages.GetBlocksRequest;
|
||||||
import bisq.core.dao.node.messages.GetBlocksResponse;
|
import bisq.core.dao.node.messages.GetBlocksResponse;
|
||||||
@ -157,6 +158,9 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
|||||||
return AddPersistableNetworkPayloadMessage.fromProto(proto.getAddPersistableNetworkPayloadMessage(), this, messageVersion);
|
return AddPersistableNetworkPayloadMessage.fromProto(proto.getAddPersistableNetworkPayloadMessage(), this, messageVersion);
|
||||||
case ACK_MESSAGE:
|
case ACK_MESSAGE:
|
||||||
return AckMessage.fromProto(proto.getAckMessage(), messageVersion);
|
return AckMessage.fromProto(proto.getAckMessage(), messageVersion);
|
||||||
|
case REPUBLISH_BLIND_VOTES_REQUEST:
|
||||||
|
return RepublishBlindVotesRequest.fromProto(proto.getRepublishBlindVotesRequest(), messageVersion);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new ProtobufferException("Unknown proto message case (PB.NetworkEnvelope). messageCase=" +
|
throw new ProtobufferException("Unknown proto message case (PB.NetworkEnvelope). messageCase=" +
|
||||||
proto.getMessageCase() + "; proto raw data=" + proto.toString());
|
proto.getMessageCase() + "; proto raw data=" + proto.toString());
|
||||||
|
Loading…
Reference in New Issue
Block a user