Use BlockHash and data for append-only store

We add the blockHash of the 10th block in the break before the relevant phase as part of the hash in the appen-only data.
Together with the date tolerance we get control over the time when the data has been added.
This protects agains various attacks (data withhold and publish late attack).

- Implement DateTolerantPayload at BlindVoteAppendOnlyPayload and ProposalAppendOnlyPayload
- Implement ExpirablePayload at ProposalPayload and BlindVotePayload
- Add blockHash and data at BlindVoteAppendOnlyPayload and ProposalAppendOnlyPayload
- Add ClosedBallotsView
This commit is contained in:
Manfred Karrer 2018-05-05 20:08:27 -05:00
parent f59aca99ee
commit 90e310263b
No known key found for this signature in database
GPG key ID: 401250966A6B2C46
7 changed files with 326 additions and 17 deletions

View file

@ -28,6 +28,7 @@ import bisq.desktop.components.MenuItem;
import bisq.desktop.main.MainView; import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.DaoView; import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.voting.active.ActiveBallotsView; import bisq.desktop.main.dao.voting.active.ActiveBallotsView;
import bisq.desktop.main.dao.voting.closed.ClosedBallotsView;
import bisq.desktop.main.dao.voting.dashboard.VotingDashboardView; import bisq.desktop.main.dao.voting.dashboard.VotingDashboardView;
import bisq.core.locale.Res; import bisq.core.locale.Res;
@ -51,7 +52,7 @@ public class VotingView extends ActivatableViewAndModel {
private final ViewLoader viewLoader; private final ViewLoader viewLoader;
private final Navigation navigation; private final Navigation navigation;
private MenuItem dashboard, ballots; private MenuItem dashboard, activeBallots, closedBallots;
private Navigation.Listener listener; private Navigation.Listener listener;
@FXML @FXML
@ -81,15 +82,18 @@ public class VotingView extends ActivatableViewAndModel {
final List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, VotingView.class); final List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, VotingView.class);
dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"), dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"),
VotingDashboardView.class, AwesomeIcon.DASHBOARD, baseNavPath); VotingDashboardView.class, AwesomeIcon.DASHBOARD, baseNavPath);
ballots = new MenuItem(navigation, toggleGroup, Res.get("dao.voting.menuItem.ballots"), activeBallots = new MenuItem(navigation, toggleGroup, Res.get("dao.voting.menuItem.activeBallots"),
ActiveBallotsView.class, AwesomeIcon.LIST_UL, baseNavPath); ActiveBallotsView.class, AwesomeIcon.LIST_UL, baseNavPath);
leftVBox.getChildren().addAll(dashboard, ballots); closedBallots = new MenuItem(navigation, toggleGroup, Res.get("dao.voting.menuItem.closedBallots"),
ClosedBallotsView.class, AwesomeIcon.LIST_UL, baseNavPath);
leftVBox.getChildren().addAll(dashboard, activeBallots, closedBallots);
} }
@Override @Override
protected void activate() { protected void activate() {
dashboard.activate(); dashboard.activate();
ballots.activate(); activeBallots.activate();
closedBallots.activate();
navigation.addListener(listener); navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath(); ViewPath viewPath = navigation.getCurrentPath();
@ -111,7 +115,8 @@ public class VotingView extends ActivatableViewAndModel {
navigation.removeListener(listener); navigation.removeListener(listener);
dashboard.deactivate(); dashboard.deactivate();
ballots.deactivate(); activeBallots.deactivate();
closedBallots.deactivate();
} }
private void loadView(Class<? extends View> viewClass) { private void loadView(Class<? extends View> viewClass) {
@ -119,7 +124,8 @@ public class VotingView extends ActivatableViewAndModel {
content.getChildren().setAll(view.getRoot()); content.getChildren().setAll(view.getRoot());
if (view instanceof VotingDashboardView) dashboard.setSelected(true); if (view instanceof VotingDashboardView) dashboard.setSelected(true);
else if (view instanceof ActiveBallotsView) ballots.setSelected(true); else if (view instanceof ActiveBallotsView) activeBallots.setSelected(true);
else if (view instanceof ClosedBallotsView) closedBallots.setSelected(true);
} }
public Class<? extends View> getSelectedViewClass() { public Class<? extends View> getSelectedViewClass() {

View file

@ -33,6 +33,8 @@ import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.wallet.BsqBalanceListener; import bisq.core.btc.wallet.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoFacade; import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.BlockListener;
import bisq.core.dao.state.blockchain.Block;
import bisq.core.dao.state.period.DaoPhase; import bisq.core.dao.state.period.DaoPhase;
import bisq.core.dao.voting.ballot.Ballot; import bisq.core.dao.voting.ballot.Ballot;
import bisq.core.dao.voting.ballot.vote.BooleanVote; import bisq.core.dao.voting.ballot.vote.BooleanVote;
@ -71,7 +73,7 @@ import static bisq.desktop.util.FormBuilder.addLabelInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg; import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView @FxmlView
public class ActiveBallotsView extends BaseProposalView implements BsqBalanceListener { public class ActiveBallotsView extends BaseProposalView implements BsqBalanceListener, BlockListener {
private Button acceptButton, rejectButton, removeMyVoteButton, voteButton; private Button acceptButton, rejectButton, removeMyVoteButton, voteButton;
private InputTextField stakeInputTextField; private InputTextField stakeInputTextField;
private BusyAnimation voteButtonBusyAnimation; private BusyAnimation voteButtonBusyAnimation;
@ -115,6 +117,10 @@ public class ActiveBallotsView extends BaseProposalView implements BsqBalanceLis
bsqWalletService.getLockedInBondsBalance()); bsqWalletService.getLockedInBondsBalance());
voteButton.setOnAction(e -> onVote()); voteButton.setOnAction(e -> onVote());
daoFacade.addBlockListener(this);
updateButtons();
} }
@ -140,6 +146,15 @@ public class ActiveBallotsView extends BaseProposalView implements BsqBalanceLis
bsqFormatter.formatCoinWithCode(confirmedBalance))); bsqFormatter.formatCoinWithCode(confirmedBalance)));
} }
///////////////////////////////////////////////////////////////////////////////////////////
// BlockListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onBlockAdded(Block block) {
updateButtons();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Protected // Protected
@ -151,6 +166,7 @@ public class ActiveBallotsView extends BaseProposalView implements BsqBalanceLis
proposalBaseProposalListItems.setAll(list.stream() proposalBaseProposalListItems.setAll(list.stream()
.map(ballot -> new ActiveBallotListItem(ballot, daoFacade, bsqWalletService, bsqFormatter)) .map(ballot -> new ActiveBallotListItem(ballot, daoFacade, bsqWalletService, bsqFormatter))
.collect(Collectors.toSet())); .collect(Collectors.toSet()));
updateButtons();
} }
@Override @Override
@ -276,15 +292,13 @@ public class ActiveBallotsView extends BaseProposalView implements BsqBalanceLis
} }
private void updateButtons() { private void updateButtons() {
if (selectedBaseProposalListItem != null && proposalDisplay != null) {
final boolean isBlindVotePhase = daoFacade.phaseProperty().get() == DaoPhase.Phase.BLIND_VOTE; final boolean isBlindVotePhase = daoFacade.phaseProperty().get() == DaoPhase.Phase.BLIND_VOTE;
stakeInputTextField.setDisable(!isBlindVotePhase); stakeInputTextField.setDisable(!isBlindVotePhase);
voteButton.setDisable(!isBlindVotePhase); voteButton.setDisable(!isBlindVotePhase);
acceptButton.setDisable(!isBlindVotePhase); if (acceptButton != null) acceptButton.setDisable(!isBlindVotePhase);
rejectButton.setDisable(!isBlindVotePhase); if (rejectButton != null) rejectButton.setDisable(!isBlindVotePhase);
removeMyVoteButton.setDisable(!isBlindVotePhase); if (removeMyVoteButton != null) removeMyVoteButton.setDisable(!isBlindVotePhase);
}
} }

View file

@ -0,0 +1,96 @@
/*
* 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.voting.closed;
import bisq.desktop.main.dao.BaseProposalListItem;
import bisq.desktop.util.BsqFormatter;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.period.DaoPhase;
import bisq.core.dao.voting.ballot.Ballot;
import bisq.core.dao.voting.ballot.vote.BooleanVote;
import bisq.core.dao.voting.ballot.vote.Vote;
import bisq.core.dao.voting.proposal.Proposal;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
@ToString
@Slf4j
@EqualsAndHashCode(callSuper = true)
public class ClosedBallotListItem extends BaseProposalListItem {
@Getter
private final Ballot ballot;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
public ClosedBallotListItem(Ballot ballot,
DaoFacade daoFacade,
BsqWalletService bsqWalletService,
BsqFormatter bsqFormatter) {
super(daoFacade,
bsqWalletService,
bsqFormatter);
this.ballot = ballot;
init();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void init() {
super.init();
}
@Override
public void onPhaseChanged(DaoPhase.Phase phase) {
super.onPhaseChanged(phase);
final Vote vote = ballot.getVote();
if (vote != null) {
imageView.setVisible(true);
if (vote instanceof BooleanVote) {
if (((BooleanVote) vote).isAccepted()) {
imageView.setId("accepted");
} else {
imageView.setId("rejected");
}
} else {
//TODO
}
} else {
imageView.setVisible(false);
}
}
@Override
public Proposal getProposal() {
return ballot.getProposal();
}
}

View file

@ -0,0 +1,33 @@
<?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.voting.closed.ClosedBallotsView"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,160 @@
/*
* 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.voting.closed;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.main.dao.BaseProposalListItem;
import bisq.desktop.main.dao.BaseProposalView;
import bisq.desktop.util.BSFormatter;
import bisq.desktop.util.BsqFormatter;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.voting.ballot.Ballot;
import javax.inject.Inject;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.image.ImageView;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.ListChangeListener;
import javafx.util.Callback;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@FxmlView
public class ClosedBallotsView extends BaseProposalView {
private ListChangeListener<Ballot> listChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ClosedBallotsView(DaoFacade daoFacade,
BsqWalletService bsqWalletService,
BsqFormatter bsqFormatter,
BSFormatter btcFormatter) {
super(daoFacade, bsqWalletService, bsqFormatter, btcFormatter);
}
@Override
public void initialize() {
super.initialize();
createProposalsTableView();
createEmptyProposalDisplay();
listChangeListener = c -> updateListItems();
}
@Override
protected void activate() {
super.activate();
daoFacade.getClosedBallots().addListener(listChangeListener);
}
@Override
protected void deactivate() {
super.deactivate();
daoFacade.getClosedBallots().removeListener(listChangeListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void fillListItems() {
List<Ballot> list = daoFacade.getClosedBallots();
proposalBaseProposalListItems.setAll(list.stream()
.map(ballot -> new ClosedBallotListItem(ballot, daoFacade, bsqWalletService, bsqFormatter))
.collect(Collectors.toSet()));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private ClosedBallotListItem getBallotListItem() {
return (ClosedBallotListItem) selectedBaseProposalListItem;
}
///////////////////////////////////////////////////////////////////////////////////////////
// TableColumns
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void createProposalColumns(TableView<BaseProposalListItem> tableView) {
super.createProposalColumns(tableView);
createConfidenceColumn(tableView);
TableColumn<BaseProposalListItem, BaseProposalListItem> actionColumn = new TableColumn<>();
actionColumn.setMinWidth(130);
actionColumn.setMaxWidth(actionColumn.getMinWidth());
actionColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
actionColumn.setCellFactory(new Callback<TableColumn<BaseProposalListItem, BaseProposalListItem>,
TableCell<BaseProposalListItem, BaseProposalListItem>>() {
@Override
public TableCell<BaseProposalListItem, BaseProposalListItem> call(TableColumn<BaseProposalListItem,
BaseProposalListItem> column) {
return new TableCell<BaseProposalListItem, BaseProposalListItem>() {
ImageView imageView;
@Override
public void updateItem(final BaseProposalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
ClosedBallotListItem closedBallotListItem = (ClosedBallotListItem) item;
if (imageView == null) {
imageView = closedBallotListItem.getImageView();
setGraphic(imageView);
}
closedBallotListItem.onPhaseChanged(currentPhase);
} else {
setGraphic(null);
if (imageView != null)
imageView = null;
}
}
};
}
});
actionColumn.setComparator(Comparator.comparing(BaseProposalListItem::getConfirmations));
tableView.getColumns().add(actionColumn);
}
}

View file

@ -20,7 +20,7 @@
<?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?> <?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?> <?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.voting.history.VotingHistoryView" <GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.voting.closed.VotingHistoryView"
hgap="5.0" vgap="5.0" hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"

View file

@ -15,7 +15,7 @@
* along with Bisq. If not, see <http://www.gnu.org/licenses/>. * along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/ */
package bisq.desktop.main.dao.voting.history; package bisq.desktop.main.dao.voting.closed;
import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView; import bisq.desktop.common.view.FxmlView;