mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 23:06:39 +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 LB = System.getProperty("line.separator");
|
||||||
public static final String LB2 = LB + LB;
|
public static final String LB2 = LB + LB;
|
||||||
|
|
||||||
|
// TODO check out Jackson lib
|
||||||
public static String objectToJson(Object object) {
|
public static String objectToJson(Object object) {
|
||||||
Gson gson = new GsonBuilder()
|
Gson gson = new GsonBuilder()
|
||||||
.setExclusionStrategies(new AnnotationExclusionStrategy())
|
.setExclusionStrategies(new AnnotationExclusionStrategy())
|
||||||
|
|
|
@ -128,14 +128,13 @@ public class BtcWalletService extends WalletService {
|
||||||
// Add fee input to prepared SQU send tx
|
// 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:
|
// preparedSquTx has following structure:
|
||||||
// inputs [1-n] SQU inputs
|
// inputs [1-n] SQU inputs
|
||||||
// outputs [0-1] SQU receivers output
|
// outputs [0-1] SQU receivers output
|
||||||
// outputs [0-1] SQU change output
|
// outputs [0-1] SQU change output
|
||||||
// mining fee: optional burned SQU fee
|
|
||||||
|
|
||||||
// We add BTC mining fee. Result tx looks like:
|
// We add BTC mining fee. Result tx looks like:
|
||||||
// inputs [1-n] SQU inputs
|
// inputs [1-n] SQU inputs
|
||||||
|
@ -143,8 +142,27 @@ public class BtcWalletService extends WalletService {
|
||||||
// outputs [0-1] SQU receivers output
|
// outputs [0-1] SQU receivers output
|
||||||
// outputs [0-1] SQU change output
|
// outputs [0-1] SQU change output
|
||||||
// outputs [0-1] BTC change output
|
// outputs [0-1] BTC change output
|
||||||
// outputs [0-1] OP_RETURN with opReturnData
|
// mining fee: BTC mining fee
|
||||||
// mining fee: BTC mining fee + optional burned SQU 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
|
// 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
|
// 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.btc.provider.fee.FeeService;
|
||||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||||
import io.bitsquare.common.handlers.ResultHandler;
|
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.dao.blockchain.SquUTXO;
|
||||||
import io.bitsquare.user.Preferences;
|
import io.bitsquare.user.Preferences;
|
||||||
import org.bitcoinj.core.*;
|
import org.bitcoinj.core.*;
|
||||||
|
@ -51,7 +51,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||||
public class SquWalletService extends WalletService {
|
public class SquWalletService extends WalletService {
|
||||||
private static final Logger log = LoggerFactory.getLogger(SquWalletService.class);
|
private static final Logger log = LoggerFactory.getLogger(SquWalletService.class);
|
||||||
|
|
||||||
private final BlockchainService blockchainService;
|
private final SquBlockchainManager squBlockchainManager;
|
||||||
private final SquCoinSelector squCoinSelector;
|
private final SquCoinSelector squCoinSelector;
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,13 +61,13 @@ public class SquWalletService extends WalletService {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SquWalletService(WalletsSetup walletsSetup,
|
public SquWalletService(WalletsSetup walletsSetup,
|
||||||
BlockchainService blockchainService,
|
SquBlockchainManager squBlockchainManager,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
FeeService feeService) {
|
FeeService feeService) {
|
||||||
super(walletsSetup,
|
super(walletsSetup,
|
||||||
preferences,
|
preferences,
|
||||||
feeService);
|
feeService);
|
||||||
this.blockchainService = blockchainService;
|
this.squBlockchainManager = squBlockchainManager;
|
||||||
this.squCoinSelector = new SquCoinSelector(true);
|
this.squCoinSelector = new SquCoinSelector(true);
|
||||||
|
|
||||||
walletsSetup.addSetupCompletedHandler(() -> {
|
walletsSetup.addSetupCompletedHandler(() -> {
|
||||||
|
@ -147,12 +147,12 @@ public class SquWalletService extends WalletService {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void requestSquUtxo(@Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
|
public void requestSquUtxo(@Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
|
||||||
if (blockchainService.isUtxoAvailable()) {
|
if (squBlockchainManager.isUtxoAvailable()) {
|
||||||
applyUtxoSetToUTXOProvider(blockchainService.getUtxoByTxIdMap());
|
applyUtxoSetToUTXOProvider(squBlockchainManager.getUtxoByTxIdMap());
|
||||||
if (resultHandler != null)
|
if (resultHandler != null)
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
} else {
|
} else {
|
||||||
blockchainService.addUtxoListener(utxoByTxIdMap -> {
|
squBlockchainManager.addUtxoListener(utxoByTxIdMap -> {
|
||||||
applyUtxoSetToUTXOProvider(utxoByTxIdMap);
|
applyUtxoSetToUTXOProvider(utxoByTxIdMap);
|
||||||
if (resultHandler != null)
|
if (resultHandler != null)
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
|
|
|
@ -56,6 +56,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
// Derived from WalletAppKit
|
// Derived from WalletAppKit
|
||||||
|
// TODO use same seed for both wallets.
|
||||||
|
// TODo add rolling backup for both wallets
|
||||||
public class WalletConfig extends AbstractIdleService {
|
public class WalletConfig extends AbstractIdleService {
|
||||||
private static final Logger log = LoggerFactory.getLogger(WalletConfig.class);
|
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
|
@Override
|
||||||
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||||
notifyBalanceListeners(tx);
|
notifyBalanceListeners(tx);
|
||||||
|
|
|
@ -20,21 +20,21 @@ package io.bitsquare.dao;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import io.bitsquare.btc.provider.squ.SquUtxoFeedService;
|
import io.bitsquare.btc.provider.squ.SquUtxoFeedService;
|
||||||
import io.bitsquare.btc.wallet.SquWalletService;
|
import io.bitsquare.btc.wallet.SquWalletService;
|
||||||
import io.bitsquare.dao.blockchain.BlockchainException;
|
import io.bitsquare.dao.blockchain.SquBlockchainException;
|
||||||
import io.bitsquare.dao.blockchain.BlockchainService;
|
import io.bitsquare.dao.blockchain.SquBlockchainManager;
|
||||||
import io.bitsquare.dao.compensation.CompensationRequestManager;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class DaoManager {
|
public class DaoManager {
|
||||||
private static final Logger log = LoggerFactory.getLogger(DaoManager.class);
|
private static final Logger log = LoggerFactory.getLogger(DaoManager.class);
|
||||||
|
|
||||||
private final BlockchainService blockchainService;
|
private final SquBlockchainManager squBlockchainManager;
|
||||||
private final SquWalletService squWalletService;
|
private final SquWalletService squWalletService;
|
||||||
private final DaoPeriodService daoPeriodService;
|
private final DaoPeriodService daoPeriodService;
|
||||||
private final SquUtxoFeedService squUtxoFeedService;
|
private final SquUtxoFeedService squUtxoFeedService;
|
||||||
private final VoteManager voteManager;
|
private final VotingManager voteManager;
|
||||||
private final CompensationRequestManager compensationRequestManager;
|
private final CompensationRequestManager compensationRequestManager;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -42,13 +42,13 @@ public class DaoManager {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DaoManager(BlockchainService blockchainService,
|
public DaoManager(SquBlockchainManager squBlockchainManager,
|
||||||
SquWalletService squWalletService,
|
SquWalletService squWalletService,
|
||||||
DaoPeriodService daoPeriodService,
|
DaoPeriodService daoPeriodService,
|
||||||
SquUtxoFeedService squUtxoFeedService,
|
SquUtxoFeedService squUtxoFeedService,
|
||||||
VoteManager voteManager,
|
VotingManager voteManager,
|
||||||
CompensationRequestManager compensationRequestManager) {
|
CompensationRequestManager compensationRequestManager) {
|
||||||
this.blockchainService = blockchainService;
|
this.squBlockchainManager = squBlockchainManager;
|
||||||
this.squWalletService = squWalletService;
|
this.squWalletService = squWalletService;
|
||||||
this.daoPeriodService = daoPeriodService;
|
this.daoPeriodService = daoPeriodService;
|
||||||
this.squUtxoFeedService = squUtxoFeedService;
|
this.squUtxoFeedService = squUtxoFeedService;
|
||||||
|
@ -56,12 +56,12 @@ public class DaoManager {
|
||||||
this.compensationRequestManager = compensationRequestManager;
|
this.compensationRequestManager = compensationRequestManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAllServicesInitialized() throws BlockchainException {
|
public void onAllServicesInitialized() throws SquBlockchainException {
|
||||||
daoPeriodService.onAllServicesInitialized();
|
daoPeriodService.onAllServicesInitialized();
|
||||||
squUtxoFeedService.onAllServicesInitialized();
|
squUtxoFeedService.onAllServicesInitialized();
|
||||||
voteManager.onAllServicesInitialized();
|
voteManager.onAllServicesInitialized();
|
||||||
compensationRequestManager.onAllServicesInitialized();
|
compensationRequestManager.onAllServicesInitialized();
|
||||||
blockchainService.onAllServicesInitialized();
|
squBlockchainManager.onAllServicesInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,13 @@ package io.bitsquare.dao;
|
||||||
|
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import io.bitsquare.app.AppModule;
|
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.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.compensation.CompensationRequestManager;
|
||||||
import io.bitsquare.dao.vote.VoteManager;
|
|
||||||
import io.bitsquare.dao.vote.VotingDefaultValues;
|
import io.bitsquare.dao.vote.VotingDefaultValues;
|
||||||
|
import io.bitsquare.dao.vote.VotingManager;
|
||||||
import io.bitsquare.dao.vote.VotingService;
|
import io.bitsquare.dao.vote.VotingService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -42,12 +43,13 @@ public class DaoModule extends AppModule {
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(DaoManager.class).in(Singleton.class);
|
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(DaoPeriodService.class).in(Singleton.class);
|
||||||
bind(VotingService.class).in(Singleton.class);
|
bind(VotingService.class).in(Singleton.class);
|
||||||
|
|
||||||
bind(CompensationRequestManager.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(DaoService.class).in(Singleton.class);
|
||||||
bind(VotingDefaultValues.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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class SquBlock {
|
public class SquBlock {
|
||||||
private static final Logger log = LoggerFactory.getLogger(SquBlock.class);
|
private static final Logger log = LoggerFactory.getLogger(SquBlock.class);
|
||||||
|
|
||||||
public final List<String> txIds;
|
|
||||||
public final int blockHeight;
|
public final int blockHeight;
|
||||||
|
public final List<String> txIds;
|
||||||
|
|
||||||
|
private Map<String, SquTransaction> squTransactions = new HashMap<>();
|
||||||
|
|
||||||
public SquBlock(List<String> txIds, int blockHeight) {
|
public SquBlock(List<String> txIds, int blockHeight) {
|
||||||
this.txIds = txIds;
|
this.txIds = txIds;
|
||||||
this.blockHeight = blockHeight;
|
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;
|
package io.bitsquare.dao.blockchain;
|
||||||
|
|
||||||
public class BlockchainException extends Exception {
|
public class SquBlockchainException extends Exception {
|
||||||
public BlockchainException(String message) {
|
public SquBlockchainException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockchainException(String message, Throwable cause) {
|
public SquBlockchainException(String message, Throwable cause) {
|
||||||
super(message, 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.client.BtcdClientImpl;
|
||||||
import com.neemre.btcdcli4j.core.domain.Block;
|
import com.neemre.btcdcli4j.core.domain.Block;
|
||||||
import com.neemre.btcdcli4j.core.domain.RawTransaction;
|
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.BtcdDaemonImpl;
|
||||||
import com.neemre.btcdcli4j.daemon.event.BlockListener;
|
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.ErrorMessageHandler;
|
||||||
import io.bitsquare.common.handlers.ResultHandler;
|
import io.bitsquare.common.handlers.ResultHandler;
|
||||||
|
import io.bitsquare.common.util.Tuple2;
|
||||||
import io.bitsquare.common.util.Utilities;
|
import io.bitsquare.common.util.Utilities;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
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 com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static org.bitcoinj.core.Utils.HEX;
|
import static org.bitcoinj.core.Utils.HEX;
|
||||||
|
|
||||||
public class BlockchainRpcService extends BlockchainService {
|
public class SquBlockchainRpcService extends SquBlockchainService {
|
||||||
private static final Logger log = LoggerFactory.getLogger(BlockchainRpcService.class);
|
private static final Logger log = LoggerFactory.getLogger(SquBlockchainRpcService.class);
|
||||||
|
|
||||||
private final String rpcUser;
|
private final String rpcUser;
|
||||||
private final String rpcPassword;
|
private final String rpcPassword;
|
||||||
|
@ -75,11 +74,11 @@ public class BlockchainRpcService extends BlockchainService {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public BlockchainRpcService(@Named(RpcOptionKeys.RPC_USER) String rpcUser,
|
public SquBlockchainRpcService(@Named(RpcOptionKeys.RPC_USER) String rpcUser,
|
||||||
@Named(RpcOptionKeys.RPC_PASSWORD) String rpcPassword,
|
@Named(RpcOptionKeys.RPC_PASSWORD) String rpcPassword,
|
||||||
@Named(RpcOptionKeys.RPC_PORT) String rpcPort,
|
@Named(RpcOptionKeys.RPC_PORT) String rpcPort,
|
||||||
@Named(RpcOptionKeys.RPC_BLOCK_PORT) String rpcBlockPort,
|
@Named(RpcOptionKeys.RPC_BLOCK_PORT) String rpcBlockPort,
|
||||||
@Named(RpcOptionKeys.RPC_WALLET_PORT) String rpcWalletPort) {
|
@Named(RpcOptionKeys.RPC_WALLET_PORT) String rpcWalletPort) {
|
||||||
this.rpcUser = rpcUser;
|
this.rpcUser = rpcUser;
|
||||||
this.rpcPassword = rpcPassword;
|
this.rpcPassword = rpcPassword;
|
||||||
this.rpcPort = rpcPort;
|
this.rpcPort = rpcPort;
|
||||||
|
@ -93,7 +92,7 @@ public class BlockchainRpcService extends BlockchainService {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setup(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
void setup(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
ListenableFuture<BtcdClientImpl> future = setupExecutorService.submit(() -> {
|
ListenableFuture<BtcdClientImpl> future = setupExecutorService.submit(() -> {
|
||||||
long startTs = System.currentTimeMillis();
|
long startTs = System.currentTimeMillis();
|
||||||
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
|
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
|
||||||
|
@ -114,16 +113,16 @@ public class BlockchainRpcService extends BlockchainService {
|
||||||
log.info("Setup took {} ms", System.currentTimeMillis() - startTs);
|
log.info("Setup took {} ms", System.currentTimeMillis() - startTs);
|
||||||
return client;
|
return client;
|
||||||
} catch (IOException | BitcoindException | CommunicationException e) {
|
} catch (IOException | BitcoindException | CommunicationException e) {
|
||||||
throw new BlockchainException(e.getMessage(), e);
|
throw new SquBlockchainException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
} catch (URISyntaxException | IOException e) {
|
} catch (URISyntaxException | IOException e) {
|
||||||
throw new BlockchainException(e.getMessage(), e);
|
throw new SquBlockchainException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Futures.addCallback(future, new FutureCallback<BtcdClientImpl>() {
|
Futures.addCallback(future, new FutureCallback<BtcdClientImpl>() {
|
||||||
public void onSuccess(BtcdClientImpl client) {
|
public void onSuccess(BtcdClientImpl client) {
|
||||||
BlockchainRpcService.this.client = client;
|
SquBlockchainRpcService.this.client = client;
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,83 +133,61 @@ public class BlockchainRpcService extends BlockchainService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void syncFromGenesis(Consumer<Map<String, Map<Integer, SquUTXO>>> resultHandler, ErrorMessageHandler errorMessageHandler) {
|
protected ListenableFuture<Tuple2<Map<String, Map<Integer, SquUTXO>>, Integer>> syncFromGenesis(int genesisBlockHeight, String genesisTxId) {
|
||||||
ListenableFuture<Map<String, Map<Integer, SquUTXO>>> future = rpcRequestsExecutor.submit(() -> {
|
return rpcRequestsExecutor.submit(() -> {
|
||||||
long startTs = System.currentTimeMillis();
|
long startTs = System.currentTimeMillis();
|
||||||
Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap = new HashMap<>();
|
Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap = new HashMap<>();
|
||||||
try {
|
int chainHeadHeight = requestChainHeadHeight();
|
||||||
parseBlockchainFromGenesis(utxoByTxIdMap, GENESIS_BLOCK_HEIGHT, GENESIS_TX_ID);
|
parseBlockchain(utxoByTxIdMap, chainHeadHeight, genesisBlockHeight, genesisTxId);
|
||||||
} catch (BlockchainException e) {
|
|
||||||
throw new BlockchainException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
log.info("syncFromGenesis took {} ms", System.currentTimeMillis() - startTs);
|
log.info("syncFromGenesis took {} ms", System.currentTimeMillis() - startTs);
|
||||||
return utxoByTxIdMap;
|
return new Tuple2<>(utxoByTxIdMap, chainHeadHeight);
|
||||||
});
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void syncFromGenesisCompete() {
|
protected void syncFromGenesisCompete(String genesisTxId, int genesisBlockHeight, Consumer<Block> onNewBlockHandler) {
|
||||||
daemon.addBlockListener(new BlockListener() {
|
daemon.addBlockListener(new BlockListener() {
|
||||||
@Override
|
@Override
|
||||||
public void blockDetected(Block block) {
|
public void blockDetected(Block block) {
|
||||||
log.info("blockDetected " + block.getHash());
|
log.info("blockDetected " + block.getHash());
|
||||||
parseBlock(new SquBlock(block.getTx(), block.getHeight()), GENESIS_TX_ID, utxoByTxIdMap);
|
if (onNewBlockHandler != null)
|
||||||
printUtxoMap(utxoByTxIdMap);
|
onNewBlockHandler.accept(block);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
daemon.addWalletListener(new WalletListener() {
|
/* daemon.addWalletListener(new WalletListener() {
|
||||||
@Override
|
@Override
|
||||||
public void walletChanged(Transaction transaction) {
|
public void walletChanged(Transaction transaction) {
|
||||||
log.info("walletChanged " + transaction.getTxId());
|
log.info("walletChanged " + transaction.getTxId());
|
||||||
try {
|
*//* try {
|
||||||
parseTransaction(transaction.getTxId(), GENESIS_TX_ID, client.getBlockCount(), utxoByTxIdMap);
|
// parseTransaction(transaction.getTxId(), GENESIS_TX_ID, client.getBlockCount(), tempUtxoByTxIdMap, utxoByTxIdMap);
|
||||||
printUtxoMap(utxoByTxIdMap);
|
printUtxoMap(utxoByTxIdMap);
|
||||||
} catch (BitcoindException | CommunicationException e) {
|
} catch (BitcoindException | CommunicationException | SquBlockchainException e) {
|
||||||
|
//TODO
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}*//*
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void parseBlockchainFromGenesis(Map<String, Map<Integer, SquUTXO>> utxoByTxIdMap, int genesisBlockHeight, String genesisTxId) throws BlockchainException {
|
int requestChainHeadHeight() throws BitcoindException, CommunicationException {
|
||||||
try {
|
return client.getBlockCount();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 {
|
try {
|
||||||
RawTransaction rawTransaction = (RawTransaction) client.getRawTransaction(txId, 1);
|
RawTransaction rawTransaction = getRawTransaction(txId);
|
||||||
return new SquTransaction(txId,
|
return new SquTransaction(txId,
|
||||||
rawTransaction.getVIn()
|
rawTransaction.getVIn()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(e -> e != null && e.getVOut() != null && e.getTxId() != null)
|
.filter(rawInput -> rawInput != null && rawInput.getVOut() != null && rawInput.getTxId() != null)
|
||||||
.map(e -> new SquTxInput(e.getVOut(), e.getTxId()))
|
.map(rawInput -> new SquTxInput(rawInput.getVOut(), rawInput.getTxId(), rawTransaction.getHex()))
|
||||||
.collect(Collectors.toList()),
|
.collect(Collectors.toList()),
|
||||||
rawTransaction.getVOut()
|
rawTransaction.getVOut()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -218,10 +195,14 @@ public class BlockchainRpcService extends BlockchainService {
|
||||||
.map(e -> new SquTxOutput(e.getN(),
|
.map(e -> new SquTxOutput(e.getN(),
|
||||||
Coin.valueOf(e.getValue().movePointRight(8).longValue()),
|
Coin.valueOf(e.getValue().movePointRight(8).longValue()),
|
||||||
e.getScriptPubKey().getAddresses(),
|
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()));
|
.collect(Collectors.toList()));
|
||||||
} catch (BitcoindException | CommunicationException e) {
|
} 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;
|
import java.io.File;
|
||||||
|
|
||||||
public class BlockchainRpcServiceMain {
|
public class SquBlockchainRpcServiceMain {
|
||||||
private static final Logger log = LoggerFactory.getLogger(BlockchainRpcServiceMain.class);
|
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.setup(System.getProperty("user.home") + File.separator + "BlockchainRpcServiceMain");
|
||||||
Log.setLevel(Level.WARN);
|
Log.setLevel(Level.WARN);
|
||||||
|
|
||||||
// regtest uses port 18332, mainnet 8332
|
// regtest uses port 18332, mainnet 8332
|
||||||
BlockchainRpcService blockchainRpcService = new BlockchainRpcService(args[0], args[1], args[2], args[3], args[4]);
|
SquBlockchainRpcService blockchainRpcService = new SquBlockchainRpcService(args[0], args[1], args[2], args[3], args[4]);
|
||||||
blockchainRpcService.onAllServicesInitialized();
|
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.inputs = inputs;
|
||||||
this.outputs = outputs;
|
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 {
|
public class SquTxInput {
|
||||||
private static final Logger log = LoggerFactory.getLogger(SquTxInput.class);
|
private static final Logger log = LoggerFactory.getLogger(SquTxInput.class);
|
||||||
|
|
||||||
public final int index;
|
public final int spendingOuptuIndex;
|
||||||
public final String spendingTxId;
|
public final String spendingTxId;
|
||||||
|
public final String txId;
|
||||||
|
|
||||||
public SquTxInput(int index, String spendingTxId) {
|
public SquTxInput(int spendingOuptuIndex, String spendingTxId, String txId) {
|
||||||
this.index = index;
|
this.spendingOuptuIndex = spendingOuptuIndex;
|
||||||
this.spendingTxId = spendingTxId;
|
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.addresses = addresses;
|
||||||
this.script = script;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
public class SquUTXO extends UTXO {
|
||||||
private static final Logger log = LoggerFactory.getLogger(SquUTXO.class);
|
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;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
public class VoteManager {
|
public class VotingManager {
|
||||||
private static final Logger log = LoggerFactory.getLogger(VoteManager.class);
|
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_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=";
|
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;
|
private VoteItemsList activeVoteItemsList;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VoteManager(BtcWalletService btcWalletService,
|
public VotingManager(BtcWalletService btcWalletService,
|
||||||
SquWalletService squWalletService,
|
SquWalletService squWalletService,
|
||||||
FeeService feeService,
|
FeeService feeService,
|
||||||
Storage<ArrayList<VoteItemsList>> voteItemCollectionsStorage,
|
Storage<ArrayList<VoteItemsList>> voteItemCollectionsStorage,
|
||||||
CompensationRequestManager compensationRequestManager,
|
CompensationRequestManager compensationRequestManager,
|
||||||
DaoPeriodService daoPeriodService,
|
DaoPeriodService daoPeriodService,
|
||||||
VotingDefaultValues votingDefaultValues) {
|
VotingDefaultValues votingDefaultValues) {
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
this.squWalletService = squWalletService;
|
this.squWalletService = squWalletService;
|
||||||
this.feeService = feeService;
|
this.feeService = feeService;
|
||||||
|
@ -81,7 +81,7 @@ public class VoteManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
VoteManager(VotingDefaultValues votingDefaultValues) {
|
VotingManager(VotingDefaultValues votingDefaultValues) {
|
||||||
this.btcWalletService = null;
|
this.btcWalletService = null;
|
||||||
this.squWalletService = null;
|
this.squWalletService = null;
|
||||||
this.feeService = null;
|
this.feeService = null;
|
|
@ -40,6 +40,8 @@ public enum VotingType {
|
||||||
|
|
||||||
COMP_REQUEST_MAPS((byte) 0x50);
|
COMP_REQUEST_MAPS((byte) 0x50);
|
||||||
|
|
||||||
|
// TODO max growth rate of SQU
|
||||||
|
|
||||||
public final Byte code;
|
public final Byte code;
|
||||||
|
|
||||||
VotingType(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
|
@Test
|
||||||
public void testGetVoteItemListFromOpReturnData() {
|
public void testGetVoteItemListFromOpReturnData() {
|
||||||
VoteManager votingManager = new VoteManager(new VotingDefaultValues());
|
VotingManager votingManager = new VotingManager(new VotingDefaultValues());
|
||||||
byte[] opReturnData;
|
byte[] opReturnData;
|
||||||
VoteItemsList voteItemsList;
|
VoteItemsList voteItemsList;
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ public class VoteManagerTest {
|
||||||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||||
fail("Expected an IllegalArgumentException to be thrown");
|
fail("Expected an IllegalArgumentException to be thrown");
|
||||||
} catch (IllegalArgumentException e) {
|
} 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
|
// compensation requests set but no votes
|
||||||
|
@ -77,7 +77,7 @@ public class VoteManagerTest {
|
||||||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||||
fail("Expected an IllegalArgumentException to be thrown");
|
fail("Expected an IllegalArgumentException to be thrown");
|
||||||
} catch (IllegalArgumentException e) {
|
} 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
|
// has voted on item 2, but we found a accepted flag at item 1
|
||||||
|
@ -90,7 +90,7 @@ public class VoteManagerTest {
|
||||||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||||
fail("Expected an IllegalArgumentException to be thrown");
|
fail("Expected an IllegalArgumentException to be thrown");
|
||||||
} catch (IllegalArgumentException e) {
|
} 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
|
// has voted on all and are declined
|
||||||
|
@ -120,7 +120,7 @@ public class VoteManagerTest {
|
||||||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||||
fail("Expected an IllegalArgumentException to be thrown");
|
fail("Expected an IllegalArgumentException to be thrown");
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
assertThat(e.getMessage(), is(VoteManager.ERROR_MSG_MISSING_BYTE));
|
assertThat(e.getMessage(), is(VotingManager.ERROR_MSG_MISSING_BYTE));
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrong length
|
// wrong length
|
||||||
|
@ -134,7 +134,7 @@ public class VoteManagerTest {
|
||||||
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
votingManager.getVoteItemListFromOpReturnData(opReturnData);
|
||||||
fail("Expected an IllegalArgumentException to be thrown");
|
fail("Expected an IllegalArgumentException to be thrown");
|
||||||
} catch (IllegalArgumentException e) {
|
} 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
|
// Invalid param vote 255 is not allowed only 0-254
|
||||||
|
@ -177,7 +177,7 @@ public class VoteManagerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCalculateHash() {
|
public void testCalculateHash() {
|
||||||
VoteManager votingManager = new VoteManager(new VotingDefaultValues());
|
VotingManager votingManager = new VotingManager(new VotingDefaultValues());
|
||||||
|
|
||||||
// assertEquals(10, votingManager.calculateHash(100, 0));
|
// assertEquals(10, votingManager.calculateHash(100, 0));
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
<!-- exclude signatures, the bundling process breaks them for some reason -->
|
<!-- exclude signatures, the bundling process breaks them for some reason -->
|
||||||
<artifact>*:*</artifact>
|
<artifact>*:*</artifact>
|
||||||
<excludes>
|
<excludes>
|
||||||
|
<exclude>META-INF/maven/**/pom.properties</exclude>
|
||||||
<exclude>META-INF/*.SF</exclude>
|
<exclude>META-INF/*.SF</exclude>
|
||||||
<exclude>META-INF/*.DSA</exclude>
|
<exclude>META-INF/*.DSA</exclude>
|
||||||
<exclude>META-INF/*.RSA</exclude>
|
<exclude>META-INF/*.RSA</exclude>
|
||||||
|
|
|
@ -42,7 +42,7 @@ import io.bitsquare.common.Timer;
|
||||||
import io.bitsquare.common.UserThread;
|
import io.bitsquare.common.UserThread;
|
||||||
import io.bitsquare.common.crypto.*;
|
import io.bitsquare.common.crypto.*;
|
||||||
import io.bitsquare.dao.DaoManager;
|
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.filter.FilterManager;
|
||||||
import io.bitsquare.gui.Navigation;
|
import io.bitsquare.gui.Navigation;
|
||||||
import io.bitsquare.gui.common.model.ViewModel;
|
import io.bitsquare.gui.common.model.ViewModel;
|
||||||
|
@ -551,7 +551,7 @@ public class MainViewModel implements ViewModel {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
daoManager.onAllServicesInitialized();
|
daoManager.onAllServicesInitialized();
|
||||||
} catch (BlockchainException e) {
|
} catch (SquBlockchainException e) {
|
||||||
new Popup<>().error(e.toString()).show();
|
new Popup<>().error(e.toString()).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ public class VoteView extends ActivatableView<GridPane, Void> {
|
||||||
private FeeService feeService;
|
private FeeService feeService;
|
||||||
private SQUFormatter squFormatter;
|
private SQUFormatter squFormatter;
|
||||||
private BSFormatter btcFormatter;
|
private BSFormatter btcFormatter;
|
||||||
private VoteManager voteManager;
|
private VotingManager voteManager;
|
||||||
private Button voteButton;
|
private Button voteButton;
|
||||||
private List<CompensationRequest> compensationRequests;
|
private List<CompensationRequest> compensationRequests;
|
||||||
private TitledGroupBg compensationRequestsTitledGroupBg, parametersTitledGroupBg;
|
private TitledGroupBg compensationRequestsTitledGroupBg, parametersTitledGroupBg;
|
||||||
|
@ -94,7 +94,7 @@ public class VoteView extends ActivatableView<GridPane, Void> {
|
||||||
@Inject
|
@Inject
|
||||||
private VoteView(CompensationRequestManager compensationRequestManager, SquWalletService squWalletService,
|
private VoteView(CompensationRequestManager compensationRequestManager, SquWalletService squWalletService,
|
||||||
BtcWalletService btcWalletService, FeeService feeService, SQUFormatter squFormatter,
|
BtcWalletService btcWalletService, FeeService feeService, SQUFormatter squFormatter,
|
||||||
BSFormatter btcFormatter, VoteManager voteManager) {
|
BSFormatter btcFormatter, VotingManager voteManager) {
|
||||||
this.compensationRequestManager = compensationRequestManager;
|
this.compensationRequestManager = compensationRequestManager;
|
||||||
this.squWalletService = squWalletService;
|
this.squWalletService = squWalletService;
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
|
|
|
@ -17,17 +17,17 @@
|
||||||
|
|
||||||
package io.bitsquare.gui.main.dao.wallet;
|
package io.bitsquare.gui.main.dao.wallet;
|
||||||
|
|
||||||
import io.bitsquare.btc.listeners.BalanceListener;
|
|
||||||
import io.bitsquare.btc.wallet.SquWalletService;
|
import io.bitsquare.btc.wallet.SquWalletService;
|
||||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||||
import io.bitsquare.gui.util.SQUFormatter;
|
import io.bitsquare.gui.util.SQUFormatter;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.*;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.script.Script;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class BalanceUtil {
|
public class BalanceUtil {
|
||||||
private static final Logger log = LoggerFactory.getLogger(BalanceUtil.class);
|
private static final Logger log = LoggerFactory.getLogger(BalanceUtil.class);
|
||||||
|
@ -35,7 +35,7 @@ public class BalanceUtil {
|
||||||
private final SquWalletService squWalletService;
|
private final SquWalletService squWalletService;
|
||||||
private final SQUFormatter formatter;
|
private final SQUFormatter formatter;
|
||||||
private TextField balanceTextField;
|
private TextField balanceTextField;
|
||||||
private BalanceListener balanceListener;
|
private WalletEventListener walletEventListener;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor, lifecycle
|
// Constructor, lifecycle
|
||||||
|
@ -52,31 +52,59 @@ public class BalanceUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
balanceListener = new BalanceListener() {
|
walletEventListener = new WalletEventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onBalanceChanged(Coin balance, Transaction tx) {
|
public void onKeysAdded(List<ECKey> keys) {
|
||||||
updateBalance(balance);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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(() -> {
|
squWalletService.requestSquUtxo(() -> {
|
||||||
updateBalance(squWalletService.getAvailableBalance());
|
balanceTextField.setText(formatter.formatCoinWithCode(squWalletService.getAvailableBalance()));
|
||||||
}, errorMessage -> {
|
}, errorMessage -> {
|
||||||
new Popup<>().warning(errorMessage);
|
new Popup<>().warning(errorMessage);
|
||||||
});
|
});
|
||||||
squWalletService.addBalanceListener(balanceListener);
|
}
|
||||||
|
|
||||||
updateBalance(squWalletService.getAvailableBalance());
|
public void activate() {
|
||||||
|
requestUtxo();
|
||||||
|
squWalletService.addEventListener(walletEventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deactivate() {
|
public void deactivate() {
|
||||||
squWalletService.removeBalanceListener(balanceListener);
|
squWalletService.removeEventListener(walletEventListener);
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBalance(Coin balance) {
|
|
||||||
balanceTextField.setText(formatter.formatCoinWithCode(balance));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ public class TokenSendView extends ActivatableView<GridPane, Void> {
|
||||||
Coin receiverAmount = squFormatter.parseToCoin(amountInputTextField.getText());
|
Coin receiverAmount = squFormatter.parseToCoin(amountInputTextField.getText());
|
||||||
try {
|
try {
|
||||||
Transaction preparedSendTx = squWalletService.getPreparedSendTx(receiversAddressString, receiverAmount);
|
Transaction preparedSendTx = squWalletService.getPreparedSendTx(receiversAddressString, receiverAmount);
|
||||||
Transaction txWithBtcFee = btcWalletService.completePreparedSquTx(preparedSendTx, true, null);
|
Transaction txWithBtcFee = btcWalletService.completePreparedSendSquTx(preparedSendTx, true);
|
||||||
Transaction signedTx = squWalletService.signTx(txWithBtcFee);
|
Transaction signedTx = squWalletService.signTx(txWithBtcFee);
|
||||||
|
|
||||||
Coin miningFee = signedTx.getFee();
|
Coin miningFee = signedTx.getFee();
|
||||||
|
|
|
@ -730,6 +730,7 @@ public class Connection implements MessageListener {
|
||||||
|
|
||||||
Object rawInputObject = objectInputStream.readObject();
|
Object rawInputObject = objectInputStream.readObject();
|
||||||
|
|
||||||
|
|
||||||
// Throttle inbound messages
|
// Throttle inbound messages
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long elapsed = now - lastReadTimeStamp;
|
long elapsed = now - lastReadTimeStamp;
|
||||||
|
|
|
@ -5,7 +5,7 @@ mkdir -p gui/deploy
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
version="0.4.9.8"
|
version="0.5.0.0"
|
||||||
|
|
||||||
mvn clean package -DskipTests -Dmaven.javadoc.skip=true
|
mvn clean package -DskipTests -Dmaven.javadoc.skip=true
|
||||||
|
|
||||||
|
|
6
pom.xml
6
pom.xml
|
@ -160,6 +160,12 @@
|
||||||
<artifactId>jsr305</artifactId>
|
<artifactId>jsr305</artifactId>
|
||||||
<version>3.0.1</version>
|
<version>3.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.16.12</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!--logging-->
|
<!--logging-->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
Loading…
Add table
Reference in a new issue