mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-07-30 19:46:16 +00:00
persist silent payment address mappings for safe rbf of sp-sending transactions
This commit is contained in:
+1
-1
Submodule drongo updated: 7df781c77c...698f8b08a1
+9
@@ -0,0 +1,9 @@
|
||||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
|
||||
public class WalletSilentPaymentAddressesChangedEvent extends WalletChangedEvent {
|
||||
public WalletSilentPaymentAddressesChangedEvent(Wallet wallet) {
|
||||
super(wallet.resolveMasterWallet());
|
||||
}
|
||||
}
|
||||
@@ -337,6 +337,11 @@ public class DbPersistence implements Persistence {
|
||||
walletConfigDao.addOrUpdate(wallet, wallet.getWalletConfig());
|
||||
}
|
||||
|
||||
if(dirtyPersistables.silentPaymentAddresses) {
|
||||
SilentPaymentAddressDao silentPaymentAddressDao = handle.attach(SilentPaymentAddressDao.class);
|
||||
silentPaymentAddressDao.clearAndAddAll(wallet);
|
||||
}
|
||||
|
||||
if(dirtyPersistables.walletTable != null) {
|
||||
WalletTableDao walletTableDao = handle.attach(WalletTableDao.class);
|
||||
walletTableDao.addOrUpdate(wallet, dirtyPersistables.walletTable.getTableType(), dirtyPersistables.walletTable);
|
||||
@@ -888,6 +893,13 @@ public class DbPersistence implements Persistence {
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletSilentPaymentAddressesChanged(WalletSilentPaymentAddressesChangedEvent event) {
|
||||
if(persistsFor(event.getWallet())) {
|
||||
updateExecutor.execute(() -> dirtyPersistablesMap.computeIfAbsent(event.getWallet(), key -> new DirtyPersistables()).silentPaymentAddresses = true);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DirtyPersistables {
|
||||
public boolean deleteAccount;
|
||||
public boolean clearHistory;
|
||||
@@ -906,6 +918,7 @@ public class DbPersistence implements Persistence {
|
||||
public final List<Keystore> labelKeystores = new ArrayList<>();
|
||||
public final List<Keystore> encryptionKeystores = new ArrayList<>();
|
||||
public final List<Keystore> registrationKeystores = new ArrayList<>();
|
||||
public boolean silentPaymentAddresses;
|
||||
|
||||
public String toString() {
|
||||
return "Dirty Persistables" +
|
||||
@@ -927,7 +940,8 @@ public class DbPersistence implements Persistence {
|
||||
"\nUTXO mixes removed:" + removedUtxoMixes +
|
||||
"\nKeystore labels:" + labelKeystores.stream().map(Keystore::getLabel).collect(Collectors.toList()) +
|
||||
"\nKeystore encryptions:" + encryptionKeystores.stream().map(Keystore::getLabel).collect(Collectors.toList()) +
|
||||
"\nKeystore registrations:" + registrationKeystores.stream().map(Keystore::getDeviceRegistration).collect(Collectors.toList());
|
||||
"\nKeystore registrations:" + registrationKeystores.stream().map(Keystore::getDeviceRegistration).collect(Collectors.toList()) +
|
||||
"\nSilent payment addresses:" + silentPaymentAddresses;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.sparrowwallet.sparrow.io.db;
|
||||
|
||||
import com.sparrowwallet.drongo.address.Address;
|
||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import org.jdbi.v3.sqlobject.config.RegisterRowMapper;
|
||||
import org.jdbi.v3.sqlobject.statement.SqlBatch;
|
||||
import org.jdbi.v3.sqlobject.statement.SqlQuery;
|
||||
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface SilentPaymentAddressDao {
|
||||
@SqlQuery("select address, silentPaymentAddress from silentPaymentAddress")
|
||||
@RegisterRowMapper(SilentPaymentAddressMapper.class)
|
||||
Map<Address, SilentPaymentAddress> getAll();
|
||||
|
||||
@SqlBatch("insert into silentPaymentAddress (address, silentPaymentAddress) values (?, ?)")
|
||||
void insertSilentPaymentAddresses(List<byte[]> addresses, List<byte[]> silentPaymentAddresses);
|
||||
|
||||
@SqlUpdate("delete from silentPaymentAddress")
|
||||
void clear();
|
||||
|
||||
default void clearAndAddAll(Wallet wallet) {
|
||||
clear();
|
||||
|
||||
List<byte[]> addresses = new ArrayList<>();
|
||||
List<byte[]> silentPaymentAddresses = new ArrayList<>();
|
||||
for(Map.Entry<Address, SilentPaymentAddress> entry : wallet.getSilentPaymentAddresses().entrySet()) {
|
||||
addresses.add(entry.getKey().getData());
|
||||
silentPaymentAddresses.add(entry.getValue().serialize());
|
||||
}
|
||||
|
||||
if(!addresses.isEmpty()) {
|
||||
insertSilentPaymentAddresses(addresses, silentPaymentAddresses);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.sparrowwallet.sparrow.io.db;
|
||||
|
||||
import com.sparrowwallet.drongo.address.Address;
|
||||
import com.sparrowwallet.drongo.address.P2TRAddress;
|
||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
||||
import org.jdbi.v3.core.mapper.RowMapper;
|
||||
import org.jdbi.v3.core.statement.StatementContext;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
public class SilentPaymentAddressMapper implements RowMapper<Map.Entry<Address, SilentPaymentAddress>> {
|
||||
@Override
|
||||
public Map.Entry<Address, SilentPaymentAddress> map(ResultSet rs, StatementContext ctx) throws SQLException {
|
||||
Address address = new P2TRAddress(rs.getBytes("address"));
|
||||
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.fromBytes(rs.getBytes("silentPaymentAddress"));
|
||||
|
||||
return new Map.Entry<>() {
|
||||
@Override
|
||||
public Address getKey() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SilentPaymentAddress getValue() {
|
||||
return silentPaymentAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SilentPaymentAddress setValue(SilentPaymentAddress value) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,9 @@ public interface WalletDao {
|
||||
@CreateSqlObject
|
||||
DetachedLabelDao createDetachedLabelDao();
|
||||
|
||||
@CreateSqlObject
|
||||
SilentPaymentAddressDao createSilentPaymentAddressDao();
|
||||
|
||||
@CreateSqlObject
|
||||
WalletConfigDao createWalletConfigDao();
|
||||
|
||||
@@ -121,6 +124,8 @@ public interface WalletDao {
|
||||
Map<String, String> detachedLabels = createDetachedLabelDao().getAll();
|
||||
wallet.getDetachedLabels().putAll(detachedLabels);
|
||||
|
||||
wallet.getSilentPaymentAddresses().putAll(createSilentPaymentAddressDao().getAll());
|
||||
|
||||
wallet.setWalletConfig(createWalletConfigDao().getForWalletId(wallet.getId()));
|
||||
|
||||
Map<TableType, WalletTable> walletTables = createWalletTableDao().getForWalletId(wallet.getId());
|
||||
@@ -144,6 +149,7 @@ public interface WalletDao {
|
||||
createWalletNodeDao().addWalletNodes(wallet);
|
||||
createBlockTransactionDao().addBlockTransactions(wallet);
|
||||
createDetachedLabelDao().clearAndAddAll(wallet);
|
||||
createSilentPaymentAddressDao().clearAndAddAll(wallet);
|
||||
createWalletConfigDao().addWalletConfig(wallet);
|
||||
createWalletTableDao().addWalletTables(wallet);
|
||||
createMixConfigDao().addMixConfig(wallet);
|
||||
|
||||
@@ -578,6 +578,8 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
|
||||
int threshold = signingWallet.getDefaultPolicy().getNumSignaturesRequired();
|
||||
signaturesProgressBar.initialize(headersForm.getSignatureKeystoreMap(), threshold);
|
||||
|
||||
learnSilentPaymentAddresses(signingWallet, headersForm.getPsbt());
|
||||
});
|
||||
|
||||
blockchainForm.setDynamicUpdate(this);
|
||||
@@ -1156,6 +1158,11 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
Map<PSBTInput, WalletNode> signingNodes = unencryptedWallet.getSigningNodes(headersForm.getPsbt());
|
||||
List<SilentPayment> silentPayments = unencryptedWallet.computeSilentPaymentOutputs(headersForm.getPsbt(), signingNodes);
|
||||
if(!silentPayments.isEmpty()) {
|
||||
Wallet signingWallet = headersForm.getSigningWallet();
|
||||
for(SilentPayment silentPayment : silentPayments) {
|
||||
signingWallet.addSilentPaymentAddress(silentPayment.getAddress(), silentPayment.getSilentPaymentAddress());
|
||||
}
|
||||
EventManager.get().post(new WalletSilentPaymentAddressesChangedEvent(signingWallet));
|
||||
EventManager.get().post(new TransactionOutputsChangedEvent(headersForm.getTransaction()));
|
||||
}
|
||||
unencryptedWallet.sign(signingNodes);
|
||||
@@ -1443,6 +1450,37 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
requestPayjoinPSBTService.start();
|
||||
}
|
||||
|
||||
private void learnSilentPaymentAddresses(Wallet wallet, PSBT psbt) {
|
||||
if(wallet == null || psbt == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Address, SilentPaymentAddress> pending = new LinkedHashMap<>();
|
||||
for(PSBTOutput psbtOutput : psbt.getPsbtOutputs()) {
|
||||
SilentPaymentAddress spAddress = psbtOutput.getSilentPaymentAddress();
|
||||
if(spAddress != null) {
|
||||
Script script = psbtOutput.getScript();
|
||||
Address address = script == null ? null : script.getToAddress();
|
||||
if(address == null) {
|
||||
return;
|
||||
}
|
||||
pending.put(address, spAddress);
|
||||
}
|
||||
}
|
||||
|
||||
boolean changed = false;
|
||||
for(Map.Entry<Address, SilentPaymentAddress> entry : pending.entrySet()) {
|
||||
if(!entry.getValue().equals(wallet.getSilentPaymentAddress(entry.getKey()))) {
|
||||
wallet.addSilentPaymentAddress(entry.getKey(), entry.getValue());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(changed) {
|
||||
EventManager.get().post(new WalletSilentPaymentAddressesChangedEvent(wallet));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
BlockTransaction blockTransaction = headersForm.getBlockTransaction();
|
||||
@@ -1664,6 +1702,7 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
@Subscribe
|
||||
public void psbtCombined(PSBTCombinedEvent event) {
|
||||
if(event.getPsbt().equals(headersForm.getPsbt())) {
|
||||
learnSilentPaymentAddresses(headersForm.getSigningWallet(), headersForm.getPsbt());
|
||||
if(headersForm.getSigningWallet() != null) {
|
||||
updateSignedKeystores(headersForm.getSigningWallet());
|
||||
} else if(headersForm.getPsbt().isSigned()) {
|
||||
@@ -1678,6 +1717,7 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
@Subscribe
|
||||
public void psbtFinalized(PSBTFinalizedEvent event) {
|
||||
if(event.getPsbt().equals(headersForm.getPsbt())) {
|
||||
learnSilentPaymentAddresses(headersForm.getSigningWallet(), headersForm.getPsbt());
|
||||
if(headersForm.getSigningWallet() != null) {
|
||||
updateSignedKeystores(headersForm.getSigningWallet());
|
||||
}
|
||||
|
||||
@@ -1559,6 +1559,11 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
@Subscribe
|
||||
public void excludeUtxo(ExcludeUtxoEvent event) {
|
||||
if(event.getWalletTransaction() == walletTransactionProperty.get()) {
|
||||
BlockTransaction replacedTransaction = replacedTransactionProperty.get();
|
||||
if(replacedTransaction != null && !getWalletForm().getWallet().isSafeToAddInputsOrOutputs(replacedTransaction)) {
|
||||
AppServices.showErrorDialog("Cannot Exclude Input", "Removing an input from this replacement transaction could break silent payment outputs as the original output script depends on the input set.");
|
||||
return;
|
||||
}
|
||||
UtxoSelector utxoSelector = utxoSelectorProperty.get();
|
||||
if(utxoSelector instanceof MaxUtxoSelector) {
|
||||
Collection<BlockTransactionHashIndex> utxos = event.getWalletTransaction().getSelectedUtxos().keySet();
|
||||
|
||||
@@ -749,6 +749,13 @@ public class WalletForm {
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletSilentPaymentAddressesChanged(WalletSilentPaymentAddressesChangedEvent event) {
|
||||
if(event.getWallet() == wallet) {
|
||||
Platform.runLater(() -> EventManager.get().post(new WalletDataChangedEvent(wallet)));
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletTabsClosed(WalletTabsClosedEvent event) {
|
||||
for(WalletTabData tabData : event.getClosedWalletTabData()) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
alter table wallet add column birthHeight integer after birthDate;
|
||||
alter table walletNode add column silentPaymentTweak varbinary(32) after addressData;
|
||||
alter table keystore add column silentPaymentScanAddress varbinary(65) after externalPaymentCode;
|
||||
alter table keystore add column silentPaymentScanAddress varbinary(65) after externalPaymentCode;
|
||||
create table silentPaymentAddress (address varbinary(32) primary key not null, silentPaymentAddress varbinary(67) not null);
|
||||
Reference in New Issue
Block a user