Skip to content

Commit

Permalink
Wallet: Elements Core (elementsd) support
Browse files Browse the repository at this point in the history
Changes:
- Elements Core (elementsd) Process Manager
  - ElementsRegtestSetup
- Implemented RPC calls:
  - claimpegin
  - createwallet
  - finalizepsbt
  - generatetoaddress
  - getbalance
    - L-BTC
    - Liquid Assets
  - getnewaddress
  - getpeginaddress
  - getrawtransaction
  - gettxoutproof
  - issueasset
  - listtransactions
  - listunspent
  - listwallets
  - loadwallet
  - sendrawtransaction
  - sendtoaddress
    - L-BTC
    - Liquid Assets
  - signmessage
  - stop
  - unloadwallet
  - verifymessage
  - walletpassphrase
  • Loading branch information
alvasw committed Mar 15, 2022
1 parent 766520b commit 7e09a66
Show file tree
Hide file tree
Showing 34 changed files with 1,816 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
import bisq.desktop.overlay.Popup;
import bisq.i18n.Res;
import bisq.wallets.NetworkType;
import bisq.wallets.WalletBackend;
import bisq.wallets.WalletConfig;
import bisq.wallets.WalletService;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
Expand Down Expand Up @@ -115,6 +118,7 @@ private void onSelectWalletPath() {

private WalletConfig createWalletConfigFromModel() {
return WalletConfig.builder()
.walletBackend(model.selectedWalletBackend.get())
.networkType(NetworkType.REGTEST)
.hostname(model.hostnameProperty.get())
.port(Integer.parseInt(model.portProperty.get()))
Expand All @@ -131,7 +135,10 @@ private void addContent() {
}

private static class Model implements bisq.desktop.common.view.Model {
private final ObservableList<String> walletBackends = FXCollections.observableArrayList("Bitcoin Core");
private final ObservableList<WalletBackend> walletBackends = FXCollections.observableArrayList(WalletBackend.values());
private final ObjectProperty<WalletBackend> selectedWalletBackend =
new SimpleObjectProperty<>(this, "selectedWalletBackend", WalletBackend.BITCOIND);

private final StringProperty walletsDataDirPathProperty = new SimpleStringProperty(this, "walletPath");
private final StringProperty hostnameProperty = new SimpleStringProperty(this, "hostname", "127.0.0.1");
private final StringProperty portProperty = new SimpleStringProperty(this, "port", "18443");
Expand Down Expand Up @@ -160,9 +167,9 @@ private void addContent() {
gridPane.addButton(Res.get("wallet.config.selectWalletPath"), controller::onSelectWalletPath);
gridPane.addTextField(Res.get("wallet.config.walletsDataDirPath"), model.walletsDataDirPathProperty);

BisqComboBox<String> walletBackendComboBox = gridPane.addComboBox(model.walletBackends);
BisqComboBox<WalletBackend> walletBackendComboBox = gridPane.addComboBox(model.walletBackends);
walletBackendComboBox.setPromptText(Res.get("wallet.config.selectWallet"));
walletBackendComboBox.getSelectionModel().selectFirst();
walletBackendComboBox.valueProperty().bindBidirectional(model.selectedWalletBackend);

gridPane.addTextField(Res.get("wallet.config.enterHostname"), model.hostnameProperty);
gridPane.addTextField(Res.get("wallet.config.enterPort"), model.portProperty);
Expand Down
37 changes: 37 additions & 0 deletions wallets/src/main/java/bisq/wallets/WalletBackend.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.wallets;

import lombok.Getter;

public enum WalletBackend {
BITCOIND("Bitcoin Core"),
ELEMENTSD("Elements Core");

@Getter
private final String label;

WalletBackend(String label) {
this.label = label;
}

@Override
public String toString() {
return label;
}
}
1 change: 1 addition & 0 deletions wallets/src/main/java/bisq/wallets/WalletConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
@Builder
@Getter
public class WalletConfig {
private final WalletBackend walletBackend;
private final NetworkType networkType;
private final String hostname;
private final int port;
Expand Down
33 changes: 27 additions & 6 deletions wallets/src/main/java/bisq/wallets/WalletService.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import bisq.common.monetary.Coin;
import bisq.common.observable.Observable;
import bisq.wallets.bitcoind.BitcoinWallet;
import bisq.wallets.elementsd.LiquidWallet;
import bisq.wallets.exceptions.WalletNotInitializedException;
import bisq.wallets.model.Transaction;
import bisq.wallets.model.Utxo;
Expand All @@ -43,16 +44,36 @@ public CompletableFuture<Void> initialize(Path walletsDataDir, WalletConfig wall
return CompletableFuture.runAsync(() -> {
walletsDataDir.toFile().mkdirs();

Path bitcoindDataDir = walletsDataDir.resolve("bitcoind"); // directory name for bitcoind wallet
RpcConfig rpcConfig = createRpcConfigFromWalletConfig(walletConfig, bitcoindDataDir);
var bitcoindWallet = new BitcoinWallet(bitcoindDataDir, rpcConfig);
bitcoindWallet.initialize(walletPassphrase);

wallet = Optional.of(bitcoindWallet);
Wallet wallet = switch (walletConfig.getWalletBackend()) {
case BITCOIND -> {
var bitcoindWallet = createBitcoinWallet(walletConfig, walletsDataDir);
bitcoindWallet.initialize(walletPassphrase);
yield bitcoindWallet;
}
case ELEMENTSD -> {
var liquidWallet = createLiquidWallet(walletConfig, walletsDataDir);
liquidWallet.initialize(walletPassphrase);
yield liquidWallet;
}
};

this.wallet = Optional.of(wallet);
log.info("Successfully created wallet at {}", walletsDataDir);
}).thenRun(this::getBalance);
}

private BitcoinWallet createBitcoinWallet(WalletConfig walletConfig, Path walletsDataDir) {
Path bitcoindDataDir = walletsDataDir.resolve("bitcoind"); // directory name for bitcoind wallet
RpcConfig rpcConfig = createRpcConfigFromWalletConfig(walletConfig, bitcoindDataDir);
return new BitcoinWallet(bitcoindDataDir, rpcConfig);
}

private LiquidWallet createLiquidWallet(WalletConfig walletConfig, Path walletsDataDir) {
Path bitcoindDataDir = walletsDataDir.resolve("elementsd"); // directory name for bitcoind wallet
RpcConfig rpcConfig = createRpcConfigFromWalletConfig(walletConfig, bitcoindDataDir);
return new LiquidWallet(bitcoindDataDir, rpcConfig);
}

public CompletableFuture<Void> shutdown() {
return CompletableFuture.runAsync(() -> wallet.ifPresent(Wallet::shutdown));
}
Expand Down
23 changes: 23 additions & 0 deletions wallets/src/main/java/bisq/wallets/elementsd/ElementsdConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.wallets.elementsd;

import bisq.wallets.rpc.RpcConfig;

public record ElementsdConfig(RpcConfig bitcoindRpcConfig, RpcConfig elementsdRpcConfig) {
}
94 changes: 94 additions & 0 deletions wallets/src/main/java/bisq/wallets/elementsd/LiquidWallet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.wallets.elementsd;

import bisq.wallets.AddressType;
import bisq.wallets.Wallet;
import bisq.wallets.bitcoind.rpc.BitcoindWallet;
import bisq.wallets.elementsd.rpc.ElementsdDaemon;
import bisq.wallets.elementsd.rpc.ElementsdWallet;
import bisq.wallets.exceptions.WalletInitializationFailedException;
import bisq.wallets.model.Transaction;
import bisq.wallets.model.Utxo;
import bisq.wallets.rpc.RpcClient;
import bisq.wallets.rpc.RpcClientFactory;
import bisq.wallets.rpc.RpcConfig;

import java.net.MalformedURLException;
import java.nio.file.Path;
import java.util.List;

public class LiquidWallet implements Wallet {
private final Path walletPath;

private final ElementsdDaemon daemon;
private final ElementsdWallet wallet;

public LiquidWallet(Path walletPath, RpcConfig rpcConfig) {
this.walletPath = walletPath;

try {
RpcClient rpcClient = RpcClientFactory.create(rpcConfig);
daemon = new ElementsdDaemon(rpcClient);
wallet = new ElementsdWallet(rpcClient);
} catch (MalformedURLException e) {
throw new WalletInitializationFailedException("Couldn't initialize WalletService", e);
}
}

@Override
public void initialize(String walletPassphrase) {
daemon.createOrLoadWallet(walletPath, walletPassphrase);
wallet.walletPassphrase(walletPassphrase, BitcoindWallet.DEFAULT_WALLET_TIMEOUT);
}

@Override
public void shutdown() {
daemon.unloadWallet(walletPath);
}

@Override
public double getBalance() {
return wallet.getLBtcBalance();
}

@Override
public String getNewAddress(AddressType addressType, String label) {
return wallet.getNewAddress(addressType, label);
}

@Override
public String signMessage(String address, String message) {
return wallet.signMessage(address, message);
}

@Override
public List<? extends Transaction> listTransactions() {
return wallet.listTransactions(1000);
}

@Override
public List<? extends Utxo> listUnspent() {
return wallet.listUnspent();
}

@Override
public String sendToAddress(String address, double amount) {
return wallet.sendLBtcToAddress(address, amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.wallets.elementsd.process;

import bisq.common.util.NetworkUtils;
import bisq.wallets.NetworkType;
import bisq.wallets.bitcoind.BitcoindProcess;
import bisq.wallets.elementsd.ElementsdConfig;
import bisq.wallets.elementsd.rpc.ElementsdDaemon;
import bisq.wallets.exceptions.RpcCallFailureException;
import bisq.wallets.process.ProcessConfig;
import bisq.wallets.rpc.RpcClient;
import bisq.wallets.rpc.RpcClientFactory;
import bisq.wallets.rpc.RpcConfig;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;

@Slf4j
public class ElementsdProcess extends BitcoindProcess {
private final ElementsdConfig elementsdConfig;

public ElementsdProcess(ElementsdConfig elementsdConfig, Path dataDir) {
super(elementsdConfig.elementsdRpcConfig(), dataDir);
this.elementsdConfig = elementsdConfig;
}

@Override
public ProcessConfig createProcessConfig() {
RpcConfig bitcoindRpcConfig = elementsdConfig.bitcoindRpcConfig();
return new ProcessConfig(
"elementsd",
List.of(
"-chain=" + getNetworkName(),
"-datadir=" + dataDir.toAbsolutePath(),

"-bind=127.0.0.1:" + NetworkUtils.findFreeSystemPort(),
"-whitelist=127.0.0.1",

"-rpcbind=127.0.0.1:" + rpcConfig.port(),
"-rpcallowip=127.0.0.1",
"-rpcuser=" + rpcConfig.user(),
"-rpcpassword=" + rpcConfig.password(),

"-mainchainrpchost=" + bitcoindRpcConfig.hostname(),
"-mainchainrpcport=" + bitcoindRpcConfig.port(),
"-mainchainrpcuser=" + bitcoindRpcConfig.user(),
"-mainchainrpcpassword=" + bitcoindRpcConfig.password(),

"-fallbackfee=0.00000001",
"-txindex=1")
);
}

@Override
public void invokeStopRpcCall() throws IOException {
try {
RpcClient rpcClient = RpcClientFactory.create(rpcConfig);
var chainBackend = new ElementsdDaemon(rpcClient);
chainBackend.stop();
} catch (RpcCallFailureException e) {
log.error("Failed to send stop command to elementsd.", e);
}
}

private String getNetworkName() {
NetworkType networkType = rpcConfig.networkType();
return switch (networkType) {
case MAINNET -> "";
case REGTEST -> "elementsregtest";
case SIGNET -> "signet";
case TESTNET -> "liquidv1test";
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.wallets.elementsd.rpc;

import bisq.wallets.bitcoind.rpc.BitcoindDaemon;
import bisq.wallets.bitcoind.rpc.calls.BitcoindGetTxOutProofRpcCall;
import bisq.wallets.elementsd.rpc.calls.ElementsdStopRpcCall;
import bisq.wallets.rpc.RpcClient;

import java.util.List;

public class ElementsdDaemon extends BitcoindDaemon {
public ElementsdDaemon(RpcClient rpcClient) {
super(rpcClient);
}

@Override
public void stop() {
var rpcCall = new ElementsdStopRpcCall();
rpcClient.invokeAndValidate(rpcCall);
}
}
Loading

0 comments on commit 7e09a66

Please sign in to comment.