mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
remove tmp backup approach for retaining labels over wallet restarts while refreshing, replaced by detached labels
This commit is contained in:
parent
dd7a3a6c8a
commit
2ca286d826
14 changed files with 97 additions and 260 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 78359961f369f4f8b016973529241048f2ef216d
|
Subproject commit f73cabad3c76c1eb28b4f02b17c9beb608ba2aa4
|
|
@ -861,7 +861,7 @@ public class AppController implements Initializable {
|
||||||
File walletFile = Storage.getWalletFile(nameAndBirthDate.getName());
|
File walletFile = Storage.getWalletFile(nameAndBirthDate.getName());
|
||||||
Storage storage = new Storage(walletFile);
|
Storage storage = new Storage(walletFile);
|
||||||
Wallet wallet = new Wallet(nameAndBirthDate.getName(), PolicyType.SINGLE, ScriptType.P2WPKH, nameAndBirthDate.getBirthDate());
|
Wallet wallet = new Wallet(nameAndBirthDate.getName(), PolicyType.SINGLE, ScriptType.P2WPKH, nameAndBirthDate.getBirthDate());
|
||||||
addWalletTabOrWindow(storage, wallet, null, false);
|
addWalletTabOrWindow(storage, wallet, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -888,8 +888,8 @@ public class AppController implements Initializable {
|
||||||
if(!storage.isEncrypted()) {
|
if(!storage.isEncrypted()) {
|
||||||
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage);
|
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage);
|
||||||
loadWalletService.setOnSucceeded(workerStateEvent -> {
|
loadWalletService.setOnSucceeded(workerStateEvent -> {
|
||||||
WalletBackupAndKey walletBackupAndKey = loadWalletService.getValue();
|
WalletAndKey walletAndKey = loadWalletService.getValue();
|
||||||
openWallet(storage, walletBackupAndKey, this, forceSameWindow);
|
openWallet(storage, walletAndKey, this, forceSameWindow);
|
||||||
});
|
});
|
||||||
loadWalletService.setOnFailed(workerStateEvent -> {
|
loadWalletService.setOnFailed(workerStateEvent -> {
|
||||||
Throwable exception = workerStateEvent.getSource().getException();
|
Throwable exception = workerStateEvent.getSource().getException();
|
||||||
|
@ -912,8 +912,8 @@ public class AppController implements Initializable {
|
||||||
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage, password);
|
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage, password);
|
||||||
loadWalletService.setOnSucceeded(workerStateEvent -> {
|
loadWalletService.setOnSucceeded(workerStateEvent -> {
|
||||||
EventManager.get().post(new StorageEvent(storage.getWalletId(null), TimedEvent.Action.END, "Done"));
|
EventManager.get().post(new StorageEvent(storage.getWalletId(null), TimedEvent.Action.END, "Done"));
|
||||||
WalletBackupAndKey walletBackupAndKey = loadWalletService.getValue();
|
WalletAndKey walletAndKey = loadWalletService.getValue();
|
||||||
openWallet(storage, walletBackupAndKey, this, forceSameWindow);
|
openWallet(storage, walletAndKey, this, forceSameWindow);
|
||||||
});
|
});
|
||||||
loadWalletService.setOnFailed(workerStateEvent -> {
|
loadWalletService.setOnFailed(workerStateEvent -> {
|
||||||
EventManager.get().post(new StorageEvent(storage.getWalletId(null), TimedEvent.Action.END, "Failed"));
|
EventManager.get().post(new StorageEvent(storage.getWalletId(null), TimedEvent.Action.END, "Failed"));
|
||||||
|
@ -947,23 +947,23 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openWallet(Storage storage, WalletBackupAndKey walletBackupAndKey, AppController appController, boolean forceSameWindow) {
|
private void openWallet(Storage storage, WalletAndKey walletAndKey, AppController appController, boolean forceSameWindow) {
|
||||||
try {
|
try {
|
||||||
checkWalletNetwork(walletBackupAndKey.getWallet());
|
checkWalletNetwork(walletAndKey.getWallet());
|
||||||
restorePublicKeysFromSeed(storage, walletBackupAndKey.getWallet(), walletBackupAndKey.getKey());
|
restorePublicKeysFromSeed(storage, walletAndKey.getWallet(), walletAndKey.getKey());
|
||||||
if(!walletBackupAndKey.getWallet().isValid()) {
|
if(!walletAndKey.getWallet().isValid()) {
|
||||||
throw new IllegalStateException("Wallet file is not valid.");
|
throw new IllegalStateException("Wallet file is not valid.");
|
||||||
}
|
}
|
||||||
AppController walletAppController = appController.addWalletTabOrWindow(storage, walletBackupAndKey.getWallet(), walletBackupAndKey.getBackupWallet(), forceSameWindow);
|
AppController walletAppController = appController.addWalletTabOrWindow(storage, walletAndKey.getWallet(), forceSameWindow);
|
||||||
for(Map.Entry<WalletBackupAndKey, Storage> entry : walletBackupAndKey.getChildWallets().entrySet()) {
|
for(Map.Entry<WalletAndKey, Storage> entry : walletAndKey.getChildWallets().entrySet()) {
|
||||||
openWallet(entry.getValue(), entry.getKey(), walletAppController, true);
|
openWallet(entry.getValue(), entry.getKey(), walletAppController, true);
|
||||||
}
|
}
|
||||||
Platform.runLater(() -> selectTab(walletBackupAndKey.getWallet()));
|
Platform.runLater(() -> selectTab(walletAndKey.getWallet()));
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
log.error("Error opening wallet", e);
|
log.error("Error opening wallet", e);
|
||||||
showErrorDialog("Error Opening Wallet", e.getMessage());
|
showErrorDialog("Error Opening Wallet", e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
walletBackupAndKey.clear();
|
walletAndKey.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1141,13 +1141,13 @@ public class AppController implements Initializable {
|
||||||
storage.saveWallet(wallet);
|
storage.saveWallet(wallet);
|
||||||
checkWalletNetwork(wallet);
|
checkWalletNetwork(wallet);
|
||||||
restorePublicKeysFromSeed(storage, wallet, null);
|
restorePublicKeysFromSeed(storage, wallet, null);
|
||||||
addWalletTabOrWindow(storage, wallet, null, false);
|
addWalletTabOrWindow(storage, wallet, false);
|
||||||
|
|
||||||
for(Wallet childWallet : wallet.getChildWallets()) {
|
for(Wallet childWallet : wallet.getChildWallets()) {
|
||||||
storage.saveWallet(childWallet);
|
storage.saveWallet(childWallet);
|
||||||
checkWalletNetwork(childWallet);
|
checkWalletNetwork(childWallet);
|
||||||
restorePublicKeysFromSeed(storage, childWallet, null);
|
restorePublicKeysFromSeed(storage, childWallet, null);
|
||||||
addWalletTabOrWindow(storage, childWallet, null, false);
|
addWalletTabOrWindow(storage, childWallet, false);
|
||||||
}
|
}
|
||||||
Platform.runLater(() -> selectTab(wallet));
|
Platform.runLater(() -> selectTab(wallet));
|
||||||
} catch(IOException | StorageException | MnemonicException e) {
|
} catch(IOException | StorageException | MnemonicException e) {
|
||||||
|
@ -1168,14 +1168,14 @@ public class AppController implements Initializable {
|
||||||
storage.saveWallet(wallet);
|
storage.saveWallet(wallet);
|
||||||
checkWalletNetwork(wallet);
|
checkWalletNetwork(wallet);
|
||||||
restorePublicKeysFromSeed(storage, wallet, key);
|
restorePublicKeysFromSeed(storage, wallet, key);
|
||||||
addWalletTabOrWindow(storage, wallet, null, false);
|
addWalletTabOrWindow(storage, wallet, false);
|
||||||
|
|
||||||
for(Wallet childWallet : wallet.getChildWallets()) {
|
for(Wallet childWallet : wallet.getChildWallets()) {
|
||||||
childWallet.encrypt(key);
|
childWallet.encrypt(key);
|
||||||
storage.saveWallet(childWallet);
|
storage.saveWallet(childWallet);
|
||||||
checkWalletNetwork(childWallet);
|
checkWalletNetwork(childWallet);
|
||||||
restorePublicKeysFromSeed(storage, childWallet, key);
|
restorePublicKeysFromSeed(storage, childWallet, key);
|
||||||
addWalletTabOrWindow(storage, childWallet, null, false);
|
addWalletTabOrWindow(storage, childWallet, false);
|
||||||
}
|
}
|
||||||
Platform.runLater(() -> selectTab(wallet));
|
Platform.runLater(() -> selectTab(wallet));
|
||||||
} catch(IOException | StorageException | MnemonicException e) {
|
} catch(IOException | StorageException | MnemonicException e) {
|
||||||
|
@ -1389,14 +1389,13 @@ public class AppController implements Initializable {
|
||||||
if(selectedWalletForm != null) {
|
if(selectedWalletForm != null) {
|
||||||
Wallet wallet = selectedWalletForm.getWallet();
|
Wallet wallet = selectedWalletForm.getWallet();
|
||||||
Wallet pastWallet = wallet.copy();
|
Wallet pastWallet = wallet.copy();
|
||||||
selectedWalletForm.getStorage().backupTempWallet();
|
|
||||||
wallet.clearHistory();
|
wallet.clearHistory();
|
||||||
AppServices.clearTransactionHistoryCache(wallet);
|
AppServices.clearTransactionHistoryCache(wallet);
|
||||||
EventManager.get().post(new WalletHistoryClearedEvent(wallet, pastWallet, selectedWalletForm.getWalletId()));
|
EventManager.get().post(new WalletHistoryClearedEvent(wallet, pastWallet, selectedWalletForm.getWalletId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppController addWalletTabOrWindow(Storage storage, Wallet wallet, Wallet backupWallet, boolean forceSameWindow) {
|
public AppController addWalletTabOrWindow(Storage storage, Wallet wallet, boolean forceSameWindow) {
|
||||||
Window existingWalletWindow = AppServices.get().getWindowForWallet(storage.getWalletId(wallet));
|
Window existingWalletWindow = AppServices.get().getWindowForWallet(storage.getWalletId(wallet));
|
||||||
if(existingWalletWindow instanceof Stage) {
|
if(existingWalletWindow instanceof Stage) {
|
||||||
Stage existingWalletStage = (Stage)existingWalletWindow;
|
Stage existingWalletStage = (Stage)existingWalletWindow;
|
||||||
|
@ -1411,15 +1410,15 @@ public class AppController implements Initializable {
|
||||||
AppController appController = AppServices.newAppWindow(stage);
|
AppController appController = AppServices.newAppWindow(stage);
|
||||||
stage.toFront();
|
stage.toFront();
|
||||||
stage.setX(AppServices.get().getWalletWindowMaxX() + 30);
|
stage.setX(AppServices.get().getWalletWindowMaxX() + 30);
|
||||||
appController.addWalletTab(storage, wallet, backupWallet);
|
appController.addWalletTab(storage, wallet);
|
||||||
return appController;
|
return appController;
|
||||||
} else {
|
} else {
|
||||||
addWalletTab(storage, wallet, backupWallet);
|
addWalletTab(storage, wallet);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addWalletTab(Storage storage, Wallet wallet, Wallet backupWallet) {
|
public void addWalletTab(Storage storage, Wallet wallet) {
|
||||||
if(wallet.isMasterWallet()) {
|
if(wallet.isMasterWallet()) {
|
||||||
String name = storage.getWalletName(wallet);
|
String name = storage.getWalletName(wallet);
|
||||||
if(!name.equals(wallet.getName())) {
|
if(!name.equals(wallet.getName())) {
|
||||||
|
@ -1449,7 +1448,7 @@ public class AppController implements Initializable {
|
||||||
subTabs.rotateGraphicProperty().set(true);
|
subTabs.rotateGraphicProperty().set(true);
|
||||||
tab.setContent(subTabs);
|
tab.setContent(subTabs);
|
||||||
|
|
||||||
WalletForm walletForm = addWalletSubTab(subTabs, storage, wallet, backupWallet);
|
WalletForm walletForm = addWalletSubTab(subTabs, storage, wallet);
|
||||||
TabData tabData = new WalletTabData(TabData.TabType.WALLET, walletForm);
|
TabData tabData = new WalletTabData(TabData.TabType.WALLET, walletForm);
|
||||||
tab.setUserData(tabData);
|
tab.setUserData(tabData);
|
||||||
tab.setContextMenu(getTabContextMenu(tab));
|
tab.setContextMenu(getTabContextMenu(tab));
|
||||||
|
@ -1478,7 +1477,7 @@ public class AppController implements Initializable {
|
||||||
WalletTabData walletTabData = (WalletTabData)tabData;
|
WalletTabData walletTabData = (WalletTabData)tabData;
|
||||||
if(walletTabData.getWallet() == wallet.getMasterWallet()) {
|
if(walletTabData.getWallet() == wallet.getMasterWallet()) {
|
||||||
TabPane subTabs = (TabPane)walletTab.getContent();
|
TabPane subTabs = (TabPane)walletTab.getContent();
|
||||||
addWalletSubTab(subTabs, storage, wallet, backupWallet);
|
addWalletSubTab(subTabs, storage, wallet);
|
||||||
Tab masterTab = subTabs.getTabs().stream().filter(tab -> ((WalletTabData)tab.getUserData()).getWallet().isMasterWallet()).findFirst().orElse(subTabs.getTabs().get(0));
|
Tab masterTab = subTabs.getTabs().stream().filter(tab -> ((WalletTabData)tab.getUserData()).getWallet().isMasterWallet()).findFirst().orElse(subTabs.getTabs().get(0));
|
||||||
Label masterLabel = (Label)masterTab.getGraphic();
|
Label masterLabel = (Label)masterTab.getGraphic();
|
||||||
masterLabel.setText(wallet.getMasterWallet().getLabel() != null ? wallet.getMasterWallet().getLabel() : wallet.getMasterWallet().getAutomaticName());
|
masterLabel.setText(wallet.getMasterWallet().getLabel() != null ? wallet.getMasterWallet().getLabel() : wallet.getMasterWallet().getAutomaticName());
|
||||||
|
@ -1507,7 +1506,7 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletForm addWalletSubTab(TabPane subTabs, Storage storage, Wallet wallet, Wallet backupWallet) {
|
public WalletForm addWalletSubTab(TabPane subTabs, Storage storage, Wallet wallet) {
|
||||||
try {
|
try {
|
||||||
Tab subTab = new Tab();
|
Tab subTab = new Tab();
|
||||||
subTab.setClosable(false);
|
subTab.setClosable(false);
|
||||||
|
@ -1525,7 +1524,7 @@ public class AppController implements Initializable {
|
||||||
EventManager.get().post(new WalletOpeningEvent(storage, wallet));
|
EventManager.get().post(new WalletOpeningEvent(storage, wallet));
|
||||||
|
|
||||||
//Note that only one WalletForm is created per wallet tab, and registered to listen for events. All wallet controllers (except SettingsController) share this instance.
|
//Note that only one WalletForm is created per wallet tab, and registered to listen for events. All wallet controllers (except SettingsController) share this instance.
|
||||||
WalletForm walletForm = new WalletForm(storage, wallet, backupWallet);
|
WalletForm walletForm = new WalletForm(storage, wallet);
|
||||||
EventManager.get().register(walletForm);
|
EventManager.get().register(walletForm);
|
||||||
controller.setWalletForm(walletForm);
|
controller.setWalletForm(walletForm);
|
||||||
|
|
||||||
|
@ -2478,7 +2477,7 @@ public class AppController implements Initializable {
|
||||||
throw new IllegalStateException("Cannot find storage for master wallet");
|
throw new IllegalStateException("Cannot find storage for master wallet");
|
||||||
}
|
}
|
||||||
|
|
||||||
addWalletTab(storage, event.getChildWallet(), null);
|
addWalletTab(storage, event.getChildWallet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|
|
@ -86,7 +86,6 @@ public class CoinTreeTable extends TreeTableView<Entry> {
|
||||||
if(optDate.isPresent()) {
|
if(optDate.isPresent()) {
|
||||||
Storage storage = AppServices.get().getOpenWallets().get(wallet);
|
Storage storage = AppServices.get().getOpenWallets().get(wallet);
|
||||||
Wallet pastWallet = wallet.copy();
|
Wallet pastWallet = wallet.copy();
|
||||||
storage.backupTempWallet();
|
|
||||||
wallet.setBirthDate(optDate.get());
|
wallet.setBirthDate(optDate.get());
|
||||||
//Trigger background save of birthdate
|
//Trigger background save of birthdate
|
||||||
EventManager.get().post(new WalletDataChangedEvent(wallet));
|
EventManager.get().post(new WalletDataChangedEvent(wallet));
|
||||||
|
|
|
@ -36,29 +36,26 @@ public class JsonPersistence implements Persistence {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WalletBackupAndKey loadWallet(Storage storage) throws IOException, StorageException {
|
public WalletAndKey loadWallet(Storage storage) throws IOException, StorageException {
|
||||||
Wallet wallet;
|
Wallet wallet;
|
||||||
|
|
||||||
try(Reader reader = new FileReader(storage.getWalletFile())) {
|
try(Reader reader = new FileReader(storage.getWalletFile())) {
|
||||||
wallet = gson.fromJson(reader, Wallet.class);
|
wallet = gson.fromJson(reader, Wallet.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<WalletBackupAndKey, Storage> childWallets = loadChildWallets(storage, wallet, null);
|
Map<WalletAndKey, Storage> childWallets = loadChildWallets(storage, wallet, null);
|
||||||
wallet.setChildWallets(childWallets.keySet().stream().map(WalletBackupAndKey::getWallet).collect(Collectors.toList()));
|
wallet.setChildWallets(childWallets.keySet().stream().map(WalletAndKey::getWallet).collect(Collectors.toList()));
|
||||||
|
|
||||||
File backupFile = storage.getTempBackup();
|
return new WalletAndKey(wallet, null, null, childWallets);
|
||||||
Wallet backupWallet = backupFile == null ? null : loadWallet(backupFile, null);
|
|
||||||
|
|
||||||
return new WalletBackupAndKey(wallet, backupWallet, null, null, childWallets);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WalletBackupAndKey loadWallet(Storage storage, CharSequence password) throws IOException, StorageException {
|
public WalletAndKey loadWallet(Storage storage, CharSequence password) throws IOException, StorageException {
|
||||||
return loadWallet(storage, password, null);
|
return loadWallet(storage, password, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WalletBackupAndKey loadWallet(Storage storage, CharSequence password, ECKey alreadyDerivedKey) throws IOException, StorageException {
|
public WalletAndKey loadWallet(Storage storage, CharSequence password, ECKey alreadyDerivedKey) throws IOException, StorageException {
|
||||||
Wallet wallet;
|
Wallet wallet;
|
||||||
ECKey encryptionKey;
|
ECKey encryptionKey;
|
||||||
|
|
||||||
|
@ -68,25 +65,22 @@ public class JsonPersistence implements Persistence {
|
||||||
wallet = gson.fromJson(reader, Wallet.class);
|
wallet = gson.fromJson(reader, Wallet.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<WalletBackupAndKey, Storage> childWallets = loadChildWallets(storage, wallet, encryptionKey);
|
Map<WalletAndKey, Storage> childWallets = loadChildWallets(storage, wallet, encryptionKey);
|
||||||
wallet.setChildWallets(childWallets.keySet().stream().map(WalletBackupAndKey::getWallet).collect(Collectors.toList()));
|
wallet.setChildWallets(childWallets.keySet().stream().map(WalletAndKey::getWallet).collect(Collectors.toList()));
|
||||||
|
|
||||||
File backupFile = storage.getTempBackup();
|
return new WalletAndKey(wallet, encryptionKey, keyDeriver, childWallets);
|
||||||
Wallet backupWallet = backupFile == null ? null : loadWallet(backupFile, encryptionKey);
|
|
||||||
|
|
||||||
return new WalletBackupAndKey(wallet, backupWallet, encryptionKey, keyDeriver, childWallets);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<WalletBackupAndKey, Storage> loadChildWallets(Storage storage, Wallet masterWallet, ECKey encryptionKey) throws IOException, StorageException {
|
private Map<WalletAndKey, Storage> loadChildWallets(Storage storage, Wallet masterWallet, ECKey encryptionKey) throws IOException, StorageException {
|
||||||
File[] walletFiles = getChildWalletFiles(storage.getWalletFile(), masterWallet);
|
File[] walletFiles = getChildWalletFiles(storage.getWalletFile(), masterWallet);
|
||||||
Map<WalletBackupAndKey, Storage> childWallets = new TreeMap<>();
|
Map<WalletAndKey, Storage> childWallets = new TreeMap<>();
|
||||||
for(File childFile : walletFiles) {
|
for(File childFile : walletFiles) {
|
||||||
Wallet childWallet = loadWallet(childFile, encryptionKey);
|
Wallet childWallet = loadWallet(childFile, encryptionKey);
|
||||||
Storage childStorage = new Storage(childFile);
|
Storage childStorage = new Storage(childFile);
|
||||||
childStorage.setEncryptionPubKey(encryptionKey == null ? Storage.NO_PASSWORD_KEY : ECKey.fromPublicOnly(encryptionKey));
|
childStorage.setEncryptionPubKey(encryptionKey == null ? Storage.NO_PASSWORD_KEY : ECKey.fromPublicOnly(encryptionKey));
|
||||||
childStorage.setKeyDeriver(getKeyDeriver());
|
childStorage.setKeyDeriver(getKeyDeriver());
|
||||||
childWallet.setMasterWallet(masterWallet);
|
childWallet.setMasterWallet(masterWallet);
|
||||||
childWallets.put(new WalletBackupAndKey(childWallet, null, encryptionKey, keyDeriver, Collections.emptyMap()), storage);
|
childWallets.put(new WalletAndKey(childWallet, encryptionKey, keyDeriver, Collections.emptyMap()), storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return childWallets;
|
return childWallets;
|
||||||
|
|
|
@ -9,9 +9,9 @@ import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public interface Persistence {
|
public interface Persistence {
|
||||||
WalletBackupAndKey loadWallet(Storage storage) throws IOException, StorageException;
|
WalletAndKey loadWallet(Storage storage) throws IOException, StorageException;
|
||||||
WalletBackupAndKey loadWallet(Storage storage, CharSequence password) throws IOException, StorageException;
|
WalletAndKey loadWallet(Storage storage, CharSequence password) throws IOException, StorageException;
|
||||||
WalletBackupAndKey loadWallet(Storage storage, CharSequence password, ECKey alreadyDerivedKey) throws IOException, StorageException;
|
WalletAndKey loadWallet(Storage storage, CharSequence password, ECKey alreadyDerivedKey) throws IOException, StorageException;
|
||||||
File storeWallet(Storage storage, Wallet wallet) throws IOException, StorageException;
|
File storeWallet(Storage storage, Wallet wallet) throws IOException, StorageException;
|
||||||
File storeWallet(Storage storage, Wallet wallet, ECKey encryptionPubKey) throws IOException, StorageException;
|
File storeWallet(Storage storage, Wallet wallet, ECKey encryptionPubKey) throws IOException, StorageException;
|
||||||
void updateWallet(Storage storage, Wallet wallet) throws IOException, StorageException;
|
void updateWallet(Storage storage, Wallet wallet) throws IOException, StorageException;
|
||||||
|
|
|
@ -111,10 +111,10 @@ public class Sparrow implements WalletImport, WalletExport {
|
||||||
if(!isEncrypted(tempFile)) {
|
if(!isEncrypted(tempFile)) {
|
||||||
wallet = storage.loadUnencryptedWallet().getWallet();
|
wallet = storage.loadUnencryptedWallet().getWallet();
|
||||||
} else {
|
} else {
|
||||||
WalletBackupAndKey walletBackupAndKey = storage.loadEncryptedWallet(password);
|
WalletAndKey walletAndKey = storage.loadEncryptedWallet(password);
|
||||||
wallet = walletBackupAndKey.getWallet();
|
wallet = walletAndKey.getWallet();
|
||||||
wallet.decrypt(walletBackupAndKey.getKey());
|
wallet.decrypt(walletAndKey.getKey());
|
||||||
for(Map.Entry<WalletBackupAndKey, Storage> entry : walletBackupAndKey.getChildWallets().entrySet()) {
|
for(Map.Entry<WalletAndKey, Storage> entry : walletAndKey.getChildWallets().entrySet()) {
|
||||||
entry.getKey().getWallet().decrypt(entry.getKey().getKey());
|
entry.getKey().getWallet().decrypt(entry.getKey().getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import java.nio.file.attribute.PosixFilePermissions;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateEncodingException;
|
import java.security.cert.CertificateEncodingException;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
@ -38,7 +37,6 @@ public class Storage {
|
||||||
public static final String WALLETS_DIR = "wallets";
|
public static final String WALLETS_DIR = "wallets";
|
||||||
public static final String WALLETS_BACKUP_DIR = "backup";
|
public static final String WALLETS_BACKUP_DIR = "backup";
|
||||||
public static final String CERTS_DIR = "certs";
|
public static final String CERTS_DIR = "certs";
|
||||||
public static final String TEMP_BACKUP_PREFIX = "tmp";
|
|
||||||
public static final List<String> RESERVED_WALLET_NAMES = List.of("temp");
|
public static final List<String> RESERVED_WALLET_NAMES = List.of("temp");
|
||||||
|
|
||||||
private Persistence persistence;
|
private Persistence persistence;
|
||||||
|
@ -87,14 +85,14 @@ public class Storage {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletBackupAndKey loadUnencryptedWallet() throws IOException, StorageException {
|
public WalletAndKey loadUnencryptedWallet() throws IOException, StorageException {
|
||||||
WalletBackupAndKey masterWalletAndKey = persistence.loadWallet(this);
|
WalletAndKey masterWalletAndKey = persistence.loadWallet(this);
|
||||||
encryptionPubKey = NO_PASSWORD_KEY;
|
encryptionPubKey = NO_PASSWORD_KEY;
|
||||||
return migrateToDb(masterWalletAndKey);
|
return migrateToDb(masterWalletAndKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletBackupAndKey loadEncryptedWallet(CharSequence password) throws IOException, StorageException {
|
public WalletAndKey loadEncryptedWallet(CharSequence password) throws IOException, StorageException {
|
||||||
WalletBackupAndKey masterWalletAndKey = persistence.loadWallet(this, password);
|
WalletAndKey masterWalletAndKey = persistence.loadWallet(this, password);
|
||||||
encryptionPubKey = ECKey.fromPublicOnly(masterWalletAndKey.getEncryptionKey());
|
encryptionPubKey = ECKey.fromPublicOnly(masterWalletAndKey.getEncryptionKey());
|
||||||
return migrateToDb(masterWalletAndKey);
|
return migrateToDb(masterWalletAndKey);
|
||||||
}
|
}
|
||||||
|
@ -136,14 +134,6 @@ public class Storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void backupTempWallet() {
|
|
||||||
try {
|
|
||||||
backupWallet(TEMP_BACKUP_PREFIX);
|
|
||||||
} catch(IOException e) {
|
|
||||||
log.error("Error creating " + TEMP_BACKUP_PREFIX + " backup wallet", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void backupWallet(String prefix) throws IOException {
|
private void backupWallet(String prefix) throws IOException {
|
||||||
File backupDir = getWalletsBackupDir();
|
File backupDir = getWalletsBackupDir();
|
||||||
|
|
||||||
|
@ -174,16 +164,6 @@ public class Storage {
|
||||||
deleteBackups(null);
|
deleteBackups(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteTempBackups(boolean forceSave) {
|
|
||||||
File[] backups = getBackups(Storage.TEMP_BACKUP_PREFIX);
|
|
||||||
if(backups.length > 0 && (forceSave || hasStartedSince(backups[0]))) {
|
|
||||||
File permanent = new File(backups[0].getParent(), backups[0].getName().substring(Storage.TEMP_BACKUP_PREFIX.length() + 1));
|
|
||||||
backups[0].renameTo(permanent);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteBackups(Storage.TEMP_BACKUP_PREFIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasStartedSince(File lastBackup) {
|
private boolean hasStartedSince(File lastBackup) {
|
||||||
try {
|
try {
|
||||||
Date date = BACKUP_DATE_FORMAT.parse(getBackupDate(lastBackup.getName()));
|
Date date = BACKUP_DATE_FORMAT.parse(getBackupDate(lastBackup.getName()));
|
||||||
|
@ -202,11 +182,6 @@ public class Storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getTempBackup() {
|
|
||||||
File[] backups = getBackups(TEMP_BACKUP_PREFIX);
|
|
||||||
return backups.length == 0 ? null : backups[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
File[] getBackups(String prefix) {
|
File[] getBackups(String prefix) {
|
||||||
File backupDir = getWalletsBackupDir();
|
File backupDir = getWalletsBackupDir();
|
||||||
String walletName = persistence.getWalletName(walletFile, null);
|
String walletName = persistence.getWalletName(walletFile, null);
|
||||||
|
@ -232,7 +207,7 @@ public class Storage {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private WalletBackupAndKey migrateToDb(WalletBackupAndKey masterWalletAndKey) throws IOException, StorageException {
|
private WalletAndKey migrateToDb(WalletAndKey masterWalletAndKey) throws IOException, StorageException {
|
||||||
if(getType() == PersistenceType.JSON) {
|
if(getType() == PersistenceType.JSON) {
|
||||||
log.info("Migrating " + masterWalletAndKey.getWallet().getName() + " from JSON to DB persistence");
|
log.info("Migrating " + masterWalletAndKey.getWallet().getName() + " from JSON to DB persistence");
|
||||||
masterWalletAndKey = migrateType(PersistenceType.DB, masterWalletAndKey.getWallet(), masterWalletAndKey.getEncryptionKey());
|
masterWalletAndKey = migrateType(PersistenceType.DB, masterWalletAndKey.getWallet(), masterWalletAndKey.getEncryptionKey());
|
||||||
|
@ -241,7 +216,7 @@ public class Storage {
|
||||||
return masterWalletAndKey;
|
return masterWalletAndKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private WalletBackupAndKey migrateType(PersistenceType type, Wallet wallet, ECKey encryptionKey) throws IOException, StorageException {
|
private WalletAndKey migrateType(PersistenceType type, Wallet wallet, ECKey encryptionKey) throws IOException, StorageException {
|
||||||
File existingFile = walletFile;
|
File existingFile = walletFile;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -530,7 +505,7 @@ public class Storage {
|
||||||
return ownerOnly;
|
return ownerOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LoadWalletService extends Service<WalletBackupAndKey> {
|
public static class LoadWalletService extends Service<WalletAndKey> {
|
||||||
private final Storage storage;
|
private final Storage storage;
|
||||||
private final SecureString password;
|
private final SecureString password;
|
||||||
|
|
||||||
|
@ -545,19 +520,19 @@ public class Storage {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Task<WalletBackupAndKey> createTask() {
|
protected Task<WalletAndKey> createTask() {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
protected WalletBackupAndKey call() throws IOException, StorageException {
|
protected WalletAndKey call() throws IOException, StorageException {
|
||||||
WalletBackupAndKey walletBackupAndKey;
|
WalletAndKey walletAndKey;
|
||||||
|
|
||||||
if(password != null) {
|
if(password != null) {
|
||||||
walletBackupAndKey = storage.loadEncryptedWallet(password);
|
walletAndKey = storage.loadEncryptedWallet(password);
|
||||||
password.clear();
|
password.clear();
|
||||||
} else {
|
} else {
|
||||||
walletBackupAndKey = storage.loadUnencryptedWallet();
|
walletAndKey = storage.loadUnencryptedWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
return walletBackupAndKey;
|
return walletAndKey;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,14 @@ import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class WalletBackupAndKey implements Comparable<WalletBackupAndKey> {
|
public class WalletAndKey implements Comparable<WalletAndKey> {
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private final Wallet backupWallet;
|
|
||||||
private final ECKey encryptionKey;
|
private final ECKey encryptionKey;
|
||||||
private final Key key;
|
private final Key key;
|
||||||
private final Map<WalletBackupAndKey, Storage> childWallets;
|
private final Map<WalletAndKey, Storage> childWallets;
|
||||||
|
|
||||||
public WalletBackupAndKey(Wallet wallet, Wallet backupWallet, ECKey encryptionKey, AsymmetricKeyDeriver keyDeriver, Map<WalletBackupAndKey, Storage> childWallets) {
|
public WalletAndKey(Wallet wallet, ECKey encryptionKey, AsymmetricKeyDeriver keyDeriver, Map<WalletAndKey, Storage> childWallets) {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.backupWallet = backupWallet;
|
|
||||||
this.encryptionKey = encryptionKey;
|
this.encryptionKey = encryptionKey;
|
||||||
this.key = encryptionKey == null ? null : new Key(encryptionKey.getPrivKeyBytes(), keyDeriver.getSalt(), EncryptionType.Deriver.ARGON2);
|
this.key = encryptionKey == null ? null : new Key(encryptionKey.getPrivKeyBytes(), keyDeriver.getSalt(), EncryptionType.Deriver.ARGON2);
|
||||||
this.childWallets = childWallets;
|
this.childWallets = childWallets;
|
||||||
|
@ -24,10 +22,6 @@ public class WalletBackupAndKey implements Comparable<WalletBackupAndKey> {
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Wallet getBackupWallet() {
|
|
||||||
return backupWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ECKey getEncryptionKey() {
|
public ECKey getEncryptionKey() {
|
||||||
return encryptionKey;
|
return encryptionKey;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +30,7 @@ public class WalletBackupAndKey implements Comparable<WalletBackupAndKey> {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<WalletBackupAndKey, Storage> getChildWallets() {
|
public Map<WalletAndKey, Storage> getChildWallets() {
|
||||||
return childWallets;
|
return childWallets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +44,7 @@ public class WalletBackupAndKey implements Comparable<WalletBackupAndKey> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(WalletBackupAndKey other) {
|
public int compareTo(WalletAndKey other) {
|
||||||
return wallet.compareTo(other.wallet);
|
return wallet.compareTo(other.wallet);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -66,17 +66,17 @@ public class DbPersistence implements Persistence {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WalletBackupAndKey loadWallet(Storage storage) throws IOException, StorageException {
|
public WalletAndKey loadWallet(Storage storage) throws IOException, StorageException {
|
||||||
return loadWallet(storage, null, null);
|
return loadWallet(storage, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WalletBackupAndKey loadWallet(Storage storage, CharSequence password) throws IOException, StorageException {
|
public WalletAndKey loadWallet(Storage storage, CharSequence password) throws IOException, StorageException {
|
||||||
return loadWallet(storage, password, null);
|
return loadWallet(storage, password, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WalletBackupAndKey loadWallet(Storage storage, CharSequence password, ECKey alreadyDerivedKey) throws IOException, StorageException {
|
public WalletAndKey loadWallet(Storage storage, CharSequence password, ECKey alreadyDerivedKey) throws IOException, StorageException {
|
||||||
ECKey encryptionKey = getEncryptionKey(password, storage.getWalletFile(), alreadyDerivedKey);
|
ECKey encryptionKey = getEncryptionKey(password, storage.getWalletFile(), alreadyDerivedKey);
|
||||||
|
|
||||||
migrate(storage, MASTER_SCHEMA, encryptionKey);
|
migrate(storage, MASTER_SCHEMA, encryptionKey);
|
||||||
|
@ -87,31 +87,22 @@ public class DbPersistence implements Persistence {
|
||||||
return walletDao.getMainWallet(MASTER_SCHEMA);
|
return walletDao.getMainWallet(MASTER_SCHEMA);
|
||||||
});
|
});
|
||||||
|
|
||||||
File backupFile = storage.getTempBackup();
|
Map<WalletAndKey, Storage> childWallets = loadChildWallets(storage, masterWallet, encryptionKey);
|
||||||
Wallet backupWallet = null;
|
masterWallet.setChildWallets(childWallets.keySet().stream().map(WalletAndKey::getWallet).collect(Collectors.toList()));
|
||||||
if(backupFile != null) {
|
|
||||||
Persistence backupPersistence = PersistenceType.DB.getInstance();
|
|
||||||
backupPersistence.setKeyDeriver(keyDeriver);
|
|
||||||
backupWallet = backupPersistence.loadWallet(new Storage(backupPersistence, backupFile), password, encryptionKey).getWallet();
|
|
||||||
backupPersistence.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<WalletBackupAndKey, Storage> childWallets = loadChildWallets(storage, masterWallet, backupWallet, encryptionKey);
|
|
||||||
masterWallet.setChildWallets(childWallets.keySet().stream().map(WalletBackupAndKey::getWallet).collect(Collectors.toList()));
|
|
||||||
|
|
||||||
createUpdateExecutor(masterWallet);
|
createUpdateExecutor(masterWallet);
|
||||||
|
|
||||||
return new WalletBackupAndKey(masterWallet, backupWallet, encryptionKey, keyDeriver, childWallets);
|
return new WalletAndKey(masterWallet, encryptionKey, keyDeriver, childWallets);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<WalletBackupAndKey, Storage> loadChildWallets(Storage storage, Wallet masterWallet, Wallet backupWallet, ECKey encryptionKey) throws StorageException {
|
private Map<WalletAndKey, Storage> loadChildWallets(Storage storage, Wallet masterWallet, ECKey encryptionKey) throws StorageException {
|
||||||
Jdbi jdbi = getJdbi(storage, getFilePassword(encryptionKey));
|
Jdbi jdbi = getJdbi(storage, getFilePassword(encryptionKey));
|
||||||
List<String> schemas = jdbi.withHandle(handle -> {
|
List<String> schemas = jdbi.withHandle(handle -> {
|
||||||
return handle.createQuery("show schemas").mapTo(String.class).list();
|
return handle.createQuery("show schemas").mapTo(String.class).list();
|
||||||
});
|
});
|
||||||
|
|
||||||
List<String> childSchemas = schemas.stream().filter(schema -> schema.startsWith(WALLET_SCHEMA_PREFIX) && !schema.equals(MASTER_SCHEMA)).collect(Collectors.toList());
|
List<String> childSchemas = schemas.stream().filter(schema -> schema.startsWith(WALLET_SCHEMA_PREFIX) && !schema.equals(MASTER_SCHEMA)).collect(Collectors.toList());
|
||||||
Map<WalletBackupAndKey, Storage> childWallets = new TreeMap<>();
|
Map<WalletAndKey, Storage> childWallets = new TreeMap<>();
|
||||||
for(String schema : childSchemas) {
|
for(String schema : childSchemas) {
|
||||||
migrate(storage, schema, encryptionKey);
|
migrate(storage, schema, encryptionKey);
|
||||||
|
|
||||||
|
@ -123,8 +114,7 @@ public class DbPersistence implements Persistence {
|
||||||
childWallet.setMasterWallet(masterWallet);
|
childWallet.setMasterWallet(masterWallet);
|
||||||
return childWallet;
|
return childWallet;
|
||||||
});
|
});
|
||||||
Wallet backupChildWallet = backupWallet == null ? null : backupWallet.getChildWallets().stream().filter(child -> wallet.getName().equals(child.getName())).findFirst().orElse(null);
|
childWallets.put(new WalletAndKey(wallet, encryptionKey, keyDeriver, Collections.emptyMap()), storage);
|
||||||
childWallets.put(new WalletBackupAndKey(wallet, backupChildWallet, encryptionKey, keyDeriver, Collections.emptyMap()), storage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return childWallets;
|
return childWallets;
|
||||||
|
|
|
@ -90,45 +90,4 @@ public class NodeEntry extends Entry implements Comparable<NodeEntry> {
|
||||||
public int compareTo(NodeEntry other) {
|
public int compareTo(NodeEntry other) {
|
||||||
return node.compareTo(other.node);
|
return node.compareTo(other.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Entry> copyLabels(WalletNode pastNode) {
|
|
||||||
if(pastNode == null) {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Entry> changedEntries = new LinkedHashSet<>();
|
|
||||||
|
|
||||||
if(node.getLabel() == null && pastNode.getLabel() != null) {
|
|
||||||
node.setLabel(pastNode.getLabel());
|
|
||||||
labelProperty().set(pastNode.getLabel());
|
|
||||||
changedEntries.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(Entry childEntry : getChildren()) {
|
|
||||||
if(childEntry instanceof HashIndexEntry) {
|
|
||||||
HashIndexEntry hashIndexEntry = (HashIndexEntry)childEntry;
|
|
||||||
BlockTransactionHashIndex txo = hashIndexEntry.getHashIndex();
|
|
||||||
Optional<BlockTransactionHashIndex> optPastTxo = pastNode.getTransactionOutputs().stream().filter(pastTxo -> pastTxo.equals(txo)).findFirst();
|
|
||||||
if(optPastTxo.isPresent()) {
|
|
||||||
BlockTransactionHashIndex pastTxo = optPastTxo.get();
|
|
||||||
if(txo.getLabel() == null && pastTxo.getLabel() != null) {
|
|
||||||
txo.setLabel(pastTxo.getLabel());
|
|
||||||
changedEntries.add(childEntry);
|
|
||||||
}
|
|
||||||
if(txo.isSpent() && pastTxo.isSpent() && txo.getSpentBy().getLabel() == null && pastTxo.getSpentBy().getLabel() != null) {
|
|
||||||
txo.getSpentBy().setLabel(pastTxo.getSpentBy().getLabel());
|
|
||||||
changedEntries.add(childEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(childEntry instanceof NodeEntry) {
|
|
||||||
NodeEntry childNodeEntry = (NodeEntry)childEntry;
|
|
||||||
Optional<WalletNode> optPastChildNodeEntry = pastNode.getChildren().stream().filter(childNodeEntry.node::equals).findFirst();
|
|
||||||
optPastChildNodeEntry.ifPresent(pastChildNode -> changedEntries.addAll(childNodeEntry.copyLabels(pastChildNode)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changedEntries;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -278,7 +278,10 @@ public class ReceiveController extends WalletFormController implements Initializ
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void walletNodesChanged(WalletNodesChangedEvent event) {
|
public void walletNodesChanged(WalletNodesChangedEvent event) {
|
||||||
if(event.getWallet().equals(walletForm.getWallet())) {
|
if(event.getWallet().equals(walletForm.getWallet())) {
|
||||||
currentEntry = null;
|
if(currentEntry != null) {
|
||||||
|
label.textProperty().unbindBidirectional(currentEntry.labelProperty());
|
||||||
|
currentEntry = null;
|
||||||
|
}
|
||||||
refreshAddress();
|
refreshAddress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ public class SettingsWalletForm extends WalletForm {
|
||||||
private Wallet walletCopy;
|
private Wallet walletCopy;
|
||||||
|
|
||||||
public SettingsWalletForm(Storage storage, Wallet currentWallet) {
|
public SettingsWalletForm(Storage storage, Wallet currentWallet) {
|
||||||
super(storage, currentWallet, null, false);
|
super(storage, currentWallet, false);
|
||||||
this.walletCopy = currentWallet.copy();
|
this.walletCopy = currentWallet.copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,15 +51,6 @@ public class SettingsWalletForm extends WalletForm {
|
||||||
boolean addressChange = isAddressChange();
|
boolean addressChange = isAddressChange();
|
||||||
|
|
||||||
if(wallet.isValid()) {
|
if(wallet.isValid()) {
|
||||||
//Don't create temp backup on changing addresses - there are no labels to lose
|
|
||||||
if(!addressChange) {
|
|
||||||
backgroundUpdate(); //Save existing wallet here for the temp backup in case password has been changed - this will update the password on the existing wallet
|
|
||||||
if(AppServices.isConnected()) {
|
|
||||||
//Backup the wallet so labels will survive application shutdown
|
|
||||||
getStorage().backupTempWallet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Clear transaction history cache before we clear the nodes
|
//Clear transaction history cache before we clear the nodes
|
||||||
AppServices.clearTransactionHistoryCache(wallet);
|
AppServices.clearTransactionHistoryCache(wallet);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,10 @@ import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.WalletTabData;
|
import com.sparrowwallet.sparrow.WalletTabData;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
|
||||||
import com.sparrowwallet.sparrow.io.StorageException;
|
import com.sparrowwallet.sparrow.io.StorageException;
|
||||||
import com.sparrowwallet.sparrow.net.AllHistoryChangedException;
|
import com.sparrowwallet.sparrow.net.AllHistoryChangedException;
|
||||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.net.ServerType;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
@ -26,7 +24,6 @@ import org.slf4j.LoggerFactory;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static com.sparrowwallet.drongo.wallet.WalletNode.nodeRangesToString;
|
import static com.sparrowwallet.drongo.wallet.WalletNode.nodeRangesToString;
|
||||||
|
|
||||||
|
@ -35,7 +32,6 @@ public class WalletForm {
|
||||||
|
|
||||||
private final Storage storage;
|
private final Storage storage;
|
||||||
protected Wallet wallet;
|
protected Wallet wallet;
|
||||||
private Wallet savedPastWallet;
|
|
||||||
|
|
||||||
private WalletTransactionsEntry walletTransactionsEntry;
|
private WalletTransactionsEntry walletTransactionsEntry;
|
||||||
private WalletUtxosEntry walletUtxosEntry;
|
private WalletUtxosEntry walletUtxosEntry;
|
||||||
|
@ -47,19 +43,16 @@ public class WalletForm {
|
||||||
|
|
||||||
private final BooleanProperty lockedProperty = new SimpleBooleanProperty(false);
|
private final BooleanProperty lockedProperty = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
public WalletForm(Storage storage, Wallet currentWallet, Wallet backupWallet) {
|
public WalletForm(Storage storage, Wallet currentWallet) {
|
||||||
this(storage, currentWallet, backupWallet, true);
|
this(storage, currentWallet, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletForm(Storage storage, Wallet currentWallet, Wallet backupWallet, boolean refreshHistory) {
|
public WalletForm(Storage storage, Wallet currentWallet, boolean refreshHistory) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.wallet = currentWallet;
|
this.wallet = currentWallet;
|
||||||
|
|
||||||
//Unencrypted wallets load before isConnected is true, waiting for the ConnectionEvent to refresh history - save the backup for this event
|
|
||||||
savedPastWallet = backupWallet;
|
|
||||||
|
|
||||||
if(refreshHistory && wallet.isValid()) {
|
if(refreshHistory && wallet.isValid()) {
|
||||||
refreshHistory(AppServices.getCurrentBlockHeight(), backupWallet);
|
refreshHistory(AppServices.getCurrentBlockHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,11 +93,9 @@ public class WalletForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveAndRefresh() throws IOException, StorageException {
|
public void saveAndRefresh() throws IOException, StorageException {
|
||||||
Wallet pastWallet = wallet.copy();
|
|
||||||
storage.backupTempWallet();
|
|
||||||
wallet.clearHistory();
|
wallet.clearHistory();
|
||||||
save();
|
save();
|
||||||
refreshHistory(AppServices.getCurrentBlockHeight(), pastWallet);
|
refreshHistory(AppServices.getCurrentBlockHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveBackup() throws IOException {
|
public void saveBackup() throws IOException {
|
||||||
|
@ -124,11 +115,11 @@ public class WalletForm {
|
||||||
storage.deleteBackups();
|
storage.deleteBackups();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshHistory(Integer blockHeight, Wallet pastWallet) {
|
public void refreshHistory(Integer blockHeight) {
|
||||||
refreshHistory(blockHeight, pastWallet, null);
|
refreshHistory(blockHeight, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshHistory(Integer blockHeight, Wallet pastWallet, Set<WalletNode> nodes) {
|
public void refreshHistory(Integer blockHeight, Set<WalletNode> nodes) {
|
||||||
Wallet previousWallet = wallet.copy();
|
Wallet previousWallet = wallet.copy();
|
||||||
if(wallet.isValid() && AppServices.isConnected()) {
|
if(wallet.isValid() && AppServices.isConnected()) {
|
||||||
if(log.isDebugEnabled()) {
|
if(log.isDebugEnabled()) {
|
||||||
|
@ -139,7 +130,7 @@ public class WalletForm {
|
||||||
historyService.setOnSucceeded(workerStateEvent -> {
|
historyService.setOnSucceeded(workerStateEvent -> {
|
||||||
if(historyService.getValue()) {
|
if(historyService.getValue()) {
|
||||||
EventManager.get().post(new WalletHistoryFinishedEvent(wallet));
|
EventManager.get().post(new WalletHistoryFinishedEvent(wallet));
|
||||||
updateWallet(blockHeight, pastWallet, previousWallet);
|
updateWallet(blockHeight, previousWallet);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
historyService.setOnFailed(workerStateEvent -> {
|
historyService.setOnFailed(workerStateEvent -> {
|
||||||
|
@ -152,7 +143,7 @@ public class WalletForm {
|
||||||
|
|
||||||
wallet.clearHistory();
|
wallet.clearHistory();
|
||||||
AppServices.clearTransactionHistoryCache(wallet);
|
AppServices.clearTransactionHistoryCache(wallet);
|
||||||
EventManager.get().post(new WalletHistoryClearedEvent(wallet, pastWallet == null ? previousWallet : pastWallet, getWalletId()));
|
EventManager.get().post(new WalletHistoryClearedEvent(wallet, previousWallet, getWalletId()));
|
||||||
} else {
|
} else {
|
||||||
if(AppServices.isConnected()) {
|
if(AppServices.isConnected()) {
|
||||||
log.error("Error retrieving wallet history", workerStateEvent.getSource().getException());
|
log.error("Error retrieving wallet history", workerStateEvent.getSource().getException());
|
||||||
|
@ -169,72 +160,20 @@ public class WalletForm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateWallet(Integer blockHeight, Wallet pastWallet, Wallet previousWallet) {
|
private void updateWallet(Integer blockHeight, Wallet previousWallet) {
|
||||||
if(blockHeight != null) {
|
if(blockHeight != null) {
|
||||||
wallet.setStoredBlockHeight(blockHeight);
|
wallet.setStoredBlockHeight(blockHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
//After the wallet settings are changed, the previous wallet is copied to pastWallet and used here to copy labels from past nodes, txos and txes
|
notifyIfChanged(blockHeight, previousWallet);
|
||||||
Set<Entry> labelChangedEntries = Collections.emptySet();
|
|
||||||
if(pastWallet != null) {
|
|
||||||
labelChangedEntries = copyLabels(pastWallet);
|
|
||||||
copyMixData(pastWallet);
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyIfChanged(blockHeight, previousWallet, labelChangedEntries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Entry> copyLabels(Wallet pastWallet) {
|
private void notifyIfChanged(Integer blockHeight, Wallet previousWallet) {
|
||||||
Set<Entry> changedEntries = new LinkedHashSet<>();
|
|
||||||
|
|
||||||
//On a full wallet refresh, walletUtxosEntry and walletTransactionsEntry will have no children yet, but AddressesController may have created accountEntries on a walletNodesChangedEvent
|
|
||||||
//Copy nodeEntry labels
|
|
||||||
for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) {
|
|
||||||
NodeEntry purposeEntry = getNodeEntry(keyPurpose);
|
|
||||||
changedEntries.addAll(purposeEntry.copyLabels(pastWallet.getNode(purposeEntry.getNode().getKeyPurpose())));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Copy node and txo labels
|
|
||||||
for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) {
|
|
||||||
if(wallet.getNode(keyPurpose).copyLabels(pastWallet.getNode(keyPurpose))) {
|
|
||||||
changedEntries.add(getWalletUtxosEntry());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Copy tx labels
|
|
||||||
for(Map.Entry<Sha256Hash, BlockTransaction> txEntry : wallet.getTransactions().entrySet()) {
|
|
||||||
BlockTransaction pastBlockTransaction = pastWallet.getTransactions().get(txEntry.getKey());
|
|
||||||
if(pastBlockTransaction != null && txEntry.getValue() != null && txEntry.getValue().getLabel() == null && pastBlockTransaction.getLabel() != null) {
|
|
||||||
txEntry.getValue().setLabel(pastBlockTransaction.getLabel());
|
|
||||||
changedEntries.add(getWalletTransactionsEntry());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Force saving the backup if the current wallet has fewer transactions than the past wallet (i.e. incomplete load)
|
|
||||||
storage.deleteTempBackups(wallet.getTransactions().size() < pastWallet.getTransactions().size());
|
|
||||||
|
|
||||||
return changedEntries;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyMixData(Wallet pastWallet) {
|
|
||||||
wallet.getUtxoMixes().forEach(pastWallet.getUtxoMixes()::putIfAbsent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyIfChanged(Integer blockHeight, Wallet previousWallet, Set<Entry> labelChangedEntries) {
|
|
||||||
List<WalletNode> historyChangedNodes = new ArrayList<>();
|
List<WalletNode> historyChangedNodes = new ArrayList<>();
|
||||||
historyChangedNodes.addAll(getHistoryChangedNodes(previousWallet.getNode(KeyPurpose.RECEIVE).getChildren(), wallet.getNode(KeyPurpose.RECEIVE).getChildren()));
|
historyChangedNodes.addAll(getHistoryChangedNodes(previousWallet.getNode(KeyPurpose.RECEIVE).getChildren(), wallet.getNode(KeyPurpose.RECEIVE).getChildren()));
|
||||||
historyChangedNodes.addAll(getHistoryChangedNodes(previousWallet.getNode(KeyPurpose.CHANGE).getChildren(), wallet.getNode(KeyPurpose.CHANGE).getChildren()));
|
historyChangedNodes.addAll(getHistoryChangedNodes(previousWallet.getNode(KeyPurpose.CHANGE).getChildren(), wallet.getNode(KeyPurpose.CHANGE).getChildren()));
|
||||||
|
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
if(!labelChangedEntries.isEmpty()) {
|
|
||||||
List<Entry> eventEntries = labelChangedEntries.stream().filter(entry -> entry != getWalletTransactionsEntry() && entry != getWalletUtxosEntry()).collect(Collectors.toList());
|
|
||||||
if(!eventEntries.isEmpty()) {
|
|
||||||
Platform.runLater(() -> EventManager.get().post(new WalletEntryLabelsChangedEvent(wallet, eventEntries)));
|
|
||||||
}
|
|
||||||
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!historyChangedNodes.isEmpty()) {
|
if(!historyChangedNodes.isEmpty()) {
|
||||||
Platform.runLater(() -> EventManager.get().post(new WalletHistoryChangedEvent(wallet, storage, historyChangedNodes)));
|
Platform.runLater(() -> EventManager.get().post(new WalletHistoryChangedEvent(wallet, storage, historyChangedNodes)));
|
||||||
changed = true;
|
changed = true;
|
||||||
|
@ -374,14 +313,9 @@ public class WalletForm {
|
||||||
accountEntries.clear();
|
accountEntries.clear();
|
||||||
EventManager.get().post(new WalletNodesChangedEvent(wallet));
|
EventManager.get().post(new WalletNodesChangedEvent(wallet));
|
||||||
|
|
||||||
//It is necessary to save the past wallet because the actual copying of the past labels only occurs on a later ConnectionEvent with bwt
|
|
||||||
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
|
|
||||||
savedPastWallet = event.getPastWallet();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Clear the cache - we will need to fetch everything again
|
//Clear the cache - we will need to fetch everything again
|
||||||
AppServices.clearTransactionHistoryCache(wallet);
|
AppServices.clearTransactionHistoryCache(wallet);
|
||||||
refreshHistory(AppServices.getCurrentBlockHeight(), event.getPastWallet());
|
refreshHistory(AppServices.getCurrentBlockHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,14 +351,13 @@ public class WalletForm {
|
||||||
public void newBlock(NewBlockEvent event) {
|
public void newBlock(NewBlockEvent event) {
|
||||||
//Check if wallet is valid to avoid saving wallets in initial setup
|
//Check if wallet is valid to avoid saving wallets in initial setup
|
||||||
if(wallet.isValid()) {
|
if(wallet.isValid()) {
|
||||||
updateWallet(event.getHeight(), null, wallet.copy());
|
updateWallet(event.getHeight(), wallet.copy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void connected(ConnectionEvent event) {
|
public void connected(ConnectionEvent event) {
|
||||||
refreshHistory(event.getBlockHeight(), savedPastWallet);
|
refreshHistory(event.getBlockHeight());
|
||||||
savedPastWallet = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
@ -437,7 +370,7 @@ public class WalletForm {
|
||||||
WalletNode walletNode = event.getWalletNode(wallet);
|
WalletNode walletNode = event.getWalletNode(wallet);
|
||||||
if(walletNode != null) {
|
if(walletNode != null) {
|
||||||
log.debug(wallet.getFullName() + " history event for node " + walletNode + " (" + event.getScriptHash() + ")");
|
log.debug(wallet.getFullName() + " history event for node " + walletNode + " (" + event.getScriptHash() + ")");
|
||||||
refreshHistory(AppServices.getCurrentBlockHeight(), null, Set.of(walletNode));
|
refreshHistory(AppServices.getCurrentBlockHeight(), Set.of(walletNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -576,7 +509,7 @@ public class WalletForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!newNodes.isEmpty()) {
|
if(!newNodes.isEmpty()) {
|
||||||
Platform.runLater(() -> refreshHistory(AppServices.getCurrentBlockHeight(), null, newNodes));
|
Platform.runLater(() -> refreshHistory(AppServices.getCurrentBlockHeight(), newNodes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class StorageTest extends IoTest {
|
||||||
@Test
|
@Test
|
||||||
public void loadSeedWallet() throws IOException, MnemonicException, StorageException {
|
public void loadSeedWallet() throws IOException, MnemonicException, StorageException {
|
||||||
Storage storage = new Storage(getFile("sparrow-single-seed-wallet"));
|
Storage storage = new Storage(getFile("sparrow-single-seed-wallet"));
|
||||||
WalletBackupAndKey walletAndKey = storage.loadEncryptedWallet("pass");
|
WalletAndKey walletAndKey = storage.loadEncryptedWallet("pass");
|
||||||
Wallet wallet = walletAndKey.getWallet();
|
Wallet wallet = walletAndKey.getWallet();
|
||||||
Wallet copy = wallet.copy();
|
Wallet copy = wallet.copy();
|
||||||
copy.decrypt(walletAndKey.getKey());
|
copy.decrypt(walletAndKey.getKey());
|
||||||
|
|
Loading…
Reference in a new issue