Merge pull request #6504 from HenrikJannsen/add_check_for_isInConflictWithSeedNode

Add check for dao state hash conflict with seed node
This commit is contained in:
Alejandro García 2023-01-06 14:09:01 +00:00 committed by GitHub
commit dda32c09ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 149 additions and 93 deletions

View file

@ -291,7 +291,8 @@ public abstract class WalletService {
continue;
}
if (!connectedOutput.isMine(wallet)) {
log.error("connectedOutput is not mine");
log.info("ConnectedOutput is not mine. This can be the case for BSQ transactions where the " +
"input gets signed by the other wallet. connectedOutput={}", connectedOutput);
continue;
}

View file

@ -57,7 +57,6 @@ import bisq.core.dao.monitoring.DaoStateMonitoringService;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.BaseTx;
import bisq.core.dao.state.model.blockchain.BaseTxOutput;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput;
@ -531,10 +530,13 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.getBlockAtHeight(chainHeight);
}
public boolean daoStateNeedsRebuilding() {
return daoStateMonitoringService.isInConflictWithSeedNode() || daoStateMonitoringService.isDaoStateBlockChainNotConnecting();
public boolean isDaoStateReadyAndInSync() {
return daoStateService.isParseBlockChainComplete() &&
!daoStateMonitoringService.isInConflictWithSeedNode() &&
!daoStateMonitoringService.isDaoStateBlockChainNotConnecting();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Use case: Bonding
///////////////////////////////////////////////////////////////////////////////////////////
@ -578,10 +580,6 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.getTotalAmountOfConfiscatedTxOutputs();
}
public long getTotalAmountOfInvalidatedBsq() {
return daoStateService.getTotalAmountOfInvalidatedBsq();
}
// Contains burned fee and invalidated bsq due invalid txs
public long getTotalAmountOfBurntBsq() {
return daoStateService.getTotalAmountOfBurntBsq();
@ -595,11 +593,6 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.getIrregularTxs();
}
public long getTotalAmountOfUnspentTxOutputs() {
// Does not consider confiscated outputs (they stay as utxo)
return daoStateService.getUnspentTxOutputMap().values().stream().mapToLong(BaseTxOutput::getValue).sum();
}
public Optional<Integer> getLockTime(String txId) {
return daoStateService.getLockTime(txId);
}

View file

@ -78,7 +78,6 @@ public class DelayedPayoutTxReceiverService implements DaoStateListener {
// spike when opening arbitration.
private static final long DPT_MIN_TX_FEE_RATE = 10;
private final DaoStateService daoStateService;
private final BurningManService burningManService;
private int currentChainHeight;
@ -127,12 +126,6 @@ public class DelayedPayoutTxReceiverService implements DaoStateListener {
burningManService.getActiveBurningManCandidates(burningManSelectionHeight) :
burningManService.getBurningManCandidatesByName(burningManSelectionHeight).values();
if (burningManCandidates.isEmpty()) {
// If there are no compensation requests (e.g. at dev testing) we fall back to the legacy BM
return List.of(new Tuple2<>(inputAmount, burningManService.getLegacyBurningManAddress(burningManSelectionHeight)));
}
// We need to use the same txFeePerVbyte value for both traders.
// We use the tradeTxFee value which is calculated from the average of taker fee tx size and deposit tx size.
// Otherwise, we would need to sync the fee rate of both traders.
@ -146,12 +139,19 @@ public class DelayedPayoutTxReceiverService implements DaoStateListener {
// Smallest tx size is 246. With additional change output we add 32. To be safe we use the largest expected size.
double txSize = 278;
long txFeePerVbyte = Math.max(DPT_MIN_TX_FEE_RATE, Math.round(tradeTxFee / txSize));
if (burningManCandidates.isEmpty()) {
// If there are no compensation requests (e.g. at dev testing) we fall back to the legacy BM
long spendableAmount = getSpendableAmount(1, inputAmount, txFeePerVbyte);
return List.of(new Tuple2<>(spendableAmount, burningManService.getLegacyBurningManAddress(burningManSelectionHeight)));
}
long spendableAmount = getSpendableAmount(burningManCandidates.size(), inputAmount, txFeePerVbyte);
// We only use outputs > 1000 sat or at least 2 times the cost for the output (32 bytes).
// If we remove outputs it will be spent as miner fee.
long minOutputAmount = Math.max(DPT_MIN_OUTPUT_AMOUNT, txFeePerVbyte * 32 * 2);
// Sanity check that max share of a non-legacy BM is 20% over MAX_BURN_SHARE (taking into account potential increase due adjustment)
long maxOutputAmount = Math.round(inputAmount * (BurningManService.MAX_BURN_SHARE * 1.2));
long maxOutputAmount = Math.round(spendableAmount * (BurningManService.MAX_BURN_SHARE * 1.2));
// We accumulate small amounts which gets filtered out and subtract it from 1 to get an adjustment factor
// used later to be applied to the remaining burningmen share.
double adjustment = 1 - burningManCandidates.stream()

View file

@ -206,7 +206,7 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List
}
private void handleRepublishGovernanceDataRequest() {
log.warn("We received a RepublishGovernanceDataRequest and re-published all proposalPayloads and " +
log.info("We received a RepublishGovernanceDataRequest and re-published all proposalPayloads and " +
"blindVotePayloads to the P2P network.");
missingDataRequestService.reRepublishAllGovernanceData();
}

View file

@ -25,6 +25,7 @@ import bisq.core.trade.protocol.bisq_v1.messages.DepositTxAndDelayedPayoutTxMess
import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.bisq_v1.messages.PayoutTxPublishedMessage;
import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter;
import bisq.core.trade.protocol.bisq_v1.tasks.CheckIfDaoStateIsInSync;
import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask;
import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
@ -71,6 +72,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
.with(message)
.from(peer))
.setup(tasks(
CheckIfDaoStateIsInSync.class,
MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,
getVerifyPeersFeePaymentClass(),

View file

@ -27,6 +27,7 @@ import bisq.core.trade.protocol.bisq_v1.messages.DepositTxAndDelayedPayoutTxMess
import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxResponse;
import bisq.core.trade.protocol.bisq_v1.messages.PayoutTxPublishedMessage;
import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter;
import bisq.core.trade.protocol.bisq_v1.tasks.CheckIfDaoStateIsInSync;
import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask;
import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.bisq_v1.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
@ -77,6 +78,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
expect(phase(Trade.Phase.INIT)
.with(TakerEvent.TAKE_OFFER))
.setup(tasks(
CheckIfDaoStateIsInSync.class,
ApplyFilter.class,
getVerifyPeersFeePaymentClass(),
CreateTakerFeeTx.class,

View file

@ -27,6 +27,7 @@ import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureRespons
import bisq.core.trade.protocol.bisq_v1.messages.DepositTxMessage;
import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter;
import bisq.core.trade.protocol.bisq_v1.tasks.CheckIfDaoStateIsInSync;
import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask;
import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerCreateAndSignContract;
import bisq.core.trade.protocol.bisq_v1.tasks.maker.MakerProcessesInputsForDepositTxRequest;
@ -73,6 +74,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
.with(message)
.from(peer))
.setup(tasks(
CheckIfDaoStateIsInSync.class,
MaybeCreateSubAccount.class,
MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,

View file

@ -26,6 +26,7 @@ import bisq.core.trade.protocol.bisq_v1.messages.CounterCurrencyTransferStartedM
import bisq.core.trade.protocol.bisq_v1.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.protocol.bisq_v1.messages.InputsForDepositTxResponse;
import bisq.core.trade.protocol.bisq_v1.tasks.ApplyFilter;
import bisq.core.trade.protocol.bisq_v1.tasks.CheckIfDaoStateIsInSync;
import bisq.core.trade.protocol.bisq_v1.tasks.TradeTask;
import bisq.core.trade.protocol.bisq_v1.tasks.seller.MaybeCreateSubAccount;
import bisq.core.trade.protocol.bisq_v1.tasks.seller.SellerCreatesDelayedPayoutTx;
@ -73,6 +74,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
.with(TakerEvent.TAKE_OFFER)
.from(trade.getTradingPeerNodeAddress()))
.setup(tasks(
CheckIfDaoStateIsInSync.class,
MaybeCreateSubAccount.class,
ApplyFilter.class,
getVerifyPeersFeePaymentClass(),

View file

@ -0,0 +1,46 @@
/*
* 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.trade.protocol.bisq_v1.tasks;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.common.taskrunner.TaskRunner;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
public class CheckIfDaoStateIsInSync extends TradeTask {
public CheckIfDaoStateIsInSync(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
checkArgument(processModel.getDaoFacade().isDaoStateReadyAndInSync(), "DAO state is not in sync with seed nodes");
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -32,7 +32,6 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
@ -74,8 +73,19 @@ public class BuyerVerifiesFinalDelayedPayoutTx extends TradeTask {
depositTx,
delayedPayoutTxReceivers,
lockTime);
checkArgument(buyersDelayedPayoutTx.getTxId().equals(finalDelayedPayoutTx.getTxId()),
"TxIds of buyersDelayedPayoutTx and finalDelayedPayoutTx must be the same");
if (!buyersDelayedPayoutTx.getTxId().equals(finalDelayedPayoutTx.getTxId())) {
String errorMsg = "TxIds of buyersDelayedPayoutTx and finalDelayedPayoutTx must be the same.";
log.error("{} \nbuyersDelayedPayoutTx={}, \nfinalDelayedPayoutTx={}, " +
"\nBtcWalletService.chainHeight={}, " +
"\nDaoState.chainHeight={}, " +
"\nisDaoStateIsInSync={}",
errorMsg, buyersDelayedPayoutTx, finalDelayedPayoutTx,
processModel.getBtcWalletService().getBestChainHeight(),
processModel.getDaoFacade().getChainHeight(),
processModel.getDaoFacade().isDaoStateReadyAndInSync());
throw new IllegalArgumentException(errorMsg);
}
}
complete();

View file

@ -32,7 +32,6 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
@ -66,8 +65,18 @@ public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask {
preparedDepositTx,
delayedPayoutTxReceivers,
lockTime);
checkArgument(buyersPreparedDelayedPayoutTx.getTxId().equals(sellersPreparedDelayedPayoutTx.getTxId()),
"TxIds of buyersPreparedDelayedPayoutTx and sellersPreparedDelayedPayoutTx must be the same");
if (!buyersPreparedDelayedPayoutTx.getTxId().equals(sellersPreparedDelayedPayoutTx.getTxId())) {
String errorMsg = "TxIds of buyersPreparedDelayedPayoutTx and sellersPreparedDelayedPayoutTx must be the same.";
log.error("{} \nbuyersPreparedDelayedPayoutTx={}, \nsellersPreparedDelayedPayoutTx={}, " +
"\nBtcWalletService.chainHeight={}, " +
"\nDaoState.chainHeight={}, " +
"\nisDaoStateIsInSync={}",
errorMsg, buyersPreparedDelayedPayoutTx, sellersPreparedDelayedPayoutTx,
processModel.getBtcWalletService().getBestChainHeight(),
processModel.getDaoFacade().getChainHeight(),
processModel.getDaoFacade().isDaoStateReadyAndInSync());
throw new IllegalArgumentException(errorMsg);
}
}
// If the deposit tx is non-malleable, we already know its final ID, so should check that now

View file

@ -310,7 +310,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
setupClockWatcherPopup();
marketPricePresentation.setup();
daoPresentation.setup();
daoPresentation.init();
accountPresentation.setup();
settingsPresentation.setup();
@ -505,7 +505,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
.show());
bisqSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> updateBtcSyncProgress());
daoPresentation.getBsqSyncProgress().addListener((observable, oldValue, newValue) -> updateBtcSyncProgress());
daoPresentation.getDaoStateSyncProgress().addListener((observable, oldValue, newValue) -> updateBtcSyncProgress());
bisqSetup.setFilterWarningHandler(warning -> new Popup().warning(warning).show());
@ -704,7 +704,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
if (btcSyncProgress.doubleValue() < 1) {
combinedSyncProgress.set(btcSyncProgress.doubleValue());
} else {
combinedSyncProgress.set(daoPresentation.getBsqSyncProgress().doubleValue());
combinedSyncProgress.set(daoPresentation.getDaoStateSyncProgress().doubleValue());
}
}
@ -783,7 +783,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
StringProperty getCombinedFooterInfo() {
final StringProperty combinedInfo = new SimpleStringProperty();
combinedInfo.bind(Bindings.concat(this.footerVersionInfo, " ", daoPresentation.getBsqInfo()));
combinedInfo.bind(Bindings.concat(this.footerVersionInfo, " ", daoPresentation.getDaoStateInfo()));
return combinedInfo;
}

View file

@ -1,11 +1,15 @@
package bisq.desktop.main.presentation;
import bisq.desktop.Navigation;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.monitor.MonitorView;
import bisq.desktop.main.dao.monitor.daostate.DaoStateMonitorView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.monitoring.DaoStateMonitoringService;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
@ -28,24 +32,21 @@ import javafx.collections.MapChangeListener;
import lombok.Getter;
@Singleton
public class DaoPresentation implements DaoStateListener {
public class DaoPresentation implements DaoStateListener, DaoStateMonitoringService.Listener {
public static final String DAO_NEWS = "daoNews";
private final Preferences preferences;
private final Navigation navigation;
private final BtcWalletService btcWalletService;
private final DaoFacade daoFacade;
private final DaoStateMonitoringService daoStateMonitoringService;
private final BsqWalletService bsqWalletService;
private final DaoStateService daoStateService;
private final ChangeListener<Number> walletChainHeightListener;
@Getter
private final DoubleProperty bsqSyncProgress = new SimpleDoubleProperty(-1);
private final DoubleProperty daoStateSyncProgress = new SimpleDoubleProperty(-1);
@Getter
private final StringProperty bsqInfo = new SimpleStringProperty("");
private final StringProperty daoStateInfo = new SimpleStringProperty("");
private final SimpleBooleanProperty showNotification = new SimpleBooleanProperty(false);
private boolean daoConflictWarningShown = false; // allow only one conflict warning per session
@Inject
public DaoPresentation(Preferences preferences,
@ -53,12 +54,11 @@ public class DaoPresentation implements DaoStateListener {
BtcWalletService btcWalletService,
BsqWalletService bsqWalletService,
DaoStateService daoStateService,
DaoFacade daoFacade) {
this.preferences = preferences;
DaoStateMonitoringService daoStateMonitoringService) {
this.navigation = navigation;
this.btcWalletService = btcWalletService;
this.bsqWalletService = bsqWalletService;
this.daoFacade = daoFacade;
this.daoStateMonitoringService = daoStateMonitoringService;
this.daoStateService = daoStateService;
preferences.getDontShowAgainMapAsObservable().addListener((MapChangeListener<? super String, ? super Boolean>) change -> {
@ -73,34 +73,24 @@ public class DaoPresentation implements DaoStateListener {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
// Public
///////////////////////////////////////////////////////////////////////////////////////////
private void onUpdateAnyChainHeight() {
final int bsqBlockChainHeight = daoFacade.getChainHeight();
final int bsqWalletChainHeight = bsqWalletService.getBestChainHeight();
if (bsqWalletChainHeight > 0) {
final boolean synced = bsqWalletChainHeight == bsqBlockChainHeight;
if (bsqBlockChainHeight != bsqWalletChainHeight) {
bsqSyncProgress.set(-1);
} else {
bsqSyncProgress.set(0);
public void init() {
showNotification.set(false);
btcWalletService.getChainHeightProperty().addListener(walletChainHeightListener);
daoStateService.addDaoStateListener(this);
daoStateMonitoringService.addListener(this);
onUpdateAnyChainHeight();
}
if (synced) {
bsqInfo.set("");
if (daoFacade.daoStateNeedsRebuilding() && !daoConflictWarningShown) {
daoConflictWarningShown = true; // only warn max 1 time per session so as not to annoy
GUIUtil.showDaoNeedsResyncPopup(navigation);
}
} else {
bsqInfo.set(Res.get("mainView.footer.bsqInfo.synchronizing"));
}
} else {
bsqInfo.set(Res.get("mainView.footer.bsqInfo.synchronizing"));
}
public BooleanProperty getShowDaoUpdatesNotification() {
return showNotification;
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@ -110,22 +100,37 @@ public class DaoPresentation implements DaoStateListener {
onUpdateAnyChainHeight();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public
// DaoStateMonitoringService.Listener
///////////////////////////////////////////////////////////////////////////////////////////
public BooleanProperty getShowDaoUpdatesNotification() {
return showNotification;
@Override
public void onDaoStateHashesChanged() {
if (!daoStateService.isParseBlockChainComplete()) {
return;
}
public void setup() {
// devs enable this when a news badge is required
//showNotification.set(DevEnv.isDaoActivated() && preferences.showAgain(DAO_NEWS));
showNotification.set(false);
this.btcWalletService.getChainHeightProperty().addListener(walletChainHeightListener);
daoStateService.addDaoStateListener(this);
onUpdateAnyChainHeight();
if (daoStateMonitoringService.isInConflictWithSeedNode() ||
daoStateMonitoringService.isDaoStateBlockChainNotConnecting()) {
new Popup().warning(Res.get("popup.warning.daoNeedsResync"))
.actionButtonTextWithGoTo("navigation.dao.networkMonitor")
.onAction(() -> navigation.navigateTo(MainView.class, DaoView.class, MonitorView.class, DaoStateMonitorView.class))
.show();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void onUpdateAnyChainHeight() {
int bsqWalletChainHeight = bsqWalletService.getBestChainHeight();
int daoStateChainHeight = daoStateService.getChainHeight();
boolean chainHeightsInSync = bsqWalletChainHeight > 0 && bsqWalletChainHeight == daoStateChainHeight;
boolean isDaoStateReady = chainHeightsInSync && daoStateService.isParseBlockChainComplete();
daoStateSyncProgress.set(isDaoStateReady ? 0 : -1);
daoStateInfo.set(isDaoStateReady ? "" : Res.get("mainView.footer.bsqInfo.synchronizing"));
}
}

View file

@ -27,9 +27,6 @@ import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.main.MainView;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.monitor.MonitorView;
import bisq.desktop.main.dao.monitor.daostate.DaoStateMonitorView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.core.account.witness.AccountAgeWitness;
@ -806,19 +803,6 @@ public class GUIUtil {
return false;
}
public static void showDaoNeedsResyncPopup(Navigation navigation) {
String key = "showDaoNeedsResyncPopup";
if (DontShowAgainLookup.showAgain(key)) {
UserThread.runAfter(() -> new Popup().warning(Res.get("popup.warning.daoNeedsResync"))
.dontShowAgainId(key)
.actionButtonTextWithGoTo("navigation.dao.networkMonitor")
.onAction(() -> {
navigation.navigateTo(MainView.class, DaoView.class, MonitorView.class, DaoStateMonitorView.class);
})
.show(), 5, TimeUnit.SECONDS);
}
}
public static boolean isReadyForTxBroadcastOrShowPopup(P2PService p2PService, WalletsSetup walletsSetup) {
if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) {
return false;