mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 15:10:44 +01:00
Keep historical bsq txos for displaying bss txs in tx list. refactor data structure. detect invalid bsq tx and show popup.
This commit is contained in:
parent
0386f1e739
commit
986ac67f84
24 changed files with 544 additions and 251 deletions
|
@ -18,11 +18,8 @@
|
|||
package io.bisq.common.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Profiler {
|
||||
private static final Logger log = LoggerFactory.getLogger(Profiler.class);
|
||||
|
||||
public static void printSystemLoad(Logger log) {
|
||||
log.info(printSystemLoadString());
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.bitcoinj.core.Transaction;
|
|||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.params.RegTestParams;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -41,7 +40,7 @@ class BsqCoinSelector extends BisqDefaultCoinSelector {
|
|||
|
||||
private final boolean permitForeignPendingTx;
|
||||
|
||||
private final Map<Script, Set<BsqUTXO>> utxoSetByScriptMap = new HashMap<>();
|
||||
private final Map<String, Set<BsqUTXO>> utxoSetByAddressMap = new HashMap<>();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
|
@ -53,11 +52,11 @@ class BsqCoinSelector extends BisqDefaultCoinSelector {
|
|||
|
||||
public void setUtxoMap(BsqUTXOMap bsqUTXOMap) {
|
||||
bsqUTXOMap.values().stream().forEach(utxo -> {
|
||||
Script script = utxo.getScript();
|
||||
if (!utxoSetByScriptMap.containsKey(script))
|
||||
utxoSetByScriptMap.put(script, new HashSet<>());
|
||||
String address = utxo.getAddress();
|
||||
if (!utxoSetByAddressMap.containsKey(address))
|
||||
utxoSetByAddressMap.put(address, new HashSet<>());
|
||||
|
||||
utxoSetByScriptMap.get(script).add(utxo);
|
||||
utxoSetByAddressMap.get(address).add(utxo);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -77,12 +76,11 @@ class BsqCoinSelector extends BisqDefaultCoinSelector {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean selectOutput(TransactionOutput transactionOutput) {
|
||||
Script scriptPubKey = transactionOutput.getScriptPubKey();
|
||||
if (scriptPubKey.isSentToAddress() || scriptPubKey.isPayToScriptHash()) {
|
||||
return utxoSetByScriptMap.containsKey(scriptPubKey);
|
||||
protected boolean selectOutput(TransactionOutput output) {
|
||||
if (WalletUtils.isOutputScriptConvertableToAddress(output)) {
|
||||
return utxoSetByAddressMap.containsKey(WalletUtils.getAddressStringFromOutput(output));
|
||||
} else {
|
||||
log.warn("transactionOutput.getScriptPubKey() not isSentToAddress or isPayToScriptHash");
|
||||
log.warn("output.getScriptPubKey() not isSentToAddress or isPayToScriptHash");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,14 +23,11 @@ import io.bisq.common.handlers.ResultHandler;
|
|||
import io.bisq.core.btc.Restrictions;
|
||||
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
||||
import io.bisq.core.btc.exceptions.WalletException;
|
||||
import io.bisq.core.dao.blockchain.BsqBlock;
|
||||
import io.bisq.core.dao.blockchain.BsqBlockchainManager;
|
||||
import io.bisq.core.dao.blockchain.BsqUTXOMap;
|
||||
import io.bisq.core.dao.blockchain.Tx;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
import io.bisq.core.user.Preferences;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -40,8 +37,11 @@ import org.bitcoinj.wallet.CoinSelection;
|
|||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
@ -113,7 +113,7 @@ public class BsqWalletService extends WalletService {
|
|||
|
||||
});
|
||||
});
|
||||
bsqBlockchainManager.getBsqBlocks().addListener((ListChangeListener<BsqBlock>) c -> updateWalletBsqTransactions());
|
||||
bsqBlockchainManager.getBsqUTXOMap().addListener(c -> updateWalletBsqTransactions());
|
||||
}
|
||||
|
||||
|
||||
|
@ -146,7 +146,7 @@ public class BsqWalletService extends WalletService {
|
|||
|
||||
public void requestBsqUtxo(@Nullable ResultHandler resultHandler) {
|
||||
if (bsqBlockchainManager.isUtxoAvailable()) {
|
||||
applyUtxoSetToUTXOProvider(bsqBlockchainManager.getUtxoByTxIdMap());
|
||||
applyUtxoSetToUTXOProvider(bsqBlockchainManager.getBsqUTXOMap());
|
||||
if (resultHandler != null)
|
||||
resultHandler.handleResult();
|
||||
} else {
|
||||
|
@ -163,14 +163,40 @@ public class BsqWalletService extends WalletService {
|
|||
}
|
||||
|
||||
private void updateWalletBsqTransactions() {
|
||||
Set<String> txIdsFromUTXOSet = bsqBlockchainManager.getBsqBlocks().stream()
|
||||
.flatMap(bsqBlock -> bsqBlock.getTxByTxIdMap().values().stream())
|
||||
.map(Tx::getId)
|
||||
.collect(Collectors.toSet());
|
||||
walletBsqTransactions.setAll(getTxsWithOutputsFoundInBsqTxo());
|
||||
}
|
||||
|
||||
walletBsqTransactions.setAll(getTransactions(true).stream()
|
||||
.filter(t -> txIdsFromUTXOSet.contains(t.getHashAsString()))
|
||||
.collect(Collectors.toSet()));
|
||||
private Set<Transaction> getTxsWithOutputsFoundInBsqTxo() {
|
||||
return getTransactions(true).stream()
|
||||
.flatMap(tx -> tx.getOutputs().stream())
|
||||
.filter(out -> out.getParentTransaction() != null && bsqBlockchainManager.getBsqTXOMap()
|
||||
.containsTuple(out.getParentTransaction().getHashAsString(), out.getIndex()))
|
||||
.map(TransactionOutput::getParentTransaction)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Set<Transaction> getInvalidBsqTransactions() {
|
||||
Set<Transaction> txsWithOutputsFoundInBsqTxo = getTxsWithOutputsFoundInBsqTxo();
|
||||
Set<Transaction> walletTxs = getTransactions(true).stream().collect(Collectors.toSet());
|
||||
checkArgument(walletTxs.size() >= txsWithOutputsFoundInBsqTxo.size(),
|
||||
"We cannot have more txsWithOutputsFoundInBsqTxo than walletTxs");
|
||||
if (walletTxs.size() > txsWithOutputsFoundInBsqTxo.size()) {
|
||||
Map<String, Transaction> map = walletTxs.stream()
|
||||
.collect(Collectors.toMap(Transaction::getHashAsString, Function.identity()));
|
||||
|
||||
Set<String> walletTxIds = walletTxs.stream()
|
||||
.map(Transaction::getHashAsString).collect(Collectors.toSet());
|
||||
Set<String> bsqTxIds = txsWithOutputsFoundInBsqTxo.stream()
|
||||
.map(Transaction::getHashAsString).collect(Collectors.toSet());
|
||||
|
||||
walletTxIds.stream()
|
||||
.filter(bsqTxIds::contains)
|
||||
.forEach(map::remove);
|
||||
|
||||
return new HashSet<>(map.values());
|
||||
} else {
|
||||
return new HashSet<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -75,15 +75,14 @@ class BtcCoinSelector extends BisqDefaultCoinSelector {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean selectOutput(TransactionOutput transactionOutput) {
|
||||
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
||||
Address address = transactionOutput.getScriptPubKey().getToAddress(params);
|
||||
log.trace(address.toString());
|
||||
protected boolean selectOutput(TransactionOutput output) {
|
||||
if (WalletUtils.isOutputScriptConvertableToAddress(output)) {
|
||||
Address address = WalletUtils.getAddressFromOutput(output);
|
||||
|
||||
boolean matchesAddress = addresses.contains(address);
|
||||
if (!matchesAddress)
|
||||
log.trace("No match found at matchesRequiredAddress address / addressEntry " +
|
||||
address.toString() + " / " + address.toString());
|
||||
log.trace("addresses not containing address " +
|
||||
addresses + " / " + address.toString());
|
||||
|
||||
return matchesAddress;
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,6 @@ import io.bisq.core.btc.exceptions.WalletException;
|
|||
import io.bisq.core.btc.listeners.AddressConfidenceListener;
|
||||
import io.bisq.core.btc.listeners.BalanceListener;
|
||||
import io.bisq.core.btc.listeners.TxConfidenceListener;
|
||||
import io.bisq.core.dao.blockchain.TxOutput;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
import io.bisq.core.user.Preferences;
|
||||
import org.bitcoinj.core.*;
|
||||
|
@ -206,32 +205,6 @@ public abstract class WalletService {
|
|||
// Sign tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//TODOcheck with signTransactionInput
|
||||
/* protected void signInput(Transaction transaction) throws SigningException {
|
||||
List<TransactionInput> inputs = transaction.getInputs();
|
||||
|
||||
int inputIndex = transaction.getInputs().size() - 1;
|
||||
TransactionInput input = transaction.getInput(inputIndex);
|
||||
|
||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
|
||||
checkNotNull(wallet);
|
||||
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
|
||||
checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" + input.getOutpoint().toString());
|
||||
if (sigKey.isEncrypted())
|
||||
checkNotNull(aesKey);
|
||||
Sha256Hash hash = transaction.hashForSignature(inputIndex, scriptPubKey, Transaction.SigHash.ALL, false);
|
||||
ECKey.ECDSASignature signature = sigKey.sign(hash, aesKey);
|
||||
TransactionSignature txSig = new TransactionSignature(signature, Transaction.SigHash.ALL, false);
|
||||
if (scriptPubKey.isSentToRawPubKey()) {
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(txSig));
|
||||
} else if (scriptPubKey.isSentToAddress()) {
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey));
|
||||
} else {
|
||||
throw new SigningException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey);
|
||||
}
|
||||
}*/
|
||||
|
||||
void signTransactionInput(Transaction tx, TransactionInput txIn, int index) {
|
||||
KeyBag maybeDecryptingKeyBag = new DecryptingKeyBag(wallet, aesKey);
|
||||
if (txIn.getConnectedOutput() != null) {
|
||||
|
@ -331,16 +304,12 @@ public abstract class WalletService {
|
|||
}
|
||||
|
||||
protected TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
|
||||
List<TransactionOutput> mergedOutputs = getOutputsWithConnectedOutputs(tx);
|
||||
List<TransactionConfidence> transactionConfidenceList = new ArrayList<>();
|
||||
|
||||
mergedOutputs.stream().filter(e -> e.getScriptPubKey().isSentToAddress() ||
|
||||
e.getScriptPubKey().isPayToScriptHash()).forEach(transactionOutput -> {
|
||||
Address outputAddress = transactionOutput.getScriptPubKey().getToAddress(params);
|
||||
if (address.equals(outputAddress)) {
|
||||
transactionConfidenceList.add(tx.getConfidence());
|
||||
}
|
||||
});
|
||||
List<TransactionConfidence> transactionConfidenceList = getOutputsWithConnectedOutputs(tx)
|
||||
.stream()
|
||||
.filter(WalletUtils::isOutputScriptConvertableToAddress)
|
||||
.filter(output -> address.equals(WalletUtils.getAddressFromOutput(output)))
|
||||
.map(o -> tx.getConfidence())
|
||||
.collect(Collectors.toList());
|
||||
return getMostRecentConfidence(transactionConfidenceList);
|
||||
}
|
||||
|
||||
|
@ -398,12 +367,10 @@ public abstract class WalletService {
|
|||
|
||||
protected Coin getBalance(List<TransactionOutput> transactionOutputs, Address address) {
|
||||
Coin balance = Coin.ZERO;
|
||||
for (TransactionOutput transactionOutput : transactionOutputs) {
|
||||
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
||||
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
|
||||
if (addressOutput.equals(address))
|
||||
balance = balance.add(transactionOutput.getValue());
|
||||
}
|
||||
for (TransactionOutput output : transactionOutputs) {
|
||||
if (WalletUtils.isOutputScriptConvertableToAddress(output) &&
|
||||
address.equals(WalletUtils.getAddressFromOutput(output)))
|
||||
balance = balance.add(output.getValue());
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
@ -412,13 +379,11 @@ public abstract class WalletService {
|
|||
List<TransactionOutput> transactionOutputs = new ArrayList<>();
|
||||
wallet.getTransactions(true).stream().forEach(t -> transactionOutputs.addAll(t.getOutputs()));
|
||||
int outputs = 0;
|
||||
for (TransactionOutput transactionOutput : transactionOutputs) {
|
||||
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
||||
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
|
||||
if (addressOutput.equals(address))
|
||||
for (TransactionOutput output : transactionOutputs) {
|
||||
if (WalletUtils.isOutputScriptConvertableToAddress(output) &&
|
||||
address.equals(WalletUtils.getAddressFromOutput(output)))
|
||||
outputs++;
|
||||
}
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
|
@ -545,7 +510,7 @@ public abstract class WalletService {
|
|||
return transactionOutput.isMine(wallet);
|
||||
}
|
||||
|
||||
public boolean isTxOutputMine(TxOutput txOutput) {
|
||||
/* public boolean isTxOutputMine(TxOutput txOutput) {
|
||||
try {
|
||||
Script script = txOutput.getScript();
|
||||
if (script.isSentToRawPubKey()) {
|
||||
|
@ -563,7 +528,7 @@ public abstract class WalletService {
|
|||
log.debug("Could not parse tx output script: {}", e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
public Coin getValueSentFromMeForTransaction(Transaction transaction) throws ScriptException {
|
||||
return transaction.getValueSentFromMe(wallet);
|
||||
|
|
53
core/src/main/java/io/bisq/core/btc/wallet/WalletUtils.java
Normal file
53
core/src/main/java/io/bisq/core/btc/wallet/WalletUtils.java
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare 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.
|
||||
*
|
||||
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bisq.core.btc.wallet;
|
||||
|
||||
import io.bisq.core.btc.BitcoinNetwork;
|
||||
import io.bisq.core.user.Preferences;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class WalletUtils {
|
||||
public static NetworkParameters getParams() {
|
||||
return Preferences.INSTANCE.getBitcoinNetwork().getParameters();
|
||||
}
|
||||
|
||||
public static boolean isOutputScriptConvertableToAddress(TransactionOutput output) {
|
||||
return output.getScriptPubKey().isSentToAddress() ||
|
||||
output.getScriptPubKey().isPayToScriptHash();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Address getAddressFromOutput(TransactionOutput output) {
|
||||
return isOutputScriptConvertableToAddress(output) ?
|
||||
output.getScriptPubKey().getToAddress(getParams()) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getAddressStringFromOutput(TransactionOutput output) {
|
||||
return isOutputScriptConvertableToAddress(output) ?
|
||||
output.getScriptPubKey().getToAddress(getParams()).toString() : null;
|
||||
}
|
||||
|
||||
public static boolean isRegTest() {
|
||||
return Preferences.INSTANCE.getBitcoinNetwork().equals(BitcoinNetwork.REGTEST);
|
||||
}
|
||||
}
|
|
@ -25,4 +25,8 @@ public class BsqBlockchainException extends Exception {
|
|||
public BsqBlockchainException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BsqBlockchainException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,8 @@ import com.google.common.util.concurrent.ListenableFuture;
|
|||
import com.google.inject.Inject;
|
||||
import io.bisq.common.UserThread;
|
||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.common.util.Tuple3;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.core.btc.wallet.WalletUtils;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -38,21 +37,24 @@ import java.util.Set;
|
|||
public class BsqBlockchainManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(BsqBlockchainManager.class);
|
||||
|
||||
private final BsqBlockchainService blockchainService;
|
||||
|
||||
// regtest
|
||||
public static final String GENESIS_TX_ID = "3bc7bc9484e112ec8ddd1a1c984379819245ac463b9ce40fa8b5bf771c0f9236";
|
||||
public static final int GENESIS_BLOCK_HEIGHT = 102;
|
||||
//mainnet
|
||||
private static final String GENESIS_TX_ID = "cabbf6073aea8f22ec678e973ac30c6d8fc89321011da6a017f63e67b9f66667";
|
||||
private static final int GENESIS_BLOCK_HEIGHT = 105301;
|
||||
private static final String REG_TEST_GENESIS_TX_ID = "3bc7bc9484e112ec8ddd1a1c984379819245ac463b9ce40fa8b5bf771c0f9236";
|
||||
private static final int REG_TEST_GENESIS_BLOCK_HEIGHT = 102;
|
||||
|
||||
private BsqUTXOMap utxoByTxIdMap;
|
||||
private final List<BsqUTXOListener> bsqUTXOListeners = new ArrayList<>();
|
||||
private boolean isUtxoAvailable;
|
||||
@Getter
|
||||
private final BsqUTXOMap bsqUTXOMap = new BsqUTXOMap();
|
||||
@Getter
|
||||
private final BsqTXOMap bsqTXOMap = new BsqTXOMap();
|
||||
@Getter
|
||||
private int chainHeadHeight;
|
||||
|
||||
// We prefer a list over a set. See: http://stackoverflow.com/questions/24799125/what-is-the-observableset-equivalent-for-setall-method-from-observablelist
|
||||
@Getter
|
||||
private final ObservableList<BsqBlock> bsqBlocks = FXCollections.observableArrayList();
|
||||
private boolean isUtxoAvailable;
|
||||
|
||||
private final BsqBlockchainService blockchainService;
|
||||
private final List<BsqUTXOListener> bsqUTXOListeners = new ArrayList<>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -64,7 +66,6 @@ public class BsqBlockchainManager {
|
|||
this.blockchainService = blockchainService;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Public methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -73,10 +74,14 @@ public class BsqBlockchainManager {
|
|||
blockchainService.setup(this::setupComplete, errorMessageHandler);
|
||||
}
|
||||
|
||||
public BsqUTXOMap getUtxoByTxIdMap() {
|
||||
return utxoByTxIdMap;
|
||||
|
||||
public Set<String> getUtxoTxIdSet() {
|
||||
return bsqUTXOMap.getTxIdSet();
|
||||
}
|
||||
|
||||
public Set<String> getTxoTxIdSet() {
|
||||
return bsqTXOMap.getTxIdSet();
|
||||
}
|
||||
|
||||
public void addUtxoListener(BsqUTXOListener bsqUTXOListener) {
|
||||
bsqUTXOListeners.add(bsqUTXOListener);
|
||||
|
@ -86,40 +91,33 @@ public class BsqBlockchainManager {
|
|||
bsqUTXOListeners.remove(bsqUTXOListener);
|
||||
}
|
||||
|
||||
public boolean isUtxoAvailable() {
|
||||
return isUtxoAvailable;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// private methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void setupComplete() {
|
||||
ListenableFuture<Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer>> future =
|
||||
blockchainService.syncFromGenesis(GENESIS_BLOCK_HEIGHT, GENESIS_TX_ID);
|
||||
Futures.addCallback(future, new FutureCallback<Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer>>() {
|
||||
ListenableFuture<Tuple2<BsqUTXOMap, Integer>> future =
|
||||
blockchainService.syncFromGenesis(bsqUTXOMap, bsqTXOMap, getGenesisBlockHeight(), getGenesisTxId());
|
||||
Futures.addCallback(future, new FutureCallback<Tuple2<BsqUTXOMap, Integer>>() {
|
||||
@Override
|
||||
public void onSuccess(Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer> tuple) {
|
||||
public void onSuccess(Tuple2<BsqUTXOMap, Integer> tuple) {
|
||||
UserThread.execute(() -> {
|
||||
BsqBlockchainManager.this.bsqBlocks.setAll(tuple.first);
|
||||
BsqBlockchainManager.this.utxoByTxIdMap = tuple.second;
|
||||
chainHeadHeight = tuple.third;
|
||||
chainHeadHeight = tuple.second;
|
||||
isUtxoAvailable = true;
|
||||
bsqUTXOListeners.stream().forEach(e -> e.onBsqUTXOChanged(utxoByTxIdMap));
|
||||
blockchainService.syncFromGenesisCompete(GENESIS_TX_ID,
|
||||
GENESIS_BLOCK_HEIGHT,
|
||||
bsqUTXOListeners.stream().forEach(e -> e.onBsqUTXOChanged(bsqUTXOMap));
|
||||
blockchainService.syncFromGenesisCompete(bsqUTXOMap, bsqTXOMap, getGenesisTxId(),
|
||||
getGenesisBlockHeight(),
|
||||
btcdBlock -> {
|
||||
if (btcdBlock != null) {
|
||||
UserThread.execute(() -> {
|
||||
try {
|
||||
final BsqBlock bsqBlock = new BsqBlock(btcdBlock.getTx(), btcdBlock.getHeight());
|
||||
blockchainService.parseBlock(bsqBlock,
|
||||
GENESIS_BLOCK_HEIGHT,
|
||||
GENESIS_TX_ID,
|
||||
utxoByTxIdMap);
|
||||
if (!BsqBlockchainManager.this.bsqBlocks.contains(bsqBlock))
|
||||
BsqBlockchainManager.this.bsqBlocks.add(bsqBlock);
|
||||
getGenesisBlockHeight(),
|
||||
getGenesisTxId(),
|
||||
bsqUTXOMap,
|
||||
bsqTXOMap);
|
||||
} catch (BsqBlockchainException e) {
|
||||
//TODO
|
||||
e.printStackTrace();
|
||||
|
@ -136,4 +134,12 @@ public class BsqBlockchainManager {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getGenesisTxId() {
|
||||
return WalletUtils.isRegTest() ? REG_TEST_GENESIS_TX_ID : GENESIS_TX_ID;
|
||||
}
|
||||
|
||||
private int getGenesisBlockHeight() {
|
||||
return WalletUtils.isRegTest() ? REG_TEST_GENESIS_BLOCK_HEIGHT : GENESIS_BLOCK_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import io.bisq.common.UserThread;
|
|||
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.common.handlers.ResultHandler;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.common.util.Tuple3;
|
||||
import io.bisq.common.util.Utilities;
|
||||
import io.bisq.core.dao.RpcOptionKeys;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
@ -49,7 +48,6 @@ import java.io.*;
|
|||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -114,9 +112,14 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
|
|||
log.info("Setup took {} ms", System.currentTimeMillis() - startTs);
|
||||
return client;
|
||||
} catch (IOException | BitcoindException | CommunicationException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
log.error(e.getCause() != null ? e.getCause().toString() : "e.getCause()=null");
|
||||
throw new BsqBlockchainException(e.getMessage(), e);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
throw new BsqBlockchainException(e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
|
@ -139,18 +142,24 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId) {
|
||||
protected ListenableFuture<Tuple2<BsqUTXOMap, Integer>> syncFromGenesis(BsqUTXOMap bsqUTXOMap,
|
||||
BsqTXOMap bsqTXOMap,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId) {
|
||||
return rpcRequestsExecutor.submit(() -> {
|
||||
long startTs = System.currentTimeMillis();
|
||||
int chainHeadHeight = requestChainHeadHeight();
|
||||
Tuple2<Set<BsqBlock>, BsqUTXOMap> tuple2 = parseAllBlocksFromGenesis(chainHeadHeight, genesisBlockHeight, genesisTxId);
|
||||
parseAllBlocksFromGenesis(bsqUTXOMap, bsqTXOMap, chainHeadHeight, genesisBlockHeight, genesisTxId);
|
||||
log.info("syncFromGenesis took {} ms", System.currentTimeMillis() - startTs);
|
||||
return new Tuple3<>(tuple2.first, tuple2.second, chainHeadHeight);
|
||||
return new Tuple2<>(bsqUTXOMap, chainHeadHeight);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void syncFromGenesisCompete(String genesisTxId, int genesisBlockHeight, Consumer<Block> onNewBlockHandler) {
|
||||
protected void syncFromGenesisCompete(BsqUTXOMap bsqUTXOMap, BsqTXOMap bsqTXOMap,
|
||||
String genesisTxId,
|
||||
int genesisBlockHeight,
|
||||
Consumer<Block> onNewBlockHandler) {
|
||||
daemon.addBlockListener(new BlockListener() {
|
||||
@Override
|
||||
public void blockDetected(Block block) {
|
||||
|
@ -196,10 +205,25 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
|
|||
final List<TxOutput> txOutputs = rawTransaction.getVOut()
|
||||
.stream()
|
||||
.filter(e -> e != null && e.getN() != null && e.getValue() != null && e.getScriptPubKey() != null)
|
||||
.map(e -> new TxOutput(e.getN(),
|
||||
.map(e -> {
|
||||
byte[] scriptProgramBytes = new byte[]{};
|
||||
try {
|
||||
scriptProgramBytes = e.getScriptPubKey().getHex() != null ?
|
||||
new Script(HEX.decode(e.getScriptPubKey().getHex())).getProgram() : null;
|
||||
} catch (Throwable t) {
|
||||
// expected for tx: 0f8e9655b29d76c5e01147704a64faf95a9c90433baacdd39d4c1d2667e570a2
|
||||
log.error(e.getScriptPubKey().getAsm());
|
||||
log.error(e.getScriptPubKey().getHex());
|
||||
log.error(e.getScriptPubKey().toString());
|
||||
log.error(t.toString());
|
||||
t.printStackTrace();
|
||||
}
|
||||
return new TxOutput(e.getN(),
|
||||
e.getValue().movePointRight(8).longValue(),
|
||||
e.getScriptPubKey().getAddresses(),
|
||||
e.getScriptPubKey().getHex() != null ? new Script(HEX.decode(e.getScriptPubKey().getHex())) : null))
|
||||
scriptProgramBytes,
|
||||
rawTransaction.getTxId());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// rawTransaction.getTime() is in seconds but we keep it in ms internally
|
||||
|
|
|
@ -40,7 +40,7 @@ public class BsqBlockchainRpcServiceMain {
|
|||
BsqBlockchainRpcService blockchainRpcService = new BsqBlockchainRpcService(rpcUser, rpcPassword,
|
||||
rpcPort, rpcBlockPort, rpcWalletPort);
|
||||
BsqBlockchainManager bsqBlockchainManager = new BsqBlockchainManager(blockchainRpcService);
|
||||
bsqBlockchainManager.onAllServicesInitialized(errorMessage -> log.error(errorMessage));
|
||||
bsqBlockchainManager.onAllServicesInitialized(log::error);
|
||||
|
||||
while (true) {
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ import com.neemre.btcdcli4j.core.domain.Block;
|
|||
import io.bisq.common.app.DevEnv;
|
||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.common.handlers.ResultHandler;
|
||||
import io.bisq.common.util.Profiler;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.common.util.Tuple3;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -56,9 +56,9 @@ abstract public class BsqBlockchainService {
|
|||
|
||||
abstract void setup(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler);
|
||||
|
||||
abstract ListenableFuture<Tuple3<Set<BsqBlock>, BsqUTXOMap, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId);
|
||||
abstract ListenableFuture<Tuple2<BsqUTXOMap, Integer>> syncFromGenesis(BsqUTXOMap bsqUTXOMap, BsqTXOMap bsqTXOMap, int genesisBlockHeight, String genesisTxId);
|
||||
|
||||
abstract void syncFromGenesisCompete(String genesisTxId, int genesisBlockHeight, Consumer<Block> onNewBlockHandler);
|
||||
abstract void syncFromGenesisCompete(BsqUTXOMap bsqUTXOMap, BsqTXOMap bsqTXOMap, String genesisTxId, int genesisBlockHeight, Consumer<Block> onNewBlockHandler);
|
||||
|
||||
abstract int requestChainHeadHeight() throws BitcoindException, CommunicationException;
|
||||
|
||||
|
@ -67,44 +67,66 @@ abstract public class BsqBlockchainService {
|
|||
abstract Tx requestTransaction(String txId) throws BsqBlockchainException;
|
||||
|
||||
@VisibleForTesting
|
||||
Tuple2<Set<BsqBlock>, BsqUTXOMap> parseAllBlocksFromGenesis(int chainHeadHeight,
|
||||
BsqUTXOMap parseAllBlocksFromGenesis(BsqUTXOMap bsqUTXOMap,
|
||||
BsqTXOMap bsqTXOMap,
|
||||
int chainHeadHeight,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId) throws BsqBlockchainException {
|
||||
try {
|
||||
BsqUTXOMap bsqUTXOMap = new BsqUTXOMap();
|
||||
Set<BsqBlock> blocks = new HashSet<>();
|
||||
log.info("chainHeadHeight=" + chainHeadHeight);
|
||||
long startTs = System.currentTimeMillis();
|
||||
long startTotalTs = System.currentTimeMillis();
|
||||
for (int height = genesisBlockHeight; height <= chainHeadHeight; height++) {
|
||||
long startBlockTs = System.currentTimeMillis();
|
||||
Block btcdBlock = requestBlock(height);
|
||||
log.info("Current block height=" + height);
|
||||
|
||||
// 1 block has about 3 MB
|
||||
final BsqBlock bsqBlock = new BsqBlock(btcdBlock.getTx(), btcdBlock.getHeight());
|
||||
blocks.add(bsqBlock);
|
||||
|
||||
String oldBsqUTXOMap = bsqUTXOMap.toString();
|
||||
parseBlock(bsqBlock,
|
||||
genesisBlockHeight,
|
||||
genesisTxId,
|
||||
bsqUTXOMap);
|
||||
bsqUTXOMap,
|
||||
bsqTXOMap);
|
||||
String newBsqUTXOMap = bsqUTXOMap.toString();
|
||||
if (!oldBsqUTXOMap.equals(newBsqUTXOMap))
|
||||
log.info(bsqUTXOMap.toString());
|
||||
|
||||
log.info("Parsing for block {} took {} ms. Total: {} ms for {} blocks",
|
||||
height,
|
||||
(System.currentTimeMillis() - startBlockTs),
|
||||
(System.currentTimeMillis() - startTotalTs),
|
||||
(height - genesisBlockHeight));
|
||||
Profiler.printSystemLoad(log);
|
||||
}
|
||||
log.info("Took {} ms", System.currentTimeMillis() - startTs);
|
||||
return new Tuple2<>(blocks, bsqUTXOMap);
|
||||
log.info("Parsing for all blocks since genesis took {} ms", System.currentTimeMillis() - startTotalTs);
|
||||
return bsqUTXOMap;
|
||||
} catch (Throwable t) {
|
||||
throw new BsqBlockchainException(t.getMessage(), t);
|
||||
log.error(t.toString());
|
||||
t.printStackTrace();
|
||||
throw new BsqBlockchainException(t);
|
||||
}
|
||||
}
|
||||
|
||||
void parseBlock(BsqBlock block,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId,
|
||||
BsqUTXOMap bsqUTXOMap)
|
||||
BsqUTXOMap bsqUTXOMap,
|
||||
BsqTXOMap bsqTXOMap)
|
||||
throws BsqBlockchainException {
|
||||
int blockHeight = block.getHeight();
|
||||
log.debug("Parse block at height={} ", blockHeight);
|
||||
// We add all transactions to the block
|
||||
|
||||
// TODO here we hve the performance bottleneck. takes about 4 sec.
|
||||
// check if there is more efficient rpc calls for tx ranges or all txs in a block with btcd 14
|
||||
List<String> txIds = block.getTxIds();
|
||||
//long startTs = System.currentTimeMillis();
|
||||
for (String txId : txIds) {
|
||||
block.addTx(requestTransaction(txId));
|
||||
}
|
||||
//log.info("requestTransaction took {} ms for {} txs", System.currentTimeMillis() - startTs, txIds.size());
|
||||
|
||||
// First we check for the genesis tx
|
||||
// All outputs of genesis are valid BSQ UTXOs
|
||||
|
@ -112,30 +134,29 @@ abstract public class BsqBlockchainService {
|
|||
if (blockHeight == genesisBlockHeight) {
|
||||
txByTxIdMap.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().equals(genesisTxId))
|
||||
.forEach(entry -> parseGenesisTx(entry.getValue(), blockHeight, bsqUTXOMap));
|
||||
.forEach(entry -> parseGenesisTx(entry.getValue(), blockHeight, bsqUTXOMap, bsqTXOMap));
|
||||
}
|
||||
|
||||
// Worst case is that all txs in a block are depending on another, so only once get resolved at each iteration.
|
||||
// Min tx size is 189 bytes (normally about 240 bytes), 1 MB can contain max. about 5300 txs (usually 2000).
|
||||
// Realistically we don't expect more then a few recursive calls.
|
||||
updateBsqUtxoMapFromBlock(txByTxIdMap.values(), bsqUTXOMap, blockHeight, 0, 5300);
|
||||
updateBsqUtxoMapFromBlock(txByTxIdMap.values(), bsqUTXOMap, bsqTXOMap, blockHeight, 0, 5300);
|
||||
}
|
||||
|
||||
// Recursive method
|
||||
// Performance-wise the recursion does not hurt (e.g. 5-20 ms).
|
||||
void updateBsqUtxoMapFromBlock(Collection<Tx> transactions,
|
||||
BsqUTXOMap bsqUTXOMap,
|
||||
BsqTXOMap bsqTXOMap,
|
||||
int blockHeight,
|
||||
int recursionCounter,
|
||||
int maxRecursions) {
|
||||
|
||||
if (recursionCounter > 10)
|
||||
log.warn("Unusual high recursive calls at resolveConnectedTxs. recursionCounter=" + recursionCounter);
|
||||
|
||||
// The set of txIds of txs which are used for inputs of another tx in same block
|
||||
Set<String> intraBlockSpendingTxIdSet = getIntraBlockSpendingTxIdSet(transactions);
|
||||
|
||||
Set<Tx> txsWithoutInputsFromSameBlock = new HashSet<>();
|
||||
Set<Tx> txsWithInputsFromSameBlock = new HashSet<>();
|
||||
|
||||
// First we find the txs which have no intra-block inputs
|
||||
outerLoop:
|
||||
for (Tx tx : transactions) {
|
||||
|
@ -154,10 +175,19 @@ abstract public class BsqBlockchainService {
|
|||
txsWithoutInputsFromSameBlock.add(tx);
|
||||
}
|
||||
|
||||
// Usual values is up to 25
|
||||
// There are soem blocks where it seems devs have tested many depending txs, but even
|
||||
// those dont exceed 200 recursions and are old blocks from 2012 or so...
|
||||
// TOD) check strategy btc core uses (sorting the dependency graph would be an optimisation)
|
||||
if (recursionCounter > 30) {
|
||||
log.warn("Unusual high recursive calls at resolveConnectedTxs. recursionCounter=" + recursionCounter);
|
||||
log.warn("txsWithoutInputsFromSameBlock " + txsWithoutInputsFromSameBlock.size());
|
||||
log.warn("txsWithInputsFromSameBlock " + txsWithInputsFromSameBlock.size());
|
||||
}
|
||||
// we check if we have any valid BSQ utxo from that tx set
|
||||
if (!txsWithoutInputsFromSameBlock.isEmpty()) {
|
||||
for (Tx tx : txsWithoutInputsFromSameBlock) {
|
||||
updateBsqUtxoMapFromTx(tx, blockHeight, bsqUTXOMap);
|
||||
updateBsqUtxoMapFromTx(tx, blockHeight, bsqUTXOMap, bsqTXOMap);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,7 +197,7 @@ abstract public class BsqBlockchainService {
|
|||
// TODO recursion risk?
|
||||
if (!txsWithInputsFromSameBlock.isEmpty()) {
|
||||
if (recursionCounter < maxRecursions) {
|
||||
updateBsqUtxoMapFromBlock(txsWithInputsFromSameBlock, bsqUTXOMap, blockHeight, ++recursionCounter, maxRecursions);
|
||||
updateBsqUtxoMapFromBlock(txsWithInputsFromSameBlock, bsqUTXOMap, bsqTXOMap, blockHeight, ++recursionCounter, maxRecursions);
|
||||
} else {
|
||||
final String msg = "We exceeded our max. recursions for resolveConnectedTxs.\n" +
|
||||
"txsWithInputsFromSameBlock=" + txsWithInputsFromSameBlock.toString() + "\n" +
|
||||
|
@ -177,7 +207,7 @@ abstract public class BsqBlockchainService {
|
|||
throw new RuntimeException(msg);
|
||||
}
|
||||
} else {
|
||||
log.info("We have no more txsWithInputsFromSameBlock.");
|
||||
log.debug("We have no more txsWithInputsFromSameBlock.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,7 +223,8 @@ abstract public class BsqBlockchainService {
|
|||
|
||||
private boolean updateBsqUtxoMapFromTx(Tx tx,
|
||||
int blockHeight,
|
||||
BsqUTXOMap bsqUTXOMap) {
|
||||
BsqUTXOMap bsqUTXOMap,
|
||||
BsqTXOMap bsqTXOMap) {
|
||||
String txId = tx.getId();
|
||||
List<TxOutput> outputs = tx.getOutputs();
|
||||
boolean utxoChanged = false;
|
||||
|
@ -222,12 +253,16 @@ abstract public class BsqBlockchainService {
|
|||
TxOutput txOutput = outputs.get(outputIndex);
|
||||
availableValue = availableValue - txOutput.getValue();
|
||||
if (availableValue >= 0) {
|
||||
if (txOutput.getAddresses().size() != 1) {
|
||||
final String msg = "We got a address list with more or less than 1 address for a BsqUTXO. " +
|
||||
"Seems to be a raw MS. Raw MS are not supported with BSQ.\n" + this.toString();
|
||||
log.warn(msg);
|
||||
if (DevEnv.DEV_MODE)
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
// We are spending available tokens
|
||||
BsqUTXO bsqUTXO = new BsqUTXO(txId,
|
||||
blockHeight,
|
||||
false,
|
||||
txOutput);
|
||||
bsqUTXOMap.putByTuple(txId, outputIndex, bsqUTXO);
|
||||
bsqUTXOMap.add(new BsqUTXO(txOutput, blockHeight, false));
|
||||
bsqTXOMap.add(txOutput);
|
||||
|
||||
if (availableValue == 0) {
|
||||
log.debug("We don't have anymore BSQ to spend");
|
||||
|
@ -255,7 +290,10 @@ abstract public class BsqBlockchainService {
|
|||
|
||||
|
||||
@VisibleForTesting
|
||||
void parseGenesisTx(Tx tx, int blockHeight, BsqUTXOMap bsqUTXOMap) {
|
||||
void parseGenesisTx(Tx tx,
|
||||
int blockHeight,
|
||||
BsqUTXOMap bsqUTXOMap,
|
||||
BsqTXOMap bsqTXOMap) {
|
||||
String txId = tx.getId();
|
||||
List<TxOutput> outputs = tx.getOutputs();
|
||||
|
||||
|
@ -263,11 +301,15 @@ abstract public class BsqBlockchainService {
|
|||
// Genesis tx uses all outputs as BSQ outputs
|
||||
for (int index = 0; index < outputs.size(); index++) {
|
||||
TxOutput txOutput = outputs.get(index);
|
||||
BsqUTXO bsqUTXO = new BsqUTXO(txId,
|
||||
blockHeight,
|
||||
true,
|
||||
txOutput);
|
||||
bsqUTXOMap.putByTuple(txId, index, bsqUTXO);
|
||||
if (txOutput.getAddresses().size() != 1) {
|
||||
final String msg = "We got a address list with more or less than 1 address. " +
|
||||
"Seems to be a raw MS. Raw MS are not supported with BSQ.\n" + this.toString();
|
||||
log.warn(msg);
|
||||
if (DevEnv.DEV_MODE)
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
bsqUTXOMap.add(new BsqUTXO(txOutput, blockHeight, true));
|
||||
bsqTXOMap.add(txOutput);
|
||||
}
|
||||
checkArgument(!bsqUTXOMap.isEmpty(), "Genesis tx need to have BSQ utxo when parsing genesis block");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare 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.
|
||||
*
|
||||
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bisq.core.dao.blockchain;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.collections.ObservableMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
// Map of any ever existing TxOutput which was a valid BSQ
|
||||
@Slf4j
|
||||
public class BsqTXOMap {
|
||||
// We don't use a Lombok delegate here as we want control the access to our map
|
||||
private ObservableMap<TxIdIndexTuple, TxOutput> map = FXCollections.observableHashMap();
|
||||
private Set<String> txIdSet = new HashSet<>();
|
||||
|
||||
public boolean containsTuple(String txId, int index) {
|
||||
return map.containsKey(new TxIdIndexTuple(txId, index));
|
||||
}
|
||||
|
||||
public Object add(TxOutput txOutput) {
|
||||
txIdSet.add(txOutput.getTxId());
|
||||
return map.put(new TxIdIndexTuple(txOutput.getTxId(), txOutput.getIndex()), txOutput);
|
||||
}
|
||||
|
||||
public TxOutput getByTuple(String txId, int index) {
|
||||
return map.get(new TxIdIndexTuple(txId, index));
|
||||
}
|
||||
|
||||
public void addListener(MapChangeListener<TxIdIndexTuple, TxOutput> listener) {
|
||||
map.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BsqUTXOMap " + map.toString();
|
||||
}
|
||||
|
||||
public Collection<TxOutput> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
public Set<String> getTxIdSet() {
|
||||
return txIdSet;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public Set<Map.Entry<TxIdIndexTuple, TxOutput>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
package io.bisq.core.dao.blockchain;
|
||||
|
||||
import lombok.Value;
|
||||
import org.bitcoinj.script.Script;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -26,52 +26,48 @@ import java.util.List;
|
|||
// 1000 UTXOs - 10 000 UTXOs: 78kb -780kb
|
||||
|
||||
@Value
|
||||
@Slf4j
|
||||
public class BsqUTXO {
|
||||
private final String txId;
|
||||
private final long index;
|
||||
private final long value;
|
||||
private final int height;
|
||||
private final boolean isBsqCoinBase;
|
||||
private final Script script;
|
||||
private final TxOutput output;
|
||||
private final String utxoId;
|
||||
|
||||
// Only at raw MS outputs addresses have more then 1 entry
|
||||
// We do not support raw MS for BSQ but lets see if is needed anyway, might be removed
|
||||
private final List<String> addresses;
|
||||
|
||||
private BsqUTXO(String txId, int index, long value, int height, boolean isBsqCoinBase, Script script, List<String> addresses) {
|
||||
this.txId = txId;
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
public BsqUTXO(TxOutput output, int height, boolean isBsqCoinBase) {
|
||||
this.height = height;
|
||||
this.isBsqCoinBase = isBsqCoinBase;
|
||||
this.script = script;
|
||||
this.addresses = addresses;
|
||||
this.output = output;
|
||||
|
||||
utxoId = txId + ":" + index;
|
||||
utxoId = output.getTxId() + ":" + output.getIndex();
|
||||
}
|
||||
|
||||
public BsqUTXO(String txId, int height, boolean isBsqCoinBase, TxOutput output) {
|
||||
this(txId,
|
||||
output.getIndex(),
|
||||
output.getValue(),
|
||||
height,
|
||||
isBsqCoinBase,
|
||||
output.getScript(),
|
||||
output.getAddresses());
|
||||
public String getAddress() {
|
||||
// Only at raw MS outputs addresses have more then 1 entry
|
||||
// We do not support raw MS for BSQ but lets see if is needed anyway, might be removed
|
||||
final List<String> addresses = output.getAddresses();
|
||||
return addresses.size() == 1 ? addresses.get(0) : addresses.toString();
|
||||
}
|
||||
|
||||
public long getValue() {
|
||||
return output.getValue();
|
||||
}
|
||||
|
||||
public String getTxId() {
|
||||
return output.getTxId();
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return output.getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BsqUTXO{" +
|
||||
"\n txId='" + txId + '\'' +
|
||||
",\n index=" + index +
|
||||
",\n value=" + value +
|
||||
"\n output='" + output + '\'' +
|
||||
",\n height=" + height +
|
||||
",\n isBsqCoinBase=" + isBsqCoinBase +
|
||||
",\n script=" + script +
|
||||
",\n utxoId='" + utxoId + '\'' +
|
||||
",\n addresses=" + addresses +
|
||||
"\n}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,41 +17,65 @@
|
|||
|
||||
package io.bisq.core.dao.blockchain;
|
||||
|
||||
import lombok.Value;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.collections.ObservableMap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class BsqUTXOMap {
|
||||
// We don't use a Lombok delegate here as we want control the access to our map
|
||||
private ObservableMap<TxIdIndexTuple, BsqUTXO> map = FXCollections.observableHashMap();
|
||||
private Set<String> txIdSet = new HashSet<>();
|
||||
|
||||
public class BsqUTXOMap extends HashMap<BsqUTXOMap.TxIdIndexTuple, BsqUTXO> {
|
||||
public boolean containsTuple(String txId, int index) {
|
||||
return super.containsKey(new TxIdIndexTuple(txId, index));
|
||||
return map.containsKey(new TxIdIndexTuple(txId, index));
|
||||
}
|
||||
|
||||
public Object putByTuple(String txId, int index, BsqUTXO bsqUTXO) {
|
||||
return super.put(new TxIdIndexTuple(txId, index), bsqUTXO);
|
||||
public Object add(BsqUTXO bsqUTXO) {
|
||||
txIdSet.add(bsqUTXO.getTxId());
|
||||
return map.put(new TxIdIndexTuple(bsqUTXO.getTxId(), bsqUTXO.getIndex()), bsqUTXO);
|
||||
}
|
||||
|
||||
public BsqUTXO getByTuple(String txId, int index) {
|
||||
return super.get(new TxIdIndexTuple(txId, index));
|
||||
return map.get(new TxIdIndexTuple(txId, index));
|
||||
}
|
||||
|
||||
public BsqUTXO removeByTuple(String txId, int index) {
|
||||
return super.remove(new TxIdIndexTuple(txId, index));
|
||||
txIdSet.remove(txId);
|
||||
return map.remove(new TxIdIndexTuple(txId, index));
|
||||
}
|
||||
|
||||
public void addListener(MapChangeListener<TxIdIndexTuple, BsqUTXO> listener) {
|
||||
map.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BsqUTXOMap " + super.toString();
|
||||
return "BsqUTXOMap " + map.toString();
|
||||
}
|
||||
|
||||
|
||||
@Value
|
||||
static class TxIdIndexTuple {
|
||||
private final String txId;
|
||||
private final int index;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\nTxIdIndexTuple(" + txId + ":" + index + ")";
|
||||
public Collection<BsqUTXO> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
public Set<String> getTxIdSet() {
|
||||
return txIdSet;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public Set<Map.Entry<TxIdIndexTuple, BsqUTXO>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare 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.
|
||||
*
|
||||
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bisq.core.dao.blockchain;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class TxIdIndexTuple {
|
||||
private final String txId;
|
||||
private final int index;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\nTxIdIndexTuple(" + txId + ":" + index + ")";
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
package io.bisq.core.dao.blockchain;
|
||||
|
||||
import lombok.Value;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -27,7 +27,8 @@ public class TxOutput {
|
|||
private final int index;
|
||||
private final long value;
|
||||
private final List<String> addresses;
|
||||
private final Script script;
|
||||
private final byte[] scriptProgramBytes;
|
||||
private final String txId;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -35,7 +36,7 @@ public class TxOutput {
|
|||
"\nindex=" + index +
|
||||
",\nvalue=" + value +
|
||||
",\naddresses=" + addresses +
|
||||
",\nscript=" + script +
|
||||
",\nscriptProgramBytes=" + Hex.toHexString(scriptProgramBytes) +
|
||||
"}\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ public class ProvidersRepository {
|
|||
//providers = "http://t4wlzy7l6k4hnolg.onion/, http://g27szt7aw2vrtowe.onion/";
|
||||
}
|
||||
}
|
||||
|
||||
providerArray = providers.replace(" ", "").split(",");
|
||||
int index = new Random().nextInt(providerArray.length);
|
||||
baseUrl = providerArray[index];
|
||||
|
|
|
@ -20,7 +20,6 @@ package io.bisq.core.dao.blockchain;
|
|||
import com.neemre.btcdcli4j.core.BitcoindException;
|
||||
import com.neemre.btcdcli4j.core.CommunicationException;
|
||||
import com.neemre.btcdcli4j.core.domain.*;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.junit.After;
|
||||
|
@ -63,9 +62,13 @@ public class BsqBlockchainServiceTest {
|
|||
public static final long ADDRESS_TX_2_VALUE = Coin.parseCoin("0.00001000").value;
|
||||
|
||||
private MockBsqBlockchainService service;
|
||||
private BsqUTXOMap bsqUTXOMap;
|
||||
private BsqTXOMap bsqTXOMap;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
bsqUTXOMap = new BsqUTXOMap();
|
||||
bsqTXOMap = new BsqTXOMap();
|
||||
service = new MockBsqBlockchainService();
|
||||
}
|
||||
|
||||
|
@ -88,7 +91,7 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_0);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
|
||||
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(GEN_TX_ID, 1);
|
||||
|
@ -127,7 +130,7 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_1);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
|
||||
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(TX1_ID, 0);
|
||||
|
@ -179,7 +182,7 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_1);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
|
||||
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(TX2_ID, 0);
|
||||
|
@ -231,7 +234,7 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_2);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
|
||||
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(TX2_ID, 0);
|
||||
|
@ -285,7 +288,7 @@ public class BsqBlockchainServiceTest {
|
|||
|
||||
service.buildBlocks(BLOCK_0, BLOCK_1);
|
||||
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis().second;
|
||||
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
|
||||
|
||||
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(TX2_ID, 0);
|
||||
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(TX2_ID, 0));
|
||||
|
@ -378,9 +381,11 @@ public class BsqBlockchainServiceTest {
|
|||
}
|
||||
|
||||
|
||||
private Tuple2<Set<BsqBlock>, BsqUTXOMap> parseAllBlocksFromGenesis()
|
||||
private BsqUTXOMap parseAllBlocksFromGenesis()
|
||||
throws BitcoindException, CommunicationException, BsqBlockchainException {
|
||||
return service.parseAllBlocksFromGenesis(service.requestChainHeadHeight(),
|
||||
return service.parseAllBlocksFromGenesis(bsqUTXOMap,
|
||||
bsqTXOMap,
|
||||
service.requestChainHeadHeight(),
|
||||
BLOCK_0,
|
||||
GEN_TX_ID);
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
|||
() -> UserThread.execute(() -> {
|
||||
log.info("Wallets restored with seed words");
|
||||
new Popup().feedback(Res.get("seed.restore.success"))
|
||||
.useShutDownButton();
|
||||
.useShutDownButton().show();
|
||||
}),
|
||||
throwable -> UserThread.execute(() -> {
|
||||
log.error(throwable.getMessage());
|
||||
|
|
|
@ -20,6 +20,8 @@ package io.bisq.gui.main.dao.wallet.tx;
|
|||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.btc.listeners.TxConfidenceListener;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.btc.wallet.WalletUtils;
|
||||
import io.bisq.gui.components.indicator.TxConfidenceIndicator;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import javafx.scene.control.Tooltip;
|
||||
|
@ -42,6 +44,7 @@ class BsqTxListItem {
|
|||
@Getter
|
||||
private final Transaction transaction;
|
||||
private BsqWalletService bsqWalletService;
|
||||
private BtcWalletService btcWalletService;
|
||||
@Getter
|
||||
private final Date date;
|
||||
@Getter
|
||||
|
@ -61,9 +64,12 @@ class BsqTxListItem {
|
|||
|
||||
private TxConfidenceListener txConfidenceListener;
|
||||
|
||||
public BsqTxListItem(Transaction transaction, BsqWalletService bsqWalletService) {
|
||||
public BsqTxListItem(Transaction transaction,
|
||||
BsqWalletService bsqWalletService,
|
||||
BtcWalletService btcWalletService) {
|
||||
this.transaction = transaction;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
|
||||
txId = transaction.getHashAsString();
|
||||
date = transaction.getUpdateTime();
|
||||
|
@ -88,16 +94,32 @@ class BsqTxListItem {
|
|||
amount = Coin.ZERO;
|
||||
direction = "";
|
||||
}
|
||||
String result = null;
|
||||
|
||||
String foreignReceiverAddress = null;
|
||||
for (TransactionOutput output : transaction.getOutputs()) {
|
||||
if (!bsqWalletService.isTransactionOutputMine(output)) {
|
||||
if (output.getScriptPubKey().isSentToAddress()
|
||||
|| output.getScriptPubKey().isPayToScriptHash()) {
|
||||
result = output.getScriptPubKey().getToAddress(bsqWalletService.getParams()).toString();
|
||||
if (!bsqWalletService.isTransactionOutputMine(output) &&
|
||||
!btcWalletService.isTransactionOutputMine(output) &&
|
||||
WalletUtils.isOutputScriptConvertableToAddress(output)) {
|
||||
// We don't support send txs with multiple outputs to multiple receivers, so we can
|
||||
// assume that only one output is not from our own wallets.
|
||||
foreignReceiverAddress = WalletUtils.getAddressStringFromOutput(output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// In the case we sent to ourselves (either to BSQ or BTC wallet) we show the first as the other is
|
||||
// usually the change output.
|
||||
String ownReceiverAddress = Res.get("shared.na");
|
||||
if (foreignReceiverAddress != null) {
|
||||
for (TransactionOutput output : transaction.getOutputs()) {
|
||||
if (WalletUtils.isOutputScriptConvertableToAddress(output)) {
|
||||
ownReceiverAddress = WalletUtils.getAddressStringFromOutput(output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
address = result != null ? result : "";
|
||||
|
||||
address = foreignReceiverAddress != null ? foreignReceiverAddress : ownReceiverAddress;
|
||||
}
|
||||
|
||||
private void setupConfidence(BsqWalletService bsqWalletService) {
|
||||
|
|
|
@ -20,12 +20,15 @@ package io.bisq.gui.main.dao.wallet.tx;
|
|||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
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.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.overlays.popups.Popup;
|
||||
import io.bisq.gui.util.BsqFormatter;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.gui.util.Layout;
|
||||
|
@ -54,6 +57,8 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
|
|||
private int gridRow = 0;
|
||||
private BsqFormatter bsqFormatter;
|
||||
private BsqWalletService bsqWalletService;
|
||||
private BsqBlockchainManager bsqBlockchainManager;
|
||||
private BtcWalletService btcWalletService;
|
||||
private BalanceUtil balanceUtil;
|
||||
private Preferences preferences;
|
||||
private ListChangeListener<Transaction> walletBsqTransactionsListener;
|
||||
|
@ -67,9 +72,12 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
|
|||
|
||||
@Inject
|
||||
private BsqTxView(BsqFormatter bsqFormatter, BsqWalletService bsqWalletService,
|
||||
BalanceUtil balanceUtil, Preferences preferences) {
|
||||
BsqBlockchainManager bsqBlockchainManager,
|
||||
BtcWalletService btcWalletService, BalanceUtil balanceUtil, Preferences preferences) {
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.bsqBlockchainManager = bsqBlockchainManager;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.balanceUtil = balanceUtil;
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
@ -120,9 +128,22 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
|
|||
observableList.forEach(BsqTxListItem::cleanup);
|
||||
|
||||
Set<BsqTxListItem> list = bsqWalletService.getWalletBsqTransactions().stream()
|
||||
.map(t -> new BsqTxListItem(t, bsqWalletService))
|
||||
.map(t -> new BsqTxListItem(t, bsqWalletService, btcWalletService))
|
||||
.collect(Collectors.toSet());
|
||||
observableList.setAll(list);
|
||||
|
||||
final Set<Transaction> invalidBsqTransactions = bsqWalletService.getInvalidBsqTransactions();
|
||||
if (!invalidBsqTransactions.isEmpty() && bsqBlockchainManager.isUtxoAvailable()) {
|
||||
Set<String> txIds = invalidBsqTransactions.stream().map(t -> t.getHashAsString()).collect(Collectors.toSet());
|
||||
String key = "invalidBsqTransactionsWarning_" + txIds;
|
||||
if (preferences.showAgain(key))
|
||||
new Popup().warning("We detected invalid Bsq transactions.\n" +
|
||||
"This must not happen if you used the bisq application only to send or receive BSQ.\n\n" +
|
||||
"invalidBsqTransactionIds=" + txIds.toString())
|
||||
.width(800)
|
||||
.dontShowAgainId(key, preferences)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void addDateColumn() {
|
||||
|
|
|
@ -21,6 +21,7 @@ import io.bisq.common.locale.Res;
|
|||
import io.bisq.core.btc.listeners.TxConfidenceListener;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.btc.wallet.WalletUtils;
|
||||
import io.bisq.core.offer.Offer;
|
||||
import io.bisq.core.offer.OpenOffer;
|
||||
import io.bisq.core.trade.Tradable;
|
||||
|
@ -90,9 +91,9 @@ class TransactionsListItem {
|
|||
txFeeForBsqPayment = true;
|
||||
} else {
|
||||
direction = Res.get("funds.tx.direction.sentTo");
|
||||
if (output.getScriptPubKey().isSentToAddress()
|
||||
|| output.getScriptPubKey().isPayToScriptHash()) {
|
||||
addressString = output.getScriptPubKey().getToAddress(btcWalletService.getParams()).toString();
|
||||
if (WalletUtils.isOutputScriptConvertableToAddress(output)) {
|
||||
addressString = WalletUtils.getAddressStringFromOutput(output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,11 +103,10 @@ class TransactionsListItem {
|
|||
direction = Res.get("funds.tx.direction.receivedWith");
|
||||
received = true;
|
||||
for (TransactionOutput output : transaction.getOutputs()) {
|
||||
if (!btcWalletService.isTransactionOutputMine(output)) {
|
||||
if (output.getScriptPubKey().isSentToAddress() ||
|
||||
output.getScriptPubKey().isPayToScriptHash()) {
|
||||
addressString = output.getScriptPubKey().getToAddress(btcWalletService.getParams()).toString();
|
||||
}
|
||||
if (btcWalletService.isTransactionOutputMine(output) &&
|
||||
WalletUtils.isOutputScriptConvertableToAddress(output)) {
|
||||
addressString = WalletUtils.getAddressStringFromOutput(output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -119,9 +119,9 @@ class TransactionsListItem {
|
|||
txFeeForBsqPayment = true;
|
||||
} else {
|
||||
outgoing = true;
|
||||
if (output.getScriptPubKey().isSentToAddress()
|
||||
|| output.getScriptPubKey().isPayToScriptHash()) {
|
||||
addressString = output.getScriptPubKey().getToAddress(btcWalletService.getParams()).toString();
|
||||
if (WalletUtils.isOutputScriptConvertableToAddress(output)) {
|
||||
addressString = WalletUtils.getAddressStringFromOutput(output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +207,6 @@ class TransactionsListItem {
|
|||
dateString = formatter.formatDateTime(date);
|
||||
}
|
||||
|
||||
|
||||
public void cleanup() {
|
||||
btcWalletService.removeTxConfidenceListener(txConfidenceListener);
|
||||
}
|
||||
|
|
|
@ -335,8 +335,8 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
// spending tx
|
||||
// MS tx
|
||||
candidates.addAll(transaction.getOutputs().stream()
|
||||
.filter(transactionOutput -> !btcWalletService.isTransactionOutputMine(transactionOutput))
|
||||
.filter(transactionOutput -> transactionOutput.getScriptPubKey().isPayToScriptHash())
|
||||
.filter(output -> !btcWalletService.isTransactionOutputMine(output))
|
||||
.filter(output -> output.getScriptPubKey().isPayToScriptHash())
|
||||
.map(transactionOutput -> transaction)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
|
|
@ -762,9 +762,9 @@ public class Connection implements MessageListener {
|
|||
reportInvalidRequest(RuleViolation.INVALID_DATA_TYPE);
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (Throwable t) {
|
||||
if (!sharedModel.stopped) {
|
||||
log.error("Invalid data arrived at inputHandler of connection " + connection, e);
|
||||
log.error("Invalid data arrived at inputHandler of connection " + connection, t);
|
||||
reportInvalidRequest(RuleViolation.INVALID_DATA_TYPE);
|
||||
}
|
||||
return;
|
||||
|
|
Loading…
Add table
Reference in a new issue