Add spend infor to tx outputs. adopt json for txo. add custom encoding for bsq addresses.

This commit is contained in:
Manfred Karrer 2017-04-10 00:04:46 -05:00
parent 2f865aa863
commit 30df6f9721
17 changed files with 514 additions and 154 deletions

View file

@ -43,6 +43,8 @@ public class Version {
public static final int TRADE_PROTOCOL_VERSION = 1;
private static int p2pMessageVersion;
public static final String BSQ_TX_VERSION = "1";
public static int getP2PMessageVersion() {
// TODO investigate why a changed NETWORK_PROTOCOL_VERSION for the serialized objects does not trigger
// reliable a disconnect., but java serialisation should be replaced anyway, so using one existing field

View file

@ -21,18 +21,17 @@ 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 com.neemre.btcdcli4j.core.domain.PubKeyScript;
import io.bisq.common.UserThread;
import io.bisq.common.handlers.ErrorMessageHandler;
import io.bisq.common.storage.FileManager;
import io.bisq.common.storage.PlainTextWrapper;
import io.bisq.common.storage.Storage;
import io.bisq.common.util.MathUtils;
import io.bisq.common.util.Utilities;
import io.bisq.core.app.BisqEnvironment;
import io.bisq.core.btc.BitcoinNetwork;
import io.bisq.core.dao.RpcOptionKeys;
import io.bisq.core.dao.blockchain.json.ScriptPubKeyJson;
import io.bisq.core.dao.blockchain.json.SpentInfoJson;
import io.bisq.core.dao.blockchain.json.ScriptPubKeyForJson;
import io.bisq.core.dao.blockchain.json.SpentInfoForJson;
import io.bisq.core.dao.blockchain.json.TxOutputForJson;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
@ -41,10 +40,10 @@ import org.slf4j.LoggerFactory;
import javax.inject.Named;
import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class BsqBlockchainManager {
private static final Logger log = LoggerFactory.getLogger(BsqBlockchainManager.class);
@ -86,6 +85,7 @@ public class BsqBlockchainManager {
private int chainHeadHeight;
@Getter
private boolean isUtxoSyncWithChainHeadHeight;
private final Storage<PlainTextWrapper> jsonStorage;
///////////////////////////////////////////////////////////////////////////////////////////
@ -96,9 +96,11 @@ public class BsqBlockchainManager {
public BsqBlockchainManager(BsqBlockchainService blockchainService,
BisqEnvironment bisqEnvironment,
@Named(Storage.DIR_KEY) File storageDir,
Storage<PlainTextWrapper> jsonStorage,
@Named(RpcOptionKeys.DUMP_BLOCKCHAIN_DATA) boolean dumpBlockchainData) {
this.blockchainService = blockchainService;
this.storageDir = storageDir;
this.jsonStorage = jsonStorage;
this.dumpBlockchainData = dumpBlockchainData;
this.bitcoinNetwork = bisqEnvironment.getBitcoinNetwork();
@ -110,7 +112,7 @@ public class BsqBlockchainManager {
bsqTXOMap.addBurnedBSQTxMapListener(c -> onBsqTXOChanged());
if (dumpBlockchainData) {
this.jsonStorage.initWithFileName("txo.json");
/* p2PService.addP2PServiceListener(new BootstrapListener() {
@Override
public void onBootstrapComplete() {
@ -132,63 +134,67 @@ public class BsqBlockchainManager {
}
private void doDumpBlockchainData() {
bsqTXOMap.getMap().values().stream()
.forEach(txOutput -> {
final boolean coinBase = false;
final int height = -1;
final int index = txOutput.getIndex();
final boolean invalid = false;
final int n = index;
final int output_index = index;
List<TxOutputForJson> list = bsqTXOMap.getMap().values().stream()
.map(this::getTxOutputForJson)
.collect(Collectors.toList());
final ScriptPubKeyJson scriptPubKey = new ScriptPubKeyJson(txOutput.getAddresses(),
"asm",
"hex",
-1,
"type");
final SpentInfoJson spent_info = new SpentInfoJson(-1, -1, "txId");
list.sort((o1, o2) -> (o1.getSortData().compareTo(o2.getSortData())));
TxOutputForJson[] array = new TxOutputForJson[list.size()];
list.toArray(array);
jsonStorage.queueUpForSave(new PlainTextWrapper(Utilities.objectToJson(array)), 5000);
final long squ_amount = txOutput.getValue();
final String status = "?";
final String transaction_version = "?";
final long tx_time = -1;
final String tx_type_str = "?";
final String txid = txOutput.getTxId();
final boolean validated = false;
final double value = MathUtils.scaleDownByPowerOf10(squ_amount, 8);
final long valueSat = squ_amount;
// keep the individual file storage option as code as we dont know yet what we will use.
/* log.error("txOutputForJson " + txOutputForJson);
File txoDir = new File(Paths.get(storageDir.getAbsolutePath(), "txo").toString());
if (!txoDir.exists())
if (!txoDir.mkdir())
log.warn("make txoDir failed.\ntxoDir=" + txoDir.getAbsolutePath());
File txoFile = new File(Paths.get(txoDir.getAbsolutePath(),
txOutput.getTxId() + ":" + outputIndex + ".json").toString());
// TODO WIP...
TxOutputForJson txOutputForJson = new TxOutputForJson(coinBase,
height,
index,
invalid,
n,
output_index,
scriptPubKey,
spent_info,
squ_amount,
status,
transaction_version,
tx_time,
tx_type_str,
txid,
validated,
value,
valueSat
);
// log.error("txOutputForJson " + txOutputForJson);
File txoDir = new File(Paths.get(storageDir.getAbsolutePath(), "txo").toString());
if (!txoDir.exists())
if (!txoDir.mkdir())
log.warn("make txoDir failed.\ntxoDir=" + txoDir.getAbsolutePath());
File txoFile = new File(Paths.get(txoDir.getAbsolutePath(), txid + ":" + index + ".json").toString());
// Nr of write requests might be a bit heavy, consider write whole list to one file
FileManager<PlainTextWrapper> fileManager = new FileManager<>(storageDir, txoFile, 1);
fileManager.saveLater(new PlainTextWrapper(Utilities.objectToJson(txOutputForJson)));*/
}
// Nr of write requests might be a bit heavy, consider write whole list to one file
FileManager<PlainTextWrapper> fileManager = new FileManager<>(storageDir, txoFile, 1);
fileManager.saveLater(new PlainTextWrapper(Utilities.objectToJson(txOutputForJson)));
});
private TxOutputForJson getTxOutputForJson(TxOutput txOutput) {
String txId = txOutput.getTxId();
int outputIndex = txOutput.getIndex();
final long bsqAmount = txOutput.getValue();
final int height = txOutput.getBlockHeight();
final boolean isBsqCoinBase = txOutput.isBsqCoinBase();
final boolean verified = txOutput.isVerified();
final long burnedFee = txOutput.getBurnedFee();
final long btcTxFee = txOutput.getBtcTxFee();
PubKeyScript pubKeyScript = txOutput.getPubKeyScript();
final ScriptPubKeyForJson scriptPubKey = new ScriptPubKeyForJson(pubKeyScript.getAddresses(),
pubKeyScript.getAsm(),
pubKeyScript.getHex(),
pubKeyScript.getReqSigs(),
pubKeyScript.getType().toString());
SpentInfoForJson spentInfoJson = null;
SpendInfo spendInfo = txOutput.getSpendInfo();
if (spendInfo != null)
spentInfoJson = new SpentInfoForJson(spendInfo.getBlockHeight(),
spendInfo.getInputIndex(),
spendInfo.getTxId());
final long time = txOutput.getTime();
final String txVersion = txOutput.getTxVersion();
return new TxOutputForJson(txId,
outputIndex,
bsqAmount,
height,
isBsqCoinBase,
verified,
burnedFee,
btcTxFee,
scriptPubKey,
spentInfoJson,
time,
txVersion
);
}
private void onBsqUTXOChanged() {

View file

@ -37,7 +37,6 @@ import io.bisq.core.dao.RpcOptionKeys;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.bitcoinj.script.Script;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -51,7 +50,6 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.bitcoinj.core.Utils.HEX;
public class BsqBlockchainRpcService extends BsqBlockchainService {
private static final Logger log = LoggerFactory.getLogger(BsqBlockchainRpcService.class);
@ -198,35 +196,27 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
}
@Override
Tx requestTransaction(String txId) throws BsqBlockchainException {
Tx requestTransaction(String txId, int blockHeight) throws BsqBlockchainException {
try {
RawTransaction rawTransaction = getRawTransaction(txId);
final long time = rawTransaction.getTime() * 1000;
final List<TxInput> txInputs = rawTransaction.getVIn()
.stream()
.filter(rawInput -> rawInput != null && rawInput.getVOut() != null && rawInput.getTxId() != null)
.map(rawInput -> new TxInput(rawInput.getVOut(), rawInput.getTxId(), rawTransaction.getHex()))
.collect(Collectors.toList());
final List<TxOutput> txOutputs = rawTransaction.getVOut()
.stream()
.filter(e -> e != null && e.getN() != null && e.getValue() != null && e.getScriptPubKey() != null)
.map(e -> {
byte[] scriptProgramBytes = new byte[]{};
try {
scriptProgramBytes = e.getScriptPubKey().getHex() != null ?
new Script(HEX.decode(e.getScriptPubKey().getHex())).getProgram() : new byte[]{};
} catch (Throwable t) {
// expected for tx: 0f8e9655b29d76c5e01147704a64faf95a9c90433baacdd39d4c1d2667e570a2
log.error(e.getScriptPubKey().getAsm());
log.error(e.getScriptPubKey().getHex());
log.error(e.getScriptPubKey().toString());
log.error(t.toString());
t.printStackTrace();
}
return new TxOutput(e.getN(),
e.getValue().movePointRight(8).longValue(),
e.getScriptPubKey().getAddresses(),
.map(rawOutput -> {
return new TxOutput(rawOutput.getN(),
rawOutput.getValue().movePointRight(8).longValue(),
rawTransaction.getTxId(),
scriptProgramBytes);
rawOutput.getScriptPubKey(),
blockHeight,
time);
})
.collect(Collectors.toList());
@ -234,7 +224,7 @@ public class BsqBlockchainRpcService extends BsqBlockchainService {
return new Tx(txId,
txInputs,
txOutputs,
rawTransaction.getTime() * 1000);
time);
} catch (BitcoindException | CommunicationException e) {
throw new BsqBlockchainException(e.getMessage(), e);
}

View file

@ -71,7 +71,7 @@ abstract public class BsqBlockchainService {
abstract Block requestBlock(int i) throws BitcoindException, CommunicationException;
abstract Tx requestTransaction(String txId) throws BsqBlockchainException;
abstract Tx requestTransaction(String txId, int blockHeight) throws BsqBlockchainException;
@VisibleForTesting
void parseBlockchain(BsqUTXOMap bsqUTXOMap,
@ -138,7 +138,7 @@ abstract public class BsqBlockchainService {
List<String> txIds = block.getTxIds();
Tx genesisTx = null;
for (String txId : txIds) {
final Tx tx = requestTransaction(txId);
final Tx tx = requestTransaction(txId, blockHeight);
block.addTx(tx);
if (txId.equals(genesisTxId))
genesisTx = tx;
@ -270,12 +270,19 @@ abstract public class BsqBlockchainService {
long availableValue = 0;
long totalAvailableBSQInputs = 0;
long totalSpendBSQOutputs = 0;
for (TxInput input : tx.getInputs()) {
final String txId = tx.getId();
for (int inputIndex = 0; inputIndex < tx.getInputs().size(); inputIndex++) {
TxInput input = tx.getInputs().get(inputIndex);
String spendingTxId = input.getSpendingTxId();
final int spendingTxOutputIndex = input.getSpendingTxOutputIndex();
if (bsqTXOMap.containsTuple(spendingTxId, spendingTxOutputIndex)) {
TxOutput txOutputFromSpendingTx = bsqTXOMap.getByTuple(spendingTxId, spendingTxOutputIndex);
txOutputFromSpendingTx.setSpendInfo(new SpendInfo(blockHeight, txId, inputIndex));
}
if (bsqUTXOMap.containsTuple(spendingTxId, spendingTxOutputIndex)) {
BsqUTXO bsqUTXO = bsqUTXOMap.getByTuple(spendingTxId, spendingTxOutputIndex);
log.debug("input value " + bsqUTXO.getValue());
availableValue = availableValue + bsqUTXO.getValue();
totalAvailableBSQInputs += bsqUTXO.getValue();
bsqUTXOMap.removeByTuple(spendingTxId, spendingTxOutputIndex);
@ -292,14 +299,8 @@ abstract public class BsqBlockchainService {
for (TxOutput txOutput : outputs) {
availableValue = availableValue - txOutput.getValue();
if (availableValue >= 0) {
if (txOutput.getAddresses().size() != 1) {
final String msg = "We got a address list with more or less than 1 address for a BsqUTXO. " +
"Seems to be a raw MS. Raw MS are not supported with BSQ.\n" + this.toString();
log.warn(msg);
if (DevEnv.DEV_MODE)
throw new RuntimeException(msg);
}
// We are spending available tokens
txOutput.setVerified(true);
bsqUTXOMap.add(new BsqUTXO(blockHeight, txOutput, false));
bsqTXOMap.add(txOutput);
totalSpendBSQOutputs += txOutput.getValue();
@ -315,11 +316,12 @@ abstract public class BsqBlockchainService {
}
}
if (totalAvailableBSQInputs - totalSpendBSQOutputs > 0) {
final long burnedAmount = totalAvailableBSQInputs - totalSpendBSQOutputs;
if (burnedAmount > 0) {
log.debug("BSQ have been left which was not spent. Burned BSQ amount={}, tx={}",
availableValue,
tx.toString());
tx.getOutputs().stream().forEach(e -> e.setBurnedFee(burnedAmount));
bsqTXOMap.addBurnedBSQTx(tx);
}
@ -336,13 +338,8 @@ abstract public class BsqBlockchainService {
//TODO use BsqTXO not BsqUTXO as we dont know if they are unspent
// Genesis tx uses all outputs as BSQ outputs
for (TxOutput txOutput : outputs) {
if (txOutput.getAddresses().size() != 1) {
final String msg = "We got a address list with more or less than 1 address. " +
"Seems to be a raw MS. Raw MS are not supported with BSQ.\n" + this.toString();
log.warn(msg);
if (DevEnv.DEV_MODE)
throw new RuntimeException(msg);
}
txOutput.setVerified(true);
txOutput.setBsqCoinBase(true);
bsqUTXOMap.add(new BsqUTXO(blockHeight, txOutput, true));
bsqTXOMap.add(txOutput);
}

View file

@ -21,7 +21,6 @@ import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.List;
// Estimation for UTXO set: 1 UTXO object has 78 byte
// 1000 UTXOs - 10 000 UTXOs: 78kb -780kb
@ -43,10 +42,7 @@ public class BsqUTXO implements Serializable {
}
public String getAddress() {
// Only at raw MS outputs addresses have more then 1 entry
// We do not support raw MS for BSQ but lets see if is needed anyway, might be removed
final List<String> addresses = txOutput.getAddresses();
return addresses.size() == 1 ? addresses.get(0) : addresses.toString();
return txOutput.getAddress();
}
public long getValue() {

View file

@ -0,0 +1,27 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.core.dao.blockchain;
import lombok.Value;
@Value
public class SpendInfo {
private final long blockHeight;
private final String txId;
private final int inputIndex;
}

View file

@ -17,28 +17,103 @@
package io.bisq.core.dao.blockchain;
import lombok.Value;
import org.bouncycastle.util.encoders.Hex;
import com.neemre.btcdcli4j.core.domain.PubKeyScript;
import io.bisq.common.app.DevEnv;
import io.bisq.common.app.Version;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.List;
@Value
@Getter
@EqualsAndHashCode
@Slf4j
public class TxOutput implements Serializable {
// Immutable
private final int index;
private final long value;
private final List<String> addresses;
private final String txId;
private final byte[] scriptProgramBytes;
private final PubKeyScript pubKeyScript;
private final int blockHeight;
private final long time;
private final String txVersion = Version.BSQ_TX_VERSION;
// Mutable
@Setter
private boolean isBsqCoinBase;
@Setter
private boolean isVerified;
@Setter
private long burnedFee;
@Nullable
@Setter
private long btcTxFee;
@Nullable
@Setter
private SpendInfo spendInfo;
// Lazy set
@Nullable
private String address;
public TxOutput(int index,
long value,
String txId,
PubKeyScript pubKeyScript,
int blockHeight,
long time) {
this.index = index;
this.value = value;
this.txId = txId;
this.pubKeyScript = pubKeyScript;
this.blockHeight = blockHeight;
this.time = time;
}
public List<String> getAddresses() {
return pubKeyScript.getAddresses();
}
public String getAddress() {
if (address == null) {
// Only at raw MS outputs addresses have more then 1 entry
// We do not support raw MS for BSQ but lets see if is needed anyway, might be removed
final List<String> addresses = pubKeyScript.getAddresses();
if (addresses.size() == 1) {
address = addresses.get(0);
} else if (addresses.size() > 1) {
final String msg = "We got a raw Multisig script. That is not supported for BSQ tokens.";
log.warn(msg);
address = addresses.toString();
if (DevEnv.DEV_MODE)
throw new RuntimeException(msg);
} else if (addresses.isEmpty()) {
final String msg = "We got no address. Unsupported pubKeyScript";
log.warn(msg);
address = "";
if (DevEnv.DEV_MODE)
throw new RuntimeException(msg);
}
}
return address;
}
@Override
public String toString() {
return "TxOutput{" +
"\n index=" + index +
",\n value=" + value +
",\n addresses=" + addresses +
",\n address=" + getAddress() +
",\n txId=" + txId +
",\n scriptProgramBytes=" + Hex.toHexString(scriptProgramBytes) +
",\n pubKeyScript=" + pubKeyScript +
",\n spendInfo=" + spendInfo +
"\n" +
" }";
}

View file

@ -22,7 +22,7 @@ import lombok.Value;
import java.util.List;
@Value
public class ScriptPubKeyJson {
public class ScriptPubKeyForJson {
private final List<String> addresses;
private final String asm;
private final String hex;

View file

@ -20,8 +20,8 @@ package io.bisq.core.dao.blockchain.json;
import lombok.Value;
@Value
public class SpentInfoJson {
public class SpentInfoForJson {
private final long height;
private final int index;
private final String txid;
}
private final int inputIndex;
private final String txId;
}

View file

@ -21,23 +21,21 @@ import lombok.Value;
@Value
public class TxOutputForJson {
private final boolean coinBase;
private final String txId;
private final int outputIndex;
private final long bsqAmount;
private final int height;
private final int index;
private final boolean invalid;
private final int n;
private final int output_index;
private final boolean isBsqCoinBase;
private final boolean isVerified;
private final long burnedFee;
private final long btcTxFee;
private final ScriptPubKeyForJson scriptPubKey;
private final SpentInfoForJson spentInfo;
private final long time;
private final String txVersion;
private final ScriptPubKeyJson scriptPubKey;
private final SpentInfoJson spent_info;
public String getSortData() {
return height + txId + outputIndex;
}
private final long squ_amount;
private final String status;
private final String transaction_version;
private final long tx_time;
private final String tx_type_str;
private final String txid;
private final boolean validated;
private final double value;
private final long valueSat;
}

View file

@ -38,6 +38,7 @@ import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import net.glxn.qrgen.QRCode;
import net.glxn.qrgen.image.ImageType;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.uri.BitcoinURI;
import org.fxmisc.easybind.EasyBind;
@ -58,7 +59,7 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
private TextField balanceTextField;
private final BsqWalletService bsqWalletService;
private final BsqFormatter formatter;
private final BsqFormatter bsqFormatter;
private final BalanceUtil balanceUtil;
private int gridRow = 0;
@ -71,9 +72,9 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BsqReceiveView(BsqWalletService bsqWalletService, BsqFormatter formatter, BalanceUtil balanceUtil) {
private BsqReceiveView(BsqWalletService bsqWalletService, BsqFormatter bsqFormatter, BalanceUtil balanceUtil) {
this.bsqWalletService = bsqWalletService;
this.formatter = formatter;
this.bsqFormatter = bsqFormatter;
this.balanceUtil = balanceUtil;
paymentLabelString = Res.get("dao.wallet.receive.fundBSQWallet");
}
@ -108,7 +109,7 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
balanceUtil.activate();
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
addressTextField.setAmountAsCoin(formatter.parseToCoin(t));
addressTextField.setAmountAsCoin(bsqFormatter.parseToCoin(t));
updateQRCode();
});
qrCodeImageView.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(
@ -116,7 +117,7 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
() -> new QRCodeWindow(getBitcoinURI()).show(),
200, TimeUnit.MILLISECONDS)
));
addressTextField.setAddress(bsqWalletService.freshReceiveAddress().toString());
addressTextField.setAddress(bsqFormatter.getBsqAddressStringFromAddress(bsqWalletService.freshReceiveAddress()));
updateQRCode();
}
@ -142,11 +143,12 @@ public class BsqReceiveView extends ActivatableView<GridPane, Void> {
}
private Coin getAmountAsCoin() {
return formatter.parseToCoin(amountTextField.getText());
return bsqFormatter.parseToCoin(amountTextField.getText());
}
private String getBitcoinURI() {
return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(),
Address addressFromBsqAddress = bsqFormatter.getAddressFromBsqAddress(addressTextField.getAddress());
return BitcoinURI.convertToBitcoinURI(addressFromBsqAddress,
getAmountAsCoin(),
paymentLabelString,
null);

View file

@ -53,7 +53,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
private final BsqWalletService bsqWalletService;
private final BtcWalletService btcWalletService;
private final BSFormatter bsqFormatter;
private final BsqFormatter bsqFormatter;
private final BSFormatter btcFormatter;
private final BalanceUtil balanceUtil;
private BsqValidator bsqValidator;
@ -113,7 +113,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
}
sendButton.setOnAction((event) -> {
String receiversAddressString = receiversAddressInputTextField.getText();
String receiversAddressString = bsqFormatter.getAddressFromBsqAddress(receiversAddressInputTextField.getText()).toString();
Coin receiverAmount = bsqFormatter.parseToCoin(amountInputTextField.getText());
try {
Transaction preparedSendTx = bsqWalletService.getPreparedSendTx(receiversAddressString, receiverAmount);
@ -124,7 +124,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
new Popup().headLine(Res.get("dao.wallet.send.sendFunds.headline"))
.confirmation(Res.get("dao.wallet.send.sendFunds.details",
bsqFormatter.formatCoinWithCode(receiverAmount),
receiversAddressString,
receiversAddressInputTextField.getText(),
btcFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
txSize / 1000d,

View file

@ -23,6 +23,7 @@ import io.bisq.core.btc.wallet.BsqWalletService;
import io.bisq.core.btc.wallet.BtcWalletService;
import io.bisq.core.btc.wallet.WalletUtils;
import io.bisq.gui.components.indicator.TxConfidenceIndicator;
import io.bisq.gui.util.BsqFormatter;
import io.bisq.gui.util.GUIUtil;
import javafx.scene.control.Tooltip;
import lombok.Getter;
@ -61,6 +62,7 @@ class BsqTxListItem {
private boolean received;
@Getter
private boolean isBurnedBsqTx;
private BsqFormatter bsqFormatter;
@Getter
private TxConfidenceIndicator txConfidenceIndicator;
@ -69,11 +71,13 @@ class BsqTxListItem {
public BsqTxListItem(Transaction transaction,
BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
boolean isBurnedBsqTx) {
boolean isBurnedBsqTx,
BsqFormatter bsqFormatter) {
this.transaction = transaction;
this.bsqWalletService = bsqWalletService;
this.btcWalletService = btcWalletService;
this.isBurnedBsqTx = isBurnedBsqTx;
this.bsqFormatter = bsqFormatter;
txId = transaction.getHashAsString();
date = transaction.getUpdateTime();
@ -106,7 +110,7 @@ class BsqTxListItem {
WalletUtils.isOutputScriptConvertableToAddress(output)) {
// We don't support send txs with multiple outputs to multiple receivers, so we can
// assume that only one output is not from our own wallets.
foreignReceiverAddress = WalletUtils.getAddressStringFromOutput(output);
foreignReceiverAddress = bsqFormatter.getBsqAddressStringFromAddress(WalletUtils.getAddressFromOutput(output));
break;
}
}
@ -117,7 +121,7 @@ class BsqTxListItem {
if (foreignReceiverAddress != null) {
for (TransactionOutput output : transaction.getOutputs()) {
if (WalletUtils.isOutputScriptConvertableToAddress(output)) {
ownReceiverAddress = WalletUtils.getAddressStringFromOutput(output);
ownReceiverAddress = bsqFormatter.getBsqAddressStringFromAddress(WalletUtils.getAddressFromOutput(output));
break;
}
}

View file

@ -134,9 +134,10 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
Map<String, Tx> burnedBSQTxIdMap = bsqBlockchainManager.getBsqTXOMap().getBurnedBSQTxMap();
Set<BsqTxListItem> list = bsqWalletService.getWalletBsqTransactions().stream()
.map(transaction -> new BsqTxListItem(transaction,
bsqWalletService,
btcWalletService,
burnedBSQTxIdMap.containsKey(transaction.getHashAsString())))
bsqWalletService,
btcWalletService,
burnedBSQTxIdMap.containsKey(transaction.getHashAsString()), bsqFormatter)
)
.collect(Collectors.toSet());
observableList.setAll(list);

View file

@ -0,0 +1,211 @@
/**
* Copyright 2011 Google Inc.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.bisq.gui.util;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.VersionedChecksummedBytes;
import java.math.BigInteger;
import java.util.Arrays;
/**
* <p>Base58 is a way to encode Bitcoin addresses as numbers and letters. Note that this is not the same base58 as used by
* Flickr, which you may see reference to around the internet.</p>
*
* <p>You may instead wish to work with {@link VersionedChecksummedBytes}, which adds support for testing the prefix
* and suffix bytes commonly found in addresses.</p>
*
* <p>Satoshi says: why base-58 instead of standard base-64 encoding?<p>
*
* <ul>
* <li>Don't want 0OIl characters that look the same in some fonts and
* could be used to create visually identical looking account numbers.</li>
* <li>A string with non-alphanumeric characters is not as easily accepted as an account number.</li>
* <li>E-mail usually won't line-break if there's no punctuation to break at.</li>
* <li>Doubleclicking selects the whole number as one word if it's all alphanumeric.</li>
* </ul>
*/
// We use Base58 with a different alphabet order, so we would get a checksum failure if the user mixes up btc and bsq
@Slf4j
public class Base58Bsq {
// public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
// we wan tot have B at beginning, usually its 1 for btc addresses
public static final char[] ALPHABET = "BCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789A".toCharArray();
private static final int[] INDEXES = new int[128];
static {
for (int i = 0; i < INDEXES.length; i++) {
INDEXES[i] = -1;
}
for (int i = 0; i < ALPHABET.length; i++) {
INDEXES[ALPHABET[i]] = i;
}
}
/** Encodes the given bytes in base58. No checksum is appended. */
public static String encode(byte[] input) {
if (input.length == 0) {
return "";
}
input = copyOfRange(input, 0, input.length);
// Count leading zeroes.
int zeroCount = 0;
while (zeroCount < input.length && input[zeroCount] == 0) {
++zeroCount;
}
// The actual encoding.
byte[] temp = new byte[input.length * 2];
int j = temp.length;
int startAt = zeroCount;
while (startAt < input.length) {
byte mod = divmod58(input, startAt);
if (input[startAt] == 0) {
++startAt;
}
temp[--j] = (byte) ALPHABET[mod];
}
// Strip extra '1' if there are some after decoding.
while (j < temp.length && temp[j] == ALPHABET[0]) {
++j;
}
// Add as many leading '1' as there were leading zeros.
while (--zeroCount >= 0) {
temp[--j] = (byte) ALPHABET[0];
}
byte[] output = copyOfRange(temp, j, temp.length);
return Utils.toString(output, "US-ASCII");
}
public static byte[] decode(String input) throws AddressFormatException {
if (input.length() == 0) {
return new byte[0];
}
byte[] input58 = new byte[input.length()];
// Transform the String to a base58 byte sequence
for (int i = 0; i < input.length(); ++i) {
char c = input.charAt(i);
int digit58 = -1;
if (c >= 0 && c < 128) {
digit58 = INDEXES[c];
}
if (digit58 < 0) {
throw new AddressFormatException("Illegal character " + c + " at " + i);
}
input58[i] = (byte) digit58;
}
// Count leading zeroes
int zeroCount = 0;
while (zeroCount < input58.length && input58[zeroCount] == 0) {
++zeroCount;
}
// The encoding
byte[] temp = new byte[input.length()];
int j = temp.length;
int startAt = zeroCount;
while (startAt < input58.length) {
byte mod = divmod256(input58, startAt);
if (input58[startAt] == 0) {
++startAt;
}
temp[--j] = mod;
}
// Do no add extra leading zeroes, move j to first non null byte.
while (j < temp.length && temp[j] == 0) {
++j;
}
return copyOfRange(temp, j - zeroCount, temp.length);
}
public static BigInteger decodeToBigInteger(String input) throws AddressFormatException {
return new BigInteger(1, decode(input));
}
/**
* Uses the checksum in the last 4 bytes of the decoded data to verify the rest are correct. The checksum is
* removed from the returned data.
*
* @throws AddressFormatException if the input is not base 58 or the checksum does not validate.
*/
public static byte[] decodeChecked(String input) throws AddressFormatException {
byte[] tmp = decode(input);
if (tmp.length < 4)
throw new AddressFormatException("Input too short");
byte[] bytes = copyOfRange(tmp, 0, tmp.length - 4);
byte[] checksum = copyOfRange(tmp, tmp.length - 4, tmp.length);
tmp = Sha256Hash.hashTwice(bytes);
byte[] hash = copyOfRange(tmp, 0, 4);
if (!Arrays.equals(checksum, hash))
throw new AddressFormatException("Checksum does not validate");
return bytes;
}
//
// number -> number / 58, returns number % 58
//
private static byte divmod58(byte[] number, int startAt) {
int remainder = 0;
for (int i = startAt; i < number.length; i++) {
int digit256 = (int) number[i] & 0xFF;
int temp = remainder * 256 + digit256;
number[i] = (byte) (temp / 58);
remainder = temp % 58;
}
return (byte) remainder;
}
//
// number -> number / 256, returns number % 256
//
private static byte divmod256(byte[] number58, int startAt) {
int remainder = 0;
for (int i = startAt; i < number58.length; i++) {
int digit58 = (int) number58[i] & 0xFF;
int temp = remainder * 58 + digit58;
number58[i] = (byte) (temp / 256);
remainder = temp % 256;
}
return (byte) remainder;
}
private static byte[] copyOfRange(byte[] source, int from, int to) {
byte[] range = new byte[to - from];
System.arraycopy(source, from, range, 0, range.length);
return range;
}
}

View file

@ -17,6 +17,10 @@
package io.bisq.gui.util;
import io.bisq.core.btc.wallet.WalletUtils;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.utils.MonetaryFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -25,6 +29,7 @@ import javax.inject.Inject;
public class BsqFormatter extends BSFormatter {
private static final Logger log = LoggerFactory.getLogger(BsqFormatter.class);
private static final boolean useBsqAddressFormat = false;
@Inject
private BsqFormatter() {
@ -32,4 +37,50 @@ public class BsqFormatter extends BSFormatter {
coinFormat = new MonetaryFormat().shift(5).code(5, "BSQ").minDecimals(3);
}
/**
* Returns the base-58 encoded String representation of this
* object, including version and checksum bytes.
*/
public String getBsqAddressStringFromAddress(Address address) {
if (useBsqAddressFormat) {
byte[] bytes = address.getHash160();
int version = address.getVersion();
// A stringified buffer is:
// 1 byte version + data bytes + 4 bytes check code (a truncated hash)
byte[] addressBytes = new byte[1 + bytes.length + 4];
addressBytes[0] = (byte) version;
System.arraycopy(bytes, 0, addressBytes, 1, bytes.length);
byte[] checksum = Sha256Hash.hashTwice(addressBytes, 0, bytes.length + 1);
System.arraycopy(checksum, 0, addressBytes, bytes.length + 1, 4);
// return "BSQ" + Base58Bsq.encode(addressBytes);
return Base58Bsq.encode(addressBytes);
} else {
return address.toString();
}
}
public Address getAddressFromBsqAddress(String encoded) {
if (useBsqAddressFormat) {
try {
//encoded = encoded.substring(3, encoded.length());
byte[] versionAndDataBytes = Base58Bsq.decodeChecked(encoded);
byte[] bytes = new byte[versionAndDataBytes.length - 1];
System.arraycopy(versionAndDataBytes, 1, bytes, 0, versionAndDataBytes.length - 1);
return new Address(WalletUtils.getParameters(), bytes);
} catch (AddressFormatException e) {
log.error(e.toString());
e.printStackTrace();
throw new RuntimeException(e);
}
} else {
try {
return new Address(WalletUtils.getParameters(), encoded);
} catch (AddressFormatException e) {
log.error(e.toString());
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}

View file

@ -18,20 +18,20 @@
package io.bisq.gui.util.validation;
import io.bisq.common.locale.Res;
import io.bisq.core.btc.wallet.WalletUtils;
import io.bisq.core.user.Preferences;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import io.bisq.gui.util.BsqFormatter;
import javax.inject.Inject;
public final class BsqAddressValidator extends InputValidator {
private final Preferences preferences;
private BsqFormatter bsqFormatter;
@Inject
public BsqAddressValidator(Preferences preferences) {
public BsqAddressValidator(Preferences preferences, BsqFormatter bsqFormatter) {
this.preferences = preferences;
this.bsqFormatter = bsqFormatter;
}
@Override
@ -46,9 +46,9 @@ public final class BsqAddressValidator extends InputValidator {
private ValidationResult validateBsqAddress(String input) {
try {
new Address(WalletUtils.getParameters(), input);
bsqFormatter.getAddressFromBsqAddress(input);
return new ValidationResult(true);
} catch (AddressFormatException e) {
} catch (Throwable e) {
return new ValidationResult(false, Res.get("validation.bsq.invalidFormat"));
}
}