Fix bug with doublespend at create offer. Handle threading with rpc request. Support snapshots. Start support for P2P network (WIP).

This commit is contained in:
Manfred Karrer 2017-04-11 20:00:12 -05:00
parent a24ca8edaf
commit 60b75f0dbd
23 changed files with 562 additions and 209 deletions

View file

@ -39,7 +39,8 @@ class BsqCoinSelector extends BisqDefaultCoinSelector {
@Override
protected boolean isTxOutputSpendable(TransactionOutput output) {
return output.getParentTransaction() != null &&
return txOutputMap != null &&
output.getParentTransaction() != null &&
txOutputMap.isTxOutputUnSpent(output.getParentTransaction().getHashAsString(), output.getIndex());
}
}

View file

@ -24,6 +24,7 @@ import io.bisq.core.btc.exceptions.TransactionVerificationException;
import io.bisq.core.btc.exceptions.WalletException;
import io.bisq.core.dao.blockchain.BsqBlockchainManager;
import io.bisq.core.dao.blockchain.TxOutput;
import io.bisq.core.dao.blockchain.TxOutputMap;
import io.bisq.core.provider.fee.FeeService;
import io.bisq.core.user.Preferences;
import javafx.collections.FXCollections;
@ -37,12 +38,9 @@ import org.bitcoinj.wallet.CoinSelection;
import javax.inject.Inject;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkArgument;
@ -50,6 +48,7 @@ import static com.google.common.base.Preconditions.checkArgument;
public class BsqWalletService extends WalletService {
private final BsqBlockchainManager bsqBlockchainManager;
private TxOutputMap txOutputMap;
private final BsqCoinSelector bsqCoinSelector;
@Getter
private final ObservableList<Transaction> walletTransactions = FXCollections.observableArrayList();
@ -64,6 +63,7 @@ public class BsqWalletService extends WalletService {
@Inject
public BsqWalletService(WalletsSetup walletsSetup,
BsqBlockchainManager bsqBlockchainManager,
TxOutputMap txOutputMap,
Preferences preferences,
FeeService feeService) {
super(walletsSetup,
@ -71,6 +71,7 @@ public class BsqWalletService extends WalletService {
feeService);
this.bsqBlockchainManager = bsqBlockchainManager;
this.txOutputMap = txOutputMap;
this.bsqCoinSelector = new BsqCoinSelector(true);
walletsSetup.addSetupCompletedHandler(() -> {
@ -166,50 +167,48 @@ public class BsqWalletService extends WalletService {
///////////////////////////////////////////////////////////////////////////////////////////
private void updateBsqWalletTransactions() {
walletTransactions.setAll(getAllBsqTransactionsFromWallet());
walletTransactions.setAll(getUnconfirmedWalletTransactions());
walletTransactions.addAll(getConfirmedBsqWalletTransactions());
}
private Set<TransactionOutput> getAllBsqTxOutputsFromWallet() {
return getWalletTransactionOutputStream()
.filter(out -> out.getParentTransaction() != null && bsqBlockchainManager.getTxOutputMap()
.contains(out.getParentTransaction().getHashAsString(), out.getIndex()))
private Set<Transaction> getUnconfirmedWalletTransactions() {
return getTransactions(false).stream()
.filter(tx -> tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING)
.collect(Collectors.toSet());
}
private Set<TransactionOutput> getAllUnspentBsqTxOutputsFromWallet() {
return getAllBsqTxOutputsFromWallet().stream()
private Set<Transaction> getConfirmedBsqWalletTransactions() {
return getTransactions(false).stream()
.filter(tx -> tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
.flatMap(tx -> tx.getOutputs().stream())
.filter(out -> out.getParentTransaction() != null && txOutputMap
.contains(out.getParentTransaction().getHashAsString(), out.getIndex()))
.map(TransactionOutput::getParentTransaction)
.collect(Collectors.toSet());
}
// for sending funds
private Set<TransactionOutput> getMyUnspentBsqTxOutputsFromWallet() {
return wallet.calculateAllSpendCandidates().stream()
.filter(out -> out.getParentTransaction() != null && txOutputMap
.contains(out.getParentTransaction().getHashAsString(), out.getIndex()))
.filter(out -> {
final Transaction tx = out.getParentTransaction();
if (tx == null) {
return false;
} else {
final TxOutput txOutput = bsqBlockchainManager.getTxOutputMap()
.get(tx.getHashAsString(), out.getIndex());
final TxOutput txOutput = txOutputMap.get(tx.getHashAsString(), out.getIndex());
return txOutput != null && txOutput.isUnSpend();
}
})
.collect(Collectors.toSet());
}
private Set<TransactionOutput> getMyUnspentBsqTxOutputsFromWallet() {
return getAllUnspentBsqTxOutputsFromWallet().stream()
.filter(out -> out.isMine(wallet))
.collect(Collectors.toSet());
}
private Stream<TransactionOutput> getWalletTransactionOutputStream() {
return getTransactions(true).stream()
.flatMap(tx -> tx.getOutputs().stream());
}
private Set<Transaction> getAllBsqTransactionsFromWallet() {
return getAllBsqTxOutputsFromWallet().stream()
.map(TransactionOutput::getParentTransaction)
.collect(Collectors.toSet());
}
// TODO
public Set<Transaction> getInvalidBsqTransactions() {
Set<Transaction> txsWithOutputsFoundInBsqTxo = getAllBsqTransactionsFromWallet();
/* Set<Transaction> txsWithOutputsFoundInBsqTxo = getAllBsqTransactionsFromWallet();
Set<Transaction> walletTxs = getTransactions(true).stream().collect(Collectors.toSet());
checkArgument(walletTxs.size() >= txsWithOutputsFoundInBsqTxo.size(),
"We cannot have more txsWithOutputsFoundInBsqTxo than walletTxs");
@ -227,9 +226,9 @@ public class BsqWalletService extends WalletService {
.forEach(map::remove);
return new HashSet<>(map.values());
} else {
return new HashSet<>();
}
} else {*/
return new HashSet<>();
// }
}

View file

@ -56,8 +56,7 @@ public class WalletUtils {
return bitcoinNetwork;
}
/* public static boolean isRegTest() {
final Preferences preferences = Preferences.INSTANCE;
return preferences != null ? preferences.getBitcoinNetwork().equals(BitcoinNetwork.REGTEST) : false;
}*/
public static boolean isRegTest() {
return WalletUtils.getBitcoinNetwork() == BitcoinNetwork.REGTEST;
}
}

View file

@ -22,6 +22,7 @@ import io.bisq.common.app.AppModule;
import io.bisq.core.dao.blockchain.BsqBlockchainManager;
import io.bisq.core.dao.blockchain.BsqBlockchainRpcService;
import io.bisq.core.dao.blockchain.BsqBlockchainService;
import io.bisq.core.dao.blockchain.TxOutputMap;
import io.bisq.core.dao.blockchain.json.JsonExporter;
import io.bisq.core.dao.compensation.CompensationRequestManager;
import io.bisq.core.dao.vote.VotingDefaultValues;
@ -45,6 +46,7 @@ public class DaoModule extends AppModule {
bind(DaoManager.class).in(Singleton.class);
bind(BsqBlockchainManager.class).in(Singleton.class);
bind(BsqBlockchainService.class).to(BsqBlockchainRpcService.class).in(Singleton.class);
bind(TxOutputMap.class).in(Singleton.class);
bind(JsonExporter.class).in(Singleton.class);
bind(DaoPeriodService.class).in(Singleton.class);
bind(VotingService.class).in(Singleton.class);

View file

@ -18,13 +18,18 @@
package io.bisq.core.dao.blockchain;
import com.google.inject.Inject;
import io.bisq.common.app.DevEnv;
import io.bisq.common.handlers.ErrorMessageHandler;
import io.bisq.common.storage.Storage;
import io.bisq.core.app.BisqEnvironment;
import io.bisq.core.btc.BitcoinNetwork;
import io.bisq.core.btc.wallet.WalletUtils;
import io.bisq.core.dao.RpcOptionKeys;
import io.bisq.core.dao.blockchain.json.JsonExporter;
import io.bisq.network.p2p.P2PService;
import io.bisq.network.p2p.storage.HashMapChangedListener;
import io.bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import io.bisq.network.p2p.storage.payload.StoragePayload;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.inject.Named;
@ -32,13 +37,38 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
// We are in UserThread context. We get callbacks from threaded classes which are already mapped to the UserThread.
@Slf4j
public class BsqBlockchainManager {
///////////////////////////////////////////////////////////////////////////////////////////
// Interface
///////////////////////////////////////////////////////////////////////////////////////////
public interface TxOutputMapListener {
void onTxOutputMapChanged(TxOutputMap txOutputMap);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Static fields
///////////////////////////////////////////////////////////////////////////////////////////
// Modulo of blocks for making snapshots of UTXO.
// We stay also the value behind for safety against reorgs.
// E.g. for SNAPSHOT_TRIGGER = 30:
// If we are block 119 and last snapshot was 60 then we get a new trigger for a snapshot at block 120 and
// new snapshot is block 90. We only persist at the new snapshot, so we always re-parse from latest snapshot after
// a restart.
// As we only store snapshots when ne Txos are added it might be that there are bigger gaps than SNAPSHOT_TRIGGER.
private static final int SNAPSHOT_TRIGGER = 10000; // set high to deactivate
public static int getSnapshotTrigger() {
return SNAPSHOT_TRIGGER;
}
//mainnet
private static final String GENESIS_TX_ID = "cabbf6073aea8f22ec678e973ac30c6d8fc89321011da6a017f63e67b9f66667";
// block 300000 2014-05-10
@ -49,35 +79,45 @@ public class BsqBlockchainManager {
private static final String REG_TEST_GENESIS_TX_ID = "3bc7bc9484e112ec8ddd1a1c984379819245ac463b9ce40fa8b5bf771c0f9236";
private static final int REG_TEST_GENESIS_BLOCK_HEIGHT = 102;
// Modulo of blocks for making snapshots of UTXO.
// We stay also the value behind for safety against reorgs.
// E.g. for SNAPSHOT_TRIGGER = 30:
// If we are block 119 and last snapshot was 60 then we get a new trigger for a snapshot at block 120 and
// new snapshot is block 90. We only persist at the new snapshot, so we always re-parse from latest snapshot after
// a restart.
private static final int SNAPSHOT_TRIGGER = 300000;
public static int getSnapshotTrigger() {
return SNAPSHOT_TRIGGER;
private static String getGenesisTxId() {
return WalletUtils.isRegTest() ? REG_TEST_GENESIS_TX_ID : GENESIS_TX_ID;
}
private static int getGenesisBlockHeight() {
return WalletUtils.isRegTest() ? REG_TEST_GENESIS_BLOCK_HEIGHT : GENESIS_BLOCK_HEIGHT;
}
public static int getSnapshotHeight(int height) {
final int trigger = Math.round(height / SNAPSHOT_TRIGGER) * SNAPSHOT_TRIGGER - SNAPSHOT_TRIGGER;
return Math.max(getGenesisBlockHeight() + SNAPSHOT_TRIGGER, trigger);
}
public static boolean triggersSnapshot(int height) {
return height - SNAPSHOT_TRIGGER == getSnapshotHeight(height);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
///////////////////////////////////////////////////////////////////////////////////////////
private final BsqBlockchainService blockchainService;
private final P2PService p2PService;
private final JsonExporter jsonExporter;
private final BitcoinNetwork bitcoinNetwork;
private final List<TxOutputMap.Listener> txOutputMapListeners = new ArrayList<>();
private final List<TxOutputMapListener> txOutputMapListeners = new ArrayList<>();
@Getter
private final TxOutputMap txOutputMap;
private final TxOutputMap snapshotTxOutputMap = new TxOutputMap();
@Getter
private int chainHeadHeight;
@Getter
@Setter
private int snapshotHeight = 0;
@Getter
private boolean parseBlockchainComplete;
private final boolean connectToBtcCore;
private transient final Storage<TxOutputMap> snapshotTxOutputMapStorage;
///////////////////////////////////////////////////////////////////////////////////////////
@ -86,31 +126,62 @@ public class BsqBlockchainManager {
@Inject
public BsqBlockchainManager(BsqBlockchainService blockchainService,
BisqEnvironment bisqEnvironment,
P2PService p2PService,
TxOutputMap txOutputMap,
JsonExporter jsonExporter,
@Named(Storage.DIR_KEY) File storageDir,
@Named(RpcOptionKeys.RPC_USER) String rpcUser) {
this.blockchainService = blockchainService;
this.p2PService = p2PService;
this.txOutputMap = txOutputMap;
this.jsonExporter = jsonExporter;
this.bitcoinNetwork = bisqEnvironment.getBitcoinNetwork();
snapshotTxOutputMapStorage = new Storage<>(storageDir);
connectToBtcCore = rpcUser != null && !rpcUser.isEmpty();
txOutputMap = new TxOutputMap(storageDir);
txOutputMap.addListener(bsqTxOutputMap -> onBsqTxoChanged());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) {
TxOutputMap persisted = snapshotTxOutputMapStorage.initAndGetPersisted(snapshotTxOutputMap, "TxOutputMap");
if (persisted != null) {
txOutputMap.putAll(persisted.getMap());
txOutputMap.setSnapshotHeight(persisted.getSnapshotHeight());
txOutputMap.setBlockHeight(persisted.getBlockHeight());
}
if (!txOutputMap.isEmpty())
onBsqTxoChanged();
if (connectToBtcCore)
blockchainService.setup(this::onSetupComplete, errorMessageHandler);
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
@Override
public void onAdded(ProtectedStorageEntry data) {
final StoragePayload storagePayload = data.getStoragePayload();
if (storagePayload instanceof TxOutput)
add((TxOutput) storagePayload);
}
@Override
public void onRemoved(ProtectedStorageEntry data) {
// We don't remove items
}
});
}
public void addTxOutputMapListener(TxOutputMap.Listener listener) {
txOutputMapListeners.add(listener);
public void add(TxOutput txOutput) {
if (!txOutputMap.contains(txOutput.getTxIdIndexTuple())) {
txOutputMap.put(txOutput);
onBsqTxoChanged();
}
}
public void addTxOutputMapListener(BsqBlockchainManager.TxOutputMapListener txOutputMapListener) {
txOutputMapListeners.add(txOutputMapListener);
}
@ -122,16 +193,20 @@ public class BsqBlockchainManager {
final int genesisBlockHeight = getGenesisBlockHeight();
final String genesisTxId = getGenesisTxId();
int startBlockHeight = Math.max(genesisBlockHeight, txOutputMap.getSnapshotHeight());
log.info("parseBlocks with: genesisTxId={}\ngenesisBlockHeight={}\nstartBlockHeight={}\nsnapshotHeight={}",
log.info("parseBlocks with:\n" +
"genesisTxId={}\n" +
"genesisBlockHeight={}\n" +
"startBlockHeight={}\n" +
"snapshotHeight={}",
genesisTxId, genesisBlockHeight, startBlockHeight, txOutputMap.getSnapshotHeight());
// If we have past data we notify our listeners
if (txOutputMap.getSnapshotHeight() > 0)
onBsqTxoChanged();
parseBlocks(startBlockHeight,
genesisBlockHeight,
genesisTxId);
// If we have past data we notify our listeners
if (txOutputMap.getSnapshotHeight() > 0)
onBsqTxoChanged();
}
// TODO handle reorgs
@ -144,9 +219,16 @@ public class BsqBlockchainManager {
genesisBlockHeight,
genesisTxId,
txOutputMap,
() -> {
snapshotTxOutputMap -> {
// we are done but it might be that new blocks have arrived in the meantime,
// so we try again with startBlockHeight set to current chainHeadHeight
//applyNewTxOutputMapAndPersistSnapshot(snapshotTxOutputMap, snapshotTxOutputMap.getLastBlockHeight());
applyNewTxOutputMap(snapshotTxOutputMap);
checkForSnapshotUpdate(snapshotTxOutputMap.getBlockHeight());
}, chainTipTxOutputMap -> {
// we are done but it might be that new blocks have arrived in the meantime,
// so we try again with startBlockHeight set to current chainHeadHeight
applyNewTxOutputMap(chainTipTxOutputMap);
parseBlocks(chainHeadHeight,
genesisBlockHeight,
genesisTxId);
@ -165,7 +247,9 @@ public class BsqBlockchainManager {
genesisBlockHeight,
genesisTxId,
txOutputMap,
() -> {
newTxOutputMap -> {
applyNewTxOutputMap(newTxOutputMap);
checkForSnapshotUpdate(bsqBlock.getHeight());
log.debug("new block parsed. bsqBlock={}", bsqBlock);
}, throwable -> {
log.error(throwable.toString());
@ -179,21 +263,41 @@ public class BsqBlockchainManager {
});
}
private void applyNewTxOutputMap(TxOutputMap newTxOutputMap) {
this.txOutputMap.putAll(newTxOutputMap.getMap());
txOutputMapListeners.stream().forEach(l -> l.onTxOutputMapChanged(txOutputMap));
}
private void checkForSnapshotUpdate(int blockHeight) {
final int snapshotHeight = getSnapshotHeight(blockHeight);
final int lastSnapshotHeight = txOutputMap.getSnapshotHeight();
log.debug("checkForSnapshotUpdate: snapshotHeight={}, lastSnapshotHeight={} , lastBlockHeight={} ",
snapshotHeight,
lastSnapshotHeight,
txOutputMap.getBlockHeight());
if (blockHeight > snapshotHeight && lastSnapshotHeight < snapshotHeight) {
snapshotTxOutputMap.putAll(TxOutputMap.getClonedMapUpToHeight(txOutputMap, snapshotHeight));
snapshotTxOutputMap.setSnapshotHeight(snapshotHeight);
snapshotTxOutputMap.setBlockHeight(blockHeight);
txOutputMap.setSnapshotHeight(snapshotHeight);
snapshotTxOutputMapStorage.queueUpForSave();
if (DevEnv.DEV_MODE) {
snapshotTxOutputMap.values().stream().mapToInt(TxOutput::getBlockHeight).max()
.ifPresent(maxHeight -> {
log.error("max blockHeight in snapshotTxOutputMap=" + maxHeight);
checkArgument(maxHeight <= snapshotHeight, "max blockHeight in snapshotTxOutputMap must " +
"not be higher than snapshotHeight");
});
}
log.error("Saved txOutputMap to disc. snapshotHeight={}, blockHeight={}",
snapshotHeight, blockHeight);
snapshotTxOutputMap.printSize();
}
}
private void onBsqTxoChanged() {
txOutputMapListeners.stream().forEach(e -> e.onMapChanged(txOutputMap));
txOutputMapListeners.stream().forEach(e -> e.onTxOutputMapChanged(txOutputMap));
jsonExporter.export(txOutputMap);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
private String getGenesisTxId() {
return bitcoinNetwork == BitcoinNetwork.REGTEST ? REG_TEST_GENESIS_TX_ID : GENESIS_TX_ID;
}
private int getGenesisBlockHeight() {
return bitcoinNetwork == BitcoinNetwork.REGTEST ? REG_TEST_GENESIS_BLOCK_HEIGHT : GENESIS_BLOCK_HEIGHT;
}
}

View file

@ -30,10 +30,12 @@ import com.neemre.btcdcli4j.core.domain.RawTransaction;
import com.neemre.btcdcli4j.daemon.BtcdDaemonImpl;
import com.neemre.btcdcli4j.daemon.event.BlockListener;
import io.bisq.common.UserThread;
import io.bisq.common.crypto.KeyRing;
import io.bisq.common.handlers.ErrorMessageHandler;
import io.bisq.common.handlers.ResultHandler;
import io.bisq.common.util.Utilities;
import io.bisq.core.dao.RpcOptionKeys;
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@ -71,10 +73,12 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public BsqBlockchainRpcService(@Named(RpcOptionKeys.RPC_USER) String rpcUser,
public BsqBlockchainRpcService(KeyRing keyRing,
@Named(RpcOptionKeys.RPC_USER) String rpcUser,
@Named(RpcOptionKeys.RPC_PASSWORD) String rpcPassword,
@Named(RpcOptionKeys.RPC_PORT) String rpcPort,
@Named(RpcOptionKeys.RPC_BLOCK_NOTIFICATION_PORT) String rpcBlockPort) {
super(keyRing);
this.rpcUser = rpcUser;
this.rpcPassword = rpcPassword;
this.rpcPort = rpcPort;
@ -159,28 +163,40 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
int genesisBlockHeight,
String genesisTxId,
TxOutputMap txOutputMap,
ResultHandler resultHandler,
Consumer<TxOutputMap> snapShotHandler,
Consumer<TxOutputMap> resultHandler,
Consumer<Throwable> errorHandler) {
ListenableFuture<Void> future = parseBlockchainExecutor.submit(() -> {
ListenableFuture<TxOutputMap> future = parseBlockchainExecutor.submit(() -> {
long startTs = System.currentTimeMillis();
BsqParser bsqParser = new BsqParser(this);
// txOutputMap us used in UserThread context, so we clone
final TxOutputMap clonedMap = TxOutputMap.getClonedMap(txOutputMap);
bsqParser.parseBlocks(startBlockHeight,
chainHeadHeight,
genesisBlockHeight,
genesisTxId, txOutputMap);
genesisTxId,
clonedMap,
clonedSnapShotMap -> {
// We map to UserThread. We don't need to clone as it was created already newly in the parser.
UserThread.execute(() -> snapShotHandler.accept(clonedSnapShotMap));
});
log.info("parseBlockchain took {} ms", System.currentTimeMillis() - startTs);
return null;
return clonedMap;
});
Futures.addCallback(future, new FutureCallback<Void>() {
Futures.addCallback(future, new FutureCallback<TxOutputMap>() {
@Override
public void onSuccess(Void ignore) {
UserThread.execute(resultHandler::handleResult);
public void onSuccess(TxOutputMap clonedMap) {
UserThread.execute(() -> {
// We map to UserThread. Map was already cloned
UserThread.execute(() -> resultHandler.accept(clonedMap));
});
}
@Override
public void onFailure(@NotNull Throwable throwable) {
errorHandler.accept(throwable);
// We map to UserThread.
UserThread.execute(() -> errorHandler.accept(throwable));
}
});
}
@ -190,26 +206,32 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
int genesisBlockHeight,
String genesisTxId,
TxOutputMap txOutputMap,
ResultHandler resultHandler,
Consumer<TxOutputMap> resultHandler,
Consumer<Throwable> errorHandler) {
ListenableFuture<Void> future = parseBlockchainExecutor.submit(() -> {
ListenableFuture<TxOutputMap> future = parseBlockchainExecutor.submit(() -> {
BsqParser bsqParser = new BsqParser(this);
// txOutputMap us used in UserThread context, so we clone);
final TxOutputMap clonedMap = TxOutputMap.getClonedMap(txOutputMap);
bsqParser.parseBlock(block,
genesisBlockHeight,
genesisTxId,
txOutputMap);
return null;
clonedMap);
return clonedMap;
});
Futures.addCallback(future, new FutureCallback<Void>() {
Futures.addCallback(future, new FutureCallback<TxOutputMap>() {
@Override
public void onSuccess(Void ignore) {
UserThread.execute(resultHandler::handleResult);
public void onSuccess(TxOutputMap clonedMap) {
UserThread.execute(() -> {
// We map to UserThread. Map was already cloned
resultHandler.accept(clonedMap);
});
}
@Override
public void onFailure(@NotNull Throwable throwable) {
errorHandler.accept(throwable);
// We map to UserThread.
UserThread.execute(() -> errorHandler.accept(throwable));
}
});
}
@ -221,7 +243,7 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
public void blockDetected(com.neemre.btcdcli4j.core.domain.Block btcdBlock) {
if (btcdBlock != null) {
UserThread.execute(() -> {
log.info("new block detected " + btcdBlock.getHash());
log.info("New block received: height={}, id={}", btcdBlock.getHeight(), btcdBlock.getHash());
blockHandler.accept(new BsqBlock(btcdBlock.getTx(), btcdBlock.getHeight()));
});
}
@ -265,9 +287,10 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
.map(rawOutput -> new TxOutput(rawOutput.getN(),
rawOutput.getValue().movePointRight(8).longValue(),
rawTransaction.getTxId(),
rawOutput.getScriptPubKey(),
new PubKeyScript(rawOutput.getScriptPubKey()),
blockHeight,
time))
time,
signaturePubKey))
.collect(Collectors.toList());
return new Tx(txId,
txInputs,

View file

@ -22,21 +22,25 @@ import com.google.inject.Inject;
import com.neemre.btcdcli4j.core.BitcoindException;
import com.neemre.btcdcli4j.core.CommunicationException;
import com.neemre.btcdcli4j.core.domain.RawTransaction;
import io.bisq.common.crypto.KeyRing;
import io.bisq.common.handlers.ErrorMessageHandler;
import io.bisq.common.handlers.ResultHandler;
import lombok.extern.slf4j.Slf4j;
import java.security.PublicKey;
import java.util.function.Consumer;
@Slf4j
abstract public class BsqBlockchainService {
protected final PublicKey signaturePubKey;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public BsqBlockchainService() {
public BsqBlockchainService(KeyRing keyRing) {
signaturePubKey = keyRing.getPubKeyRing().getSignaturePubKey();
}
@ -53,18 +57,19 @@ abstract public class BsqBlockchainService {
int genesisBlockHeight,
String genesisTxId,
TxOutputMap txOutputMap,
ResultHandler resultHandler,
Consumer<TxOutputMap> snapShotHandler,
Consumer<TxOutputMap> resultHandler,
Consumer<Throwable> errorHandler);
abstract void addBlockHandler(Consumer<BsqBlock> onNewBlockHandler);
abstract void parseBlock(BsqBlock block,
int genesisBlockHeight,
String genesisTxId,
TxOutputMap txOutputMap,
ResultHandler resultHandler,
Consumer<TxOutputMap> resultHandler,
Consumer<Throwable> errorHandler);
abstract void addBlockHandler(Consumer<BsqBlock> onNewBlockHandler);
///////////////////////////////////////////////////////////////////////////////////////////
// Blocking methods

View file

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
@ -53,7 +54,8 @@ public class BsqParser {
int chainHeadHeight,
int genesisBlockHeight,
String genesisTxId,
TxOutputMap txOutputMap) throws BsqBlockchainException {
TxOutputMap txOutputMap,
Consumer<TxOutputMap> snapShotHandler) throws BsqBlockchainException {
try {
log.info("chainHeadHeight=" + chainHeadHeight);
long startTotalTs = System.currentTimeMillis();
@ -70,6 +72,15 @@ public class BsqParser {
genesisTxId,
txOutputMap);
txOutputMap.setBlockHeight(height);
if (BsqBlockchainManager.triggersSnapshot(height)) {
// We clone the map to isolate thread context. TxOutputMap is used in UserThread.
final TxOutputMap clonedSnapShotMap = TxOutputMap.getClonedMapUpToHeight(txOutputMap,
BsqBlockchainManager.getSnapshotHeight(height));
snapShotHandler.accept(clonedSnapShotMap);
}
/* StringBuilder sb = new StringBuilder("recursionMap:\n");
List<String> list = new ArrayList<>();
//recursionMap.entrySet().stream().forEach(e -> sb.append(e.getKey()).append(": ").append(e.getValue()).append("\n"));
@ -86,7 +97,10 @@ public class BsqParser {
(height - startBlockHeight + 1));
Profiler.printSystemLoad(log);*/
}
log.info("Parsing for all blocks since genesis took {} ms", System.currentTimeMillis() - startTotalTs);
log.info("Parsing for blocks {} to {} took {} ms",
startBlockHeight,
chainHeadHeight,
System.currentTimeMillis() - startTotalTs);
} catch (Throwable t) {
log.error(t.toString());
t.printStackTrace();
@ -125,18 +139,6 @@ public class BsqParser {
// one get resolved.
// Lately there is a patter with 24 iterations observed
parseTransactions(block.getTxList(), txOutputMap, blockHeight, 0, 5300);
// We persist only at certain snapshots. They have a safety distance from head so re-orgs should not cause
// issues in our persisted data.
int trigger = BsqBlockchainManager.getSnapshotTrigger();
int snapshotHeight = txOutputMap.getSnapshotHeight();
if (blockHeight % trigger == 0 && blockHeight > snapshotHeight - trigger) {
snapshotHeight = blockHeight - trigger;
log.info("We reached a new snapshot trigger at height {}. New snapshotHeight is {}",
blockHeight, snapshotHeight);
txOutputMap.setSnapshotHeight(snapshotHeight);
txOutputMap.persist();
}
}
@VisibleForTesting

View file

@ -17,10 +17,17 @@
package io.bisq.core.dao.blockchain;
import io.bisq.common.app.Version;
import io.bisq.common.util.JsonExclude;
import lombok.Value;
import java.io.Serializable;
@Value
public class SpendInfo {
public class SpendInfo implements Serializable {
@JsonExclude
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
private final long blockHeight;
private final String txId;
private final int inputIndex;

View file

@ -17,22 +17,34 @@
package io.bisq.core.dao.blockchain;
import com.neemre.btcdcli4j.core.domain.PubKeyScript;
import com.google.protobuf.Message;
import io.bisq.common.app.DevEnv;
import io.bisq.common.app.Version;
import io.bisq.common.util.JsonExclude;
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
import io.bisq.network.p2p.storage.payload.LazyProcessedStoragePayload;
import io.bisq.network.p2p.storage.payload.PersistedStoragePayload;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Getter
@EqualsAndHashCode
@Slf4j
public class TxOutput implements Serializable {
public class TxOutput implements LazyProcessedStoragePayload, PersistedStoragePayload {
@JsonExclude
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
@JsonExclude
public static final long TTL = TimeUnit.DAYS.toMillis(30);
// Immutable
private final int index;
private final long value;
@ -41,6 +53,8 @@ public class TxOutput implements Serializable {
private final int blockHeight;
private final long time;
private final String txVersion = Version.BSQ_TX_VERSION;
@JsonExclude
private PublicKey signaturePubKey;
// Mutable
@Setter
@ -62,18 +76,43 @@ public class TxOutput implements Serializable {
@Nullable
private String address;
// Should be only used in emergency case if we need to add data but do not want to break backward compatibility
// at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new
// field in a class would break that hash and therefore break the storage mechanism.
@Getter
@Nullable
private HashMap<String, String> extraDataMap;
public TxOutput(int index,
long value,
String txId,
PubKeyScript pubKeyScript,
int blockHeight,
long time) {
long time,
PublicKey signaturePubKey) {
this.index = index;
this.value = value;
this.txId = txId;
this.pubKeyScript = pubKeyScript;
this.blockHeight = blockHeight;
this.time = time;
this.signaturePubKey = signaturePubKey;
}
@Override
public long getTTL() {
return TTL;
}
@Override
public PublicKey getOwnerPubKey() {
return signaturePubKey;
}
@Nullable
@Override
public Map<String, String> getExtraDataMap() {
return extraDataMap;
}
public List<String> getAddresses() {
@ -116,6 +155,14 @@ public class TxOutput implements Serializable {
return txId + ":" + index;
}
public TxIdIndexTuple getTxIdIndexTuple() {
return new TxIdIndexTuple(txId, index);
}
@Override
public Message toProto() {
return null;
}
@Override
public String toString() {

View file

@ -17,58 +17,73 @@
package io.bisq.core.dao.blockchain;
import io.bisq.common.storage.Storage;
import io.bisq.common.util.Utilities;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
// Map of any ever existing TxOutput which was a valid BSQ
// Map of any TxOutput which was ever used in context of a BSQ TX.
// Outputs can come from various txs :
// - genesis tx
// - spend BSQ tx
// - burn fee
// - voting
// - comp. request
// - sponsoring tx (new genesis)
@Slf4j
public class TxOutputMap implements Serializable {
///////////////////////////////////////////////////////////////////////////////////////////
// Interface
// Statics
///////////////////////////////////////////////////////////////////////////////////////////
public interface Listener {
void onMapChanged(TxOutputMap txOutputMap);
public static TxOutputMap getClonedMap(TxOutputMap txOutputMap) {
return new TxOutputMap(txOutputMap);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Instance fields
///////////////////////////////////////////////////////////////////////////////////////////
public static TxOutputMap getClonedMapUpToHeight(TxOutputMap txOutputMap, int snapshotHeight) {
final TxOutputMap txOutputMapClone = new TxOutputMap();
txOutputMapClone.setBlockHeight(txOutputMap.getBlockHeight());
txOutputMapClone.setSnapshotHeight(txOutputMap.getSnapshotHeight());
Map<TxIdIndexTuple, TxOutput> map = txOutputMap.entrySet().stream()
.filter(entry -> entry.getValue().getBlockHeight() <= snapshotHeight)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
txOutputMapClone.putAll(map);
// We don't use a Lombok delegate here as we want control the access to our map
return txOutputMapClone;
}
@Getter
private HashMap<TxIdIndexTuple, TxOutput> map = new HashMap<>();
@Getter
@Setter
private int snapshotHeight = 0;
private transient final Storage<TxOutputMap> storage;
private final List<Listener> listeners = new ArrayList<>();
@Getter
@Setter
private int blockHeight;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public TxOutputMap(File storageDir) {
storage = new Storage<>(storageDir);
TxOutputMap persisted = storage.initAndGetPersisted(this, "BsqTxOutputMap");
if (persisted != null) {
map.putAll(persisted.getMap());
snapshotHeight = persisted.getSnapshotHeight();
}
public TxOutputMap() {
}
private TxOutputMap(TxOutputMap txOutputMap) {
map = txOutputMap.getMap();
snapshotHeight = txOutputMap.getSnapshotHeight();
blockHeight = txOutputMap.getBlockHeight();
}
@ -86,38 +101,49 @@ public class TxOutputMap implements Serializable {
return txOutput != null && txOutput.hasBurnedFee();
}
public void persist() {
storage.queueUpForSave();
}
public void addListener(Listener listener) {
listeners.add(listener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Delegated map methods
///////////////////////////////////////////////////////////////////////////////////////////
public Object put(TxOutput txOutput) {
final TxOutput result = map.put(new TxIdIndexTuple(txOutput.getTxId(), txOutput.getIndex()), txOutput);
listeners.stream().forEach(l -> l.onMapChanged(this));
return result;
return map.put(txOutput.getTxIdIndexTuple(), txOutput);
}
public void putAll(Map<TxIdIndexTuple, TxOutput> txOutputs) {
map.putAll(txOutputs);
}
public void putAll(TxOutputMap txOutputMap) {
map.putAll(txOutputMap.getMap());
}
@Nullable
public TxOutput get(String txId, int index) {
return map.get(new TxIdIndexTuple(txId, index));
return get(new TxIdIndexTuple(txId, index));
}
@Nullable
public TxOutput get(TxIdIndexTuple txIdIndexTuple) {
return map.get(txIdIndexTuple);
}
public boolean contains(String txId, int index) {
return map.containsKey(new TxIdIndexTuple(txId, index));
return contains(new TxIdIndexTuple(txId, index));
}
public boolean contains(TxIdIndexTuple txIdIndexTuple) {
return map.containsKey(txIdIndexTuple);
}
public Collection<TxOutput> values() {
return map.values();
}
public Set<Map.Entry<TxIdIndexTuple, TxOutput>> entrySet() {
return map.entrySet();
}
public boolean isEmpty() {
return map.isEmpty();
}
@ -128,7 +154,11 @@ public class TxOutputMap implements Serializable {
@Override
public String toString() {
return "BsqTxOutputMap " + map.toString();
return "TxOutputMap " + map.toString();
}
public void printSize() {
log.info("Nr of entries={}; Size in kb={}", size(), Utilities.serialize(this).length / 1000);
}
}

View file

@ -0,0 +1,54 @@
/*
* 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 io.bisq.core.dao.blockchain.btcd;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import io.bisq.common.app.Version;
import io.bisq.common.util.JsonExclude;
import lombok.*;
import java.io.Serializable;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class PubKeyScript implements Serializable {
@JsonExclude
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
private Integer reqSigs;
private ScriptTypes type;
private List<String> addresses;
private String asm;
private String hex;
public PubKeyScript(com.neemre.btcdcli4j.core.domain.PubKeyScript scriptPubKey) {
this(scriptPubKey.getReqSigs(),
ScriptTypes.forName(scriptPubKey.getType().getName()),
scriptPubKey.getAddresses(),
scriptPubKey.getAsm(),
scriptPubKey.getHex());
}
}

View file

@ -0,0 +1,62 @@
/*
* 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 io.bisq.core.dao.blockchain.btcd;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
@ToString
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public enum ScriptTypes implements Serializable {
PUB_KEY("pubkey"),
PUB_KEY_HASH("pubkeyhash"),
SCRIPT_HASH("scripthash"),
MULTISIG("multisig"),
NULL_DATA("nulldata"),
NONSTANDARD("nonstandard");
private final String name;
@JsonValue
public String getName() {
return name;
}
@JsonCreator
public static ScriptTypes forName(String name) {
if (name != null) {
for (ScriptTypes scriptType : ScriptTypes.values()) {
if (name.equals(scriptType.getName())) {
return scriptType;
}
}
}
throw new IllegalArgumentException("Expected the argument to be a valid 'bitcoind' script type, "
+ "but was invalid/unsupported instead.");
}
}

View file

@ -18,7 +18,6 @@
package io.bisq.core.dao.blockchain.json;
import com.google.inject.Inject;
import com.neemre.btcdcli4j.core.domain.PubKeyScript;
import io.bisq.common.storage.PlainTextWrapper;
import io.bisq.common.storage.Storage;
import io.bisq.common.util.Utilities;
@ -26,6 +25,7 @@ import io.bisq.core.dao.RpcOptionKeys;
import io.bisq.core.dao.blockchain.SpendInfo;
import io.bisq.core.dao.blockchain.TxOutput;
import io.bisq.core.dao.blockchain.TxOutputMap;
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
import javax.inject.Named;
import java.io.File;

View file

@ -26,7 +26,7 @@ public class DontShowAgainLookup {
}
public static boolean showAgain(String key) {
return !preferences.showAgain(key);
return preferences.showAgain(key);
}
public static void dontShowAgain(String key, boolean dontShowAgain) {

View file

@ -26,9 +26,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.math.BigDecimal;
import java.net.URL;
import java.util.*;
import static org.junit.Assert.*;
@ -63,10 +61,7 @@ public class BsqBlockchainServiceTest {
@Before
public void setup() {
final URL resource = this.getClass().getClassLoader().getResource("");
final String path = resource != null ? resource.getFile() : "";
log.info("path for BsqUTXOMap=" + path);
txOutputMap = new TxOutputMap(new File(path));
txOutputMap = new TxOutputMap();
service = new MockBsqBlockchainService();
}
@ -407,7 +402,9 @@ public class BsqBlockchainServiceTest {
service.requestChainHeadHeight(),
BLOCK_0,
GEN_TX_ID,
txOutputMap);
txOutputMap,
txOutputMap -> {
});
}
private String getTxoId(String txId, int index) {
@ -430,7 +427,7 @@ class MockBsqBlockchainService extends BsqBlockchainRpcService {
private final Map<Integer, List<String>> txIdsInBlockMap = new HashMap<>();
public MockBsqBlockchainService() {
super(null, null, null, null);
super(null, null, null, null, null);
}
public void buildBlocks(int from, int to) {

View file

@ -17,6 +17,7 @@
package io.bisq.gui.main.dao.wallet;
import io.bisq.common.app.DevEnv;
import io.bisq.core.btc.wallet.BsqBalanceListener;
import io.bisq.core.btc.wallet.BsqWalletService;
import io.bisq.gui.util.BsqFormatter;
@ -27,16 +28,16 @@ import org.bitcoinj.core.Coin;
import javax.inject.Inject;
@Slf4j
public class BalanceUtil implements BsqBalanceListener {
public class BsqBalanceUtil implements BsqBalanceListener {
private final BsqWalletService bsqWalletService;
private final BsqFormatter formatter;
private final BsqFormatter bsqFormatter;
private TextField balanceTextField;
@Inject
private BalanceUtil(BsqWalletService bsqWalletService,
BsqFormatter formatter) {
private BsqBalanceUtil(BsqWalletService bsqWalletService,
BsqFormatter bsqFormatter) {
this.bsqWalletService = bsqWalletService;
this.formatter = formatter;
this.bsqFormatter = bsqFormatter;
}
public void setBalanceTextField(TextField balanceTextField) {
@ -58,6 +59,16 @@ public class BalanceUtil implements BsqBalanceListener {
@Override
public void updateAvailableBalance(Coin availableBalance) {
balanceTextField.setText(formatter.formatCoinWithCode(availableBalance));
if (balanceTextField != null)
balanceTextField.setText(bsqFormatter.formatCoinWithCode(availableBalance));
else {
log.error("balanceTextField is null");
if (DevEnv.DEV_MODE) {
final Throwable throwable = new Throwable("balanceTextField is null");
throwable.printStackTrace();
throw new RuntimeException(throwable);
}
}
}
}

View file

@ -21,7 +21,7 @@ import io.bisq.common.locale.Res;
import io.bisq.core.btc.wallet.BsqWalletService;
import io.bisq.gui.common.view.ActivatableView;
import io.bisq.gui.common.view.FxmlView;
import io.bisq.gui.main.dao.wallet.BalanceUtil;
import io.bisq.gui.main.dao.wallet.BsqBalanceUtil;
import io.bisq.gui.util.BsqFormatter;
import io.bisq.gui.util.FormBuilder;
import io.bisq.gui.util.Layout;
@ -37,7 +37,7 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> {
private final BsqWalletService bsqWalletService;
private final BsqFormatter formatter;
private final BalanceUtil balanceUtil;
private final BsqBalanceUtil bsqBalanceUtil;
private final int gridRow = 0;
@ -46,29 +46,29 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BsqDashboardView(BsqWalletService bsqWalletService, BsqFormatter formatter, BalanceUtil balanceUtil) {
private BsqDashboardView(BsqWalletService bsqWalletService, BsqFormatter formatter, BsqBalanceUtil bsqBalanceUtil) {
this.bsqWalletService = bsqWalletService;
this.formatter = formatter;
this.balanceUtil = balanceUtil;
this.bsqBalanceUtil = bsqBalanceUtil;
}
@Override
public void initialize() {
FormBuilder.addTitledGroupBg(root, gridRow, 1, Res.get("shared.balance"));
balanceTextField = FormBuilder.addLabelTextField(root, gridRow, Res.get("shared.bsqBalance"), Layout.FIRST_ROW_DISTANCE).second;
balanceUtil.setBalanceTextField(balanceTextField);
balanceUtil.initialize();
bsqBalanceUtil.setBalanceTextField(balanceTextField);
bsqBalanceUtil.initialize();
}
@Override
protected void activate() {
balanceUtil.activate();
bsqBalanceUtil.activate();
}
@Override
protected void deactivate() {
balanceUtil.deactivate();
bsqBalanceUtil.deactivate();
}
}

View file

@ -25,7 +25,7 @@ import io.bisq.gui.common.view.ActivatableView;
import io.bisq.gui.common.view.FxmlView;
import io.bisq.gui.components.AddressTextField;
import io.bisq.gui.components.InputTextField;
import io.bisq.gui.main.dao.wallet.BalanceUtil;
import io.bisq.gui.main.dao.wallet.BsqBalanceUtil;
import io.bisq.gui.main.overlays.windows.QRCodeWindow;
import io.bisq.gui.util.BsqFormatter;
import io.bisq.gui.util.GUIUtil;
@ -60,7 +60,7 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
private final BsqWalletService bsqWalletService;
private final BsqFormatter bsqFormatter;
private final BalanceUtil balanceUtil;
private final BsqBalanceUtil bsqBalanceUtil;
private int gridRow = 0;
private final String paymentLabelString;
@ -72,10 +72,10 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BsqReceiveView(BsqWalletService bsqWalletService, BsqFormatter bsqFormatter, BalanceUtil balanceUtil) {
private BsqReceiveView(BsqWalletService bsqWalletService, BsqFormatter bsqFormatter, BsqBalanceUtil bsqBalanceUtil) {
this.bsqWalletService = bsqWalletService;
this.bsqFormatter = bsqFormatter;
this.balanceUtil = balanceUtil;
this.bsqBalanceUtil = bsqBalanceUtil;
paymentLabelString = Res.get("dao.wallet.receive.fundBSQWallet");
}
@ -83,8 +83,8 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
public void initialize() {
addTitledGroupBg(root, gridRow, 1, Res.get("shared.balance"));
balanceTextField = addLabelTextField(root, gridRow, Res.get("shared.bsqBalance"), Layout.FIRST_ROW_DISTANCE).second;
balanceUtil.setBalanceTextField(balanceTextField);
balanceUtil.initialize();
bsqBalanceUtil.setBalanceTextField(balanceTextField);
bsqBalanceUtil.initialize();
addTitledGroupBg(root, ++gridRow, 3, Res.get("dao.wallet.receive.fundYourWallet"), Layout.GROUP_DISTANCE);
@ -106,7 +106,7 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
@Override
protected void activate() {
balanceUtil.activate();
bsqBalanceUtil.activate();
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
addressTextField.setAmountAsCoin(bsqFormatter.parseToCoin(t));
@ -123,7 +123,7 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
@Override
protected void deactivate() {
balanceUtil.deactivate();
bsqBalanceUtil.deactivate();
qrCodeImageView.setOnMouseClicked(null);
amountTextFieldSubscription.unsubscribe();

View file

@ -28,7 +28,7 @@ import io.bisq.core.util.CoinUtil;
import io.bisq.gui.common.view.ActivatableView;
import io.bisq.gui.common.view.FxmlView;
import io.bisq.gui.components.InputTextField;
import io.bisq.gui.main.dao.wallet.BalanceUtil;
import io.bisq.gui.main.dao.wallet.BsqBalanceUtil;
import io.bisq.gui.main.overlays.popups.Popup;
import io.bisq.gui.util.BSFormatter;
import io.bisq.gui.util.BsqFormatter;
@ -55,7 +55,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
private final BtcWalletService btcWalletService;
private final BsqFormatter bsqFormatter;
private final BSFormatter btcFormatter;
private final BalanceUtil balanceUtil;
private final BsqBalanceUtil bsqBalanceUtil;
private BsqValidator bsqValidator;
private BsqAddressValidator bsqAddressValidator;
@ -73,12 +73,12 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
@Inject
private BsqSendView(BsqWalletService bsqWalletService, BtcWalletService btcWalletService,
BsqFormatter bsqFormatter, BSFormatter btcFormatter,
BalanceUtil balanceUtil, BsqValidator bsqValidator, BsqAddressValidator bsqAddressValidator) {
BsqBalanceUtil bsqBalanceUtil, BsqValidator bsqValidator, BsqAddressValidator bsqAddressValidator) {
this.bsqWalletService = bsqWalletService;
this.btcWalletService = btcWalletService;
this.bsqFormatter = bsqFormatter;
this.btcFormatter = btcFormatter;
this.balanceUtil = balanceUtil;
this.bsqBalanceUtil = bsqBalanceUtil;
this.bsqValidator = bsqValidator;
this.bsqAddressValidator = bsqAddressValidator;
}
@ -87,8 +87,8 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
public void initialize() {
addTitledGroupBg(root, gridRow, 1, Res.get("shared.balance"));
TextField balanceTextField = addLabelTextField(root, gridRow, Res.get("shared.bsqBalance"), Layout.FIRST_ROW_DISTANCE).second;
balanceUtil.setBalanceTextField(balanceTextField);
balanceUtil.initialize();
bsqBalanceUtil.setBalanceTextField(balanceTextField);
bsqBalanceUtil.initialize();
addTitledGroupBg(root, ++gridRow, 3, Res.get("dao.wallet.send.sendFunds"), Layout.GROUP_DISTANCE);
amountInputTextField = addLabelInputTextField(root, gridRow, Res.get("dao.wallet.send.amount"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
@ -168,7 +168,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
@Override
protected void activate() {
balanceUtil.activate();
bsqBalanceUtil.activate();
receiversAddressInputTextField.focusedProperty().addListener(focusOutListener);
amountInputTextField.focusedProperty().addListener(focusOutListener);
verifyInputs();
@ -176,7 +176,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
@Override
protected void deactivate() {
balanceUtil.deactivate();
bsqBalanceUtil.deactivate();
receiversAddressInputTextField.focusedProperty().removeListener(focusOutListener);
amountInputTextField.focusedProperty().removeListener(focusOutListener);
}

View file

@ -22,13 +22,14 @@ import io.bisq.common.locale.Res;
import io.bisq.core.btc.wallet.BsqWalletService;
import io.bisq.core.btc.wallet.BtcWalletService;
import io.bisq.core.dao.blockchain.BsqBlockchainManager;
import io.bisq.core.dao.blockchain.TxOutputMap;
import io.bisq.core.user.DontShowAgainLookup;
import io.bisq.core.user.Preferences;
import io.bisq.gui.common.view.ActivatableView;
import io.bisq.gui.common.view.FxmlView;
import io.bisq.gui.components.AddressWithIconAndDirection;
import io.bisq.gui.components.HyperlinkWithIcon;
import io.bisq.gui.main.dao.wallet.BalanceUtil;
import io.bisq.gui.main.dao.wallet.BsqBalanceUtil;
import io.bisq.gui.main.overlays.popups.Popup;
import io.bisq.gui.util.BsqFormatter;
import io.bisq.gui.util.GUIUtil;
@ -60,9 +61,10 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
private int gridRow = 0;
private BsqFormatter bsqFormatter;
private BsqWalletService bsqWalletService;
private TxOutputMap txOutputMap;
private BsqBlockchainManager bsqBlockchainManager;
private BtcWalletService btcWalletService;
private BalanceUtil balanceUtil;
private BsqBalanceUtil bsqBalanceUtil;
private Preferences preferences;
private ListChangeListener<Transaction> walletBsqTransactionsListener;
private final ObservableList<BsqTxListItem> observableList = FXCollections.observableArrayList();
@ -75,13 +77,16 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
@Inject
private BsqTxView(BsqFormatter bsqFormatter, BsqWalletService bsqWalletService,
TxOutputMap txOutputMap,
BsqBlockchainManager bsqBlockchainManager,
BtcWalletService btcWalletService, BalanceUtil balanceUtil, Preferences preferences) {
BtcWalletService btcWalletService, BsqBalanceUtil bsqBalanceUtil, Preferences preferences) {
this.bsqFormatter = bsqFormatter;
this.bsqWalletService = bsqWalletService;
this.txOutputMap = txOutputMap;
this.bsqBlockchainManager = bsqBlockchainManager;
this.btcWalletService = btcWalletService;
this.balanceUtil = balanceUtil;
this.bsqBalanceUtil = bsqBalanceUtil;
this.preferences = preferences;
}
@ -90,8 +95,8 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
addTitledGroupBg(root, gridRow, 1, Res.get("shared.balance"));
TextField balanceTextField = addLabelTextField(root, gridRow, Res.get("shared.bsqBalance"),
Layout.FIRST_ROW_DISTANCE).second;
balanceUtil.setBalanceTextField(balanceTextField);
balanceUtil.initialize();
bsqBalanceUtil.setBalanceTextField(balanceTextField);
bsqBalanceUtil.initialize();
tableView = new TableView<>();
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
@ -111,7 +116,7 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
@Override
protected void activate() {
balanceUtil.activate();
bsqBalanceUtil.activate();
bsqWalletService.getWalletTransactions().addListener(walletBsqTransactionsListener);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
@ -122,7 +127,7 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
@Override
protected void deactivate() {
balanceUtil.deactivate();
bsqBalanceUtil.deactivate();
sortedList.comparatorProperty().unbind();
bsqWalletService.getWalletTransactions().removeListener(walletBsqTransactionsListener);
observableList.forEach(BsqTxListItem::cleanup);
@ -139,7 +144,7 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
return new BsqTxListItem(transaction,
bsqWalletService,
btcWalletService,
bsqBlockchainManager.getTxOutputMap().hasTxBurnedFee(transaction.getHashAsString()),
txOutputMap.hasTxBurnedFee(transaction.getHashAsString()),
bsqFormatter);
}
)

View file

@ -121,7 +121,8 @@ public class GetDataRequestHandler {
}
}
GetDataResponse getDataResponse = new GetDataResponse(filteredDataSet, getDataRequest.getNonce(), getDataRequest instanceof GetUpdatedDataRequest);
GetDataResponse getDataResponse = new GetDataResponse(filteredDataSet, getDataRequest.getNonce(),
getDataRequest instanceof GetUpdatedDataRequest);
if (timeoutTimer == null) {
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions

View file

@ -176,14 +176,18 @@ public class RequestDataHandler implements MessageListener {
log.warn("StoragePayload was null: {}", msg.toString());
return;
}
// For logging different data types
String className = storagePayload.getClass().getSimpleName();
if (!payloadByClassName.containsKey(className))
payloadByClassName.put(className, new HashSet<>());
payloadByClassName.get(className).add(storagePayload);
});
// Log different data types
StringBuilder sb = new StringBuilder("Received data size: ").append(dataSet.size()).append(", data items: ");
payloadByClassName.entrySet().stream().forEach(e -> sb.append(e.getValue().size()).append(" items of ").append(e.getKey()).append("; "));
payloadByClassName.entrySet().stream().forEach(e -> sb.append(e.getValue().size()).append(" items of ")
.append(e.getKey()).append("; "));
log.info(sb.toString());
if (getDataResponse.requestNonce == nonce) {