mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-20 13:54:32 +01:00
Merge branch 'master' into 02-cli-console-formatting-api
This commit is contained in:
commit
20cc085dc8
66 changed files with 1145 additions and 742 deletions
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
|
||||
// @Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class BsqSwapTradeTestLoop extends AbstractOfferTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
createBsqSwapBsqPaymentAccounts();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetBalancesBeforeTrade() {
|
||||
BsqSwapTradeTest test = new BsqSwapTradeTest();
|
||||
runTradeLoop(test);
|
||||
}
|
||||
|
||||
private void runTradeLoop(BsqSwapTradeTest test) {
|
||||
// TODO Fix wallet inconsistency bugs after 2nd trades.
|
||||
for (int tradeCount = 1; tradeCount <= 2; tradeCount++) {
|
||||
log.warn("================================ Trade # {} ================================", tradeCount);
|
||||
test.testGetBalancesBeforeTrade();
|
||||
|
||||
test.testAliceCreateBsqSwapBuyOffer();
|
||||
genBtcBlocksThenWait(1, 8000);
|
||||
|
||||
test.testBobTakesBsqSwapOffer();
|
||||
genBtcBlocksThenWait(1, 8000);
|
||||
|
||||
test.testGetBalancesAfterTrade();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -501,8 +501,6 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
if (completeHandler != null) {
|
||||
UserThread.execute(completeHandler);
|
||||
}
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package bisq.common.util;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.DevEnv;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -47,18 +48,31 @@ public class GcUtil {
|
|||
* @param trigger Threshold for free memory in MB when we invoke the garbage collector
|
||||
*/
|
||||
private static void autoReleaseMemory(long trigger) {
|
||||
UserThread.runPeriodically(() -> maybeReleaseMemory(trigger), 60);
|
||||
UserThread.runPeriodically(() -> maybeReleaseMemory(trigger), 120);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trigger Threshold for free memory in MB when we invoke the garbage collector
|
||||
*/
|
||||
private static void maybeReleaseMemory(long trigger) {
|
||||
long totalMemory = Runtime.getRuntime().totalMemory();
|
||||
if (totalMemory > trigger * 1024 * 1024) {
|
||||
log.info("Invoke garbage collector. Total memory: {} {} {}", Utilities.readableFileSize(totalMemory), totalMemory, trigger * 1024 * 1024);
|
||||
long ts = System.currentTimeMillis();
|
||||
long preGcMemory = Runtime.getRuntime().totalMemory();
|
||||
if (preGcMemory > trigger * 1024 * 1024) {
|
||||
System.gc();
|
||||
log.info("Total memory after gc() call: {}", Utilities.readableFileSize(Runtime.getRuntime().totalMemory()));
|
||||
long postGcMemory = Runtime.getRuntime().totalMemory();
|
||||
log.info("GC reduced memory by {}. Total memory before/after: {}/{}. Took {} ms.",
|
||||
Utilities.readableFileSize(preGcMemory - postGcMemory),
|
||||
Utilities.readableFileSize(preGcMemory),
|
||||
Utilities.readableFileSize(postGcMemory),
|
||||
System.currentTimeMillis() - ts);
|
||||
if (DevEnv.isDevMode()) {
|
||||
try {
|
||||
// To see from where we got called
|
||||
throw new RuntimeException("Dummy Exception for print stacktrace at maybeReleaseMemory");
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import bisq.core.dao.governance.proofofburn.MyProofOfBurnListService;
|
|||
import bisq.core.dao.governance.proposal.MyProposalListService;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
@ -42,6 +43,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
@Slf4j
|
||||
public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
private final DaoSetup daoSetup;
|
||||
private final Preferences preferences;
|
||||
|
||||
@Inject
|
||||
public AppSetupWithP2PAndDAO(P2PService p2PService,
|
||||
|
@ -58,6 +60,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
|||
MyProposalListService myProposalListService,
|
||||
MyReputationListService myReputationListService,
|
||||
MyProofOfBurnListService myProofOfBurnListService,
|
||||
Preferences preferences,
|
||||
Config config) {
|
||||
super(p2PService,
|
||||
p2PDataStorage,
|
||||
|
@ -69,6 +72,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
|||
config);
|
||||
|
||||
this.daoSetup = daoSetup;
|
||||
this.preferences = preferences;
|
||||
|
||||
// TODO Should be refactored/removed. In the meantime keep in sync with CorePersistedDataHost
|
||||
if (config.daoActivated) {
|
||||
|
@ -86,5 +90,8 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
|||
super.onBasicServicesInitialized();
|
||||
|
||||
daoSetup.onAllServicesInitialized(log::error, log::warn);
|
||||
|
||||
// For seed nodes we need to set default value to true
|
||||
preferences.setUseFullModeDaoMonitor(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +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;
|
||||
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
import bisq.core.dao.state.DaoStateListener;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class DaoEventCoordinator implements DaoSetupService, DaoStateListener {
|
||||
private final DaoStateService daoStateService;
|
||||
private final DaoStateSnapshotService daoStateSnapshotService;
|
||||
private final DaoStateMonitoringService daoStateMonitoringService;
|
||||
|
||||
@Inject
|
||||
public DaoEventCoordinator(DaoStateService daoStateService,
|
||||
DaoStateSnapshotService daoStateSnapshotService,
|
||||
DaoStateMonitoringService daoStateMonitoringService) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.daoStateSnapshotService = daoStateSnapshotService;
|
||||
this.daoStateMonitoringService = daoStateMonitoringService;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoSetupService
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void addListeners() {
|
||||
this.daoStateService.addDaoStateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We listen onDaoStateChanged to ensure the dao state has been processed from listener clients after parsing.
|
||||
// We need to listen during batch processing as well to write snapshots during that process.
|
||||
@Override
|
||||
public void onDaoStateChanged(Block block) {
|
||||
// We need to execute first the daoStateMonitoringService
|
||||
daoStateMonitoringService.createHashFromBlock(block);
|
||||
daoStateSnapshotService.maybeCreateSnapshot(block);
|
||||
}
|
||||
}
|
|
@ -102,7 +102,6 @@ public class DaoModule extends AppModule {
|
|||
protected void configure() {
|
||||
bind(DaoSetup.class).in(Singleton.class);
|
||||
bind(DaoFacade.class).in(Singleton.class);
|
||||
bind(DaoEventCoordinator.class).in(Singleton.class);
|
||||
bind(DaoKillSwitch.class).in(Singleton.class);
|
||||
|
||||
// Node, parser
|
||||
|
|
|
@ -39,6 +39,7 @@ import bisq.core.dao.node.BsqNode;
|
|||
import bisq.core.dao.node.BsqNodeProvider;
|
||||
import bisq.core.dao.node.explorer.ExportJsonFilesService;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
|
@ -78,16 +79,11 @@ public class DaoSetup {
|
|||
DaoStateMonitoringService daoStateMonitoringService,
|
||||
ProposalStateMonitoringService proposalStateMonitoringService,
|
||||
BlindVoteStateMonitoringService blindVoteStateMonitoringService,
|
||||
DaoEventCoordinator daoEventCoordinator) {
|
||||
DaoStateSnapshotService daoStateSnapshotService) {
|
||||
|
||||
bsqNode = bsqNodeProvider.getBsqNode();
|
||||
|
||||
// We need to take care of order of execution.
|
||||
|
||||
// For order critical event flow we use the daoEventCoordinator to delegate the calls from anonymous listeners
|
||||
// to concrete clients.
|
||||
daoSetupServices.add(daoEventCoordinator);
|
||||
|
||||
daoSetupServices.add(daoStateService);
|
||||
daoSetupServices.add(cycleService);
|
||||
daoSetupServices.add(ballotListService);
|
||||
|
@ -110,6 +106,7 @@ public class DaoSetup {
|
|||
daoSetupServices.add(daoStateMonitoringService);
|
||||
daoSetupServices.add(proposalStateMonitoringService);
|
||||
daoSetupServices.add(blindVoteStateMonitoringService);
|
||||
daoSetupServices.add(daoStateSnapshotService);
|
||||
|
||||
daoSetupServices.add(bsqNodeProvider.getBsqNode());
|
||||
}
|
||||
|
|
|
@ -90,8 +90,7 @@ public class CycleService implements DaoStateListener, DaoSetupService {
|
|||
}
|
||||
|
||||
public int getCycleIndex(Cycle cycle) {
|
||||
Optional<Cycle> previousCycle = getCycle(cycle.getHeightOfFirstBlock() - 1, daoStateService.getCycles());
|
||||
return previousCycle.map(cycle1 -> getCycleIndex(cycle1) + 1).orElse(0);
|
||||
return daoStateService.getCycles().indexOf(cycle);
|
||||
}
|
||||
|
||||
public boolean isTxInCycle(Cycle cycle, String txId) {
|
||||
|
|
|
@ -274,7 +274,7 @@ public class BlindVoteStateMonitoringService implements DaoSetupService, DaoStat
|
|||
byte[] combined = ArrayUtils.addAll(prevHash, serializedBlindVotes);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(combined);
|
||||
|
||||
BlindVoteStateHash myBlindVoteStateHash = new BlindVoteStateHash(blockHeight, hash, prevHash, blindVotes.size());
|
||||
BlindVoteStateHash myBlindVoteStateHash = new BlindVoteStateHash(blockHeight, hash, blindVotes.size());
|
||||
BlindVoteStateBlock blindVoteStateBlock = new BlindVoteStateBlock(myBlindVoteStateHash);
|
||||
blindVoteStateBlockChain.add(blindVoteStateBlock);
|
||||
blindVoteStateHashChain.add(myBlindVoteStateHash);
|
||||
|
|
|
@ -31,6 +31,7 @@ import bisq.core.dao.state.GenesisTxInfo;
|
|||
import bisq.core.dao.state.model.blockchain.BaseTxOutput;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
import bisq.core.dao.state.model.governance.IssuanceType;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
|
@ -40,7 +41,6 @@ import bisq.common.UserThread;
|
|||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.file.FileUtil;
|
||||
import bisq.common.util.GcUtil;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
@ -54,19 +54,20 @@ import javafx.collections.ObservableList;
|
|||
import java.io.File;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Monitors the DaoState by using a hash for the complete daoState and make it accessible to the network
|
||||
|
@ -88,7 +89,7 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
DaoStateNetworkService.Listener<NewDaoStateHashMessage, GetDaoStateHashesRequest, DaoStateHash> {
|
||||
|
||||
public interface Listener {
|
||||
void onChangeAfterBatchProcessing();
|
||||
void onDaoStateHashesChanged();
|
||||
|
||||
void onCheckpointFail();
|
||||
}
|
||||
|
@ -98,7 +99,6 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
private final GenesisTxInfo genesisTxInfo;
|
||||
private final Set<String> seedNodeAddresses;
|
||||
|
||||
|
||||
@Getter
|
||||
private final LinkedList<DaoStateBlock> daoStateBlockChain = new LinkedList<>();
|
||||
@Getter
|
||||
|
@ -110,6 +110,8 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
@Getter
|
||||
private boolean isInConflictWithSeedNode;
|
||||
@Getter
|
||||
private boolean daoStateBlockChainNotConnecting;
|
||||
@Getter
|
||||
private final ObservableList<UtxoMismatch> utxoMismatches = FXCollections.observableArrayList();
|
||||
|
||||
private final List<Checkpoint> checkpoints = Arrays.asList(
|
||||
|
@ -120,7 +122,13 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
private int numCalls;
|
||||
private long accumulatedDuration;
|
||||
|
||||
private final Preferences preferences;
|
||||
private final File storageDir;
|
||||
@Nullable
|
||||
private Runnable createSnapshotHandler;
|
||||
// Lookup map
|
||||
private Map<Integer, DaoStateBlock> daoStateBlockByHeight = new HashMap<>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
|
@ -131,11 +139,13 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
DaoStateNetworkService daoStateNetworkService,
|
||||
GenesisTxInfo genesisTxInfo,
|
||||
SeedNodeRepository seedNodeRepository,
|
||||
Preferences preferences,
|
||||
@Named(Config.STORAGE_DIR) File storageDir,
|
||||
@Named(Config.IGNORE_DEV_MSG) boolean ignoreDevMsg) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.daoStateNetworkService = daoStateNetworkService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
this.preferences = preferences;
|
||||
this.storageDir = storageDir;
|
||||
this.ignoreDevMsg = ignoreDevMsg;
|
||||
seedNodeAddresses = seedNodeRepository.getSeedNodeAddresses().stream()
|
||||
|
@ -163,16 +173,19 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We do not use onDaoStateChanged but let the DaoEventCoordinator call createHashFromBlock to ensure the
|
||||
// correct order of execution.
|
||||
|
||||
@Override
|
||||
public void onParseBlockChainComplete() {
|
||||
parseBlockChainComplete = true;
|
||||
daoStateService.getLastBlock().ifPresent(this::checkUtxos);
|
||||
|
||||
daoStateNetworkService.addListeners();
|
||||
|
||||
// We wait for processing messages until we have completed batch processing
|
||||
int fromHeight = daoStateService.getChainHeight() - 10;
|
||||
// We take either the height of the previous hashBlock we have or 10 blocks below the chain tip.
|
||||
int nextBlockHeight = daoStateBlockChain.isEmpty() ?
|
||||
genesisTxInfo.getGenesisBlockHeight() :
|
||||
daoStateBlockChain.getLast().getHeight() + 1;
|
||||
int past10 = daoStateService.getChainHeight() - 10;
|
||||
int fromHeight = Math.min(nextBlockHeight, past10);
|
||||
daoStateNetworkService.requestHashesFromAllConnectedSeedNodes(fromHeight);
|
||||
|
||||
if (!ignoreDevMsg) {
|
||||
|
@ -188,16 +201,9 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
|
||||
@Override
|
||||
public void onDaoStateChanged(Block block) {
|
||||
long genesisTotalSupply = daoStateService.getGenesisTotalSupply().value;
|
||||
long compensationIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.COMPENSATION);
|
||||
long reimbursementIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT);
|
||||
long totalAmountOfBurntBsq = daoStateService.getTotalAmountOfBurntBsq();
|
||||
// confiscated funds are still in the utxo set
|
||||
long sumUtxo = daoStateService.getUnspentTxOutputMap().values().stream().mapToLong(BaseTxOutput::getValue).sum();
|
||||
long sumBsq = genesisTotalSupply + compensationIssuance + reimbursementIssuance - totalAmountOfBurntBsq;
|
||||
|
||||
if (sumBsq != sumUtxo) {
|
||||
utxoMismatches.add(new UtxoMismatch(block.getHeight(), sumUtxo, sumBsq));
|
||||
// During syncing we do not call checkUtxos as its a bit slow (about 4 ms)
|
||||
if (parseBlockChainComplete) {
|
||||
checkUtxos(block);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,44 +214,47 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
|
||||
@Override
|
||||
public void onNewStateHashMessage(NewDaoStateHashMessage newStateHashMessage, Connection connection) {
|
||||
if (newStateHashMessage.getStateHash().getHeight() <= daoStateService.getChainHeight()) {
|
||||
processPeersDaoStateHash(newStateHashMessage.getStateHash(), connection.getPeersNodeAddressOptional(), true);
|
||||
// Called when receiving NewDaoStateHashMessages from peers after a new block
|
||||
DaoStateHash peersDaoStateHash = newStateHashMessage.getStateHash();
|
||||
if (peersDaoStateHash.getHeight() <= daoStateService.getChainHeight()) {
|
||||
putInPeersMapAndCheckForConflicts(getPeersAddress(connection.getPeersNodeAddressOptional()), peersDaoStateHash);
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPeersStateHashes(List<DaoStateHash> stateHashes, Optional<NodeAddress> peersNodeAddress) {
|
||||
// Called when receiving GetDaoStateHashesResponse from seed nodes
|
||||
processPeersDaoStateHashes(stateHashes, peersNodeAddress);
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
if (createSnapshotHandler != null) {
|
||||
createSnapshotHandler.run();
|
||||
// As we get called multiple times from hashes of diff. seed nodes we want to avoid to
|
||||
// call our handler multiple times.
|
||||
createSnapshotHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetStateHashRequest(Connection connection, GetDaoStateHashesRequest getStateHashRequest) {
|
||||
int fromHeight = getStateHashRequest.getHeight();
|
||||
List<DaoStateHash> daoStateHashes = daoStateBlockChain.stream()
|
||||
List<DaoStateHash> daoStateHashes = daoStateHashChain.stream()
|
||||
.filter(e -> e.getHeight() >= fromHeight)
|
||||
.map(DaoStateBlock::getMyStateHash)
|
||||
.collect(Collectors.toList());
|
||||
daoStateNetworkService.sendGetStateHashesResponse(connection, getStateHashRequest.getNonce(), daoStateHashes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPeersStateHashes(List<DaoStateHash> stateHashes, Optional<NodeAddress> peersNodeAddress) {
|
||||
AtomicBoolean hasChanged = new AtomicBoolean(false);
|
||||
|
||||
stateHashes.forEach(daoStateHash -> {
|
||||
boolean changed = processPeersDaoStateHash(daoStateHash, peersNodeAddress, false);
|
||||
if (changed) {
|
||||
hasChanged.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasChanged.get()) {
|
||||
listeners.forEach(Listener::onChangeAfterBatchProcessing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void createHashFromBlock(Block block) {
|
||||
updateHashChain(block);
|
||||
createDaoStateBlock(block);
|
||||
if (parseBlockChainComplete) {
|
||||
// We notify listeners only after batch processing to avoid performance issues at UI code
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestHashesFromGenesisBlockHeight(String peersAddress) {
|
||||
|
@ -256,6 +265,7 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
// We could get a reset from a reorg, so we clear all and start over from the genesis block.
|
||||
daoStateHashChain.clear();
|
||||
daoStateBlockChain.clear();
|
||||
daoStateBlockByHeight.clear();
|
||||
daoStateNetworkService.reset();
|
||||
|
||||
if (!persistedDaoStateHashChain.isEmpty()) {
|
||||
|
@ -263,7 +273,15 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
persistedDaoStateHashChain.size(), persistedDaoStateHashChain.getLast());
|
||||
}
|
||||
daoStateHashChain.addAll(persistedDaoStateHashChain);
|
||||
daoStateHashChain.forEach(e -> daoStateBlockChain.add(new DaoStateBlock(e)));
|
||||
daoStateHashChain.forEach(daoStateHash -> {
|
||||
DaoStateBlock daoStateBlock = new DaoStateBlock(daoStateHash);
|
||||
daoStateBlockChain.add(daoStateBlock);
|
||||
daoStateBlockByHeight.put(daoStateHash.getHeight(), daoStateBlock);
|
||||
});
|
||||
}
|
||||
|
||||
public void setCreateSnapshotHandler(Runnable handler) {
|
||||
createSnapshotHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
|
@ -284,7 +302,7 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void updateHashChain(Block block) {
|
||||
private Optional<DaoStateBlock> createDaoStateBlock(Block block) {
|
||||
long ts = System.currentTimeMillis();
|
||||
byte[] prevHash;
|
||||
int height = block.getHeight();
|
||||
|
@ -295,34 +313,45 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
} else {
|
||||
log.warn("DaoStateBlockchain is empty but we received the block which was not the genesis block. " +
|
||||
"We stop execution here.");
|
||||
return;
|
||||
daoStateBlockChainNotConnecting = true;
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
checkArgument(height == daoStateBlockChain.getLast().getHeight() + 1,
|
||||
"New block must be 1 block above previous block. height={}, " +
|
||||
"daoStateBlockChain.getLast().getHeight()={}",
|
||||
height, daoStateBlockChain.getLast().getHeight());
|
||||
prevHash = daoStateBlockChain.getLast().getHash();
|
||||
DaoStateBlock last = daoStateBlockChain.getLast();
|
||||
int heightOfLastBlock = last.getHeight();
|
||||
if (height == heightOfLastBlock + 1) {
|
||||
prevHash = last.getHash();
|
||||
} else {
|
||||
log.warn("New block must be 1 block above previous block. height={}, " +
|
||||
"daoStateBlockChain.getLast().getHeight()={}",
|
||||
height, heightOfLastBlock);
|
||||
daoStateBlockChainNotConnecting = true;
|
||||
listeners.forEach(Listener::onDaoStateHashesChanged);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] stateAsBytes = daoStateService.getSerializedStateForHashChain();
|
||||
// We include the prev. hash in our new hash so we can be sure that if one hash is matching all the past would
|
||||
// match as well.
|
||||
byte[] combined = ArrayUtils.addAll(prevHash, stateAsBytes);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(combined);
|
||||
|
||||
DaoStateHash myDaoStateHash = new DaoStateHash(height, hash, prevHash);
|
||||
DaoStateHash myDaoStateHash = new DaoStateHash(height, hash, true);
|
||||
DaoStateBlock daoStateBlock = new DaoStateBlock(myDaoStateHash);
|
||||
daoStateBlockChain.add(daoStateBlock);
|
||||
daoStateBlockByHeight.put(height, daoStateBlock);
|
||||
daoStateHashChain.add(myDaoStateHash);
|
||||
|
||||
// We only broadcast after parsing of blockchain is complete
|
||||
if (parseBlockChainComplete) {
|
||||
// We notify listeners only after batch processing to avoid performance issues at UI code
|
||||
listeners.forEach(Listener::onChangeAfterBatchProcessing);
|
||||
|
||||
// We delay broadcast to give peers enough time to have received the block.
|
||||
// Otherwise they would ignore our data if received block is in future to their local blockchain.
|
||||
int delayInSec = 5 + new Random().nextInt(10);
|
||||
if (Config.baseCurrencyNetwork().isRegtest()) {
|
||||
delayInSec = 1;
|
||||
}
|
||||
UserThread.runAfter(() -> daoStateNetworkService.broadcastMyStateHash(myDaoStateHash), delayInSec);
|
||||
}
|
||||
long duration = System.currentTimeMillis() - ts;
|
||||
|
@ -332,59 +361,93 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
duration);
|
||||
accumulatedDuration += duration;
|
||||
numCalls++;
|
||||
return Optional.of(daoStateBlock);
|
||||
}
|
||||
|
||||
private boolean processPeersDaoStateHash(DaoStateHash daoStateHash,
|
||||
Optional<NodeAddress> peersNodeAddress,
|
||||
boolean notifyListeners) {
|
||||
GcUtil.maybeReleaseMemory();
|
||||
private void processPeersDaoStateHashes(List<DaoStateHash> stateHashes, Optional<NodeAddress> peersNodeAddress) {
|
||||
boolean useDaoMonitor = preferences.isUseFullModeDaoMonitor();
|
||||
stateHashes.forEach(peersHash -> {
|
||||
Optional<DaoStateBlock> optionalDaoStateBlock;
|
||||
// If we do not add own hashes during initial parsing we fill the missing hashes from the peer and create
|
||||
// at the last block our own hash.
|
||||
int height = peersHash.getHeight();
|
||||
if (!useDaoMonitor &&
|
||||
!findDaoStateBlock(height).isPresent()) {
|
||||
if (daoStateService.getChainHeight() == height) {
|
||||
// At the most recent block we create our own hash
|
||||
optionalDaoStateBlock = daoStateService.getLastBlock()
|
||||
.map(this::createDaoStateBlock)
|
||||
.orElse(findDaoStateBlock(height));
|
||||
} else {
|
||||
// Otherwise we create a block from the peers daoStateHash
|
||||
DaoStateHash daoStateHash = new DaoStateHash(height, peersHash.getHash(), false);
|
||||
DaoStateBlock daoStateBlock = new DaoStateBlock(daoStateHash);
|
||||
daoStateBlockChain.add(daoStateBlock);
|
||||
daoStateBlockByHeight.put(height, daoStateBlock);
|
||||
daoStateHashChain.add(daoStateHash);
|
||||
optionalDaoStateBlock = Optional.of(daoStateBlock);
|
||||
}
|
||||
} else {
|
||||
optionalDaoStateBlock = findDaoStateBlock(height);
|
||||
}
|
||||
|
||||
AtomicBoolean changed = new AtomicBoolean(false);
|
||||
AtomicBoolean inConflictWithNonSeedNode = new AtomicBoolean(this.isInConflictWithNonSeedNode);
|
||||
AtomicBoolean inConflictWithSeedNode = new AtomicBoolean(this.isInConflictWithSeedNode);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
daoStateBlockChain.stream()
|
||||
.filter(e -> e.getHeight() == daoStateHash.getHeight()).findAny()
|
||||
.ifPresent(daoStateBlock -> {
|
||||
String peersNodeAddressAsString = peersNodeAddress.map(NodeAddress::getFullAddress)
|
||||
.orElseGet(() -> "Unknown peer " + new Random().nextInt(10000));
|
||||
daoStateBlock.putInPeersMap(peersNodeAddressAsString, daoStateHash);
|
||||
if (!daoStateBlock.getMyStateHash().hasEqualHash(daoStateHash)) {
|
||||
daoStateBlock.putInConflictMap(peersNodeAddressAsString, daoStateHash);
|
||||
if (seedNodeAddresses.contains(peersNodeAddressAsString)) {
|
||||
inConflictWithSeedNode.set(true);
|
||||
} else {
|
||||
inConflictWithNonSeedNode.set(true);
|
||||
}
|
||||
sb.append("We received a block hash from peer ")
|
||||
.append(peersNodeAddressAsString)
|
||||
.append(" which conflicts with our block hash.\n")
|
||||
.append("my daoStateHash=")
|
||||
.append(daoStateBlock.getMyStateHash())
|
||||
.append("\npeers daoStateHash=")
|
||||
.append(daoStateHash);
|
||||
}
|
||||
changed.set(true);
|
||||
});
|
||||
// In any case we add the peer to our peersMap and check for conflicts on the relevant daoStateBlock
|
||||
putInPeersMapAndCheckForConflicts(optionalDaoStateBlock, getPeersAddress(peersNodeAddress), peersHash);
|
||||
});
|
||||
}
|
||||
|
||||
this.isInConflictWithNonSeedNode = inConflictWithNonSeedNode.get();
|
||||
this.isInConflictWithSeedNode = inConflictWithSeedNode.get();
|
||||
private void putInPeersMapAndCheckForConflicts(String peersAddress, DaoStateHash peersHash) {
|
||||
putInPeersMapAndCheckForConflicts(findDaoStateBlock(peersHash.getHeight()), peersAddress, peersHash);
|
||||
}
|
||||
|
||||
String conflictMsg = sb.toString();
|
||||
if (!conflictMsg.isEmpty()) {
|
||||
if (this.isInConflictWithSeedNode)
|
||||
log.warn("Conflict with seed nodes: {}", conflictMsg);
|
||||
else if (this.isInConflictWithNonSeedNode)
|
||||
log.debug("Conflict with non-seed nodes: {}", conflictMsg);
|
||||
private void putInPeersMapAndCheckForConflicts(Optional<DaoStateBlock> optionalDaoStateBlock,
|
||||
String peersAddress,
|
||||
DaoStateHash peersHash) {
|
||||
optionalDaoStateBlock.ifPresent(daoStateBlock -> {
|
||||
daoStateBlock.putInPeersMap(peersAddress, peersHash);
|
||||
checkForHashConflicts(peersHash, peersAddress, daoStateBlock);
|
||||
});
|
||||
}
|
||||
|
||||
private void checkForHashConflicts(DaoStateHash peersDaoStateHash,
|
||||
String peersNodeAddress,
|
||||
DaoStateBlock daoStateBlock) {
|
||||
if (daoStateBlock.getMyStateHash().hasEqualHash(peersDaoStateHash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (notifyListeners && changed.get()) {
|
||||
listeners.forEach(Listener::onChangeAfterBatchProcessing);
|
||||
daoStateBlock.putInConflictMap(peersNodeAddress, peersDaoStateHash);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("We received a block hash from peer ")
|
||||
.append(peersNodeAddress)
|
||||
.append(" which conflicts with our block hash.\n")
|
||||
.append("my peersDaoStateHash=")
|
||||
.append(daoStateBlock.getMyStateHash())
|
||||
.append("\npeers peersDaoStateHash=")
|
||||
.append(peersDaoStateHash);
|
||||
String conflictMsg = stringBuilder.toString();
|
||||
|
||||
if (isSeedNode(peersNodeAddress)) {
|
||||
isInConflictWithSeedNode = true;
|
||||
log.warn("Conflict with seed nodes: {}", conflictMsg);
|
||||
} else {
|
||||
isInConflictWithNonSeedNode = true;
|
||||
log.debug("Conflict with non-seed nodes: {}", conflictMsg);
|
||||
}
|
||||
}
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
private void checkUtxos(Block block) {
|
||||
long genesisTotalSupply = daoStateService.getGenesisTotalSupply().value;
|
||||
long compensationIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.COMPENSATION);
|
||||
long reimbursementIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT);
|
||||
long totalAmountOfBurntBsq = daoStateService.getTotalAmountOfBurntBsq();
|
||||
// confiscated funds are still in the utxo set
|
||||
long sumUtxo = daoStateService.getUnspentTxOutputMap().values().stream().mapToLong(BaseTxOutput::getValue).sum();
|
||||
long sumBsq = genesisTotalSupply + compensationIssuance + reimbursementIssuance - totalAmountOfBurntBsq;
|
||||
|
||||
return changed.get();
|
||||
if (sumBsq != sumUtxo) {
|
||||
utxoMismatches.add(new UtxoMismatch(block.getHeight(), sumUtxo, sumBsq));
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyCheckpoints() {
|
||||
|
@ -431,4 +494,17 @@ public class DaoStateMonitoringService implements DaoSetupService, DaoStateListe
|
|||
log.error(t.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSeedNode(String peersNodeAddress) {
|
||||
return seedNodeAddresses.contains(peersNodeAddress);
|
||||
}
|
||||
|
||||
private String getPeersAddress(Optional<NodeAddress> peersNodeAddress) {
|
||||
return peersNodeAddress.map(NodeAddress::getFullAddress)
|
||||
.orElseGet(() -> "Unknown peer " + new Random().nextInt(10000));
|
||||
}
|
||||
|
||||
private Optional<DaoStateBlock> findDaoStateBlock(int height) {
|
||||
return Optional.ofNullable(daoStateBlockByHeight.get(height));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -275,7 +275,7 @@ public class ProposalStateMonitoringService implements DaoSetupService, DaoState
|
|||
}
|
||||
byte[] combined = ArrayUtils.addAll(prevHash, serializedProposals);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(combined);
|
||||
ProposalStateHash myProposalStateHash = new ProposalStateHash(blockHeight, hash, prevHash, proposals.size());
|
||||
ProposalStateHash myProposalStateHash = new ProposalStateHash(blockHeight, hash, proposals.size());
|
||||
ProposalStateBlock proposalStateBlock = new ProposalStateBlock(myProposalStateHash);
|
||||
proposalStateBlockChain.add(proposalStateBlock);
|
||||
proposalStateHashChain.add(myProposalStateHash);
|
||||
|
|
|
@ -29,8 +29,8 @@ public final class BlindVoteStateHash extends StateHash {
|
|||
@Getter
|
||||
private final int numBlindVotes;
|
||||
|
||||
public BlindVoteStateHash(int cycleStartBlockHeight, byte[] hash, byte[] prevHash, int numBlindVotes) {
|
||||
super(cycleStartBlockHeight, hash, prevHash);
|
||||
public BlindVoteStateHash(int cycleStartBlockHeight, byte[] hash, int numBlindVotes) {
|
||||
super(cycleStartBlockHeight, hash);
|
||||
this.numBlindVotes = numBlindVotes;
|
||||
}
|
||||
|
||||
|
@ -43,14 +43,12 @@ public final class BlindVoteStateHash extends StateHash {
|
|||
return protobuf.BlindVoteStateHash.newBuilder()
|
||||
.setHeight(height)
|
||||
.setHash(ByteString.copyFrom(hash))
|
||||
.setPrevHash(ByteString.copyFrom(prevHash))
|
||||
.setNumBlindVotes(numBlindVotes).build();
|
||||
}
|
||||
|
||||
public static BlindVoteStateHash fromProto(protobuf.BlindVoteStateHash proto) {
|
||||
return new BlindVoteStateHash(proto.getHeight(),
|
||||
proto.getHash().toByteArray(),
|
||||
proto.getPrevHash().toByteArray(),
|
||||
proto.getNumBlindVotes());
|
||||
}
|
||||
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
package bisq.core.dao.monitoring.model;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DaoStateBlock extends StateBlock<DaoStateHash> {
|
||||
public DaoStateBlock(DaoStateHash myDaoStateHash) {
|
||||
super(myDaoStateHash);
|
||||
}
|
||||
|
||||
public boolean isSelfCreated() {
|
||||
return myStateHash.isSelfCreated();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,17 @@ package bisq.core.dao.monitoring.model;
|
|||
import com.google.protobuf.ByteString;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class DaoStateHash extends StateHash {
|
||||
public DaoStateHash(int height, byte[] hash, byte[] prevHash) {
|
||||
super(height, hash, prevHash);
|
||||
// If we have built the hash by ourself opposed to that we got delivered the hash from seed nodes or resources
|
||||
private final boolean isSelfCreated;
|
||||
|
||||
public DaoStateHash(int height, byte[] hash, boolean isSelfCreated) {
|
||||
super(height, hash);
|
||||
this.isSelfCreated = isSelfCreated;
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,12 +44,18 @@ public final class DaoStateHash extends StateHash {
|
|||
return protobuf.DaoStateHash.newBuilder()
|
||||
.setHeight(height)
|
||||
.setHash(ByteString.copyFrom(hash))
|
||||
.setPrevHash(ByteString.copyFrom(prevHash)).build();
|
||||
.setIsSelfCreated(isSelfCreated)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DaoStateHash fromProto(protobuf.DaoStateHash proto) {
|
||||
return new DaoStateHash(proto.getHeight(),
|
||||
proto.getHash().toByteArray(),
|
||||
proto.getPrevHash().toByteArray());
|
||||
return new DaoStateHash(proto.getHeight(), proto.getHash().toByteArray(), proto.getIsSelfCreated());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DaoStateHash{" +
|
||||
"\r\n isSelfCreated=" + isSelfCreated +
|
||||
"\r\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ public final class ProposalStateHash extends StateHash {
|
|||
@Getter
|
||||
private final int numProposals;
|
||||
|
||||
public ProposalStateHash(int cycleStartBlockHeight, byte[] hash, byte[] prevHash, int numProposals) {
|
||||
super(cycleStartBlockHeight, hash, prevHash);
|
||||
public ProposalStateHash(int cycleStartBlockHeight, byte[] hash, int numProposals) {
|
||||
super(cycleStartBlockHeight, hash);
|
||||
this.numProposals = numProposals;
|
||||
}
|
||||
|
||||
|
@ -43,14 +43,12 @@ public final class ProposalStateHash extends StateHash {
|
|||
return protobuf.ProposalStateHash.newBuilder()
|
||||
.setHeight(height)
|
||||
.setHash(ByteString.copyFrom(hash))
|
||||
.setPrevHash(ByteString.copyFrom(prevHash))
|
||||
.setNumProposals(numProposals).build();
|
||||
}
|
||||
|
||||
public static ProposalStateHash fromProto(protobuf.ProposalStateHash proto) {
|
||||
return new ProposalStateHash(proto.getHeight(),
|
||||
proto.getHash().toByteArray(),
|
||||
proto.getPrevHash().toByteArray(),
|
||||
proto.getNumProposals());
|
||||
}
|
||||
|
||||
|
|
|
@ -56,10 +56,6 @@ public abstract class StateBlock<T extends StateHash> {
|
|||
return myStateHash.getHash();
|
||||
}
|
||||
|
||||
public byte[] getPrevHash() {
|
||||
return myStateHash.getPrevHash();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StateBlock{" +
|
||||
|
|
|
@ -40,13 +40,10 @@ import lombok.extern.slf4j.Slf4j;
|
|||
public abstract class StateHash implements PersistablePayload, NetworkPayload {
|
||||
protected final int height;
|
||||
protected final byte[] hash;
|
||||
// For first block the prevHash is an empty byte array
|
||||
protected final byte[] prevHash;
|
||||
|
||||
StateHash(int height, byte[] hash, byte[] prevHash) {
|
||||
StateHash(int height, byte[] hash) {
|
||||
this.height = height;
|
||||
this.hash = hash;
|
||||
this.prevHash = prevHash;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -67,7 +64,6 @@ public abstract class StateHash implements PersistablePayload, NetworkPayload {
|
|||
return "StateHash{" +
|
||||
"\n height=" + height +
|
||||
",\n hash=" + Utilities.bytesAsHexString(hash) +
|
||||
",\n prevHash=" + Utilities.bytesAsHexString(prevHash) +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,8 @@ public abstract class StateNetworkService<Msg extends NewStateHashMessage,
|
|||
|
||||
protected abstract Msg getNewStateHashMessage(StH myStateHash);
|
||||
|
||||
protected abstract Han getRequestStateHashesHandler(NodeAddress nodeAddress, RequestStateHashesHandler.Listener<Res> listener);
|
||||
protected abstract Han getRequestStateHashesHandler(NodeAddress nodeAddress,
|
||||
RequestStateHashesHandler.Listener<Res> listener);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -155,8 +156,7 @@ public abstract class StateNetworkService<Msg extends NewStateHashMessage,
|
|||
}
|
||||
|
||||
public void broadcastMyStateHash(StH myStateHash) {
|
||||
NewStateHashMessage newStateHashMessage = getNewStateHashMessage(myStateHash);
|
||||
broadcaster.broadcast(newStateHashMessage, networkNode.getNodeAddress());
|
||||
broadcaster.broadcast(getNewStateHashMessage(myStateHash), networkNode.getNodeAddress());
|
||||
}
|
||||
|
||||
public void requestHashes(int fromHeight, String peersAddress) {
|
||||
|
@ -167,6 +167,10 @@ public abstract class StateNetworkService<Msg extends NewStateHashMessage,
|
|||
requestStateHashHandlerMap.clear();
|
||||
}
|
||||
|
||||
public boolean isSeedNode(NodeAddress nodeAddress) {
|
||||
return peerManager.isSeedNode(nodeAddress);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listeners
|
||||
|
|
|
@ -135,10 +135,11 @@ public class FullNode extends BsqNode {
|
|||
protected void onParseBlockChainComplete() {
|
||||
super.onParseBlockChainComplete();
|
||||
|
||||
if (p2pNetworkReady)
|
||||
if (p2pNetworkReady) {
|
||||
addBlockHandler();
|
||||
else
|
||||
} else {
|
||||
log.info("onParseBlockChainComplete but P2P network is not ready yet.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -222,7 +222,7 @@ public class LiteNode extends BsqNode {
|
|||
|
||||
runDelayedBatchProcessing(new ArrayList<>(blockList),
|
||||
() -> {
|
||||
log.info("runDelayedBatchProcessing Parsing {} blocks took {} seconds.", blockList.size(),
|
||||
log.info("Parsing {} blocks took {} seconds.", blockList.size(),
|
||||
(System.currentTimeMillis() - ts) / 1000d);
|
||||
// We only request again if wallet is synced, otherwise we would get repeated calls we want to avoid.
|
||||
// We deal with that case at the setupWalletBestBlockListener method above.
|
||||
|
|
|
@ -200,6 +200,7 @@ public class LiteNodeNetworkService implements MessageListener, ConnectionListen
|
|||
|
||||
@Override
|
||||
public void onAllConnectionsLost() {
|
||||
log.info("onAllConnectionsLost");
|
||||
closeAllHandlers();
|
||||
stopRetryTimer();
|
||||
stopped = true;
|
||||
|
@ -208,6 +209,7 @@ public class LiteNodeNetworkService implements MessageListener, ConnectionListen
|
|||
|
||||
@Override
|
||||
public void onNewConnectionAfterAllConnectionsLost() {
|
||||
log.info("onNewConnectionAfterAllConnectionsLost");
|
||||
closeAllHandlers();
|
||||
stopped = false;
|
||||
tryWithNewSeedNode(lastRequestedBlockHeight);
|
||||
|
|
|
@ -24,7 +24,6 @@ import bisq.core.dao.state.DaoStateService;
|
|||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.util.GcUtil;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
|
@ -113,10 +112,12 @@ public class BlockParser {
|
|||
.ifPresent(tx -> daoStateService.onNewTxForLastBlock(block, tx)));
|
||||
|
||||
daoStateService.onParseBlockComplete(block);
|
||||
log.info("Parsing {} transactions at block height {} took {} ms", rawBlock.getRawTxs().size(),
|
||||
blockHeight, System.currentTimeMillis() - startTs);
|
||||
long duration = System.currentTimeMillis() - startTs;
|
||||
if (duration > 10) {
|
||||
log.info("Parsing {} transactions at block height {} took {} ms", rawBlock.getRawTxs().size(),
|
||||
blockHeight, duration);
|
||||
}
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
return block;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
|
||||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.core.dao.DaoSetupService;
|
||||
import bisq.core.dao.monitoring.DaoStateMonitoringService;
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
import bisq.core.dao.state.model.DaoState;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
import bisq.core.dao.state.storage.DaoStateStorageService;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.util.GcUtil;
|
||||
|
@ -49,13 +51,14 @@ import javax.annotation.Nullable;
|
|||
* SNAPSHOT_GRID old not less than 2 times the SNAPSHOT_GRID old.
|
||||
*/
|
||||
@Slf4j
|
||||
public class DaoStateSnapshotService {
|
||||
public class DaoStateSnapshotService implements DaoSetupService, DaoStateListener {
|
||||
private static final int SNAPSHOT_GRID = 20;
|
||||
|
||||
private final DaoStateService daoStateService;
|
||||
private final GenesisTxInfo genesisTxInfo;
|
||||
private final DaoStateStorageService daoStateStorageService;
|
||||
private final DaoStateMonitoringService daoStateMonitoringService;
|
||||
private final Preferences preferences;
|
||||
private final File storageDir;
|
||||
|
||||
private DaoState daoStateSnapshotCandidate;
|
||||
|
@ -64,7 +67,8 @@ public class DaoStateSnapshotService {
|
|||
@Setter
|
||||
@Nullable
|
||||
private Runnable daoRequiresRestartHandler;
|
||||
private boolean requestPersistenceCalled;
|
||||
private boolean readyForPersisting = true;
|
||||
private boolean isParseBlockChainComplete;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -76,21 +80,86 @@ public class DaoStateSnapshotService {
|
|||
GenesisTxInfo genesisTxInfo,
|
||||
DaoStateStorageService daoStateStorageService,
|
||||
DaoStateMonitoringService daoStateMonitoringService,
|
||||
Preferences preferences,
|
||||
@Named(Config.STORAGE_DIR) File storageDir) {
|
||||
this.daoStateService = daoStateService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
this.daoStateStorageService = daoStateStorageService;
|
||||
this.daoStateMonitoringService = daoStateMonitoringService;
|
||||
this.preferences = preferences;
|
||||
this.storageDir = storageDir;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoSetupService
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void addListeners() {
|
||||
daoStateService.addDaoStateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DaoStateListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We listen onDaoStateChanged to ensure the dao state has been processed from listener clients after parsing.
|
||||
// We need to listen during batch processing as well to write snapshots during that process.
|
||||
@Override
|
||||
public void onDaoStateChanged(Block block) {
|
||||
// If we have isUseDaoMonitor activated we apply the hash and snapshots at each new block during initial parsing.
|
||||
// Otherwise we do it only after the initial blockchain parsing is completed to not delay the parsing.
|
||||
// In that case we get the missing hashes from the seed nodes. At any new block we do the hash calculation
|
||||
// ourself and therefore get back confidence that our DAO state is in sync with the network.
|
||||
if (preferences.isUseFullModeDaoMonitor() || isParseBlockChainComplete) {
|
||||
// We need to execute first the daoStateMonitoringService.createHashFromBlock to get the hash created
|
||||
daoStateMonitoringService.createHashFromBlock(block);
|
||||
maybeCreateSnapshot(block);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onParseBlockChainComplete() {
|
||||
isParseBlockChainComplete = true;
|
||||
|
||||
// In case we have dao monitoring deactivated we create the snapshot after we are completed with parsing
|
||||
// and we got called back from daoStateMonitoringService once the hashes are created from peers data.
|
||||
if (!preferences.isUseFullModeDaoMonitor()) {
|
||||
// We register a callback handler once the daoStateMonitoringService has received the missing hashes from
|
||||
// the seed node and applied the latest hash. After that we are ready to make a snapshot and persist it.
|
||||
daoStateMonitoringService.setCreateSnapshotHandler(() -> {
|
||||
// As we did not have created any snapshots during initial parsing we create it now. We cannot use the past
|
||||
// snapshot height as we have not cloned a candidate (that would cause quite some delay during parsing).
|
||||
// The next snapshots will be created again according to the snapshot height grid (each 20 blocks).
|
||||
// This also comes with the improvement that the user does not need to load the past blocks back to the last
|
||||
// snapshot height. Though it comes also with the small risk that in case of re-orgs the user need to do
|
||||
// a resync in case the dao state would have been affected by that reorg.
|
||||
long ts = System.currentTimeMillis();
|
||||
// We do not keep a copy of the clone as we use it immediately for persistence.
|
||||
GcUtil.maybeReleaseMemory();
|
||||
log.info("Create snapshot at height {}", daoStateService.getChainHeight());
|
||||
daoStateStorageService.requestPersistence(daoStateService.getClone(),
|
||||
new LinkedList<>(daoStateMonitoringService.getDaoStateHashChain()),
|
||||
() -> {
|
||||
log.info("Persisted daoState after parsing completed at height {}. Took {} ms",
|
||||
daoStateService.getChainHeight(), System.currentTimeMillis() - ts);
|
||||
});
|
||||
GcUtil.maybeReleaseMemory();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We do not use DaoStateListener.onDaoStateChanged but let the DaoEventCoordinator call maybeCreateSnapshot to ensure the
|
||||
// correct order of execution.
|
||||
// We need to process during batch processing as well to write snapshots during that process.
|
||||
public void maybeCreateSnapshot(Block block) {
|
||||
int chainHeight = block.getHeight();
|
||||
|
@ -107,42 +176,68 @@ public class DaoStateSnapshotService {
|
|||
// We protect to get called while we are not completed with persisting the daoState. This can take about
|
||||
// 20 seconds and it is not expected that we get triggered another snapshot event in that period, but this
|
||||
// check guards that we would skip such calls..
|
||||
if (requestPersistenceCalled) {
|
||||
log.warn("We try to persist a daoState but the previous call has not completed yet. " +
|
||||
"We ignore that call and skip that snapshot. " +
|
||||
"Snapshot will be created at next snapshot height again. This is not to be expected with live " +
|
||||
"blockchain data.");
|
||||
if (!readyForPersisting) {
|
||||
if (preferences.isUseFullModeDaoMonitor()) {
|
||||
// In case we dont use isUseFullModeDaoMonitor we might called here too often as the parsing is much
|
||||
// faster than the persistence and we likely create only 1 snapshot during initial parsing, so
|
||||
// we log only if isUseFullModeDaoMonitor is true as then parsing is likely slower and we would
|
||||
// expect that we do a snapshot at each trigger block.
|
||||
log.info("We try to persist a daoState but the previous call has not completed yet. " +
|
||||
"We ignore that call and skip that snapshot. " +
|
||||
"Snapshot will be created at next snapshot height again. This is not to be expected with live " +
|
||||
"blockchain data.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
|
||||
// At trigger event we store the latest snapshotCandidate to disc
|
||||
long ts = System.currentTimeMillis();
|
||||
requestPersistenceCalled = true;
|
||||
daoStateStorageService.requestPersistence(daoStateSnapshotCandidate,
|
||||
daoStateHashChainSnapshotCandidate,
|
||||
() -> {
|
||||
log.info("Serializing snapshotCandidate for writing to Disc with height {} at height {} took {} ms",
|
||||
daoStateSnapshotCandidate != null ? daoStateSnapshotCandidate.getChainHeight() : "N/A",
|
||||
chainHeight,
|
||||
System.currentTimeMillis() - ts);
|
||||
|
||||
long ts2 = System.currentTimeMillis();
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
|
||||
// Now we clone and keep it in memory for the next trigger event
|
||||
daoStateSnapshotCandidate = daoStateService.getClone();
|
||||
daoStateHashChainSnapshotCandidate = new LinkedList<>(daoStateMonitoringService.getDaoStateHashChain());
|
||||
|
||||
log.info("Cloned new snapshotCandidate at height {} took {} ms", chainHeight, System.currentTimeMillis() - ts2);
|
||||
requestPersistenceCalled = false;
|
||||
GcUtil.maybeReleaseMemory();
|
||||
});
|
||||
if (daoStateSnapshotCandidate != null) {
|
||||
persist();
|
||||
} else {
|
||||
createClones();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void persist() {
|
||||
long ts = System.currentTimeMillis();
|
||||
readyForPersisting = false;
|
||||
daoStateStorageService.requestPersistence(daoStateSnapshotCandidate,
|
||||
daoStateHashChainSnapshotCandidate,
|
||||
() -> {
|
||||
log.info("Serializing snapshotCandidate for writing to Disc at chainHeight {} took {} ms.\n" +
|
||||
"daoStateSnapshotCandidate.height={};\n" +
|
||||
"daoStateHashChainSnapshotCandidate.height={}",
|
||||
daoStateService.getChainHeight(),
|
||||
System.currentTimeMillis() - ts,
|
||||
daoStateSnapshotCandidate != null ? daoStateSnapshotCandidate.getChainHeight() : "N/A",
|
||||
daoStateHashChainSnapshotCandidate != null && !daoStateHashChainSnapshotCandidate.isEmpty() ?
|
||||
daoStateHashChainSnapshotCandidate.getLast().getHeight() : "N/A");
|
||||
|
||||
createClones();
|
||||
readyForPersisting = true;
|
||||
});
|
||||
}
|
||||
|
||||
private void createClones() {
|
||||
long ts = System.currentTimeMillis();
|
||||
// Now we clone and keep it in memory for the next trigger event
|
||||
// We do not fit into the target grid of 20 blocks as we get called here once persistence is
|
||||
// done from the write thread (mapped back to user thread).
|
||||
// As we want to prevent to maintain 2 clones we prefer that strategy. If we would do the clone
|
||||
// after the persist call we would keep an additional copy in memory.
|
||||
daoStateSnapshotCandidate = daoStateService.getClone();
|
||||
daoStateHashChainSnapshotCandidate = new LinkedList<>(daoStateMonitoringService.getDaoStateHashChain());
|
||||
GcUtil.maybeReleaseMemory();
|
||||
|
||||
log.info("Cloned new snapshotCandidate at chainHeight {} took {} ms.\n" +
|
||||
"daoStateSnapshotCandidate.height={};\n" +
|
||||
"daoStateHashChainSnapshotCandidate.height={}",
|
||||
daoStateService.getChainHeight(), System.currentTimeMillis() - ts,
|
||||
daoStateSnapshotCandidate != null ? daoStateSnapshotCandidate.getChainHeight() : "N/A",
|
||||
daoStateHashChainSnapshotCandidate != null && !daoStateHashChainSnapshotCandidate.isEmpty() ?
|
||||
daoStateHashChainSnapshotCandidate.getLast().getHeight() : "N/A");
|
||||
}
|
||||
|
||||
public void applySnapshot(boolean fromReorg) {
|
||||
DaoState persistedBsqState = daoStateStorageService.getPersistedBsqState();
|
||||
LinkedList<DaoStateHash> persistedDaoStateHashChain = daoStateStorageService.getPersistedDaoStateHashChain();
|
||||
|
@ -156,6 +251,7 @@ public class DaoStateSnapshotService {
|
|||
chainHeightOfLastApplySnapshot = chainHeightOfPersisted;
|
||||
daoStateService.applySnapshot(persistedBsqState);
|
||||
daoStateMonitoringService.applySnapshot(persistedDaoStateHashChain);
|
||||
daoStateStorageService.pruneStore();
|
||||
} else {
|
||||
// The reorg might have been caused by the previous parsing which might contains a range of
|
||||
// blocks.
|
||||
|
|
|
@ -27,6 +27,7 @@ import bisq.network.p2p.storage.persistence.StoreService;
|
|||
import bisq.common.config.Config;
|
||||
import bisq.common.file.FileUtil;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.util.GcUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
@ -92,15 +93,20 @@ public class DaoStateStorageService extends StoreService<DaoStateStore> {
|
|||
new Thread(() -> {
|
||||
Thread.currentThread().setName("Serialize and write DaoState");
|
||||
persistenceManager.persistNow(() -> {
|
||||
// After we have written to disk we remove the the daoState in the store to avoid that it stays in
|
||||
// After we have written to disk we remove the daoState in the store to avoid that it stays in
|
||||
// memory there until the next persist call.
|
||||
store.setDaoState(null);
|
||||
|
||||
pruneStore();
|
||||
completeHandler.run();
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void pruneStore() {
|
||||
store.setDaoState(null);
|
||||
store.setDaoStateHashChain(null);
|
||||
GcUtil.maybeReleaseMemory();
|
||||
}
|
||||
|
||||
public DaoState getPersistedBsqState() {
|
||||
return store.getDaoState();
|
||||
}
|
||||
|
|
|
@ -82,7 +82,6 @@ public class TradeStatisticsManager {
|
|||
this.storageDir = storageDir;
|
||||
this.dumpStatistics = dumpStatistics;
|
||||
|
||||
|
||||
appendOnlyDataStoreService.addService(tradeStatistics3StorageService);
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
|
|||
|
||||
// We set serviceAddresses at request time. If user changes AutoConfirmSettings after request has started
|
||||
// it will have no impact on serviceAddresses and numRequiredSuccessResults.
|
||||
// Thought numRequiredConfirmations can be changed during request process and will be read from
|
||||
// Though numRequiredConfirmations can be changed during request process and will be read from
|
||||
// autoConfirmSettings at result parsing.
|
||||
List<String> serviceAddresses = autoConfirmSettings.getServiceAddresses();
|
||||
numRequiredSuccessResults = serviceAddresses.size();
|
||||
|
|
|
@ -800,6 +800,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
requestPersistence();
|
||||
}
|
||||
|
||||
public void setUseFullModeDaoMonitor(boolean value) {
|
||||
prefPayload.setUseFullModeDaoMonitor(value);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getter
|
||||
|
@ -1115,5 +1120,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
void setDenyApiTaker(boolean value);
|
||||
|
||||
void setNotifyOnPreRelease(boolean value);
|
||||
|
||||
void setUseFullModeDaoMonitor(boolean value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,6 +134,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||
private boolean showOffersMatchingMyAccounts;
|
||||
private boolean denyApiTaker;
|
||||
private boolean notifyOnPreRelease;
|
||||
private boolean useFullModeDaoMonitor;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
|
@ -201,7 +202,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||
.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods)
|
||||
.setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts)
|
||||
.setDenyApiTaker(denyApiTaker)
|
||||
.setNotifyOnPreRelease(notifyOnPreRelease);
|
||||
.setNotifyOnPreRelease(notifyOnPreRelease)
|
||||
.setUseFullModeDaoMonitor(useFullModeDaoMonitor);
|
||||
|
||||
Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory);
|
||||
Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage()));
|
||||
|
@ -299,7 +301,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||
proto.getHideNonAccountPaymentMethods(),
|
||||
proto.getShowOffersMatchingMyAccounts(),
|
||||
proto.getDenyApiTaker(),
|
||||
proto.getNotifyOnPreRelease()
|
||||
proto.getNotifyOnPreRelease(),
|
||||
proto.getUseFullModeDaoMonitor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -336,6 +336,7 @@ market.trades.showVolumeInUSD=Show volume in USD
|
|||
|
||||
offerbook.createOffer=Create offer
|
||||
offerbook.takeOffer=Take offer
|
||||
offerbook.takeOffer.createAccount=Create account and take offer
|
||||
offerbook.takeOfferToBuy=Take offer to buy {0}
|
||||
offerbook.takeOfferToSell=Take offer to sell {0}
|
||||
offerbook.trader=Trader
|
||||
|
@ -388,6 +389,7 @@ offerbook.createOfferToBuy.withFiat=Create new offer to buy {0} with {1}
|
|||
offerbook.createOfferToSell.forFiat=Create new offer to sell {0} for {1}
|
||||
offerbook.createOfferToBuy.withCrypto=Create new offer to sell {0} (buy {1})
|
||||
offerbook.createOfferToSell.forCrypto=Create new offer to buy {0} (sell {1})
|
||||
offerbook.createOfferDisabled.tooltip=You can only create one offer at a time
|
||||
|
||||
offerbook.takeOfferButton.tooltip=Take offer for {0}
|
||||
offerbook.yesCreateOffer=Yes, create offer
|
||||
|
@ -402,6 +404,7 @@ offerbook.warning.noTradingAccountForCurrency.headline=No payment account for se
|
|||
offerbook.warning.noTradingAccountForCurrency.msg=You don't have a payment account set up for the selected currency.\n\nWould you like to create an offer for another currency instead?
|
||||
offerbook.warning.noMatchingAccount.headline=No matching payment account.
|
||||
offerbook.warning.noMatchingAccount.msg=This offer uses a payment method you haven't set up yet. \n\nWould you like to set up a new payment account now?
|
||||
offerbook.warning.noMatchingBsqAccount.msg=This offer uses BSQ as a payment method you haven't set up yet. \n\nWould you like to automatically create an account now?
|
||||
|
||||
offerbook.warning.counterpartyTradeRestrictions=This offer cannot be taken due to counterparty trade restrictions
|
||||
|
||||
|
@ -440,6 +443,13 @@ offerbook.info.buyAtFixedPrice=You will buy at this fixed price.
|
|||
offerbook.info.sellAtFixedPrice=You will sell at this fixed price.
|
||||
offerbook.info.noArbitrationInUserLanguage=In case of a dispute, please note that arbitration for this offer will be handled in {0}. Language is currently set to {1}.
|
||||
offerbook.info.roundedFiatVolume=The amount was rounded to increase the privacy of your trade.
|
||||
offerbook.info.accountCreated.headline=Congratulations
|
||||
offerbook.info.accountCreated.message=You''ve just successfully created a BSQ payment account.\n\
|
||||
Your account can be found under Account > Altcoins Accounts > {0} and your BSQ wallet under DAO > BSQ Wallet.\n\n
|
||||
offerbook.info.accountCreated.tradeInstant=You've chosen to take a BSQ instant offer, so a BSQ instant payment account was created for you. \
|
||||
Be aware that instant trades are meant to be completed within 1 hour, \
|
||||
so you should be online and available for the next 1 hour.\n\n
|
||||
offerbook.info.accountCreated.takeOffer=You can now proceed to take this offer after closing this popup.
|
||||
|
||||
offerbook.bsqSwap.createOffer=Create Bsq swap offer
|
||||
|
||||
|
@ -481,6 +491,11 @@ createOffer.triggerPrice.tooltip=As protection against drastic price movements y
|
|||
createOffer.triggerPrice.invalid.tooLow=Value must be higher than {0}
|
||||
createOffer.triggerPrice.invalid.tooHigh=Value must be lower than {0}
|
||||
|
||||
createOffer.buyBsq.popupMessage=Trading fees are paid to fund the Bisq DAO. Fees can be paid in BSQ or BTC.\n\n\
|
||||
BSQ fees directly help fund Bisq's development, so Bisq encourages traders to use BSQ by offering a 50% discount on trading fees. \
|
||||
This discount varies as the BSQ/BTC rate fluctuates. To maintain the 50% discount target, trading fees are updated every cycle as necessary.\n\n\
|
||||
For more about fees, see [HYPERLINK:https://bisq.wiki/Trading_fees]. For more about trading BSQ, see [HYPERLINK:https://bisq.wiki/Trading_BSQ]. For more about the Bisq DAO, see [HYPERLINK:https://bisq.wiki/Introduction_to_the_DAO#The_Bisq_DAO].
|
||||
|
||||
# new entries
|
||||
createOffer.placeOfferButton=Review: Place offer to {0} bitcoin
|
||||
createOffer.createOfferFundWalletInfo.headline=Fund your offer
|
||||
|
@ -860,6 +875,7 @@ portfolio.pending.step3_seller.xmrTxHash=Transaction ID
|
|||
portfolio.pending.step3_seller.xmrTxKey=Transaction key
|
||||
portfolio.pending.step3_seller.buyersAccount=Buyers account data
|
||||
portfolio.pending.step3_seller.confirmReceipt=Confirm payment receipt
|
||||
portfolio.pending.step3_seller.showBsqWallet=Show payment in BSQ wallet
|
||||
portfolio.pending.step3_seller.buyerStartedPayment=The BTC buyer has started the {0} payment.\n{1}
|
||||
portfolio.pending.step3_seller.buyerStartedPayment.altcoin=Check for blockchain confirmations at your altcoin wallet or block explorer and confirm the payment when you have sufficient blockchain confirmations.
|
||||
portfolio.pending.step3_seller.buyerStartedPayment.fiat=Check at your trading account (e.g. bank account) and confirm when you have received the payment.
|
||||
|
@ -1335,6 +1351,18 @@ setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdow
|
|||
setting.preferences.dao.isDaoFullNode=Run Bisq as DAO full node
|
||||
setting.preferences.dao.activated=DAO activated
|
||||
setting.preferences.dao.activated.popup=The change will be applied after a restart
|
||||
|
||||
setting.preferences.dao.fullModeDaoMonitor=Full-mode DAO state monitoring
|
||||
setting.preferences.dao.fullModeDaoMonitor.popup=If full-mode DAO state monitoring is activated the DAO state \
|
||||
hashes are created during parsing the BSQ blocks. This comes with considerable performance costs at the initial DAO sync.\n\n\
|
||||
For users who are regularily using Bisq this should not be an issue as there are not many blocks pending for parsing, though for \
|
||||
users who only use Bisq casually creating the DAO state hashes for 100s or 1000s of blocks degrades heavily the user experience.\n\n\
|
||||
In case full-mode is deactivated (default) the missing DAO state hashes are requested from network nodes and \
|
||||
the DAO state hash based on the most recent block will be created by the user. As all hashes are connected by \
|
||||
reference to the previous hash a correct hash at the chain tip means that all past hashes are correct as well. The \
|
||||
main functionality of the DAO state monitoring - to detect if the local DAO state is out of sync with the rest of the network - \
|
||||
is therefore still fulfilled.
|
||||
|
||||
setting.preferences.dao.rpcUser=RPC username
|
||||
setting.preferences.dao.rpcPw=RPC password
|
||||
setting.preferences.dao.blockNotifyPort=Block notify port
|
||||
|
@ -2483,6 +2511,9 @@ dao.monitor.proposals=Proposals state
|
|||
dao.monitor.blindVotes=Blind votes state
|
||||
|
||||
dao.monitor.table.peers=Peers
|
||||
dao.monitor.table.hashCreator=Hash creator
|
||||
dao.monitor.table.hashCreator.self=Self
|
||||
dao.monitor.table.hashCreator.peer=Peer
|
||||
dao.monitor.table.conflicts=Conflicts
|
||||
dao.monitor.state=Status
|
||||
dao.monitor.requestAlHashes=Request all hashes
|
||||
|
@ -2516,6 +2547,8 @@ dao.monitor.isInConflictWithSeedNode=Your local data is not in consensus with at
|
|||
Please resync the DAO state.
|
||||
dao.monitor.isInConflictWithNonSeedNode=One of your peers is not in consensus with the network but your node \
|
||||
is in sync with the seed nodes.
|
||||
dao.monitor.isDaoStateBlockChainNotConnecting=Your DAO state chain is not connecting with the new data. \
|
||||
Please resync the DAO state.
|
||||
dao.monitor.daoStateInSync=Your local node is in consensus with the network
|
||||
|
||||
dao.monitor.blindVote.headline=Blind votes state
|
||||
|
|
|
@ -38,6 +38,7 @@ public class DaoStateSnapshotServiceTest {
|
|||
mock(GenesisTxInfo.class),
|
||||
mock(DaoStateStorageService.class),
|
||||
mock(DaoStateMonitoringService.class),
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
|
|
@ -175,6 +175,14 @@
|
|||
-fx-padding: 0 10 0 10;
|
||||
}
|
||||
|
||||
.tiny-button,
|
||||
.action-button.tiny-button {
|
||||
-fx-font-size: 0.769em;
|
||||
-fx-pref-height: 20;
|
||||
-fx-padding: 3 8 3 8;
|
||||
-fx-border-radius: 5;
|
||||
}
|
||||
|
||||
.text-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-underline: true;
|
||||
|
|
|
@ -41,11 +41,15 @@ public class HyperlinkWithIcon extends Hyperlink {
|
|||
this(text, AwesomeIcon.INFO_SIGN);
|
||||
}
|
||||
|
||||
public HyperlinkWithIcon(String text, AwesomeIcon awesomeIcon) {
|
||||
public HyperlinkWithIcon(String text, String fontSize) {
|
||||
this(text, AwesomeIcon.INFO_SIGN, fontSize);
|
||||
}
|
||||
|
||||
public HyperlinkWithIcon(String text, AwesomeIcon awesomeIcon, String fontSize) {
|
||||
super(text);
|
||||
|
||||
Label icon = new Label();
|
||||
AwesomeDude.setIcon(icon, awesomeIcon);
|
||||
AwesomeDude.setIcon(icon, awesomeIcon, fontSize);
|
||||
icon.setMinWidth(20);
|
||||
icon.setOpacity(0.7);
|
||||
icon.getStyleClass().addAll("hyperlink", "no-underline");
|
||||
|
@ -55,6 +59,10 @@ public class HyperlinkWithIcon extends Hyperlink {
|
|||
setIcon(icon);
|
||||
}
|
||||
|
||||
public HyperlinkWithIcon(String text, AwesomeIcon awesomeIcon) {
|
||||
this(text, awesomeIcon, "1.231em");
|
||||
}
|
||||
|
||||
public HyperlinkWithIcon(String text, GlyphIcons icon) {
|
||||
this(text, icon, null);
|
||||
}
|
||||
|
|
|
@ -34,8 +34,6 @@ import bisq.core.util.validation.InputValidator;
|
|||
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
@ -62,8 +60,13 @@ public class AdvancedCashForm extends PaymentMethodForm {
|
|||
return gridRow;
|
||||
}
|
||||
|
||||
public AdvancedCashForm(PaymentAccount paymentAccount, AccountAgeWitnessService accountAgeWitnessService, AdvancedCashValidator advancedCashValidator,
|
||||
InputValidator inputValidator, GridPane gridPane, int gridRow, CoinFormatter formatter) {
|
||||
public AdvancedCashForm(PaymentAccount paymentAccount,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
AdvancedCashValidator advancedCashValidator,
|
||||
InputValidator inputValidator,
|
||||
GridPane gridPane,
|
||||
int gridRow,
|
||||
CoinFormatter formatter) {
|
||||
super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter);
|
||||
this.advancedCashAccount = (AdvancedCashAccount) paymentAccount;
|
||||
this.advancedCashValidator = advancedCashValidator;
|
||||
|
@ -101,12 +104,7 @@ public class AdvancedCashForm extends PaymentMethodForm {
|
|||
|
||||
@Override
|
||||
protected void autoFillNameTextField() {
|
||||
if (useCustomAccountNameToggleButton != null && !useCustomAccountNameToggleButton.isSelected()) {
|
||||
String accountNr = accountNrInputTextField.getText();
|
||||
accountNr = StringUtils.abbreviate(accountNr, 9);
|
||||
String method = Res.get(paymentAccount.getPaymentMethod().getId());
|
||||
accountNameTextField.setText(method.concat(": ").concat(accountNr));
|
||||
}
|
||||
setAccountNameWithString(accountNrInputTextField.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -41,8 +41,6 @@ import bisq.core.util.validation.InputValidator;
|
|||
import bisq.common.UserThread;
|
||||
import bisq.common.util.Tuple3;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
|
@ -53,6 +51,7 @@ import javafx.geometry.Insets;
|
|||
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import static bisq.desktop.util.DisplayUtils.createAssetsAccountName;
|
||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
|
||||
import static bisq.desktop.util.FormBuilder.addLabelCheckBox;
|
||||
|
@ -167,12 +166,7 @@ public class AssetsForm extends PaymentMethodForm {
|
|||
@Override
|
||||
protected void autoFillNameTextField() {
|
||||
if (useCustomAccountNameToggleButton != null && !useCustomAccountNameToggleButton.isSelected()) {
|
||||
String currency = paymentAccount.getSingleTradeCurrency() != null ? paymentAccount.getSingleTradeCurrency().getCode() : "";
|
||||
if (currency != null) {
|
||||
String address = addressInputTextField.getText();
|
||||
address = StringUtils.abbreviate(address, 9);
|
||||
accountNameTextField.setText(currency.concat(": ").concat(address));
|
||||
}
|
||||
accountNameTextField.setText(createAssetsAccountName(paymentAccount, addressInputTextField.getText()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,6 @@ import bisq.core.util.validation.InputValidator;
|
|||
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
@ -100,12 +98,7 @@ public class CapitualForm extends PaymentMethodForm {
|
|||
|
||||
@Override
|
||||
protected void autoFillNameTextField() {
|
||||
if (useCustomAccountNameToggleButton != null && !useCustomAccountNameToggleButton.isSelected()) {
|
||||
String accountNr = accountNrInputTextField.getText();
|
||||
accountNr = StringUtils.abbreviate(accountNr, 9);
|
||||
String method = Res.get(paymentAccount.getPaymentMethod().getId());
|
||||
accountNameTextField.setText(method.concat(": ").concat(accountNr));
|
||||
}
|
||||
setAccountNameWithString(accountNrInputTextField.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,7 +25,6 @@ import bisq.desktop.util.validation.JapanBankAccountNameValidator;
|
|||
import bisq.desktop.util.validation.JapanBankAccountNumberValidator;
|
||||
import bisq.desktop.util.validation.JapanBankBranchCodeValidator;
|
||||
import bisq.desktop.util.validation.JapanBankBranchNameValidator;
|
||||
import bisq.desktop.util.validation.JapanBankTransferValidator;
|
||||
import bisq.desktop.util.validation.LengthValidator;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
|
@ -57,24 +56,17 @@ import javafx.util.StringConverter;
|
|||
import static bisq.desktop.util.FormBuilder.*;
|
||||
import static bisq.desktop.util.GUIUtil.getComboBoxButtonCell;
|
||||
|
||||
public class JapanBankTransferForm extends PaymentMethodForm
|
||||
{
|
||||
public class JapanBankTransferForm extends PaymentMethodForm {
|
||||
private final JapanBankAccount japanBankAccount;
|
||||
protected ComboBox<String> bankComboBox, bankAccountTypeComboBox;
|
||||
private InputTextField bankAccountNumberInputTextField;
|
||||
protected ComboBox<String> bankComboBox;
|
||||
|
||||
private JapanBankTransferValidator japanBankTransferValidator;
|
||||
private JapanBankBranchNameValidator japanBankBranchNameValidator;
|
||||
private JapanBankBranchCodeValidator japanBankBranchCodeValidator;
|
||||
private JapanBankAccountNameValidator japanBankAccountNameValidator;
|
||||
private JapanBankAccountNumberValidator japanBankAccountNumberValidator;
|
||||
private final JapanBankBranchNameValidator japanBankBranchNameValidator;
|
||||
private final JapanBankBranchCodeValidator japanBankBranchCodeValidator;
|
||||
private final JapanBankAccountNameValidator japanBankAccountNameValidator;
|
||||
private final JapanBankAccountNumberValidator japanBankAccountNumberValidator;
|
||||
|
||||
private LengthValidator lengthValidator;
|
||||
private RegexValidator regexValidator;
|
||||
|
||||
public static int addFormForBuyer(GridPane gridPane, int gridRow, // {{{
|
||||
PaymentAccountPayload paymentAccountPayload)
|
||||
{
|
||||
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
||||
PaymentAccountPayload paymentAccountPayload) {
|
||||
JapanBankAccountPayload japanBankAccount = ((JapanBankAccountPayload) paymentAccountPayload);
|
||||
|
||||
String bankText = japanBankAccount.getBankCode() + " " + japanBankAccount.getBankName();
|
||||
|
@ -90,30 +82,26 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, Res.get("payment.japan.recipient"), accountNameText);
|
||||
|
||||
return gridRow;
|
||||
} // }}}
|
||||
}
|
||||
|
||||
public JapanBankTransferForm(PaymentAccount paymentAccount,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
JapanBankTransferValidator japanBankTransferValidator,
|
||||
InputValidator inputValidator, GridPane gridPane,
|
||||
int gridRow, CoinFormatter formatter)
|
||||
{
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
InputValidator inputValidator, GridPane gridPane,
|
||||
int gridRow, CoinFormatter formatter) {
|
||||
super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter);
|
||||
this.japanBankAccount = (JapanBankAccount) paymentAccount;
|
||||
|
||||
this.japanBankTransferValidator = japanBankTransferValidator;
|
||||
this.japanBankBranchCodeValidator = new JapanBankBranchCodeValidator();
|
||||
this.japanBankAccountNumberValidator = new JapanBankAccountNumberValidator();
|
||||
|
||||
this.lengthValidator = new LengthValidator();
|
||||
this.regexValidator = new RegexValidator();
|
||||
LengthValidator lengthValidator = new LengthValidator();
|
||||
RegexValidator regexValidator = new RegexValidator();
|
||||
this.japanBankBranchNameValidator = new JapanBankBranchNameValidator(lengthValidator, regexValidator);
|
||||
this.japanBankAccountNameValidator = new JapanBankAccountNameValidator(lengthValidator, regexValidator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormForDisplayAccount() // {{{
|
||||
{
|
||||
public void addFormForDisplayAccount() {
|
||||
gridRowFrom = gridRow;
|
||||
|
||||
addTopLabelTextField(gridPane, ++gridRow, Res.get("payment.account.name"),
|
||||
|
@ -128,37 +116,36 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
addBankAccountTypeDisplay();
|
||||
|
||||
addLimitations(true);
|
||||
} // }}}
|
||||
private void addBankDisplay() // {{{
|
||||
{
|
||||
}
|
||||
|
||||
private void addBankDisplay() {
|
||||
String bankText = japanBankAccount.getBankCode() + " " + japanBankAccount.getBankName();
|
||||
TextField bankTextField = addCompactTopLabelTextField(gridPane, ++gridRow, JapanBankData.getString("bank"), bankText).second;
|
||||
bankTextField.setEditable(false);
|
||||
} // }}}
|
||||
private void addBankBranchDisplay() // {{{
|
||||
{
|
||||
}
|
||||
|
||||
private void addBankBranchDisplay() {
|
||||
String branchText = japanBankAccount.getBankBranchCode() + " " + japanBankAccount.getBankBranchName();
|
||||
TextField branchTextField = addCompactTopLabelTextField(gridPane, ++gridRow, JapanBankData.getString("branch"), branchText).second;
|
||||
branchTextField.setEditable(false);
|
||||
} // }}}
|
||||
private void addBankAccountDisplay() // {{{
|
||||
{
|
||||
}
|
||||
|
||||
private void addBankAccountDisplay() {
|
||||
String accountText = japanBankAccount.getBankAccountNumber() + " " + japanBankAccount.getBankAccountName();
|
||||
TextField accountTextField = addCompactTopLabelTextField(gridPane, ++gridRow, JapanBankData.getString("account"), accountText).second;
|
||||
accountTextField.setEditable(false);
|
||||
} // }}}
|
||||
private void addBankAccountTypeDisplay() // {{{
|
||||
{
|
||||
}
|
||||
|
||||
private void addBankAccountTypeDisplay() {
|
||||
TradeCurrency singleTradeCurrency = japanBankAccount.getSingleTradeCurrency();
|
||||
String currency = singleTradeCurrency != null ? singleTradeCurrency.getNameAndCode() : "null";
|
||||
String accountTypeText = currency + " " + japanBankAccount.getBankAccountType();
|
||||
TextField accountTypeTextField = addCompactTopLabelTextField(gridPane, ++gridRow, JapanBankData.getString("account.type"), accountTypeText).second;
|
||||
accountTypeTextField.setEditable(false);
|
||||
} // }}}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormForAddAccount() // {{{
|
||||
{
|
||||
public void addFormForAddAccount() {
|
||||
gridRowFrom = gridRow;
|
||||
|
||||
addBankInput();
|
||||
|
@ -168,9 +155,9 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
|
||||
addLimitations(false);
|
||||
addAccountNameTextFieldWithAutoFillToggleButton();
|
||||
} // }}}
|
||||
private void addBankInput() // {{{
|
||||
{
|
||||
}
|
||||
|
||||
private void addBankInput() {
|
||||
gridRow++;
|
||||
|
||||
Tuple4<Label, TextField, Label, ComboBox<String>> tuple4 = addTopLabelTextFieldAutocompleteComboBox(gridPane, gridRow, JapanBankData.getString("bank.code"), JapanBankData.getString("bank.name"), 10);
|
||||
|
@ -185,14 +172,13 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
bankComboBox = tuple4.fourth;
|
||||
bankComboBox.setPromptText(JapanBankData.getString("bank.select"));
|
||||
bankComboBox.setButtonCell(getComboBoxButtonCell(JapanBankData.getString("bank.name"), bankComboBox));
|
||||
bankComboBox.getEditor().focusedProperty().addListener(observable -> {
|
||||
bankComboBox.setPromptText("");
|
||||
});
|
||||
bankComboBox.setConverter(new StringConverter<String>() {
|
||||
bankComboBox.getEditor().focusedProperty().addListener(observable -> bankComboBox.setPromptText(""));
|
||||
bankComboBox.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(String bank) {
|
||||
return bank != null ? bank : "";
|
||||
}
|
||||
|
||||
public String fromString(String s) {
|
||||
return s != null ? s : "";
|
||||
}
|
||||
|
@ -208,8 +194,7 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
|
||||
// parse first 4 characters as bank code
|
||||
String bankCode = StringUtils.substring(bank, 0, 4);
|
||||
if (bankCode != null)
|
||||
{
|
||||
if (bankCode != null) {
|
||||
// set bank code field to this value
|
||||
bankCodeField.setText(bankCode);
|
||||
// save to payload
|
||||
|
@ -217,26 +202,20 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
|
||||
// parse remainder as bank name
|
||||
String bankNameFull = StringUtils.substringAfter(bank, JapanBankData.SPACE);
|
||||
if (bankNameFull != null)
|
||||
{
|
||||
// parse beginning as Japanese bank name
|
||||
String bankNameJa = StringUtils.substringBefore(bankNameFull, JapanBankData.SPACE);
|
||||
if (bankNameJa != null)
|
||||
{
|
||||
// set bank name field to this value
|
||||
bankComboBox.getEditor().setText(bankNameJa);
|
||||
// save to payload
|
||||
japanBankAccount.setBankName(bankNameJa);
|
||||
}
|
||||
}
|
||||
// parse beginning as Japanese bank name
|
||||
String bankNameJa = StringUtils.substringBefore(bankNameFull, JapanBankData.SPACE);
|
||||
// set bank name field to this value
|
||||
bankComboBox.getEditor().setText(bankNameJa);
|
||||
// save to payload
|
||||
japanBankAccount.setBankName(bankNameJa);
|
||||
}
|
||||
|
||||
|
||||
updateFromInputs();
|
||||
});
|
||||
} // }}}
|
||||
private void addBankBranchInput() // {{{
|
||||
{
|
||||
}
|
||||
|
||||
private void addBankBranchInput() {
|
||||
gridRow++;
|
||||
Tuple2<InputTextField, InputTextField> tuple2 = addInputTextFieldInputTextField(gridPane, gridRow, JapanBankData.getString("branch.code"), JapanBankData.getString("branch.name"));
|
||||
|
||||
|
@ -259,14 +238,14 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
japanBankAccount.setBankBranchName(newValue);
|
||||
updateFromInputs();
|
||||
});
|
||||
} // }}}
|
||||
private void addBankAccountInput() // {{{
|
||||
{
|
||||
}
|
||||
|
||||
private void addBankAccountInput() {
|
||||
gridRow++;
|
||||
Tuple2<InputTextField, InputTextField> tuple2 = addInputTextFieldInputTextField(gridPane, gridRow, JapanBankData.getString("account.number"), JapanBankData.getString("account.name"));
|
||||
|
||||
// account number
|
||||
bankAccountNumberInputTextField = tuple2.first;
|
||||
InputTextField bankAccountNumberInputTextField = tuple2.first;
|
||||
bankAccountNumberInputTextField.setValidator(japanBankAccountNumberValidator);
|
||||
bankAccountNumberInputTextField.setPrefWidth(200);
|
||||
bankAccountNumberInputTextField.setMaxWidth(200);
|
||||
|
@ -284,9 +263,9 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
japanBankAccount.setBankAccountName(newValue);
|
||||
updateFromInputs();
|
||||
});
|
||||
} // }}}
|
||||
private void addBankAccountTypeInput() // {{{
|
||||
{
|
||||
}
|
||||
|
||||
private void addBankAccountTypeInput() {
|
||||
// account currency
|
||||
gridRow++;
|
||||
|
||||
|
@ -299,13 +278,13 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
|
||||
ToggleGroup toggleGroup = new ToggleGroup();
|
||||
Tuple3<Label, RadioButton, RadioButton> tuple3 =
|
||||
addTopLabelRadioButtonRadioButton(
|
||||
gridPane, gridRow, toggleGroup,
|
||||
JapanBankData.getString("account.type.select"),
|
||||
JapanBankData.getString("account.type.futsu"),
|
||||
JapanBankData.getString("account.type.touza"),
|
||||
0
|
||||
);
|
||||
addTopLabelRadioButtonRadioButton(
|
||||
gridPane, gridRow, toggleGroup,
|
||||
JapanBankData.getString("account.type.select"),
|
||||
JapanBankData.getString("account.type.futsu"),
|
||||
JapanBankData.getString("account.type.touza"),
|
||||
0
|
||||
);
|
||||
|
||||
toggleGroup.getToggles().get(0).setSelected(true);
|
||||
japanBankAccount.setBankAccountType(JapanBankData.getString("account.type.futsu.ja"));
|
||||
|
@ -314,66 +293,60 @@ public class JapanBankTransferForm extends PaymentMethodForm
|
|||
RadioButton touza = tuple3.third;
|
||||
|
||||
toggleGroup.selectedToggleProperty().addListener
|
||||
(
|
||||
(ov, oldValue, newValue) ->
|
||||
{
|
||||
if (futsu.isSelected())
|
||||
japanBankAccount.setBankAccountType(JapanBankData.getString("account.type.futsu.ja"));
|
||||
if (touza.isSelected())
|
||||
japanBankAccount.setBankAccountType(JapanBankData.getString("account.type.touza.ja"));
|
||||
}
|
||||
);
|
||||
} // }}}
|
||||
(
|
||||
(ov, oldValue, newValue) ->
|
||||
{
|
||||
if (futsu.isSelected())
|
||||
japanBankAccount.setBankAccountType(JapanBankData.getString("account.type.futsu.ja"));
|
||||
if (touza.isSelected())
|
||||
japanBankAccount.setBankAccountType(JapanBankData.getString("account.type.touza.ja"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromInputs() // {{{
|
||||
{
|
||||
public void updateFromInputs() {
|
||||
System.out.println("JapanBankTransferForm: updateFromInputs()");
|
||||
System.out.println("bankName: "+japanBankAccount.getBankName());
|
||||
System.out.println("bankCode: "+japanBankAccount.getBankCode());
|
||||
System.out.println("bankBranchName: "+japanBankAccount.getBankBranchName());
|
||||
System.out.println("bankBranchCode: "+japanBankAccount.getBankBranchCode());
|
||||
System.out.println("bankAccountType: "+japanBankAccount.getBankAccountType());
|
||||
System.out.println("bankAccountName: "+japanBankAccount.getBankAccountName());
|
||||
System.out.println("bankAccountNumber: "+japanBankAccount.getBankAccountNumber());
|
||||
System.out.println("bankName: " + japanBankAccount.getBankName());
|
||||
System.out.println("bankCode: " + japanBankAccount.getBankCode());
|
||||
System.out.println("bankBranchName: " + japanBankAccount.getBankBranchName());
|
||||
System.out.println("bankBranchCode: " + japanBankAccount.getBankBranchCode());
|
||||
System.out.println("bankAccountType: " + japanBankAccount.getBankAccountType());
|
||||
System.out.println("bankAccountName: " + japanBankAccount.getBankAccountName());
|
||||
System.out.println("bankAccountNumber: " + japanBankAccount.getBankAccountNumber());
|
||||
super.updateFromInputs();
|
||||
} // }}}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void autoFillNameTextField() // {{{
|
||||
{
|
||||
if (useCustomAccountNameToggleButton != null && !useCustomAccountNameToggleButton.isSelected())
|
||||
{
|
||||
protected void autoFillNameTextField() {
|
||||
if (useCustomAccountNameToggleButton != null && !useCustomAccountNameToggleButton.isSelected()) {
|
||||
accountNameTextField.setText(
|
||||
Res.get(paymentAccount.getPaymentMethod().getId())
|
||||
.concat(": ")
|
||||
.concat(japanBankAccount.getBankName())
|
||||
.concat(" ")
|
||||
.concat(japanBankAccount.getBankBranchName())
|
||||
.concat(" ")
|
||||
.concat(japanBankAccount.getBankAccountNumber())
|
||||
.concat(" ")
|
||||
.concat(japanBankAccount.getBankAccountName())
|
||||
.concat(": ")
|
||||
.concat(japanBankAccount.getBankName())
|
||||
.concat(" ")
|
||||
.concat(japanBankAccount.getBankBranchName())
|
||||
.concat(" ")
|
||||
.concat(japanBankAccount.getBankAccountNumber())
|
||||
.concat(" ")
|
||||
.concat(japanBankAccount.getBankAccountName())
|
||||
);
|
||||
}
|
||||
} // }}}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAllInputsValid() // {{{
|
||||
{
|
||||
public void updateAllInputsValid() {
|
||||
boolean result =
|
||||
(
|
||||
isAccountNameValid() &&
|
||||
inputValidator.validate(japanBankAccount.getBankCode()).isValid &&
|
||||
inputValidator.validate(japanBankAccount.getBankName()).isValid &&
|
||||
japanBankBranchCodeValidator.validate(japanBankAccount.getBankBranchCode()).isValid &&
|
||||
japanBankBranchNameValidator.validate(japanBankAccount.getBankBranchName()).isValid &&
|
||||
japanBankAccountNumberValidator.validate(japanBankAccount.getBankAccountNumber()).isValid &&
|
||||
japanBankAccountNameValidator.validate(japanBankAccount.getBankAccountName()).isValid &&
|
||||
inputValidator.validate(japanBankAccount.getBankAccountType()).isValid
|
||||
);
|
||||
(
|
||||
isAccountNameValid() &&
|
||||
inputValidator.validate(japanBankAccount.getBankCode()).isValid &&
|
||||
inputValidator.validate(japanBankAccount.getBankName()).isValid &&
|
||||
japanBankBranchCodeValidator.validate(japanBankAccount.getBankBranchCode()).isValid &&
|
||||
japanBankBranchNameValidator.validate(japanBankAccount.getBankBranchName()).isValid &&
|
||||
japanBankAccountNumberValidator.validate(japanBankAccount.getBankAccountNumber()).isValid &&
|
||||
japanBankAccountNameValidator.validate(japanBankAccount.getBankAccountName()).isValid &&
|
||||
inputValidator.validate(japanBankAccount.getBankAccountType()).isValid
|
||||
);
|
||||
allInputsValid.set(result);
|
||||
} // }}}
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4:sw=4:expandtab:foldmethod=marker:nowrap:
|
||||
|
|
|
@ -37,8 +37,6 @@ import bisq.core.util.validation.InputValidator;
|
|||
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
@ -172,11 +170,7 @@ public class MoneyGramForm extends PaymentMethodForm {
|
|||
|
||||
@Override
|
||||
protected void autoFillNameTextField() {
|
||||
if (useCustomAccountNameToggleButton != null && !useCustomAccountNameToggleButton.isSelected()) {
|
||||
accountNameTextField.setText(Res.get(paymentAccount.getPaymentMethod().getId())
|
||||
.concat(": ")
|
||||
.concat(StringUtils.abbreviate(holderNameInputTextField.getText(), 9)));
|
||||
}
|
||||
setAccountNameWithString(holderNameInputTextField.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -73,6 +73,7 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.desktop.util.DisplayUtils.createAccountName;
|
||||
import static bisq.desktop.util.FormBuilder.*;
|
||||
|
||||
@Slf4j
|
||||
|
@ -287,10 +288,8 @@ public abstract class PaymentMethodForm {
|
|||
|
||||
void setAccountNameWithString(String name) {
|
||||
if (useCustomAccountNameToggleButton != null && !useCustomAccountNameToggleButton.isSelected()) {
|
||||
name = name.trim();
|
||||
name = StringUtils.abbreviate(name, 9);
|
||||
String method = Res.get(paymentAccount.getPaymentMethod().getId());
|
||||
accountNameTextField.setText(method.concat(": ").concat(name));
|
||||
String accountName = createAccountName(paymentAccount.getPaymentMethod().getId(), name);
|
||||
accountNameTextField.setText(accountName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -416,7 +416,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onChangeAfterBatchProcessing() {
|
||||
public void onDaoStateHashesChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,9 +20,7 @@ package bisq.desktop.main.account.content.altcoinaccounts;
|
|||
import bisq.desktop.common.model.ActivatableDataModel;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.locale.CryptoCurrency;
|
||||
import bisq.core.locale.FiatCurrency;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.payment.AssetAccount;
|
||||
|
@ -44,7 +42,6 @@ import javafx.collections.SetChangeListener;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class AltCoinAccountsDataModel extends ActivatableDataModel {
|
||||
|
@ -53,7 +50,6 @@ class AltCoinAccountsDataModel extends ActivatableDataModel {
|
|||
private final Preferences preferences;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
private final TradeManager tradeManager;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
final ObservableList<PaymentAccount> paymentAccounts = FXCollections.observableArrayList();
|
||||
private final SetChangeListener<PaymentAccount> setChangeListener;
|
||||
private final String accountsFileName = "AltcoinPaymentAccounts";
|
||||
|
@ -65,14 +61,12 @@ class AltCoinAccountsDataModel extends ActivatableDataModel {
|
|||
Preferences preferences,
|
||||
OpenOfferManager openOfferManager,
|
||||
TradeManager tradeManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
PersistenceProtoResolver persistenceProtoResolver,
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler) {
|
||||
this.user = user;
|
||||
this.preferences = preferences;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.tradeManager = tradeManager;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.persistenceProtoResolver = persistenceProtoResolver;
|
||||
this.corruptedStorageFileHandler = corruptedStorageFileHandler;
|
||||
setChangeListener = change -> fillAndSortPaymentAccounts();
|
||||
|
@ -105,25 +99,8 @@ class AltCoinAccountsDataModel extends ActivatableDataModel {
|
|||
|
||||
public void onSaveNewAccount(PaymentAccount paymentAccount) {
|
||||
TradeCurrency singleTradeCurrency = paymentAccount.getSingleTradeCurrency();
|
||||
List<TradeCurrency> tradeCurrencies = paymentAccount.getTradeCurrencies();
|
||||
if (singleTradeCurrency != null) {
|
||||
if (singleTradeCurrency instanceof FiatCurrency)
|
||||
preferences.addFiatCurrency((FiatCurrency) singleTradeCurrency);
|
||||
else
|
||||
preferences.addCryptoCurrency((CryptoCurrency) singleTradeCurrency);
|
||||
} else if (tradeCurrencies != null && !tradeCurrencies.isEmpty()) {
|
||||
tradeCurrencies.forEach(tradeCurrency -> {
|
||||
if (tradeCurrency instanceof FiatCurrency)
|
||||
preferences.addFiatCurrency((FiatCurrency) tradeCurrency);
|
||||
else
|
||||
preferences.addCryptoCurrency((CryptoCurrency) tradeCurrency);
|
||||
});
|
||||
}
|
||||
|
||||
preferences.addCryptoCurrency((CryptoCurrency) singleTradeCurrency);
|
||||
user.addPaymentAccount(paymentAccount);
|
||||
|
||||
if (!(paymentAccount instanceof AssetAccount))
|
||||
accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload());
|
||||
}
|
||||
|
||||
public boolean onDeleteAccount(PaymentAccount paymentAccount) {
|
||||
|
@ -147,9 +124,9 @@ class AltCoinAccountsDataModel extends ActivatableDataModel {
|
|||
|
||||
public void exportAccounts(Stage stage) {
|
||||
if (user.getPaymentAccounts() != null) {
|
||||
ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream()
|
||||
ArrayList<PaymentAccount> accounts = user.getPaymentAccounts().stream()
|
||||
.filter(paymentAccount -> paymentAccount instanceof AssetAccount)
|
||||
.collect(Collectors.toList()));
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import bisq.desktop.common.model.ActivatableDataModel;
|
|||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.locale.CryptoCurrency;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.FiatCurrency;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
|
@ -109,22 +108,14 @@ class FiatAccountsDataModel extends ActivatableDataModel {
|
|||
TradeCurrency singleTradeCurrency = paymentAccount.getSingleTradeCurrency();
|
||||
List<TradeCurrency> tradeCurrencies = paymentAccount.getTradeCurrencies();
|
||||
if (singleTradeCurrency != null) {
|
||||
if (singleTradeCurrency instanceof FiatCurrency)
|
||||
preferences.addFiatCurrency((FiatCurrency) singleTradeCurrency);
|
||||
else
|
||||
preferences.addCryptoCurrency((CryptoCurrency) singleTradeCurrency);
|
||||
preferences.addFiatCurrency((FiatCurrency) singleTradeCurrency);
|
||||
} else if (tradeCurrencies != null && !tradeCurrencies.isEmpty()) {
|
||||
if (tradeCurrencies.contains(CurrencyUtil.getDefaultTradeCurrency()))
|
||||
paymentAccount.setSelectedTradeCurrency(CurrencyUtil.getDefaultTradeCurrency());
|
||||
else
|
||||
paymentAccount.setSelectedTradeCurrency(tradeCurrencies.get(0));
|
||||
|
||||
tradeCurrencies.forEach(tradeCurrency -> {
|
||||
if (tradeCurrency instanceof FiatCurrency)
|
||||
preferences.addFiatCurrency((FiatCurrency) tradeCurrency);
|
||||
else
|
||||
preferences.addCryptoCurrency((CryptoCurrency) tradeCurrency);
|
||||
});
|
||||
tradeCurrencies.forEach(tradeCurrency -> preferences.addFiatCurrency((FiatCurrency) tradeCurrency));
|
||||
}
|
||||
|
||||
user.addPaymentAccount(paymentAccount);
|
||||
|
@ -154,9 +145,9 @@ class FiatAccountsDataModel extends ActivatableDataModel {
|
|||
|
||||
public void exportAccounts(Stage stage) {
|
||||
if (user.getPaymentAccounts() != null) {
|
||||
ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream()
|
||||
ArrayList<PaymentAccount> accounts = user.getPaymentAccounts().stream()
|
||||
.filter(paymentAccount -> !(paymentAccount instanceof AssetAccount))
|
||||
.collect(Collectors.toList()));
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -520,7 +520,7 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
|||
case PaymentMethod.SPECIFIC_BANKS_ID:
|
||||
return new SpecificBankForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter);
|
||||
case PaymentMethod.JAPAN_BANK_ID:
|
||||
return new JapanBankTransferForm(paymentAccount, accountAgeWitnessService, japanBankTransferValidator, inputValidator, root, gridRow, formatter);
|
||||
return new JapanBankTransferForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter);
|
||||
case PaymentMethod.AUSTRALIA_PAYID_ID:
|
||||
return new AustraliaPayidForm(paymentAccount, accountAgeWitnessService, australiapayidValidator, inputValidator, root, gridRow, formatter);
|
||||
case PaymentMethod.ALI_PAY_ID:
|
||||
|
|
|
@ -36,10 +36,9 @@ import lombok.extern.slf4j.Slf4j;
|
|||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public abstract class StateBlockListItem<StH extends StateHash, StB extends StateBlock<StH>> {
|
||||
private final StateBlock<StH> stateBlock;
|
||||
protected final StateBlock<StH> stateBlock;
|
||||
private final Supplier<String> height;
|
||||
private final String hash;
|
||||
private final String prevHash;
|
||||
private final String numNetworkMessages;
|
||||
private final String numMisMatches;
|
||||
private final boolean isInSync;
|
||||
|
@ -58,7 +57,6 @@ public abstract class StateBlockListItem<StH extends StateHash, StB extends Stat
|
|||
Res.get("dao.monitor.table.cycleBlockHeight", cycleIndexSupplier.getAsInt() + 1,
|
||||
String.valueOf(stateBlock.getHeight())))::get;
|
||||
hash = Utilities.bytesAsHexString(stateBlock.getHash());
|
||||
prevHash = stateBlock.getPrevHash().length > 0 ? Utilities.bytesAsHexString(stateBlock.getPrevHash()) : "-";
|
||||
numNetworkMessages = String.valueOf(stateBlock.getPeersMap().size());
|
||||
int size = stateBlock.getInConflictMap().size();
|
||||
numMisMatches = String.valueOf(size);
|
||||
|
|
|
@ -38,7 +38,6 @@ public abstract class StateInConflictListItem<T extends StateHash> {
|
|||
private final String peerAddressString;
|
||||
private final String height;
|
||||
private final String hash;
|
||||
private final String prevHash;
|
||||
private final T stateHash;
|
||||
|
||||
protected StateInConflictListItem(String peerAddress, T stateHash, int cycleIndex,
|
||||
|
@ -52,7 +51,5 @@ public abstract class StateInConflictListItem<T extends StateHash> {
|
|||
cycleIndex + 1,
|
||||
String.valueOf(stateHash.getHeight()));
|
||||
hash = Utilities.bytesAsHexString(stateHash.getHash());
|
||||
prevHash = stateHash.getPrevHash().length > 0 ?
|
||||
Utilities.bytesAsHexString(stateHash.getPrevHash()) : "-";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,7 @@ public abstract class StateMonitorView<StH extends StateHash,
|
|||
private Subscription selectedItemSubscription;
|
||||
protected final BooleanProperty isInConflictWithNonSeedNode = new SimpleBooleanProperty();
|
||||
protected final BooleanProperty isInConflictWithSeedNode = new SimpleBooleanProperty();
|
||||
protected final BooleanProperty isDaoStateBlockChainNotConnecting = new SimpleBooleanProperty();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -134,8 +135,10 @@ public abstract class StateMonitorView<StH extends StateHash,
|
|||
|
||||
daoStateService.addDaoStateListener(this);
|
||||
|
||||
resyncButton.visibleProperty().bind(isInConflictWithSeedNode);
|
||||
resyncButton.managedProperty().bind(isInConflictWithSeedNode);
|
||||
resyncButton.visibleProperty().bind(isInConflictWithSeedNode
|
||||
.or(isDaoStateBlockChainNotConnecting));
|
||||
resyncButton.managedProperty().bind(isInConflictWithSeedNode
|
||||
.or(isDaoStateBlockChainNotConnecting));
|
||||
|
||||
resyncButton.setOnAction(ev -> resyncDaoState());
|
||||
|
||||
|
@ -177,8 +180,6 @@ public abstract class StateMonitorView<StH extends StateHash,
|
|||
|
||||
protected abstract String getPeersTableHeader();
|
||||
|
||||
protected abstract String getPrevHashTableHeader();
|
||||
|
||||
protected abstract String getHashTableHeader();
|
||||
|
||||
protected abstract String getBlockHeightTableHeader();
|
||||
|
@ -272,6 +273,9 @@ public abstract class StateMonitorView<StH extends StateHash,
|
|||
} else if (isInConflictWithNonSeedNode.get()) {
|
||||
statusTextField.setText(Res.get("dao.monitor.isInConflictWithNonSeedNode"));
|
||||
statusTextField.getStyleClass().remove("dao-inConflict");
|
||||
} else if (isDaoStateBlockChainNotConnecting.get()) {
|
||||
statusTextField.setText(Res.get("dao.monitor.isDaoStateBlockChainNotConnecting"));
|
||||
statusTextField.getStyleClass().add("dao-inConflict");
|
||||
} else {
|
||||
statusTextField.setText(Res.get("dao.monitor.daoStateInSync"));
|
||||
statusTextField.getStyleClass().remove("dao-inConflict");
|
||||
|
@ -328,7 +332,6 @@ public abstract class StateMonitorView<StH extends StateHash,
|
|||
tableView.getSortOrder().add(column);
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getHashTableHeader());
|
||||
column.setMinWidth(120);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
|
@ -353,31 +356,6 @@ public abstract class StateMonitorView<StH extends StateHash,
|
|||
tableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getPrevHashTableHeader());
|
||||
column.setMinWidth(120);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public TableCell<BLI, BLI> call(TableColumn<BLI,
|
||||
BLI> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final BLI item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.getPrevHash());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(BLI::getPrevHash));
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getPeersTableHeader());
|
||||
column.setMinWidth(80);
|
||||
column.setMaxWidth(column.getMinWidth());
|
||||
|
@ -543,30 +521,6 @@ public abstract class StateMonitorView<StH extends StateHash,
|
|||
conflictTableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>(getPrevHashTableHeader());
|
||||
column.setMinWidth(120);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<CLI, CLI> call(
|
||||
TableColumn<CLI, CLI> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final CLI item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.getPrevHash());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(CLI::getPrevHash));
|
||||
conflictTableView.getColumns().add(column);
|
||||
|
||||
|
||||
column = new AutoTooltipTableColumn<>("");
|
||||
column.setMinWidth(120);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
|
|
|
@ -149,11 +149,6 @@ public class BlindVoteStateMonitorView extends StateMonitorView<BlindVoteStateHa
|
|||
return Res.get("dao.monitor.table.peers");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrevHashTableHeader() {
|
||||
return Res.get("dao.monitor.blindVote.table.prev");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHashTableHeader() {
|
||||
return Res.get("dao.monitor.blindVote.table.hash");
|
||||
|
@ -221,7 +216,6 @@ public class BlindVoteStateMonitorView extends StateMonitorView<BlindVoteStateHa
|
|||
tableView.getColumns().add(1, column);
|
||||
}
|
||||
|
||||
|
||||
protected void createConflictColumns() {
|
||||
super.createConflictColumns();
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import bisq.desktop.main.dao.monitor.StateBlockListItem;
|
|||
|
||||
import bisq.core.dao.monitoring.model.DaoStateBlock;
|
||||
import bisq.core.dao.monitoring.model.DaoStateHash;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
|
@ -35,4 +36,10 @@ class DaoStateBlockListItem extends StateBlockListItem<DaoStateHash, DaoStateBlo
|
|||
DaoStateBlockListItem(DaoStateBlock stateBlock, IntSupplier cycleIndexSupplier) {
|
||||
super(stateBlock, cycleIndexSupplier);
|
||||
}
|
||||
|
||||
String hashCreator() {
|
||||
return ((DaoStateBlock) stateBlock).isSelfCreated() ?
|
||||
Res.get("dao.monitor.table.hashCreator.self") :
|
||||
Res.get("dao.monitor.table.hashCreator.peer");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package bisq.desktop.main.dao.monitor.daostate;
|
||||
|
||||
import bisq.desktop.common.view.FxmlView;
|
||||
import bisq.desktop.components.AutoTooltipTableColumn;
|
||||
import bisq.desktop.main.dao.monitor.StateMonitorView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
|
@ -40,10 +41,18 @@ import bisq.common.util.Utilities;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -60,7 +69,6 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
|||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@Inject
|
||||
private DaoStateMonitorView(DaoStateService daoStateService,
|
||||
DaoFacade daoFacade,
|
||||
|
@ -111,7 +119,7 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onChangeAfterBatchProcessing() {
|
||||
public void onDaoStateHashesChanged() {
|
||||
if (daoStateService.isParseBlockChainComplete()) {
|
||||
onDataUpdate();
|
||||
}
|
||||
|
@ -160,11 +168,6 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
|||
return Res.get("dao.monitor.table.peers");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrevHashTableHeader() {
|
||||
return Res.get("dao.monitor.daoState.table.prev");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHashTableHeader() {
|
||||
return Res.get("dao.monitor.daoState.table.hash");
|
||||
|
@ -189,6 +192,7 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
|||
protected void onDataUpdate() {
|
||||
isInConflictWithSeedNode.set(daoStateMonitoringService.isInConflictWithSeedNode());
|
||||
isInConflictWithNonSeedNode.set(daoStateMonitoringService.isInConflictWithNonSeedNode());
|
||||
isDaoStateBlockChainNotConnecting.set(daoStateMonitoringService.isDaoStateBlockChainNotConnecting());
|
||||
|
||||
listItems.setAll(daoStateMonitoringService.getDaoStateBlockChain().stream()
|
||||
.map(this::getStateBlockListItem)
|
||||
|
@ -202,6 +206,35 @@ public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoState
|
|||
daoStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createColumns() {
|
||||
super.createColumns();
|
||||
|
||||
TableColumn<DaoStateBlockListItem, DaoStateBlockListItem> column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.table.hashCreator"));
|
||||
column.setMinWidth(90);
|
||||
column.setMaxWidth(column.getMinWidth());
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<DaoStateBlockListItem, DaoStateBlockListItem> call(
|
||||
TableColumn<DaoStateBlockListItem, DaoStateBlockListItem> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final DaoStateBlockListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(item.hashCreator());
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getPeersMap().size()));
|
||||
tableView.getColumns().add(2, column);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
|
|
@ -147,11 +147,6 @@ public class ProposalStateMonitorView extends StateMonitorView<ProposalStateHash
|
|||
return Res.get("dao.monitor.table.peers");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrevHashTableHeader() {
|
||||
return Res.get("dao.monitor.proposal.table.prev");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHashTableHeader() {
|
||||
return Res.get("dao.monitor.proposal.table.hash");
|
||||
|
@ -219,7 +214,6 @@ public class ProposalStateMonitorView extends StateMonitorView<ProposalStateHash
|
|||
tableView.getColumns().add(1, column);
|
||||
}
|
||||
|
||||
|
||||
protected void createConflictColumns() {
|
||||
super.createConflictColumns();
|
||||
|
||||
|
|
|
@ -176,7 +176,9 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
|
|||
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
|
||||
root.getTabs().addListener(tabListChangeListener);
|
||||
navigation.addListener(navigationListener);
|
||||
navigation.navigateTo(MainView.class, this.getClass(), OfferBookView.class);
|
||||
if (offerBookView == null) {
|
||||
navigation.navigateTo(MainView.class, this.getClass(), OfferBookView.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -202,17 +204,21 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
|
|||
View view;
|
||||
boolean isBuy = direction == OfferDirection.BUY;
|
||||
|
||||
if (viewClass == OfferBookView.class && offerBookView == null) {
|
||||
view = viewLoader.load(viewClass);
|
||||
// Offerbook must not be cached by ViewLoader as we use 2 instances for sell and buy screens.
|
||||
offerBookTab = new Tab(isBuy ? Res.get("shared.buyBitcoin").toUpperCase() : Res.get("shared.sellBitcoin").toUpperCase());
|
||||
offerBookTab.setClosable(false);
|
||||
offerBookTab.setContent(view.getRoot());
|
||||
tabPane.getTabs().add(offerBookTab);
|
||||
offerBookView = (OfferBookView) view;
|
||||
offerBookView.onTabSelected(true);
|
||||
offerBookView.setOfferActionHandler(offerActionHandler);
|
||||
offerBookView.setDirection(direction);
|
||||
if (viewClass == OfferBookView.class) {
|
||||
if (offerBookTab != null && offerBookView != null) {
|
||||
tabPane.getSelectionModel().select(offerBookTab);
|
||||
} else {
|
||||
view = viewLoader.load(viewClass);
|
||||
// Offerbook must not be cached by ViewLoader as we use 2 instances for sell and buy screens.
|
||||
offerBookTab = new Tab(isBuy ? Res.get("shared.buyBitcoin").toUpperCase() : Res.get("shared.sellBitcoin").toUpperCase());
|
||||
offerBookTab.setClosable(false);
|
||||
offerBookTab.setContent(view.getRoot());
|
||||
tabPane.getTabs().add(offerBookTab);
|
||||
offerBookView = (OfferBookView) view;
|
||||
offerBookView.onTabSelected(true);
|
||||
offerBookView.setOfferActionHandler(offerActionHandler);
|
||||
offerBookView.setDirection(direction);
|
||||
}
|
||||
} else if (viewClass == CreateOfferView.class && createOfferView == null) {
|
||||
view = viewLoader.load(viewClass);
|
||||
// CreateOffer and TakeOffer must not be cached by ViewLoader as we cannot use a view multiple times
|
||||
|
@ -247,7 +253,7 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
|
|||
// in different graphs
|
||||
takeOfferView = (TakeOfferView) view;
|
||||
takeOfferView.initWithData(offer);
|
||||
takeOfferPane = ((TakeOfferView) view).getRoot();
|
||||
takeOfferPane = takeOfferView.getRoot();
|
||||
takeOfferTab = new Tab(getTakeOfferTabName());
|
||||
takeOfferTab.setClosable(true);
|
||||
// close handler from close on take offer action
|
||||
|
|
|
@ -781,6 +781,12 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
|||
return offerUtil.isBsqForMakerFeeAvailable(amount.get());
|
||||
}
|
||||
|
||||
boolean isAttemptToBuyBsq() {
|
||||
// When you buy an asset you actually sell BTC.
|
||||
// This is why an offer to buy BSQ is actually an offer to sell BTC for BSQ.
|
||||
return !isBuyOffer() && getTradeCurrency().getCode().equals("BSQ");
|
||||
}
|
||||
|
||||
boolean canPlaceOffer() {
|
||||
return GUIUtil.isBootstrappedOrShowPopup(p2PService) &&
|
||||
GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation, tradeCurrency);
|
||||
|
|
|
@ -83,7 +83,6 @@ import javafx.scene.control.Tooltip;
|
|||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
@ -122,6 +121,7 @@ import lombok.Setter;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static bisq.core.payment.payload.PaymentMethod.HAL_CASH_ID;
|
||||
import static bisq.desktop.main.offer.bisq_v1.OfferViewUtil.addPayInfoEntry;
|
||||
import static bisq.desktop.util.FormBuilder.*;
|
||||
import static javafx.beans.binding.Bindings.createStringBinding;
|
||||
|
||||
|
@ -156,7 +156,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
private VBox currencySelection, fixedPriceBox, percentagePriceBox, currencyTextFieldBox, triggerPriceVBox;
|
||||
private HBox fundingHBox, firstRowHBox, secondRowHBox, placeOfferBox, amountValueCurrencyBox,
|
||||
priceAsPercentageValueCurrencyBox, volumeValueCurrencyBox, priceValueCurrencyBox,
|
||||
minAmountValueCurrencyBox, advancedOptionsBox, triggerPriceHBox;
|
||||
minAmountValueCurrencyBox, advancedOptionsBox, triggerPriceHBox, buyBsqBox;
|
||||
|
||||
private Subscription isWaitingForFundsSubscription, balanceSubscription;
|
||||
private ChangeListener<Boolean> amountFocusedListener, minAmountFocusedListener, volumeFocusedListener,
|
||||
|
@ -270,6 +270,13 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
tradeFeeInBtcToggle.setManaged(false);
|
||||
tradeFeeInBsqToggle.setVisible(false);
|
||||
tradeFeeInBsqToggle.setManaged(false);
|
||||
buyBsqBox.setVisible(false);
|
||||
buyBsqBox.setManaged(false);
|
||||
}
|
||||
|
||||
if (!model.isShowBuyBsqHint()) {
|
||||
buyBsqBox.setVisible(false);
|
||||
buyBsqBox.setManaged(false);
|
||||
}
|
||||
|
||||
Label popOverLabel = OfferViewUtil.createPopOverLabel(Res.get("createOffer.triggerPrice.tooltip"));
|
||||
|
@ -336,7 +343,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
showInsufficientBsqFundsForBtcFeePaymentPopup();
|
||||
}
|
||||
|
||||
// called form parent as the view does not get notified when the tab is closed
|
||||
// called from parent as the view does not get notified when the tab is closed
|
||||
public void onClose() {
|
||||
// we use model.placeOfferCompleted to not react on close which was triggered by a successful placeOffer
|
||||
if (model.getDataModel().getBalance().get().isPositive() && !model.placeOfferCompleted.get()) {
|
||||
|
@ -372,6 +379,16 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
}
|
||||
|
||||
private void showInsufficientBsqFundsForBtcFeePaymentPopup() {
|
||||
String message = getMissingBsqForFeePaymentMessage();
|
||||
|
||||
if (message != null)
|
||||
new Popup().warning(message)
|
||||
.actionButtonTextWithGoTo("navigation.dao.wallet.receive")
|
||||
.onAction(() -> navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class, BsqReceiveView.class))
|
||||
.show();
|
||||
}
|
||||
|
||||
private String getMissingBsqForFeePaymentMessage() {
|
||||
Coin makerFee = model.getDataModel().getMakerFee(false);
|
||||
String message = null;
|
||||
if (makerFee != null) {
|
||||
|
@ -381,11 +398,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
} else if (model.getDataModel().getUsableBsqBalance().isZero())
|
||||
message = Res.get("popup.warning.noBsqFundsForBtcFeePayment");
|
||||
|
||||
if (message != null)
|
||||
new Popup().warning(message)
|
||||
.actionButtonTextWithGoTo("navigation.dao.wallet.receive")
|
||||
.onAction(() -> navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class, BsqReceiveView.class))
|
||||
.show();
|
||||
return message;
|
||||
}
|
||||
|
||||
private void onShowPayFundsScreen() {
|
||||
|
@ -400,6 +413,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
|
||||
tradeFeeInBtcToggle.setMouseTransparent(true);
|
||||
tradeFeeInBsqToggle.setMouseTransparent(true);
|
||||
buyBsqBox.setVisible(false);
|
||||
buyBsqBox.setManaged(false);
|
||||
|
||||
setDepositTitledGroupBg.setVisible(false);
|
||||
setDepositTitledGroupBg.setManaged(false);
|
||||
|
@ -514,17 +529,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
|
||||
private void maybeShowAccountWarning(PaymentAccount paymentAccount, boolean isBuyer) {
|
||||
String msgKey = paymentAccount.getPreTradeMessage(isBuyer);
|
||||
if (msgKey == null || paymentAccountWarningDisplayed.getOrDefault(msgKey, false)) {
|
||||
return;
|
||||
}
|
||||
paymentAccountWarningDisplayed.put(msgKey, true);
|
||||
UserThread.runAfter(() -> {
|
||||
new Popup().information(Res.get(msgKey))
|
||||
.width(900)
|
||||
.closeButtonText(Res.get("shared.iConfirm"))
|
||||
.dontShowAgainId(msgKey)
|
||||
.show();
|
||||
}, 500, TimeUnit.MILLISECONDS);
|
||||
OfferViewUtil.showPaymentAccountWarning(msgKey, paymentAccountWarningDisplayed);
|
||||
}
|
||||
|
||||
protected void onPaymentAccountsComboBoxSelected() {
|
||||
|
@ -894,6 +899,10 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
if (DevEnv.isDaoActivated()) {
|
||||
tradeFeeInBtcToggle.setVisible(newValue);
|
||||
tradeFeeInBsqToggle.setVisible(newValue);
|
||||
if (model.isShowBuyBsqHint()) {
|
||||
buyBsqBox.setVisible(newValue);
|
||||
buyBsqBox.setManaged(newValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1007,15 +1016,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void addScrollPane() {
|
||||
scrollPane = new ScrollPane();
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
AnchorPane.setLeftAnchor(scrollPane, 0d);
|
||||
AnchorPane.setTopAnchor(scrollPane, 0d);
|
||||
AnchorPane.setRightAnchor(scrollPane, 0d);
|
||||
AnchorPane.setBottomAnchor(scrollPane, 0d);
|
||||
scrollPane = GUIUtil.createScrollPane();
|
||||
root.getChildren().add(scrollPane);
|
||||
}
|
||||
|
||||
|
@ -1025,13 +1026,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
gridPane.setPadding(new Insets(30, 25, -1, 25));
|
||||
gridPane.setHgap(5);
|
||||
gridPane.setVgap(5);
|
||||
ColumnConstraints columnConstraints1 = new ColumnConstraints();
|
||||
columnConstraints1.setHalignment(HPos.RIGHT);
|
||||
columnConstraints1.setHgrow(Priority.NEVER);
|
||||
columnConstraints1.setMinWidth(200);
|
||||
ColumnConstraints columnConstraints2 = new ColumnConstraints();
|
||||
columnConstraints2.setHgrow(Priority.ALWAYS);
|
||||
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2);
|
||||
GUIUtil.setDefaultTwoColumnConstraintsForGridPane(gridPane);
|
||||
scrollPane.setContent(gridPane);
|
||||
}
|
||||
|
||||
|
@ -1105,12 +1100,21 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
advancedOptionsBox.setSpacing(40);
|
||||
|
||||
GridPane.setRowIndex(advancedOptionsBox, gridRow);
|
||||
GridPane.setColumnSpan(advancedOptionsBox, GridPane.REMAINING);
|
||||
GridPane.setColumnIndex(advancedOptionsBox, 0);
|
||||
GridPane.setHalignment(advancedOptionsBox, HPos.LEFT);
|
||||
GridPane.setMargin(advancedOptionsBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
|
||||
gridPane.getChildren().add(advancedOptionsBox);
|
||||
|
||||
advancedOptionsBox.getChildren().addAll(getBuyerSecurityDepositBox(), getTradeFeeFieldsBox());
|
||||
Tuple2<AutoTooltipButton, HBox> buyBsqButtonBox = OfferViewUtil.createBuyBsqButtonBox(
|
||||
navigation, preferences);
|
||||
buyBsqBox = buyBsqButtonBox.second;
|
||||
buyBsqBox.setManaged(false);
|
||||
buyBsqBox.setVisible(false);
|
||||
|
||||
VBox tradeFeeFieldsBox = getTradeFeeFieldsBox();
|
||||
tradeFeeFieldsBox.setMinWidth(240);
|
||||
advancedOptionsBox.getChildren().addAll(getBuyerSecurityDepositBox(), tradeFeeFieldsBox, buyBsqBox);
|
||||
|
||||
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow,
|
||||
Res.get("shared.nextStep"), Res.get("shared.cancel"));
|
||||
|
@ -1152,15 +1156,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
boolean isPreferredFeeCurrencyBtc = model.getDataModel().isPreferredFeeCurrencyBtc();
|
||||
boolean isBsqForFeeAvailable = model.getDataModel().isBsqForFeeAvailable();
|
||||
if (!isPreferredFeeCurrencyBtc && !isBsqForFeeAvailable) {
|
||||
Coin makerFee = model.getDataModel().getMakerFee(false);
|
||||
String missingBsq = null;
|
||||
if (makerFee != null) {
|
||||
missingBsq = Res.get("popup.warning.insufficientBsqFundsForBtcFeePayment",
|
||||
bsqFormatter.formatCoinWithCode(makerFee.subtract(model.getDataModel().getUsableBsqBalance())));
|
||||
|
||||
} else if (model.getDataModel().getUsableBsqBalance().isZero()) {
|
||||
missingBsq = Res.get("popup.warning.noBsqFundsForBtcFeePayment");
|
||||
}
|
||||
String missingBsq = getMissingBsqForFeePaymentMessage();
|
||||
|
||||
if (missingBsq != null) {
|
||||
new Popup().warning(missingBsq)
|
||||
|
@ -1542,8 +1538,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
infoGridPane.setPadding(new Insets(10, 10, 10, 10));
|
||||
|
||||
int i = 0;
|
||||
if (model.isSellOffer())
|
||||
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.tradeAmount"), model.tradeAmount.get());
|
||||
if (model.isSellOffer()) {
|
||||
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.tradeAmount"), model.getTradeAmount());
|
||||
}
|
||||
|
||||
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.yourSecurityDeposit"), model.getSecurityDepositInfo());
|
||||
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("createOffer.fundsBox.offerFee"), model.getTradeFee());
|
||||
|
@ -1553,19 +1550,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
separator.getStyleClass().add("offer-separator");
|
||||
GridPane.setConstraints(separator, 1, i++);
|
||||
infoGridPane.getChildren().add(separator);
|
||||
addPayInfoEntry(infoGridPane, i, Res.getWithCol("shared.total"), model.getTotalToPayInfo());
|
||||
addPayInfoEntry(infoGridPane, i, Res.getWithCol("shared.total"),
|
||||
model.getTotalToPayInfo());
|
||||
return infoGridPane;
|
||||
}
|
||||
|
||||
private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
|
||||
Label label = new AutoTooltipLabel(labelText);
|
||||
TextField textField = new TextField(value);
|
||||
textField.setMinWidth(500);
|
||||
textField.setEditable(false);
|
||||
textField.setFocusTraversable(false);
|
||||
textField.setId("payment-info");
|
||||
GridPane.setConstraints(label, 0, row, 1, 1, HPos.RIGHT, VPos.CENTER);
|
||||
GridPane.setConstraints(textField, 1, row);
|
||||
infoGridPane.getChildren().addAll(label, textField);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -678,11 +678,10 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
updateSpinnerInfo();
|
||||
}
|
||||
|
||||
boolean fundFromSavingsWallet() {
|
||||
void fundFromSavingsWallet() {
|
||||
dataModel.fundFromSavingsWallet();
|
||||
if (dataModel.getIsBtcWalletFunded().get()) {
|
||||
updateButtonDisableState();
|
||||
return true;
|
||||
} else {
|
||||
new Popup().warning(Res.get("shared.notEnoughFunds",
|
||||
btcFormatter.formatCoinWithCode(dataModel.totalToPayAsCoinProperty().get()),
|
||||
|
@ -690,7 +689,6 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
.actionButtonTextWithGoTo("navigation.funds.depositFunds")
|
||||
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class))
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1355,4 +1353,8 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowBuyBsqHint() {
|
||||
return !dataModel.isBsqForFeeAvailable() && !dataModel.isAttemptToBuyBsq();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,37 @@
|
|||
|
||||
package bisq.desktop.main.offer.bisq_v1;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
import bisq.desktop.Navigation;
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.AutoTooltipLabel;
|
||||
import bisq.desktop.components.HyperlinkWithIcon;
|
||||
import bisq.desktop.main.MainView;
|
||||
import bisq.desktop.main.offer.SellOfferView;
|
||||
import bisq.desktop.main.offer.offerbook.OfferBookView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
// Shared utils for Views
|
||||
public class OfferViewUtil {
|
||||
|
||||
public static Label createPopOverLabel(String text) {
|
||||
final Label label = new Label(text);
|
||||
label.setPrefWidth(300);
|
||||
|
@ -31,4 +56,64 @@ public class OfferViewUtil {
|
|||
label.setPadding(new Insets(10));
|
||||
return label;
|
||||
}
|
||||
|
||||
public static void showPaymentAccountWarning(String msgKey,
|
||||
HashMap<String, Boolean> paymentAccountWarningDisplayed) {
|
||||
if (msgKey == null || paymentAccountWarningDisplayed.getOrDefault(msgKey, false)) {
|
||||
return;
|
||||
}
|
||||
paymentAccountWarningDisplayed.put(msgKey, true);
|
||||
UserThread.runAfter(() -> {
|
||||
new Popup().information(Res.get(msgKey))
|
||||
.width(900)
|
||||
.closeButtonText(Res.get("shared.iConfirm"))
|
||||
.dontShowAgainId(msgKey)
|
||||
.show();
|
||||
}, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public static void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
|
||||
Label label = new AutoTooltipLabel(labelText);
|
||||
TextField textField = new TextField(value);
|
||||
textField.setMinWidth(500);
|
||||
textField.setEditable(false);
|
||||
textField.setFocusTraversable(false);
|
||||
textField.setId("payment-info");
|
||||
GridPane.setConstraints(label, 0, row, 1, 1, HPos.RIGHT, VPos.CENTER);
|
||||
GridPane.setConstraints(textField, 1, row);
|
||||
infoGridPane.getChildren().addAll(label, textField);
|
||||
}
|
||||
|
||||
public static Tuple2<AutoTooltipButton, HBox> createBuyBsqButtonBox(Navigation navigation,
|
||||
Preferences preferences) {
|
||||
String buyBsqText = Res.get("shared.buyCurrency", "BSQ");
|
||||
var buyBsqButton = new AutoTooltipButton(buyBsqText);
|
||||
buyBsqButton.getStyleClass().add("action-button");
|
||||
buyBsqButton.getStyleClass().add("tiny-button");
|
||||
buyBsqButton.setMinWidth(60);
|
||||
buyBsqButton.setOnAction(e -> openBuyBsqOfferBook(navigation, preferences)
|
||||
);
|
||||
|
||||
var info = new AutoTooltipLabel("BSQ is colored BTC that helps fund Bisq developers.");
|
||||
var learnMore = new HyperlinkWithIcon("Learn More");
|
||||
learnMore.setOnAction(e -> new Popup().headLine(buyBsqText)
|
||||
.information(Res.get("createOffer.buyBsq.popupMessage"))
|
||||
.actionButtonText(buyBsqText)
|
||||
.buttonAlignment(HPos.CENTER)
|
||||
.onAction(() -> openBuyBsqOfferBook(navigation, preferences)).show());
|
||||
learnMore.setMinWidth(100);
|
||||
|
||||
HBox buyBsqBox = new HBox(buyBsqButton, info, learnMore);
|
||||
buyBsqBox.setAlignment(Pos.BOTTOM_LEFT);
|
||||
buyBsqBox.setSpacing(10);
|
||||
buyBsqBox.setPadding(new Insets(0, 0, 4, -20));
|
||||
|
||||
return new Tuple2<>(buyBsqButton, buyBsqBox);
|
||||
}
|
||||
|
||||
private static void openBuyBsqOfferBook(Navigation navigation, Preferences preferences) {
|
||||
preferences.setSellScreenCurrencyCode("BSQ");
|
||||
navigation.navigateTo(
|
||||
MainView.class, SellOfferView.class, OfferBookView.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -712,4 +712,10 @@ class TakeOfferDataModel extends OfferDataModel {
|
|||
public boolean isBsqForFeeAvailable() {
|
||||
return offerUtil.isBsqForTakerFeeAvailable(amount.get());
|
||||
}
|
||||
|
||||
public boolean isAttemptToBuyBsq() {
|
||||
// When you buy an asset you actually sell BTC.
|
||||
// This is why an offer to buy BSQ is actually an offer to sell BTC for BSQ.
|
||||
return !isBuyOffer() && getOffer().getCurrencyCode().equals("BSQ");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,6 @@ import javafx.scene.control.Tooltip;
|
|||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
@ -118,6 +117,7 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static bisq.desktop.main.offer.bisq_v1.OfferViewUtil.addPayInfoEntry;
|
||||
import static bisq.desktop.util.FormBuilder.*;
|
||||
import static javafx.beans.binding.Bindings.createStringBinding;
|
||||
|
||||
|
@ -135,7 +135,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
private VBox priceAsPercentageInputBox, amountRangeBox;
|
||||
private HBox fundingHBox, amountValueCurrencyBox, priceValueCurrencyBox, volumeValueCurrencyBox,
|
||||
priceAsPercentageValueCurrencyBox, minAmountValueCurrencyBox, advancedOptionsBox,
|
||||
takeOfferBox, buttonBox, firstRowHBox;
|
||||
takeOfferBox, buttonBox, firstRowHBox, buyBsqBox;
|
||||
private ComboBox<PaymentAccount> paymentAccountsComboBox;
|
||||
private Label amountDescriptionLabel,
|
||||
paymentMethodLabel,
|
||||
|
@ -247,6 +247,10 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
if (DevEnv.isDaoActivated()) {
|
||||
tradeFeeInBtcToggle.setVisible(newValue);
|
||||
tradeFeeInBsqToggle.setVisible(newValue);
|
||||
if (model.isShowBuyBsqHint()) {
|
||||
buyBsqBox.setVisible(newValue);
|
||||
buyBsqBox.setManaged(newValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -330,6 +334,13 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
tradeFeeInBtcToggle.setManaged(false);
|
||||
tradeFeeInBsqToggle.setVisible(false);
|
||||
tradeFeeInBsqToggle.setManaged(false);
|
||||
buyBsqBox.setVisible(false);
|
||||
buyBsqBox.setManaged(false);
|
||||
}
|
||||
|
||||
if (!model.isShowBuyBsqHint()) {
|
||||
buyBsqBox.setVisible(false);
|
||||
buyBsqBox.setManaged(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -470,6 +481,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
|
||||
tradeFeeInBtcToggle.setMouseTransparent(true);
|
||||
tradeFeeInBsqToggle.setMouseTransparent(true);
|
||||
buyBsqBox.setVisible(false);
|
||||
buyBsqBox.setManaged(false);
|
||||
|
||||
int delay = 500;
|
||||
int diff = 100;
|
||||
|
@ -672,7 +685,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
offerDetailsWindow.hide();
|
||||
|
||||
UserThread.runAfter(() -> new Popup().warning(newValue + "\n\n" +
|
||||
Res.get("takeOffer.alreadyPaidInFunds"))
|
||||
Res.get("takeOffer.alreadyPaidInFunds"))
|
||||
.actionButtonTextWithGoTo("navigation.funds.availableForWithdrawal")
|
||||
.onAction(() -> {
|
||||
errorPopupDisplayed.set(true);
|
||||
|
@ -787,15 +800,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void addScrollPane() {
|
||||
scrollPane = new ScrollPane();
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
AnchorPane.setLeftAnchor(scrollPane, 0d);
|
||||
AnchorPane.setTopAnchor(scrollPane, 0d);
|
||||
AnchorPane.setRightAnchor(scrollPane, 0d);
|
||||
AnchorPane.setBottomAnchor(scrollPane, 0d);
|
||||
scrollPane = GUIUtil.createScrollPane();
|
||||
root.getChildren().add(scrollPane);
|
||||
}
|
||||
|
||||
|
@ -805,13 +810,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
gridPane.setPadding(new Insets(15, 15, -1, 15));
|
||||
gridPane.setHgap(5);
|
||||
gridPane.setVgap(5);
|
||||
ColumnConstraints columnConstraints1 = new ColumnConstraints();
|
||||
columnConstraints1.setHalignment(HPos.RIGHT);
|
||||
columnConstraints1.setHgrow(Priority.NEVER);
|
||||
columnConstraints1.setMinWidth(200);
|
||||
ColumnConstraints columnConstraints2 = new ColumnConstraints();
|
||||
columnConstraints2.setHgrow(Priority.ALWAYS);
|
||||
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2);
|
||||
GUIUtil.setDefaultTwoColumnConstraintsForGridPane(gridPane);
|
||||
scrollPane.setContent(gridPane);
|
||||
}
|
||||
|
||||
|
@ -879,12 +878,21 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
advancedOptionsBox.setSpacing(40);
|
||||
|
||||
GridPane.setRowIndex(advancedOptionsBox, gridRow);
|
||||
GridPane.setColumnSpan(advancedOptionsBox, GridPane.REMAINING);
|
||||
GridPane.setColumnIndex(advancedOptionsBox, 0);
|
||||
GridPane.setHalignment(advancedOptionsBox, HPos.LEFT);
|
||||
GridPane.setMargin(advancedOptionsBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
|
||||
gridPane.getChildren().add(advancedOptionsBox);
|
||||
|
||||
advancedOptionsBox.getChildren().addAll(getTradeFeeFieldsBox());
|
||||
Tuple2<AutoTooltipButton, HBox> buyBsqButtonBox = OfferViewUtil.createBuyBsqButtonBox(
|
||||
navigation, model.dataModel.preferences);
|
||||
buyBsqBox = buyBsqButtonBox.second;
|
||||
buyBsqBox.setManaged(false);
|
||||
buyBsqBox.setVisible(false);
|
||||
|
||||
VBox tradeFeeFieldsBox = getTradeFeeFieldsBox();
|
||||
tradeFeeFieldsBox.setMinWidth(240);
|
||||
advancedOptionsBox.getChildren().addAll(tradeFeeFieldsBox, buyBsqBox);
|
||||
}
|
||||
|
||||
private void addButtons() {
|
||||
|
@ -1279,17 +1287,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
|
||||
private void maybeShowAccountWarning(PaymentAccount paymentAccount, boolean isBuyer) {
|
||||
String msgKey = paymentAccount.getPreTradeMessage(!isBuyer);
|
||||
if (msgKey == null || paymentAccountWarningDisplayed.getOrDefault(msgKey, false)) {
|
||||
return;
|
||||
}
|
||||
paymentAccountWarningDisplayed.put(msgKey, true);
|
||||
UserThread.runAfter(() -> {
|
||||
new Popup().information(Res.get(msgKey))
|
||||
.width(900)
|
||||
.closeButtonText(Res.get("shared.iConfirm"))
|
||||
.dontShowAgainId(msgKey)
|
||||
.show();
|
||||
}, 500, TimeUnit.MILLISECONDS);
|
||||
OfferViewUtil.showPaymentAccountWarning(msgKey, paymentAccountWarningDisplayed);
|
||||
}
|
||||
|
||||
private void maybeShowCashByMailWarning(PaymentAccount paymentAccount, Offer offer) {
|
||||
|
@ -1329,8 +1327,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
infoGridPane.setPadding(new Insets(10, 10, 10, 10));
|
||||
|
||||
int i = 0;
|
||||
if (model.isSeller())
|
||||
if (model.isSeller()) {
|
||||
addPayInfoEntry(infoGridPane, i++, Res.get("takeOffer.fundsBox.tradeAmount"), model.getTradeAmount());
|
||||
}
|
||||
|
||||
addPayInfoEntry(infoGridPane, i++, Res.getWithCol("shared.yourSecurityDeposit"), model.getSecurityDepositInfo());
|
||||
addPayInfoEntry(infoGridPane, i++, Res.get("takeOffer.fundsBox.offerFee"), model.getTradeFee());
|
||||
|
@ -1345,17 +1344,5 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
|
||||
return infoGridPane;
|
||||
}
|
||||
|
||||
private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
|
||||
Label label = new AutoTooltipLabel(labelText);
|
||||
TextField textField = new TextField(value);
|
||||
textField.setMinWidth(500);
|
||||
textField.setEditable(false);
|
||||
textField.setFocusTraversable(false);
|
||||
textField.setId("payment-info");
|
||||
GridPane.setConstraints(label, 0, row, 1, 1, HPos.RIGHT, VPos.CENTER);
|
||||
GridPane.setConstraints(textField, 1, row);
|
||||
infoGridPane.getChildren().addAll(label, textField);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -800,4 +800,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
Res.get("shared.aboveInPercent");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowBuyBsqHint() {
|
||||
return !dataModel.isBsqForFeeAvailable() && !dataModel.isAttemptToBuyBsq();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import bisq.desktop.components.PeerInfoIconTrading;
|
|||
import bisq.desktop.components.TitledGroupBg;
|
||||
import bisq.desktop.main.MainView;
|
||||
import bisq.desktop.main.account.AccountView;
|
||||
import bisq.desktop.main.account.content.altcoinaccounts.AltCoinAccountsView;
|
||||
import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView;
|
||||
import bisq.desktop.main.funds.FundsView;
|
||||
import bisq.desktop.main.funds.withdrawal.WithdrawalView;
|
||||
|
@ -91,6 +92,7 @@ import javafx.scene.layout.GridPane;
|
|||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
|
||||
|
@ -135,17 +137,22 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
private AutocompleteComboBox<PaymentMethod> paymentMethodComboBox;
|
||||
private AutoTooltipButton createOfferButton;
|
||||
private AutoTooltipSlideToggleButton matchingOffersToggle;
|
||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> amountColumn, volumeColumn, marketColumn,
|
||||
priceColumn, paymentMethodColumn, depositColumn, signingStateColumn, avatarColumn;
|
||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> amountColumn;
|
||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> volumeColumn;
|
||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> marketColumn;
|
||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> priceColumn;
|
||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> depositColumn;
|
||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> signingStateColumn;
|
||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> avatarColumn;
|
||||
private TableView<OfferBookListItem> tableView;
|
||||
|
||||
private OfferView.OfferActionHandler offerActionHandler;
|
||||
private int gridRow = 0;
|
||||
private Label nrOfOffersLabel;
|
||||
private ListChangeListener<OfferBookListItem> offerListListener;
|
||||
private ChangeListener<Number> priceFeedUpdateCounterListener;
|
||||
private Subscription currencySelectionSubscriber;
|
||||
private static final int SHOW_ALL = 0;
|
||||
private Label disabledCreateOfferButtonTooltip;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
|
@ -200,13 +207,23 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
matchingOffersToggle.setText(Res.get("offerbook.matchingOffers"));
|
||||
HBox.setMargin(matchingOffersToggle, new Insets(7, 0, -9, -15));
|
||||
|
||||
|
||||
createOfferButton = new AutoTooltipButton();
|
||||
createOfferButton.setMinHeight(40);
|
||||
createOfferButton.setGraphicTextGap(10);
|
||||
|
||||
disabledCreateOfferButtonTooltip = new Label("");
|
||||
disabledCreateOfferButtonTooltip.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||
disabledCreateOfferButtonTooltip.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||
disabledCreateOfferButtonTooltip.prefWidthProperty().bind(createOfferButton.widthProperty());
|
||||
disabledCreateOfferButtonTooltip.prefHeightProperty().bind(createOfferButton.heightProperty());
|
||||
disabledCreateOfferButtonTooltip.setTooltip(new Tooltip(Res.get("offerbook.createOfferDisabled.tooltip")));
|
||||
disabledCreateOfferButtonTooltip.setManaged(false);
|
||||
disabledCreateOfferButtonTooltip.setVisible(false);
|
||||
|
||||
var createOfferButtonStack = new StackPane(createOfferButton, disabledCreateOfferButtonTooltip);
|
||||
|
||||
offerToolsBox.getChildren().addAll(currencyBoxTuple.first, paymentBoxTuple.first,
|
||||
matchingOffersToggle, getSpacer(), createOfferButton);
|
||||
matchingOffersToggle, getSpacer(), createOfferButtonStack);
|
||||
|
||||
GridPane.setHgrow(offerToolsBox, Priority.ALWAYS);
|
||||
GridPane.setRowIndex(offerToolsBox, gridRow);
|
||||
|
@ -231,7 +248,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
tableView.getColumns().add(amountColumn);
|
||||
volumeColumn = getVolumeColumn();
|
||||
tableView.getColumns().add(volumeColumn);
|
||||
paymentMethodColumn = getPaymentMethodColumn();
|
||||
AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> paymentMethodColumn = getPaymentMethodColumn();
|
||||
tableView.getColumns().add(paymentMethodColumn);
|
||||
depositColumn = getDepositColumn();
|
||||
tableView.getColumns().add(depositColumn);
|
||||
|
@ -538,6 +555,16 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
|
||||
public void enableCreateOfferButton() {
|
||||
createOfferButton.setDisable(false);
|
||||
disabledCreateOfferButtonTooltip.setManaged(false);
|
||||
disabledCreateOfferButtonTooltip.setVisible(false);
|
||||
}
|
||||
|
||||
private void disableCreateOfferButton() {
|
||||
createOfferButton.setDisable(true);
|
||||
disabledCreateOfferButtonTooltip.setManaged(true);
|
||||
disabledCreateOfferButtonTooltip.setVisible(true);
|
||||
|
||||
model.onCreateOffer();
|
||||
}
|
||||
|
||||
public void setDirection(OfferDirection direction) {
|
||||
|
@ -581,11 +608,16 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
}
|
||||
|
||||
public void setOfferActionHandler(OfferView.OfferActionHandler offerActionHandler) {
|
||||
this.offerActionHandler = offerActionHandler;
|
||||
model.setOfferActionHandler(offerActionHandler);
|
||||
}
|
||||
|
||||
public void onTabSelected(boolean isSelected) {
|
||||
model.onTabSelected(isSelected);
|
||||
|
||||
if (isSelected) {
|
||||
updateCurrencyComboBoxFromModel();
|
||||
root.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -594,16 +626,11 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
|
||||
private void onCreateOffer() {
|
||||
if (model.canCreateOrTakeOffer()) {
|
||||
PaymentMethod selectedPaymentMethod = model.selectedPaymentMethod;
|
||||
TradeCurrency selectedTradeCurrency = model.getSelectedTradeCurrency();
|
||||
if (!model.hasPaymentAccountForCurrency()) {
|
||||
new Popup().headLine(Res.get("offerbook.warning.noTradingAccountForCurrency.headline"))
|
||||
.instruction(Res.get("offerbook.warning.noTradingAccountForCurrency.msg"))
|
||||
.actionButtonText(Res.get("offerbook.yesCreateOffer"))
|
||||
.onAction(() -> {
|
||||
createOfferButton.setDisable(true);
|
||||
offerActionHandler.onCreateOffer(selectedTradeCurrency, selectedPaymentMethod);
|
||||
})
|
||||
.onAction(this::disableCreateOfferButton)
|
||||
.secondaryActionButtonText(Res.get("offerbook.setupNewAccount"))
|
||||
.onSecondaryAction(() -> {
|
||||
navigation.setReturnPath(navigation.getCurrentPath());
|
||||
|
@ -614,24 +641,18 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
return;
|
||||
}
|
||||
|
||||
createOfferButton.setDisable(true);
|
||||
offerActionHandler.onCreateOffer(selectedTradeCurrency, selectedPaymentMethod);
|
||||
disableCreateOfferButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void onShowInfo(Offer offer, OfferFilterService.Result result) {
|
||||
switch (result) {
|
||||
case VALID:
|
||||
break;
|
||||
case API_DISABLED:
|
||||
DevEnv.logErrorAndThrowIfDevMode("We are in desktop and in the taker position " +
|
||||
"viewing offers, so it cannot be that we got that result as we are not an API user.");
|
||||
break;
|
||||
case HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER:
|
||||
openPopupForMissingAccountSetup(Res.get("offerbook.warning.noMatchingAccount.headline"),
|
||||
Res.get("offerbook.warning.noMatchingAccount.msg"),
|
||||
FiatAccountsView.class,
|
||||
"navigation.account");
|
||||
openPopupForMissingAccountSetup(offer);
|
||||
break;
|
||||
case HAS_NOT_SAME_PROTOCOL_VERSION:
|
||||
new Popup().warning(Res.get("offerbook.warning.wrongTradeProtocol")).show();
|
||||
|
@ -675,6 +696,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
case HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED:
|
||||
new Popup().warning(Res.get("offerbook.warning.hideBsqSwapsDueDaoDeactivated")).show();
|
||||
break;
|
||||
case VALID:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -686,10 +708,10 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
offer.getPaymentMethod().getId().equals(PaymentMethod.CASH_DEPOSIT.getId())) {
|
||||
new Popup().confirmation(Res.get("popup.info.cashDepositInfo", offer.getBankId()))
|
||||
.actionButtonText(Res.get("popup.info.cashDepositInfo.confirm"))
|
||||
.onAction(() -> offerActionHandler.onTakeOffer(offer))
|
||||
.onAction(() -> model.onTakeOffer(offer))
|
||||
.show();
|
||||
} else {
|
||||
offerActionHandler.onTakeOffer(offer);
|
||||
model.onTakeOffer(offer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -728,14 +750,37 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
});
|
||||
}
|
||||
|
||||
private void openPopupForMissingAccountSetup(String headLine, String message, Class target, String targetAsString) {
|
||||
new Popup().headLine(headLine)
|
||||
.instruction(message)
|
||||
.actionButtonTextWithGoTo(targetAsString)
|
||||
.onAction(() -> {
|
||||
navigation.setReturnPath(navigation.getCurrentPath());
|
||||
navigation.navigateTo(MainView.class, AccountView.class, target);
|
||||
}).show();
|
||||
private void openPopupForMissingAccountSetup(Offer offer) {
|
||||
String headline = Res.get("offerbook.warning.noMatchingAccount.headline");
|
||||
|
||||
if (offer.getCurrencyCode().equals("BSQ")) {
|
||||
new Popup().headLine(headline)
|
||||
.instruction(Res.get("offerbook.warning.noMatchingBsqAccount.msg"))
|
||||
.actionButtonText(Res.get("offerbook.takeOffer.createAccount"))
|
||||
.onAction(() -> {
|
||||
var bsqAccount = model.createBsqAccount(offer);
|
||||
var message = Res.get("offerbook.info.accountCreated.message", bsqAccount.getAccountName());
|
||||
if (model.isInstantPaymentMethod(offer)) {
|
||||
message += Res.get("offerbook.info.accountCreated.tradeInstant");
|
||||
}
|
||||
message += Res.get("offerbook.info.accountCreated.takeOffer");
|
||||
new Popup().headLine(Res.get("offerbook.info.accountCreated.headline"))
|
||||
.information(message)
|
||||
.onClose(() -> model.onTakeOffer(offer))
|
||||
.show();
|
||||
}).show();
|
||||
} else {
|
||||
|
||||
var accountViewClass = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) ? FiatAccountsView.class : AltCoinAccountsView.class;
|
||||
|
||||
new Popup().headLine(headline)
|
||||
.instruction(Res.get("offerbook.warning.noMatchingAccount.msg"))
|
||||
.actionButtonTextWithGoTo("navigation.account")
|
||||
.onAction(() -> {
|
||||
navigation.setReturnPath(navigation.getCurrentPath());
|
||||
navigation.navigateTo(MainView.class, AccountView.class, accountViewClass);
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1068,13 +1113,11 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||
tableRow.setOnMousePressed(null);
|
||||
} else {
|
||||
button.setDefaultButton(false);
|
||||
if (!myOffer) {
|
||||
tableRow.setOnMousePressed(e -> {
|
||||
// ugly hack to get the icon clickable when deactivated
|
||||
if (!(e.getTarget() instanceof ImageView || e.getTarget() instanceof Canvas))
|
||||
onShowInfo(offer, canTakeOfferResult);
|
||||
});
|
||||
}
|
||||
tableRow.setOnMousePressed(e -> {
|
||||
// ugly hack to get the icon clickable when deactivated
|
||||
if (!(e.getTarget() instanceof ImageView || e.getTarget() instanceof Canvas))
|
||||
onShowInfo(offer, canTakeOfferResult);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,13 +20,16 @@ package bisq.desktop.main.offer.offerbook;
|
|||
import bisq.desktop.Navigation;
|
||||
import bisq.desktop.common.model.ActivatableViewModel;
|
||||
import bisq.desktop.main.MainView;
|
||||
import bisq.desktop.main.offer.OfferView;
|
||||
import bisq.desktop.main.settings.SettingsView;
|
||||
import bisq.desktop.main.settings.preferences.PreferencesView;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.api.CoreApi;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.locale.BankUtil;
|
||||
import bisq.core.locale.CountryUtil;
|
||||
import bisq.core.locale.CryptoCurrency;
|
||||
|
@ -111,6 +114,8 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
private final BsqFormatter bsqFormatter;
|
||||
|
||||
private final FilteredList<OfferBookListItem> filteredItems;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final CoreApi coreApi;
|
||||
private final SortedList<OfferBookListItem> sortedItems;
|
||||
private final ListChangeListener<TradeCurrency> tradeCurrencyListChangeListener;
|
||||
private final ListChangeListener<OfferBookListItem> filterItemsListener;
|
||||
|
@ -121,6 +126,8 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
|
||||
final StringProperty tradeCurrencyCode = new SimpleStringProperty();
|
||||
|
||||
private OfferView.OfferActionHandler offerActionHandler;
|
||||
|
||||
// If id is empty string we ignore filter (display all methods)
|
||||
|
||||
PaymentMethod selectedPaymentMethod = getShowAllEntryForPaymentMethod();
|
||||
|
@ -154,7 +161,9 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
PriceUtil priceUtil,
|
||||
OfferFilterService offerFilterService,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||
BsqFormatter bsqFormatter) {
|
||||
BsqFormatter bsqFormatter,
|
||||
BsqWalletService bsqWalletService,
|
||||
CoreApi coreApi) {
|
||||
super();
|
||||
|
||||
this.openOfferManager = openOfferManager;
|
||||
|
@ -173,6 +182,8 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
this.bsqFormatter = bsqFormatter;
|
||||
|
||||
this.filteredItems = new FilteredList<>(offerBook.getOfferBookListItems());
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.coreApi = coreApi;
|
||||
this.sortedItems = new SortedList<>(filteredItems);
|
||||
|
||||
tradeCurrencyListChangeListener = c -> fillAllTradeCurrencies();
|
||||
|
@ -207,7 +218,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
.filter(o -> o.getOffer().isUseMarketBasedPrice())
|
||||
.max(Comparator.comparing(o -> new DecimalFormat("#0.00").format(o.getOffer().getMarketPriceMargin() * 100).length()));
|
||||
|
||||
highestMarketPriceMarginOffer.ifPresent(offerBookListItem -> maxPlacesForMarketPriceMargin.set(formatMarketPriceMargin(offerBookListItem.getOffer(), false).length()));
|
||||
highestMarketPriceMarginOffer.ifPresent(offerBookListItem -> maxPlacesForMarketPriceMargin.set(formatMarketPriceMargin(offerBookListItem.getOffer()).length()));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -215,16 +226,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
protected void activate() {
|
||||
filteredItems.addListener(filterItemsListener);
|
||||
|
||||
String code = direction == OfferDirection.BUY ? preferences.getBuyScreenCurrencyCode() : preferences.getSellScreenCurrencyCode();
|
||||
if (code != null && !code.isEmpty() && !isShowAllEntry(code) &&
|
||||
CurrencyUtil.getTradeCurrency(code).isPresent()) {
|
||||
showAllTradeCurrenciesProperty.set(false);
|
||||
selectedTradeCurrency = CurrencyUtil.getTradeCurrency(code).get();
|
||||
} else {
|
||||
showAllTradeCurrenciesProperty.set(true);
|
||||
selectedTradeCurrency = GlobalSettings.getDefaultTradeCurrency();
|
||||
}
|
||||
tradeCurrencyCode.set(selectedTradeCurrency.getCode());
|
||||
updateSelectedTradeCurrency();
|
||||
|
||||
if (user != null) {
|
||||
disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty());
|
||||
|
@ -258,6 +260,11 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
void onTabSelected(boolean isSelected) {
|
||||
this.isTabSelected = isSelected;
|
||||
setMarketPriceFeedCurrency();
|
||||
|
||||
if (isTabSelected) {
|
||||
updateSelectedTradeCurrency();
|
||||
filterOffers();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -413,16 +420,12 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
return priceUtil.getMarketBasedPrice(offer, direction);
|
||||
}
|
||||
|
||||
String formatMarketPriceMargin(Offer offer, boolean decimalAligned) {
|
||||
String formatMarketPriceMargin(Offer offer) {
|
||||
String postFix = "";
|
||||
if (offer.isUseMarketBasedPrice()) {
|
||||
postFix = " (" + FormattingUtils.formatPercentagePrice(offer.getMarketPriceMargin()) + ")";
|
||||
}
|
||||
|
||||
if (decimalAligned) {
|
||||
postFix = FormattingUtils.fillUpPlacesWithEmptyStrings(postFix, maxPlacesForMarketPriceMargin.get());
|
||||
}
|
||||
|
||||
return postFix;
|
||||
}
|
||||
|
||||
|
@ -670,4 +673,43 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
PaymentMethod getShowAllEntryForPaymentMethod() {
|
||||
return PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG);
|
||||
}
|
||||
|
||||
public boolean isInstantPaymentMethod(Offer offer) {
|
||||
return offer.getPaymentMethod().equals(PaymentMethod.BLOCK_CHAINS_INSTANT);
|
||||
}
|
||||
|
||||
public PaymentAccount createBsqAccount(Offer offer) {
|
||||
var unusedBsqAddressAsString = bsqWalletService.getUnusedBsqAddressAsString();
|
||||
|
||||
return coreApi.createCryptoCurrencyPaymentAccount(DisplayUtils.createAssetsAccountName("BSQ", unusedBsqAddressAsString),
|
||||
"BSQ",
|
||||
unusedBsqAddressAsString,
|
||||
isInstantPaymentMethod(offer),
|
||||
false);
|
||||
}
|
||||
|
||||
public void setOfferActionHandler(OfferView.OfferActionHandler offerActionHandler) {
|
||||
this.offerActionHandler = offerActionHandler;
|
||||
}
|
||||
|
||||
public void onCreateOffer() {
|
||||
offerActionHandler.onCreateOffer(getSelectedTradeCurrency(), selectedPaymentMethod);
|
||||
}
|
||||
|
||||
public void onTakeOffer(Offer offer) {
|
||||
offerActionHandler.onTakeOffer(offer);
|
||||
}
|
||||
|
||||
private void updateSelectedTradeCurrency() {
|
||||
String code = direction == OfferDirection.BUY ? preferences.getBuyScreenCurrencyCode() : preferences.getSellScreenCurrencyCode();
|
||||
if (code != null && !code.isEmpty() && !isShowAllEntry(code) &&
|
||||
CurrencyUtil.getTradeCurrency(code).isPresent()) {
|
||||
showAllTradeCurrenciesProperty.set(false);
|
||||
selectedTradeCurrency = CurrencyUtil.getTradeCurrency(code).get();
|
||||
} else {
|
||||
showAllTradeCurrenciesProperty.set(true);
|
||||
selectedTradeCurrency = GlobalSettings.getDefaultTradeCurrency();
|
||||
}
|
||||
tradeCurrencyCode.set(selectedTradeCurrency.getCode());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,15 @@
|
|||
|
||||
package bisq.desktop.main.portfolio.pendingtrades.steps.seller;
|
||||
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.BusyAnimation;
|
||||
import bisq.desktop.components.InfoTextField;
|
||||
import bisq.desktop.components.TextFieldWithCopyIcon;
|
||||
import bisq.desktop.components.indicator.TxConfidenceIndicator;
|
||||
import bisq.desktop.main.MainView;
|
||||
import bisq.desktop.main.dao.DaoView;
|
||||
import bisq.desktop.main.dao.wallet.BsqWalletView;
|
||||
import bisq.desktop.main.dao.wallet.tx.BsqTxView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
|
||||
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
|
||||
|
@ -82,6 +87,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
public class SellerStep3View extends TradeStepView {
|
||||
|
||||
private Button confirmButton;
|
||||
private AutoTooltipButton showBsqWallet;
|
||||
private Label statusLabel;
|
||||
private BusyAnimation busyAnimation;
|
||||
private Subscription tradeStatePropertySubscription;
|
||||
|
@ -296,11 +302,21 @@ public class SellerStep3View extends TradeStepView {
|
|||
Tuple4<Button, BusyAnimation, Label, HBox> tuple = addButtonBusyAnimationLabelAfterGroup(gridPane, ++gridRow,
|
||||
Res.get("portfolio.pending.step3_seller.confirmReceipt"));
|
||||
|
||||
HBox hBox = tuple.fourth;
|
||||
GridPane.setColumnSpan(tuple.fourth, 2);
|
||||
confirmButton = tuple.first;
|
||||
confirmButton.setOnAction(e -> onPaymentReceived());
|
||||
busyAnimation = tuple.second;
|
||||
statusLabel = tuple.third;
|
||||
|
||||
if (trade.getOffer().getCurrencyCode().equals("BSQ")) {
|
||||
showBsqWallet = new AutoTooltipButton(Res.get("portfolio.pending.step3_seller.showBsqWallet"));
|
||||
hBox.getChildren().add(1, showBsqWallet);
|
||||
showBsqWallet.setOnAction(e -> {
|
||||
model.getNavigation().navigateTo(MainView.class, DaoView.class, BsqWalletView.class,
|
||||
BsqTxView.class);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ import bisq.core.locale.TradeCurrency;
|
|||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.user.DontShowAgainLookup;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
|
@ -122,13 +123,12 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
|
||||
private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically,
|
||||
avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle,
|
||||
notifyOnPreReleaseToggle;
|
||||
notifyOnPreReleaseToggle, isDaoFullNodeToggleButton, daoActivatedToggleButton, fullModeDaoMonitorToggleButton;
|
||||
private int gridRow = 0;
|
||||
private int displayCurrenciesGridRowIndex = 0;
|
||||
private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField,
|
||||
autoConfRequiredConfirmationsTf, autoConfServiceAddressTf, autoConfTradeLimitTf, /*referralIdInputTextField,*/
|
||||
autoConfRequiredConfirmationsTf, autoConfServiceAddressTf, autoConfTradeLimitTf,
|
||||
rpcUserTextField, blockNotifyPortTextField;
|
||||
private ToggleButton isDaoFullNodeToggleButton, daoActivatedToggleButton;
|
||||
private PasswordTextField rpcPwTextField;
|
||||
private TitledGroupBg daoOptionsTitledGroupBg;
|
||||
|
||||
|
@ -623,7 +623,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
}
|
||||
|
||||
private void initializeDaoOptions() {
|
||||
int rowSpan = DevEnv.isDaoActivated() ? 5 : 1;
|
||||
int rowSpan = DevEnv.isDaoActivated() ? 6 : 1;
|
||||
daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, rowSpan,
|
||||
Res.get("setting.preferences.daoOptions"), Layout.GROUP_DISTANCE);
|
||||
daoActivatedToggleButton = addSlideToggleButton(root, gridRow,
|
||||
|
@ -632,6 +632,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
return;
|
||||
}
|
||||
|
||||
fullModeDaoMonitorToggleButton = addSlideToggleButton(root, ++gridRow,
|
||||
Res.get("setting.preferences.dao.fullModeDaoMonitor"));
|
||||
|
||||
resyncDaoFromResourcesButton = addButton(root, ++gridRow, Res.get("setting.preferences.dao.resyncFromResources.label"));
|
||||
resyncDaoFromResourcesButton.setMaxWidth(Double.MAX_VALUE);
|
||||
GridPane.setHgrow(resyncDaoFromResourcesButton, Priority.ALWAYS);
|
||||
|
@ -1021,6 +1024,21 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
return;
|
||||
}
|
||||
|
||||
fullModeDaoMonitorToggleButton.setSelected(preferences.isUseFullModeDaoMonitor());
|
||||
fullModeDaoMonitorToggleButton.setOnAction(e -> {
|
||||
preferences.setUseFullModeDaoMonitor(fullModeDaoMonitorToggleButton.isSelected());
|
||||
if (fullModeDaoMonitorToggleButton.isSelected()) {
|
||||
String key = "fullModeDaoMonitor";
|
||||
if (DontShowAgainLookup.showAgain(key)) {
|
||||
new Popup().information(Res.get("setting.preferences.dao.fullModeDaoMonitor.popup"))
|
||||
.width(1000)
|
||||
.dontShowAgainId(key)
|
||||
.closeButtonText(Res.get("shared.iUnderstand"))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
boolean daoFullNode = preferences.isDaoFullNode();
|
||||
isDaoFullNodeToggleButton.setSelected(daoFullNode);
|
||||
|
||||
|
@ -1173,6 +1191,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
return;
|
||||
}
|
||||
|
||||
fullModeDaoMonitorToggleButton.setOnAction(null);
|
||||
resyncDaoFromResourcesButton.setOnAction(null);
|
||||
resyncDaoFromGenesisButton.setOnAction(null);
|
||||
bsqAverageTrimThresholdTextField.textProperty().removeListener(bsqAverageTrimThresholdListener);
|
||||
|
|
|
@ -6,8 +6,9 @@ import bisq.core.locale.Res;
|
|||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferDirection;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.offer.OfferDirection;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.VolumeUtil;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
@ -251,4 +252,21 @@ public class DisplayUtils {
|
|||
public static Coin reduceTo4Decimals(Coin coin, CoinFormatter coinFormatter) {
|
||||
return ParsingUtils.parseToCoin(coinFormatter.formatCoin(coin), coinFormatter);
|
||||
}
|
||||
|
||||
public static String createAccountName(String paymentMethodId, String name) {
|
||||
name = name.trim();
|
||||
name = StringUtils.abbreviate(name, 9);
|
||||
String method = Res.get(paymentMethodId);
|
||||
return method.concat(": ").concat(name);
|
||||
}
|
||||
|
||||
public static String createAssetsAccountName(PaymentAccount paymentAccount, String address) {
|
||||
String currency = paymentAccount.getSingleTradeCurrency() != null ? paymentAccount.getSingleTradeCurrency().getCode() : "";
|
||||
return createAssetsAccountName(currency, address);
|
||||
}
|
||||
|
||||
public static String createAssetsAccountName(String currency, String address) {
|
||||
address = StringUtils.abbreviate(address, 9);
|
||||
return currency.concat(": ").concat(address);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,13 +108,17 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Orientation;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -1218,4 +1222,28 @@ public class GUIUtil {
|
|||
return result.name();
|
||||
}
|
||||
}
|
||||
|
||||
public static ScrollPane createScrollPane() {
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
AnchorPane.setLeftAnchor(scrollPane, 0d);
|
||||
AnchorPane.setTopAnchor(scrollPane, 0d);
|
||||
AnchorPane.setRightAnchor(scrollPane, 0d);
|
||||
AnchorPane.setBottomAnchor(scrollPane, 0d);
|
||||
return scrollPane;
|
||||
}
|
||||
|
||||
public static void setDefaultTwoColumnConstraintsForGridPane(GridPane gridPane) {
|
||||
ColumnConstraints columnConstraints1 = new ColumnConstraints();
|
||||
columnConstraints1.setHalignment(HPos.RIGHT);
|
||||
columnConstraints1.setHgrow(Priority.NEVER);
|
||||
columnConstraints1.setMinWidth(200);
|
||||
ColumnConstraints columnConstraints2 = new ColumnConstraints();
|
||||
columnConstraints2.setHgrow(Priority.ALWAYS);
|
||||
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class DaoStateBlockListItemTest {
|
|||
|
||||
@Test
|
||||
public void testEqualsAndHashCode() {
|
||||
var block = new DaoStateBlock(new DaoStateHash(0, new byte[0], new byte[0]));
|
||||
var block = new DaoStateBlock(new DaoStateHash(0, new byte[0], true));
|
||||
var item1 = new DaoStateBlockListItem(block, newSupplier(1));
|
||||
var item2 = new DaoStateBlockListItem(block, newSupplier(2));
|
||||
var item3 = new DaoStateBlockListItem(block, newSupplier(1));
|
||||
|
|
|
@ -239,7 +239,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
assertEquals(0, model.maxPlacesForAmount.intValue());
|
||||
}
|
||||
|
||||
|
@ -253,7 +253,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
model.activate();
|
||||
|
||||
assertEquals(6, model.maxPlacesForAmount.intValue());
|
||||
|
@ -271,7 +271,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
model.activate();
|
||||
|
||||
assertEquals(15, model.maxPlacesForAmount.intValue());
|
||||
|
@ -290,7 +290,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
assertEquals(0, model.maxPlacesForVolume.intValue());
|
||||
}
|
||||
|
||||
|
@ -304,7 +304,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
model.activate();
|
||||
|
||||
assertEquals(5, model.maxPlacesForVolume.intValue());
|
||||
|
@ -322,7 +322,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
model.activate();
|
||||
|
||||
assertEquals(9, model.maxPlacesForVolume.intValue());
|
||||
|
@ -341,7 +341,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
assertEquals(0, model.maxPlacesForPrice.intValue());
|
||||
}
|
||||
|
||||
|
@ -355,7 +355,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
model.activate();
|
||||
|
||||
assertEquals(7, model.maxPlacesForPrice.intValue());
|
||||
|
@ -373,7 +373,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
assertEquals(0, model.maxPlacesForMarketPriceMargin.intValue());
|
||||
}
|
||||
|
||||
|
@ -408,7 +408,7 @@ public class OfferBookViewModelTest {
|
|||
offerBookListItems.addAll(item1, item2);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, priceFeedService,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
model.activate();
|
||||
|
||||
assertEquals(8, model.maxPlacesForMarketPriceMargin.intValue()); //" (1.97%)"
|
||||
|
@ -429,7 +429,7 @@ public class OfferBookViewModelTest {
|
|||
when(priceFeedService.getMarketPrice(anyString())).thenReturn(new MarketPrice("USD", 12684.0450, Instant.now().getEpochSecond(), true));
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter(), null, null);
|
||||
|
||||
final OfferBookListItem item = make(btcBuyItem.but(
|
||||
with(useMarketBasedPrice, true),
|
||||
|
|
|
@ -1945,6 +1945,7 @@ message PreferencesPayload {
|
|||
bool show_offers_matching_my_accounts = 59;
|
||||
bool deny_api_taker = 60;
|
||||
bool notify_on_pre_release = 61;
|
||||
bool use_full_mode_dao_monitor = 62;
|
||||
}
|
||||
|
||||
message AutoConfirmSettings {
|
||||
|
@ -2381,20 +2382,21 @@ message DaoStateStore {
|
|||
message DaoStateHash {
|
||||
int32 height = 1;
|
||||
bytes hash = 2;
|
||||
bytes prev_hash = 3;
|
||||
bytes prev_hash = 3 [deprecated = true];
|
||||
bool is_self_created = 4;
|
||||
}
|
||||
|
||||
message ProposalStateHash {
|
||||
int32 height = 1;
|
||||
bytes hash = 2;
|
||||
bytes prev_hash = 3;
|
||||
bytes prev_hash = 3 [deprecated = true];
|
||||
int32 num_proposals = 4;
|
||||
}
|
||||
|
||||
message BlindVoteStateHash {
|
||||
int32 height = 1;
|
||||
bytes hash = 2;
|
||||
bytes prev_hash = 3;
|
||||
bytes prev_hash = 3 [deprecated = true];
|
||||
int32 num_blind_votes = 4;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue