Merge pull request #5764 from chimp1984/avoid-usage-of-outdated-addresses

Avoid that outdated donation and fee addresses are used.
This commit is contained in:
Christoph Atteneder 2021-11-09 21:01:47 +01:00 committed by GitHub
commit 06da45fb7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 103 additions and 35 deletions

View file

@ -73,6 +73,7 @@ import bisq.core.dao.state.model.governance.Role;
import bisq.core.dao.state.model.governance.RoleProposal; import bisq.core.dao.state.model.governance.RoleProposal;
import bisq.core.dao.state.model.governance.Vote; import bisq.core.dao.state.model.governance.Vote;
import bisq.core.dao.state.storage.DaoStateStorageService; import bisq.core.dao.state.storage.DaoStateStorageService;
import bisq.core.trade.DelayedPayoutAddressProvider;
import bisq.asset.Asset; import bisq.asset.Asset;
@ -799,9 +800,9 @@ public class DaoFacade implements DaoSetupService {
if (Config.baseCurrencyNetwork().isMainnet()) { if (Config.baseCurrencyNetwork().isMainnet()) {
// If Dao is deactivated we need to add the past addresses used as well. // If Dao is deactivated we need to add the past addresses used as well.
// This list need to be updated once a new address gets defined. // This list need to be updated once a new address gets defined.
allPastParamValues.add("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp"); // burning man 2019 allPastParamValues.add(DelayedPayoutAddressProvider.BM2019_ADDRESS);
allPastParamValues.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV"); // burningman2 allPastParamValues.add(DelayedPayoutAddressProvider.BM2_ADDRESS);
allPastParamValues.add("34VLFgtFKAtwTdZ5rengTT2g2zC99sWQLC"); // burningman3 (https://github.com/bisq-network/roles/issues/80#issuecomment-723577776) allPastParamValues.add(DelayedPayoutAddressProvider.BM3_ADDRESS);
} }
return allPastParamValues; return allPastParamValues;

View file

@ -18,11 +18,13 @@
package bisq.core.dao.state; package bisq.core.dao.state;
import bisq.core.dao.DaoSetupService; import bisq.core.dao.DaoSetupService;
import bisq.core.dao.governance.param.Param;
import bisq.core.dao.monitoring.DaoStateMonitoringService; import bisq.core.dao.monitoring.DaoStateMonitoringService;
import bisq.core.dao.monitoring.model.DaoStateHash; import bisq.core.dao.monitoring.model.DaoStateHash;
import bisq.core.dao.state.model.DaoState; import bisq.core.dao.state.model.DaoState;
import bisq.core.dao.state.model.blockchain.Block; import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.storage.DaoStateStorageService; import bisq.core.dao.state.storage.DaoStateStorageService;
import bisq.core.trade.DelayedPayoutAddressProvider;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.common.config.Config; import bisq.common.config.Config;
@ -62,6 +64,7 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
private final DaoStateStorageService daoStateStorageService; private final DaoStateStorageService daoStateStorageService;
private final DaoStateMonitoringService daoStateMonitoringService; private final DaoStateMonitoringService daoStateMonitoringService;
private final Preferences preferences; private final Preferences preferences;
private final Config config;
private final File storageDir; private final File storageDir;
private protobuf.DaoState daoStateCandidate; private protobuf.DaoState daoStateCandidate;
@ -86,12 +89,14 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
DaoStateStorageService daoStateStorageService, DaoStateStorageService daoStateStorageService,
DaoStateMonitoringService daoStateMonitoringService, DaoStateMonitoringService daoStateMonitoringService,
Preferences preferences, Preferences preferences,
Config config,
@Named(Config.STORAGE_DIR) File storageDir) { @Named(Config.STORAGE_DIR) File storageDir) {
this.daoStateService = daoStateService; this.daoStateService = daoStateService;
this.genesisTxInfo = genesisTxInfo; this.genesisTxInfo = genesisTxInfo;
this.daoStateStorageService = daoStateStorageService; this.daoStateStorageService = daoStateStorageService;
this.daoStateMonitoringService = daoStateMonitoringService; this.daoStateMonitoringService = daoStateMonitoringService;
this.preferences = preferences; this.preferences = preferences;
this.config = config;
this.storageDir = storageDir; this.storageDir = storageDir;
} }
@ -114,6 +119,20 @@ public class DaoStateSnapshotService implements DaoSetupService, DaoStateListene
// DaoStateListener // DaoStateListener
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
if (config.baseCurrencyNetwork.isMainnet()) {
// In case the DAO state is invalid we might get an outdated RECIPIENT_BTC_ADDRESS. In that case we trigger
// a dao resync from resources.
String address = daoStateService.getParamValue(Param.RECIPIENT_BTC_ADDRESS, daoStateService.getChainHeight());
if (DelayedPayoutAddressProvider.isOutdatedAddress(address)) {
log.warn("The RECIPIENT_BTC_ADDRESS is not as expected. The DAO state is probably out of " +
"sync and a resync should fix that issue.");
resyncDaoStateFromResources();
}
}
}
// We listen onDaoStateChanged to ensure the dao state has been processed from listener clients after parsing. // We listen onDaoStateChanged to ensure the dao state has been processed from listener clients after parsing.
// We need to listen during batch processing as well to write snapshots during that process. // We need to listen during batch processing as well to write snapshots during that process.
@Override @Override

View file

@ -66,7 +66,7 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
TradeWalletService tradeWalletService = model.getTradeWalletService(); TradeWalletService tradeWalletService = model.getTradeWalletService();
String feeReceiver = FeeReceiverSelector.getAddress(model.getDaoFacade(), model.getFilterManager()); String feeReceiver = FeeReceiverSelector.getAddress(model.getFilterManager());
if (offer.isCurrencyForMakerFeeBtc()) { if (offer.isCurrencyForMakerFeeBtc()) {
tradeWalletService.createBtcTradingFeeTx( tradeWalletService.createBtcTradingFeeTx(

View file

@ -0,0 +1,53 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.param.Param;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DelayedPayoutAddressProvider {
public static final String INITIAL_BM_ADDRESS = "1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7"; // Initial DAO donation address
public static final String BM2019_ADDRESS = "3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp"; // burning2019
public static final String BM2_ADDRESS = "3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV"; // burningman2
// burningman3 https://github.com/bisq-network/roles/issues/80#issuecomment-723577776
public static final String BM3_ADDRESS = "34VLFgtFKAtwTdZ5rengTT2g2zC99sWQLC";
public static String getDelayedPayoutAddress(DaoFacade daoFacade) {
String address = daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS);
if (isOutdatedAddress(address)) {
log.warn("Outdated delayed payout address. " +
"This can be the case if the DAO is deactivated or if the user has an invalid DAO state." +
"We set the address to the recent one (BM3_ADDRESS).");
return getAddress();
}
return address;
}
public static boolean isOutdatedAddress(String address) {
return address.equals(INITIAL_BM_ADDRESS) ||
address.equals(BM2019_ADDRESS) ||
address.equals(BM2_ADDRESS);
}
public static String getAddress() {
return BM3_ADDRESS;
}
}

View file

@ -65,7 +65,7 @@ public class CreateTakerFeeTx extends TradeTask {
TradeWalletService tradeWalletService = processModel.getTradeWalletService(); TradeWalletService tradeWalletService = processModel.getTradeWalletService();
Transaction transaction; Transaction transaction;
String feeReceiver = FeeReceiverSelector.getAddress(processModel.getDaoFacade(), processModel.getFilterManager()); String feeReceiver = FeeReceiverSelector.getAddress(processModel.getFilterManager());
if (trade.isCurrencyForTakerFeeBtc()) { if (trade.isCurrencyForTakerFeeBtc()) {
transaction = tradeWalletService.createBtcTradingFeeTx( transaction = tradeWalletService.createBtcTradingFeeTx(

View file

@ -17,8 +17,6 @@
package bisq.core.util; package bisq.core.util;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.param.Param;
import bisq.core.filter.FilterManager; import bisq.core.filter.FilterManager;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -34,12 +32,18 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class FeeReceiverSelector { public class FeeReceiverSelector {
public static String getAddress(DaoFacade daoFacade, FilterManager filterManager) { public static final String BTC_FEE_RECEIVER_ADDRESS = "38bZBj5peYS3Husdz7AH3gEUiUbYRD951t";
return getAddress(daoFacade, filterManager, new Random());
public static String getMostRecentAddress() {
return BTC_FEE_RECEIVER_ADDRESS;
}
public static String getAddress(FilterManager filterManager) {
return getAddress(filterManager, new Random());
} }
@VisibleForTesting @VisibleForTesting
static String getAddress(DaoFacade daoFacade, FilterManager filterManager, Random rnd) { static String getAddress(FilterManager filterManager, Random rnd) {
List<String> feeReceivers = Optional.ofNullable(filterManager.getFilter()) List<String> feeReceivers = Optional.ofNullable(filterManager.getFilter())
.flatMap(f -> Optional.ofNullable(f.getBtcFeeReceiverAddresses())) .flatMap(f -> Optional.ofNullable(f.getBtcFeeReceiverAddresses()))
.orElse(List.of()); .orElse(List.of());
@ -61,8 +65,8 @@ public class FeeReceiverSelector {
return receiverAddressList.get(weightedSelection(amountList, rnd)); return receiverAddressList.get(weightedSelection(amountList, rnd));
} }
// We keep default value as fallback in case no filter value is available or user has old version. // If no fee address receiver is defined via filter we use the hard coded recent address
return daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS); return getMostRecentAddress();
} }
@VisibleForTesting @VisibleForTesting

View file

@ -39,6 +39,7 @@ public class DaoStateSnapshotServiceTest {
mock(DaoStateStorageService.class), mock(DaoStateStorageService.class),
mock(DaoStateMonitoringService.class), mock(DaoStateMonitoringService.class),
null, null,
null,
null); null);
} }

View file

@ -19,15 +19,17 @@ package bisq.core.provider.mempool;
import bisq.core.dao.governance.param.Param; import bisq.core.dao.governance.param.Param;
import bisq.core.dao.state.DaoStateService; import bisq.core.dao.state.DaoStateService;
import bisq.core.trade.DelayedPayoutAddressProvider;
import bisq.core.util.FeeReceiverSelector;
import bisq.core.util.ParsingUtils; import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.BsqFormatter;
import org.bitcoinj.core.Coin;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.bitcoinj.core.Coin;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -43,8 +45,8 @@ import org.slf4j.LoggerFactory;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.junit.Test;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -61,11 +63,11 @@ public class TxValidatorTest {
btcFeeReceivers.add("13sxMq8mTw7CTSqgGiMPfwo6ZDsVYrHLmR"); btcFeeReceivers.add("13sxMq8mTw7CTSqgGiMPfwo6ZDsVYrHLmR");
btcFeeReceivers.add("19qA2BVPoyXDfHKVMovKG7SoxGY7xrBV8c"); btcFeeReceivers.add("19qA2BVPoyXDfHKVMovKG7SoxGY7xrBV8c");
btcFeeReceivers.add("19BNi5EpZhgBBWAt5ka7xWpJpX2ZWJEYyq"); btcFeeReceivers.add("19BNi5EpZhgBBWAt5ka7xWpJpX2ZWJEYyq");
btcFeeReceivers.add("38bZBj5peYS3Husdz7AH3gEUiUbYRD951t"); btcFeeReceivers.add(FeeReceiverSelector.BTC_FEE_RECEIVER_ADDRESS);
btcFeeReceivers.add("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp"); btcFeeReceivers.add(DelayedPayoutAddressProvider.BM2019_ADDRESS);
btcFeeReceivers.add("1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7"); btcFeeReceivers.add("1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7");
btcFeeReceivers.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV"); btcFeeReceivers.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV");
btcFeeReceivers.add("34VLFgtFKAtwTdZ5rengTT2g2zC99sWQLC"); btcFeeReceivers.add(DelayedPayoutAddressProvider.BM3_ADDRESS);
log.warn("Known BTC fee receivers: {}", btcFeeReceivers.toString()); log.warn("Known BTC fee receivers: {}", btcFeeReceivers.toString());
} }

View file

@ -17,8 +17,6 @@
package bisq.core.util; package bisq.core.util;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.param.Param;
import bisq.core.filter.Filter; import bisq.core.filter.Filter;
import bisq.core.filter.FilterManager; import bisq.core.filter.FilterManager;
@ -42,8 +40,6 @@ import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class FeeReceiverSelectorTest { public class FeeReceiverSelectorTest {
@Mock
private DaoFacade daoFacade;
@Mock @Mock
private FilterManager filterManager; private FilterManager filterManager;
@ -55,7 +51,7 @@ public class FeeReceiverSelectorTest {
Map<String, Integer> selectionCounts = new HashMap<>(); Map<String, Integer> selectionCounts = new HashMap<>();
for (int i = 0; i < 400; i++) { for (int i = 0; i < 400; i++) {
String address = FeeReceiverSelector.getAddress(daoFacade, filterManager, rnd); String address = FeeReceiverSelector.getAddress(filterManager, rnd);
selectionCounts.compute(address, (k, n) -> n != null ? n + 1 : 1); selectionCounts.compute(address, (k, n) -> n != null ? n + 1 : 1);
} }
@ -69,34 +65,26 @@ public class FeeReceiverSelectorTest {
@Test @Test
public void testGetAddress_noValidReceivers_nullFilter() { public void testGetAddress_noValidReceivers_nullFilter() {
when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default");
when(filterManager.getFilter()).thenReturn(null); when(filterManager.getFilter()).thenReturn(null);
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager)); assertEquals(FeeReceiverSelector.getMostRecentAddress(), FeeReceiverSelector.getAddress(filterManager));
} }
@Test @Test
public void testGetAddress_noValidReceivers_filterWithNullList() { public void testGetAddress_noValidReceivers_filterWithNullList() {
when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default");
when(filterManager.getFilter()).thenReturn(filterWithReceivers(null)); when(filterManager.getFilter()).thenReturn(filterWithReceivers(null));
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager)); assertEquals(FeeReceiverSelector.getMostRecentAddress(), FeeReceiverSelector.getAddress(filterManager));
} }
@Test @Test
public void testGetAddress_noValidReceivers_filterWithEmptyList() { public void testGetAddress_noValidReceivers_filterWithEmptyList() {
when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default");
when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of())); when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of()));
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager)); assertEquals(FeeReceiverSelector.getMostRecentAddress(), FeeReceiverSelector.getAddress(filterManager));
} }
@Test @Test
public void testGetAddress_noValidReceivers_filterWithIllFormedList() { public void testGetAddress_noValidReceivers_filterWithIllFormedList() {
when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default");
when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of("ill-formed"))); when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of("ill-formed")));
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager)); assertEquals(FeeReceiverSelector.getMostRecentAddress(), FeeReceiverSelector.getAddress(filterManager));
} }
@Test @Test