mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 15:00:30 +01:00
Add UTXO handling of orphan transactions, Add SquBlockchainManager, add Unittests
This commit is contained in:
parent
37a8d6af94
commit
8f871e2629
30 changed files with 1116 additions and 437 deletions
|
@ -50,6 +50,7 @@ public class Utilities {
|
|||
public static final String LB = System.getProperty("line.separator");
|
||||
public static final String LB2 = LB + LB;
|
||||
|
||||
// TODO check out Jackson lib
|
||||
public static String objectToJson(Object object) {
|
||||
Gson gson = new GsonBuilder()
|
||||
.setExclusionStrategies(new AnnotationExclusionStrategy())
|
||||
|
|
|
@ -128,14 +128,13 @@ public class BtcWalletService extends WalletService {
|
|||
// Add fee input to prepared SQU send tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction completePreparedSquTx(Transaction preparedSquTx, boolean isSendTx, @Nullable byte[] opReturnData) throws
|
||||
TransactionVerificationException, WalletException, InsufficientFundsException, InsufficientMoneyException {
|
||||
|
||||
public Transaction completePreparedSendSquTx(Transaction preparedSquTx, boolean isSendTx) throws
|
||||
TransactionVerificationException, WalletException, InsufficientFundsException, InsufficientMoneyException {
|
||||
// preparedSquTx has following structure:
|
||||
// inputs [1-n] SQU inputs
|
||||
// outputs [0-1] SQU receivers output
|
||||
// outputs [0-1] SQU change output
|
||||
// mining fee: optional burned SQU fee
|
||||
|
||||
// We add BTC mining fee. Result tx looks like:
|
||||
// inputs [1-n] SQU inputs
|
||||
|
@ -143,8 +142,27 @@ public class BtcWalletService extends WalletService {
|
|||
// outputs [0-1] SQU receivers output
|
||||
// outputs [0-1] SQU change output
|
||||
// outputs [0-1] BTC change output
|
||||
// outputs [0-1] OP_RETURN with opReturnData
|
||||
// mining fee: BTC mining fee + optional burned SQU fee
|
||||
// mining fee: BTC mining fee
|
||||
return completePreparedSquTx(preparedSquTx, isSendTx, null);
|
||||
}
|
||||
|
||||
public Transaction completePreparedSquTx(Transaction preparedSquTx, boolean isSendTx, @Nullable byte[] opReturnData) throws
|
||||
TransactionVerificationException, WalletException, InsufficientFundsException, InsufficientMoneyException {
|
||||
|
||||
// preparedSquTx has following structure:
|
||||
// inputs [1-n] SQU inputs
|
||||
// outputs [0-1] SQU receivers output
|
||||
// outputs [0-1] SQU change output
|
||||
// mining fee: optional burned SQU fee (only if opReturnData != null)
|
||||
|
||||
// We add BTC mining fee. Result tx looks like:
|
||||
// inputs [1-n] SQU inputs
|
||||
// inputs [1-n] BTC inputs
|
||||
// outputs [0-1] SQU receivers output
|
||||
// outputs [0-1] SQU change output
|
||||
// outputs [0-1] BTC change output
|
||||
// outputs [0-1] OP_RETURN with opReturnData (only if opReturnData != null)
|
||||
// mining fee: BTC mining fee + optional burned SQU fee (only if opReturnData != null)
|
||||
|
||||
// In case of txs for burned SQU fees we have no receiver output and it might be that there is no change outputs
|
||||
// We need to guarantee that min. 1 valid output is added (OP_RETURN does not count). So we use a higher input
|
||||
|
|
|
@ -25,7 +25,7 @@ import io.bitsquare.btc.exceptions.WalletException;
|
|||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
import io.bitsquare.common.handlers.ResultHandler;
|
||||
import io.bitsquare.dao.blockchain.BlockchainService;
|
||||
import io.bitsquare.dao.blockchain.SquBlockchainManager;
|
||||
import io.bitsquare.dao.blockchain.SquUTXO;
|
||||
import io.bitsquare.user.Preferences;
|
||||
import org.bitcoinj.core.*;
|
||||
|
@ -51,7 +51,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||
public class SquWalletService extends WalletService {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquWalletService.class);
|
||||
|
||||
private final BlockchainService blockchainService;
|
||||
private final SquBlockchainManager squBlockchainManager;
|
||||
private final SquCoinSelector squCoinSelector;
|
||||
|
||||
|
||||
|
@ -61,13 +61,13 @@ public class SquWalletService extends WalletService {
|
|||
|
||||
@Inject
|
||||
public SquWalletService(WalletsSetup walletsSetup,
|
||||
BlockchainService blockchainService,
|
||||
SquBlockchainManager squBlockchainManager,
|
||||
Preferences preferences,
|
||||
FeeService feeService) {
|
||||
super(walletsSetup,
|
||||
preferences,
|
||||
feeService);
|
||||
this.blockchainService = blockchainService;
|
||||
this.squBlockchainManager = squBlockchainManager;
|
||||
this.squCoinSelector = new SquCoinSelector(true);
|
||||
|
||||
walletsSetup.addSetupCompletedHandler(() -> {
|
||||
|
@ -147,12 +147,12 @@ public class SquWalletService extends WalletService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void requestSquUtxo(@Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
|
||||
if (blockchainService.isUtxoAvailable()) {
|
||||
applyUtxoSetToUTXOProvider(blockchainService.getUtxoByTxIdMap());
|
||||
if (squBlockchainManager.isUtxoAvailable()) {
|
||||
applyUtxoSetToUTXOProvider(squBlockchainManager.getUtxoByTxIdMap());
|
||||
if (resultHandler != null)
|
||||
resultHandler.handleResult();
|
||||
} else {
|
||||
blockchainService.addUtxoListener(utxoByTxIdMap -> {
|
||||
squBlockchainManager.addUtxoListener(utxoByTxIdMap -> {
|
||||
applyUtxoSetToUTXOProvider(utxoByTxIdMap);
|
||||
if (resultHandler != null)
|
||||
resultHandler.handleResult();
|
||||
|
|
|
@ -56,6 +56,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
// Derived from WalletAppKit
|
||||
// TODO use same seed for both wallets.
|
||||
// TODo add rolling backup for both wallets
|
||||
public class WalletConfig extends AbstractIdleService {
|
||||
private static final Logger log = LoggerFactory.getLogger(WalletConfig.class);
|
||||
|
||||
|
|
|
@ -570,7 +570,7 @@ public abstract class WalletService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class BitsquareWalletEventListener extends AbstractWalletEventListener {
|
||||
public class BitsquareWalletEventListener extends AbstractWalletEventListener {
|
||||
@Override
|
||||
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||
notifyBalanceListeners(tx);
|
||||
|
|
|
@ -20,21 +20,21 @@ package io.bitsquare.dao;
|
|||
import com.google.inject.Inject;
|
||||
import io.bitsquare.btc.provider.squ.SquUtxoFeedService;
|
||||
import io.bitsquare.btc.wallet.SquWalletService;
|
||||
import io.bitsquare.dao.blockchain.BlockchainException;
|
||||
import io.bitsquare.dao.blockchain.BlockchainService;
|
||||
import io.bitsquare.dao.blockchain.SquBlockchainException;
|
||||
import io.bitsquare.dao.blockchain.SquBlockchainManager;
|
||||
import io.bitsquare.dao.compensation.CompensationRequestManager;
|
||||
import io.bitsquare.dao.vote.VoteManager;
|
||||
import io.bitsquare.dao.vote.VotingManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DaoManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(DaoManager.class);
|
||||
|
||||
private final BlockchainService blockchainService;
|
||||
private final SquBlockchainManager squBlockchainManager;
|
||||
private final SquWalletService squWalletService;
|
||||
private final DaoPeriodService daoPeriodService;
|
||||
private final SquUtxoFeedService squUtxoFeedService;
|
||||
private final VoteManager voteManager;
|
||||
private final VotingManager voteManager;
|
||||
private final CompensationRequestManager compensationRequestManager;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -42,13 +42,13 @@ public class DaoManager {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public DaoManager(BlockchainService blockchainService,
|
||||
public DaoManager(SquBlockchainManager squBlockchainManager,
|
||||
SquWalletService squWalletService,
|
||||
DaoPeriodService daoPeriodService,
|
||||
SquUtxoFeedService squUtxoFeedService,
|
||||
VoteManager voteManager,
|
||||
VotingManager voteManager,
|
||||
CompensationRequestManager compensationRequestManager) {
|
||||
this.blockchainService = blockchainService;
|
||||
this.squBlockchainManager = squBlockchainManager;
|
||||
this.squWalletService = squWalletService;
|
||||
this.daoPeriodService = daoPeriodService;
|
||||
this.squUtxoFeedService = squUtxoFeedService;
|
||||
|
@ -56,12 +56,12 @@ public class DaoManager {
|
|||
this.compensationRequestManager = compensationRequestManager;
|
||||
}
|
||||
|
||||
public void onAllServicesInitialized() throws BlockchainException {
|
||||
public void onAllServicesInitialized() throws SquBlockchainException {
|
||||
daoPeriodService.onAllServicesInitialized();
|
||||
squUtxoFeedService.onAllServicesInitialized();
|
||||
voteManager.onAllServicesInitialized();
|
||||
compensationRequestManager.onAllServicesInitialized();
|
||||
blockchainService.onAllServicesInitialized();
|
||||
squBlockchainManager.onAllServicesInitialized();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,12 +19,13 @@ package io.bitsquare.dao;
|
|||
|
||||
import com.google.inject.Singleton;
|
||||
import io.bitsquare.app.AppModule;
|
||||
import io.bitsquare.dao.blockchain.BlockchainRpcService;
|
||||
import io.bitsquare.dao.blockchain.BlockchainService;
|
||||
import io.bitsquare.dao.blockchain.RpcOptionKeys;
|
||||
import io.bitsquare.dao.blockchain.SquBlockchainManager;
|
||||
import io.bitsquare.dao.blockchain.SquBlockchainRpcService;
|
||||
import io.bitsquare.dao.blockchain.SquBlockchainService;
|
||||
import io.bitsquare.dao.compensation.CompensationRequestManager;
|
||||
import io.bitsquare.dao.vote.VoteManager;
|
||||
import io.bitsquare.dao.vote.VotingDefaultValues;
|
||||
import io.bitsquare.dao.vote.VotingManager;
|
||||
import io.bitsquare.dao.vote.VotingService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -42,12 +43,13 @@ public class DaoModule extends AppModule {
|
|||
@Override
|
||||
protected void configure() {
|
||||
bind(DaoManager.class).in(Singleton.class);
|
||||
bind(BlockchainService.class).to(BlockchainRpcService.class).in(Singleton.class);
|
||||
bind(SquBlockchainManager.class).in(Singleton.class);
|
||||
bind(SquBlockchainService.class).to(SquBlockchainRpcService.class).in(Singleton.class);
|
||||
bind(DaoPeriodService.class).in(Singleton.class);
|
||||
bind(VotingService.class).in(Singleton.class);
|
||||
|
||||
bind(CompensationRequestManager.class).in(Singleton.class);
|
||||
bind(VoteManager.class).in(Singleton.class);
|
||||
bind(VotingManager.class).in(Singleton.class);
|
||||
bind(DaoService.class).in(Singleton.class);
|
||||
bind(VotingDefaultValues.class).in(Singleton.class);
|
||||
|
||||
|
|
|
@ -1,295 +0,0 @@
|
|||
/*
|
||||
* 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.bitsquare.dao.blockchain;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
import io.bitsquare.common.handlers.ResultHandler;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
abstract public class BlockchainService {
|
||||
private static final Logger log = LoggerFactory.getLogger(BlockchainService.class);
|
||||
|
||||
// regtest
|
||||
public static final String GENESIS_TX_ID = "f3eb7bd51d792ecd7df7f74905a1452a43aa4f678ff2c0ded853939b1d2b590e";
|
||||
public static final int GENESIS_BLOCK_HEIGHT = 102; //at regtest: 700, 447000
|
||||
|
||||
protected Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap;
|
||||
private List<UtxoListener> utxoListeners = new ArrayList<>();
|
||||
private boolean isUtxoAvailable;
|
||||
protected int chainHeadHeight;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public BlockchainService() {
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Public methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
setup(this::setupComplete, errorMessage -> {
|
||||
log.error("setup failed" + errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
public Map<String, Map<Integer, SquUTXO>> getUtxoByTxIdMap() {
|
||||
return utxoByTxIdMap;
|
||||
}
|
||||
|
||||
|
||||
public void addUtxoListener(UtxoListener utxoListener) {
|
||||
utxoListeners.add(utxoListener);
|
||||
}
|
||||
|
||||
public void removeUtxoListener(UtxoListener utxoListener) {
|
||||
utxoListeners.remove(utxoListener);
|
||||
}
|
||||
|
||||
public boolean isUtxoAvailable() {
|
||||
return isUtxoAvailable;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void setupComplete() {
|
||||
syncFromGenesis(utxoByTxIdMap -> {
|
||||
this.utxoByTxIdMap = utxoByTxIdMap;
|
||||
isUtxoAvailable = true;
|
||||
utxoListeners.stream().forEach(e -> e.onUtxoChanged(utxoByTxIdMap));
|
||||
syncFromGenesisCompete();
|
||||
}, errorMessage -> {
|
||||
log.error("syncFromGenesis failed" + errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
abstract protected void syncFromGenesisCompete();
|
||||
|
||||
abstract protected void setup(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler);
|
||||
|
||||
abstract protected void syncFromGenesis(Consumer<Map<String, Map<Integer, SquUTXO>>> resultHandler, ErrorMessageHandler errorMessageHandler);
|
||||
|
||||
abstract void parseBlockchainFromGenesis(Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap, int genesisBlockHeight, String genesisTxId) throws BlockchainException;
|
||||
|
||||
abstract SquTransaction getSquTransaction(String txId) throws BlockchainException;
|
||||
|
||||
void parseBlock(SquBlock block, String genesisTxId, Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap) {
|
||||
int blockHeight = block.blockHeight;
|
||||
log.debug("Parse block at height={} ", blockHeight);
|
||||
for (String txId : block.txIds) {
|
||||
parseTransaction(txId, genesisTxId, blockHeight, utxoByTxIdMap);
|
||||
}
|
||||
}
|
||||
|
||||
SquTransaction parseTransaction(String txId, String genesisTxId, int blockHeight, Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap) {
|
||||
log.debug("Parse transaction with id={} ", txId);
|
||||
try {
|
||||
SquTransaction squTransaction = getSquTransaction(txId);
|
||||
List<SquTxOutput> outputs = squTransaction.outputs;
|
||||
if (txId.equals(genesisTxId)) {
|
||||
// Genesis tx uses all outputs as SQU outputs
|
||||
Map<Integer, SquUTXO> utxoByIndexMap = new HashMap<>();
|
||||
for (int i = 0; i < outputs.size(); i++) {
|
||||
SquTxOutput 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 SQU
|
||||
if (addresses.size() == 1) {
|
||||
String address = addresses.get(0);
|
||||
//TODO set coinbase to true after testing
|
||||
SquUTXO utxo = new SquUTXO(txId,
|
||||
output.index,
|
||||
output.value,
|
||||
blockHeight,
|
||||
false,
|
||||
output.script,
|
||||
address);
|
||||
utxoByIndexMap.put(i, utxo);
|
||||
}
|
||||
}
|
||||
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);
|
||||
} else if (isIssuanceTx(txId)) {
|
||||
// Issuance tx has the funding output at index 0 and the SQU output at index 1
|
||||
if (outputs.size() > 1) {
|
||||
if (isValidFundingOutput(outputs.get(0))) {
|
||||
// We have a valid funding tx so we add out issuance output to the utxo
|
||||
SquTxOutput squOutput = outputs.get(1);
|
||||
if (isValidIssuanceOutput(squOutput)) {
|
||||
List<String> addresses = squOutput.addresses;
|
||||
// Only at raw MS outputs addresses have more then 1 entry
|
||||
// We do not support raw MS for SQU
|
||||
if (addresses.size() == 1) {
|
||||
String address = addresses.get(0);
|
||||
//TODO set coinbase to true after testing
|
||||
SquUTXO utxo = new SquUTXO(txId,
|
||||
squOutput.index,
|
||||
squOutput.value,
|
||||
blockHeight,
|
||||
false,
|
||||
squOutput.script,
|
||||
address);
|
||||
Map<Integer, SquUTXO> utxoByIndexMap = new HashMap<>();
|
||||
utxoByIndexMap.put(1, utxo);
|
||||
boolean wasEmpty = utxoByTxIdMap.put(txId, utxoByIndexMap) == null;
|
||||
checkArgument(wasEmpty, "We must not have that tx in the map. txId=" + txId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other txs
|
||||
Coin availableValue = Coin.ZERO;
|
||||
for (SquTxInput input : squTransaction.inputs) {
|
||||
String spendingTxId = input.spendingTxId;
|
||||
if (utxoByTxIdMap.containsKey(spendingTxId)) {
|
||||
Map<Integer, SquUTXO> utxoByIndexMap = utxoByTxIdMap.get(spendingTxId);
|
||||
Integer index = input.index;
|
||||
if (utxoByIndexMap.containsKey(index)) {
|
||||
SquUTXO utxo = utxoByIndexMap.get(index);
|
||||
availableValue = availableValue.add(utxo.getValue());
|
||||
utxoByIndexMap.remove(index);
|
||||
if (utxoByIndexMap.isEmpty()) {
|
||||
// If no more entries by index we can remove the whole entry by txId
|
||||
utxoByTxIdMap.remove(spendingTxId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we have an input spending tokens we iterate the outputs
|
||||
if (availableValue.isPositive()) {
|
||||
Map<Integer, SquUTXO> 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++) {
|
||||
SquTxOutput 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 SQU
|
||||
if (addresses.size() == 1) {
|
||||
String address = addresses.get(0);
|
||||
availableValue = availableValue.subtract(squOutput.value);
|
||||
if (!availableValue.isNegative()) {
|
||||
// We are spending available tokens
|
||||
SquUTXO utxo = new SquUTXO(txId,
|
||||
squOutput.index,
|
||||
squOutput.value,
|
||||
blockHeight,
|
||||
false,
|
||||
squOutput.script,
|
||||
address);
|
||||
utxoByIndexMap.put(i, utxo);
|
||||
} else {
|
||||
// As soon we have spent our inputs we can break
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isRequestPhase(blockHeight)) {
|
||||
|
||||
/* Check if tx is in time interval of phase 1
|
||||
Check if OP_RETURN data starts with version byte and has latest version
|
||||
Check if burned SQU have been mature
|
||||
Check if SQU is a valid SQU input
|
||||
Check if fee payment is correct
|
||||
Check if there is min. 1 Btc output (small payment to own compensation receiving address)
|
||||
*/
|
||||
} else if (isVotingPhase(blockHeight)) {
|
||||
|
||||
} else if (isFundingPhase(blockHeight)) {
|
||||
}
|
||||
return squTransaction;
|
||||
} catch (BlockchainException e) {
|
||||
//TODO throw
|
||||
log.error(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boolean isValidIssuanceOutput(SquTxOutput output) {
|
||||
//TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isValidFundingOutput(SquTxOutput output) {
|
||||
//TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isIssuanceTx(String txId) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isRequestPhase(int blockHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isVotingPhase(int blockHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isFundingPhase(int blockHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected void printUtxoMap(Map<String, Map<Integer, SquUTXO>> 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());
|
||||
}
|
||||
|
||||
public int getChainHeadHeight() {
|
||||
return chainHeadHeight;
|
||||
}
|
||||
}
|
|
@ -20,16 +20,62 @@ package io.bitsquare.dao.blockchain;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SquBlock {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquBlock.class);
|
||||
|
||||
public final List<String> txIds;
|
||||
public final int blockHeight;
|
||||
public final List<String> txIds;
|
||||
|
||||
private Map<String, SquTransaction> squTransactions = new HashMap<>();
|
||||
|
||||
public SquBlock(List<String> txIds, int blockHeight) {
|
||||
this.txIds = txIds;
|
||||
this.blockHeight = blockHeight;
|
||||
}
|
||||
|
||||
public void addSquTransaction(SquTransaction squTransaction) {
|
||||
squTransactions.put(squTransaction.txId, squTransaction);
|
||||
}
|
||||
|
||||
public SquTransaction getSquTransaction(String txId) {
|
||||
return squTransactions.get(txId);
|
||||
}
|
||||
|
||||
public Map<String, SquTransaction> getTransactions() {
|
||||
return squTransactions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
SquBlock squBlock = (SquBlock) o;
|
||||
|
||||
if (blockHeight != squBlock.blockHeight) return false;
|
||||
if (txIds != null ? !txIds.equals(squBlock.txIds) : squBlock.txIds != null) return false;
|
||||
return !(squTransactions != null ? !squTransactions.equals(squBlock.squTransactions) : squBlock.squTransactions != null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = blockHeight;
|
||||
result = 31 * result + (txIds != null ? txIds.hashCode() : 0);
|
||||
result = 31 * result + (squTransactions != null ? squTransactions.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SquBlock{" +
|
||||
"blockHeight=" + blockHeight +
|
||||
", txIds=" + txIds +
|
||||
", squTransactions=" + squTransactions +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
|
||||
package io.bitsquare.dao.blockchain;
|
||||
|
||||
public class BlockchainException extends Exception {
|
||||
public BlockchainException(String message) {
|
||||
public class SquBlockchainException extends Exception {
|
||||
public SquBlockchainException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BlockchainException(String message, Throwable cause) {
|
||||
public SquBlockchainException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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.bitsquare.dao.blockchain;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.inject.Inject;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SquBlockchainManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquBlockchainManager.class);
|
||||
|
||||
private final SquBlockchainService blockchainService;
|
||||
|
||||
// regtest
|
||||
public static final String GENESIS_TX_ID = "c01129ff48082f8f9613dd505899359227cb71aa457903359cfd0ca9c152dcd6";
|
||||
public static final int GENESIS_BLOCK_HEIGHT = 103;
|
||||
|
||||
protected Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap;
|
||||
private List<UtxoListener> utxoListeners = new ArrayList<>();
|
||||
private boolean isUtxoAvailable;
|
||||
protected int chainHeadHeight;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public SquBlockchainManager(SquBlockchainService blockchainService) {
|
||||
this.blockchainService = blockchainService;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Public methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
blockchainService.setup(this::setupComplete, errorMessage -> {
|
||||
log.error("setup failed" + errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
public Map<String, Map<Integer, SquUTXO>> getUtxoByTxIdMap() {
|
||||
return utxoByTxIdMap;
|
||||
}
|
||||
|
||||
|
||||
public void addUtxoListener(UtxoListener utxoListener) {
|
||||
utxoListeners.add(utxoListener);
|
||||
}
|
||||
|
||||
public void removeUtxoListener(UtxoListener utxoListener) {
|
||||
utxoListeners.remove(utxoListener);
|
||||
}
|
||||
|
||||
public boolean isUtxoAvailable() {
|
||||
return isUtxoAvailable;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void setupComplete() {
|
||||
ListenableFuture<Tuple2<Map<String, Map<Integer, SquUTXO>>, Integer>> future = blockchainService.syncFromGenesis(GENESIS_BLOCK_HEIGHT, GENESIS_TX_ID);
|
||||
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Map<Integer, SquUTXO>>, Integer>>() {
|
||||
@Override
|
||||
public void onSuccess(Tuple2<Map<String, Map<Integer, SquUTXO>>, Integer> tulpe) {
|
||||
UserThread.execute(() -> {
|
||||
SquBlockchainManager.this.utxoByTxIdMap = tulpe.first;
|
||||
chainHeadHeight = tulpe.second;
|
||||
isUtxoAvailable = true;
|
||||
utxoListeners.stream().forEach(e -> e.onUtxoChanged(utxoByTxIdMap));
|
||||
blockchainService.syncFromGenesisCompete(GENESIS_TX_ID,
|
||||
GENESIS_BLOCK_HEIGHT,
|
||||
block -> {
|
||||
UserThread.execute(() -> {
|
||||
log.error("### new block");
|
||||
try {
|
||||
blockchainService.parseBlock(new SquBlock(block.getTx(), block.getHeight()),
|
||||
GENESIS_BLOCK_HEIGHT,
|
||||
GENESIS_TX_ID,
|
||||
utxoByTxIdMap);
|
||||
} catch (SquBlockchainException e) {
|
||||
//TODO
|
||||
e.printStackTrace();
|
||||
}
|
||||
blockchainService.printUtxoMap(utxoByTxIdMap);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
UserThread.execute(() -> log.error("syncFromGenesis failed" + throwable.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public int getChainHeadHeight() {
|
||||
return chainHeadHeight;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Setters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
}
|
|
@ -27,12 +27,11 @@ import com.neemre.btcdcli4j.core.CommunicationException;
|
|||
import com.neemre.btcdcli4j.core.client.BtcdClientImpl;
|
||||
import com.neemre.btcdcli4j.core.domain.Block;
|
||||
import com.neemre.btcdcli4j.core.domain.RawTransaction;
|
||||
import com.neemre.btcdcli4j.core.domain.Transaction;
|
||||
import com.neemre.btcdcli4j.daemon.BtcdDaemonImpl;
|
||||
import com.neemre.btcdcli4j.daemon.event.BlockListener;
|
||||
import com.neemre.btcdcli4j.daemon.event.WalletListener;
|
||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
import io.bitsquare.common.handlers.ResultHandler;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
|
@ -56,8 +55,8 @@ import java.util.stream.Collectors;
|
|||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.bitcoinj.core.Utils.HEX;
|
||||
|
||||
public class BlockchainRpcService extends BlockchainService {
|
||||
private static final Logger log = LoggerFactory.getLogger(BlockchainRpcService.class);
|
||||
public class SquBlockchainRpcService extends SquBlockchainService {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquBlockchainRpcService.class);
|
||||
|
||||
private final String rpcUser;
|
||||
private final String rpcPassword;
|
||||
|
@ -75,11 +74,11 @@ public class BlockchainRpcService extends BlockchainService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public BlockchainRpcService(@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) {
|
||||
public SquBlockchainRpcService(@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) {
|
||||
this.rpcUser = rpcUser;
|
||||
this.rpcPassword = rpcPassword;
|
||||
this.rpcPort = rpcPort;
|
||||
|
@ -93,7 +92,7 @@ public class BlockchainRpcService extends BlockchainService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void setup(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
void setup(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
ListenableFuture<BtcdClientImpl> future = setupExecutorService.submit(() -> {
|
||||
long startTs = System.currentTimeMillis();
|
||||
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
|
||||
|
@ -114,16 +113,16 @@ public class BlockchainRpcService extends BlockchainService {
|
|||
log.info("Setup took {} ms", System.currentTimeMillis() - startTs);
|
||||
return client;
|
||||
} catch (IOException | BitcoindException | CommunicationException e) {
|
||||
throw new BlockchainException(e.getMessage(), e);
|
||||
throw new SquBlockchainException(e.getMessage(), e);
|
||||
}
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
throw new BlockchainException(e.getMessage(), e);
|
||||
throw new SquBlockchainException(e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<BtcdClientImpl>() {
|
||||
public void onSuccess(BtcdClientImpl client) {
|
||||
BlockchainRpcService.this.client = client;
|
||||
SquBlockchainRpcService.this.client = client;
|
||||
resultHandler.handleResult();
|
||||
}
|
||||
|
||||
|
@ -134,83 +133,61 @@ public class BlockchainRpcService extends BlockchainService {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void syncFromGenesis(Consumer<Map<String, Map<Integer, SquUTXO>>> resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
ListenableFuture<Map<String, Map<Integer, SquUTXO>>> future = rpcRequestsExecutor.submit(() -> {
|
||||
protected ListenableFuture<Tuple2<Map<String, Map<Integer, SquUTXO>>, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId) {
|
||||
return rpcRequestsExecutor.submit(() -> {
|
||||
long startTs = System.currentTimeMillis();
|
||||
Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap = new HashMap<>();
|
||||
try {
|
||||
parseBlockchainFromGenesis(utxoByTxIdMap, GENESIS_BLOCK_HEIGHT, GENESIS_TX_ID);
|
||||
} catch (BlockchainException e) {
|
||||
throw new BlockchainException(e.getMessage(), e);
|
||||
}
|
||||
int chainHeadHeight = requestChainHeadHeight();
|
||||
parseBlockchain(utxoByTxIdMap, chainHeadHeight, genesisBlockHeight, genesisTxId);
|
||||
log.info("syncFromGenesis took {} ms", System.currentTimeMillis() - startTs);
|
||||
return utxoByTxIdMap;
|
||||
});
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<Map<String, Map<Integer, SquUTXO>>>() {
|
||||
public void onSuccess(Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap) {
|
||||
resultHandler.accept(utxoByTxIdMap);
|
||||
}
|
||||
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
errorMessageHandler.handleErrorMessage(throwable.getMessage());
|
||||
}
|
||||
return new Tuple2<>(utxoByTxIdMap, chainHeadHeight);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void syncFromGenesisCompete() {
|
||||
protected void syncFromGenesisCompete(String genesisTxId, int genesisBlockHeight, Consumer<Block> onNewBlockHandler) {
|
||||
daemon.addBlockListener(new BlockListener() {
|
||||
@Override
|
||||
public void blockDetected(Block block) {
|
||||
log.info("blockDetected " + block.getHash());
|
||||
parseBlock(new SquBlock(block.getTx(), block.getHeight()), GENESIS_TX_ID, utxoByTxIdMap);
|
||||
printUtxoMap(utxoByTxIdMap);
|
||||
if (onNewBlockHandler != null)
|
||||
onNewBlockHandler.accept(block);
|
||||
}
|
||||
});
|
||||
daemon.addWalletListener(new WalletListener() {
|
||||
/* daemon.addWalletListener(new WalletListener() {
|
||||
@Override
|
||||
public void walletChanged(Transaction transaction) {
|
||||
log.info("walletChanged " + transaction.getTxId());
|
||||
try {
|
||||
parseTransaction(transaction.getTxId(), GENESIS_TX_ID, client.getBlockCount(), utxoByTxIdMap);
|
||||
*//* try {
|
||||
// parseTransaction(transaction.getTxId(), GENESIS_TX_ID, client.getBlockCount(), tempUtxoByTxIdMap, utxoByTxIdMap);
|
||||
printUtxoMap(utxoByTxIdMap);
|
||||
} catch (BitcoindException | CommunicationException e) {
|
||||
} catch (BitcoindException | CommunicationException | SquBlockchainException e) {
|
||||
//TODO
|
||||
e.printStackTrace();
|
||||
}
|
||||
}*//*
|
||||
}
|
||||
});
|
||||
});*/
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseBlockchainFromGenesis(Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap, int genesisBlockHeight, String genesisTxId) throws BlockchainException {
|
||||
try {
|
||||
chainHeadHeight = client.getBlockCount();
|
||||
|
||||
log.info("blockCount=" + chainHeadHeight);
|
||||
long startTs = System.currentTimeMillis();
|
||||
for (int i = genesisBlockHeight; i <= chainHeadHeight; i++) {
|
||||
String blockHash = client.getBlockHash(i);
|
||||
Block block = client.getBlock(blockHash);
|
||||
log.info("blockHeight=" + i);
|
||||
parseBlock(new SquBlock(block.getTx(), block.getHeight()), genesisTxId, utxoByTxIdMap);
|
||||
}
|
||||
printUtxoMap(utxoByTxIdMap);
|
||||
log.info("Took {} ms", System.currentTimeMillis() - startTs);
|
||||
} catch (BitcoindException | CommunicationException e) {
|
||||
throw new BlockchainException(e.getMessage(), e);
|
||||
}
|
||||
int requestChainHeadHeight() throws BitcoindException, CommunicationException {
|
||||
return client.getBlockCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
SquTransaction getSquTransaction(String txId) throws BlockchainException {
|
||||
Block requestBlock(int blockHeight) throws BitcoindException, CommunicationException {
|
||||
return client.getBlock(client.getBlockHash(blockHeight));
|
||||
}
|
||||
|
||||
@Override
|
||||
SquTransaction requestTransaction(String txId) throws SquBlockchainException {
|
||||
try {
|
||||
RawTransaction rawTransaction = (RawTransaction) client.getRawTransaction(txId, 1);
|
||||
RawTransaction rawTransaction = getRawTransaction(txId);
|
||||
return new SquTransaction(txId,
|
||||
rawTransaction.getVIn()
|
||||
.stream()
|
||||
.filter(e -> e != null && e.getVOut() != null && e.getTxId() != null)
|
||||
.map(e -> new SquTxInput(e.getVOut(), e.getTxId()))
|
||||
.filter(rawInput -> rawInput != null && rawInput.getVOut() != null && rawInput.getTxId() != null)
|
||||
.map(rawInput -> new SquTxInput(rawInput.getVOut(), rawInput.getTxId(), rawTransaction.getHex()))
|
||||
.collect(Collectors.toList()),
|
||||
rawTransaction.getVOut()
|
||||
.stream()
|
||||
|
@ -218,10 +195,14 @@ public class BlockchainRpcService extends BlockchainService {
|
|||
.map(e -> new SquTxOutput(e.getN(),
|
||||
Coin.valueOf(e.getValue().movePointRight(8).longValue()),
|
||||
e.getScriptPubKey().getAddresses(),
|
||||
new Script(HEX.decode(e.getScriptPubKey().getHex()))))
|
||||
e.getScriptPubKey().getHex() != null ? new Script(HEX.decode(e.getScriptPubKey().getHex())) : null))
|
||||
.collect(Collectors.toList()));
|
||||
} catch (BitcoindException | CommunicationException e) {
|
||||
throw new BlockchainException(e.getMessage(), e);
|
||||
throw new SquBlockchainException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected RawTransaction getRawTransaction(String txId) throws BitcoindException, CommunicationException {
|
||||
return (RawTransaction) client.getRawTransaction(txId, 1);
|
||||
}
|
||||
}
|
|
@ -24,15 +24,16 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.io.File;
|
||||
|
||||
public class BlockchainRpcServiceMain {
|
||||
private static final Logger log = LoggerFactory.getLogger(BlockchainRpcServiceMain.class);
|
||||
public class SquBlockchainRpcServiceMain {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquBlockchainRpcServiceMain.class);
|
||||
|
||||
public static void main(String[] args) throws BlockchainException {
|
||||
public static void main(String[] args) throws SquBlockchainException {
|
||||
Log.setup(System.getProperty("user.home") + File.separator + "BlockchainRpcServiceMain");
|
||||
Log.setLevel(Level.WARN);
|
||||
|
||||
// regtest uses port 18332, mainnet 8332
|
||||
BlockchainRpcService blockchainRpcService = new BlockchainRpcService(args[0], args[1], args[2], args[3], args[4]);
|
||||
blockchainRpcService.onAllServicesInitialized();
|
||||
SquBlockchainRpcService blockchainRpcService = new SquBlockchainRpcService(args[0], args[1], args[2], args[3], args[4]);
|
||||
SquBlockchainManager squBlockchainManager = new SquBlockchainManager(blockchainRpcService);
|
||||
squBlockchainManager.onAllServicesInitialized();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* 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.bitsquare.dao.blockchain;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
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.bitsquare.common.handlers.ErrorMessageHandler;
|
||||
import io.bitsquare.common.handlers.ResultHandler;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
abstract public class SquBlockchainService {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquBlockchainService.class);
|
||||
|
||||
protected Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public SquBlockchainService() {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
abstract void setup(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler);
|
||||
|
||||
abstract ListenableFuture<Tuple2<Map<String, Map<Integer, SquUTXO>>, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId);
|
||||
|
||||
abstract void syncFromGenesisCompete(String genesisTxId, int genesisBlockHeight, Consumer<Block> onNewBlockHandler);
|
||||
|
||||
abstract int requestChainHeadHeight() throws BitcoindException, CommunicationException;
|
||||
|
||||
abstract Block requestBlock(int i) throws BitcoindException, CommunicationException;
|
||||
|
||||
abstract SquTransaction requestTransaction(String txId) throws SquBlockchainException;
|
||||
|
||||
Map<String, Map<Integer, SquUTXO>> parseBlockchain(Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap,
|
||||
int chainHeadHeight,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId)
|
||||
throws SquBlockchainException {
|
||||
try {
|
||||
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 SquBlock(block.getTx(), block.getHeight()),
|
||||
genesisBlockHeight,
|
||||
genesisTxId,
|
||||
utxoByTxIdMap);
|
||||
}
|
||||
printUtxoMap(utxoByTxIdMap);
|
||||
log.info("Took {} ms", System.currentTimeMillis() - startTs);
|
||||
} catch (BitcoindException | CommunicationException e) {
|
||||
throw new SquBlockchainException(e.getMessage(), e);
|
||||
}
|
||||
return utxoByTxIdMap;
|
||||
}
|
||||
|
||||
void parseBlock(SquBlock block,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId,
|
||||
Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap)
|
||||
throws SquBlockchainException {
|
||||
int blockHeight = block.blockHeight;
|
||||
log.debug("Parse block at height={} ", blockHeight);
|
||||
// We add all transactions to the block
|
||||
List<String> txIds = block.txIds;
|
||||
for (String txId : txIds) {
|
||||
block.addSquTransaction(requestTransaction(txId));
|
||||
}
|
||||
|
||||
// First we check for the genesis tx
|
||||
Map<String, SquTransaction> transactionsMap = block.getTransactions();
|
||||
if (blockHeight == genesisBlockHeight) {
|
||||
transactionsMap.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().equals(genesisTxId))
|
||||
.forEach(entry -> parseGenesisTx(entry.getValue(), blockHeight, utxoByTxIdMap));
|
||||
}
|
||||
|
||||
resolveConnectedTxs(transactionsMap.values(), utxoByTxIdMap, blockHeight, 0, 100);
|
||||
}
|
||||
|
||||
void resolveConnectedTxs(Collection<SquTransaction> transactions,
|
||||
Map<String, Map<Integer, SquUTXO>> 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<SquTransaction> connectedTxs = new HashSet<>();
|
||||
Set<SquTransaction> orphanTxs = new HashSet<>();
|
||||
outerLoop:
|
||||
for (SquTransaction transaction : transactions) {
|
||||
boolean isConnected = false;
|
||||
for (SquTxInput 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, SquUTXO> 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 SQU transactions
|
||||
for (SquTransaction 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<SquTransaction> 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(SquTransaction squTransaction,
|
||||
int blockHeight,
|
||||
Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap
|
||||
) {
|
||||
String txId = squTransaction.txId;
|
||||
List<SquTxOutput> outputs = squTransaction.outputs;
|
||||
|
||||
Coin availableValue = Coin.ZERO;
|
||||
for (SquTxInput input : squTransaction.inputs) {
|
||||
String spendingTxId = input.spendingTxId;
|
||||
if (utxoByTxIdMap.containsKey(spendingTxId)) {
|
||||
Map<Integer, SquUTXO> utxoByIndexMap = utxoByTxIdMap.get(spendingTxId);
|
||||
Integer index = input.spendingOuptuIndex;
|
||||
if (utxoByIndexMap.containsKey(index)) {
|
||||
SquUTXO utxo = utxoByIndexMap.get(index);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we have an input spending tokens we iterate the outputs
|
||||
if (availableValue.isPositive()) {
|
||||
Map<Integer, SquUTXO> 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++) {
|
||||
SquTxOutput 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 SQU
|
||||
if (addresses.size() == 1) {
|
||||
String address = addresses.get(0);
|
||||
availableValue = availableValue.subtract(squOutput.value);
|
||||
if (!availableValue.isNegative()) {
|
||||
// We are spending available tokens
|
||||
SquUTXO utxo = new SquUTXO(txId,
|
||||
squOutput.index,
|
||||
squOutput.value,
|
||||
blockHeight,
|
||||
false,
|
||||
squOutput.script,
|
||||
address);
|
||||
utxoByIndexMap.put(i, utxo);
|
||||
} else {
|
||||
log.warn("We tried to spend more SQU as we have in our inputs");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log.warn("addresses.size() is not 1.");
|
||||
}
|
||||
}
|
||||
|
||||
//TODO write that warning to a handler
|
||||
if (availableValue.isPositive()) {
|
||||
log.warn("SQU have been left which was not spent. Burned SQU amount={}, tx={}",
|
||||
availableValue.value,
|
||||
squTransaction.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
void parseGenesisTx(SquTransaction squTransaction, int blockHeight, Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap) {
|
||||
String txId = squTransaction.txId;
|
||||
List<SquTxOutput> outputs = squTransaction.outputs;
|
||||
|
||||
// Genesis tx uses all outputs as SQU outputs
|
||||
Map<Integer, SquUTXO> utxoByIndexMap = new HashMap<>();
|
||||
for (int i = 0; i < outputs.size(); i++) {
|
||||
SquTxOutput 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 SQU
|
||||
if (addresses.size() == 1) {
|
||||
String address = addresses.get(0);
|
||||
//TODO set coinbase to true after testing
|
||||
SquUTXO utxo = new SquUTXO(txId,
|
||||
output.index,
|
||||
output.value,
|
||||
blockHeight,
|
||||
false,
|
||||
output.script,
|
||||
address);
|
||||
utxoByIndexMap.put(i, utxo);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
void printUtxoMap(Map<String, Map<Integer, SquUTXO>> 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());
|
||||
}
|
||||
}
|
|
@ -34,4 +34,34 @@ public class SquTransaction {
|
|||
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;
|
||||
|
||||
SquTransaction that = (SquTransaction) 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 "SquTransaction{" +
|
||||
"txId='" + txId + '\'' +
|
||||
", inputs=" + inputs +
|
||||
", outputs=" + outputs +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,43 @@ import org.slf4j.LoggerFactory;
|
|||
public class SquTxInput {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquTxInput.class);
|
||||
|
||||
public final int index;
|
||||
public final int spendingOuptuIndex;
|
||||
public final String spendingTxId;
|
||||
public final String txId;
|
||||
|
||||
public SquTxInput(int index, String spendingTxId) {
|
||||
this.index = index;
|
||||
public SquTxInput(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;
|
||||
|
||||
SquTxInput that = (SquTxInput) 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 "SquTxInput{" +
|
||||
"spendingOuptuIndex=" + spendingOuptuIndex +
|
||||
", spendingTxId='" + spendingTxId + '\'' +
|
||||
", txId='" + txId + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,4 +38,37 @@ public class SquTxOutput {
|
|||
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;
|
||||
|
||||
SquTxOutput that = (SquTxOutput) 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 "SquTxOutput{" +
|
||||
"index=" + index +
|
||||
", value=" + value +
|
||||
", addresses=" + addresses +
|
||||
", script=" + script +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,9 @@ import org.bitcoinj.script.Script;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// Estimation for UTXO set: 1 UTXO object has 78 byte
|
||||
// 1000 UTXOs - 10 000 UTXOs: 78kb -780kb
|
||||
|
||||
public class SquUTXO extends UTXO {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquUTXO.class);
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ import java.util.stream.Collectors;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
public class VoteManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(VoteManager.class);
|
||||
public class VotingManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(VotingManager.class);
|
||||
|
||||
public static final String ERROR_MSG_MISSING_BYTE = "We need to have at least 1 more byte for the voting value.";
|
||||
public static final String ERROR_MSG_WRONG_SIZE = "sizeOfCompReqVotesInBytes must be 0 or multiple of 2. sizeOfCompReqVotesInBytes=";
|
||||
|
@ -60,13 +60,13 @@ public class VoteManager {
|
|||
private VoteItemsList activeVoteItemsList;
|
||||
|
||||
@Inject
|
||||
public VoteManager(BtcWalletService btcWalletService,
|
||||
SquWalletService squWalletService,
|
||||
FeeService feeService,
|
||||
Storage<ArrayList<VoteItemsList>> voteItemCollectionsStorage,
|
||||
CompensationRequestManager compensationRequestManager,
|
||||
DaoPeriodService daoPeriodService,
|
||||
VotingDefaultValues votingDefaultValues) {
|
||||
public VotingManager(BtcWalletService btcWalletService,
|
||||
SquWalletService squWalletService,
|
||||
FeeService feeService,
|
||||
Storage<ArrayList<VoteItemsList>> voteItemCollectionsStorage,
|
||||
CompensationRequestManager compensationRequestManager,
|
||||
DaoPeriodService daoPeriodService,
|
||||
VotingDefaultValues votingDefaultValues) {
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.squWalletService = squWalletService;
|
||||
this.feeService = feeService;
|
||||
|
@ -81,7 +81,7 @@ public class VoteManager {
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
VoteManager(VotingDefaultValues votingDefaultValues) {
|
||||
VotingManager(VotingDefaultValues votingDefaultValues) {
|
||||
this.btcWalletService = null;
|
||||
this.squWalletService = null;
|
||||
this.feeService = null;
|
|
@ -40,6 +40,8 @@ public enum VotingType {
|
|||
|
||||
COMP_REQUEST_MAPS((byte) 0x50);
|
||||
|
||||
// TODO max growth rate of SQU
|
||||
|
||||
public final Byte code;
|
||||
|
||||
VotingType(Byte code) {
|
||||
|
|
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
* 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.bitsquare.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 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.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/*
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
*/
|
||||
|
||||
public class SquBlockchainServiceTest {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquBlockchainServiceTest.class);
|
||||
private MockSquBlockchainService squBlockchainService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
squBlockchainService = new MockSquBlockchainService();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenesisBlock() throws SquBlockchainException, BitcoindException, CommunicationException {
|
||||
int genesisBlockHeight = 0;
|
||||
String genesisTxId = "000000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
|
||||
buildGenesisBlock(genesisBlockHeight, genesisTxId);
|
||||
Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap = squBlockchainService.parseBlockchain(new HashMap<>(),
|
||||
squBlockchainService.requestChainHeadHeight(),
|
||||
genesisBlockHeight,
|
||||
genesisTxId);
|
||||
SquUTXO squUTXO1 = utxoByTxIdMap.get(genesisTxId).get(0);
|
||||
SquUTXO squUTXO2 = utxoByTxIdMap.get(genesisTxId).get(1);
|
||||
assertEquals(1, utxoByTxIdMap.size());
|
||||
assertEquals("addressGen1", squUTXO1.getAddress());
|
||||
assertEquals("addressGen2", squUTXO2.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenToTx1Block1() throws SquBlockchainException, BitcoindException, CommunicationException {
|
||||
int genesisBlockHeight = 0;
|
||||
String genesisTxId = "000000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
|
||||
buildGenesisBlock(genesisBlockHeight, genesisTxId);
|
||||
|
||||
// We spend from output 1 of gen tx
|
||||
String txId = "100000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
|
||||
buildSpendingTx(genesisBlockHeight,
|
||||
genesisTxId,
|
||||
txId,
|
||||
1,
|
||||
0,
|
||||
0.00001000,
|
||||
"addressTx1");
|
||||
|
||||
Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap = squBlockchainService.parseBlockchain(new HashMap<>(),
|
||||
squBlockchainService.requestChainHeadHeight(),
|
||||
genesisBlockHeight,
|
||||
genesisTxId);
|
||||
|
||||
SquUTXO squUTXO1 = utxoByTxIdMap.get(genesisTxId).get(0);
|
||||
SquUTXO squUTXO2 = utxoByTxIdMap.get(txId).get(0);
|
||||
assertEquals(2, utxoByTxIdMap.size());
|
||||
assertEquals("addressGen1", squUTXO1.getAddress());
|
||||
assertEquals("addressTx1", squUTXO2.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenToTx1toTx2Block1() throws SquBlockchainException, BitcoindException, CommunicationException {
|
||||
int genesisBlockHeight = 0;
|
||||
String genesisTxId = "000000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
|
||||
buildGenesisBlock(genesisBlockHeight, genesisTxId);
|
||||
|
||||
// We spend from output 1 of gen tx
|
||||
String tx1Id = "100000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
|
||||
buildSpendingTx(genesisBlockHeight,
|
||||
genesisTxId,
|
||||
tx1Id,
|
||||
1,
|
||||
0,
|
||||
0.00001000,
|
||||
"addressTx1");
|
||||
|
||||
// We spend from output 0 of tx1 (same block)
|
||||
String tx2Id = "200000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
|
||||
buildSpendingTx(1,
|
||||
tx1Id,
|
||||
tx2Id,
|
||||
0,
|
||||
0,
|
||||
0.00001000,
|
||||
"addressTx2");
|
||||
|
||||
Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap = squBlockchainService.parseBlockchain(new HashMap<>(),
|
||||
squBlockchainService.requestChainHeadHeight(),
|
||||
genesisBlockHeight,
|
||||
genesisTxId);
|
||||
|
||||
SquUTXO squUTXO1 = utxoByTxIdMap.get(genesisTxId).get(0);
|
||||
SquUTXO squUTXO2 = utxoByTxIdMap.get(tx2Id).get(0);
|
||||
assertEquals(2, utxoByTxIdMap.size());
|
||||
assertEquals("addressGen1", squUTXO1.getAddress());
|
||||
assertEquals("addressTx2", squUTXO2.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenToTx1toTx2AndGenToTx2Block1() throws SquBlockchainException, BitcoindException, CommunicationException {
|
||||
int genesisBlockHeight = 0;
|
||||
String genesisTxId = "000000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
|
||||
buildGenesisBlock(genesisBlockHeight, genesisTxId);
|
||||
|
||||
// We spend from output 1 of gen tx
|
||||
String tx1Id = "100000a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627";
|
||||
buildSpendingTx(genesisBlockHeight,
|
||||
genesisTxId,
|
||||
tx1Id,
|
||||
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, SquUTXO>> utxoByTxIdMap = squBlockchainService.parseBlockchain(new HashMap<>(),
|
||||
squBlockchainService.requestChainHeadHeight(),
|
||||
genesisBlockHeight,
|
||||
genesisTxId);
|
||||
|
||||
SquUTXO squUTXO1 = utxoByTxIdMap.get(tx2Id).get(0);
|
||||
SquUTXO squUTXO2 = utxoByTxIdMap.get(tx2Id).get(1);
|
||||
assertEquals(1, utxoByTxIdMap.size());
|
||||
assertEquals("addressTx3a", squUTXO1.getAddress());
|
||||
assertEquals("addressTx3b", squUTXO2.getAddress());
|
||||
}
|
||||
|
||||
|
||||
private RawTransaction buildSpendingTx(int inputTxBlockHeight,
|
||||
String inputTxId,
|
||||
String txId,
|
||||
int inputIndex,
|
||||
int outputIndex,
|
||||
double outputValue,
|
||||
String outputAddress) {
|
||||
RawTransaction rawTransaction = getRawTransaction(inputTxBlockHeight + 1, txId);
|
||||
|
||||
List<RawInput> rawInputs = new ArrayList<>();
|
||||
rawInputs.add(getRawInput(inputIndex, inputTxId));
|
||||
rawTransaction.setVIn(rawInputs);
|
||||
|
||||
List<RawOutput> rawOutputs = new ArrayList<>();
|
||||
rawOutputs.add(getRawOutput(outputIndex, outputValue, outputAddress));
|
||||
rawTransaction.setVOut(rawOutputs);
|
||||
|
||||
squBlockchainService.addTxToBlock(1, rawTransaction);
|
||||
squBlockchainService.buildBlocks(0, 1);
|
||||
return rawTransaction;
|
||||
}
|
||||
|
||||
private void buildGenesisBlock(int genesisBlockHeight, String genesisTxId) throws SquBlockchainException, BitcoindException, CommunicationException {
|
||||
RawTransaction genesisRawTransaction = getRawTransaction(genesisBlockHeight, genesisTxId);
|
||||
|
||||
List<RawInput> rawInputs = new ArrayList<>();
|
||||
rawInputs.add(getRawInput(0, "000001a4d94cb612b5d722d531083f59f317d5dea1db4a191f61b2ab34af2627"));
|
||||
genesisRawTransaction.setVIn(rawInputs);
|
||||
|
||||
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) {
|
||||
RawTransaction genesisRawTransaction = new RawTransaction();
|
||||
genesisRawTransaction.setBlockHash("BlockHash" + genesisBlockHeight);
|
||||
genesisRawTransaction.setTxId(txId);
|
||||
return genesisRawTransaction;
|
||||
}
|
||||
|
||||
private RawOutput getRawOutput(int index, double value, String address) {
|
||||
RawOutput rawOutput = new RawOutput();
|
||||
rawOutput.setN(index);
|
||||
rawOutput.setValue(BigDecimal.valueOf((long) (value * 100000000), 8));
|
||||
PubKeyScript scriptPubKey = new PubKeyScript();
|
||||
scriptPubKey.setAddresses(Arrays.asList(address));
|
||||
rawOutput.setScriptPubKey(scriptPubKey);
|
||||
return rawOutput;
|
||||
}
|
||||
|
||||
private RawInput getRawInput(int index, String spendingTxId) {
|
||||
RawInput rawInput = new RawInput();
|
||||
rawInput.setTxId(spendingTxId);
|
||||
rawInput.setVOut(index);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
class MockSquBlockchainService extends SquBlockchainRpcService {
|
||||
private static final Logger log = LoggerFactory.getLogger(MockSquBlockchainService.class);
|
||||
private List<Block> blocks;
|
||||
private int chainHeadHeight;
|
||||
private String genesisTxId;
|
||||
private int genesisBlockHeight;
|
||||
private Map<String, RawTransaction> txsByIsMap = new HashMap<>();
|
||||
private Map<Integer, List<RawTransaction>> txsInBlockMap = new HashMap<>();
|
||||
private Map<Integer, List<String>> txIdsInBlockMap = new HashMap<>();
|
||||
|
||||
public MockSquBlockchainService() {
|
||||
super(null, null, null, null, null);
|
||||
}
|
||||
|
||||
public void buildBlocks(int from, int to) {
|
||||
this.chainHeadHeight = to;
|
||||
blocks = new ArrayList<>();
|
||||
for (int blockIndex = from; blockIndex <= to; blockIndex++) {
|
||||
blocks.add(getBlock(blockIndex, txIdsInBlockMap.get(blockIndex)));
|
||||
}
|
||||
}
|
||||
|
||||
public void addTxToBlock(int blockHeight, RawTransaction transaction) {
|
||||
List<RawTransaction> txs;
|
||||
if (txsInBlockMap.containsKey(blockHeight)) {
|
||||
txs = txsInBlockMap.get(blockHeight);
|
||||
} else {
|
||||
txs = new ArrayList<>();
|
||||
txsInBlockMap.put(blockHeight, txs);
|
||||
}
|
||||
txs.add(transaction);
|
||||
|
||||
List<String> ids;
|
||||
if (txIdsInBlockMap.containsKey(blockHeight)) {
|
||||
ids = txIdsInBlockMap.get(blockHeight);
|
||||
} else {
|
||||
ids = new ArrayList<>();
|
||||
txIdsInBlockMap.put(blockHeight, ids);
|
||||
}
|
||||
String txId = transaction.getTxId();
|
||||
ids.add(txId);
|
||||
|
||||
txsByIsMap.put(txId, transaction);
|
||||
}
|
||||
|
||||
public void buildTxList(int from, int to) {
|
||||
blocks = new ArrayList<>();
|
||||
for (int blockIndex = from; blockIndex < to; blockIndex++) {
|
||||
blocks.add(getBlock(blockIndex, getTxList(blockIndex)));
|
||||
}
|
||||
}
|
||||
|
||||
private Block getBlock(int blockIndex, List<String> txList) {
|
||||
Block block = new Block();
|
||||
block.setHeight(blockIndex);
|
||||
block.setHash("hash" + blockIndex);
|
||||
block.setTx(txList);
|
||||
return block;
|
||||
}
|
||||
|
||||
private List<String> getTxList(int blockIndex) {
|
||||
List<String> txList = new ArrayList<>();
|
||||
if (blockIndex == genesisBlockHeight) {
|
||||
txList.add(genesisTxId);
|
||||
}
|
||||
return txList;
|
||||
}
|
||||
|
||||
@Override
|
||||
int requestChainHeadHeight() throws BitcoindException, CommunicationException {
|
||||
return chainHeadHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
Block requestBlock(int index) throws BitcoindException, CommunicationException {
|
||||
return blocks.get(index);
|
||||
}
|
||||
|
||||
public void setGenesisTx(String genesisTxId, int genesisBlockHeight) {
|
||||
this.genesisTxId = genesisTxId;
|
||||
this.genesisBlockHeight = genesisBlockHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RawTransaction getRawTransaction(String txId) throws BitcoindException, CommunicationException {
|
||||
return txsByIsMap.get(txId);
|
||||
}
|
||||
|
||||
}
|
|
@ -31,7 +31,7 @@ public class VoteManagerTest {
|
|||
|
||||
@Test
|
||||
public void testGetVoteItemListFromOpReturnData() {
|
||||
VoteManager votingManager = new VoteManager(new VotingDefaultValues());
|
||||
VotingManager votingManager = new VotingManager(new VotingDefaultValues());
|
||||
byte[] opReturnData;
|
||||
VoteItemsList voteItemsList;
|
||||
|
||||
|
@ -55,7 +55,7 @@ public class VoteManagerTest {
|
|||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||
fail("Expected an IllegalArgumentException to be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), startsWith(VoteManager.ERROR_MSG_WRONG_SIZE));
|
||||
assertThat(e.getMessage(), startsWith(VotingManager.ERROR_MSG_WRONG_SIZE));
|
||||
}
|
||||
|
||||
// compensation requests set but no votes
|
||||
|
@ -77,7 +77,7 @@ public class VoteManagerTest {
|
|||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||
fail("Expected an IllegalArgumentException to be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), is(VoteManager.ERROR_MSG_INVALID_COMP_REQ_MAPS));
|
||||
assertThat(e.getMessage(), is(VotingManager.ERROR_MSG_INVALID_COMP_REQ_MAPS));
|
||||
}
|
||||
|
||||
// has voted on item 2, but we found a accepted flag at item 1
|
||||
|
@ -90,7 +90,7 @@ public class VoteManagerTest {
|
|||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||
fail("Expected an IllegalArgumentException to be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), is(VoteManager.ERROR_MSG_INVALID_COMP_REQ_VAL));
|
||||
assertThat(e.getMessage(), is(VotingManager.ERROR_MSG_INVALID_COMP_REQ_VAL));
|
||||
}
|
||||
|
||||
// has voted on all and are declined
|
||||
|
@ -120,7 +120,7 @@ public class VoteManagerTest {
|
|||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||
fail("Expected an IllegalArgumentException to be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), is(VoteManager.ERROR_MSG_MISSING_BYTE));
|
||||
assertThat(e.getMessage(), is(VotingManager.ERROR_MSG_MISSING_BYTE));
|
||||
}
|
||||
|
||||
// wrong length
|
||||
|
@ -134,7 +134,7 @@ public class VoteManagerTest {
|
|||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||
fail("Expected an IllegalArgumentException to be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), is(VoteManager.ERROR_MSG_MISSING_BYTE));
|
||||
assertThat(e.getMessage(), is(VotingManager.ERROR_MSG_MISSING_BYTE));
|
||||
}
|
||||
|
||||
// Invalid param vote 255 is not allowed only 0-254
|
||||
|
@ -177,7 +177,7 @@ public class VoteManagerTest {
|
|||
|
||||
@Test
|
||||
public void testCalculateHash() {
|
||||
VoteManager votingManager = new VoteManager(new VotingDefaultValues());
|
||||
VotingManager votingManager = new VotingManager(new VotingDefaultValues());
|
||||
|
||||
// assertEquals(10, votingManager.calculateHash(100, 0));
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<!-- exclude signatures, the bundling process breaks them for some reason -->
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/maven/**/pom.properties</exclude>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
|
|
|
@ -42,7 +42,7 @@ import io.bitsquare.common.Timer;
|
|||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.crypto.*;
|
||||
import io.bitsquare.dao.DaoManager;
|
||||
import io.bitsquare.dao.blockchain.BlockchainException;
|
||||
import io.bitsquare.dao.blockchain.SquBlockchainException;
|
||||
import io.bitsquare.filter.FilterManager;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.model.ViewModel;
|
||||
|
@ -551,7 +551,7 @@ public class MainViewModel implements ViewModel {
|
|||
|
||||
try {
|
||||
daoManager.onAllServicesInitialized();
|
||||
} catch (BlockchainException e) {
|
||||
} catch (SquBlockchainException e) {
|
||||
new Popup<>().error(e.toString()).show();
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ public class VoteView extends ActivatableView<GridPane, Void> {
|
|||
private FeeService feeService;
|
||||
private SQUFormatter squFormatter;
|
||||
private BSFormatter btcFormatter;
|
||||
private VoteManager voteManager;
|
||||
private VotingManager voteManager;
|
||||
private Button voteButton;
|
||||
private List<CompensationRequest> compensationRequests;
|
||||
private TitledGroupBg compensationRequestsTitledGroupBg, parametersTitledGroupBg;
|
||||
|
@ -94,7 +94,7 @@ public class VoteView extends ActivatableView<GridPane, Void> {
|
|||
@Inject
|
||||
private VoteView(CompensationRequestManager compensationRequestManager, SquWalletService squWalletService,
|
||||
BtcWalletService btcWalletService, FeeService feeService, SQUFormatter squFormatter,
|
||||
BSFormatter btcFormatter, VoteManager voteManager) {
|
||||
BSFormatter btcFormatter, VotingManager voteManager) {
|
||||
this.compensationRequestManager = compensationRequestManager;
|
||||
this.squWalletService = squWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
|
|
|
@ -17,17 +17,17 @@
|
|||
|
||||
package io.bitsquare.gui.main.dao.wallet;
|
||||
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.btc.wallet.SquWalletService;
|
||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
import io.bitsquare.gui.util.SQUFormatter;
|
||||
import javafx.scene.control.TextField;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
public class BalanceUtil {
|
||||
private static final Logger log = LoggerFactory.getLogger(BalanceUtil.class);
|
||||
|
@ -35,7 +35,7 @@ public class BalanceUtil {
|
|||
private final SquWalletService squWalletService;
|
||||
private final SQUFormatter formatter;
|
||||
private TextField balanceTextField;
|
||||
private BalanceListener balanceListener;
|
||||
private WalletEventListener walletEventListener;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
|
@ -52,31 +52,59 @@ public class BalanceUtil {
|
|||
}
|
||||
|
||||
public void initialize() {
|
||||
balanceListener = new BalanceListener() {
|
||||
walletEventListener = new WalletEventListener() {
|
||||
@Override
|
||||
public void onBalanceChanged(Coin balance, Transaction tx) {
|
||||
updateBalance(balance);
|
||||
public void onKeysAdded(List<ECKey> keys) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||
requestUtxo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||
requestUtxo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReorganize(Wallet wallet) {
|
||||
requestUtxo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
|
||||
requestUtxo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletChanged(Wallet wallet) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScriptsChanged(Wallet wallet, List<Script> scripts, boolean isAddingScripts) {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
private void requestUtxo() {
|
||||
squWalletService.requestSquUtxo(() -> {
|
||||
updateBalance(squWalletService.getAvailableBalance());
|
||||
balanceTextField.setText(formatter.formatCoinWithCode(squWalletService.getAvailableBalance()));
|
||||
}, errorMessage -> {
|
||||
new Popup<>().warning(errorMessage);
|
||||
});
|
||||
squWalletService.addBalanceListener(balanceListener);
|
||||
}
|
||||
|
||||
updateBalance(squWalletService.getAvailableBalance());
|
||||
public void activate() {
|
||||
requestUtxo();
|
||||
squWalletService.addEventListener(walletEventListener);
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
squWalletService.removeBalanceListener(balanceListener);
|
||||
}
|
||||
|
||||
private void updateBalance(Coin balance) {
|
||||
balanceTextField.setText(formatter.formatCoinWithCode(balance));
|
||||
squWalletService.removeEventListener(walletEventListener);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ public class TokenSendView extends ActivatableView<GridPane, Void> {
|
|||
Coin receiverAmount = squFormatter.parseToCoin(amountInputTextField.getText());
|
||||
try {
|
||||
Transaction preparedSendTx = squWalletService.getPreparedSendTx(receiversAddressString, receiverAmount);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedSquTx(preparedSendTx, true, null);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendSquTx(preparedSendTx, true);
|
||||
Transaction signedTx = squWalletService.signTx(txWithBtcFee);
|
||||
|
||||
Coin miningFee = signedTx.getFee();
|
||||
|
|
|
@ -730,6 +730,7 @@ public class Connection implements MessageListener {
|
|||
|
||||
Object rawInputObject = objectInputStream.readObject();
|
||||
|
||||
|
||||
// Throttle inbound messages
|
||||
long now = System.currentTimeMillis();
|
||||
long elapsed = now - lastReadTimeStamp;
|
||||
|
|
|
@ -5,7 +5,7 @@ mkdir -p gui/deploy
|
|||
|
||||
set -e
|
||||
|
||||
version="0.4.9.8"
|
||||
version="0.5.0.0"
|
||||
|
||||
mvn clean package -DskipTests -Dmaven.javadoc.skip=true
|
||||
|
||||
|
|
6
pom.xml
6
pom.xml
|
@ -160,6 +160,12 @@
|
|||
<artifactId>jsr305</artifactId>
|
||||
<version>3.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.16.12</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--logging-->
|
||||
<dependency>
|
||||
|
|
Loading…
Add table
Reference in a new issue