fix naming when using a subtab wallet, import and export wallets with child wallets with db persistence on sparrow exporter

This commit is contained in:
Craig Raw 2021-07-30 14:17:21 +02:00
parent 02d3817cb1
commit 55e69bf263
22 changed files with 104 additions and 39 deletions

2
drongo

@ -1 +1 @@
Subproject commit 46e4413cf6ed052d3377550760c9ffcf64a0c68d
Subproject commit debafd42e8dd017779e193f609717a3eb72cd39a

View file

@ -847,7 +847,7 @@ public class AppController implements Initializable {
for(Keystore copyKeystore : copy.getKeystores()) {
if(copyKeystore.hasSeed()) {
if(copyKeystore.getSeed().needsPassphrase()) {
KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(wallet.getName(), copyKeystore);
KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(wallet.getFullName(), copyKeystore);
Optional<String> optionalPassphrase = passphraseDialog.showAndWait();
if(optionalPassphrase.isPresent()) {
copyKeystore.getSeed().setPassphrase(optionalPassphrase.get());
@ -985,6 +985,14 @@ public class AppController implements Initializable {
checkWalletNetwork(wallet);
restorePublicKeysFromSeed(wallet, null);
addWalletTabOrWindow(storage, wallet, null, false);
for(Wallet childWallet : wallet.getChildWallets()) {
storage.saveWallet(childWallet);
checkWalletNetwork(childWallet);
restorePublicKeysFromSeed(childWallet, null);
addWalletTabOrWindow(storage, childWallet, null, false);
}
Platform.runLater(() -> selectTab(wallet));
} catch(IOException | StorageException | MnemonicException e) {
log.error("Error saving imported wallet", e);
}
@ -1004,6 +1012,15 @@ public class AppController implements Initializable {
checkWalletNetwork(wallet);
restorePublicKeysFromSeed(wallet, key);
addWalletTabOrWindow(storage, wallet, null, false);
for(Wallet childWallet : wallet.getChildWallets()) {
childWallet.encrypt(key);
storage.saveWallet(childWallet);
checkWalletNetwork(childWallet);
restorePublicKeysFromSeed(childWallet, key);
addWalletTabOrWindow(storage, childWallet, null, false);
}
Platform.runLater(() -> selectTab(wallet));
} catch(IOException | StorageException | MnemonicException e) {
log.error("Error saving imported wallet", e);
} finally {
@ -1140,16 +1157,21 @@ public class AppController implements Initializable {
subTabs.getStyleClass().add("master-only");
tab.setContent(subTabs);
WalletForm walletForm = addWalletSubTab(subTabs, storage, wallet, backupWallet);
TabData tabData = new WalletTabData(TabData.TabType.WALLET, walletForm);
tab.setUserData(tabData);
subTabs.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedTab) -> {
if(selectedTab != null) {
EventManager.get().post(new WalletTabSelectedEvent(tab));
}
});
WalletForm walletForm = addWalletSubTab(subTabs, storage, wallet, backupWallet);
TabData tabData = new WalletTabData(TabData.TabType.WALLET, walletForm);
tab.setUserData(tabData);
subTabs.getTabs().addListener((ListChangeListener<Tab>) c -> {
if(c.next() && (c.wasAdded() || c.wasRemoved())) {
EventManager.get().post(new OpenWalletsEvent(tabs.getScene().getWindow(), getOpenWalletTabData()));
}
});
tabs.getTabs().add(tab);
tabs.getSelectionModel().select(tab);
@ -1608,7 +1630,7 @@ public class AppController implements Initializable {
Image image = new Image("image/sparrow-small.png", 50, 50, false, false);
Notifications notificationBuilder = Notifications.create()
.title("Sparrow - " + event.getWallet().getName())
.title("Sparrow - " + event.getWallet().getFullName())
.text(text)
.graphic(new ImageView(image))
.hideAfter(Duration.seconds(15))

View file

@ -80,9 +80,11 @@ public class FileWalletExportPane extends TitledDescriptionPane {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Export " + exporter.getWalletModel().toDisplayString() + " File");
String extension = exporter.getExportFileExtension(wallet);
fileChooser.setInitialFileName(wallet.getName() +
(exporter instanceof Sparrow ? "" : "-" + exporter.getWalletModel().toDisplayString().toLowerCase().replace(" ", "")) +
(extension == null || extension.isEmpty() ? "" : "." + extension));
String fileName = wallet.getFullName() + "-" + exporter.getWalletModel().toDisplayString().toLowerCase().replace(" ", "");
if(exporter instanceof Sparrow) {
fileName = wallet.getMasterName();
}
fileChooser.setInitialFileName(fileName + (extension == null || extension.isEmpty() ? "" : "." + extension));
AppServices.moveToActiveWindowScreen(window, 800, 450);
File file = fileChooser.showSaveDialog(window);
@ -94,7 +96,7 @@ public class FileWalletExportPane extends TitledDescriptionPane {
private void exportWallet(File file) {
if(wallet.isEncrypted() && exporter.walletExportRequiresDecryption()) {
Wallet copy = wallet.copy();
WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getName(), WalletPasswordDialog.PasswordRequirement.LOAD);
WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<SecureString> password = dlg.showAndWait();
if(password.isPresent()) {
final String walletId = AppServices.get().getOpenWallets().get(wallet).getWalletId(wallet);

View file

@ -399,7 +399,7 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
return;
}
WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getName(), WalletPasswordDialog.PasswordRequirement.LOAD);
WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<SecureString> password = dlg.showAndWait();
if(password.isPresent()) {
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(wallet.copy(), password.get());

View file

@ -100,7 +100,7 @@ public class CaravanMultisig implements WalletImport, WalletExport {
try {
CaravanFile cf = new CaravanFile();
cf.name = wallet.getName();
cf.name = wallet.getFullName();
cf.addressType = wallet.getScriptType().toString().replace('-', '_');
cf.network = Network.get().getName();
cf.client = new Client();

View file

@ -183,7 +183,7 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
writer.append("# " + getName() + " setup file (created by Sparrow)\n");
writer.append("#\n");
writer.append("Name: ").append(wallet.getName()).append("\n");
writer.append("Name: ").append(wallet.getFullName()).append("\n");
writer.append("Policy: ").append(Integer.toString(wallet.getDefaultPolicy().getNumSignaturesRequired())).append(" of ").append(Integer.toString(wallet.getKeystores().size())).append("\n");
if(!multipleDerivations) {
writer.append("Derivation: ").append(wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()).append("\n");

View file

@ -12,6 +12,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.StandardCopyOption;
import java.util.Map;
public class Sparrow implements WalletImport, WalletExport {
private static final Logger log = LoggerFactory.getLogger(Sparrow.class);
@ -29,12 +30,20 @@ public class Sparrow implements WalletImport, WalletExport {
@Override
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
try {
Storage storage = AppServices.get().getOpenWallets().get(wallet);
File tempFile = File.createTempFile(wallet.getName(), null);
Storage tempStorage = new Storage(PersistenceType.JSON, tempFile);
Wallet exportedWallet = !wallet.isMasterWallet() ? wallet.getMasterWallet() : wallet;
PersistenceType persistenceType = exportedWallet.getChildWallets().isEmpty() ? PersistenceType.JSON : PersistenceType.DB;
Persistence persistence = persistenceType.getInstance();
Storage storage = AppServices.get().getOpenWallets().get(exportedWallet);
File tempFile = File.createTempFile(exportedWallet.getName(), "." + persistenceType.getExtension());
tempFile.delete();
Storage tempStorage = new Storage(persistence, tempFile);
tempStorage.setKeyDeriver(storage.getKeyDeriver());
tempStorage.setEncryptionPubKey(storage.getEncryptionPubKey());
tempStorage.saveWallet(wallet);
tempStorage.saveWallet(exportedWallet);
for(Wallet childWallet : exportedWallet.getChildWallets()) {
tempStorage.saveWallet(childWallet);
}
persistence.close();
Files.copy(tempStorage.getWalletFile(), outputStream);
outputStream.flush();
tempStorage.getWalletFile().delete();
@ -53,7 +62,8 @@ public class Sparrow implements WalletImport, WalletExport {
public String getExportFileExtension(Wallet wallet) {
try {
Storage storage = AppServices.get().getOpenWallets().get(wallet);
return storage.isEncrypted() ? "" : PersistenceType.JSON.getExtension();
Wallet exportedWallet = !wallet.isMasterWallet() ? wallet.getMasterWallet() : wallet;
return !exportedWallet.getChildWallets().isEmpty() ? PersistenceType.DB.getExtension() : (storage.isEncrypted() ? "" : PersistenceType.JSON.getExtension());
} catch(IOException e) {
//ignore
}
@ -89,13 +99,21 @@ public class Sparrow implements WalletImport, WalletExport {
try {
tempFile = File.createTempFile("sparrow", null);
java.nio.file.Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
storage = new Storage(PersistenceType.JSON, tempFile);
PersistenceType persistenceType = Storage.detectPersistenceType(tempFile);
persistenceType = (persistenceType == null ? PersistenceType.JSON : persistenceType);
File tempTypedFile = new File(tempFile.getParentFile(), tempFile.getName() + "." + persistenceType.getExtension());
tempFile.renameTo(tempTypedFile);
tempFile = tempTypedFile;
storage = new Storage(persistenceType, tempFile);
if(!isEncrypted(tempFile)) {
wallet = storage.loadUnencryptedWallet().getWallet();
} else {
WalletBackupAndKey walletBackupAndKey = storage.loadEncryptedWallet(password);
wallet = walletBackupAndKey.getWallet();
wallet.decrypt(walletBackupAndKey.getKey());
for(Map.Entry<Storage, WalletBackupAndKey> entry : walletBackupAndKey.getChildWallets().entrySet()) {
entry.getValue().getWallet().decrypt(entry.getValue().getKey());
}
}
return wallet;

View file

@ -69,7 +69,7 @@ public class SpecterDIY implements KeystoreFileImport, WalletExport {
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
try {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
writer.append("addwallet ").append(wallet.getName()).append("&").append(OutputDescriptor.getOutputDescriptor(wallet).toString().replace('\'', 'h')).append("\n");
writer.append("addwallet ").append(wallet.getFullName()).append("&").append(OutputDescriptor.getOutputDescriptor(wallet).toString().replace('\'', 'h')).append("\n");
writer.flush();
} catch(Exception e) {
log.error("Error exporting " + getName() + " wallet", e);

View file

@ -21,7 +21,7 @@ public class SpecterDesktop implements WalletImport, WalletExport {
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
try {
SpecterWallet specterWallet = new SpecterWallet();
specterWallet.label = wallet.getName();
specterWallet.label = wallet.getFullName();
specterWallet.blockheight = wallet.getTransactions().values().stream().mapToInt(BlockTransactionHash::getHeight).min().orElse(wallet.getStoredBlockHeight());
specterWallet.descriptor = OutputDescriptor.getOutputDescriptor(wallet).toString(true);

View file

@ -338,6 +338,11 @@ public class Storage {
return type.getInstance().isEncrypted(walletFile);
}
}
PersistenceType detectedType = detectPersistenceType(walletFile);
if(detectedType != null) {
return detectedType.getInstance().isEncrypted(walletFile);
}
} catch(IOException e) {
//ignore
}
@ -345,6 +350,24 @@ public class Storage {
return FileType.BINARY.equals(IOUtils.getFileType(walletFile));
}
public static PersistenceType detectPersistenceType(File walletFile) {
try(Reader reader = new FileReader(walletFile)) {
int firstChar = reader.read();
if(firstChar == 'U' || firstChar == '{') {
return PersistenceType.JSON;
}
if(firstChar == 'H') {
return PersistenceType.DB;
}
} catch(IOException e) {
log.error("Error detecting persistence type", e);
}
return null;
}
public static File getWalletsBackupDir() {
File walletsBackupDir = new File(getWalletsDir(), WALLETS_BACKUP_DIR);
if(!walletsBackupDir.exists()) {

View file

@ -180,7 +180,7 @@ public class DbPersistence implements Persistence {
return;
}
log.debug("Updating " + wallet.getName() + " on " + Thread.currentThread().getName());
log.debug("Updating " + wallet.getFullName() + " on " + Thread.currentThread().getName());
log.debug(dirtyPersistables.toString());
Jdbi jdbi = getJdbi(storage, password);

View file

@ -253,7 +253,7 @@ public class ElectrumServer {
if(!newReferences.isEmpty()) {
//Look for additional nodes to fetch history for by considering the inputs and outputs of new transactions found
log.debug(wallet.getName() + " found new transactions: " + newReferences);
log.debug(wallet.getFullName() + " found new transactions: " + newReferences);
Set<WalletNode> additionalNodes = new HashSet<>();
Map<String, WalletNode> walletScriptHashes = getAllScriptHashes(wallet);
for(BlockTransactionHash reference : newReferences) {
@ -394,7 +394,7 @@ public class ElectrumServer {
Map<String, String> pathScriptHashes = new LinkedHashMap<>();
for(WalletNode node : nodes) {
if(node == null) {
log.error("Null node for wallet " + wallet.getName() + " subscribing nodes " + nodes + " startIndex " + startIndex, new Throwable());
log.error("Null node for wallet " + wallet.getFullName() + " subscribing nodes " + nodes + " startIndex " + startIndex, new Throwable());
}
if(node != null && node.getIndex() >= startIndex) {

View file

@ -708,7 +708,7 @@ public class HeadersController extends TransactionFormController implements Init
String walletId = headersForm.getAvailableWallets().get(headersForm.getSigningWallet()).getWalletId(headersForm.getSigningWallet());
if(copy.isEncrypted()) {
WalletPasswordDialog dlg = new WalletPasswordDialog(copy.getName(), WalletPasswordDialog.PasswordRequirement.LOAD);
WalletPasswordDialog dlg = new WalletPasswordDialog(copy.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<SecureString> password = dlg.showAndWait();
if(password.isPresent()) {
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get());

View file

@ -146,7 +146,7 @@ public class InputController extends TransactionFormController implements Initia
String baseText = getLegendText(txInput);
if(signingWallet != null) {
if(inputForm.isWalletTxo()) {
inputFieldset.setText(baseText + " - " + signingWallet.getName());
inputFieldset.setText(baseText + " - " + signingWallet.getFullName());
inputFieldset.setIcon(TransactionDiagram.getTxoGlyph());
} else {
inputFieldset.setText(baseText + " - Payjoin");

View file

@ -113,7 +113,7 @@ public class AddressesController extends WalletFormController implements Initial
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Export Addresses to CSV");
fileChooser.setInitialFileName(getWalletForm().getWallet().getName() + "-" + keyPurpose.name().toLowerCase() + "-addresses.csv");
fileChooser.setInitialFileName(getWalletForm().getWallet().getFullName() + "-" + keyPurpose.name().toLowerCase() + "-addresses.csv");
Wallet copy = getWalletForm().getWallet().copy();
WalletNode purposeNode = copy.getNode(keyPurpose);

View file

@ -341,7 +341,7 @@ public class KeystoreController extends WalletFormController implements Initiali
Wallet copy = getWalletForm().getWallet().copy();
if(copy.isEncrypted()) {
WalletPasswordDialog dlg = new WalletPasswordDialog(copy.getName(), WalletPasswordDialog.PasswordRequirement.LOAD);
WalletPasswordDialog dlg = new WalletPasswordDialog(copy.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<SecureString> password = dlg.showAndWait();
if(password.isPresent()) {
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get());

View file

@ -406,7 +406,7 @@ public class SettingsController extends WalletFormController implements Initiali
}
if(wallet == null) {
throw new IllegalStateException("Cannot find child wallet " + walletForm.getWallet().getName() + " to export");
throw new IllegalStateException("Cannot find child wallet " + walletForm.getWallet().getFullName() + " to export");
}
WalletExportDialog dlg = new WalletExportDialog(wallet);

View file

@ -93,7 +93,7 @@ public class TransactionEntry extends Entry implements Comparable<TransactionEnt
if(optRef.isPresent()) {
validEntries++;
if(getChildren().stream().noneMatch(entry -> ((HashIndexEntry)entry).getHashIndex().equals(optRef.get().getSpentBy()) && ((HashIndexEntry)entry).getType().equals(HashIndexEntry.Type.INPUT))) {
log.warn("TransactionEntry " + blockTransaction.getHash() + " for wallet " + getWallet().getName() + " missing child for input " + optRef.get().getSpentBy() + " on output " + optRef.get());
log.warn("TransactionEntry " + blockTransaction.getHash() + " for wallet " + getWallet().getFullName() + " missing child for input " + optRef.get().getSpentBy() + " on output " + optRef.get());
return false;
}
}
@ -103,14 +103,14 @@ public class TransactionEntry extends Entry implements Comparable<TransactionEnt
if(optRef.isPresent()) {
validEntries++;
if(getChildren().stream().noneMatch(entry -> ((HashIndexEntry)entry).getHashIndex().equals(optRef.get()) && ((HashIndexEntry)entry).getType().equals(HashIndexEntry.Type.OUTPUT))) {
log.warn("TransactionEntry " + blockTransaction.getHash() + " for wallet " + getWallet().getName() + " missing child for output " + optRef.get());
log.warn("TransactionEntry " + blockTransaction.getHash() + " for wallet " + getWallet().getFullName() + " missing child for output " + optRef.get());
return false;
}
}
}
if(getChildren().size() != validEntries) {
log.warn("TransactionEntry " + blockTransaction.getHash() + " for wallet " + getWallet().getName() + " has incorrect number of children " + getChildren().size() + " (should be " + validEntries + ")");
log.warn("TransactionEntry " + blockTransaction.getHash() + " for wallet " + getWallet().getFullName() + " has incorrect number of children " + getChildren().size() + " (should be " + validEntries + ")");
return false;
}

View file

@ -102,7 +102,7 @@ public class TransactionsController extends WalletFormController implements Init
});
transactionsMasterDetail.setShowDetailNode(Config.get().isShowLoadingLog());
loadingLog.appendText("Wallet loading history for " + getWalletForm().getWallet().getName());
loadingLog.appendText("Wallet loading history for " + getWalletForm().getWallet().getFullName());
loadingLog.setEditable(false);
}
@ -125,7 +125,7 @@ public class TransactionsController extends WalletFormController implements Init
Stage window = new Stage();
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Export Transactions as CSV");
fileChooser.setInitialFileName(getWalletForm().getWallet().getName() + ".csv");
fileChooser.setInitialFileName(getWalletForm().getWallet().getFullName() + ".csv");
AppServices.moveToActiveWindowScreen(window, 800, 450);
File file = fileChooser.showSaveDialog(window);

View file

@ -113,7 +113,7 @@ public class UtxosController extends WalletFormController implements Initializab
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Export UTXOs to CSV");
fileChooser.setInitialFileName(getWalletForm().getWallet().getName() + "-utxos.csv");
fileChooser.setInitialFileName(getWalletForm().getWallet().getFullName() + "-utxos.csv");
AppServices.moveToActiveWindowScreen(window, 800, 450);
File file = fileChooser.showSaveDialog(window);

View file

@ -114,7 +114,7 @@ public class WalletForm {
public void refreshHistory(Integer blockHeight, Wallet pastWallet, WalletNode node) {
Wallet previousWallet = wallet.copy();
if(wallet.isValid() && AppServices.isConnected()) {
log.debug(node == null ? wallet.getName() + " refreshing full wallet history" : wallet.getName() + " requesting node wallet history for " + node.getDerivationPath());
log.debug(node == null ? wallet.getFullName() + " refreshing full wallet history" : wallet.getFullName() + " requesting node wallet history for " + node.getDerivationPath());
ElectrumServer.TransactionHistoryService historyService = new ElectrumServer.TransactionHistoryService(wallet, getWalletTransactionNodes(node));
historyService.setOnSucceeded(workerStateEvent -> {
if(historyService.getValue()) {
@ -363,7 +363,7 @@ public class WalletForm {
if(wallet.isValid()) {
WalletNode walletNode = event.getWalletNode(wallet);
if(walletNode != null) {
log.debug(wallet.getName() + " history event for node " + walletNode + " (" + event.getScriptHash() + ")");
log.debug(wallet.getFullName() + " history event for node " + walletNode + " (" + event.getScriptHash() + ")");
refreshHistory(AppServices.getCurrentBlockHeight(), null, walletNode);
}
}

View file

@ -28,7 +28,7 @@ public abstract class WalletFormController extends BaseController {
for(WalletTabData tabData : event.getClosedWalletTabData()) {
if(tabData.getWalletForm() == walletForm) {
EventManager.get().unregister(this);
} else if(walletForm instanceof SettingsWalletForm && tabData.getStorage() == walletForm.getStorage()) {
} else if(walletForm instanceof SettingsWalletForm && tabData.getStorage().getWalletId(tabData.getWallet()).equals(walletForm.getWalletId())) {
EventManager.get().unregister(this);
}
}