re-export multisig hex on create multisig tx or open dispute

This commit is contained in:
woodser 2024-07-31 19:39:54 -04:00
parent d4a9838cd8
commit 79cd9f3e82
9 changed files with 120 additions and 104 deletions

View File

@ -112,7 +112,7 @@ public class CoreDisputesService {
// Sends the openNewDisputeMessage to arbitrator, who will then create 2 disputes
// one for the opener, the other for the peer, see sendPeerOpenedDisputeMessage.
disputeManager.sendDisputeOpenedMessage(dispute, false, trade.getSelf().getUpdatedMultisigHex(), resultHandler, faultHandler);
disputeManager.sendDisputeOpenedMessage(dispute, false, resultHandler, faultHandler);
tradeManager.requestPersistence();
}, trade.getId());
}

View File

@ -157,6 +157,11 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
disputeListService.requestPersistence();
}
protected void requestPersistence(Trade trade) {
trade.requestPersistence();
disputeListService.requestPersistence();
}
@Override
public NodeAddress getPeerNodeAddress(ChatMessage message) {
Optional<Dispute> disputeOptional = findDispute(message);
@ -322,7 +327,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// trader sends message to arbitrator to open dispute
public void sendDisputeOpenedMessage(Dispute dispute,
boolean reOpen,
String updatedMultisigHex,
ResultHandler resultHandler,
FaultHandler faultHandler) {
@ -372,12 +376,13 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
}
// create dispute opened message
trade.exportMultisigHex();
NodeAddress agentNodeAddress = getAgentNodeAddress(dispute);
DisputeOpenedMessage disputeOpenedMessage = new DisputeOpenedMessage(dispute,
p2PService.getAddress(),
UUID.randomUUID().toString(),
getSupportType(),
updatedMultisigHex,
trade.getSelf().getUpdatedMultisigHex(),
trade.getArbitrator().getPaymentSentMessage());
log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, " +
"chatMessage.uid={}",
@ -792,7 +797,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
disputeResult.getChatMessage().setArrived(true);
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG);
trade.pollWalletNormallyForMs(30000);
requestPersistence();
requestPersistence(trade);
resultHandler.handleResult();
}
@ -811,7 +816,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
disputeResult.getChatMessage().setStoredInMailbox(true);
Trade trade = tradeManager.getTrade(dispute.getTradeId());
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_STORED_IN_MAILBOX_DISPUTE_CLOSED_MSG);
requestPersistence();
requestPersistence(trade);
resultHandler.handleResult();
}
@ -828,13 +833,13 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// the state, as that is displayed to the user and we only persist that msg
disputeResult.getChatMessage().setSendMessageError(errorMessage);
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SEND_FAILED_DISPUTE_CLOSED_MSG);
requestPersistence();
requestPersistence(trade);
faultHandler.handleFault(errorMessage, new RuntimeException(errorMessage));
}
}
);
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SENT_DISPUTE_CLOSED_MSG);
requestPersistence();
requestPersistence(trade);
} catch (Exception e) {
faultHandler.handleFault(e.getMessage(), e);
}
@ -900,11 +905,11 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// update trade state
if (updateState) {
trade.getProcessModel().setUnsignedPayoutTx(payoutTx);
trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex());
trade.updatePayout(payoutTx);
if (trade.getBuyer().getUpdatedMultisigHex() != null && trade.getBuyer().getUnsignedPayoutTxHex() == null) trade.getBuyer().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
if (trade.getSeller().getUpdatedMultisigHex() != null && trade.getSeller().getUnsignedPayoutTxHex() == null) trade.getSeller().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
}
trade.requestPersistence();
return payoutTx;
} catch (Exception e) {
trade.syncAndPollWallet();

View File

@ -252,7 +252,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
// save dispute closed message for reprocessing
trade.getArbitrator().setDisputeClosedMessage(disputeClosedMessage);
requestPersistence();
requestPersistence(trade);
// verify arbitrator does not receive DisputeClosedMessage
if (keyRing.getPubKeyRing().equals(dispute.getAgentPubKeyRing())) {
@ -326,17 +326,17 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
// We use the chatMessage as we only persist those not the DisputeClosedMessage.
// If we would use the DisputeClosedMessage we could not lookup for the msg when we receive the AckMessage.
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null);
requestPersistence();
requestPersistence(trade);
} catch (Exception e) {
log.warn("Error processing dispute closed message: " + e.getMessage());
e.printStackTrace();
requestPersistence();
requestPersistence(trade);
// nack bad message and do not reprocess
if (e instanceof IllegalArgumentException) {
trade.getArbitrator().setDisputeClosedMessage(null); // message is processed
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), false, e.getMessage());
requestPersistence();
requestPersistence(trade);
throw e;
}
@ -447,7 +447,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
String signedMultisigTxHex = result.getSignedMultisigTxHex();
disputeTxSet.setMultisigTxHex(signedMultisigTxHex);
trade.setPayoutTxHex(signedMultisigTxHex);
requestPersistence();
requestPersistence(trade);
// verify mining fee is within tolerance by recreating payout tx
// TODO (monero-project): creating tx will require exchanging updated multisig hex if message needs reprocessed. provide weight with describe_transfer so fee can be estimated?
@ -487,6 +487,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
trade.updatePayout(disputeTxSet.getTxs().get(0));
trade.setPayoutState(Trade.PayoutState.PAYOUT_PUBLISHED);
dispute.setDisputePayoutTxId(disputeTxSet.getTxs().get(0).getHash());
requestPersistence(trade);
return disputeTxSet;
}

View File

@ -1058,11 +1058,21 @@ public abstract class Trade implements Tradable, Model {
public MoneroTxWallet createTx(MoneroTxConfig txConfig) {
synchronized (walletLock) {
synchronized (HavenoUtils.getWalletFunctionLock()) {
return wallet.createTx(txConfig);
MoneroTxWallet tx = wallet.createTx(txConfig);
exportMultisigHex();
requestSaveWallet();
return tx;
}
}
}
public void exportMultisigHex() {
synchronized (walletLock) {
getSelf().setUpdatedMultisigHex(wallet.exportMultisigHex());
requestPersistence();
}
}
public void importMultisigHex() {
synchronized (walletLock) {
synchronized (HavenoUtils.getDaemonLock()) { // lock on daemon because import calls full refresh
@ -1220,13 +1230,11 @@ public abstract class Trade implements Tradable, Model {
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY));
// update state
saveWallet();
BigInteger payoutTxFeeSplit = payoutTx.getFee().divide(BigInteger.valueOf(2));
getBuyer().setPayoutTxFee(payoutTxFeeSplit);
getBuyer().setPayoutAmount(HavenoUtils.getDestination(buyerPayoutAddress, payoutTx).getAmount());
getSeller().setPayoutTxFee(payoutTxFeeSplit);
getSeller().setPayoutAmount(HavenoUtils.getDestination(sellerPayoutAddress, payoutTx).getAmount());
getSelf().setUpdatedMultisigHex(wallet.exportMultisigHex());
return payoutTx;
}
@ -1273,6 +1281,9 @@ public abstract class Trade implements Tradable, Model {
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection();
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
} finally {
requestSaveWallet();
requestPersistence();
}
}
}

View File

@ -64,9 +64,9 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
// skip if payout tx already created
if (trade.getSelf().getUnsignedPayoutTxHex() != null) {
log.warn("Skipping preparation of payment sent message because payout tx is already created for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
complete();
return;
log.warn("Skipping preparation of payment sent message because payout tx is already created for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
complete();
return;
}
// validate state
@ -87,6 +87,7 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
MoneroTxWallet payoutTx = trade.createPayoutTx();
trade.updatePayout(payoutTx);
trade.getSelf().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
trade.requestPersistence();
}
complete();
@ -107,101 +108,100 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
*/
public static class Pair<F, S> {
private F first;
private S second;
private F first;
private S second;
public Pair(F first, S second) {
super();
this.first = first;
this.second = second;
}
public Pair(F first, S second) {
super();
this.first = first;
this.second = second;
}
public F getFirst() {
return first;
}
public F getFirst() {
return first;
}
public void setFirst(F first) {
this.first = first;
}
public void setFirst(F first) {
this.first = first;
}
public S getSecond() {
return second;
}
public S getSecond() {
return second;
}
public void setSecond(S second) {
this.second = second;
}
public void setSecond(S second) {
this.second = second;
}
}
public static void printBalances(MoneroWallet wallet) {
// collect info about subaddresses
List<Pair<String, List<Object>>> pairs = new ArrayList<Pair<String, List<Object>>>();
//if (wallet == null) wallet = TestUtils.getWalletJni();
BigInteger balance = wallet.getBalance();
BigInteger unlockedBalance = wallet.getUnlockedBalance();
List<MoneroAccount> accounts = wallet.getAccounts(true);
System.out.println("Wallet balance: " + balance);
System.out.println("Wallet unlocked balance: " + unlockedBalance);
for (MoneroAccount account : accounts) {
add(pairs, "ACCOUNT", account.getIndex());
add(pairs, "SUBADDRESS", "");
add(pairs, "LABEL", "");
add(pairs, "ADDRESS", "");
add(pairs, "BALANCE", account.getBalance());
add(pairs, "UNLOCKED", account.getUnlockedBalance());
for (MoneroSubaddress subaddress : account.getSubaddresses()) {
add(pairs, "ACCOUNT", account.getIndex());
add(pairs, "SUBADDRESS", subaddress.getIndex());
add(pairs, "LABEL", subaddress.getLabel());
add(pairs, "ADDRESS", subaddress.getAddress());
add(pairs, "BALANCE", subaddress.getBalance());
add(pairs, "UNLOCKED", subaddress.getUnlockedBalance());
// collect info about subaddresses
List<Pair<String, List<Object>>> pairs = new ArrayList<Pair<String, List<Object>>>();
//if (wallet == null) wallet = TestUtils.getWalletJni();
BigInteger balance = wallet.getBalance();
BigInteger unlockedBalance = wallet.getUnlockedBalance();
List<MoneroAccount> accounts = wallet.getAccounts(true);
System.out.println("Wallet balance: " + balance);
System.out.println("Wallet unlocked balance: " + unlockedBalance);
for (MoneroAccount account : accounts) {
add(pairs, "ACCOUNT", account.getIndex());
add(pairs, "SUBADDRESS", "");
add(pairs, "LABEL", "");
add(pairs, "ADDRESS", "");
add(pairs, "BALANCE", account.getBalance());
add(pairs, "UNLOCKED", account.getUnlockedBalance());
for (MoneroSubaddress subaddress : account.getSubaddresses()) {
add(pairs, "ACCOUNT", account.getIndex());
add(pairs, "SUBADDRESS", subaddress.getIndex());
add(pairs, "LABEL", subaddress.getLabel());
add(pairs, "ADDRESS", subaddress.getAddress());
add(pairs, "BALANCE", subaddress.getBalance());
add(pairs, "UNLOCKED", subaddress.getUnlockedBalance());
}
}
}
// convert info to csv
Integer length = null;
for (Pair<String, List<Object>> pair : pairs) {
if (length == null) length = pair.getSecond().size();
}
// convert info to csv
Integer length = null;
for (Pair<String, List<Object>> pair : pairs) {
if (length == null)
length = pair.getSecond().size();
}
System.out.println(pairsToCsv(pairs));
System.out.println(pairsToCsv(pairs));
}
private static void add(List<Pair<String, List<Object>>> pairs, String header, Object value) {
if (value == null) value = "";
Pair<String, List<Object>> pair = null;
for (Pair<String, List<Object>> aPair : pairs) {
if (aPair.getFirst().equals(header)) {
pair = aPair;
break;
if (value == null) value = "";
Pair<String, List<Object>> pair = null;
for (Pair<String, List<Object>> aPair : pairs) {
if (aPair.getFirst().equals(header)) {
pair = aPair;
break;
}
}
}
if (pair == null) {
List<Object> vals = new ArrayList<Object>();
pair = new Pair<String, List<Object>>(header, vals);
pairs.add(pair);
}
pair.getSecond().add(value);
if (pair == null) {
List<Object> vals = new ArrayList<Object>();
pair = new Pair<String, List<Object>>(header, vals);
pairs.add(pair);
}
pair.getSecond().add(value);
}
private static String pairsToCsv(List<Pair<String, List<Object>>> pairs) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < pairs.size(); i++) {
sb.append(pairs.get(i).getFirst());
if (i < pairs.size() - 1) sb.append(',');
else sb.append('\n');
}
for (int i = 0; i < pairs.get(0).getSecond().size(); i++) {
for (int j = 0; j < pairs.size(); j++) {
sb.append(pairs.get(j).getSecond().get(i));
if (j < pairs.size() - 1) sb.append(',');
else sb.append('\n');
StringBuilder sb = new StringBuilder();
for (int i = 0; i < pairs.size(); i++) {
sb.append(pairs.get(i).getFirst());
if (i < pairs.size() - 1) sb.append(',');
else sb.append('\n');
}
}
return sb.toString();
for (int i = 0; i < pairs.get(0).getSecond().size(); i++) {
for (int j = 0; j < pairs.size(); j++) {
sb.append(pairs.get(j).getSecond().get(i));
if (j < pairs.size() - 1) sb.append(',');
else sb.append('\n');
}
}
return sb.toString();
}
}

View File

@ -58,7 +58,6 @@ public class ProcessPaymentSentMessage extends TradeTask {
// if seller, decrypt buyer's payment account payload
if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());
trade.requestPersistence();
// update state
trade.advanceState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);

View File

@ -90,7 +90,7 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
for (Dispute dispute : trade.getDisputes()) dispute.setIsClosed();
}
processModel.getTradeManager().requestPersistence();
trade.requestPersistence();
complete();
} catch (Throwable t) {
failed(t);

View File

@ -76,8 +76,7 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
// export multisig hex once
if (trade.getSelf().getUpdatedMultisigHex() == null) {
trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex());
processModel.getTradeManager().requestPersistence();
trade.exportMultisigHex();
}
// We do not use a real unique ID here as we want to be able to re-send the exact same message in case the

View File

@ -545,27 +545,28 @@ public class PendingTradesDataModel extends ActivatableDataModel {
dispute.setExtraData("counterCurrencyExtraData", trade.getCounterCurrencyExtraData());
trade.setDisputeState(Trade.DisputeState.MEDIATION_REQUESTED);
sendDisputeOpenedMessage(dispute, false, disputeManager, trade.getSelf().getUpdatedMultisigHex());
sendDisputeOpenedMessage(dispute, false, disputeManager);
tradeManager.requestPersistence();
} else if (useArbitration) {
// Only if we have completed mediation we allow arbitration
disputeManager = arbitrationManager;
Dispute dispute = disputesService.createDisputeForTrade(trade, offer, pubKeyRingProvider.get(), isMaker, isSupportTicket);
sendDisputeOpenedMessage(dispute, false, disputeManager, trade.getSelf().getUpdatedMultisigHex());
trade.exportMultisigHex();
sendDisputeOpenedMessage(dispute, false, disputeManager);
tradeManager.requestPersistence();
} else {
log.warn("Invalid dispute state {}", disputeState.name());
}
}
private void sendDisputeOpenedMessage(Dispute dispute, boolean reOpen, DisputeManager<? extends DisputeList<Dispute>> disputeManager, String senderMultisigHex) {
disputeManager.sendDisputeOpenedMessage(dispute, reOpen, senderMultisigHex,
private void sendDisputeOpenedMessage(Dispute dispute, boolean reOpen, DisputeManager<? extends DisputeList<Dispute>> disputeManager) {
disputeManager.sendDisputeOpenedMessage(dispute, reOpen,
() -> navigation.navigateTo(MainView.class, SupportView.class, ArbitrationClientView.class), (errorMessage, throwable) -> {
if ((throwable instanceof DisputeAlreadyOpenException)) {
errorMessage += "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg");
new Popup().warning(errorMessage)
.actionButtonText(Res.get("portfolio.pending.openAgainDispute.button"))
.onAction(() -> sendDisputeOpenedMessage(dispute, true, disputeManager, senderMultisigHex))
.onAction(() -> sendDisputeOpenedMessage(dispute, true, disputeManager))
.closeButtonText(Res.get("shared.cancel")).show();
} else {
new Popup().warning(errorMessage).show();