Fix incorrect handling of logical and/or. Rename rpc optionkeys. Use lombok in BSQ tx value objcts. Use map with txId, index tuple instead of nested map for utxo. Rename BSQ Tx classes. Fix BSQ parsing algo. Add and improve tests.

This commit is contained in:
Manfred Karrer 2017-04-01 11:41:13 -05:00
parent 207f14cd78
commit e96e408e33
38 changed files with 719 additions and 1354 deletions

View file

@ -76,7 +76,7 @@ public class BisqEnvironment extends StandardEnvironment {
private final String btcNetworkDir;
private final String logLevel, providers;
private BitcoinNetwork bitcoinNetwork;
private final String btcNodes, seedNodes, ignoreDevMsg, useTorForBtc, rpcUser, rpcPassword, rpcPort, rpcBlockPort, rpcWalletPort,
private final String btcNodes, seedNodes, ignoreDevMsg, useTorForBtc, rpcUser, rpcPassword, rpcPort, rpcBlockNotificationPort, rpcWalletNotificationPort,
myAddress, banList, dumpStatistics, maxMemory, socks5ProxyBtcAddress, socks5ProxyHttpAddress;
public BitcoinNetwork getBitcoinNetwork() {
@ -176,11 +176,11 @@ public class BisqEnvironment extends StandardEnvironment {
rpcPort = commandLineProperties.containsProperty(RpcOptionKeys.RPC_PORT) ?
(String) commandLineProperties.getProperty(RpcOptionKeys.RPC_PORT) :
"";
rpcBlockPort = commandLineProperties.containsProperty(RpcOptionKeys.RPC_BLOCK_PORT) ?
(String) commandLineProperties.getProperty(RpcOptionKeys.RPC_BLOCK_PORT) :
rpcBlockNotificationPort = commandLineProperties.containsProperty(RpcOptionKeys.RPC_BLOCK_NOTIFICATION_PORT) ?
(String) commandLineProperties.getProperty(RpcOptionKeys.RPC_BLOCK_NOTIFICATION_PORT) :
"";
rpcWalletPort = commandLineProperties.containsProperty(RpcOptionKeys.RPC_WALLET_PORT) ?
(String) commandLineProperties.getProperty(RpcOptionKeys.RPC_WALLET_PORT) :
rpcWalletNotificationPort = commandLineProperties.containsProperty(RpcOptionKeys.RPC_WALLET_NOTIFICATION_PORT) ?
(String) commandLineProperties.getProperty(RpcOptionKeys.RPC_WALLET_NOTIFICATION_PORT) :
"";
//BtcOptionKeys
@ -257,8 +257,8 @@ public class BisqEnvironment extends StandardEnvironment {
setProperty(RpcOptionKeys.RPC_USER, rpcUser);
setProperty(RpcOptionKeys.RPC_PASSWORD, rpcPassword);
setProperty(RpcOptionKeys.RPC_PORT, rpcPort);
setProperty(RpcOptionKeys.RPC_BLOCK_PORT, rpcBlockPort);
setProperty(RpcOptionKeys.RPC_WALLET_PORT, rpcWalletPort);
setProperty(RpcOptionKeys.RPC_BLOCK_NOTIFICATION_PORT, rpcBlockNotificationPort);
setProperty(RpcOptionKeys.RPC_WALLET_NOTIFICATION_PORT, rpcWalletNotificationPort);
setProperty(BtcOptionKeys.BTC_NODES, btcNodes);
setProperty(BtcOptionKeys.USE_TOR_FOR_BTC, useTorForBtc);

View file

@ -166,10 +166,10 @@ public abstract class BisqExecutable {
parser.accepts(RpcOptionKeys.RPC_PORT,
description("Bitcoind rpc port", ""))
.withRequiredArg();
parser.accepts(RpcOptionKeys.RPC_BLOCK_PORT,
parser.accepts(RpcOptionKeys.RPC_BLOCK_NOTIFICATION_PORT,
description("Bitcoind rpc port for block notifications", ""))
.withRequiredArg();
parser.accepts(RpcOptionKeys.RPC_WALLET_PORT,
parser.accepts(RpcOptionKeys.RPC_WALLET_NOTIFICATION_PORT,
description("Bitcoind rpc port for wallet notifications", ""))
.withRequiredArg();

View file

@ -18,6 +18,7 @@
package io.bisq.core.btc.wallet;
import io.bisq.core.dao.blockchain.BsqUTXO;
import io.bisq.core.dao.blockchain.BsqUTXOMap;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionOutput;
@ -50,8 +51,8 @@ class BsqCoinSelector extends BisqDefaultCoinSelector {
this.permitForeignPendingTx = permitForeignPendingTx;
}
public void setUtxoSet(Set<BsqUTXO> utxoSet) {
utxoSet.stream().forEach(utxo -> {
public void setUtxoMap(BsqUTXOMap bsqUTXOMap) {
bsqUTXOMap.values().stream().forEach(utxo -> {
Script script = utxo.getScript();
if (!utxoSetByScriptMap.containsKey(script))
utxoSetByScriptMap.put(script, new HashSet<>());

View file

@ -25,7 +25,7 @@ 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.BsqBlockchainManager;
import io.bisq.core.dao.blockchain.BsqUTXO;
import io.bisq.core.dao.blockchain.BsqUTXOMap;
import io.bisq.core.provider.fee.FeeService;
import io.bisq.core.user.Preferences;
import org.bitcoinj.core.*;
@ -36,9 +36,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -149,20 +147,16 @@ public class BsqWalletService extends WalletService {
if (resultHandler != null)
resultHandler.handleResult();
} else {
bsqBlockchainManager.addUtxoListener(utxoByTxIdMap -> {
applyUtxoSetToUTXOProvider(utxoByTxIdMap);
bsqBlockchainManager.addUtxoListener(bsqUTXOMap -> {
applyUtxoSetToUTXOProvider(bsqUTXOMap);
if (resultHandler != null)
resultHandler.handleResult();
});
}
}
private void applyUtxoSetToUTXOProvider(Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap) {
Set<BsqUTXO> utxoSet = new HashSet<>();
utxoByTxIdMap.entrySet().stream()
.forEach(e -> e.getValue().entrySet().stream()
.forEach(u -> utxoSet.add(u.getValue())));
bsqCoinSelector.setUtxoSet(utxoSet);
private void applyUtxoSetToUTXOProvider(BsqUTXOMap bsqUTXOMap) {
bsqCoinSelector.setUtxoMap(bsqUTXOMap);
}

View file

@ -18,9 +18,9 @@
package io.bisq.core.dao;
import com.google.inject.Inject;
import io.bisq.common.handlers.ErrorMessageHandler;
import io.bisq.core.btc.provider.squ.BsqUtxoFeedService;
import io.bisq.core.btc.wallet.BsqWalletService;
import io.bisq.core.dao.blockchain.BsqBlockchainException;
import io.bisq.core.dao.blockchain.BsqBlockchainManager;
import io.bisq.core.dao.compensation.CompensationRequestManager;
import io.bisq.core.dao.vote.VotingManager;
@ -37,6 +37,7 @@ public class DaoManager {
private final VotingManager voteManager;
private final CompensationRequestManager compensationRequestManager;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@ -56,12 +57,12 @@ public class DaoManager {
this.compensationRequestManager = compensationRequestManager;
}
public void onAllServicesInitialized() throws BsqBlockchainException {
public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) {
daoPeriodService.onAllServicesInitialized();
bsqUtxoFeedService.onAllServicesInitialized();
voteManager.onAllServicesInitialized();
compensationRequestManager.onAllServicesInitialized();
bsqBlockchainManager.onAllServicesInitialized();
bsqBlockchainManager.onAllServicesInitialized(errorMessageHandler);
}
@ -73,15 +74,4 @@ public class DaoManager {
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
}

View file

@ -55,8 +55,8 @@ public class DaoModule extends AppModule {
bindConstant().annotatedWith(named(RpcOptionKeys.RPC_USER)).to(env.getRequiredProperty(RpcOptionKeys.RPC_USER));
bindConstant().annotatedWith(named(RpcOptionKeys.RPC_PASSWORD)).to(env.getRequiredProperty(RpcOptionKeys.RPC_PASSWORD));
bindConstant().annotatedWith(named(RpcOptionKeys.RPC_PORT)).to(env.getRequiredProperty(RpcOptionKeys.RPC_PORT));
bindConstant().annotatedWith(named(RpcOptionKeys.RPC_BLOCK_PORT)).to(env.getRequiredProperty(RpcOptionKeys.RPC_BLOCK_PORT));
bindConstant().annotatedWith(named(RpcOptionKeys.RPC_WALLET_PORT)).to(env.getRequiredProperty(RpcOptionKeys.RPC_WALLET_PORT));
bindConstant().annotatedWith(named(RpcOptionKeys.RPC_BLOCK_NOTIFICATION_PORT)).to(env.getRequiredProperty(RpcOptionKeys.RPC_BLOCK_NOTIFICATION_PORT));
bindConstant().annotatedWith(named(RpcOptionKeys.RPC_WALLET_NOTIFICATION_PORT)).to(env.getRequiredProperty(RpcOptionKeys.RPC_WALLET_NOTIFICATION_PORT));
}
}

View file

@ -4,6 +4,6 @@ public class RpcOptionKeys {
public static final String RPC_USER = "rpcUser";
public static final String RPC_PASSWORD = "rpcPassword";
public static final String RPC_PORT = "rpcPort";
public static final String RPC_BLOCK_PORT = "rpcBlockPort";
public static final String RPC_WALLET_PORT = "rpcWalletPort";
public static final String RPC_BLOCK_NOTIFICATION_PORT = "rpcBlockNotificationPort";
public static final String RPC_WALLET_NOTIFICATION_PORT = "rpcWalletNotificationPort";
}

View file

@ -17,65 +17,30 @@
package io.bisq.core.dao.blockchain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Value
@Slf4j
public class BsqBlock {
private static final Logger log = LoggerFactory.getLogger(BsqBlock.class);
private final int height;
private final List<String> txIds;
private final Map<String, Tx> txByTxIdMap = new HashMap<>();
public final int blockHeight;
public final List<String> txIds;
private final Map<String, BsqTransaction> bsqTransactions = new HashMap<>();
public BsqBlock(List<String> txIds, int blockHeight) {
public BsqBlock(List<String> txIds, int height) {
this.txIds = txIds;
this.blockHeight = blockHeight;
this.height = height;
}
public void addBsqTransaction(BsqTransaction bsqTransaction) {
bsqTransactions.put(bsqTransaction.txId, bsqTransaction);
public void addTx(Tx tx) {
txByTxIdMap.put(tx.getId(), tx);
}
public BsqTransaction getBsqTransaction(String txId) {
return bsqTransactions.get(txId);
}
public Map<String, BsqTransaction> getTransactions() {
return bsqTransactions;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BsqBlock bsqBlock = (BsqBlock) o;
if (blockHeight != bsqBlock.blockHeight) return false;
if (txIds != null ? !txIds.equals(bsqBlock.txIds) : bsqBlock.txIds != null) return false;
return !(bsqTransactions != null ? !bsqTransactions.equals(bsqBlock.bsqTransactions) : bsqBlock.bsqTransactions != null);
}
@Override
public int hashCode() {
int result = blockHeight;
result = 31 * result + (txIds != null ? txIds.hashCode() : 0);
result = 31 * result + (bsqTransactions != null ? bsqTransactions.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "BsqBlock{" +
"blockHeight=" + blockHeight +
", txIds=" + txIds +
", bsqTransactions=" + bsqTransactions +
'}';
public Tx getTxByTxId(String txId) {
return txByTxIdMap.get(txId);
}
}

View file

@ -22,6 +22,7 @@ import com.google.common.util.concurrent.Futures;
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.Tuple2;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@ -29,7 +30,6 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class BsqBlockchainManager {
private static final Logger log = LoggerFactory.getLogger(BsqBlockchainManager.class);
@ -37,11 +37,11 @@ public class BsqBlockchainManager {
private final BsqBlockchainService blockchainService;
// regtest
public static final String GENESIS_TX_ID = "c01129ff48082f8f9613dd505899359227cb71aa457903359cfd0ca9c152dcd6";
public static final int GENESIS_BLOCK_HEIGHT = 103;
public static final String GENESIS_TX_ID = "c0ddf75202579fd43e0d5a41ac5bae05a6e8295633695af6c350b9100878c5e7";
public static final int GENESIS_BLOCK_HEIGHT = 102;
protected Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap;
private final List<UtxoListener> utxoListeners = new ArrayList<>();
protected BsqUTXOMap utxoByTxIdMap;
private final List<BsqUTXOListener> bsqUTXOListeners = new ArrayList<>();
private boolean isUtxoAvailable;
protected int chainHeadHeight;
@ -60,23 +60,21 @@ public class BsqBlockchainManager {
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
public void onAllServicesInitialized() {
blockchainService.setup(this::setupComplete, errorMessage -> {
log.error("setup failed" + errorMessage);
});
public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) {
blockchainService.setup(this::setupComplete, errorMessageHandler);
}
public Map<String, Map<Integer, BsqUTXO>> getUtxoByTxIdMap() {
public BsqUTXOMap getUtxoByTxIdMap() {
return utxoByTxIdMap;
}
public void addUtxoListener(UtxoListener utxoListener) {
utxoListeners.add(utxoListener);
public void addUtxoListener(BsqUTXOListener bsqUTXOListener) {
bsqUTXOListeners.add(bsqUTXOListener);
}
public void removeUtxoListener(UtxoListener utxoListener) {
utxoListeners.remove(utxoListener);
public void removeUtxoListener(BsqUTXOListener bsqUTXOListener) {
bsqUTXOListeners.remove(bsqUTXOListener);
}
public boolean isUtxoAvailable() {
@ -89,22 +87,23 @@ public class BsqBlockchainManager {
///////////////////////////////////////////////////////////////////////////////////////////
protected void setupComplete() {
ListenableFuture<Tuple2<Map<String, Map<Integer, BsqUTXO>>, Integer>> future = blockchainService.syncFromGenesis(GENESIS_BLOCK_HEIGHT, GENESIS_TX_ID);
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Map<Integer, BsqUTXO>>, Integer>>() {
ListenableFuture<Tuple2<BsqUTXOMap, Integer>> future =
blockchainService.syncFromGenesis(GENESIS_BLOCK_HEIGHT, GENESIS_TX_ID);
Futures.addCallback(future, new FutureCallback<Tuple2<BsqUTXOMap, Integer>>() {
@Override
public void onSuccess(Tuple2<Map<String, Map<Integer, BsqUTXO>>, Integer> tulpe) {
public void onSuccess(Tuple2<BsqUTXOMap, Integer> tuple) {
UserThread.execute(() -> {
BsqBlockchainManager.this.utxoByTxIdMap = tulpe.first;
chainHeadHeight = tulpe.second;
BsqBlockchainManager.this.utxoByTxIdMap = tuple.first;
chainHeadHeight = tuple.second;
isUtxoAvailable = true;
utxoListeners.stream().forEach(e -> e.onUtxoChanged(utxoByTxIdMap));
bsqUTXOListeners.stream().forEach(e -> e.onBsqUTXOChanged(utxoByTxIdMap));
blockchainService.syncFromGenesisCompete(GENESIS_TX_ID,
GENESIS_BLOCK_HEIGHT,
block -> {
if (block != null) {
btcdBlock -> {
if (btcdBlock != null) {
UserThread.execute(() -> {
try {
blockchainService.parseBlock(new BsqBlock(block.getTx(), block.getHeight()),
blockchainService.parseBlock(new BsqBlock(btcdBlock.getTx(), btcdBlock.getHeight()),
GENESIS_BLOCK_HEIGHT,
GENESIS_TX_ID,
utxoByTxIdMap);

View file

@ -45,10 +45,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Named;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -78,8 +75,8 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
public BsqBlockchainRpcService(@Named(RpcOptionKeys.RPC_USER) String rpcUser,
@Named(RpcOptionKeys.RPC_PASSWORD) String rpcPassword,
@Named(RpcOptionKeys.RPC_PORT) String rpcPort,
@Named(RpcOptionKeys.RPC_BLOCK_PORT) String rpcBlockPort,
@Named(RpcOptionKeys.RPC_WALLET_PORT) String rpcWalletPort) {
@Named(RpcOptionKeys.RPC_BLOCK_NOTIFICATION_PORT) String rpcBlockPort,
@Named(RpcOptionKeys.RPC_WALLET_NOTIFICATION_PORT) String rpcWalletPort) {
this.rpcUser = rpcUser;
this.rpcPassword = rpcPassword;
this.rpcPort = rpcPort;
@ -116,7 +113,7 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
} catch (IOException | BitcoindException | CommunicationException e) {
throw new BsqBlockchainException(e.getMessage(), e);
}
} catch (URISyntaxException | IOException e) {
} catch (Throwable e) {
throw new BsqBlockchainException(e.getMessage(), e);
}
});
@ -134,14 +131,13 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
}
@Override
protected ListenableFuture<Tuple2<Map<String, Map<Integer, BsqUTXO>>, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId) {
protected ListenableFuture<Tuple2<BsqUTXOMap, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId) {
return rpcRequestsExecutor.submit(() -> {
long startTs = System.currentTimeMillis();
Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap = new HashMap<>();
int chainHeadHeight = requestChainHeadHeight();
parseBlockchain(utxoByTxIdMap, chainHeadHeight, genesisBlockHeight, genesisTxId);
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis(chainHeadHeight, genesisBlockHeight, genesisTxId);
log.info("syncFromGenesis took {} ms", System.currentTimeMillis() - startTs);
return new Tuple2<>(utxoByTxIdMap, chainHeadHeight);
return new Tuple2<>(bsqUTXOMap, chainHeadHeight);
});
}
@ -181,19 +177,19 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
}
@Override
BsqTransaction requestTransaction(String txId) throws BsqBlockchainException {
Tx requestTransaction(String txId) throws BsqBlockchainException {
try {
RawTransaction rawTransaction = getRawTransaction(txId);
return new BsqTransaction(txId,
return new Tx(txId,
rawTransaction.getVIn()
.stream()
.filter(rawInput -> rawInput != null && rawInput.getVOut() != null && rawInput.getTxId() != null)
.map(rawInput -> new BsqTxInput(rawInput.getVOut(), rawInput.getTxId(), rawTransaction.getHex()))
.map(rawInput -> new TxInput(rawInput.getVOut(), rawInput.getTxId(), rawTransaction.getHex()))
.collect(Collectors.toList()),
rawTransaction.getVOut()
.stream()
.filter(e -> e != null && e.getN() != null && e.getValue() != null && e.getScriptPubKey() != null)
.map(e -> new BsqTxOutput(e.getN(),
.map(e -> new TxOutput(e.getN(),
Coin.valueOf(e.getValue().movePointRight(8).longValue()),
e.getScriptPubKey().getAddresses(),
e.getScriptPubKey().getHex() != null ? new Script(HEX.decode(e.getScriptPubKey().getHex())) : null))

View file

@ -32,8 +32,17 @@ public class BsqBlockchainRpcServiceMain {
Log.setLevel(Level.WARN);
// regtest uses port 18332, mainnet 8332
BsqBlockchainRpcService blockchainRpcService = new BsqBlockchainRpcService(args[0], args[1], args[2], args[3], args[4]);
final String rpcUser = args[0];
final String rpcPassword = args[1];
final String rpcPort = args[2];
final String rpcBlockPort = args.length > 3 ? args[3] : "";
final String rpcWalletPort = args.length > 4 ? args[4] : "";
BsqBlockchainRpcService blockchainRpcService = new BsqBlockchainRpcService(rpcUser, rpcPassword,
rpcPort, rpcBlockPort, rpcWalletPort);
BsqBlockchainManager bsqBlockchainManager = new BsqBlockchainManager(blockchainRpcService);
bsqBlockchainManager.onAllServicesInitialized();
bsqBlockchainManager.onAllServicesInitialized(errorMessage -> log.error(errorMessage));
while (true) {
}
}
}

View file

@ -23,6 +23,7 @@ import com.google.inject.Inject;
import com.neemre.btcdcli4j.core.BitcoindException;
import com.neemre.btcdcli4j.core.CommunicationException;
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.Tuple2;
@ -39,8 +40,6 @@ import static com.google.common.base.Preconditions.checkArgument;
abstract public class BsqBlockchainService {
private static final Logger log = LoggerFactory.getLogger(BsqBlockchainService.class);
protected Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -57,7 +56,7 @@ abstract public class BsqBlockchainService {
abstract void setup(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler);
abstract ListenableFuture<Tuple2<Map<String, Map<Integer, BsqUTXO>>, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId);
abstract ListenableFuture<Tuple2<BsqUTXOMap, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId);
abstract void syncFromGenesisCompete(String genesisTxId, int genesisBlockHeight, Consumer<Block> onNewBlockHandler);
@ -65,171 +64,186 @@ abstract public class BsqBlockchainService {
abstract Block requestBlock(int i) throws BitcoindException, CommunicationException;
abstract BsqTransaction requestTransaction(String txId) throws BsqBlockchainException;
abstract Tx requestTransaction(String txId) throws BsqBlockchainException;
Map<String, Map<Integer, BsqUTXO>> parseBlockchain(Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap,
int chainHeadHeight,
int genesisBlockHeight,
String genesisTxId)
throws BsqBlockchainException {
@VisibleForTesting
BsqUTXOMap parseAllBlocksFromGenesis(int chainHeadHeight,
int genesisBlockHeight,
String genesisTxId) throws BsqBlockchainException {
try {
//log.info("blockCount=" + chainHeadHeight);
BsqUTXOMap bsqUTXOMap = new BsqUTXOMap();
log.info("blockCount=" + chainHeadHeight);
long startTs = System.currentTimeMillis();
for (int blockHeight = genesisBlockHeight; blockHeight <= chainHeadHeight; blockHeight++) {
Block block = requestBlock(blockHeight);
//log.info("blockHeight=" + blockHeight);
parseBlock(new BsqBlock(block.getTx(), block.getHeight()),
for (int height = genesisBlockHeight; height <= chainHeadHeight; height++) {
Block btcdBlock = requestBlock(height);
log.info("height=" + height);
parseBlock(new BsqBlock(btcdBlock.getTx(), btcdBlock.getHeight()),
genesisBlockHeight,
genesisTxId,
utxoByTxIdMap);
bsqUTXOMap);
}
printUtxoMap(utxoByTxIdMap);
// log.info("Took {} ms", System.currentTimeMillis() - startTs);
} catch (BitcoindException | CommunicationException e) {
throw new BsqBlockchainException(e.getMessage(), e);
printUtxoMap(bsqUTXOMap);
log.info("Took {} ms", System.currentTimeMillis() - startTs);
return bsqUTXOMap;
} catch (Throwable t) {
throw new BsqBlockchainException(t.getMessage(), t);
}
return utxoByTxIdMap;
}
void parseBlock(BsqBlock block,
int genesisBlockHeight,
String genesisTxId,
Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap)
BsqUTXOMap bsqUTXOMap)
throws BsqBlockchainException {
int blockHeight = block.blockHeight;
int blockHeight = block.getHeight();
log.debug("Parse block at height={} ", blockHeight);
// We add all transactions to the block
List<String> txIds = block.txIds;
List<String> txIds = block.getTxIds();
for (String txId : txIds) {
block.addBsqTransaction(requestTransaction(txId));
block.addTx(requestTransaction(txId));
}
// First we check for the genesis tx
Map<String, BsqTransaction> transactionsMap = block.getTransactions();
// All outputs of genesis are valid BSQ UTXOs
Map<String, Tx> txByTxIdMap = block.getTxByTxIdMap();
if (blockHeight == genesisBlockHeight) {
transactionsMap.entrySet().stream()
txByTxIdMap.entrySet().stream()
.filter(entry -> entry.getKey().equals(genesisTxId))
.forEach(entry -> parseGenesisTx(entry.getValue(), blockHeight, utxoByTxIdMap));
.forEach(entry -> parseGenesisTx(entry.getValue(), blockHeight, bsqUTXOMap));
// We need to remove the genesis tx from further parsing (would be treated as intraBlockInputTx)
txByTxIdMap.remove(genesisTxId);
}
resolveConnectedTxs(transactionsMap.values(), utxoByTxIdMap, blockHeight, 0, 100);
// 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);
}
void resolveConnectedTxs(Collection<BsqTransaction> transactions,
Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap,
int blockHeight,
int recursions,
int maxRecursions) {
// The set of txIds of txs which are used for inputs in a tx in that block
Set<String> spendingTxIdSet = getSpendingTxIdSet(transactions);
// We check if the tx has only connections to the UTXO set, if so we add it to the connectedTxs, otherwise it
// is an orphaned tx.
// connectedTxs: Those who have inputs in the UTXO set
// orphanTxs: Those who have inputs from other txs in the same block
Set<BsqTransaction> connectedTxs = new HashSet<>();
Set<BsqTransaction> orphanTxs = new HashSet<>();
outerLoop:
for (BsqTransaction transaction : transactions) {
boolean isConnected = false;
for (BsqTxInput input : transaction.inputs) {
String spendingTxId = input.spendingTxId;
if (spendingTxIdSet.contains(spendingTxId)) {
// We have an input in one of the blocks transactions, so we cannot process that tx now.
// We break out here if at least 1 input points to a tx in the same block
orphanTxs.add(transaction);
continue outerLoop;
} else if (utxoByTxIdMap.containsKey(spendingTxId)) {
// If we find the tx in the utxo set we set the isConnected flag.
Map<Integer, BsqUTXO> utxoByIndexMap = utxoByTxIdMap.get(spendingTxId);
if (utxoByIndexMap != null && utxoByIndexMap.containsKey(input.spendingOuptuIndex)) {
// Our input has a connection to an tx from the utxo set
isConnected = true;
}
}
}
if (isConnected)
connectedTxs.add(transaction);
}
// Now we check if our connected txs are valid BSQ transactions
for (BsqTransaction transaction : connectedTxs) {
verifyTransaction(transaction, blockHeight, utxoByTxIdMap);
}
//log.info("orphanTxs " + orphanTxs);
if (!orphanTxs.isEmpty() && recursions < maxRecursions)
resolveConnectedTxs(orphanTxs, utxoByTxIdMap, blockHeight, ++recursions, maxRecursions);
}
private Set<String> getSpendingTxIdSet(Collection<BsqTransaction> transactions) {
Set<String> txIdSet = transactions.stream().map(tx -> tx.txId).collect(Collectors.toSet());
Set<String> spendingTxIdSet = new HashSet<>();
transactions.stream()
.forEach(transaction -> transaction.inputs.stream()
.forEach(input -> {
String spendingTxId = input.spendingTxId;
if (txIdSet.contains(spendingTxId))
spendingTxIdSet.add(spendingTxId);
}));
return spendingTxIdSet;
}
private void verifyTransaction(BsqTransaction bsqTransaction,
// Recursive method
void updateBsqUtxoMapFromBlock(Collection<Tx> transactions,
BsqUTXOMap bsqUTXOMap,
int blockHeight,
Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap
) {
String txId = bsqTransaction.txId;
List<BsqTxOutput> outputs = bsqTransaction.outputs;
int recursionCounter,
int maxRecursions) {
Coin availableValue = Coin.ZERO;
for (BsqTxInput input : bsqTransaction.inputs) {
String spendingTxId = input.spendingTxId;
if (utxoByTxIdMap.containsKey(spendingTxId)) {
Map<Integer, BsqUTXO> utxoByIndexMap = utxoByTxIdMap.get(spendingTxId);
Integer index = input.spendingOuptuIndex;
if (utxoByIndexMap.containsKey(index)) {
BsqUTXO utxo = utxoByIndexMap.get(index);
if (recursionCounter > 10)
log.warn("Unusual high recursive calls at resolveConnectedTxs. recursionCounter=" + recursionCounter);
utxoByIndexMap.remove(index);
availableValue = availableValue.add(utxo.getValue());
if (utxoByIndexMap.isEmpty()) {
// If no more entries by index we can remove the whole entry by txId
utxoByTxIdMap.remove(spendingTxId);
}
// The set of txIds of txs which are used for inputs of another tx in same block
Set<String> intraBlockSpendingTxIdSet = getIntraBlockSpendingTxIdSet(transactions);
Set<Tx> nonIntraBlockInputTxs = new HashSet<>();
Set<Tx> intraBlockInputTxs = new HashSet<>();
// First we find the txs which have no intra-block inputs
outerLoop:
for (Tx tx : transactions) {
for (TxInput input : tx.getInputs()) {
if (intraBlockSpendingTxIdSet.contains(input.getSpendingTxId())) {
// We have an input from one of the intra-block-transactions, so we cannot process that tx now.
// We add the tx for later parsing to the intraBlockInputTxs and move to the next
// outerLoop iteration .
intraBlockInputTxs.add(tx);
continue outerLoop;
}
}
// If we have not found any tx input pointing to anther tx in the same block we add it to our
// nonIntraBlockInputTxs for first pass of BSQ validation
nonIntraBlockInputTxs.add(tx);
}
log.debug("intraBlockInputTxs " + intraBlockInputTxs.size());
log.debug("nonIntraBlockInputTxs " + nonIntraBlockInputTxs.size());
// we check if we have any valid BSQ utxo from that tx set
boolean utxoChanged = false;
if (!nonIntraBlockInputTxs.isEmpty()) {
for (Tx tx : nonIntraBlockInputTxs) {
utxoChanged = utxoChanged || updateBsqUtxoMapFromTx(tx, blockHeight, bsqUTXOMap);
}
}
// we check if we have any valid BSQ utxo from that tx set
if (!intraBlockInputTxs.isEmpty()) {
if (utxoChanged) {
if (recursionCounter < maxRecursions) {
updateBsqUtxoMapFromBlock(intraBlockInputTxs, bsqUTXOMap, blockHeight, ++recursionCounter, maxRecursions);
} else {
final String msg = "We exceeded our max. recursions for resolveConnectedTxs.\n" +
"intraBlockInputTxs=" + intraBlockInputTxs.toString() + "\n" +
"nonIntraBlockInputTxs=" + nonIntraBlockInputTxs;
log.warn(msg);
if (DevEnv.DEV_MODE)
throw new RuntimeException(msg);
}
} else {
final String msg = "If we have intraBlockInputTxs we must have had got the utxoChanged, otherwise we cannot " +
"satisfy the open intraBlockInputTxs.\n" +
"intraBlockInputTxs=" + intraBlockInputTxs.toString() + "\n" +
"nonIntraBlockInputTxs=" + nonIntraBlockInputTxs;
log.warn(msg);
if (DevEnv.DEV_MODE)
throw new RuntimeException(msg);
}
}
}
private Set<String> getIntraBlockSpendingTxIdSet(Collection<Tx> transactions) {
Set<String> txIdSet = transactions.stream().map(Tx::getId).collect(Collectors.toSet());
Set<String> intraBlockSpendingTxIdSet = new HashSet<>();
transactions.stream()
.forEach(tx -> tx.getInputs().stream()
.filter(input -> txIdSet.contains(input.getSpendingTxId()))
.forEach(input -> intraBlockSpendingTxIdSet.add(input.getSpendingTxId())));
return intraBlockSpendingTxIdSet;
}
private boolean updateBsqUtxoMapFromTx(Tx tx,
int blockHeight,
BsqUTXOMap bsqUTXOMap) {
String txId = tx.getId();
List<TxOutput> outputs = tx.getOutputs();
boolean utxoChanged = false;
Coin availableValue = Coin.ZERO;
for (TxInput input : tx.getInputs()) {
String spendingTxId = input.getSpendingTxId();
final int spendingTxOutputIndex = input.getSpendingTxOutputIndex();
if (bsqUTXOMap.containsTuple(spendingTxId, spendingTxOutputIndex)) {
BsqUTXO bsqUTXO = bsqUTXOMap.getByTuple(spendingTxId, spendingTxOutputIndex);
availableValue = availableValue.add(bsqUTXO.getValue());
bsqUTXOMap.removeByTuple(spendingTxId, spendingTxOutputIndex);
utxoChanged = true;
if (bsqUTXOMap.isEmpty())
break;
}
}
// If we have an input spending tokens we iterate the outputs
if (availableValue.isPositive()) {
Map<Integer, BsqUTXO> utxoByIndexMap = utxoByTxIdMap.containsKey(txId) ?
utxoByTxIdMap.get(txId) :
new HashMap<>();
// We sort by index, inputs are tokens as long there is enough input value
for (int i = 0; i < outputs.size(); i++) {
BsqTxOutput squOutput = outputs.get(i);
List<String> addresses = squOutput.addresses;
// Only at raw MS outputs addresses have more then 1 entry
// We do not support raw MS for BSQ
if (addresses.size() == 1) {
String address = addresses.get(0);
availableValue = availableValue.subtract(squOutput.value);
if (!availableValue.isNegative()) {
// We are spending available tokens
BsqUTXO utxo = new BsqUTXO(txId,
squOutput.index,
squOutput.value,
blockHeight,
false,
squOutput.script,
address);
utxoByIndexMap.put(i, utxo);
} else {
log.warn("We tried to spend more BSQ as we have in our inputs");
break;
}
// We use order of output index. An output is a BSQ utxo as long there is enough input value
for (int outputIndex = 0; outputIndex < outputs.size(); outputIndex++) {
TxOutput txOutput = outputs.get(outputIndex);
availableValue = availableValue.subtract(txOutput.getValue());
if (!availableValue.isNegative()) {
// We are spending available tokens
BsqUTXO bsqUTXO = new BsqUTXO(txId,
blockHeight,
false,
txOutput);
bsqUTXOMap.putByTuple(txId, outputIndex, bsqUTXO);
} else {
log.warn("addresses.size() is not 1.");
log.warn("We tried to spend more BSQ as we have in our inputs");
// TODO report burnt BSQ
// TODO: check if we should be more tolerant and use
// availableValue = availableValue.subtract(txOutput.getValue());
// only temp and allow follow up outputs to use the left input.
break;
}
}
@ -237,57 +251,40 @@ abstract public class BsqBlockchainService {
if (availableValue.isPositive()) {
log.warn("BSQ have been left which was not spent. Burned BSQ amount={}, tx={}",
availableValue.value,
bsqTransaction.toString());
}
if (!utxoByIndexMap.isEmpty() && !utxoByTxIdMap.containsKey(txId)) {
boolean wasEmpty = utxoByTxIdMap.put(txId, utxoByIndexMap) == null;
checkArgument(wasEmpty, "We must not have that tx in the map. txId=" + txId);
tx.toString());
}
}
return utxoChanged;
}
@VisibleForTesting
void parseGenesisTx(BsqTransaction bsqTransaction, int blockHeight, Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap) {
String txId = bsqTransaction.txId;
List<BsqTxOutput> outputs = bsqTransaction.outputs;
void parseGenesisTx(Tx tx, int blockHeight, BsqUTXOMap bsqUTXOMap) {
String txId = tx.getId();
List<TxOutput> outputs = tx.getOutputs();
//TODO use BsqTXO not BsqUTXO as we dont know if they are unspent
// Genesis tx uses all outputs as BSQ outputs
Map<Integer, BsqUTXO> utxoByIndexMap = new HashMap<>();
for (int i = 0; i < outputs.size(); i++) {
BsqTxOutput output = outputs.get(i);
List<String> addresses = output.addresses;
// Only at raw MS outputs addresses have more then 1 entry
// We do not support raw MS for BSQ
if (addresses.size() == 1) {
String address = addresses.get(0);
//TODO set coinbase to true after testing
BsqUTXO utxo = new BsqUTXO(txId,
output.index,
output.value,
blockHeight,
false,
output.script,
address);
utxoByIndexMap.put(i, utxo);
}
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);
}
checkArgument(!utxoByIndexMap.isEmpty(), "Genesis tx must have squ utxo");
boolean wasEmpty = utxoByTxIdMap.put(txId, utxoByIndexMap) == null;
checkArgument(wasEmpty, "We must not have that tx in the map. txId=" + txId);
checkArgument(!bsqUTXOMap.isEmpty(), "Genesis tx need to have BSQ utxo when parsing genesis block");
}
void printUtxoMap(Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap) {
/*
StringBuilder sb = new StringBuilder("utxoByTxIdMap:\n");
utxoByTxIdMap.entrySet().stream().forEach(e -> {
sb.append("TxId: ").append(e.getKey()).append("\n");
e.getValue().entrySet().stream().forEach(a -> {
sb.append(" [").append(a.getKey()).append("] {")
.append(a.getValue().toString()).append("}\n");
});
});
log.info(sb.toString());
*/
void printUtxoMap(BsqUTXOMap bsqUTXOMap) {
if (log.isInfoEnabled() || log.isDebugEnabled() || log.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("bsqUTXOMap:\n");
bsqUTXOMap.entrySet().stream().forEach(e -> {
sb.append("TxId:Index = ").append(e.getKey()).append("\n");
sb.append(" UTXO = ").append(e.getValue().toString()).append("\n");
}
);
log.error(sb.toString());
}
}
}

View file

@ -1,67 +0,0 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.blockchain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class BsqTransaction {
private static final Logger log = LoggerFactory.getLogger(BsqTransaction.class);
public final String txId;
public final List<BsqTxInput> inputs;
public final List<BsqTxOutput> outputs;
public BsqTransaction(String txId, List<BsqTxInput> inputs, List<BsqTxOutput> outputs) {
this.txId = txId;
this.inputs = inputs;
this.outputs = outputs;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BsqTransaction that = (BsqTransaction) o;
if (txId != null ? !txId.equals(that.txId) : that.txId != null) return false;
if (inputs != null ? !inputs.equals(that.inputs) : that.inputs != null) return false;
return !(outputs != null ? !outputs.equals(that.outputs) : that.outputs != null);
}
@Override
public int hashCode() {
int result = txId != null ? txId.hashCode() : 0;
result = 31 * result + (inputs != null ? inputs.hashCode() : 0);
result = 31 * result + (outputs != null ? outputs.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "BsqTransaction{" +
"txId='" + txId + '\'' +
", inputs=" + inputs +
", outputs=" + outputs +
'}';
}
}

View file

@ -1,65 +0,0 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.blockchain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BsqTxInput {
private static final Logger log = LoggerFactory.getLogger(BsqTxInput.class);
public final int spendingOuptuIndex;
public final String spendingTxId;
public final String txId;
public BsqTxInput(int spendingOuptuIndex, String spendingTxId, String txId) {
this.spendingOuptuIndex = spendingOuptuIndex;
this.spendingTxId = spendingTxId;
this.txId = txId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BsqTxInput that = (BsqTxInput) o;
if (spendingOuptuIndex != that.spendingOuptuIndex) return false;
if (spendingTxId != null ? !spendingTxId.equals(that.spendingTxId) : that.spendingTxId != null) return false;
return !(txId != null ? !txId.equals(that.txId) : that.txId != null);
}
@Override
public int hashCode() {
int result = spendingOuptuIndex;
result = 31 * result + (spendingTxId != null ? spendingTxId.hashCode() : 0);
result = 31 * result + (txId != null ? txId.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "BsqTxInput{" +
"spendingOuptuIndex=" + spendingOuptuIndex +
", spendingTxId='" + spendingTxId + '\'' +
", txId='" + txId + '\'' +
'}';
}
}

View file

@ -1,74 +0,0 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.blockchain;
import org.bitcoinj.core.Coin;
import org.bitcoinj.script.Script;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class BsqTxOutput {
private static final Logger log = LoggerFactory.getLogger(BsqTxOutput.class);
public final int index;
public final Coin value;
public final List<String> addresses;
public final Script script;
public BsqTxOutput(int index, Coin value, List<String> addresses, Script script) {
this.index = index;
this.value = value;
this.addresses = addresses;
this.script = script;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BsqTxOutput that = (BsqTxOutput) o;
if (index != that.index) return false;
if (value != null ? !value.equals(that.value) : that.value != null) return false;
if (addresses != null ? !addresses.equals(that.addresses) : that.addresses != null) return false;
return !(script != null ? !script.equals(that.script) : that.script != null);
}
@Override
public int hashCode() {
int result = index;
result = 31 * result + (value != null ? value.hashCode() : 0);
result = 31 * result + (addresses != null ? addresses.hashCode() : 0);
result = 31 * result + (script != null ? script.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "BsqTxOutput{" +
"index=" + index +
", value=" + value +
", addresses=" + addresses +
", script=" + script +
'}';
}
}

View file

@ -17,26 +17,48 @@
package io.bisq.core.dao.blockchain;
import lombok.Value;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.UTXO;
import org.bitcoinj.core.Utils;
import org.bitcoinj.script.Script;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
// Estimation for UTXO set: 1 UTXO object has 78 byte
// 1000 UTXOs - 10 000 UTXOs: 78kb -780kb
public class BsqUTXO extends UTXO {
private static final Logger log = LoggerFactory.getLogger(BsqUTXO.class);
@Value
public class BsqUTXO {
private final String txId;
private final long index;
private final Coin value;
private final int height;
private final boolean isBsqCoinBase;
private final Script script;
private final String utxoId;
public BsqUTXO(String txId, long index, Coin value, int height, boolean coinBase, Script script, String address) {
super(Sha256Hash.wrap(Utils.HEX.decode(txId)), index, value, height, coinBase, script, address);
// 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, Coin value, int height, boolean isBsqCoinBase, Script script, List<String> addresses) {
this.txId = txId;
this.index = index;
this.value = value;
this.height = height;
this.isBsqCoinBase = isBsqCoinBase;
this.script = script;
this.addresses = addresses;
utxoId = txId + ":" + index;
}
@Override
public String toString() {
return String.format("value:%d, spending tx:%s at index:%d)", getValue().value, getHash(), getIndex());
public BsqUTXO(String txId, int height, boolean isBsqCoinBase, TxOutput output) {
this(txId,
output.getIndex(),
output.getValue(),
height,
isBsqCoinBase,
output.getScript(),
output.getAddresses());
}
}

View file

@ -0,0 +1,6 @@
package io.bisq.core.dao.blockchain;
public interface BsqUTXOListener {
void onBsqUTXOChanged(BsqUTXOMap utxoByTxIdMap);
}

View file

@ -0,0 +1,51 @@
/*
* 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;
import java.util.HashMap;
public class BsqUTXOMap extends HashMap<BsqUTXOMap.TxIdIndexTuple, BsqUTXO> {
public boolean containsTuple(String txId, int index) {
return super.containsKey(new TxIdIndexTuple(txId, index));
}
public Object putByTuple(String txId, int index, BsqUTXO bsqUTXO) {
return super.put(new TxIdIndexTuple(txId, index), bsqUTXO);
}
public BsqUTXO getByTuple(String txId, int index) {
return super.get(new TxIdIndexTuple(txId, index));
}
public BsqUTXO removeByTuple(String txId, int index) {
return super.remove(new TxIdIndexTuple(txId, index));
}
@Value
static class TxIdIndexTuple {
private final String txId;
private final int index;
@Override
public String toString() {
return txId + ":" + index;
}
}
}

View file

@ -15,26 +15,15 @@
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.tokens;
package io.bisq.core.dao.blockchain;
import java.util.HashMap;
import java.util.Map;
import lombok.Value;
class MockTxService extends TxService {
private Map<String, Tx> txMap = new HashMap<>();
import java.util.List;
public MockTxService() {
}
public Tx getTx(String txId) {
return txMap.get(txId);
}
public void addTx(Tx tx) {
txMap.put(tx.id, tx);
}
public void cleanup() {
txMap = new HashMap<>();
}
@Value
public class Tx {
private final String id;
private final List<TxInput> inputs;
private final List<TxOutput> outputs;
}

View file

@ -15,10 +15,13 @@
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.tokens;
package io.bisq.core.dao.blockchain;
public abstract class TxService {
abstract public Tx getTx(String txId);
import lombok.Value;
abstract public void addTx(Tx tx);
@Value
public class TxInput {
private final int spendingTxOutputIndex;
private final String spendingTxId;
private final String txId;
}

View file

@ -0,0 +1,32 @@
/*
* 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;
import lombok.Value;
import org.bitcoinj.core.Coin;
import org.bitcoinj.script.Script;
import java.util.List;
@Value
public class TxOutput {
private final int index;
private final Coin value;
private final List<String> addresses;
private final Script script;
}

View file

@ -1,8 +0,0 @@
package io.bisq.core.dao.blockchain;
import java.util.Map;
public interface UtxoListener {
void onUtxoChanged(Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap);
}

View file

@ -1,101 +0,0 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.tokens;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class TransactionParser {
private static final Logger log = LoggerFactory.getLogger(TransactionParser.class);
private final String genesisTxId;
private final TxService txService;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TransactionParser(String genesisTxId, TxService txService) {
this.genesisTxId = genesisTxId;
this.txService = txService;
}
public Tx getTx(String txId) {
return txService.getTx(txId);
}
public void applyIsTokenForAllOutputs(Tx parentTx) {
if (parentTx.id.equals(genesisTxId)) {
// direct output from genesisTx
parentTx.outputs.stream().forEach(e -> e.isToken = true);
} else {
// we are not a direct output so we check if our inputs are valid and sufficiently funded with tokens
int accumulatedTokenInputValue = 0;
for (TxInput input : parentTx.inputs) {
if (isValidInput(input)) {
accumulatedTokenInputValue += input.value;
}
}
log.debug("accumulatedTokenInputValue " + accumulatedTokenInputValue);
List<TxOutput> outputs = parentTx.outputs;
for (int i = 0; i < outputs.size(); i++) {
TxOutput out = outputs.get(i);
log.debug("index {}, out.value {}, available input value {}", i, out.value, accumulatedTokenInputValue);
accumulatedTokenInputValue -= out.value;
// If we had enough token funds for our output we are a valid token output
if (accumulatedTokenInputValue >= 0)
out.isToken = true;
else
log.error("");
}
}
}
public Set<TxOutput> getAllUTXOs(Tx tx) {
Set<TxOutput> allUTXOs = new HashSet<>();
tx.outputs.stream()
.filter(e -> e.isToken)
.forEach(output -> {
if (!output.isSpent) {
allUTXOs.add(output);
} else {
allUTXOs.addAll(getAllUTXOs(output.inputOfSpendingTx.tx));
}
});
return allUTXOs;
}
public boolean isValidOutput(TxOutput output) {
return !output.isSpent && output.isToken;
}
public boolean isValidInput(TxInput input) {
return input.isToken || input.tx.id.equals(genesisTxId) || (input.output != null && input.output.isToken);
}
}

View file

@ -1,86 +0,0 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.tokens;
import java.util.ArrayList;
import java.util.List;
public class Tx {
public Tx(String id) {
this.id = id;
}
public Tx(String id, List<TxInput> inputs, List<TxOutput> outputs) {
this.id = id;
this.inputs = inputs;
this.outputs = outputs;
}
public final String id;
public List<TxInput> inputs = new ArrayList<>();
public List<TxOutput> outputs = new ArrayList<>();
public void addOutput(TxOutput output) {
output.tx = this;
output.index = outputs.size();
outputs.add(output);
}
public void addInput(TxInput input) {
input.tx = this;
input.index = inputs.size();
// TODO our mocks have null values, might be not null in production
if (input.output != null) {
input.output.isSpent = true;
input.output.inputOfSpendingTx = input;
input.value = input.output.value;
}
inputs.add(input);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tx tx = (Tx) o;
if (id != null ? !id.equals(tx.id) : tx.id != null) return false;
if (outputs != null ? !outputs.equals(tx.outputs) : tx.outputs != null) return false;
return !(inputs != null ? !inputs.equals(tx.inputs) : tx.inputs != null);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (outputs != null ? outputs.hashCode() : 0);
result = 31 * result + (inputs != null ? inputs.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Tx{" +
"id='" + id + '\'' +
", inputs=" + inputs +
", outputs=" + outputs +
'}';
}
}

View file

@ -1,47 +0,0 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.tokens;
import java.util.UUID;
public class TxInput {
public final String id;
public final TxOutput output;
public Tx tx;
public long value;
public int index;
public boolean isToken;
public TxInput(Tx parentTx, TxOutput output) {
this.tx = parentTx;
this.output = output;
id = UUID.randomUUID().toString();
}
@Override
public String toString() {
return "TxInput{" +
"output.id=" + (output != null ? output.id : "null") +
", tx.id=" + (tx != null ? tx.id : "null") +
", value=" + value +
", index=" + index +
", isToken=" + isToken +
'}';
}
}

View file

@ -1,50 +0,0 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.tokens;
import java.util.UUID;
public class TxOutput {
public final String id;
public Tx tx;
public TxInput inputOfSpendingTx;
public boolean isSpent;
public final String address;
public final long value;
public int index;
public boolean isToken;
public TxOutput(String address, long value) {
this.address = address;
this.value = value;
id = UUID.randomUUID().toString();
}
@Override
public String toString() {
return "TxOutput{" +
"tx.id=" + (tx != null ? tx.id : "null") +
", inputOfSpendingTx.id=" + (inputOfSpendingTx != null ? inputOfSpendingTx.id : "null") +
", isSpent=" + isSpent +
", address='" + address + '\'' +
", value=" + value +
", index=" + index +
", isToken=" + isToken +
'}';
}
}

View file

@ -20,12 +20,11 @@ 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 org.bitcoinj.core.Utils;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.Coin;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.*;
@ -37,14 +36,36 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
*/
@Slf4j
public class BsqBlockchainServiceTest {
private static final Logger log = LoggerFactory.getLogger(BsqBlockchainServiceTest.class);
private MockBsqBlockchainService squBlockchainService;
public static final int BLOCK_0 = 0;
public static final int BLOCK_1 = 1;
public static final int BLOCK_2 = 2;
public static final String FUND_GEN_FUND_TX_ID = "FUND_GEN_FUND_TX_ID";
public static final String FUND_GEN_TX_ID = "FUND_GEN_TX_ID";
public static final String GEN_TX_ID = "GEN_TX_ID";
public static final String TX1_ID = "TX1_ID";
public static final String TX2_ID = "TX2_ID";
public static final String ADDRESS_GEN_FUND_TX = "ADDRESS_GEN_FUND_TX";
public static final String ADDRESS_GEN_1 = "ADDRESS_GEN_1";
public static final String ADDRESS_GEN_2 = "ADDRESS_GEN_2";
public static final String ADDRESS_TX_1 = "ADDRESS_TX_1";
public static final String ADDRESS_TX_2 = "ADDRESS_TX_2";
public static final long ADDRESS_GEN_1_VALUE = Coin.parseCoin("0.00005000").value;
public static final long ADDRESS_GEN_2_VALUE = Coin.parseCoin("0.00001000").value;
public static final long ADDRESS_TX_1_VALUE = Coin.parseCoin("0.00001000").value;
public static final long ADDRESS_TX_2_VALUE = Coin.parseCoin("0.00001000").value;
private MockBsqBlockchainService service;
@Before
public void setup() {
squBlockchainService = new MockBsqBlockchainService();
service = new MockBsqBlockchainService();
}
@After
@ -52,186 +73,241 @@ public class BsqBlockchainServiceTest {
}
@Test
public void testGenesisBlock() throws BsqBlockchainException, BitcoindException, CommunicationException {
int genesisBlockHeight = 0;
String genesisTxId = "000000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
buildGenesisBlock(genesisBlockHeight, genesisTxId);
Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap = squBlockchainService.parseBlockchain(new HashMap<>(),
squBlockchainService.requestChainHeadHeight(),
genesisBlockHeight,
genesisTxId);
BsqUTXO bsqUTXO1 = utxoByTxIdMap.get(genesisTxId).get(0);
BsqUTXO bsqUTXO2 = utxoByTxIdMap.get(genesisTxId).get(1);
assertEquals(1, utxoByTxIdMap.size());
assertEquals("addressGen1", bsqUTXO1.getAddress());
assertEquals("addressGen2", bsqUTXO2.getAddress());
public void testGenTx() throws BsqBlockchainException, BitcoindException, CommunicationException {
// GENESIS_TX (block 0):
// Input 0: output from GEN_FUNDING_TX_ID
// Output 0: ADDRESS_GEN_1 ADDRESS_GEN_1_VALUE
// Output 1: ADDRESS_GEN_2 ADDRESS_GEN_2_VALUE
// UTXO:
// GENESIS_TX_ID:0
// GENESIS_TX_ID:1
buildGenesisBlock();
service.buildBlocks(BLOCK_0, BLOCK_0);
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(GEN_TX_ID, 1);
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(GEN_TX_ID, 0));
assertEquals(bsqUTXO2.getUtxoId(), getUTXOId(GEN_TX_ID, 1));
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue().value);
assertEquals(ADDRESS_GEN_2_VALUE, bsqUTXO2.getValue().value);
assertEquals(2, bsqUTXOMap.size());
}
@Test
public void testGenToTx1() throws BsqBlockchainException, BitcoindException, CommunicationException {
// GENESIS_TX (block 0):
// Input 0: Output 0 from GEN_FUNDING_TX_ID
// Output 0: ADDRESS_GEN_1 ADDRESS_GEN_1_VALUE
// Output 1: ADDRESS_GEN_2 ADDRESS_GEN_2_VALUE
// TX1 (block 1):
// Input 0: Output 1 from GENESIS_TX
// Output 0: ADDRESS_TX_1 ADDRESS_TX_1_VALUE (=ADDRESS_GEN_2_VALUE)
// UTXO:
// GENESIS_TX_ID:0
// TX1_ID:0
buildGenesisBlock();
buildTx(GEN_TX_ID,
1,
TX1_ID,
BLOCK_1,
0,
ADDRESS_TX_1_VALUE,
ADDRESS_TX_1);
service.buildBlocks(BLOCK_0, BLOCK_1);
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(TX1_ID, 0);
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(GEN_TX_ID, 0));
assertEquals(bsqUTXO2.getUtxoId(), getUTXOId(TX1_ID, 0));
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue().value);
assertEquals(ADDRESS_TX_1_VALUE, bsqUTXO2.getValue().value);
assertEquals(2, bsqUTXOMap.size());
}
@Test
public void testGenToTx1Block1() throws BsqBlockchainException, BitcoindException, CommunicationException {
int genesisBlockHeight = 0;
String genesisTxId = "000000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
buildGenesisBlock(genesisBlockHeight, genesisTxId);
public void testGenToTx1ToTx2InBlock1() throws BsqBlockchainException, BitcoindException, CommunicationException {
// GENESIS_TX (block 0):
// Input 0: Output 0 from GEN_FUNDING_TX_ID
// Output 0: ADDRESS_GEN_1 ADDRESS_GEN_1_VALUE
// Output 1: ADDRESS_GEN_2 ADDRESS_GEN_2_VALUE
// We spend from output 1 of gen tx
String txId = "100000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
buildSpendingTx(genesisBlockHeight,
genesisTxId,
txId,
// TX1 (block 1):
// Input 0: Output 1 from GENESIS_TX
// Output 0: ADDRESS_TX_1 ADDRESS_TX_1_VALUE (=ADDRESS_GEN_2_VALUE)
// TX2 (block 1):
// Input 0: Output 0 from TX1
// Output 0: ADDRESS_TX_2 ADDRESS_TX_2_VALUE (=ADDRESS_TX_1_VALUE)
// UTXO:
// GENESIS_TX_ID:0
// TX2_ID:0
buildGenesisBlock();
// Tx1 uses as input the output 1 of genTx
buildTx(GEN_TX_ID,
1,
TX1_ID,
BLOCK_1,
0,
0.00001000,
"addressTx1");
ADDRESS_TX_1_VALUE,
ADDRESS_TX_1);
Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap = squBlockchainService.parseBlockchain(new HashMap<>(),
squBlockchainService.requestChainHeadHeight(),
genesisBlockHeight,
genesisTxId);
// Tx2 uses as input the output 0 of Tx1
buildTx(TX1_ID,
0,
TX2_ID,
BLOCK_1,
0,
ADDRESS_TX_2_VALUE,
ADDRESS_TX_2);
BsqUTXO bsqUTXO1 = utxoByTxIdMap.get(genesisTxId).get(0);
BsqUTXO bsqUTXO2 = utxoByTxIdMap.get(txId).get(0);
assertEquals(2, utxoByTxIdMap.size());
assertEquals("addressGen1", bsqUTXO1.getAddress());
assertEquals("addressTx1", bsqUTXO2.getAddress());
service.buildBlocks(BLOCK_0, BLOCK_1);
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(TX2_ID, 0);
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(GEN_TX_ID, 0));
assertEquals(bsqUTXO2.getUtxoId(), getUTXOId(TX2_ID, 0));
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue().value);
assertEquals(ADDRESS_TX_2_VALUE, bsqUTXO2.getValue().value);
assertEquals(2, bsqUTXOMap.size());
}
@Test
public void testGenToTx1toTx2Block1() throws BsqBlockchainException, BitcoindException, CommunicationException {
int genesisBlockHeight = 0;
String genesisTxId = "000000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
buildGenesisBlock(genesisBlockHeight, genesisTxId);
public void testGenToTx1ToTx2InBlock2() throws BsqBlockchainException, BitcoindException, CommunicationException {
// GENESIS_TX (block 0):
// Input 0: Output 0 from GEN_FUNDING_TX_ID
// Output 0: ADDRESS_GEN_1 ADDRESS_GEN_1_VALUE
// Output 1: ADDRESS_GEN_2 ADDRESS_GEN_2_VALUE
// We spend from output 1 of gen tx
String tx1Id = "100000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
buildSpendingTx(genesisBlockHeight,
genesisTxId,
tx1Id,
// TX1 (block 1):
// Input 0: Output 1 from GENESIS_TX
// Output 0: ADDRESS_TX_1 ADDRESS_TX_1_VALUE (=ADDRESS_GEN_2_VALUE)
// TX2 (block 2):
// Input 0: Output 0 from TX1
// Output 0: ADDRESS_TX_2 ADDRESS_TX_2_VALUE (=ADDRESS_TX_1_VALUE)
// UTXO:
// GENESIS_TX_ID:0
// TX2_ID:0
buildGenesisBlock();
// Tx1 uses as input the output 1 of genTx
buildTx(GEN_TX_ID,
1,
TX1_ID,
BLOCK_1,
0,
0.00001000,
"addressTx1");
ADDRESS_TX_1_VALUE,
ADDRESS_TX_1);
// We spend from output 0 of tx1 (same block)
String tx2Id = "200000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
buildSpendingTx(1,
tx1Id,
tx2Id,
// Tx2 uses as input the output 0 of Tx1
buildTx(TX1_ID,
0,
TX2_ID,
BLOCK_2,
0,
0.00001000,
"addressTx2");
ADDRESS_TX_2_VALUE,
ADDRESS_TX_2);
Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap = squBlockchainService.parseBlockchain(new HashMap<>(),
squBlockchainService.requestChainHeadHeight(),
genesisBlockHeight,
genesisTxId);
service.buildBlocks(BLOCK_0, BLOCK_2);
BsqUTXO bsqUTXO1 = utxoByTxIdMap.get(genesisTxId).get(0);
BsqUTXO bsqUTXO2 = utxoByTxIdMap.get(tx2Id).get(0);
assertEquals(2, utxoByTxIdMap.size());
assertEquals("addressGen1", bsqUTXO1.getAddress());
assertEquals("addressTx2", bsqUTXO2.getAddress());
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(GEN_TX_ID, 0);
BsqUTXO bsqUTXO2 = bsqUTXOMap.getByTuple(TX2_ID, 0);
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(GEN_TX_ID, 0));
assertEquals(bsqUTXO2.getUtxoId(), getUTXOId(TX2_ID, 0));
assertEquals(ADDRESS_GEN_1_VALUE, bsqUTXO1.getValue().value);
assertEquals(ADDRESS_TX_2_VALUE, bsqUTXO2.getValue().value);
assertEquals(2, bsqUTXOMap.size());
}
@Test
public void testGenToTx1toTx2AndGenToTx2Block1() throws BsqBlockchainException, BitcoindException, CommunicationException {
int genesisBlockHeight = 0;
String genesisTxId = "000000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
buildGenesisBlock(genesisBlockHeight, genesisTxId);
public void testGenToTx1ToTx2AndGenToTx2InBlock1() throws BsqBlockchainException, BitcoindException, CommunicationException {
// GENESIS_TX (block 0):
// Input 0: Output 0 from GEN_FUNDING_TX_ID
// Output 0: ADDRESS_GEN_1 ADDRESS_GEN_1_VALUE
// Output 1: ADDRESS_GEN_2 ADDRESS_GEN_2_VALUE
// We spend from output 1 of gen tx
String tx1Id = "100000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
buildSpendingTx(genesisBlockHeight,
genesisTxId,
tx1Id,
// TX1 (block 1):
// Input 0: Output 1 from GENESIS_TX
// Output 0: ADDRESS_TX_1 ADDRESS_TX_1_VALUE (=ADDRESS_GEN_2_VALUE)
// TX2 (block 1):
// Input 0: Output 0 from TX1
// Input 1: Output 0 from GENESIS_TX
// Output 0: ADDRESS_TX_2 ADDRESS_TX_1_VALUE + ADDRESS_GEN_1_VALUE
// UTXO:
// TX2_ID:0
buildGenesisBlock();
// Tx1 uses as input the output 1 of genTx
buildTx(GEN_TX_ID,
1,
TX1_ID,
BLOCK_1,
0,
0.00001000,
"addressTx1");
// We spend from output 0 of tx1 (same block)
String tx2Id = "200000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
RawTransaction tx2 = buildSpendingTx(1,
tx1Id,
tx2Id,
0,
0,
0.00001000,
"addressTx3a");
// We spend from output 0 of gen tx to tx2
List<RawInput> rawInputs = tx2.getVIn();
rawInputs.add(getRawInput(0, genesisTxId));
tx2.setVIn(rawInputs);
List<RawOutput> rawOutputs = tx2.getVOut();
rawOutputs.add(getRawOutput(0, 0.00005000, "addressTx3b"));
tx2.setVOut(rawOutputs);
Map<String, Map<Integer, BsqUTXO>> utxoByTxIdMap = squBlockchainService.parseBlockchain(new HashMap<>(),
squBlockchainService.requestChainHeadHeight(),
genesisBlockHeight,
genesisTxId);
BsqUTXO bsqUTXO1 = utxoByTxIdMap.get(tx2Id).get(0);
BsqUTXO bsqUTXO2 = utxoByTxIdMap.get(tx2Id).get(1);
assertEquals(1, utxoByTxIdMap.size());
assertEquals("addressTx3a", bsqUTXO1.getAddress());
assertEquals("addressTx3b", bsqUTXO2.getAddress());
}
private RawTransaction buildSpendingTx(int inputTxBlockHeight,
String inputTxId,
String txId,
int inputIndex,
int outputIndex,
double outputValue,
String outputAddress) {
RawTransaction rawTransaction = getRawTransaction(inputTxBlockHeight + 1, txId);
ADDRESS_TX_1_VALUE,
ADDRESS_TX_1);
// Tx2 uses as input the output 0 of Tx1 and output 0 of genTx
List<RawInput> rawInputs = new ArrayList<>();
rawInputs.add(getRawInput(inputIndex, inputTxId));
rawInputs.add(getRawInput(0, TX1_ID));
rawInputs.add(getRawInput(0, GEN_TX_ID));
RawTransaction rawTransaction = getRawTransaction(BLOCK_1, TX2_ID);
rawTransaction.setVIn(rawInputs);
List<RawOutput> rawOutputs = new ArrayList<>();
rawOutputs.add(getRawOutput(outputIndex, outputValue, outputAddress));
rawOutputs.add(getRawOutput(0, ADDRESS_TX_1_VALUE + ADDRESS_GEN_1_VALUE, ADDRESS_TX_2));
rawTransaction.setVOut(rawOutputs);
service.addTxToBlock(BLOCK_1, rawTransaction);
squBlockchainService.addTxToBlock(1, rawTransaction);
squBlockchainService.buildBlocks(0, 1);
return rawTransaction;
service.buildBlocks(BLOCK_0, BLOCK_1);
BsqUTXOMap bsqUTXOMap = parseAllBlocksFromGenesis();
BsqUTXO bsqUTXO1 = bsqUTXOMap.getByTuple(TX2_ID, 0);
assertEquals(bsqUTXO1.getUtxoId(), getUTXOId(TX2_ID, 0));
assertEquals(ADDRESS_GEN_1_VALUE + ADDRESS_GEN_2_VALUE, bsqUTXO1.getValue().value);
assertEquals(1, bsqUTXOMap.size());
}
private void buildGenesisBlock(int genesisBlockHeight, String genesisTxId) throws BsqBlockchainException, BitcoindException, CommunicationException {
RawTransaction genesisRawTransaction = getRawTransaction(genesisBlockHeight, genesisTxId);
List<RawInput> rawInputs = new ArrayList<>();
rawInputs.add(getRawInput(0, "000001a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627"));
genesisRawTransaction.setVIn(rawInputs);
///////////////////////////////////////////////////////////////////////////////////////////
// Get btcd objects
///////////////////////////////////////////////////////////////////////////////////////////
List<RawOutput> rawOutputs = new ArrayList<>();
rawOutputs.add(getRawOutput(0, 0.00005000, "addressGen1"));
rawOutputs.add(getRawOutput(1, 0.00001000, "addressGen2"));
genesisRawTransaction.setVOut(rawOutputs);
squBlockchainService.setGenesisTx(genesisTxId, genesisBlockHeight);
squBlockchainService.addTxToBlock(0, genesisRawTransaction);
squBlockchainService.buildBlocks(0, 0);
}
private RawTransaction getRawTransaction(int genesisBlockHeight, String txId) {
private RawTransaction getRawTransaction(int height, String txId) {
RawTransaction genesisRawTransaction = new RawTransaction();
genesisRawTransaction.setBlockHash("BlockHash" + genesisBlockHeight);
genesisRawTransaction.setBlockHash("BlockHash" + height);
genesisRawTransaction.setTxId(txId);
return genesisRawTransaction;
}
private RawOutput getRawOutput(int index, double value, String address) {
private RawOutput getRawOutput(int index, long value, String address) {
RawOutput rawOutput = new RawOutput();
rawOutput.setN(index);
rawOutput.setValue(BigDecimal.valueOf((long) (value * 100000000), 8));
rawOutput.setValue(BigDecimal.valueOf(value).divide(BigDecimal.valueOf(100000000)));
PubKeyScript scriptPubKey = new PubKeyScript();
scriptPubKey.setAddresses(Collections.singletonList(address));
rawOutput.setScriptPubKey(scriptPubKey);
@ -245,26 +321,84 @@ public class BsqBlockchainServiceTest {
return rawInput;
}
private String getHex(String txId) {
byte[] bytes = new byte[32];
byte[] inputBytes = txId.getBytes();
for (int i = 0; i < 32; i++) {
if (inputBytes.length > i)
bytes[i] = inputBytes[i];
else
bytes[i] = 0x00;
}
return Utils.HEX.encode(bytes);
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private void buildGenesisBlock()
throws BsqBlockchainException, BitcoindException, CommunicationException {
// tx funding the funding tx for genesis tx
List<RawInput> inputForFundGenTx = new ArrayList<>();
inputForFundGenTx.add(getRawInput(0, FUND_GEN_FUND_TX_ID));
final RawTransaction fundGenTx = getRawTransaction(BLOCK_0, FUND_GEN_TX_ID);
fundGenTx.setVIn(inputForFundGenTx);
List<RawOutput> outputForFundGenTx = new ArrayList<>();
outputForFundGenTx.add(getRawOutput(0, ADDRESS_GEN_1_VALUE + ADDRESS_GEN_2_VALUE, ADDRESS_GEN_FUND_TX));
fundGenTx.setVOut(outputForFundGenTx);
service.addTxToBlock(BLOCK_0, fundGenTx);
List<RawInput> inputsForGenTx = new ArrayList<>();
inputsForGenTx.add(getRawInput(0, FUND_GEN_TX_ID));
RawTransaction genesisTx = getRawTransaction(BLOCK_0, GEN_TX_ID);
genesisTx.setVIn(inputsForGenTx);
List<RawOutput> outputs = new ArrayList<>();
outputs.add(getRawOutput(0, ADDRESS_GEN_1_VALUE, ADDRESS_GEN_1));
outputs.add(getRawOutput(1, ADDRESS_GEN_2_VALUE, ADDRESS_GEN_2));
genesisTx.setVOut(outputs);
service.setGenesisTx(GEN_TX_ID, BLOCK_0);
service.addTxToBlock(BLOCK_0, genesisTx);
service.buildBlocks(BLOCK_0, BLOCK_0);
}
private void buildTx(String spendingTxId,
int spendingTxOutputIndex,
String txId,
int txBlockHeight,
int outputIndex,
long outputValue,
String outputAddress) {
List<RawInput> rawInputs = new ArrayList<>();
rawInputs.add(getRawInput(spendingTxOutputIndex, spendingTxId));
RawTransaction rawTransaction = getRawTransaction(txBlockHeight, txId);
rawTransaction.setVIn(rawInputs);
List<RawOutput> rawOutputs = new ArrayList<>();
rawOutputs.add(getRawOutput(outputIndex, outputValue, outputAddress));
rawTransaction.setVOut(rawOutputs);
service.addTxToBlock(txBlockHeight, rawTransaction);
}
private BsqUTXOMap parseAllBlocksFromGenesis()
throws BitcoindException, CommunicationException, BsqBlockchainException {
return service.parseAllBlocksFromGenesis(service.requestChainHeadHeight(),
BLOCK_0,
GEN_TX_ID);
}
private String getUTXOId(String TxId, int index) {
return TxId + ":" + index;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Mock
///////////////////////////////////////////////////////////////////////////////////////////
@Slf4j
class MockBsqBlockchainService extends BsqBlockchainRpcService {
private static final Logger log = LoggerFactory.getLogger(MockBsqBlockchainService.class);
private List<Block> blocks;
private int chainHeadHeight;
private String genesisTxId;
private int genesisBlockHeight;
private final Map<String, RawTransaction> txsByIsMap = new HashMap<>();
private String GENESIS_TX_ID;
private int GENESIS_HEIGHT;
private final Map<String, RawTransaction> txByIdMap = new HashMap<>();
private final Map<Integer, List<RawTransaction>> txsInBlockMap = new HashMap<>();
private final Map<Integer, List<String>> txIdsInBlockMap = new HashMap<>();
@ -300,7 +434,7 @@ class MockBsqBlockchainService extends BsqBlockchainRpcService {
String txId = transaction.getTxId();
ids.add(txId);
txsByIsMap.put(txId, transaction);
txByIdMap.put(txId, transaction);
}
public void buildTxList(int from, int to) {
@ -320,8 +454,8 @@ class MockBsqBlockchainService extends BsqBlockchainRpcService {
private List<String> getTxList(int blockIndex) {
List<String> txList = new ArrayList<>();
if (blockIndex == genesisBlockHeight) {
txList.add(genesisTxId);
if (blockIndex == GENESIS_HEIGHT) {
txList.add(GENESIS_TX_ID);
}
return txList;
}
@ -336,14 +470,13 @@ class MockBsqBlockchainService extends BsqBlockchainRpcService {
return blocks.get(index);
}
public void setGenesisTx(String genesisTxId, int genesisBlockHeight) {
this.genesisTxId = genesisTxId;
this.genesisBlockHeight = genesisBlockHeight;
public void setGenesisTx(String GENESIS_TX_ID, int GENESIS_HEIGHT) {
this.GENESIS_TX_ID = GENESIS_TX_ID;
this.GENESIS_HEIGHT = GENESIS_HEIGHT;
}
@Override
protected RawTransaction getRawTransaction(String txId) throws BitcoindException, CommunicationException {
return txsByIsMap.get(txId);
return txByIdMap.get(txId);
}
}

View file

@ -1,320 +0,0 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.tokens;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import static org.junit.Assert.*;
public class TransactionParserTest {
private static final Logger log = LoggerFactory.getLogger(TransactionParserTest.class);
private MockTxService txService;
private TransactionParser transactionParser;
private Tx genesisTx;
private Tx tx1;
private TxOutput output1;
private TxOutput output2;
private TxInput input1;
private TxOutput output1_1;
private TxInput input2;
private TxOutput output2_1;
private TxInput genesisInput;
private TxOutput output2_2;
private TxOutput output2_3;
private TxInput input3;
private TxOutput output3_1;
private TxOutput output3;
private TxOutput output4;
private TxInput input4;
private TxOutput output5_1;
private TxOutput output5_2;
private TxOutput output5;
private TxInput input5;
@Before
public void setup() {
txService = new MockTxService();
transactionParser = new TransactionParser("id_genesis", txService);
}
@After
public void tearDown() {
txService.cleanup();
}
@Test
public void testGetTx() {
assertEquals(createGenesisTx(), transactionParser.getTx("id_genesis"));
assertEquals(createTx1(), transactionParser.getTx("id_tx1"));
}
@Test
public void testValidTxs() {
transactionParser.applyIsTokenForAllOutputs(createGenesisTx());
assertTrue(transactionParser.isValidInput(genesisInput));
assertTrue(transactionParser.isValidOutput(output1));
assertTrue(transactionParser.isValidOutput(output2));
assertTrue(transactionParser.isValidOutput(output3));
assertTrue(transactionParser.isValidOutput(output4));
assertTrue(transactionParser.isValidOutput(output5));
// output1 -> output1_1
transactionParser.applyIsTokenForAllOutputs(createTx1());
assertFalse(transactionParser.isValidOutput(output1));
assertTrue(transactionParser.isValidOutput(output2));
assertTrue(transactionParser.isValidOutput(output3));
assertTrue(transactionParser.isValidOutput(output4));
assertTrue(transactionParser.isValidOutput(output5));
assertTrue(transactionParser.isValidInput(input1));
assertTrue(transactionParser.isValidOutput(output1_1));
// output2 -> output2_1, output2_2, output2_3 (invalid)
transactionParser.applyIsTokenForAllOutputs(createTx2());
assertFalse(transactionParser.isValidOutput(output1));
assertFalse(transactionParser.isValidOutput(output2));
assertTrue(transactionParser.isValidOutput(output3));
assertTrue(transactionParser.isValidOutput(output4));
assertTrue(transactionParser.isValidOutput(output5));
assertTrue(transactionParser.isValidInput(input1));
assertTrue(transactionParser.isValidOutput(output1_1));
assertTrue(transactionParser.isValidInput(input2));
assertTrue(transactionParser.isValidOutput(output2_1));
assertTrue(transactionParser.isValidOutput(output2_2));
assertFalse(transactionParser.isValidOutput(output2_3));
// output3 + output4 -> output3_1
transactionParser.applyIsTokenForAllOutputs(createTx3());
assertFalse(transactionParser.isValidOutput(output1));
assertFalse(transactionParser.isValidOutput(output2));
assertFalse(transactionParser.isValidOutput(output3));
assertFalse(transactionParser.isValidOutput(output4));
assertTrue(transactionParser.isValidOutput(output5));
assertTrue(transactionParser.isValidInput(input1));
assertTrue(transactionParser.isValidOutput(output1_1));
assertTrue(transactionParser.isValidInput(input2));
assertTrue(transactionParser.isValidOutput(output2_1));
assertTrue(transactionParser.isValidOutput(output2_2));
assertFalse(transactionParser.isValidOutput(output2_3));
assertTrue(transactionParser.isValidOutput(output3_1));
// output5 -> output5_1, output5_2 (both invalid)
transactionParser.applyIsTokenForAllOutputs(createTx5());
assertFalse(transactionParser.isValidOutput(output1));
assertFalse(transactionParser.isValidOutput(output2));
assertFalse(transactionParser.isValidOutput(output3));
assertFalse(transactionParser.isValidOutput(output4));
assertFalse(transactionParser.isValidOutput(output5));
assertTrue(transactionParser.isValidInput(input1));
assertTrue(transactionParser.isValidOutput(output1_1));
assertTrue(transactionParser.isValidInput(input2));
assertTrue(transactionParser.isValidOutput(output2_1));
assertTrue(transactionParser.isValidOutput(output2_2));
assertFalse(transactionParser.isValidOutput(output2_3));
assertTrue(transactionParser.isValidOutput(output3_1));
assertFalse(transactionParser.isValidOutput(output5_1));
assertFalse(transactionParser.isValidOutput(output5_2));
}
@Test
public void testGetAllUTXOs() {
Tx genesisTx = createGenesisTx();
transactionParser.applyIsTokenForAllOutputs(genesisTx);
Set<TxOutput> allUTXOs = transactionParser.getAllUTXOs(genesisTx);
assertEquals(5, allUTXOs.size());
allUTXOs.stream().forEach(output -> {
log.debug("output " + output);
assertTrue(transactionParser.isValidOutput(output));
});
// output1 -> output1_1
transactionParser.applyIsTokenForAllOutputs(createTx1());
allUTXOs = transactionParser.getAllUTXOs(genesisTx);
assertEquals(5, allUTXOs.size());
allUTXOs.stream().forEach(output -> {
log.debug("output " + output);
assertTrue(transactionParser.isValidOutput(output));
});
// output2 -> output2_1, output2_2, output2_3 (invalid)
transactionParser.applyIsTokenForAllOutputs(createTx2());
allUTXOs = transactionParser.getAllUTXOs(genesisTx);
assertEquals(6, allUTXOs.size());
allUTXOs.stream().forEach(output -> {
log.debug("output " + output);
assertTrue(transactionParser.isValidOutput(output));
});
// output3 + output4 -> output3_1
transactionParser.applyIsTokenForAllOutputs(createTx3());
allUTXOs = transactionParser.getAllUTXOs(genesisTx);
assertEquals(5, allUTXOs.size());
allUTXOs.stream().forEach(output -> {
log.debug("output " + output);
assertTrue(transactionParser.isValidOutput(output));
});
// output5 -> output5_1, output5_2 (both invalid) renders output5 invalid as well (burned)
transactionParser.applyIsTokenForAllOutputs(createTx5());
allUTXOs = transactionParser.getAllUTXOs(genesisTx);
assertEquals(4, allUTXOs.size());
allUTXOs.stream().forEach(output -> {
log.debug("output " + output);
assertTrue(transactionParser.isValidOutput(output));
});
}
@Test
public void testInvalidTxs() {
transactionParser.applyIsTokenForAllOutputs(createGenesisTx());
transactionParser.applyIsTokenForAllOutputs(createInvalidTx1_tooHighValue());
assertTrue(transactionParser.isValidInput(input1));
assertFalse(transactionParser.isValidOutput(output1)); // spent
assertFalse(transactionParser.isValidOutput(output1_1)); // to high value
assertTrue(transactionParser.isValidOutput(output2));
// TODO a double spend could be applied, though once confirmed the btc tx is invalid even in our mock its valid...
transactionParser.applyIsTokenForAllOutputs(createTx1());
assertTrue(transactionParser.isValidInput(input1));
assertFalse(transactionParser.isValidOutput(output1));
assertTrue(transactionParser.isValidOutput(output1_1));
assertTrue(transactionParser.isValidOutput(output2));
}
private Tx createGenesisTx() {
Tx tx = new Tx("id_genesis");
genesisInput = new TxInput(new Tx("id_genesisInput", null, null), null);
genesisInput.value = 10_000;
tx.addInput(genesisInput);
output1 = new TxOutput("addr_1", 1000);
tx.addOutput(output1);
output2 = new TxOutput("addr_2", 2000);
tx.addOutput(output2);
output3 = new TxOutput("addr_3", 300);
tx.addOutput(output3);
output4 = new TxOutput("addr_4", 500);
tx.addOutput(output4);
output5 = new TxOutput("addr_5", 333);
tx.addOutput(output5);
txService.addTx(tx);
return tx;
}
private Tx createTx1() {
Tx tx = new Tx("id_tx1");
input1 = new TxInput(genesisTx, output1);
tx.addInput(input1);
TxInput feeInput = new TxInput(new Tx("id_fee_1", null, null), null);
tx.addInput(feeInput);
output1_1 = new TxOutput("addr_1_1", 1000);
tx.addOutput(output1_1);
txService.addTx(tx);
return tx;
}
private Tx createTx2() {
Tx tx = new Tx("id_tx2");
input2 = new TxInput(genesisTx, output2);
tx.addInput(input2);
TxInput feeInput = new TxInput(new Tx("id_fee_2", null, null), null);
tx.addInput(feeInput);
output2_1 = new TxOutput("addr_2_1", 500);
tx.addOutput(output2_1);
output2_2 = new TxOutput("addr_2_2", 1500);
tx.addOutput(output2_2);
output2_3 = new TxOutput("addr_2_3", 5000); // this will be invalid as we spent the token balance in the first 2 outputs, remaining 3000 is not enough
tx.addOutput(output2_3);
txService.addTx(tx);
return tx;
}
private Tx createTx3() {
Tx tx = new Tx("id_tx2");
input3 = new TxInput(genesisTx, output3);
tx.addInput(input3);
input4 = new TxInput(genesisTx, output4);
tx.addInput(input4);
TxInput feeInput = new TxInput(new Tx("id_fee_3", null, null), null);
tx.addInput(feeInput);
output3_1 = new TxOutput("addr_3_1", 800);
tx.addOutput(output3_1);
txService.addTx(tx);
return tx;
}
private Tx createTx5() {
Tx tx = new Tx("id_tx5");
input5 = new TxInput(genesisTx, output5);
tx.addInput(input5);
TxInput feeInput = new TxInput(new Tx("id_fee_5", null, null), null);
tx.addInput(feeInput);
output5_1 = new TxOutput("addr_5_1", 5000); // invalid as only 333 available
tx.addOutput(output5_1);
output5_2 = new TxOutput("addr_5_2", 333); // would match value but invalid because it is not first token output
tx.addOutput(output5_2);
txService.addTx(tx);
return tx;
}
private Tx createInvalidTx1_tooHighValue() {
Tx tx = new Tx("id_tx1");
input1 = new TxInput(genesisTx, output1);
tx.addInput(input1);
TxInput feeInput = new TxInput(new Tx("id_fee_1", null, null), null);
tx.addInput(feeInput);
output1_1 = new TxOutput("addr_1_1", 4000);
tx.addOutput(output1_1);
txService.addTx(tx);
return tx;
}
}

View file

@ -568,22 +568,22 @@ abstract class BankForm extends PaymentMethodForm {
String countryCode = bankAccountPayload.getCountryCode();
if (validatorsApplied && BankUtil.useValidation(countryCode)) {
if (BankUtil.isBankNameRequired(countryCode))
result &= bankNameInputTextField.getValidator().validate(bankAccountPayload.getBankName()).isValid;
result = result && bankNameInputTextField.getValidator().validate(bankAccountPayload.getBankName()).isValid;
if (BankUtil.isBankIdRequired(countryCode))
result &= bankIdInputTextField.getValidator().validate(bankAccountPayload.getBankId()).isValid;
result = result && bankIdInputTextField.getValidator().validate(bankAccountPayload.getBankId()).isValid;
if (BankUtil.isBranchIdRequired(countryCode))
result &= branchIdInputTextField.getValidator().validate(bankAccountPayload.getBranchId()).isValid;
result = result && branchIdInputTextField.getValidator().validate(bankAccountPayload.getBranchId()).isValid;
if (BankUtil.isAccountNrRequired(countryCode))
result &= accountNrInputTextField.getValidator().validate(bankAccountPayload.getAccountNr()).isValid;
result = result && accountNrInputTextField.getValidator().validate(bankAccountPayload.getAccountNr()).isValid;
if (BankUtil.isAccountTypeRequired(countryCode))
result &= bankAccountPayload.getAccountType() != null;
result = result && bankAccountPayload.getAccountType() != null;
if (useHolderID && BankUtil.isHolderIdRequired(countryCode))
result &= holderIdInputTextField.getValidator().validate(bankAccountPayload.getHolderTaxId()).isValid;
result = result && holderIdInputTextField.getValidator().validate(bankAccountPayload.getHolderTaxId()).isValid;
}
allInputsValid.set(result);
}

View file

@ -603,22 +603,22 @@ public class CashDepositForm extends PaymentMethodForm {
String countryCode = cashDepositAccountPayload.getCountryCode();
if (validatorsApplied && BankUtil.useValidation(countryCode)) {
if (BankUtil.isBankNameRequired(countryCode))
result &= bankNameInputTextField.getValidator().validate(cashDepositAccountPayload.getBankName()).isValid;
result = result && bankNameInputTextField.getValidator().validate(cashDepositAccountPayload.getBankName()).isValid;
if (BankUtil.isBankIdRequired(countryCode))
result &= bankIdInputTextField.getValidator().validate(cashDepositAccountPayload.getBankId()).isValid;
result = result && bankIdInputTextField.getValidator().validate(cashDepositAccountPayload.getBankId()).isValid;
if (BankUtil.isBranchIdRequired(countryCode))
result &= branchIdInputTextField.getValidator().validate(cashDepositAccountPayload.getBranchId()).isValid;
result = result && branchIdInputTextField.getValidator().validate(cashDepositAccountPayload.getBranchId()).isValid;
if (BankUtil.isAccountNrRequired(countryCode))
result &= accountNrInputTextField.getValidator().validate(cashDepositAccountPayload.getAccountNr()).isValid;
result = result && accountNrInputTextField.getValidator().validate(cashDepositAccountPayload.getAccountNr()).isValid;
if (BankUtil.isAccountTypeRequired(countryCode))
result &= cashDepositAccountPayload.getAccountType() != null;
result = result && cashDepositAccountPayload.getAccountType() != null;
if (useHolderID && BankUtil.isHolderIdRequired(countryCode))
result &= holderIdInputTextField.getValidator().validate(cashDepositAccountPayload.getHolderTaxId()).isValid;
result = result && holderIdInputTextField.getValidator().validate(cashDepositAccountPayload.getHolderTaxId()).isValid;
}
allInputsValid.set(result);
}

View file

@ -66,16 +66,16 @@ public class SameBankForm extends BankForm {
&& ((CountryBasedPaymentAccount) paymentAccount).getCountry() != null;
if (BankUtil.isBankNameRequired(bankAccountPayload.getCountryCode()))
result &= inputValidator.validate(bankAccountPayload.getBankName()).isValid;
result = result && inputValidator.validate(bankAccountPayload.getBankName()).isValid;
if (BankUtil.isBankIdRequired(bankAccountPayload.getCountryCode()))
result &= inputValidator.validate(bankAccountPayload.getBankId()).isValid;
result = result && inputValidator.validate(bankAccountPayload.getBankId()).isValid;
if (BankUtil.isBranchIdRequired(bankAccountPayload.getCountryCode()))
result &= inputValidator.validate(bankAccountPayload.getBranchId()).isValid;
result = result && inputValidator.validate(bankAccountPayload.getBranchId()).isValid;
if (BankUtil.isAccountNrRequired(bankAccountPayload.getCountryCode()))
result &= inputValidator.validate(bankAccountPayload.getAccountNr()).isValid;
result = result && inputValidator.validate(bankAccountPayload.getAccountNr()).isValid;
allInputsValid.set(result);
}

View file

@ -43,7 +43,6 @@ import io.bisq.core.btc.wallet.BtcWalletService;
import io.bisq.core.btc.wallet.WalletsManager;
import io.bisq.core.btc.wallet.WalletsSetup;
import io.bisq.core.dao.DaoManager;
import io.bisq.core.dao.blockchain.BsqBlockchainException;
import io.bisq.core.filter.FilterManager;
import io.bisq.core.offer.OpenOffer;
import io.bisq.core.offer.OpenOfferManager;
@ -534,11 +533,7 @@ public class MainViewModel implements ViewModel {
feeService.onAllServicesInitialized();
try {
daoManager.onAllServicesInitialized();
} catch (BsqBlockchainException e) {
new Popup<>().error(e.toString()).show();
}
daoManager.onAllServicesInitialized(errorMessage -> new Popup<>().error(errorMessage).show());
setupBtcNumPeersWatcher();
setupP2PNumPeersWatcher();

View file

@ -199,22 +199,23 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
GridPane.setHalignment(autoSelectAllMatchingCheckBox, HPos.LEFT);
GridPane.setColumnIndex(autoSelectAllMatchingCheckBox, 0);
GridPane.setMargin(autoSelectAllMatchingCheckBox, new Insets(0, -10, 0, -10));
autoSelectAllMatchingCheckBox.setOnAction(event -> model.setAutoSelectArbitrators(autoSelectAllMatchingCheckBox.isSelected()));
autoSelectAllMatchingCheckBox.setOnAction(event ->
model.setAutoSelectArbitrators(autoSelectAllMatchingCheckBox.isSelected()));
TableColumn<ArbitratorListItem, String> dateColumn = new TableColumn<>(Res.get("account.arbitratorSelection.regDate"));
dateColumn.setSortable(false);
dateColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getRegistrationDate()));
dateColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getRegistrationDate()));
dateColumn.setMinWidth(140);
dateColumn.setMaxWidth(140);
TableColumn<ArbitratorListItem, String> nameColumn = new TableColumn<>(Res.get("shared.onionAddress"));
nameColumn.setSortable(false);
nameColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getAddressString()));
nameColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getAddressString()));
nameColumn.setMinWidth(90);
TableColumn<ArbitratorListItem, String> languagesColumn = new TableColumn<>(Res.get("account.arbitratorSelection.languages"));
languagesColumn.setSortable(false);
languagesColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getLanguageCodes()));
languagesColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getLanguageCodes()));
languagesColumn.setMinWidth(130);
TableColumn<ArbitratorListItem, ArbitratorListItem> selectionColumn = new TableColumn<ArbitratorListItem, ArbitratorListItem>(

View file

@ -120,7 +120,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
btcFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
txSize / 1000d,
bsqFormatter.formatCoinWithCode(receiverAmount.subtract(miningFee))))
bsqFormatter.formatCoinWithCode(receiverAmount)))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
try {
@ -154,7 +154,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
TransactionVerificationException | WalletException | InsufficientMoneyException e) {
log.error(e.toString());
e.printStackTrace();
new Popup<>().warning(e.toString());
new Popup<>().warning(e.toString()).show();
}
});
}

View file

@ -1061,7 +1061,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void updateToggleButtons(boolean fixedPriceSelected) {
int marketPriceAvailable = model.marketPriceAvailableProperty.get();
fixedPriceSelected |= marketPriceAvailable == 0;
fixedPriceSelected = fixedPriceSelected || (marketPriceAvailable == 0);
if (marketPriceAvailable == 1) {
model.dataModel.setUseMarketBasedPrice(!fixedPriceSelected);

View file

@ -26,7 +26,7 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
<version>4.5.3</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>

View file

@ -259,7 +259,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
boolean containsKey = map.containsKey(hashOfPayload);
if (containsKey)
result &= checkIfStoredDataPubKeyMatchesNewDataPubKey(protectedStorageEntry.ownerPubKey, hashOfPayload);
result = result && checkIfStoredDataPubKeyMatchesNewDataPubKey(protectedStorageEntry.ownerPubKey, hashOfPayload);
// printData("before add");
if (result) {

View file

@ -190,7 +190,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>
<dependency>