mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 15:10:44 +01:00
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:
parent
a24ca8edaf
commit
60b75f0dbd
23 changed files with 562 additions and 209 deletions
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,16 +79,21 @@ 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;
|
||||
private static String getGenesisTxId() {
|
||||
return WalletUtils.isRegTest() ? REG_TEST_GENESIS_TX_ID : GENESIS_TX_ID;
|
||||
}
|
||||
|
||||
public static int getSnapshotTrigger() {
|
||||
return SNAPSHOT_TRIGGER;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -67,17 +102,22 @@ public class BsqBlockchainManager {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
||||
return txOutputMapClone;
|
||||
}
|
||||
|
||||
|
||||
// We don't use a Lombok delegate here as we want control the access to our map
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue