mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-10 09:20:04 +01:00
walletfx: WalletApplication to improve encapsulation
* Make all public fields private and wrap with accessors * Make `WalletAppKit` (was confusingly named `bitcoin`) field a member not a static * Rename `bitcoin` to `walletAppKit`
This commit is contained in:
parent
515a558ec2
commit
85725a18a6
6 changed files with 68 additions and 40 deletions
|
@ -42,12 +42,12 @@ import static org.bitcoinj.walletfx.utils.GuiUtils.informationalAlert;
|
|||
* Base class for JavaFX Wallet Applications
|
||||
*/
|
||||
public abstract class WalletApplication implements AppDelegate {
|
||||
public static WalletAppKit bitcoin;
|
||||
public static WalletApplication instance;
|
||||
public final String applicationName;
|
||||
public final NetworkParameters params;
|
||||
public final Script.ScriptType preferredOutputScriptType;
|
||||
protected final String walletFileName;
|
||||
private static WalletApplication instance;
|
||||
private WalletAppKit walletAppKit;
|
||||
private final String applicationName;
|
||||
private final NetworkParameters params;
|
||||
private final Script.ScriptType preferredOutputScriptType;
|
||||
private final String walletFileName;
|
||||
private MainWindowController controller;
|
||||
|
||||
public WalletApplication(String applicationName, NetworkParameters params, Script.ScriptType preferredOutputScriptType) {
|
||||
|
@ -58,6 +58,26 @@ public abstract class WalletApplication implements AppDelegate {
|
|||
this.preferredOutputScriptType = preferredOutputScriptType;
|
||||
}
|
||||
|
||||
public static WalletApplication instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public WalletAppKit walletAppKit() {
|
||||
return walletAppKit;
|
||||
}
|
||||
|
||||
public String applicationName() {
|
||||
return applicationName;
|
||||
}
|
||||
|
||||
public NetworkParameters params() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public Script.ScriptType preferredOutputScriptType() {
|
||||
return preferredOutputScriptType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage mainWindow) throws Exception {
|
||||
try {
|
||||
|
@ -93,7 +113,7 @@ public abstract class WalletApplication implements AppDelegate {
|
|||
// Create the app kit. It won't do any heavyweight initialization until after we start it.
|
||||
setupWalletKit(null);
|
||||
|
||||
if (bitcoin.isChainFileLocked()) {
|
||||
if (walletAppKit.isChainFileLocked()) {
|
||||
informationalAlert("Already running", "This application is already running and cannot be started twice.");
|
||||
Platform.exit();
|
||||
return;
|
||||
|
@ -103,21 +123,21 @@ public abstract class WalletApplication implements AppDelegate {
|
|||
|
||||
WalletSetPasswordController.estimateKeyDerivationTimeMsec();
|
||||
|
||||
bitcoin.addListener(new Service.Listener() {
|
||||
walletAppKit.addListener(new Service.Listener() {
|
||||
@Override
|
||||
public void failed(Service.State from, Throwable failure) {
|
||||
GuiUtils.crashAlert(failure);
|
||||
}
|
||||
}, Platform::runLater);
|
||||
bitcoin.startAsync();
|
||||
walletAppKit.startAsync();
|
||||
|
||||
controller.scene().getAccelerators().put(KeyCombination.valueOf("Shortcut+F"), () -> bitcoin.peerGroup().getDownloadPeer().close());
|
||||
controller.scene().getAccelerators().put(KeyCombination.valueOf("Shortcut+F"), () -> walletAppKit().peerGroup().getDownloadPeer().close());
|
||||
}
|
||||
|
||||
public void setupWalletKit(@Nullable DeterministicSeed seed) {
|
||||
// If seed is non-null it means we are restoring from backup.
|
||||
File appDataDirectory = AppDataDirectory.get(applicationName).toFile();
|
||||
bitcoin = new WalletAppKit(params, preferredOutputScriptType, null, appDataDirectory, walletFileName) {
|
||||
walletAppKit = new WalletAppKit(params, preferredOutputScriptType, null, appDataDirectory, walletFileName) {
|
||||
@Override
|
||||
protected void onSetupCompleted() {
|
||||
Platform.runLater(controller::onBitcoinSetup);
|
||||
|
@ -126,19 +146,19 @@ public abstract class WalletApplication implements AppDelegate {
|
|||
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
|
||||
// or progress widget to keep the user engaged whilst we initialise, but we don't.
|
||||
if (params == RegTestParams.get()) {
|
||||
bitcoin.connectToLocalHost(); // You should run a regtest mode bitcoind locally.
|
||||
walletAppKit.connectToLocalHost(); // You should run a regtest mode bitcoind locally.
|
||||
}
|
||||
bitcoin.setDownloadListener(controller.progressBarUpdater())
|
||||
walletAppKit.setDownloadListener(controller.progressBarUpdater())
|
||||
.setBlockingStartup(false)
|
||||
.setUserAgent(applicationName, "1.0");
|
||||
if (seed != null)
|
||||
bitcoin.restoreWalletFromSeed(seed);
|
||||
walletAppKit.restoreWalletFromSeed(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
bitcoin.stopAsync();
|
||||
bitcoin.awaitTerminated();
|
||||
walletAppKit.stopAsync();
|
||||
walletAppKit.awaitTerminated();
|
||||
// Forcibly terminate the JVM because Orchid likes to spew non-daemon threads everywhere.
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
|
|
|
@ -43,8 +43,6 @@ import org.bitcoinj.walletfx.utils.BitcoinUIModel;
|
|||
import org.bitcoinj.walletfx.utils.easing.EasingMode;
|
||||
import org.bitcoinj.walletfx.utils.easing.ElasticInterpolator;
|
||||
|
||||
import static org.bitcoinj.walletfx.application.WalletApplication.bitcoin;
|
||||
|
||||
/**
|
||||
* Gets created auto-magically by FXMLLoader via reflection. The widget fields are set to the GUI controls they're named
|
||||
* after. This class handles all the updates and event handling for the main UI.
|
||||
|
@ -60,15 +58,17 @@ public class MainController extends MainWindowController {
|
|||
private NotificationBarPane.Item syncItem;
|
||||
private static final MonetaryFormat MONETARY_FORMAT = MonetaryFormat.BTC.noCode();
|
||||
|
||||
private WalletApplication app;
|
||||
private NotificationBarPane notificationBar;
|
||||
|
||||
// Called by FXMLLoader.
|
||||
public void initialize() {
|
||||
instance = this;
|
||||
app = WalletApplication.instance();
|
||||
// Special case of initOverlay that passes null as the 2nd parameter because ClickableBitcoinAddress is loaded by FXML
|
||||
// TODO: Extract QRCode Pane to separate reusable class that is a more standard OverlayController instance
|
||||
addressControl.initOverlay(this, null);
|
||||
addressControl.setAppName(WalletApplication.instance.applicationName);
|
||||
addressControl.setAppName(app.applicationName());
|
||||
addressControl.setOpacity(0.0);
|
||||
}
|
||||
|
||||
|
@ -85,12 +85,12 @@ public class MainController extends MainWindowController {
|
|||
// Add CSS that we need. cssResourceName will be loaded from the same package as this class.
|
||||
scene.getStylesheets().add(getClass().getResource(cssResourceName).toString());
|
||||
uiStack.getChildren().add(notificationBar);
|
||||
scene.getAccelerators().put(KeyCombination.valueOf("Shortcut+F"), () -> bitcoin.peerGroup().getDownloadPeer().close());
|
||||
scene.getAccelerators().put(KeyCombination.valueOf("Shortcut+F"), () -> app.walletAppKit().peerGroup().getDownloadPeer().close());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitcoinSetup() {
|
||||
model.setWallet(bitcoin.wallet());
|
||||
model.setWallet(app.walletAppKit().wallet());
|
||||
addressControl.addressProperty().bind(model.addressProperty());
|
||||
balance.textProperty().bind(createBalanceStringBinding(model.balanceProperty()));
|
||||
// Don't let the user click send money when the wallet is empty.
|
||||
|
|
|
@ -50,6 +50,7 @@ public class SendMoneyController implements OverlayController<SendMoneyControlle
|
|||
public TextField amountEdit;
|
||||
public Label btcLabel;
|
||||
|
||||
private WalletApplication app;
|
||||
private OverlayableStackPaneController rootController;
|
||||
private OverlayableStackPaneController.OverlayUI<? extends OverlayController<SendMoneyController>> overlayUI;
|
||||
|
||||
|
@ -64,13 +65,14 @@ public class SendMoneyController implements OverlayController<SendMoneyControlle
|
|||
|
||||
// Called by FXMLLoader
|
||||
public void initialize() {
|
||||
Coin balance = WalletApplication.bitcoin.wallet().getBalance();
|
||||
app = WalletApplication.instance();
|
||||
Coin balance = app.walletAppKit().wallet().getBalance();
|
||||
checkState(!balance.isZero());
|
||||
new BitcoinAddressValidator(WalletApplication.instance.params, address, sendBtn);
|
||||
new BitcoinAddressValidator(app.params(), address, sendBtn);
|
||||
new TextFieldValidator(amountEdit, text ->
|
||||
!WTUtils.didThrow(() -> checkState(Coin.parseCoin(text).compareTo(balance) <= 0)));
|
||||
amountEdit.setText(balance.toPlainString());
|
||||
address.setPromptText(Address.fromKey(WalletApplication.instance.params, new ECKey(), WalletApplication.instance.preferredOutputScriptType).toString());
|
||||
address.setPromptText(Address.fromKey(app.params(), new ECKey(), app.preferredOutputScriptType()).toString());
|
||||
}
|
||||
|
||||
public void cancel(ActionEvent event) {
|
||||
|
@ -81,9 +83,9 @@ public class SendMoneyController implements OverlayController<SendMoneyControlle
|
|||
// Address exception cannot happen as we validated it beforehand.
|
||||
try {
|
||||
Coin amount = Coin.parseCoin(amountEdit.getText());
|
||||
Address destination = Address.fromString(WalletApplication.instance.params, address.getText());
|
||||
Address destination = Address.fromString(app.params(), address.getText());
|
||||
SendRequest req;
|
||||
if (amount.equals(WalletApplication.bitcoin.wallet().getBalance()))
|
||||
if (amount.equals(app.walletAppKit().wallet().getBalance()))
|
||||
req = SendRequest.emptyWallet(destination);
|
||||
else
|
||||
req = SendRequest.to(destination, amount);
|
||||
|
@ -91,7 +93,7 @@ public class SendMoneyController implements OverlayController<SendMoneyControlle
|
|||
// Don't make the user wait for confirmations for now, as the intention is they're sending it
|
||||
// their own money!
|
||||
req.allowUnconfirmed();
|
||||
sendResult = WalletApplication.bitcoin.wallet().sendCoins(req);
|
||||
sendResult = app.walletAppKit().wallet().sendCoins(req);
|
||||
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction result) {
|
||||
|
|
|
@ -57,6 +57,7 @@ public class WalletPasswordController implements OverlayController<WalletPasswor
|
|||
@FXML GridPane widgetGrid;
|
||||
@FXML Label explanationLabel;
|
||||
|
||||
private WalletApplication app;
|
||||
private OverlayableStackPaneController rootController;
|
||||
private OverlayableStackPaneController.OverlayUI<? extends OverlayController<WalletPasswordController>> overlayUI;
|
||||
|
||||
|
@ -69,6 +70,7 @@ public class WalletPasswordController implements OverlayController<WalletPasswor
|
|||
}
|
||||
|
||||
public void initialize() {
|
||||
app = WalletApplication.instance();
|
||||
progressMeter.setOpacity(0);
|
||||
Platform.runLater(pass1::requestFocus);
|
||||
}
|
||||
|
@ -80,13 +82,13 @@ public class WalletPasswordController implements OverlayController<WalletPasswor
|
|||
return;
|
||||
}
|
||||
|
||||
final KeyCrypterScrypt keyCrypter = (KeyCrypterScrypt) WalletApplication.bitcoin.wallet().getKeyCrypter();
|
||||
final KeyCrypterScrypt keyCrypter = (KeyCrypterScrypt) app.walletAppKit().wallet().getKeyCrypter();
|
||||
checkNotNull(keyCrypter); // We should never arrive at this GUI if the wallet isn't actually encrypted.
|
||||
KeyDerivationTasks tasks = new KeyDerivationTasks(keyCrypter, password, getTargetTime()) {
|
||||
@Override
|
||||
protected final void onFinish(KeyParameter aesKey, int timeTakenMsec) {
|
||||
checkGuiThread();
|
||||
if (WalletApplication.bitcoin.wallet().checkAESKey(aesKey)) {
|
||||
if (app.walletAppKit().wallet().checkAESKey(aesKey)) {
|
||||
WalletPasswordController.this.aesKey.set(aesKey);
|
||||
} else {
|
||||
log.warn("User entered incorrect password");
|
||||
|
@ -123,11 +125,11 @@ public class WalletPasswordController implements OverlayController<WalletPasswor
|
|||
// Writes the given time to the wallet as a tag so we can find it again in this class.
|
||||
public static void setTargetTime(Duration targetTime) {
|
||||
ByteString bytes = ByteString.copyFrom(Longs.toByteArray(targetTime.toMillis()));
|
||||
WalletApplication.bitcoin.wallet().setTag(TAG, bytes);
|
||||
WalletApplication.instance().walletAppKit().wallet().setTag(TAG, bytes);
|
||||
}
|
||||
|
||||
// Reads target time or throws if not set yet (should never happen).
|
||||
public static Duration getTargetTime() throws IllegalArgumentException {
|
||||
return Duration.ofMillis(Longs.fromByteArray(WalletApplication.bitcoin.wallet().getTag(TAG).toByteArray()));
|
||||
return Duration.ofMillis(Longs.fromByteArray(WalletApplication.instance().walletAppKit().wallet().getTag(TAG).toByteArray()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ public class WalletSetPasswordController implements OverlayController<WalletSetP
|
|||
public Button closeButton;
|
||||
public Label explanationLabel;
|
||||
|
||||
private WalletApplication app;
|
||||
private OverlayableStackPaneController rootController;
|
||||
private OverlayableStackPaneController.OverlayUI<? extends OverlayController<WalletSetPasswordController>> overlayUI;
|
||||
// These params were determined empirically on a top-range (as of 2014) MacBook Pro with native scrypt support,
|
||||
|
@ -64,6 +65,7 @@ public class WalletSetPasswordController implements OverlayController<WalletSetP
|
|||
}
|
||||
|
||||
public void initialize() {
|
||||
app = WalletApplication.instance();
|
||||
progressMeter.setOpacity(0);
|
||||
}
|
||||
|
||||
|
@ -118,7 +120,7 @@ public class WalletSetPasswordController implements OverlayController<WalletSetP
|
|||
WalletPasswordController.setTargetTime(Duration.ofMillis(timeTakenMsec));
|
||||
// The actual encryption part doesn't take very long as most private keys are derived on demand.
|
||||
log.info("Key derived, now encrypting");
|
||||
WalletApplication.bitcoin.wallet().encrypt(scrypt, aesKey);
|
||||
app.walletAppKit().wallet().encrypt(scrypt, aesKey);
|
||||
log.info("Encryption done");
|
||||
informationalAlert("Wallet encrypted",
|
||||
"You can remove the password at any time from the settings screen.");
|
||||
|
|
|
@ -58,6 +58,7 @@ public class WalletSettingsController implements OverlayController<WalletSetting
|
|||
@FXML TextArea wordsArea;
|
||||
@FXML Button restoreButton;
|
||||
|
||||
private WalletApplication app;
|
||||
private OverlayableStackPaneController rootController;
|
||||
private OverlayableStackPaneController.OverlayUI<? extends OverlayController<WalletSettingsController>> overlayUI;
|
||||
|
||||
|
@ -71,7 +72,8 @@ public class WalletSettingsController implements OverlayController<WalletSetting
|
|||
|
||||
// Note: NOT called by FXMLLoader!
|
||||
public void initialize(@Nullable KeyParameter aesKey) {
|
||||
DeterministicSeed seed = WalletApplication.bitcoin.wallet().getKeyChainSeed();
|
||||
app = WalletApplication.instance();
|
||||
DeterministicSeed seed = app.walletAppKit().wallet().getKeyChainSeed();
|
||||
if (aesKey == null) {
|
||||
if (seed.isEncrypted()) {
|
||||
log.info("Wallet is encrypted, requesting password first.");
|
||||
|
@ -81,7 +83,7 @@ public class WalletSettingsController implements OverlayController<WalletSetting
|
|||
}
|
||||
} else {
|
||||
this.aesKey = aesKey;
|
||||
seed = seed.decrypt(checkNotNull(WalletApplication.bitcoin.wallet().getKeyCrypter()), "", aesKey);
|
||||
seed = seed.decrypt(checkNotNull(app.walletAppKit().wallet().getKeyCrypter()), "", aesKey);
|
||||
// Now we can display the wallet seed as appropriate.
|
||||
passwordButton.setText("Remove password");
|
||||
}
|
||||
|
@ -159,7 +161,7 @@ public class WalletSettingsController implements OverlayController<WalletSetting
|
|||
public void restoreClicked(ActionEvent event) {
|
||||
// Don't allow a restore unless this wallet is presently empty. We don't want to end up with two wallets, too
|
||||
// much complexity, even though WalletAppKit will keep the current one as a backup file in case of disaster.
|
||||
if (WalletApplication.bitcoin.wallet().getBalance().value > 0) {
|
||||
if (app.walletAppKit().wallet().getBalance().value > 0) {
|
||||
informationalAlert("Wallet is not empty",
|
||||
"You must empty this wallet out before attempting to restore an older one, as mixing wallets " +
|
||||
"together can lead to invalidated backups.");
|
||||
|
@ -181,14 +183,14 @@ public class WalletSettingsController implements OverlayController<WalletSetting
|
|||
long birthday = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
|
||||
DeterministicSeed seed = new DeterministicSeed(Splitter.on(' ').splitToList(wordsArea.getText()), null, "", birthday);
|
||||
// Shut down bitcoinj and restart it with the new seed.
|
||||
WalletApplication.bitcoin.addListener(new Service.Listener() {
|
||||
app.walletAppKit().addListener(new Service.Listener() {
|
||||
@Override
|
||||
public void terminated(Service.State from) {
|
||||
WalletApplication.instance.setupWalletKit(seed);
|
||||
WalletApplication.bitcoin.startAsync();
|
||||
app.setupWalletKit(seed);
|
||||
app.walletAppKit().startAsync();
|
||||
}
|
||||
}, Platform::runLater);
|
||||
WalletApplication.bitcoin.stopAsync();
|
||||
app.walletAppKit().stopAsync();
|
||||
}
|
||||
|
||||
|
||||
|
@ -196,7 +198,7 @@ public class WalletSettingsController implements OverlayController<WalletSetting
|
|||
if (aesKey == null) {
|
||||
rootController.overlayUI("wallet_set_password.fxml");
|
||||
} else {
|
||||
WalletApplication.bitcoin.wallet().decrypt(aesKey);
|
||||
app.walletAppKit().wallet().decrypt(aesKey);
|
||||
informationalAlert("Wallet decrypted", "A password will no longer be required to send money or edit settings.");
|
||||
passwordButton.setText("Set password");
|
||||
aesKey = null;
|
||||
|
|
Loading…
Add table
Reference in a new issue