Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wallet changes for Segwit BSQ implementation #5109

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import bisq.proto.grpc.BsqBalanceInfo;

import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.NetworkParameters;

import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -64,7 +64,7 @@ public void testGetUnusedBsqAddress() {
assertFalse(address.isEmpty());
assertTrue(address.startsWith("B"));

NetworkParameters networkParameters = LegacyAddress.getParametersFromAddress(address.substring(1));
NetworkParameters networkParameters = Address.fromString(null, address.substring(1)).getParameters();
String addressNetwork = networkParameters.getPaymentProtocolId();
assertNotEquals(PAYMENT_PROTOCOL_ID_MAINNET, addressNetwork);
// TODO Fix bug causing the regtest bsq address network to be evaluated as 'testnet' here.
Expand Down
6 changes: 3 additions & 3 deletions assets/src/main/java/bisq/asset/coins/BSQ.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package bisq.asset.coins;

import bisq.asset.AddressValidationResult;
import bisq.asset.Base58AddressValidator;
import bisq.asset.BitcoinAddressValidator;
import bisq.asset.Coin;

import org.bitcoinj.core.NetworkParameters;
Expand Down Expand Up @@ -57,7 +57,7 @@ public Regtest() {
}


public static class BSQAddressValidator extends Base58AddressValidator {
public static class BSQAddressValidator extends BitcoinAddressValidator {

public BSQAddressValidator(NetworkParameters networkParameters) {
super(networkParameters);
Expand All @@ -68,7 +68,7 @@ public AddressValidationResult validate(String address) {
if (!address.startsWith("B"))
return AddressValidationResult.invalidAddress("BSQ address must start with 'B'");

String addressAsBtc = address.substring(1, address.length());
String addressAsBtc = address.substring(1);

return super.validate(addressAsBtc);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts");

// Validate the BSQ address string but ignore the return value.
coreWalletsService.getValidBsqLegacyAddress(address);
coreWalletsService.getValidBsqAddress(address);

var cryptoCurrencyAccount = tradeInstant
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
Expand Down
23 changes: 9 additions & 14 deletions core/src/main/java/bisq/core/api/CoreWalletsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.BsqTransferModel;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BsqTransferService;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
Expand All @@ -51,7 +50,6 @@
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
Expand Down Expand Up @@ -98,7 +96,6 @@ class CoreWalletsService {
private final CoreContext coreContext;
private final Balances balances;
private final WalletsManager walletsManager;
private final WalletsSetup walletsSetup;
private final BsqWalletService bsqWalletService;
private final BsqTransferService bsqTransferService;
private final BsqFormatter bsqFormatter;
Expand All @@ -120,7 +117,6 @@ public CoreWalletsService(AppStartupState appStartupState,
CoreContext coreContext,
Balances balances,
WalletsManager walletsManager,
WalletsSetup walletsSetup,
BsqWalletService bsqWalletService,
BsqTransferService bsqTransferService,
BsqFormatter bsqFormatter,
Expand All @@ -132,7 +128,6 @@ public CoreWalletsService(AppStartupState appStartupState,
this.coreContext = coreContext;
this.balances = balances;
this.walletsManager = walletsManager;
this.walletsSetup = walletsSetup;
this.bsqWalletService = bsqWalletService;
this.bsqTransferService = bsqTransferService;
this.bsqFormatter = bsqFormatter;
Expand Down Expand Up @@ -226,18 +221,18 @@ String getUnusedBsqAddress() {
return bsqWalletService.getUnusedBsqAddressAsString();
}

void sendBsq(String address,
void sendBsq(String addressStr,
String amount,
String txFeeRate,
TxBroadcaster.Callback callback) {
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();

try {
LegacyAddress legacyAddress = getValidBsqLegacyAddress(address);
Address address = getValidBsqAddress(addressStr);
Coin receiverAmount = getValidTransferAmount(amount, bsqFormatter);
Coin txFeePerVbyte = getTxFeeRateFromParamOrPreferenceOrFeeService(txFeeRate);
BsqTransferModel model = bsqTransferService.getBsqTransferModel(legacyAddress,
BsqTransferModel model = bsqTransferService.getBsqTransferModel(address,
receiverAmount,
txFeePerVbyte);
log.info("Sending {} BSQ to {} with tx fee rate {} sats/byte.",
Expand Down Expand Up @@ -313,7 +308,7 @@ void sendBtc(String address,
}

boolean verifyBsqSentToAddress(String address, String amount) {
Address receiverAddress = getValidBsqLegacyAddress(address);
Address receiverAddress = getValidBsqAddress(address);
NetworkParameters networkParameters = getNetworkParameters();
Predicate<TransactionOutput> isTxOutputAddressMatch = (txOut) ->
txOut.getScriptPubKey().getToAddress(networkParameters).equals(receiverAddress);
Expand Down Expand Up @@ -553,12 +548,12 @@ void verifyApplicationIsFullyInitialized() {
throw new IllegalStateException("server is not fully initialized");
}

// Returns a LegacyAddress for the string, or a RuntimeException if invalid.
LegacyAddress getValidBsqLegacyAddress(String address) {
// Returns an Address for the string, or a RuntimeException if invalid.
Address getValidBsqAddress(String address) {
try {
return bsqFormatter.getAddressFromBsqAddress(address);
} catch (Throwable t) {
log.error("", t);
} catch (RuntimeException e) {
log.error("", e);
throw new IllegalStateException(format("%s is not a valid bsq address", address));
}
}
Expand All @@ -582,7 +577,7 @@ private void maybeSetWalletsManagerKey() {
if (btcWalletService.getAesKey() == null || bsqWalletService.getAesKey() == null) {
KeyParameter aesKey = new KeyParameter(tempAesKey.getKey());
walletsManager.setAesKey(aesKey);
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(), aesKey);
walletsManager.maybeAddSegwitKeychains(aesKey);
}
}

Expand Down
3 changes: 1 addition & 2 deletions core/src/main/java/bisq/core/app/BisqSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -423,8 +423,7 @@ private void initWallet() {
if (requestWalletPasswordHandler != null) {
requestWalletPasswordHandler.accept(aesKey -> {
walletsManager.setAesKey(aesKey);
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(),
aesKey);
walletsManager.maybeAddSegwitKeychains(aesKey);
if (getResyncSpvSemaphore()) {
if (showFirstPopupIfResyncSPVRequestedHandler != null)
showFirstPopupIfResyncSPVRequestedHandler.run();
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/java/bisq/core/btc/model/BsqTransferModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

import bisq.core.dao.state.model.blockchain.TxType;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.Transaction;

import lombok.Getter;

@Getter
public final class BsqTransferModel {

private final LegacyAddress receiverAddress;
private final Address receiverAddress;
private final Coin receiverAmount;
private final Transaction preparedSendTx;
private final Transaction txWithBtcFee;
Expand All @@ -20,7 +20,7 @@ public final class BsqTransferModel {
private final int txSize;
private final TxType txType;

public BsqTransferModel(LegacyAddress receiverAddress,
public BsqTransferModel(Address receiverAddress,
Coin receiverAmount,
Transaction preparedSendTx,
Transaction txWithBtcFee,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,14 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
new ChildNumber(142, true),
ChildNumber.ZERO_HARDENED);

// We don't use segwit for BSQ
// public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
// new ChildNumber(44, true),
// new ChildNumber(142, true),
// ChildNumber.ONE_HARDENED);
public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
new ChildNumber(44, true),
new ChildNumber(142, true),
ChildNumber.ONE_HARDENED);

private boolean isBsqWallet;
private final boolean isBsqWallet;

public BisqKeyChainGroupStructure (boolean isBsqWallet) {
public BisqKeyChainGroupStructure(boolean isBsqWallet) {
this.isBsqWallet = isBsqWallet;
}

Expand All @@ -72,8 +71,7 @@ else if (outputScriptType == Script.ScriptType.P2WPKH)
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
return BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH;
else if (outputScriptType == Script.ScriptType.P2WPKH)
//return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
throw new IllegalArgumentException(outputScriptType.toString());
return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
else
throw new IllegalArgumentException(outputScriptType.toString());
}
Expand Down
39 changes: 27 additions & 12 deletions core/src/main/java/bisq/core/btc/setup/WalletConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;

import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

Expand Down Expand Up @@ -143,7 +144,11 @@ public class WalletConfig extends AbstractIdleService {
@Setter
private int minBroadcastConnections;
@Getter
private BooleanProperty migratedWalletToSegwit = new SimpleBooleanProperty(false);
private BooleanProperty migratedWalletToBtcSegwit = new SimpleBooleanProperty(false);
@Getter
private BooleanProperty migratedWalletToBsqSegwit = new SimpleBooleanProperty(false);
@Getter
private BooleanExpression migratedWalletToSegwit = migratedWalletToBtcSegwit.and(migratedWalletToBsqSegwit);

/**
* Creates a new WalletConfig, with a newly created {@link Context}. Files will be stored in the given directory.
Expand Down Expand Up @@ -406,15 +411,13 @@ private Wallet loadWallet(boolean shouldReplayWallet, File walletFile, boolean i
wallet = serializer.readWallet(params, extArray, proto);
if (shouldReplayWallet)
wallet.reset();
if (!isBsqWallet) {
maybeAddSegwitKeychain(wallet, null);
}
maybeAddSegwitKeychain(wallet, null, isBsqWallet);
}
return wallet;
}

protected Wallet createWallet(boolean isBsqWallet) {
Script.ScriptType preferredOutputScriptType = isBsqWallet ? Script.ScriptType.P2PKH : Script.ScriptType.P2WPKH;
Script.ScriptType preferredOutputScriptType = Script.ScriptType.P2WPKH;
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure(isBsqWallet);
KeyChainGroup.Builder kcgBuilder = KeyChainGroup.builder(params, structure);
if (restoreFromSeed != null) {
Expand Down Expand Up @@ -545,21 +548,29 @@ public File directory() {
return directory;
}

public void maybeAddSegwitKeychain(Wallet wallet, KeyParameter aesKey) {
if (BisqKeyChainGroupStructure.BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH.equals(wallet.getActiveKeyChain().getAccountPath())) {
public void maybeAddSegwitKeychain(Wallet wallet, KeyParameter aesKey, boolean isBsqWallet) {
var nonSegwitAccountPath = isBsqWallet
? BisqKeyChainGroupStructure.BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH
: BisqKeyChainGroupStructure.BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH;
var preSegwitBackupFilename = isBsqWallet
? WalletsSetup.PRE_SEGWIT_BSQ_WALLET_BACKUP
: WalletsSetup.PRE_SEGWIT_BTC_WALLET_BACKUP;
var walletFilename = isBsqWallet ? "bisq_BSQ.wallet" : "bisq_BTC.wallet";

if (nonSegwitAccountPath.equals(wallet.getActiveKeyChain().getAccountPath())) {
if (wallet.isEncrypted() && aesKey == null) {
// wait for the aesKey to be set and this method to be invoked again.
return;
}
// Do a backup of the wallet
File backup = new File(directory, WalletsSetup.PRE_SEGWIT_WALLET_BACKUP);
File backup = new File(directory, preSegwitBackupFilename);
try {
FileUtil.copyFile(new File(directory, "bisq_BTC.wallet"), backup);
FileUtil.copyFile(new File(directory, walletFilename), backup);
} catch (IOException e) {
log.error(e.toString(), e);
}

// Btc wallet does not have a native segwit keychain, we should add one.
// Wallet does not have a native segwit keychain, we should add one.
DeterministicSeed seed = wallet.getKeyChainSeed();
if (aesKey != null) {
// If wallet is encrypted, decrypt the seed.
Expand All @@ -568,15 +579,19 @@ public void maybeAddSegwitKeychain(Wallet wallet, KeyParameter aesKey) {
}
DeterministicKeyChain nativeSegwitKeyChain = DeterministicKeyChain.builder().seed(seed)
.outputScriptType(Script.ScriptType.P2WPKH)
.accountPath(new BisqKeyChainGroupStructure(false).accountPathFor(Script.ScriptType.P2WPKH)).build();
.accountPath(new BisqKeyChainGroupStructure(isBsqWallet).accountPathFor(Script.ScriptType.P2WPKH)).build();
if (aesKey != null) {
// If wallet is encrypted, encrypt the new keychain.
KeyCrypter keyCrypter = wallet.getKeyCrypter();
nativeSegwitKeyChain = nativeSegwitKeyChain.toEncrypted(keyCrypter, aesKey);
}
wallet.addAndActivateHDChain(nativeSegwitKeyChain);
}
migratedWalletToSegwit.set(true);
if (isBsqWallet) {
migratedWalletToBsqSegwit.set(true);
} else {
migratedWalletToBtcSegwit.set(true);
}
}

public boolean stateStartingOrRunning() {
Expand Down
17 changes: 10 additions & 7 deletions core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@
@Slf4j
public class WalletsSetup {

public static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_bisq_BTC.wallet.backup";
public static final String PRE_SEGWIT_BTC_WALLET_BACKUP = "pre_segwit_bisq_BTC.wallet.backup";
public static final String PRE_SEGWIT_BSQ_WALLET_BACKUP = "pre_segwit_bisq_BSQ.wallet.backup";

@Getter
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
Expand Down Expand Up @@ -427,12 +428,14 @@ public void clearBackups() {
e.printStackTrace();
}

File segwitBackup = new File(walletDir, PRE_SEGWIT_WALLET_BACKUP);
try {
FileUtil.deleteFileIfExists(segwitBackup);
} catch (IOException e) {
log.error(e.toString(), e);
}
List.of(PRE_SEGWIT_BTC_WALLET_BACKUP, PRE_SEGWIT_BSQ_WALLET_BACKUP).forEach(filename -> {
File segwitBackup = new File(walletDir, filename);
try {
FileUtil.deleteFileIfExists(segwitBackup);
} catch (IOException e) {
log.error(e.toString(), e);
}
});
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.model.BsqTransferModel;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.Transaction;

import javax.inject.Inject;
Expand All @@ -32,7 +32,7 @@ public BsqTransferService(WalletsManager walletsManager,
this.btcWalletService = btcWalletService;
}

public BsqTransferModel getBsqTransferModel(LegacyAddress address,
public BsqTransferModel getBsqTransferModel(Address address,
Coin receiverAmount,
Coin txFeePerVbyte)
throws TransactionVerificationException,
Expand Down
Loading