Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request]: A way to automatically recover an ongoing swap's data if the user's local data-dir gets wiped #1160

Closed
gcharang opened this issue Dec 7, 2021 · 10 comments · Fixed by #1164
Assignees

Comments

@gcharang
Copy link

gcharang commented Dec 7, 2021

We have seen many times that a user's local data directory gets wiped for various reasons

On desktop: HDD/computer crash can cause corruption, and a user might delete the data-dir
On mobile: a user might reinstall the app for any number of reasons, which causes their local app data to be lost

Currently, if the user had an unfinished swap that had their coins locked in a p2sh address, they have no way to automatically continue and finish the swap after they reinstall and recover their wallet. Also, a slightly less serious concern is that even if there was no ongoing swap, wiping the data-dir causes past trade history to be lost, which might be important to some serious users who want to track them for tax or other bookkeeping reasons

The most straightforward solution I can think of for this issue is to integrate cloud service providers like Backblaze b2, AWS s3 (both support the s3 API), Dropbox or iCloud, and Google drive specifically for mobile. Swaps can be updated in the cloud after each swap event or once every 20 seconds or any other appropriate batching strategy. We could even use a versioning system like git for maximum recoverability. And, we can encrypt the data with a key derived from the user's seed words for maximum safety. This approach will allow recovery of ongoing swaps and also preserves the complete trade history in case of local data loss.

While straightforward, this solution might be very tedious/time-taking to implement in a user/dev-friendly manner across platforms. A minimalist alternative that shouldn't take a lot of additional work can be to store not just error data, but also ongoing swap JSONs on seed nodes. Not sure what the storage requirements will look like for seed nodes after this feature's addition though. The seed node's code can delete a swap's data as soon as it is finished (both parties claimed their coins) automatically. And, maybe by default, seed nodes can store the data of unfinished swaps for at least 15 days before deleting them. Or maybe 7 days for sure and more if they don't have a lot more than 1 GB of unfinished swap data in storage. Any new swap JSON added that makes the total data to spill over 1 GB could make the oldest swap JSON be deleted if it was more than 7 days old. Of course, the numbers or whole system seems like a sensible default to me but can be changed/exposed to a config, etc., allowing seed node runners the option to tweak them.

We can't store all of the ongoing swap JSONs on seed nodes, making them almost public, as it could lead to loss of privacy/coins if the other party of trade becomes aware of them(like secret hash), we could encrypt those particular fields with the user's (privkey or a hashed equivalent) as the encryption key.

This can allow easy reconstruction of ongoing swap data with a single query sent to seed nodes if the wallets were restored within a "reasonable" time frame

There is one last alternative that is to create an RPC method that takes as input: all the info a user can piece together from block explorer like their address, dexfee txn, taker payment txn/maker payment txn/any spend transactions and the networks they were done, etc., and try to intelligently output: a swap JSON that can be used by mm2 to recover/finish the swap. This is just automating one part of the manual resolution process of today. In my personal opinion, this option shouldn't be the only implemented solution for this issue. This is because even if GUI devs are able to translate each possible option of this RPC into an easily understandable interface for the end-users, 99% of them won't have the necessary knowledge to search through the block explorers and input the necessary details correctly. This will cause workload on our team and we may not be able to meet it if the user base becomes even 10x of what it is right now. Also, as @sergeyboyko0791 could explain more, it may not even be possible to recreate a swap JSON automatically and reliably for every user's case.

TLDR:

To solve the issue of lost local data causing swaps being unrecoverable without our manual help, there can be backups (cloud/seednode) or automatic recreation of swap JSON based on data a user/support-tech were able to piece together. I recommend the backup option for the long term to have the least friction. And cloud backup should be the ultimate goal.

cc: @ca333 @artemii235 @tonymorony @sergeyboyko0791

@sergeyboyko0791
Copy link

We can start implementing such functionality by adding a cli mode to mm2.
I'm going to try to add the client mode today.

@artemii235
Copy link
Member

artemii235 commented Dec 7, 2021

@sergeyboyko0791 If you started (or plan to start very soon) working on the issue, please assign yourself and add it to the MM2 project "In progress" column. You can do it on the issue page at the right:

@sergeyboyko0791
Copy link

@artemii235 sorry, I forgot to do this

@sergeyboyko0791 sergeyboyko0791 linked a pull request Dec 8, 2021 that will close this issue
artemii235 pushed a commit that referenced this issue Dec 14, 2021
* Add and implement a swap recoverer
* Add the `recover_swap` RPC call

* Fix PR issues
* Rename `recover_swap` to `recreate_swap_data`
* Handle the Taker `TakerPaymentSpent` event
* Handle the Maker `TakerPaymentReceived`, 'TakerPaymentSpent' events

* Fix PR issues
* Rename `recover_swap.rs` to `recreate_swap_data.rs`
* Handle error events

* Fix PR issues
* Push `MakerSwapEvent::TakerFeeValidated` if only Taker received `MakerPayment`
@artemii235
Copy link
Member

@sergeyboyko0791 Could you please prepare examples for documentation and transfer this issue to @smk762?

@tonymorony
Copy link

tonymorony commented Dec 15, 2021

cf: #893

and #820 (to have a point of some "good" statuses sync state)

@smk762
Copy link

smk762 commented Dec 29, 2021

@sergeyboyko0791 can you pls let me know the params and an example for this method?

@sergeyboyko0791
Copy link

@smk762 thanks for helping! If you have questions, please feel free to ask here or DM :)

Command

curl --url "http://127.0.0.1:7783" --data "
{
     \"userpass\": \"${userpass}\",
     \"mmrpc\": \"2.0\",
     \"method\": \"recreate_swap_data\",
     \"params\": {
          \"swap\": \"${opposite_swap_data}\"
     },
     \"id\": 0
}
"

where ${opposite_swap_data} can be either tagged by the type field or not:

export opposite_swap_data = '{
    "type": "Taker",
    "uuid": "f87fa9ce-0820-4675-b85d-db18c7bc9fb4",
    "my_order_uuid": "f87fa9ce-0820-4675-b85d-db18c7bc9fb4",
    "events": [
        {
            "timestamp": 1638984440546,
            "event": {
                "type": "Started",
                "data": {
                    "taker_coin": "MORTY",
                    "maker_coin": "RICK",
                    "maker": "15d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732",
                    "my_persistent_pub": "03b1e544ce2d860219bc91314b5483421a553a7b33044659eff0be9214ed58addd",
                    "lock_duration": 7800,
                    "maker_amount": "0.9090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909091",
                    "taker_amount": "1",
                    "maker_payment_confirmations": 1,
                    "maker_payment_requires_nota": false,
                    "taker_payment_confirmations": 1,
                    "taker_payment_requires_nota": false,
                    "taker_payment_lock": 1638992240,
                    "uuid": "f87fa9ce-0820-4675-b85d-db18c7bc9fb4",
                    "started_at": 1638984440,
                    "maker_payment_wait": 1638987560,
                    "maker_coin_start_block": 1207822,
                    "taker_coin_start_block": 1222573,
                    "fee_to_send_taker_fee": {
                        "coin": "MORTY",
                        "amount": "0.00001",
                        "paid_from_trading_vol": false
                    },
                    "taker_payment_trade_fee": {
                        "coin": "MORTY",
                        "amount": "0.00001",
                        "paid_from_trading_vol": false
                    },
                    "maker_payment_spend_trade_fee": {
                        "coin": "RICK",
                        "amount": "0.00001",
                        "paid_from_trading_vol": true
                    }
                }
            }
        },
        {
            "timestamp": 1638984456603,
            "event": {
                "type": "Negotiated",
                "data": {
                    "maker_payment_locktime": 1639000040,
                    "maker_pubkey": "0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732",
                    "secret_hash": "4da9e7080175e8e10842e0e161b33cd298cab30b",
                    "maker_coin_swap_contract_addr": null,
                    "taker_coin_swap_contract_addr": null
                }
            }
        },
        {
            "timestamp": 1638984456814,
            "event": {
                "type": "TakerFeeSent",
                "data": {
                    "tx_hex": "0400008085202f89016383e8aced2256378bb126a1ca1a41e2f344d9295f65b3ea4b99055c5eb4a6cb000000006a47304402201c7e661e0dbeb9b3eb6e4e9e3194010e5772227017772b2e48c1b8d48ed3b21f02201c2eda64e74455fa1878a5c221f25d22fe626abd0078a26a9fc0f829e0921639012103b1e544ce2d860219bc91314b5483421a553a7b33044659eff0be9214ed58adddffffffff02bcf60100000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac74c3e90b000000001976a91483762a373935ca241d557dfce89171d582b486de88ac08ebb061000000000000000000000000000000",
                    "tx_hash": "fcb49167c79e8e014143643b94878866f7e80b26c5a5dcf693010543da70b5bc"
                }
            }
        },
        {
            "timestamp": 1638984457822,
            "event": {
                "type": "MakerPaymentReceived",
                "data": {
                    "tx_hex": "0400008085202f8901c41fdf6b9d8aea4b472f83e4fa0d99dfafc245e897d681fd2ca7df30707fbf48020000006b483045022100c7b294bd46cbf3b13530879a43c5cf67414047266d8b64c3c7263b5e75b989ba02201974f38d688b184bc44e628806c6ab2ac9092f394729d0ce838f14e1e76117c001210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff03a2296b050000000017a91491c45f69e1760c12a1f90fb2a811f6dfde35cc35870000000000000000166a144da9e7080175e8e10842e0e161b33cd298cab30bac503d64000000001976a9141462c3dd3f936d595c9af55978003b27c250441f88ac09ebb061000000000000000000000000000000",
                    "tx_hash": "6287e0d30951cd859bfb837eb1e5409f7596e75ffeb2e61fd6df1843bfd0203d"
                }
            }
        },
        {
            "timestamp": 1638984457826,
            "event": {
                "type": "MakerPaymentWaitConfirmStarted"
            }
        },
        {
            "timestamp": 1638984503611,
            "event": {
                "type": "MakerPaymentWaitConfirmFailed",
                "data": {
                    "error": "An error"
                }
            }
        },
        {
            "timestamp": 1638984503615,
            "event": {
                "type": "Finished"
            }
        }
    ],
    "maker_amount": "0.9090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909091",
    "maker_coin": "RICK",
    "taker_amount": "1",
    "taker_coin": "MORTY",
    "gui": "atomicDEX 0.5.1 iOS",
    "mm_version": "1b065636a",
    "success_events": [
        "Started",
        "Negotiated",
        "TakerFeeSent",
        "MakerPaymentReceived",
        "MakerPaymentWaitConfirmStarted",
        "MakerPaymentValidatedAndConfirmed",
        "TakerPaymentSent",
        "TakerPaymentSpent",
        "MakerPaymentSpent",
        "Finished"
    ],
    "error_events": [
        "StartFailed",
        "NegotiateFailed",
        "TakerFeeSendFailed",
        "MakerPaymentValidateFailed",
        "MakerPaymentWaitConfirmFailed",
        "TakerPaymentTransactionFailed",
        "TakerPaymentWaitConfirmFailed",
        "TakerPaymentDataSendFailed",
        "TakerPaymentWaitForSpendFailed",
        "MakerPaymentSpendFailed",
        "TakerPaymentWaitRefundStarted",
        "TakerPaymentRefunded",
        "TakerPaymentRefundFailed"
    ]
}'

Response

{
    "mmrpc": "2.0",
    "result": {
        "swap": {
            "type": "Maker",
            "uuid": "f87fa9ce-0820-4675-b85d-db18c7bc9fb4",
            "my_order_uuid": "f87fa9ce-0820-4675-b85d-db18c7bc9fb4",
            "events": [
                {
                    "timestamp": 1638984440546,
                    "event": {
                        "type": "Started",
                        "data": {
                            "taker_coin": "MORTY",
                            "maker_coin": "RICK",
                            "taker": "b1e544ce2d860219bc91314b5483421a553a7b33044659eff0be9214ed58addd",
                            "secret": "0000000000000000000000000000000000000000000000000000000000000000",
                            "secret_hash": "4da9e7080175e8e10842e0e161b33cd298cab30b",
                            "my_persistent_pub": "0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732",
                            "lock_duration": 7800,
                            "maker_amount": "0.9090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909091",
                            "taker_amount": "1",
                            "maker_payment_confirmations": 1,
                            "maker_payment_requires_nota": false,
                            "taker_payment_confirmations": 1,
                            "taker_payment_requires_nota": false,
                            "maker_payment_lock": 1639000040,
                            "uuid": "f87fa9ce-0820-4675-b85d-db18c7bc9fb4",
                            "started_at": 1638984440,
                            "maker_coin_start_block": 1207822,
                            "taker_coin_start_block": 1222573,
                            "maker_payment_trade_fee": null,
                            "taker_payment_spend_trade_fee": null
                        }
                    }
                },
                {
                    "timestamp": 1638984456603,
                    "event": {
                        "type": "Negotiated",
                        "data": {
                            "taker_payment_locktime": 1638992240,
                            "taker_pubkey": "03b1e544ce2d860219bc91314b5483421a553a7b33044659eff0be9214ed58addd",
                            "maker_coin_swap_contract_addr": null,
                            "taker_coin_swap_contract_addr": null
                        }
                    }
                },
                {
                    "timestamp": 1638984457822,
                    "event": {
                        "type": "TakerFeeValidated",
                        "data": {
                            "tx_hex": "0400008085202f89016383e8aced2256378bb126a1ca1a41e2f344d9295f65b3ea4b99055c5eb4a6cb000000006a47304402201c7e661e0dbeb9b3eb6e4e9e3194010e5772227017772b2e48c1b8d48ed3b21f02201c2eda64e74455fa1878a5c221f25d22fe626abd0078a26a9fc0f829e0921639012103b1e544ce2d860219bc91314b5483421a553a7b33044659eff0be9214ed58adddffffffff02bcf60100000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac74c3e90b000000001976a91483762a373935ca241d557dfce89171d582b486de88ac08ebb061000000000000000000000000000000",
                            "tx_hash": "fcb49167c79e8e014143643b94878866f7e80b26c5a5dcf693010543da70b5bc"
                        }
                    }
                },
                {
                    "timestamp": 1638984457822,
                    "event": {
                        "type": "MakerPaymentSent",
                        "data": {
                            "tx_hex": "0400008085202f8901c41fdf6b9d8aea4b472f83e4fa0d99dfafc245e897d681fd2ca7df30707fbf48020000006b483045022100c7b294bd46cbf3b13530879a43c5cf67414047266d8b64c3c7263b5e75b989ba02201974f38d688b184bc44e628806c6ab2ac9092f394729d0ce838f14e1e76117c001210315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732ffffffff03a2296b050000000017a91491c45f69e1760c12a1f90fb2a811f6dfde35cc35870000000000000000166a144da9e7080175e8e10842e0e161b33cd298cab30bac503d64000000001976a9141462c3dd3f936d595c9af55978003b27c250441f88ac09ebb061000000000000000000000000000000",
                            "tx_hash": "6287e0d30951cd859bfb837eb1e5409f7596e75ffeb2e61fd6df1843bfd0203d"
                        }
                    }
                },
                {
                    "timestamp": 1638984503611,
                    "event": {
                        "type": "TakerPaymentValidateFailed",
                        "data": {
                            "error": "Origin Taker error event: MakerPaymentWaitConfirmFailed(SwapError { error: \"An error\" })"
                        }
                    }
                },
                {
                    "timestamp": 1638984503611,
                    "event": {
                        "type": "MakerPaymentWaitRefundStarted",
                        "data": {
                            "wait_until": 1639003740
                        }
                    }
                }
            ],
            "maker_amount": "0.9090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909091",
            "maker_coin": "RICK",
            "taker_amount": "1",
            "taker_coin": "MORTY",
            "gui": "nogui",
            "mm_version": "",
            "success_events": [
                "Started",
                "Negotiated",
                "TakerFeeValidated",
                "MakerPaymentSent",
                "TakerPaymentReceived",
                "TakerPaymentWaitConfirmStarted",
                "TakerPaymentValidatedAndConfirmed",
                "TakerPaymentSpent",
                "TakerPaymentSpendConfirmStarted",
                "TakerPaymentSpendConfirmed",
                "Finished"
            ],
            "error_events": [
                "StartFailed",
                "NegotiateFailed",
                "TakerFeeValidateFailed",
                "MakerPaymentTransactionFailed",
                "MakerPaymentDataSendFailed",
                "MakerPaymentWaitConfirmFailed",
                "TakerPaymentValidateFailed",
                "TakerPaymentWaitConfirmFailed",
                "TakerPaymentSpendFailed",
                "TakerPaymentSpendConfirmFailed",
                "MakerPaymentWaitRefundStarted",
                "MakerPaymentRefunded",
                "MakerPaymentRefundFailed"
            ]
        }
    },
    "id": null
}

To import the result swap user can use the import_swaps RPC call.

@smk762
Copy link

smk762 commented Jan 1, 2022

either tagged by the type field or not
In an example where not tagged, does this just mean "type": "Taker" or "type": "Maker" is optional ?

What is the minimum level of info in opposite_swap_data required for a response? And how would an average user recover this?

Most support queries so far, we get as little as an address and amount, and from there we can find usually find related txids. If we're lucky, user has the uuid also.

@sergeyboyko0791
Copy link

In an example where not tagged, does this just mean "type": "Taker" or "type": "Maker" is optional ?

Yes, the algorithm determines the input data because Maker swap file contains some fields and events that are not in Taker swap file, and vice versa.

What is the minimum level of info in opposite_swap_data required for a response? And how would an average user recover this?
Most support queries so far, we get as little as an address and amount, and from there we can find usually find related txids. If we're lucky, user has the uuid also.

At this moment, we can recreate an opposite swap file using info from seednodes. The opposite swap file must contain a correct locktime, a secret hash, etc. There are many fields that we actually can extract from the swap transactions, but know we expect only complete swap data requested from seednodes :(

@artemii235
Copy link
Member

@sergeyboyko0791 @gcharang @smk762 Can we close this one?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants