This commit is contained in:
woodser 2021-05-04 20:20:30 -04:00
parent 8a38081c04
commit a22edd60f8
241 changed files with 10631 additions and 4905 deletions

2
.gitattributes vendored
View File

@ -12,4 +12,4 @@
*.jpg binary
*.jpeg binary
*.png binary
p2p/src/main/resources/*BTC_MAINNET filter=lfs diff=lfs merge=lfs -text
p2p/src/main/resources/*XMR_MAINNET filter=lfs diff=lfs merge=lfs -text

View File

@ -1,18 +0,0 @@
Please fill in the following data to request for a new asset to be listed on Bisq. For more details, be sure to read [the full documentation](https://docs.bisq.network/exchange/howto/list-asset.html) on adding a new asset.
### 1. Asset name
### 2. Ticker
_Your asset's ticker must not conflict with any national currency tickers (per [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217)) or any of the [top 100 cryptocurrency tickers](https://coinmarketcap.com/coins/)._
### 3. Block explorer URL
_Your asset's block explorer must be active and publicly available so that transactions can be verified with a receiver's address...if this isn't possible, [see workarounds here](file:///home/steve/wb/bisq/bisq-docs/build/asciidoc/html5/exchange/howto/list-asset.html#arbitrators-must-be-able-to-look-up-transactions-in-the-asset-block-explorer)._
### 4. Additional technical requirements (yes/no)
_Your asset should not have any additional requirements (for example, needing input fields for anything other than an address)._
### 5. Initial coin offering (yes/no)
_Bisq will not list your token if it has taken part in an initial coin offering (ICO)_

View File

@ -1,15 +0,0 @@
labelPRBasedOnFilePath:
in:altcoins:
- assets/**/*
is:no-priority:
- assets/**/*
firstPRWelcomeComment: >
**Thanks for opening this pull request!**<br/><br/>Please check out our [contributor checklist](https://docs.bisq.network/contributor-checklist.html) and check if *Travis* or *Codacy* found any issues with your PR. Also make sure your commits are signed, and that you applied [Bisq's code style](https://github.com/bisq-network/style/issues) and [formatting](.editorconfig).<br/><br/>A maintainer will add an `is:priority` label to your PR if it is up for compensation. Please see our [Bisq Q1 2020 Update post](https://bisq.network/blog/q1-2020-update/) for more details.
firstPRMergeComment: >
Awesome work, congrats on your first merged pull request!
firstIssueWelcomeComment: >
**Thanks for opening your first issue here!**<br/><br/>Be sure to follow the issue template. Your issue will be reviewed by a maintainer and labeled for further action.

35
.github/stale.yml vendored
View File

@ -1,35 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 90
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- a:bug
- re:security
- re:privacy
- re:Tor
- in:dao
- $BSQ bounty
- good first issue
- Epic
- a:feature
- is:priority
# Label to use when marking an issue as stale
staleLabel: was:dropped
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been automatically closed because of inactivity.
Feel free to reopen it if you think it is still relevant.
pulls:
daysUntilStale: 30
markComment: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.

35
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: CI
on:
push:
pull_request:
paths-ignore:
- 'docs/**'
- '**/README.md'
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
with:
lfs: true
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Pull lfs
run: git lfs pull
- name: Build with Gradle
run: ./gradlew build --stacktrace --scan
- uses: actions/upload-artifact@v2
if: failure()
with:
name: gradlew-report
path: 'desktop/build/reports/tests/test/index.html'
retention-days: 30

1
.gitignore vendored
View File

@ -35,3 +35,4 @@ deploy
.java-version
.localnet
/apitest/src/main/resources/dao-setup*
/monero-wallet-rpc

View File

@ -1,14 +1,2 @@
# This doc specifies who gets requested to review GitHub pull requests.
# See https://help.github.com/articles/about-codeowners/.
/core/main/java/bisq/core/dao/ @ManfredKarrer
# For seednode configuration changes
/seednode/bisq-seednode.env @wiz
/seednode/bisq-seednode.service @wiz
/seednode/bitcoin.conf @wiz
/seednode/bitcoin.service @wiz
/seednode/docker-compose.yml @wiz
/seednode/install_seednode_debian.sh @wiz
/seednode/torrc @wiz
/seednode/uninstall_seednode_debian.sh @wiz
# See https://help.github.com/articles/about-codeowners/.

View File

@ -1,101 +0,0 @@
# Contributing to Bisq
Anyone is welcome to contribute to Bisq. This document provides an overview of how we work. If you're looking for somewhere to start contributing, check out [critical bugs](https://bisq.wiki/Critical_Bugs) or see [good first issue](https://github.com/bisq-network/bisq/issues?q=is%3Aopen+is%3Aissue+label%3A"good+first+issue") list.
## Communication Channels
Most communication about Bisq happens on [Keybase](https://keybase.io).
Install Keybase and enter "bisq" from the teams tab. This is an "open" team, which means the admins will auto-accept any request to join, and you can get in fast.
Discussion about code changes happens in GitHub issues and pull requests.
Discussion about larger changes to the way Bisq works happens in issues the [bisq-network/proposals](https://github.com/bisq-network/proposals/issues) repository. See https://docs.bisq.network/proposals.html for details.
## Contributor Workflow
All Bisq contributors submit changes via pull requests. The workflow is as follows:
- Fork the repository
- Create a topic branch from the `master` branch
- Commit patches
- Squash redundant or unnecessary commits
- Submit a pull request from your topic branch back to the `master` branch of the main repository
- Make changes to the pull request if reviewers request them and __**request a re-review**__
Pull requests should be focused on a single change. Do not mix, for example, refactorings with a bug fix or implementation of a new feature. This practice makes it easier for fellow contributors to review each pull request on its merits and to give a clear ACK/NACK (see below).
## Reviewing Pull Requests
Bisq follows the review workflow established by the Bitcoin Core project. The following is adapted from the [Bitcoin Core contributor documentation](https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#peer-review):
Anyone may participate in peer review which is expressed by comments in the pull request. Typically reviewers will review the code for obvious errors, as well as test out the patch set and opine on the technical merits of the patch. Project maintainers take into account the peer review when determining if there is consensus to merge a pull request (remember that discussions may have been spread out over GitHub, mailing list and IRC discussions). The following language is used within pull-request comments:
- `ACK` means "I have tested the code and I agree it should be merged";
- `NACK` means "I disagree this should be merged", and must be accompanied by sound technical justification. NACKs without accompanying reasoning may be disregarded;
- `utACK` means "I have not tested the code, but I have reviewed it and it looks OK, I agree it can be merged";
- `Concept ACK` means "I agree in the general principle of this pull request";
- `Nit` refers to trivial, often non-blocking issues.
Please note that Pull Requests marked `NACK` and/or GitHub's `Change requested` are closed after 30 days if not addressed.
## Compensation
Bisq is not a company, but operates as a _decentralized autonomous organization_ (DAO).
Since our [Q1 2020 update](https://bisq.network/blog/q1-2020-update/) contributions are NOT eligible for compensation unless they are allocated as part of the development budget. Fixes for [critical bugs](https://bisq.wiki/Critical_Bugs) are eligible for compensation when delivered.
In any case please contact the team lead for development (@ripcurlx) upfront if you want to get compensated for your contributions.
For any work that was approved and merged into Bisq's `master` branch, you can [submit a compensation request](https://docs.bisq.network/dao/phase-zero.html#how-to-request-compensation) and earn BSQ (the Bisq DAO native token). Learn more about the Bisq DAO and BSQ [here](https://docs.bisq.network/dao/phase-zero.html).
## Style and Coding Conventions
### Configure Git user name and email metadata
See https://help.github.com/articles/setting-your-username-in-git/ for instructions.
### Write well-formed commit messages
From https://chris.beams.io/posts/git-commit/#seven-rules:
1. Separate subject from body with a blank line
2. Limit the subject line to 50 characters (*)
3. Capitalize the subject line
4. Do not end the subject line with a period
5. Use the imperative mood in the subject line
6. Wrap the body at 72 characters (*)
7. Use the body to explain what and why vs. how
*) See [here](https://stackoverflow.com/a/45563628/8340320) for how to enforce these two checks in IntelliJ IDEA.
See also [bisq-network/style#9](https://github.com/bisq-network/style/issues/9).
### Sign your commits with GPG
See https://github.com/blog/2144-gpg-signature-verification for background and
https://help.github.com/articles/signing-commits-with-gpg/ for instructions.
### Use an editor that supports Editorconfig
The [.editorconfig](.editorconfig) settings in this repository ensure consistent management of whitespace, line endings and more. Most modern editors support it natively or with plugin. See http://editorconfig.org for details. See also [bisq-network/style#10](https://github.com/bisq-network/style/issues/10).
### Keep the git history clean
It's very important to keep the git history clear, light and easily browsable. This means contributors must make sure their pull requests include only meaningful commits (if they are redundant or were added after a review, they should be removed) and _no merge commits_.
### Additional style guidelines
See the issues in the [bisq-network/style](https://github.com/bisq-network/style/issues) repository.
## See also
- [contributor checklist](https://docs.bisq.network/contributor-checklist.html)
- [developer docs](docs#readme) including build and dev environment setup instructions
- [project management process](https://bisq.wiki/Project_management)

View File

@ -2,6 +2,7 @@
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2020 Haveno Dex
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

122
README.md
View File

@ -1,20 +1,124 @@
# Bisq
<div align="center">
<img src="https://raw.githubusercontent.com/haveno-dex/haveno-meta/721e52919b28b44d12b6e1e5dac57265f1c05cda/logo/haveno_logo_landscape.svg" alt="Haveno logo">
</div>
[![Build Status](https://travis-ci.org/bisq-network/bisq.svg?branch=master)](https://travis-ci.org/bisq-network/bisq)
## What is Haveno?
Haveno (pronounced ha‧ve‧no) is a private and decentralized way to exchange Monero for national currencies or other cryptocurrencies. Haveno uses peer-to-peer networking and multi-signature escrow to facilitate trading without a trusted third party custodian. Disputes can be resolved using non-custodial arbitration. Everything is built around Monero and Tor.
## What is Bisq?
Haveno is the Esperanto word for "Harbor". The project is stewarded by a core Team, currently formed by 2 people: ErCiccione and Woodser.
Bisq is a safe, private and decentralized way to exchange bitcoin for national currencies and other digital assets. Bisq uses peer-to-peer networking and multi-signature escrow to facilitate trading without a third party. Bisq is non-custodial and incorporates a human arbitration system to resolve disputes.
## Why a new platform?
To learn more, see the doc and video at https://bisq.network/intro.
Haveno is a fork of Bisq, the Bitcoin based decentralized exchange. We believe Bisq is not enough for Monero users, which badly need a private way to exchange Monero for other (crypto)currencies.
Haveno is built on Monero, which means all transactions between users are obfuscated by default. Bisq's system is based on Bitcoin and inherits all its design flaws, for example:
## Get started using Bisq
- All Bisq's in-platform transactions are based on Bitcoin, which make them slow and fully traceable.
- Bisq transactions are unique and easily visible on the blockchain. This means it's trivial to check which Bitcoin transactions are the result of a trade on Bisq.
Follow the step-by-step instructions at https://bisq.network/get-started.
Trade fees will also be drastically lower, as Monero has much lower transaction fees compared to bitcoin (average transaction fee: XMR=$0.003 BTC=$9 ).
Even if XMR transactions compose the vast majority of Bisq's activity, Bisq's team haven't displayed much interest in improving their Monero support. The important privacy issues mentioned above will be solved by simply having Monero as a base currency instead of Bitcoin.
## Contribute to Bisq
We acknowledge and thank Bisq for their efforts, but we think the Monero community needs a native, private way to exchange XMR for other currencies without passing through Bitcoin first and Haveno is here to fill that gap! We commit to contribute back to Bisq when possible.
See [CONTRIBUTING.md](CONTRIBUTING.md) and the [developer docs](docs/README.md).
## Status of the project
At the moment Haveno is only a Proof of Concept. It's already possible to initiate crypto <-> XMR and fiat <-> XMR trades, but the platform still needs a lot of work before being available for public use.
There is a lot in progress and a lot to do. To make contributions easier, we use some of github's tools, like labels and projects. We set up a [labelling system](https://github.com/haveno-dex/haveno/wiki/Labelling-system) which should make easier for people to contribute. Problems and requests about the Haveno platform are tracked on this repository. For general discussions and proposals that affect the entire Haveno ecosystem, please open an issue in the [haveno-meta repository](https://github.com/haveno-dex/haveno-meta).
These are the main priorities for the near future:
- The User Interface is basically still Bisq. Needs to be completely reworked and adapted for Monero as base currency. The new design is discussed and developed in [haveno-design](https://github.com/haveno-dex/haveno-design)
- Cleanup the repository from Bisq-specific content (https://github.com/haveno-dex/haveno/projects/1)
### Bounties
To incentivize development we adopt a simple bounty system. Contributors may be awarded bounties after completing a task (resolving an issue). [More details in the docs](https://github.com/erciccione/haveno/blob/master/docs/bounties.md).
## Keep in touch and help out!
Haveno is a community-driven project. For it to be succesful it's fundamental to have the support and help of the Monero community. We have our own Matrix server. Registrations are not open at the moment, but the rooms are public and can be joined from any matrix client (like Element). We look forward to hearing from you!
- General discussions: **Haveno** (`#haveno:haveno.network`) relayed on Freenode (`#haveno`)
- Development discussions: **Haveno Development** (`#haveno-dev:haveno.network`) relayed on Freenode (`#haveno-dev`)
Temporary email: havenodex@protonmail.com
## FAQ
See the [FAQ in the wiki](https://github.com/haveno-dex/haveno/wiki/FAQ).
## Running a local Haveno test network
1. Download [Monero CLI](https://www.getmonero.org/downloads/) for your system and sync Monero stagenet: `./monerod --stagenet --rpc-login superuser:abctesting123`, or alternatively, [set up a local Monero stagenet network](#running-a-local-monero-stagenet-network) (recommended)
3. Download and install [Bitcoin-Qt](https://bitcoin.org/en/download)
4. Run Bitcoin-Qt in regtest mode, e.g.: `./Bitcoin-Qt -regtest -peerbloomfilters=1`
5. In Bitcoin-Qt console, mine BTC regtest blocks: `generatetoaddress 101 bcrt1q6j90vywv8x7eyevcnn2tn2wrlg3vsjlsvt46qz`
6. Install [git lfs](https://git-lfs.github.com) for your system<br>
Ubuntu: `sudo apt install git-lfs`
7. `git clone https://github.com/Haveno-Dex/haveno`
8. Copy monero-wallet-rpc from step 1 to the haveno project root
9. Apply permission to run monero-wallet-rpc, e.g. `chmod 777 monero-wallet-rpc`
10. Optionally modify [WalletConfig.java](core/src/main/java/bisq/core/btc/setup/WalletConfig.java) with custom settings
11. `cd haveno`
12. `./gradlew build`
13. Start seed node, arbitrator, Alice, and Bob:
1. `./bisq-seednode --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=2002 --appName=bisq-BTC_REGTEST_Seed_2002 --daoActivated=false`
2. `./bisq-desktop --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=4444 --appName=bisq-BTC_REGTEST_arbitrator --daoActivated=false --apiPassword=apitest --apiPort=9998`
3. `./bisq-desktop --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=5555 --appName=bisq-BTC_REGTEST_Alice --daoActivated=false --apiPassword=apitest --apiPort=9999`
4. `./bisq-desktop --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=6666 --appName=bisq-BTC_REGTEST_Bob --daoActivated=false --apiPassword=apitest --apiPort=10000`
14. Arbitrator window > Account > cmd+n to register a new arbitrator
15. Arbitrator window > Account > cmd+d to register a new mediator
16. Deposit stagenet XMR to Alice and Bob's Haveno wallets (wallet address printed to terminal)
17. When deposited XMR is available, proceed to post offers, etc
### Running a local Monero stagenet network
1. Build [monero-project](https://github.com/monero-project/monero) with the following modification to the bottom of hardforks.cpp:
```c++
const hardfork_t stagenet_hard_forks[] = {
// version 1 from the start of the blockchain
{ 1, 1, 0, 1341378000 },
// versions 2-7 in rapid succession from March 13th, 2018
{ 2, 10, 0, 1521000000 },
{ 3, 20, 0, 1521120000 },
{ 4, 30, 0, 1521240000 },
{ 5, 40, 0, 1521360000 },
{ 6, 50, 0, 1521480000 },
{ 7, 60, 0, 1521600000 },
{ 8, 70, 0, 1537821770 },
{ 9, 80, 0, 1537821771 },
{ 10, 90, 0, 1550153694 },
{ 11, 100, 0, 1550225678 },
{ 12, 110, 0, 1571419280 },
{ 13, 120, 0, 1598180817 },
{ 14, 130, 0, 1598180818 }
};
```
2. Using the executables built in step 1:
* `./monerod --stagenet --no-igd --hide-my-port --data-dir node1 --p2p-bind-ip 127.0.0.1 --p2p-bind-port 48080 --rpc-bind-port 48081 --zmq-rpc-bind-port 48082 --add-exclusive-node 127.0.0.1:38080 --rpc-login superuser:abctesting123 --rpc-access-control-origins http://localhost:8080`
* `./monerod --stagenet --no-igd --hide-my-port --data-dir node2 --p2p-bind-ip 127.0.0.1 --rpc-bind-ip 0.0.0.0 --confirm-external-bind --add-exclusive-node 127.0.0.1:48080 --rpc-login superuser:abctesting123 --rpc-access-control-origins http://localhost:8080`
4. Mine the first 130 blocks to a random address before using so wallets only use the latest output type. For example, in a daemon: `start_mining 56k9Yra1pxwcTYzqKcnLip8mymSQdEfA6V7476W9XhSiHPp1hAboo1F6na7kxTxwvXU6JjDQtu8VJdGj9FEcjkxGJfsyyah 1`
## Sponsors
Would you like to help us build Haveno? Become a sponsor! We will show your logo here. Contact us at havenodex@protonmail.com.
<a href="https://getmonero.org"><img src="/media/sponsors/monero-community.png" title="Monero community" alt="Monero community logo" width="100px"></a>
<a href="https://samouraiwallet.com/"><img src="/media/sponsors/samourai.png" title="Samourai wallet" alt="Samourai wallet logo" width="100px"></a>
<a href="https://cakewallet.com/"><img src="/media/sponsors/cake-logo-blue.jpg" title="Cake wallet" alt="Cake wallet logo" width="100px"></a>
<a href="https://twitter.com/DonYakka"><img src="/media/sponsors/donyakka.jpg" title="Don Yakka" alt="Don Yakka logo" width="100px"></a>
## Support
To bring Haveno to life, we need resources. If you have the possibility, please consider donating to the project. At this stage, donations are fundamental:
`42sjokkT9FmiWPqVzrWPFE5NCJXwt96bkBozHf4vgLR9hXyJDqKHEHKVscAARuD7in5wV1meEcSTJTanCTDzidTe2cFXS1F`
![Qr code](https://raw.githubusercontent.com/haveno-dex/haveno/master/media/qrhaveno.png)
If you are using a wallet that supports Openalias (like the 'official' CLI and GUI wallets), you can simply put `donations@haveno.network` as the "receiver" address.

View File

@ -103,7 +103,6 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
assertFalse(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@ -115,9 +114,10 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getDepositTxId(),
trade.getMakerDepositTxId(),
trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;

View File

@ -86,7 +86,6 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
assertFalse(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@ -100,9 +99,10 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getDepositTxId(),
trade.getMakerDepositTxId(),
trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;

View File

@ -104,7 +104,6 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
assertTrue(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@ -116,9 +115,10 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getDepositTxId(),
trade.getMakerDepositTxId(),
trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;

View File

@ -90,7 +90,6 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
assertTrue(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@ -104,9 +103,10 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
trade.getDepositTxId(),
trade.getMakerDepositTxId(),
trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;

View File

@ -304,7 +304,7 @@ public abstract class BotProtocol {
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
}
} // end while
throw new IllegalStateException(stoppedWaitingForDepositFeeTxMsg(this.getBotClient().getTrade(tradeId).getDepositTxId()));
throw new IllegalStateException(stoppedWaitingForDepositFeeTxMsg(this.getBotClient().getTrade(tradeId).getTakerDepositTxId()));
} catch (ManualBotShutdownException ex) {
throw ex; // not an error, tells bot to shutdown
} catch (Exception ex) {
@ -314,10 +314,10 @@ public abstract class BotProtocol {
private final Predicate<TradeInfo> isDepositFeeTxStepComplete = (trade) -> {
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) && trade.getIsDepositPublished()) {
log.info("Taker deposit fee tx {} has been published.", trade.getDepositTxId());
log.info("Taker deposit fee tx {} has been published.", trade.getTakerDepositTxId());
return true;
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED) && trade.getIsDepositConfirmed()) {
log.info("Taker deposit fee tx {} has been confirmed.", trade.getDepositTxId());
log.info("Taker deposit fee tx {} has been confirmed.", trade.getTakerDepositTxId());
return true;
} else {
return false;

View File

@ -16,7 +16,9 @@
*/
package bisq.asset.coins;
import bisq.asset.AbstractAssetTest;
import org.junit.Test;
public class IridiumTest extends AbstractAssetTest {

View File

@ -40,6 +40,8 @@ configure(subprojects) {
grpcVersion = '1.25.0'
gsonVersion = '2.8.5'
guavaVersion = '28.2-jre'
moneroJavaVersion = '0.5.3'
httpclient5Version = '5.0'
guiceVersion = '4.2.2'
hamcrestVersion = '1.3'
httpclientVersion = '4.5.12'
@ -77,7 +79,9 @@ configure(subprojects) {
repositories {
mavenCentral()
//mavenLocal()
maven { url 'https://jitpack.io' }
maven { url 'https://mvnrepository.com' }
}
dependencies {
@ -293,7 +297,7 @@ configure(project(':p2p')) {
// If they have not, e.g. because Git LFS is not installed, they will be text files
// containing a sha256 hash of the remote object, indicating we should stop the
// build and inform the user how to fix the problem.
if (file('src/main/resources/ProposalStore_BTC_MAINNET').text.contains("oid sha256:"))
if (file('src/main/resources/AccountAgeWitnessStore_XMR_MAINNET_placeholder').text.contains("oid sha256:"))
throw new GradleException("p2p data store files have not been synchronized. " +
"To fix this, ensure you have Git LFS installed and run `git lfs pull`. " +
"See docs/build.md for more information.")
@ -317,6 +321,13 @@ configure(project(':core')) {
exclude(module: 'base64')
exclude(module: 'httpcore-nio')
}
compile("io.github.monero-ecosystem:monero-java:$moneroJavaVersion") {
exclude(module: 'jackson-core')
exclude(module: 'jackson-annotations')
exclude(module: 'jackson-databind')
exclude(module: 'bcprov-jdk15on')
}
implementation("org.apache.httpcomponents.client5:httpclient5:$httpclient5Version")
compile "com.fasterxml.jackson.core:jackson-core:$jacksonVersion"
compile "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion"
compile("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") {
@ -343,6 +354,7 @@ configure(project(':cli')) {
dependencies {
compile project(':proto')
compile project(':core')
implementation "net.sf.jopt-simple:jopt-simple:$joptVersion"
implementation "com.google.guava:guava:$guavaVersion"
implementation "com.google.protobuf:protobuf-java:$protobufVersion"

View File

@ -25,6 +25,7 @@ import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Locale;
@ -32,6 +33,10 @@ import static java.lang.String.format;
import static java.math.RoundingMode.HALF_UP;
import static java.math.RoundingMode.UNNECESSARY;
import monero.common.MoneroUtils;
@VisibleForTesting
public class CurrencyFormat {
@ -57,6 +62,10 @@ public class CurrencyFormat {
return BSQ_FORMAT.format(BigDecimal.valueOf(sats).divide(BSQ_SATOSHI_DIVISOR));
}
public static String formatXmr(BigInteger amount) {
return "" + MoneroUtils.atomicUnitsToXmr(amount);
}
public static String formatBsqAmount(long bsqSats) {
// BSQ sats = trade.getOffer().getVolume()
NUMBER_FORMAT.setMinimumFractionDigits(2);

View File

@ -17,6 +17,8 @@
package bisq.cli;
import bisq.core.util.ParsingUtils;
import bisq.proto.grpc.ContractInfo;
import bisq.proto.grpc.TradeInfo;
@ -133,7 +135,7 @@ public class TradeFormat {
bsqReceiveAddress.apply(tradeInfo, showBsqBuyerAddress));
}
private static final Function<TradeInfo, String> priceHeader = (t) ->
private static final Function<TradeInfo, String> priceHeader = (t) -> // TODO (woodser): update these to XMR
t.getOffer().getBaseCurrencyCode().equals("BTC")
? COL_HEADER_PRICE
: COL_HEADER_PRICE_OF_ALTCOIN;
@ -144,11 +146,7 @@ public class TradeFormat {
: t.getOffer().getBaseCurrencyCode();
private static final BiFunction<TradeInfo, Boolean, String> makerTakerFeeHeaderCurrencyCode = (t, isTaker) -> {
if (isTaker) {
return t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ";
} else {
return t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ";
}
return "XMR";
};
private static final Function<TradeInfo, String> paymentStatusHeaderCurrencyCode = (t) ->
@ -163,7 +161,7 @@ public class TradeFormat {
private static final Function<TradeInfo, String> amountFormat = (t) ->
t.getOffer().getBaseCurrencyCode().equals("BTC")
? formatSatoshis(t.getTradeAmountAsLong())
? formatSatoshis(t.getTradeAmountAsLong()) // TODO (woodser): delete formatSatoshis(), formatBsq() and change base currency code to XMR
: formatCryptoCurrencyOfferVolume(t.getOffer().getVolume());
private static final BiFunction<TradeInfo, Boolean, String> makerTakerMinerTxFeeFormat = (t, isTaker) -> {
@ -175,15 +173,7 @@ public class TradeFormat {
};
private static final BiFunction<TradeInfo, Boolean, String> makerTakerFeeFormat = (t, isTaker) -> {
if (isTaker) {
return t.getIsCurrencyForTakerFeeBtc()
? formatSatoshis(t.getTakerFeeAsLong())
: formatBsq(t.getTakerFeeAsLong());
} else {
return t.getOffer().getIsCurrencyForMakerFeeBtc()
? formatSatoshis(t.getOffer().getMakerFee())
: formatBsq(t.getOffer().getMakerFee());
}
return formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTakerFeeAsLong()));
};
private static final Function<TradeInfo, String> tradeCostFormat = (t) ->

View File

@ -25,7 +25,7 @@ public abstract class Task<T extends Model> {
public static Class<? extends Task> taskToIntercept;
private final TaskRunner taskHandler;
protected final TaskRunner taskHandler;
protected final T model;
protected String errorMessage = "An error occurred at task: " + getClass().getSimpleName();
protected boolean completed;

View File

@ -33,6 +33,7 @@ import bisq.core.payment.payload.PaymentMethod;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer;
@ -300,6 +301,7 @@ public class AccountAgeWitnessService {
}
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
if (trade instanceof ArbitratorTrade) return Optional.empty(); // TODO (woodser): arbitrator trade has two peers
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
return (tradingPeer == null ||
tradingPeer.getPaymentAccountPayload() == null ||
@ -842,6 +844,7 @@ public class AccountAgeWitnessService {
}
public SignState getSignState(Trade trade) {
if (trade instanceof ArbitratorTrade) return SignState.UNSIGNED; // TODO (woodser): arbitrator has two peers
return findTradePeerWitness(trade)
.map(this::getSignState)
.orElse(SignState.UNSIGNED);

View File

@ -109,7 +109,6 @@ class CoreTradesService {
tradeManager.onTakeOffer(offer.getAmount(),
takeOfferModel.getTxFeeFromFeeService(),
takeOfferModel.getTakerFee(),
takeOfferModel.isCurrencyForTakerFeeBtc(),
offer.getPrice().getValue(),
takeOfferModel.getFundsNeededForTrade(),
offer,

View File

@ -84,7 +84,6 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.common.config.BaseCurrencyNetwork.BTC_DAO_REGTEST;
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
import static bisq.core.util.ParsingUtils.parseToCoin;
import static java.lang.String.format;

View File

@ -34,8 +34,7 @@ public class ContractInfo implements Payload {
private final String buyerNodeAddress;
private final String sellerNodeAddress;
private final String mediatorNodeAddress;
private final String refundAgentNodeAddress;
private final String arbitratorNodeAddress;
private final boolean isBuyerMakerAndSellerTaker;
private final String makerAccountId;
private final String takerAccountId;
@ -47,8 +46,7 @@ public class ContractInfo implements Payload {
public ContractInfo(String buyerNodeAddress,
String sellerNodeAddress,
String mediatorNodeAddress,
String refundAgentNodeAddress,
String arbitratorNodeAddress,
boolean isBuyerMakerAndSellerTaker,
String makerAccountId,
String takerAccountId,
@ -59,8 +57,7 @@ public class ContractInfo implements Payload {
long lockTime) {
this.buyerNodeAddress = buyerNodeAddress;
this.sellerNodeAddress = sellerNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
this.refundAgentNodeAddress = refundAgentNodeAddress;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
this.makerAccountId = makerAccountId;
this.takerAccountId = takerAccountId;
@ -75,7 +72,6 @@ public class ContractInfo implements Payload {
// For transmitting TradeInfo messages when no contract is available.
public static Supplier<ContractInfo> emptyContract = () ->
new ContractInfo("",
"",
"",
"",
false,
@ -94,8 +90,7 @@ public class ContractInfo implements Payload {
public static ContractInfo fromProto(bisq.proto.grpc.ContractInfo proto) {
return new ContractInfo(proto.getBuyerNodeAddress(),
proto.getSellerNodeAddress(),
proto.getMediatorNodeAddress(),
proto.getRefundAgentNodeAddress(),
proto.getArbitratorNodeAddress(),
proto.getIsBuyerMakerAndSellerTaker(),
proto.getMakerAccountId(),
proto.getTakerAccountId(),
@ -111,8 +106,7 @@ public class ContractInfo implements Payload {
return bisq.proto.grpc.ContractInfo.newBuilder()
.setBuyerNodeAddress(buyerNodeAddress)
.setSellerNodeAddress(sellerNodeAddress)
.setMediatorNodeAddress(mediatorNodeAddress)
.setRefundAgentNodeAddress(refundAgentNodeAddress)
.setArbitratorNodeAddress(arbitratorNodeAddress)
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
.setMakerAccountId(makerAccountId)
.setTakerAccountId(takerAccountId)

View File

@ -47,7 +47,8 @@ public class TradeInfo implements Payload {
private final long txFeeAsLong;
private final long takerFeeAsLong;
private final String takerFeeTxId;
private final String depositTxId;
private final String makerDepositTxId;
private final String takerDepositTxId;
private final String payoutTxId;
private final long tradeAmountAsLong;
private final long tradePrice;
@ -74,7 +75,8 @@ public class TradeInfo implements Payload {
this.txFeeAsLong = builder.txFeeAsLong;
this.takerFeeAsLong = builder.takerFeeAsLong;
this.takerFeeTxId = builder.takerFeeTxId;
this.depositTxId = builder.depositTxId;
this.makerDepositTxId = builder.makerDepositTxId;
this.takerDepositTxId = builder.takerDepositTxId;
this.payoutTxId = builder.payoutTxId;
this.tradeAmountAsLong = builder.tradeAmountAsLong;
this.tradePrice = builder.tradePrice;
@ -102,8 +104,7 @@ public class TradeInfo implements Payload {
Contract contract = trade.getContract();
contractInfo = new ContractInfo(contract.getBuyerPayoutAddressString(),
contract.getSellerPayoutAddressString(),
contract.getMediatorNodeAddress().getFullAddress(),
contract.getRefundAgentNodeAddress().getFullAddress(),
contract.getArbitratorNodeAddress().getFullAddress(),
contract.isBuyerMakerAndSellerTaker(),
contract.getMakerAccountId(),
contract.getTakerAccountId(),
@ -122,12 +123,12 @@ public class TradeInfo implements Payload {
.withShortId(trade.getShortId())
.withDate(trade.getDate().getTime())
.withRole(role == null ? "" : role)
.withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc())
.withTxFeeAsLong(trade.getTxFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeTxId(trade.getTakerFeeTxId())
.withDepositTxId(trade.getDepositTxId())
.withMakerDepositTxId(trade.getMakerDepositTxId())
.withTakerDepositTxId(trade.getTakerDepositTxId())
.withPayoutTxId(trade.getPayoutTxId())
.withTradeAmountAsLong(trade.getTradeAmountAsLong())
.withTradePrice(trade.getTradePrice().getValue())
@ -159,11 +160,11 @@ public class TradeInfo implements Payload {
.setShortId(shortId)
.setDate(date)
.setRole(role)
.setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc)
.setTxFeeAsLong(txFeeAsLong)
.setTakerFeeAsLong(takerFeeAsLong)
.setTakerFeeTxId(takerFeeTxId == null ? "" : takerFeeTxId)
.setDepositTxId(depositTxId == null ? "" : depositTxId)
.setMakerDepositTxId(makerDepositTxId == null ? "" : makerDepositTxId)
.setTakerDepositTxId(takerDepositTxId == null ? "" : takerDepositTxId)
.setPayoutTxId(payoutTxId == null ? "" : payoutTxId)
.setTradeAmountAsLong(tradeAmountAsLong)
.setTradePrice(tradePrice)
@ -189,11 +190,11 @@ public class TradeInfo implements Payload {
.withShortId(proto.getShortId())
.withDate(proto.getDate())
.withRole(proto.getRole())
.withIsCurrencyForTakerFeeBtc(proto.getIsCurrencyForTakerFeeBtc())
.withTxFeeAsLong(proto.getTxFeeAsLong())
.withTakerFeeAsLong(proto.getTakerFeeAsLong())
.withTakerFeeTxId(proto.getTakerFeeTxId())
.withDepositTxId(proto.getDepositTxId())
.withMakerDepositTxId(proto.getMakerDepositTxId())
.withTakerDepositTxId(proto.getTakerDepositTxId())
.withPayoutTxId(proto.getPayoutTxId())
.withTradeAmountAsLong(proto.getTradeAmountAsLong())
.withTradePrice(proto.getTradePrice())
@ -228,7 +229,8 @@ public class TradeInfo implements Payload {
private long txFeeAsLong;
private long takerFeeAsLong;
private String takerFeeTxId;
private String depositTxId;
private String makerDepositTxId;
private String takerDepositTxId;
private String payoutTxId;
private long tradeAmountAsLong;
private long tradePrice;
@ -290,8 +292,13 @@ public class TradeInfo implements Payload {
return this;
}
public TradeInfoBuilder withDepositTxId(String depositTxId) {
this.depositTxId = depositTxId;
public TradeInfoBuilder withMakerDepositTxId(String makerDepositTxId) {
this.makerDepositTxId = makerDepositTxId;
return this;
}
public TradeInfoBuilder withTakerDepositTxId(String takerDepositTxId) {
this.takerDepositTxId = takerDepositTxId;
return this;
}
@ -386,7 +393,8 @@ public class TradeInfo implements Payload {
", txFeeAsLong='" + txFeeAsLong + '\'' + "\n" +
", takerFeeAsLong='" + takerFeeAsLong + '\'' + "\n" +
", takerFeeTxId='" + takerFeeTxId + '\'' + "\n" +
", depositTxId='" + depositTxId + '\'' + "\n" +
", makerDepositTxId='" + makerDepositTxId + '\'' + "\n" +
", takerDepositTxId='" + takerDepositTxId + '\'' + "\n" +
", payoutTxId='" + payoutTxId + '\'' + "\n" +
", tradeAmountAsLong='" + tradeAmountAsLong + '\'' + "\n" +
", tradePrice='" + tradePrice + '\'' + "\n" +

View File

@ -20,6 +20,7 @@ package bisq.core.app;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoSetup;
import bisq.core.dao.node.full.RpcService;
import bisq.core.offer.OpenOfferManager;
@ -235,6 +236,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
injector.getInstance(RpcService.class).shutDown();
injector.getInstance(DaoSetup.class).shutDown();
injector.getInstance(AvoidStandbyModeService.class).shutDown();
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc?
log.info("OpenOfferManager shutdown started");
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
log.info("OpenOfferManager shutdown completed");

View File

@ -28,6 +28,7 @@ import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
import bisq.core.dao.governance.voteresult.VoteResultException;
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
@ -126,6 +127,7 @@ public class BisqSetup {
private final WalletsManager walletsManager;
private final WalletsSetup walletsSetup;
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final P2PService p2PService;
private final SignedWitnessStorageService signedWitnessStorageService;
private final TradeManager tradeManager;
@ -210,6 +212,7 @@ public class BisqSetup {
WalletAppSetup walletAppSetup,
WalletsManager walletsManager,
WalletsSetup walletsSetup,
XmrWalletService xmrWalletService,
BtcWalletService btcWalletService,
P2PService p2PService,
SignedWitnessStorageService signedWitnessStorageService,
@ -231,6 +234,7 @@ public class BisqSetup {
this.walletAppSetup = walletAppSetup;
this.walletsManager = walletsManager;
this.walletsSetup = walletsSetup;
this.xmrWalletService = xmrWalletService;
this.btcWalletService = btcWalletService;
this.p2PService = p2PService;
this.signedWitnessStorageService = signedWitnessStorageService;
@ -514,17 +518,18 @@ public class BisqSetup {
// We check if we have open offers with no confidence object at the maker fee tx. That can happen if the
// miner fee was too low and the transaction got removed from mempool and got out from our wallet after a
// resync.
openOfferManager.getObservableList().forEach(e -> {
String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId();
if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) {
String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx",
e.getOffer().getShortId(), offerFeePaymentTxId);
log.warn(message);
if (lockedUpFundsHandler != null) {
lockedUpFundsHandler.accept(message);
}
}
});
// TODO (woodser): check for invalid maker fee txs with xmr?
// openOfferManager.getObservableList().forEach(e -> {
// String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId();
// if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) { // TODO (woodser): needed for xmr base?
// String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx",
// e.getOffer().getShortId(), offerFeePaymentTxId);
// log.warn(message);
// if (lockedUpFundsHandler != null) {
// lockedUpFundsHandler.accept(message);
// }
// }
// });
}
@Nullable

View File

@ -249,9 +249,12 @@ public class WalletAppSetup {
.filter(trade -> trade.getOffer() != null)
.forEach(trade -> {
String details = null;
if (txId.equals(trade.getDepositTxId())) {
details = Res.get("popup.warning.trade.txRejected.deposit");
if (txId.equals(trade.getMakerDepositTxId())) {
details = Res.get("popup.warning.trade.txRejected.deposit"); // TODO (woodser): txRejected.maker_deposit, txRejected.taker_deposit
}
if (txId.equals(trade.getTakerDepositTxId())) {
details = Res.get("popup.warning.trade.txRejected.deposit");
}
if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) {
details = Res.get("popup.warning.trade.txRejected.tradeFee");
}

View File

@ -21,6 +21,7 @@ import bisq.core.app.BisqExecutable;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoSetup;
import bisq.core.dao.node.full.RpcService;
import bisq.core.offer.OpenOfferManager;
@ -101,6 +102,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
});
});
injector.getInstance(WalletsSetup.class).shutDown();
injector.getInstance(XmrWalletService.class).shutDown(); // TODO (woodser): this is not actually called, perhaps because WalletsSetup.class completes too quick so its listener calls System.exit(0)
injector.getInstance(BtcWalletService.class).shutDown();
injector.getInstance(BsqWalletService.class).shutDown();
}));

View File

@ -17,9 +17,7 @@
package bisq.core.btc;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.dispute.Dispute;
@ -32,7 +30,6 @@ import bisq.core.trade.failed.FailedTradesManager;
import bisq.common.UserThread;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
@ -41,16 +38,23 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import java.util.Objects;
import java.util.stream.Stream;
import java.math.BigInteger;
import java.util.List;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.model.MoneroAccount;
import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroWalletListener;
@Slf4j
public class Balances {
private final TradeManager tradeManager;
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final OpenOfferManager openOfferManager;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
@ -65,13 +69,13 @@ public class Balances {
@Inject
public Balances(TradeManager tradeManager,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
OpenOfferManager openOfferManager,
ClosedTradableManager closedTradableManager,
FailedTradesManager failedTradesManager,
RefundManager refundManager) {
this.tradeManager = tradeManager;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.openOfferManager = openOfferManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
@ -82,13 +86,11 @@ public class Balances {
openOfferManager.getObservableList().addListener((ListChangeListener<OpenOffer>) c -> updateBalance());
tradeManager.getObservableList().addListener((ListChangeListener<Trade>) change -> updateBalance());
refundManager.getDisputesAsObservableList().addListener((ListChangeListener<Dispute>) c -> updateBalance());
btcWalletService.addBalanceListener(new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance();
}
xmrWalletService.getWallet().addListener(new MoneroWalletListener() {
@Override public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { updateBalance(); }
@Override public void onOutputReceived(MoneroOutputWallet output) { updateBalance(); }
@Override public void onOutputSpent(MoneroOutputWallet output) { updateBalance(); }
});
updateBalance();
}
@ -101,31 +103,24 @@ public class Balances {
});
}
// TODO (woodser): reserved balance = reserved for trade, locked balance = locked in multisig
private void updateAvailableBalance() {
long sum = btcWalletService.getAddressEntriesForAvailableBalanceStream()
.mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value)
.sum();
availableBalance.set(Coin.valueOf(sum));
availableBalance.set(Coin.valueOf(xmrWalletService.getWallet().getUnlockedBalance(0).longValue()));
}
private void updateReservedBalance() {
long sum = openOfferManager.getObservableList().stream()
.map(openOffer -> btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE)
.orElse(null))
.filter(Objects::nonNull)
.mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value)
.sum();
reservedBalance.set(Coin.valueOf(sum));
BigInteger sum = new BigInteger("0");
List<MoneroAccount> accounts = xmrWalletService.getWallet().getAccounts();
for (MoneroAccount account : accounts) {
if (account.getIndex() != 0) sum = sum.add(account.getBalance());
}
reservedBalance.set(Coin.valueOf(sum.longValue()));
}
private void updateLockedBalance() {
Stream<Trade> lockedTrades = Stream.concat(closedTradableManager.getTradesStreamWithFundsLockedIn(), failedTradesManager.getTradesStreamWithFundsLockedIn());
lockedTrades = Stream.concat(lockedTrades, tradeManager.getTradesStreamWithFundsLockedIn());
long sum = lockedTrades.map(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG)
.orElse(null))
.filter(Objects::nonNull)
.mapToLong(AddressEntry::getCoinLockedInMultiSig)
.sum();
lockedBalance.set(Coin.valueOf(sum));
BigInteger balance = xmrWalletService.getWallet().getBalance(0);
BigInteger unlockedBalance = xmrWalletService.getWallet().getUnlockedBalance(0);
lockedBalance.set(Coin.valueOf(balance.subtract(unlockedBalance).longValue()));
}
}

View File

@ -18,6 +18,7 @@
package bisq.core.btc;
import bisq.core.btc.model.AddressEntryList;
import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.btc.nodes.BtcNodes;
import bisq.core.btc.setup.RegTestHost;
import bisq.core.btc.setup.WalletsSetup;
@ -26,6 +27,7 @@ import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.NonBsqCoinSelector;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.provider.ProvidersRepository;
import bisq.core.provider.fee.FeeProvider;
import bisq.core.provider.fee.FeeService;
@ -85,7 +87,9 @@ public class BitcoinModule extends AppModule {
bind(new TypeLiteral<List<String>>(){}).annotatedWith(named(PROVIDERS)).toInstance(config.providers);
bind(AddressEntryList.class).in(Singleton.class);
bind(XmrAddressEntryList.class).in(Singleton.class);
bind(WalletsSetup.class).in(Singleton.class);
bind(XmrWalletService.class).in(Singleton.class);
bind(BtcWalletService.class).in(Singleton.class);
bind(BsqWalletService.class).in(Singleton.class);
bind(TradeWalletService.class).in(Singleton.class);

View File

@ -0,0 +1,38 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.btc.listeners;
import java.math.BigInteger;
public class XmrBalanceListener {
private Integer accountIndex;
public XmrBalanceListener() {
}
public XmrBalanceListener(Integer accountIndex) {
this.accountIndex = accountIndex;
}
public Integer getAccountIndex() {
return accountIndex;
}
public void onBalanceChanged(BigInteger balance) {
}
}

View File

@ -0,0 +1,150 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.btc.model;
import bisq.common.proto.ProtoUtil;
import bisq.common.proto.persistable.PersistablePayload;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
/**
* Every trade uses a XmrAddressEntry with a dedicated address for all transactions related to the trade.
* That way we have a kind of separated trade wallet, isolated from other transactions and avoiding coin merge.
* If we would not avoid coin merge the user would lose privacy between trades.
*/
@EqualsAndHashCode
@Slf4j
public final class XmrAddressEntry implements PersistablePayload {
public enum Context {
ARBITRATOR,
AVAILABLE,
OFFER_FUNDING,
RESERVED_FOR_TRADE,
MULTI_SIG,
TRADE_PAYOUT
}
// keyPair can be null in case the object is created from deserialization as it is transient.
// It will be restored when the wallet is ready at setDeterministicKey
// So after startup it must never be null
@Nullable
@Getter
private final String offerId;
@Getter
private final Context context;
@Getter
private final int accountIndex;
@Getter
private final String addressString;
private long coinLockedInMultiSig;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialization
///////////////////////////////////////////////////////////////////////////////////////////
public XmrAddressEntry(int accountIndex, String address, Context context) {
this(accountIndex, address, context, null, null);
}
public XmrAddressEntry(int accountIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) {
this.accountIndex = accountIndex;
this.addressString = address;
this.offerId = offerId;
this.context = context;
if (coinLockedInMultiSig != null) this.coinLockedInMultiSig = coinLockedInMultiSig.value;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
public static XmrAddressEntry fromProto(protobuf.XmrAddressEntry proto) {
return new XmrAddressEntry(proto.getAccountIndex(),
ProtoUtil.stringOrNullFromProto(proto.getAddressString()),
ProtoUtil.enumFromProto(XmrAddressEntry.Context.class, proto.getContext().name()),
ProtoUtil.stringOrNullFromProto(proto.getOfferId()),
Coin.valueOf(proto.getCoinLockedInMultiSig()));
}
@Override
public protobuf.XmrAddressEntry toProtoMessage() {
protobuf.XmrAddressEntry.Builder builder = protobuf.XmrAddressEntry.newBuilder()
.setAccountIndex(accountIndex)
.setAddressString(addressString)
.setContext(protobuf.XmrAddressEntry.Context.valueOf(context.name()))
.setCoinLockedInMultiSig(coinLockedInMultiSig);
Optional.ofNullable(offerId).ifPresent(builder::setOfferId);
return builder.build();
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void setCoinLockedInMultiSig(@NotNull Coin coinLockedInMultiSig) {
this.coinLockedInMultiSig = coinLockedInMultiSig.value;
}
// For display we usually only display the first 8 characters.
@Nullable
public String getShortOfferId() {
return offerId != null ? Utilities.getShortId(offerId) : null;
}
public boolean isOpenOffer() {
return context == Context.OFFER_FUNDING || context == Context.RESERVED_FOR_TRADE;
}
public boolean isTrade() {
return context == Context.MULTI_SIG || context == Context.TRADE_PAYOUT;
}
public boolean isTradable() {
return isOpenOffer() || isTrade();
}
public Coin getCoinLockedInMultiSig() {
return Coin.valueOf(coinLockedInMultiSig);
}
@Override
public String toString() {
return "XmrAddressEntry{" +
"offerId='" + getOfferId() + '\'' +
", context=" + context +
", accountIndex=" + getAccountIndex() +
", address=" + getAddressString() +
'}';
}
}

View File

@ -0,0 +1,235 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.btc.model;
import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.persistable.PersistableEnvelope;
import bisq.common.proto.persistable.PersistedDataHost;
import com.google.protobuf.Message;
import com.google.inject.Inject;
import com.google.common.collect.ImmutableList;
import java.math.BigInteger;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroAccount;
import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroWalletListener;
/**
* The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the
* associated protobuf message.
*/
@Slf4j
public final class XmrAddressEntryList implements PersistableEnvelope, PersistedDataHost {
transient private PersistenceManager<XmrAddressEntryList> persistenceManager;
transient private MoneroWallet wallet;
private final Set<XmrAddressEntry> entrySet = new CopyOnWriteArraySet<>();
@Inject
public XmrAddressEntryList(PersistenceManager<XmrAddressEntryList> persistenceManager) {
this.persistenceManager = persistenceManager;
this.persistenceManager.initialize(this, PersistenceManager.Source.PRIVATE);
}
@Override
public void readPersisted(Runnable completeHandler) {
persistenceManager.readPersisted(persisted -> {
entrySet.clear();
entrySet.addAll(persisted.entrySet);
completeHandler.run();
},
completeHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private XmrAddressEntryList(Set<XmrAddressEntry> entrySet) {
this.entrySet.addAll(entrySet);
}
public static XmrAddressEntryList fromProto(protobuf.XmrAddressEntryList proto) {
Set<XmrAddressEntry> entrySet = proto.getXmrAddressEntryList().stream()
.map(XmrAddressEntry::fromProto)
.collect(Collectors.toSet());
return new XmrAddressEntryList(entrySet);
}
@Override
public Message toProtoMessage() {
Set<protobuf.XmrAddressEntry> addressEntries = entrySet.stream()
.map(XmrAddressEntry::toProtoMessage)
.collect(Collectors.toSet());
return protobuf.PersistableEnvelope.newBuilder()
.setXmrAddressEntryList(protobuf.XmrAddressEntryList.newBuilder()
.addAllXmrAddressEntry(addressEntries))
.build();
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void onWalletReady(MoneroWallet wallet) {
this.wallet = wallet;
if (!entrySet.isEmpty()) {
// Set<XmrAddressEntry> toBeRemoved = new HashSet<>();
// entrySet.forEach(addressEntry -> {
// DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubKeyHash(
// addressEntry.getPubKeyHash(),
// Script.ScriptType.P2PKH);
// if (keyFromPubHash != null) {
// Address addressFromKey = LegacyAddress.fromKey(Config.baseCurrencyNetworkParameters(), keyFromPubHash);
// // We want to ensure key and address matches in case we have address in entry available already
// if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) {
// addressEntry.setDeterministicKey(keyFromPubHash);
// } else {
// log.error("We found an address entry without key but cannot apply the key as the address " +
// "is not matching. " +
// "We remove that entry as it seems it is not compatible with our wallet. " +
// "addressFromKey={}, addressEntry.getAddress()={}",
// addressFromKey, addressEntry.getAddress());
// toBeRemoved.add(addressEntry);
// }
// } else {
// log.error("Key from addressEntry {} not found in that wallet. We remove that entry. " +
// "This is expected at restore from seeds.", addressEntry.toString());
// toBeRemoved.add(addressEntry);
// }
// });
//
// toBeRemoved.forEach(entrySet::remove);
} else {
// As long the old arbitration domain is not removed from the code base we still support it here.
MoneroAccount account = wallet.createAccount();
entrySet.add(new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), XmrAddressEntry.Context.ARBITRATOR));
}
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
// IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to
// incoming txs at blockchain sync to add the rest.
if (wallet.getBalance().compareTo(new BigInteger("0")) > 0) {
wallet.getAccounts().forEach(acct -> {
log.info("Create XmrAddressEntry for IssuedReceiveAddress. address={}", acct.getPrimaryAddress());
if (acct.getIndex() != 0) entrySet.add(new XmrAddressEntry(acct.getIndex(), acct.getPrimaryAddress(), XmrAddressEntry.Context.AVAILABLE));
});
}
// We add those listeners to get notified about potential new transactions and
// add an address entry list in case it does not exist yet. This is mainly needed for restore from seed words
// but can help as well in case the addressEntry list would miss an address where the wallet was received
// funds (e.g. if the user sends funds to an address which has not been provided in the main UI - like from the
// wallet details window).
wallet.addListener(new MoneroWalletListener() {
@Override public void onOutputReceived(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); }
@Override public void onOutputSpent(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); }
});
requestPersistence();
}
public ImmutableList<XmrAddressEntry> getAddressEntriesAsListImmutable() {
return ImmutableList.copyOf(entrySet);
}
public void addAddressEntry(XmrAddressEntry addressEntry) {
boolean entryWithSameOfferIdAndContextAlreadyExist = entrySet.stream().anyMatch(e -> {
if (addressEntry.getOfferId() != null) {
return addressEntry.getOfferId().equals(e.getOfferId()) && addressEntry.getContext() == e.getContext();
}
return false;
});
if (entryWithSameOfferIdAndContextAlreadyExist) {
log.error("We have an address entry with the same offer ID and context. We do not add the new one. " +
"addressEntry={}, entrySet={}", addressEntry, entrySet);
if (true) throw new RuntimeException("why?");
return;
}
boolean setChangedByAdd = entrySet.add(addressEntry);
if (setChangedByAdd)
requestPersistence();
}
public void swapToAvailable(XmrAddressEntry addressEntry) {
boolean setChangedByRemove = entrySet.remove(addressEntry);
boolean setChangedByAdd = entrySet.add(new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(),
XmrAddressEntry.Context.AVAILABLE));
if (setChangedByRemove || setChangedByAdd) {
requestPersistence();
}
}
public XmrAddressEntry swapAvailableToAddressEntryWithOfferId(XmrAddressEntry addressEntry,
XmrAddressEntry.Context context,
String offerId) {
boolean setChangedByRemove = entrySet.remove(addressEntry);
final XmrAddressEntry newAddressEntry = new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(), context, offerId, null);
boolean setChangedByAdd = entrySet.add(newAddressEntry);
if (setChangedByRemove || setChangedByAdd)
requestPersistence();
return newAddressEntry;
}
public void requestPersistence() {
persistenceManager.requestPersistence();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void maybeAddNewAddressEntry(MoneroOutputWallet output) {
if (output.getAccountIndex() == 0) return;
String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex());
if (!isAddressInEntries(address)) addAddressEntry(new XmrAddressEntry(output.getAccountIndex(), address, XmrAddressEntry.Context.AVAILABLE));
}
private boolean isAddressInEntries(String address) {
for (XmrAddressEntry entry : entrySet) {
if (entry.getAddressString().equals(address)) return true;
}
return false;
}
@Override
public String toString() {
return "XmrAddressEntryList{" +
",\n entrySet=" + entrySet +
"\n}";
}
}

View File

@ -0,0 +1,128 @@
package bisq.core.btc.setup;
import java.net.ServerSocket;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import monero.common.MoneroError;
import monero.wallet.MoneroWalletRpc;
/**
* Manages monero-wallet-rpc processes bound to ports.
*/
public class MoneroWalletRpcManager {
private static int NUM_ALLOWED_ATTEMPTS = 1; // allow this many attempts to bind to an assigned port
private Integer startPort;
private Map<Integer, MoneroWalletRpc> registeredPorts = new HashMap<Integer, MoneroWalletRpc>();
/**
* Manage monero-wallet-rpc instances by auto-assigning ports.
*/
public MoneroWalletRpcManager() { }
/**
* Manage monero-wallet-rpc instances by assigning consecutive ports from a starting port.
*
* @param startPort is the starting port to bind to
*/
public MoneroWalletRpcManager(int startPort) {
this.startPort = startPort;
}
/**
* Start a new instance of monero-wallet-rpc.
*
* @param cmd command line parameters to start monero-wallet-rpc
* @return a client connected to the monero-wallet-rpc instance
*/
public MoneroWalletRpc startInstance(List<String> cmd) {
try {
// register given port
if (cmd.indexOf("--rpc-bind-port") >= 0) {
int port = Integer.valueOf(cmd.indexOf("--rpc-bind-port") + 1);
MoneroWalletRpc walletRpc = new MoneroWalletRpc(cmd); // starts monero-wallet-rpc process
registeredPorts.put(port, walletRpc);
return walletRpc;
}
// register assigned ports up to maximum attempts
else {
int numAttempts = 0;
while (numAttempts < NUM_ALLOWED_ATTEMPTS) {
try {
numAttempts++;
int port = registerPort();
List<String> cmdCopy = new ArrayList<String>(cmd); // preserve original cmd
cmdCopy.add("--rpc-bind-port");
cmdCopy.add("" + port);
System.out.println(cmdCopy);
MoneroWalletRpc walletRpc = new MoneroWalletRpc(cmdCopy); // start monero-wallet-rpc process
registeredPorts.put(port, walletRpc);
return walletRpc;
} catch (Exception e) {
if (numAttempts >= NUM_ALLOWED_ATTEMPTS) {
System.err.println("Unable to start monero-wallet-rpc instance after " + NUM_ALLOWED_ATTEMPTS + " attempts");
throw e;
}
}
}
throw new MoneroError("Failed to start monero-wallet-rpc instance after " + NUM_ALLOWED_ATTEMPTS + " attempts"); // should never reach here
}
} catch (IOException e) {
throw new MoneroError(e);
}
}
/**
* Stop an instance of monero-wallet-rpc.
*
* @param walletRpc the client connected to the monero-wallet-rpc instance to stop
*/
public void stopInstance(MoneroWalletRpc walletRpc) {
boolean found = false;
for (Map.Entry<Integer, MoneroWalletRpc> entry : registeredPorts.entrySet()) {
if (walletRpc == entry.getValue()) {
walletRpc.stop();
found = true;
try { unregisterPort(entry.getKey()); }
catch (Exception e) { throw new MoneroError(e); }
break;
}
}
if (!found) throw new RuntimeException("MoneroWalletRpc instance not associated with port");
}
private int registerPort() throws IOException {
// register next consecutive port
if (startPort != null) {
int port = startPort;
while (registeredPorts.containsKey(port)) port++;
registeredPorts.put(port, null);
return port;
}
// register auto-assigned port
else {
ServerSocket socket = new ServerSocket(0); // use socket to get available port
int port = socket.getLocalPort();
socket.close();
registeredPorts.put(port, null);
return port;
}
}
private void unregisterPort(int port) {
registeredPorts.remove(port);
}
}

View File

@ -71,6 +71,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
@ -85,6 +86,14 @@ import static bisq.common.util.Preconditions.checkDir;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import monero.common.MoneroUtils;
import monero.daemon.model.MoneroNetworkType;
import monero.wallet.MoneroWallet;
import monero.wallet.MoneroWalletRpc;
import monero.wallet.model.MoneroWalletConfig;
/**
* <p>Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory
* and file prefix, optionally configure a few things, then use startAsync and optionally awaitRunning. The object will
@ -112,15 +121,29 @@ public class WalletConfig extends AbstractIdleService {
protected static final Logger log = LoggerFactory.getLogger(WalletConfig.class);
// Monero configuration
// TODO: don't hard code configuration, inject into classes?
private static final MoneroNetworkType MONERO_NETWORK_TYPE = MoneroNetworkType.STAGENET;
private static final String MONERO_DAEMON_URI = "http://localhost:38081";
private static final String MONERO_DAEMON_USERNAME = "superuser";
private static final String MONERO_DAEMON_PASSWORD = "abctesting123";
private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager();
private static final String MONERO_WALLET_RPC_PATH = System.getProperty("user.dir") + "/monero-wallet-rpc"; // current working directory
private static final String MONERO_WALLET_RPC_USERNAME = "rpc_user";
private static final String MONERO_WALLET_RPC_PASSWORD = "abc123";
private static final long MONERO_WALLET_SYNC_RATE = 5000l;
protected final NetworkParameters params;
protected final String filePrefix;
protected volatile BlockChain vChain;
protected volatile SPVBlockStore vStore;
protected volatile MoneroWallet vXmrWallet;
protected volatile Wallet vBtcWallet;
protected volatile Wallet vBsqWallet;
protected volatile PeerGroup vPeerGroup;
protected final File directory;
protected volatile File vXmrWalletFile;
protected volatile File vBtcWalletFile;
protected volatile File vBsqWalletFile;
@ -261,6 +284,60 @@ public class WalletConfig extends AbstractIdleService {
// Meant to be overridden by subclasses
}
public MoneroWallet createWallet(MoneroWalletConfig config) {
// start monero-wallet-rpc instance
MoneroWalletRpc walletRpc = startWalletRpcInstance();
// create wallet
try {
walletRpc.createWallet(config);
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
return walletRpc;
} catch (Exception e) {
e.printStackTrace();
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc);
throw e;
}
}
public MoneroWallet openWallet(MoneroWalletConfig config) {
// start monero-wallet-rpc instance
MoneroWalletRpc walletRpc = startWalletRpcInstance();
// open wallet
try {
walletRpc.openWallet(config);
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
return walletRpc;
} catch (Exception e) {
e.printStackTrace();
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc);
throw e;
}
}
private MoneroWalletRpc startWalletRpcInstance() {
// check if monero-wallet-rpc exists
if (!new File(MONERO_WALLET_RPC_PATH).exists()) throw new Error("monero-wallet-rpc executable doesn't exist at path " + MONERO_WALLET_RPC_PATH + "; copy monero-wallet-rpc to the project root or set WalletConfig.java MONERO_WALLET_RPC_PATH for your system");
// start monero-wallet-rpc instance and return connected client
return WalletConfig.MONERO_WALLET_RPC_MANAGER.startInstance(Arrays.asList(
MONERO_WALLET_RPC_PATH,
"--" + MONERO_NETWORK_TYPE.toString().toLowerCase(),
"--daemon-address", MONERO_DAEMON_URI,
"--daemon-login", MONERO_DAEMON_USERNAME + ":" + MONERO_DAEMON_PASSWORD,
"--rpc-login", MONERO_WALLET_RPC_USERNAME + ":" + MONERO_WALLET_RPC_PASSWORD,
"--wallet-dir", directory.toString()
));
}
public void closeWallet(MoneroWallet walletRpc) {
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc);
}
@Override
protected void startUp() throws Exception {
// Runs in a separate thread.
@ -268,6 +345,24 @@ public class WalletConfig extends AbstractIdleService {
try {
File chainFile = new File(directory, filePrefix + ".spvchain");
boolean chainFileExists = chainFile.exists();
// XMR wallet
String xmrPrefix = "_XMR";
vXmrWalletFile = new File(directory, filePrefix + xmrPrefix); // TODO: *.wallet?
if (MoneroUtils.walletExists(vXmrWalletFile.getPath())) {
vXmrWallet = openWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"));
} else {
vXmrWallet = createWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"));
}
System.out.println("Monero wallet path: " + vXmrWallet.getPath());
System.out.println("Monero wallet address: " + vXmrWallet.getPrimaryAddress());
System.out.println("Monero mnemonic: " + vXmrWallet.getMnemonic());
// vXmrWallet.rescanSpent();
// vXmrWallet.rescanBlockchain();
vXmrWallet.sync();
vXmrWallet.save();
System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance());
String btcPrefix = "_BTC";
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
@ -531,6 +626,11 @@ public class WalletConfig extends AbstractIdleService {
return vBtcWallet;
}
public MoneroWallet getXmrWallet() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vXmrWallet;
}
public Wallet bsqWallet() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vBsqWallet;

View File

@ -21,6 +21,7 @@ import bisq.core.btc.exceptions.InvalidHostException;
import bisq.core.btc.exceptions.RejectedTxException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.AddressEntryList;
import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.btc.nodes.BtcNetworkConfig;
import bisq.core.btc.nodes.BtcNodes;
import bisq.core.btc.nodes.BtcNodes.BtcNode;
@ -58,8 +59,8 @@ import org.bitcoinj.wallet.Wallet;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.Service;
@ -103,6 +104,10 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
// Setup wallets and use WalletConfig for BitcoinJ wiring.
// Other like WalletConfig we are here always on the user thread. That is one reason why we do not
// merge WalletsSetup with WalletConfig to one class.
@ -120,6 +125,7 @@ public class WalletsSetup {
private final RegTestHost regTestHost;
private final AddressEntryList addressEntryList;
private final XmrAddressEntryList xmrAddressEntryList;
private final Preferences preferences;
private final Socks5ProxyProvider socks5ProxyProvider;
private final Config config;
@ -148,6 +154,7 @@ public class WalletsSetup {
@Inject
public WalletsSetup(RegTestHost regTestHost,
AddressEntryList addressEntryList,
XmrAddressEntryList xmrAddressEntryList,
Preferences preferences,
Socks5ProxyProvider socks5ProxyProvider,
Config config,
@ -160,6 +167,7 @@ public class WalletsSetup {
@Named(Config.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) {
this.regTestHost = regTestHost;
this.addressEntryList = addressEntryList;
this.xmrAddressEntryList = xmrAddressEntryList;
this.preferences = preferences;
this.socks5ProxyProvider = socks5ProxyProvider;
this.config = config;
@ -253,6 +261,7 @@ public class WalletsSetup {
UserThread.execute(() -> {
chainHeight.set(chain.getBestChainHeight());
addressEntryList.onWalletReady(walletConfig.btcWallet());
xmrAddressEntryList.onWalletReady(walletConfig.getXmrWallet());
timeoutTimer.stop();
setupCompletedHandlers.forEach(Runnable::run);
});
@ -479,6 +488,10 @@ public class WalletsSetup {
return walletConfig.btcWallet();
}
public MoneroWallet getXmrWallet() {
return walletConfig.getXmrWallet();
}
@Nullable
public Wallet getBsqWallet() {
return walletConfig.bsqWallet();

View File

@ -28,6 +28,7 @@ import bisq.core.btc.setup.WalletConfig;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.ParsingUtils;
import bisq.common.config.Config;
import bisq.common.util.Tuple2;
@ -75,6 +76,13 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxWallet;
public class TradeWalletService {
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000);
@ -86,6 +94,8 @@ public class TradeWalletService {
@Nullable
private Wallet wallet;
@Nullable
private MoneroWallet xmrWallet;
@Nullable
private WalletConfig walletConfig;
@Nullable
private KeyParameter aesKey;
@ -103,6 +113,7 @@ public class TradeWalletService {
walletsSetup.addSetupCompletedHandler(() -> {
walletConfig = walletsSetup.getWalletConfig();
wallet = walletsSetup.getBtcWallet();
xmrWallet = walletsSetup.getXmrWallet();
});
}
@ -125,6 +136,21 @@ public class TradeWalletService {
// Trade fee
///////////////////////////////////////////////////////////////////////////////////////////
public MoneroTxWallet createXmrTradingFeeTx(
String reservedForTradeAddress,
Coin reservedFundsForOffer,
Coin makerFee,
Coin txFee,
String feeReceiver,
boolean broadcastTx) {
return xmrWallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.setDestinations(
new MoneroDestination(feeReceiver, ParsingUtils.satoshisToXmrAtomicUnits(makerFee.value)),
new MoneroDestination(reservedForTradeAddress, ParsingUtils.satoshisToXmrAtomicUnits(reservedFundsForOffer.value)))
.setRelay(broadcastTx));
}
/**
* Create a BTC trading fee transaction for the maker or taker of an offer. The first output of the tx is for the
* fee receiver. The second output is the reserve of the trade. There is an optional third output for change.
@ -1206,6 +1232,16 @@ public class TradeWalletService {
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
*
* @param txHash the transaction hash of the transaction we want to lookup
*/
public MoneroTxWallet getWalletTx(String txHash) {
checkNotNull(xmrWallet);
return xmrWallet.getTx(txHash);
}
/**
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
*

View File

@ -105,6 +105,11 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
/**
* Abstract base class for BTC and BSQ wallet. Provides all non-trade specific functionality.
*/
@ -228,7 +233,6 @@ public abstract class WalletService {
balanceListeners.remove(listener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Checks
///////////////////////////////////////////////////////////////////////////////////////////
@ -802,6 +806,22 @@ public abstract class WalletService {
}
}
public static MoneroTxWallet maybeAddNetworkTxToWallet(byte[] serializedTransaction, MoneroWallet wallet) throws VerificationException {
throw new RuntimeException("Not implemented"); // TODO (woodser): need to serialize/deserialize tx for xmr integration?
// Transaction tx = new Transaction(wallet.getParams(), serializedTransaction);
// Transaction walletTransaction = wallet.getTransaction(tx.getHash());
//
// if (walletTransaction == null) {
// // We need to recreate the transaction otherwise we get a null pointer...
// tx.getConfidence(Context.get()).setSource(source);
// //wallet.maybeCommitTx(tx);
// wallet.receivePending(tx, null, true);
// return tx;
// } else {
// return walletTransaction;
// }
}
public static Transaction maybeAddNetworkTxToWallet(byte[] serializedTransaction,
Wallet wallet) throws VerificationException {
return maybeAddTxToWallet(serializedTransaction, wallet, TransactionConfidence.Source.NETWORK);

View File

@ -0,0 +1,460 @@
package bisq.core.btc.wallet;
import bisq.core.btc.exceptions.AddressEntryException;
import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.util.ParsingUtils;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import javax.inject.Inject;
import com.google.common.util.concurrent.FutureCallback;
import javafx.application.Platform;
import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Getter;
import monero.common.MoneroUtils;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroAccount;
import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroSubaddress;
import monero.wallet.model.MoneroTransfer;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxQuery;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletConfig;
import monero.wallet.model.MoneroWalletListener;
import monero.wallet.model.MoneroWalletListenerI;
public class XmrWalletService {
private static final Logger log = LoggerFactory.getLogger(XmrWalletService.class);
private WalletsSetup walletsSetup;
private final XmrAddressEntryList addressEntryList;
protected final CopyOnWriteArraySet<XmrBalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>();
private Map<String, MoneroWallet> multisigWallets;
@Getter
private MoneroWallet wallet;
@Inject
XmrWalletService(WalletsSetup walletsSetup,
XmrAddressEntryList addressEntryList) {
this.walletsSetup = walletsSetup;
this.addressEntryList = addressEntryList;
this.multisigWallets = new HashMap<String, MoneroWallet>();
walletsSetup.addSetupCompletedHandler(() -> {
wallet = walletsSetup.getXmrWallet();
wallet.addListener(new MoneroWalletListener() {
@Override
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { }
@Override
public void onNewBlock(long height) { }
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx
@Override public void run() {
notifyBalanceListeners();
}
});
}
});
});
}
// TODO (woodser): move hard-coded values to config
public MoneroWallet getOrCreateMultisigWallet(String tradeId) {
String path = "xmr_multisig_trade_" + tradeId;
MoneroWallet multisigWallet = null;
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
else if (MoneroUtils.walletExists(new File(walletsSetup.getWalletConfig().directory(), path).getPath())) { // TODO: use monero-wallet-rpc to determine existence?
multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig()
.setPath(path)
.setPassword("abctesting123"));
} else {
multisigWallet = walletsSetup.getWalletConfig().createWallet(new MoneroWalletConfig()
.setPath(path)
.setPassword("abctesting123"));
}
multisigWallets.put(tradeId, multisigWallet);
multisigWallet.startSyncing(5000l);
return multisigWallet;
}
public XmrAddressEntry getArbitratorAddressEntry() {
XmrAddressEntry.Context context = XmrAddressEntry.Context.ARBITRATOR;
Optional<XmrAddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
.filter(e -> context == e.getContext())
.findAny();
return getOrCreateAddressEntry(context, addressEntry);
}
public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) {
var available = findAddressEntry(address, XmrAddressEntry.Context.AVAILABLE);
if (!available.isPresent())
return null;
return addressEntryList.swapAvailableToAddressEntryWithOfferId(available.get(), context, offerId);
}
public XmrAddressEntry getNewAddressEntry(String offerId, XmrAddressEntry.Context context) {
if (context == XmrAddressEntry.Context.TRADE_PAYOUT) {
XmrAddressEntry entry = new XmrAddressEntry(0, wallet.createSubaddress(0).getAddress(), context, offerId, null);
System.out.println("Adding address entry: " + entry.getAccountIndex() + ", " + entry.getAddressString());
addressEntryList.addAddressEntry(entry);
return entry;
} else {
MoneroAccount account = wallet.createAccount();
XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null);
addressEntryList.addAddressEntry(entry);
return entry;
}
}
public XmrAddressEntry getOrCreateAddressEntry(String offerId, XmrAddressEntry.Context context) {
Optional<XmrAddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
.findAny();
if (addressEntry.isPresent()) {
return addressEntry.get();
} else {
// We try to use available and not yet used entries // TODO (woodser): "available" entries is not applicable in xmr which uses account 0 for main wallet and subsequent accounts for reserved trades, refactor address association for xmr?
Optional<XmrAddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream()
.filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext())
.filter(e -> isAccountUnused(e.getAccountIndex()))
.findAny();
if (emptyAvailableAddressEntry.isPresent()) {
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
} else {
MoneroAccount account = wallet.createAccount();
XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null);
addressEntryList.addAddressEntry(entry);
return entry;
}
}
}
private XmrAddressEntry getOrCreateAddressEntry(XmrAddressEntry.Context context, Optional<XmrAddressEntry> addressEntry) {
if (addressEntry.isPresent()) {
return addressEntry.get();
} else {
if (context == XmrAddressEntry.Context.ARBITRATOR) {
MoneroSubaddress subaddress = wallet.createSubaddress(0);
XmrAddressEntry entry = new XmrAddressEntry(0, subaddress.getAddress(), context);
addressEntryList.addAddressEntry(entry);
return entry;
} else {
throw new RuntimeException("XmrWalletService.getOrCreateAddressEntry(context, addressEntry) not implemented for non-arbitrator context"); // TODO (woodser): this method used with non-arbitrator context?
}
}
}
public Optional<XmrAddressEntry> getAddressEntry(String offerId, XmrAddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream()
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
.findAny();
}
public void swapTradeEntryToAvailableEntry(String offerId, XmrAddressEntry.Context context) {
Optional<XmrAddressEntry> addressEntryOptional = getAddressEntryListAsImmutableList().stream()
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
.findAny();
addressEntryOptional.ifPresent(e -> {
log.info("swap addressEntry with address {} and offerId {} from context {} to available",
e.getAddressString(), e.getOfferId(), context);
addressEntryList.swapToAvailable(e);
saveAddressEntryList();
});
}
public void resetAddressEntriesForOpenOffer(String offerId) {
log.info("resetAddressEntriesForOpenOffer offerId={}", offerId);
swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.OFFER_FUNDING);
swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.RESERVED_FOR_TRADE);
}
public void resetAddressEntriesForPendingTrade(String offerId) {
swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.MULTI_SIG);
// We swap also TRADE_PAYOUT to be sure all is cleaned up. There might be cases where a user cannot send the funds
// to an external wallet directly in the last step of the trade, but the funds are in the Bisq wallet anyway and
// the dealing with the external wallet is pure UI thing. The user can move the funds to the wallet and then
// send out the funds to the external wallet. As this cleanup is a rare situation and most users do not use
// the feature to send out the funds we prefer that strategy (if we keep the address entry it might cause
// complications in some edge cases after a SPV resync).
swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.TRADE_PAYOUT);
}
private Optional<XmrAddressEntry> findAddressEntry(String address, XmrAddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream()
.filter(e -> address.equals(e.getAddressString()))
.filter(e -> context == e.getContext())
.findAny();
}
public List<XmrAddressEntry> getAvailableAddressEntries() {
return getAddressEntryListAsImmutableList().stream()
.filter(addressEntry -> XmrAddressEntry.Context.AVAILABLE == addressEntry.getContext())
.collect(Collectors.toList());
}
public List<XmrAddressEntry> getAddressEntriesForTrade() {
return getAddressEntryListAsImmutableList().stream()
.filter(addressEntry -> XmrAddressEntry.Context.MULTI_SIG == addressEntry.getContext() ||
XmrAddressEntry.Context.TRADE_PAYOUT == addressEntry.getContext())
.collect(Collectors.toList());
}
public List<XmrAddressEntry> getAddressEntries(XmrAddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream()
.filter(addressEntry -> context == addressEntry.getContext())
.collect(Collectors.toList());
}
public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
return getAvailableAddressEntries().stream()
.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive())
.collect(Collectors.toList());
}
public List<XmrAddressEntry> getAddressEntryListAsImmutableList() {
return addressEntryList.getAddressEntriesAsListImmutable();
}
public boolean isAccountUnused(int accountIndex) {
return accountIndex != 0 && getBalanceForAccount(accountIndex).value == 0;
//return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed() does not include unconfirmed funds
}
public Coin getBalanceForAccount(int accountIndex) {
// get wallet balance
BigInteger balance = wallet.getBalance(accountIndex);
// balance from xmr wallet does not include unconfirmed funds, so add them // TODO: support lower in stack?
for (MoneroTxWallet unconfirmedTx : wallet.getTxs(new MoneroTxQuery().setIsConfirmed(false))) {
for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) {
if (transfer.getAccountIndex() == accountIndex) {
balance = transfer.isIncoming() ? balance.add(transfer.getAmount()) : balance.subtract(transfer.getAmount());
}
}
}
System.out.println("Returning balance for account " + accountIndex + ": " + balance.longValueExact());
return Coin.valueOf(balance.longValueExact());
}
public Coin getAvailableConfirmedBalance() {
return wallet != null ? Coin.valueOf(wallet.getUnlockedBalance(0).longValueExact()) : Coin.ZERO;
}
public Coin getSavingWalletBalance() {
return wallet != null ? Coin.valueOf(wallet.getBalance(0).longValueExact()) : Coin.ZERO;
}
public Stream<XmrAddressEntry> getAddressEntriesForAvailableBalanceStream() {
Stream<XmrAddressEntry> availableAndPayout = Stream.concat(getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream(), getFundedAvailableAddressEntries().stream());
Stream<XmrAddressEntry> available = Stream.concat(availableAndPayout, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream());
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream());
return available.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive());
}
public void addBalanceListener(XmrBalanceListener listener) {
balanceListeners.add(listener);
}
public void removeBalanceListener(XmrBalanceListener listener) {
balanceListeners.remove(listener);
}
public void saveAddressEntryList() {
addressEntryList.requestPersistence();
}
public List<MoneroTxWallet> getTransactions(boolean includeDead) {
return wallet.getTxs(new MoneroTxQuery().setIsFailed(includeDead ? null : false));
}
public void shutDown() {
System.out.println("XmrWalletService.shutDown()");
// collect wallets to shutdown
List<MoneroWallet> openWallets = new ArrayList<MoneroWallet>();
if (wallet != null) openWallets.add(wallet);
for (String multisigWalletKey : multisigWallets.keySet()) {
openWallets.add(multisigWallets.get(multisigWalletKey));
}
// create shutdown threads
List<Thread> threads = new ArrayList<Thread>();
for (MoneroWallet openWallet : openWallets) {
threads.add(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("XmrWalletServie.shutDown() closing wallet within thread!!!");
System.out.println("Wallet balance: " + wallet.getBalance());
try { walletsSetup.getWalletConfig().closeWallet(openWallet); }
catch (Exception e) { e.printStackTrace(); }
}
}));
}
// run shutdown threads in parallel
for (Thread thread : threads) thread.start();
// wait for all threads
System.out.println("Joining threads");
for (Thread thread : threads) {
try { thread.join(); }
catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("Done joining threads");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Withdrawal Send
///////////////////////////////////////////////////////////////////////////////////////////
public String sendFunds(int fromAccountIndex,
String toAddress,
Coin receiverAmount,
@SuppressWarnings("SameParameterValue") XmrAddressEntry.Context context,
FutureCallback<MoneroTxWallet> callback) throws AddressFormatException,
AddressEntryException, InsufficientMoneyException {
try {
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(fromAccountIndex)
.setAddress(toAddress)
.setAmount(ParsingUtils.satoshisToXmrAtomicUnits(receiverAmount.value))
.setRelay(true));
callback.onSuccess(tx);
printTxs("sendFunds", tx);
return tx.getHash();
} catch (Exception e) {
callback.onFailure(e);
throw e;
}
}
// public String sendFunds(String fromAddress, String toAddress, Coin receiverAmount, Coin fee, @Nullable KeyParameter aesKey, @SuppressWarnings("SameParameterValue") AddressEntry.Context context,
// FutureCallback<Transaction> callback) throws AddressFormatException, AddressEntryException, InsufficientMoneyException {
// SendRequest sendRequest = getSendRequest(fromAddress, toAddress, receiverAmount, fee, aesKey, context);
// Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
// Futures.addCallback(sendResult.broadcastComplete, callback, MoreExecutors.directExecutor());
//
// printTx("sendFunds", sendResult.tx);
// return sendResult.tx.getTxId().toString();
// }
///////////////////////////////////////////////////////////////////////////////////////////
// Util
///////////////////////////////////////////////////////////////////////////////////////////
public static void printTxs(String tracePrefix, MoneroTxWallet... txs) {
StringBuilder sb = new StringBuilder();
for (MoneroTxWallet tx : txs) sb.append('\n' + tx.toString());
log.info("\n" + tracePrefix + ":" + sb.toString());
}
private void notifyBalanceListeners() {
for (XmrBalanceListener balanceListener : balanceListeners) {
Coin balance;
if (balanceListener.getAccountIndex() != null && balanceListener.getAccountIndex() != 0) {
balance = getBalanceForAccount(balanceListener.getAccountIndex());
} else {
balance = getAvailableConfirmedBalance();
}
balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value));
}
}
/**
* Wraps a MoneroWalletListener to notify the Haveno application.
*/
public class HavenoWalletListener extends MoneroWalletListener {
private MoneroWalletListener listener;
public HavenoWalletListener(MoneroWalletListener listener) {
this.listener = listener;
}
@Override
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx
@Override public void run() {
listener.onSyncProgress(height, startHeight, endHeight, percentDone, message);
}
});
}
@Override
public void onNewBlock(long height) {
Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx
@Override public void run() {
listener.onNewBlock(height);
}
});
}
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx
@Override public void run() {
listener.onBalancesChanged(newBalance, newUnlockedBalance);
}
});
}
@Override
public void onOutputReceived(MoneroOutputWallet output) {
Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx
@Override public void run() {
listener.onOutputReceived(output);
}
});
}
@Override
public void onOutputSpent(MoneroOutputWallet output) {
Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx
@Override public void run() {
listener.onOutputSpent(output);
}
});
}
}
}

View File

@ -57,12 +57,12 @@ import static java.lang.String.format;
@Slf4j
public class CurrencyUtil {
public static void setup() {
setBaseCurrencyCode(Config.baseCurrencyNetwork().getCurrencyCode());
setBaseCurrencyCode("XMR");
}
private static final AssetRegistry assetRegistry = new AssetRegistry();
private static String baseCurrencyCode = "BTC";
private static String baseCurrencyCode = "XMR";
// Calls to isFiatCurrency and isCryptoCurrency are very frequent so we use a cache of the results.
// The main improvement was already achieved with using memoize for the source maps, but

View File

@ -46,8 +46,8 @@ import org.jetbrains.annotations.NotNull;
public class Res {
public static void setup() {
BaseCurrencyNetwork baseCurrencyNetwork = Config.baseCurrencyNetwork();
setBaseCurrencyCode(baseCurrencyNetwork.getCurrencyCode());
setBaseCurrencyName(baseCurrencyNetwork.getCurrencyName());
setBaseCurrencyCode("XMR");
setBaseCurrencyName("Monero");
}
@SuppressWarnings("CanBeFinal")
@ -78,20 +78,20 @@ public class Res {
private static String baseCurrencyNameLowerCase;
public static void setBaseCurrencyCode(String baseCurrencyCode) {
Res.baseCurrencyCode = baseCurrencyCode;
Res.baseCurrencyCode = "XMR";
}
public static void setBaseCurrencyName(String baseCurrencyName) {
Res.baseCurrencyName = baseCurrencyName;
Res.baseCurrencyName = "Monero";
baseCurrencyNameLowerCase = baseCurrencyName.toLowerCase();
}
public static String getBaseCurrencyCode() {
return baseCurrencyCode;
return "XMR";
}
public static String getBaseCurrencyName() {
return baseCurrencyName;
return "Monero";
}
// Capitalize first character
@ -110,9 +110,9 @@ public class Res {
public static String get(String key) {
try {
return resourceBundle.getString(key)
.replace("BTC", baseCurrencyCode)
.replace("Bitcoin", baseCurrencyName)
.replace("bitcoin", baseCurrencyNameLowerCase);
.replace("XMR", baseCurrencyCode)
.replace("Monero", baseCurrencyName)
.replace("monero", baseCurrencyNameLowerCase);
} catch (MissingResourceException e) {
log.warn("Missing resource for key: {}", key);
e.printStackTrace();

View File

@ -21,6 +21,7 @@ import bisq.core.api.CoreContext;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.filter.FilterManager;
@ -108,6 +109,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final User user;
private final P2PService p2PService;
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final TradeWalletService tradeWalletService;
private final BsqWalletService bsqWalletService;
private final OfferBookService offerBookService;
@ -141,6 +143,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
User user,
P2PService p2PService,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
TradeWalletService tradeWalletService,
BsqWalletService bsqWalletService,
OfferBookService offerBookService,
@ -161,6 +164,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
this.user = user;
this.p2PService = p2PService;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.tradeWalletService = tradeWalletService;
this.bsqWalletService = bsqWalletService;
this.offerBookService = offerBookService;
@ -381,6 +385,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
reservedFundsForOffer,
useSavingsWallet,
btcWalletService,
xmrWalletService,
tradeWalletService,
bsqWalletService,
offerBookService,
@ -642,11 +647,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
Offer offer = openOffer.getOffer();
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress();
openOffer.setMediatorNodeAddress(mediatorNodeAddress);
refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress();
openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress);
arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress();
openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
try {
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference

View File

@ -44,23 +44,14 @@ import static com.google.common.base.Preconditions.checkArgument;
public class DisputeAgentSelection {
public static final int LOOK_BACK_RANGE = 100;
public static <T extends DisputeAgent> T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager,
public static <T extends DisputeAgent> T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager,
disputeAgentManager,
true);
}
public static <T extends DisputeAgent> T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager,
disputeAgentManager,
false);
disputeAgentManager);
}
private static <T extends DisputeAgent> T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager,
boolean isMediator) {
DisputeAgentManager<T> disputeAgentManager) {
// We take last 100 entries from trade statistics
List<TradeStatistics3> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
list.sort(Comparator.comparing(TradeStatistics3::getDateAsLong));
@ -72,7 +63,7 @@ public class DisputeAgentSelection {
// We stored only first 4 chars of disputeAgents onion address
List<String> lastAddressesUsedInTrades = list.stream()
.map(tradeStatistics3 -> isMediator ? tradeStatistics3.getMediator() : tradeStatistics3.getRefundAgent())
.map(tradeStatistics3 -> tradeStatistics3.getArbitrator())
.filter(Objects::nonNull)
.collect(Collectors.toList());

View File

@ -62,7 +62,7 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
NodeAddress mediator = offerAvailabilityResponse.getMediator();
if (mediator == null) {
// We do not get a mediator from old clients so we need to handle the null case.
mediator = DisputeAgentSelection.getLeastUsedMediator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
mediator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
}
model.setSelectedMediator(mediator);

View File

@ -20,6 +20,7 @@ package bisq.core.offer.placeoffer;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.offer.Offer;
@ -37,6 +38,10 @@ import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.model.MoneroTxWallet;
@Slf4j
@Getter
public class PlaceOfferModel implements Model {
@ -45,6 +50,7 @@ public class PlaceOfferModel implements Model {
private final Coin reservedFundsForOffer;
private final boolean useSavingsWallet;
private final BtcWalletService walletService;
private final XmrWalletService xmrWalletService;
private final TradeWalletService tradeWalletService;
private final BsqWalletService bsqWalletService;
private final OfferBookService offerBookService;
@ -60,11 +66,14 @@ public class PlaceOfferModel implements Model {
private boolean offerAddedToOfferBook;
@Setter
private Transaction transaction;
@Setter
private MoneroTxWallet xmrTransaction;
public PlaceOfferModel(Offer offer,
Coin reservedFundsForOffer,
boolean useSavingsWallet,
BtcWalletService walletService,
XmrWalletService xmrWalletService,
TradeWalletService tradeWalletService,
BsqWalletService bsqWalletService,
OfferBookService offerBookService,
@ -77,6 +86,7 @@ public class PlaceOfferModel implements Model {
this.reservedFundsForOffer = reservedFundsForOffer;
this.useSavingsWallet = useSavingsWallet;
this.walletService = walletService;
this.xmrWalletService = xmrWalletService;
this.tradeWalletService = tradeWalletService;
this.bsqWalletService = bsqWalletService;
this.offerBookService = offerBookService;

View File

@ -19,9 +19,9 @@ package bisq.core.offer.placeoffer;
import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
import bisq.core.offer.placeoffer.tasks.CheckNumberOfUnconfirmedTransactions;
import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx;
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.protocol.tasks.maker.MakerCreateFeeTx;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.taskrunner.TaskRunner;
@ -79,7 +79,7 @@ public class PlaceOfferProtocol {
taskRunner.addTasks(
ValidateOffer.class,
CheckNumberOfUnconfirmedTransactions.class,
CreateMakerFeeTx.class,
MakerCreateFeeTx.class,
AddToOfferBook.class
);

View File

@ -17,19 +17,10 @@
package bisq.core.payment;
import bisq.core.locale.FiatCurrency;
import bisq.core.payment.payload.JapanBankAccountPayload;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.payment.payload.JapanBankAccountPayload;
import org.jetbrains.annotations.NotNull;
import lombok.Getter;
import lombok.Setter;
import bisq.core.locale.Country;
import bisq.core.locale.FiatCurrency;
import bisq.core.payment.payload.JapanBankAccountPayload;
public final class JapanBankAccount extends PaymentAccount
{

View File

@ -50,7 +50,8 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
///////////////////////////////////////////////////////////////////////////////////////////
// time in blocks (average 10 min for one block confirmation
private static final long DAY = TimeUnit.HOURS.toMillis(24);
//private static final long DAY = TimeUnit.HOURS.toMillis(24);
private static final long DAY = TimeUnit.MINUTES.toMillis(1); // TODO (woodser): changed to 1 minute for development
// Default trade limits.
// We initialize very early before reading persisted data. We will apply later the limit from

View File

@ -18,20 +18,21 @@
package bisq.core.presentation;
import bisq.core.btc.Balances;
import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.CoinFormatter;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.math.BigInteger;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BalancePresentation {
private static final BigInteger AU_PER_XMR = new BigInteger("1000000000000");
@Getter
private final StringProperty availableBalance = new SimpleStringProperty();
@Getter
@ -40,20 +41,25 @@ public class BalancePresentation {
private final StringProperty lockedBalance = new SimpleStringProperty();
@Inject
public BalancePresentation(Balances balances, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter) {
public BalancePresentation(Balances balances) {
balances.getAvailableBalance().addListener((observable, oldValue, newValue) -> {
String value = formatter.formatCoinWithCode(newValue);
// If we get full precision the BTC postfix breaks layout so we omit it
if (value.length() > 11)
value = formatter.formatCoin(newValue);
availableBalance.set(value);
availableBalance.set(longToXmr(newValue.value));
});
balances.getReservedBalance().addListener((observable, oldValue, newValue) -> {
reservedBalance.set(formatter.formatCoinWithCode(newValue));
reservedBalance.set(longToXmr(newValue.value));
});
balances.getLockedBalance().addListener((observable, oldValue, newValue) -> {
lockedBalance.set(formatter.formatCoinWithCode(newValue));
lockedBalance.set(longToXmr(newValue.value));
});
}
// TODO: truncate full precision with ellipses to not break layout?
// TODO (woodser): formatting utils in monero-java
private static String longToXmr(long amt) {
BigInteger auAmt = BigInteger.valueOf(amt);
BigInteger[] quotientAndRemainder = auAmt.divideAndRemainder(AU_PER_XMR);
double decimalRemainder = quotientAndRemainder[1].doubleValue() / AU_PER_XMR.doubleValue();
return quotientAndRemainder[0].doubleValue() + decimalRemainder + " XMR";
}
}

View File

@ -43,6 +43,8 @@ import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.support.dispute.messages.ArbitratorPayoutTxRequest;
import bisq.core.support.dispute.messages.ArbitratorPayoutTxResponse;
import bisq.core.support.dispute.messages.DisputeResultMessage;
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
@ -53,14 +55,20 @@ import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.RefreshTradeStateRequest;
import bisq.core.trade.messages.TraderSignedWitnessMessage;
import bisq.core.trade.messages.UpdateMultisigRequest;
import bisq.core.trade.messages.UpdateMultisigResponse;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.BundleOfEnvelopes;
@ -147,6 +155,18 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
// trade protocol messages
case REFRESH_TRADE_STATE_REQUEST:
return RefreshTradeStateRequest.fromProto(proto.getRefreshTradeStateRequest(), messageVersion);
case INIT_TRADE_REQUEST:
return InitTradeRequest.fromProto(proto.getInitTradeRequest(), this, messageVersion);
case INIT_MULTISIG_MESSAGE:
return InitMultisigMessage.fromProto(proto.getInitMultisigMessage(), this, messageVersion);
case UPDATE_MULTISIG_REQUEST:
return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion);
case UPDATE_MULTISIG_RESPONSE:
return UpdateMultisigResponse.fromProto(proto.getUpdateMultisigResponse(), this, messageVersion);
case MAKER_READY_TO_FUND_MULTISIG_REQUEST:
return MakerReadyToFundMultisigRequest.fromProto(proto.getMakerReadyToFundMultisigRequest(), this, messageVersion);
case MAKER_READY_TO_FUND_MULTISIG_RESPONSE:
return MakerReadyToFundMultisigResponse.fromProto(proto.getMakerReadyToFundMultisigResponse(), this, messageVersion);
case INPUTS_FOR_DEPOSIT_TX_REQUEST:
return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion);
case INPUTS_FOR_DEPOSIT_TX_RESPONSE:
@ -185,6 +205,10 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
return DisputeResultMessage.fromProto(proto.getDisputeResultMessage(), messageVersion);
case PEER_PUBLISHED_DISPUTE_PAYOUT_TX_MESSAGE:
return PeerPublishedDisputePayoutTxMessage.fromProto(proto.getPeerPublishedDisputePayoutTxMessage(), messageVersion);
case ARBITRATOR_PAYOUT_TX_REQUEST:
return ArbitratorPayoutTxRequest.fromProto(proto.getArbitratorPayoutTxRequest(), this, messageVersion);
case ARBITRATOR_PAYOUT_TX_RESPONSE:
return ArbitratorPayoutTxResponse.fromProto(proto.getArbitratorPayoutTxResponse(), this, messageVersion);
case PRIVATE_NOTIFICATION_MESSAGE:
return PrivateNotificationMessage.fromProto(proto.getPrivateNotificationMessage(), messageVersion);

View File

@ -20,7 +20,9 @@ package bisq.core.proto.persistable;
import bisq.core.account.sign.SignedWitnessStore;
import bisq.core.account.witness.AccountAgeWitnessStore;
import bisq.core.btc.model.AddressEntryList;
import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.governance.blindvote.MyBlindVoteList;
import bisq.core.dao.governance.blindvote.storage.BlindVoteStore;
import bisq.core.dao.governance.bond.reputation.MyReputationList;
@ -67,12 +69,15 @@ import lombok.extern.slf4j.Slf4j;
@Singleton
public class CorePersistenceProtoResolver extends CoreProtoResolver implements PersistenceProtoResolver {
private final Provider<BtcWalletService> btcWalletService;
private final Provider<XmrWalletService> xmrWalletService;
private final NetworkProtoResolver networkProtoResolver;
@Inject
public CorePersistenceProtoResolver(Provider<BtcWalletService> btcWalletService,
Provider<XmrWalletService> xmrWalletService,
NetworkProtoResolver networkProtoResolver) {
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.networkProtoResolver = networkProtoResolver;
}
@ -86,8 +91,10 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
return PeerList.fromProto(proto.getPeerList());
case ADDRESS_ENTRY_LIST:
return AddressEntryList.fromProto(proto.getAddressEntryList());
case XMR_ADDRESS_ENTRY_LIST:
return XmrAddressEntryList.fromProto(proto.getXmrAddressEntryList());
case TRADABLE_LIST:
return TradableList.fromProto(proto.getTradableList(), this, btcWalletService.get());
return TradableList.fromProto(proto.getTradableList(), this, xmrWalletService.get());
case ARBITRATION_DISPUTE_LIST:
return ArbitrationDisputeList.fromProto(proto.getArbitrationDisputeList(), this);
case MEDIATION_DISPUTE_LIST:

View File

@ -106,8 +106,9 @@ public class MempoolService {
}
public void validateOfferTakerTx(Trade trade, Consumer<TxValidator> resultHandler) {
validateOfferTakerTx(new TxValidator(daoStateService, trade.getTakerFeeTxId(), trade.getTradeAmount(),
trade.isCurrencyForTakerFeeBtc()), resultHandler);
throw new RuntimeException("MempoolService.validateOfferTakerTx needs updated for XMR");
// validateOfferTakerTx(new TxValidator(daoStateService, trade.getTakerFeeTxId(), trade.getTradeAmount(),
// trade.isCurrencyForTakerFeeBtc()), resultHandler);
}
public void validateOfferTakerTx(TxValidator txValidator, Consumer<TxValidator> resultHandler) {

View File

@ -18,6 +18,7 @@
package bisq.core.setup;
import bisq.core.btc.model.AddressEntryList;
import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.dao.governance.ballot.BallotListService;
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
@ -60,6 +61,7 @@ public class CorePersistedDataHost {
persistedDataHosts.add(injector.getInstance(Preferences.class));
persistedDataHosts.add(injector.getInstance(User.class));
persistedDataHosts.add(injector.getInstance(AddressEntryList.class));
persistedDataHosts.add(injector.getInstance(XmrAddressEntryList.class));
persistedDataHosts.add(injector.getInstance(OpenOfferManager.class));
persistedDataHosts.add(injector.getInstance(TradeManager.class));
persistedDataHosts.add(injector.getInstance(ClosedTradableManager.class));

View File

@ -128,6 +128,9 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
@Nullable
private String delayedPayoutTxId;
// Added for XMR integration
private boolean isOpener;
// Added at v1.4.0
@Setter
@Nullable
@ -160,6 +163,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
public Dispute(long openingDate,
String tradeId,
int traderId,
boolean isOpener,
boolean disputeOpenerIsBuyer,
boolean disputeOpenerIsMaker,
PubKeyRing traderPubKeyRing,
@ -180,6 +184,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
this.openingDate = openingDate;
this.tradeId = tradeId;
this.traderId = traderId;
this.isOpener = isOpener;
this.disputeOpenerIsBuyer = disputeOpenerIsBuyer;
this.disputeOpenerIsMaker = disputeOpenerIsMaker;
this.traderPubKeyRing = traderPubKeyRing;
@ -215,6 +220,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
protobuf.Dispute.Builder builder = protobuf.Dispute.newBuilder()
.setTradeId(tradeId)
.setTraderId(traderId)
.setIsOpener(isOpener)
.setDisputeOpenerIsBuyer(disputeOpenerIsBuyer)
.setDisputeOpenerIsMaker(disputeOpenerIsMaker)
.setTraderPubKeyRing(traderPubKeyRing.toProtoMessage())
@ -253,6 +259,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
Dispute dispute = new Dispute(proto.getOpeningDate(),
proto.getTradeId(),
proto.getTraderId(),
proto.getIsOpener(),
proto.getDisputeOpenerIsBuyer(),
proto.getDisputeOpenerIsMaker(),
PubKeyRing.fromProto(proto.getTraderPubKeyRing()),
@ -327,6 +334,9 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
}
}
public boolean isMediationDispute() {
return !chatMessages.isEmpty() && chatMessages.get(0).getSupportType() == SupportType.MEDIATION;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
@ -447,6 +457,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
",\n uid='" + uid + '\'' +
",\n state=" + disputeState +
",\n traderId=" + traderId +
",\n isOpener=" + isOpener +
",\n disputeOpenerIsBuyer=" + disputeOpenerIsBuyer +
",\n disputeOpenerIsMaker=" + disputeOpenerIsMaker +
",\n traderPubKeyRing=" + traderPubKeyRing +

View File

@ -18,9 +18,9 @@
package bisq.core.support.dispute;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
@ -66,6 +66,7 @@ import javafx.collections.ObservableList;
import java.security.KeyPair;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@ -81,10 +82,14 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
@Slf4j
public abstract class DisputeManager<T extends DisputeList<Dispute>> extends SupportManager {
protected final TradeWalletService tradeWalletService;
protected final BtcWalletService btcWalletService;
protected final XmrWalletService xmrWalletService;
protected final TradeManager tradeManager;
protected final ClosedTradableManager closedTradableManager;
protected final OpenOfferManager openOfferManager;
@ -107,7 +112,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
public DisputeManager(P2PService p2PService,
TradeWalletService tradeWalletService,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
WalletsSetup walletsSetup,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
@ -120,7 +125,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
super(p2PService, walletsSetup);
this.tradeWalletService = tradeWalletService;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.openOfferManager = openOfferManager;
@ -184,7 +189,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
dispute.addAndPersistChatMessage(message);
requestPersistence();
} else {
log.warn("We got a chatMessage that we have already stored. UId = {} TradeId = {}",
log.warn("We got a chatMessage what we have already stored. UId = {} TradeId = {}",
message.getUid(), message.getTradeId());
}
});
@ -198,7 +203,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// We get that message at both peers. The dispute object is in context of the trader
public abstract void onDisputeResultMessage(DisputeResultMessage disputeResultMessage);
@Nullable
public abstract NodeAddress getAgentNodeAddress(Dispute dispute);
protected abstract Trade.DisputeState getDisputeStateStartedByPeer();
@ -276,11 +280,12 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
}
});
TradeDataValidation.testIfAnyDisputeTriedReplay(disputes,
disputeReplayException -> {
log.error(disputeReplayException.toString());
validationExceptions.add(disputeReplayException);
});
// TODO (woodser): disabled for xmr, needed?
// TradeDataValidation.testIfAnyDisputeTriedReplay(disputes,
// disputeReplayException -> {
// log.error(disputeReplayException.toString());
// validationExceptions.add(disputeReplayException);
// });
}
public boolean isTrader(Dispute dispute) {
@ -302,7 +307,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// Message handler
///////////////////////////////////////////////////////////////////////////////////////////
// dispute agent receives that from trader who opens dispute
// arbitrator receives that from trader who opens dispute
protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessage) {
T disputeList = getDisputeList();
if (disputeList == null) {
@ -322,6 +327,13 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
if (isAgent(dispute)) {
// update arbitrator's multisig wallet
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
multisigWallet.importMultisigHex(Arrays.asList(openNewDisputeMessage.getUpdatedMultisigHex()));
System.out.println("Arbitrator multisig wallet updated on new dispute message, current txs:");
System.out.println(multisigWallet.getTxs());
if (!disputeList.contains(dispute)) {
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent()) {
@ -333,8 +345,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
dispute.getTradeId());
}
} else {
errorMessage = "We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId();
log.warn(errorMessage);
errorMessage = "We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId();
log.warn(errorMessage);
}
} else {
errorMessage = "Trader received openNewDisputeMessage. That must never happen.";
@ -342,22 +354,21 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
}
// We use the ChatMessage not the openNewDisputeMessage for the ACK
ObservableList<ChatMessage> messages = dispute.getChatMessages();
ObservableList<ChatMessage> messages = openNewDisputeMessage.getDispute().getChatMessages();
if (!messages.isEmpty()) {
ChatMessage chatMessage = messages.get(0);
ChatMessage msg = messages.get(0);
PubKeyRing sendersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
sendAckMessage(chatMessage, sendersPubKeyRing, errorMessage == null, errorMessage);
sendAckMessage(msg, sendersPubKeyRing, errorMessage == null, errorMessage);
}
addMediationResultMessage(dispute);
try {
TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList());
//TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed?
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config);
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config);
} catch (TradeDataValidation.AddressException |
TradeDataValidation.DisputeReplayException |
TradeDataValidation.NodeAddressException e) {
log.error(e.toString());
validationExceptions.add(e);
@ -380,20 +391,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
if (!optionalTrade.isPresent()) {
return;
}
Trade trade = optionalTrade.get();
try {
TradeDataValidation.validateDelayedPayoutTx(trade,
trade.getDelayedPayoutTx(),
dispute,
daoFacade,
btcWalletService);
} catch (TradeDataValidation.ValidationException e) {
// The peer sent us an invalid donation address. We do not return here as we don't want to break
// mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get
// a popup displayed to react.
log.warn("Donation address is invalid. {}", e.toString());
}
if (!isAgent(dispute)) {
if (!disputeList.contains(dispute)) {
@ -435,6 +433,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
public void sendOpenNewDisputeMessage(Dispute dispute,
boolean reOpen,
String updatedMultisigHex,
ResultHandler resultHandler,
FaultHandler faultHandler) {
T disputeList = getDisputeList();
@ -453,18 +452,16 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent() || reOpen) {
String disputeInfo = getDisputeInfo(dispute);
String disputeMessage = getDisputeIntroForDisputeCreator(disputeInfo);
String sysMsg = dispute.isSupportTicket() ?
Res.get("support.youOpenedTicket", disputeInfo, Version.VERSION)
: disputeMessage;
: Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
String message = Res.get("support.systemMsg", sysMsg);
ChatMessage chatMessage = new ChatMessage(
getSupportType(),
dispute.getTradeId(),
pubKeyRing.hashCode(),
false,
message,
Res.get("support.systemMsg", sysMsg),
p2PService.getAddress());
chatMessage.setSystemMessage(true);
dispute.addAndPersistChatMessage(chatMessage);
@ -473,22 +470,16 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
}
NodeAddress agentNodeAddress = getAgentNodeAddress(dispute);
if (agentNodeAddress == null) {
return;
}
OpenNewDisputeMessage openNewDisputeMessage = new OpenNewDisputeMessage(dispute,
p2PService.getAddress(),
UUID.randomUUID().toString(),
getSupportType());
log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, chatMessage.uid={}",
openNewDisputeMessage.getClass().getSimpleName(),
agentNodeAddress,
openNewDisputeMessage.getTradeId(),
openNewDisputeMessage.getUid(),
getSupportType(),
updatedMultisigHex);
log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, " +
"chatMessage.uid={}",
openNewDisputeMessage.getClass().getSimpleName(), agentNodeAddress,
openNewDisputeMessage.getTradeId(), openNewDisputeMessage.getUid(),
chatMessage.getUid());
mailboxMessageService.sendEncryptedMailboxMessage(agentNodeAddress,
dispute.getAgentPubKeyRing(),
openNewDisputeMessage,
@ -575,6 +566,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
Dispute dispute = new Dispute(new Date().getTime(),
disputeFromOpener.getTradeId(),
pubKeyRing.hashCode(),
false,
!disputeFromOpener.isDisputeOpenerIsBuyer(),
!disputeFromOpener.isDisputeOpenerIsMaker(),
pubKeyRing,
@ -636,7 +628,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
chatMessage.getUid());
mailboxMessageService.sendEncryptedMailboxMessage(peersNodeAddress,
peersPubKeyRing,
peerOpenedDisputeMessage,
@ -687,7 +678,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
requestPersistence();
}
// dispute agent send result to trader
// arbitrator send result to trader
public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String summaryText) {
T disputeList = getDisputeList();
if (disputeList == null) {
@ -769,7 +760,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
requestPersistence();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
@ -811,7 +801,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
return findDispute(message.getTradeId(), message.getTraderId());
}
private Optional<Dispute> findDispute(String tradeId, int traderId) {
protected Optional<Dispute> findDispute(String tradeId, int traderId) {
T disputeList = getDisputeList();
if (disputeList == null) {
log.warn("disputes is null");

View File

@ -80,9 +80,6 @@ public final class DisputeResult implements NetworkPayload {
@Setter
@Nullable
private ChatMessage chatMessage;
@Setter
@Nullable
private byte[] arbitratorSignature;
private long buyerPayoutAmount;
private long sellerPayoutAmount;
@Setter
@ -92,6 +89,14 @@ public final class DisputeResult implements NetworkPayload {
@Setter
private boolean isLoserPublisher;
// added for XMR integration
@Nullable
@Setter
String arbitratorSignedPayoutTxHex;
@Nullable
@Setter
String arbitratorUpdatedMultisigHex;
public DisputeResult(String tradeId, int traderId) {
this.tradeId = tradeId;
this.traderId = traderId;
@ -106,7 +111,8 @@ public final class DisputeResult implements NetworkPayload {
boolean screenCast,
String summaryNotes,
@Nullable ChatMessage chatMessage,
@Nullable byte[] arbitratorSignature,
@Nullable String arbitratorPayoutTxSigned,
@Nullable String arbitratorUpdatedMultisigHex,
long buyerPayoutAmount,
long sellerPayoutAmount,
@Nullable byte[] arbitratorPubKey,
@ -121,7 +127,8 @@ public final class DisputeResult implements NetworkPayload {
this.screenCastProperty.set(screenCast);
this.summaryNotesProperty.set(summaryNotes);
this.chatMessage = chatMessage;
this.arbitratorSignature = arbitratorSignature;
this.arbitratorSignedPayoutTxHex = arbitratorPayoutTxSigned;
this.arbitratorUpdatedMultisigHex = arbitratorUpdatedMultisigHex;
this.buyerPayoutAmount = buyerPayoutAmount;
this.sellerPayoutAmount = sellerPayoutAmount;
this.arbitratorPubKey = arbitratorPubKey;
@ -144,7 +151,8 @@ public final class DisputeResult implements NetworkPayload {
proto.getScreenCast(),
proto.getSummaryNotes(),
proto.getChatMessage() == null ? null : ChatMessage.fromPayloadProto(proto.getChatMessage()),
proto.getArbitratorSignature().toByteArray(),
ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignedPayoutTxHex()),
ProtoUtil.stringOrNullFromProto(proto.getArbitratorUpdatedMultisigHex()),
proto.getBuyerPayoutAmount(),
proto.getSellerPayoutAmount(),
proto.getArbitratorPubKey().toByteArray(),
@ -167,7 +175,8 @@ public final class DisputeResult implements NetworkPayload {
.setCloseDate(closeDate)
.setIsLoserPublisher(isLoserPublisher);
Optional.ofNullable(arbitratorSignature).ifPresent(arbitratorSignature -> builder.setArbitratorSignature(ByteString.copyFrom(arbitratorSignature)));
Optional.ofNullable(arbitratorSignedPayoutTxHex).ifPresent(arbitratorPayoutTxSigned -> builder.setArbitratorSignedPayoutTxHex(arbitratorPayoutTxSigned));
Optional.ofNullable(arbitratorUpdatedMultisigHex).ifPresent(arbitratorUpdatedMultisigHex -> builder.setArbitratorUpdatedMultisigHex(arbitratorUpdatedMultisigHex));
Optional.ofNullable(arbitratorPubKey).ifPresent(arbitratorPubKey -> builder.setArbitratorPubKey(ByteString.copyFrom(arbitratorPubKey)));
Optional.ofNullable(winner).ifPresent(result -> builder.setWinner(protobuf.DisputeResult.Winner.valueOf(winner.name())));
Optional.ofNullable(chatMessage).ifPresent(chatMessage ->
@ -248,7 +257,8 @@ public final class DisputeResult implements NetworkPayload {
",\n screenCastProperty=" + screenCastProperty +
",\n summaryNotesProperty=" + summaryNotesProperty +
",\n chatMessage=" + chatMessage +
",\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) +
",\n arbitratorPayoutTxSigned=" + arbitratorSignedPayoutTxHex +
",\n arbitratorUpdatedMultisigHex=" + arbitratorUpdatedMultisigHex +
",\n buyerPayoutAmount=" + buyerPayoutAmount +
",\n sellerPayoutAmount=" + sellerPayoutAmount +
",\n arbitratorPubKey=" + Utilities.bytesAsHexString(arbitratorPubKey) +

View File

@ -17,14 +17,9 @@
package bisq.core.support.dispute.arbitration;
import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
@ -34,7 +29,10 @@ import bisq.core.support.SupportType;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeManager;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.DisputeResult.Winner;
import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
import bisq.core.support.dispute.messages.ArbitratorPayoutTxRequest;
import bisq.core.support.dispute.messages.ArbitratorPayoutTxResponse;
import bisq.core.support.dispute.messages.DisputeResultMessage;
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
@ -45,10 +43,12 @@ import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.util.ParsingUtils;
import bisq.network.p2p.AckMessageSourceType;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.SendDirectMessageListener;
import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.Timer;
@ -58,24 +58,31 @@ import bisq.common.config.Config;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.SignatureDecodeException;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.crypto.DeterministicKey;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.common.base.Preconditions;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.common.MoneroError;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroMultisigSignResult;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxSet;
import monero.wallet.model.MoneroTxWallet;
@Slf4j
@Singleton
public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeList> {
@ -87,7 +94,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
@Inject
public ArbitrationManager(P2PService p2PService,
TradeWalletService tradeWalletService,
BtcWalletService walletService,
XmrWalletService walletService,
WalletsSetup walletsSetup,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
@ -127,16 +134,19 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
onDisputeResultMessage((DisputeResultMessage) message);
} else if (message instanceof PeerPublishedDisputePayoutTxMessage) {
onDisputedPayoutTxMessage((PeerPublishedDisputePayoutTxMessage) message);
} else if (message instanceof ArbitratorPayoutTxRequest) {
onArbitratorPayoutTxRequest((ArbitratorPayoutTxRequest) message);
} else if (message instanceof ArbitratorPayoutTxResponse) {
onArbitratorPayoutTxResponse((ArbitratorPayoutTxResponse) message);
} else {
log.warn("Unsupported message at dispatchMessage. message={}", message);
}
}
}
@Nullable
@Override
public NodeAddress getAgentNodeAddress(Dispute dispute) {
return null;
return dispute.getContract().getArbitratorNodeAddress();
}
@Override
@ -186,11 +196,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
DisputeResult disputeResult = disputeResultMessage.getDisputeResult();
ChatMessage chatMessage = disputeResult.getChatMessage();
checkNotNull(chatMessage, "chatMessage must not be null");
if (Arrays.equals(disputeResult.getArbitratorPubKey(),
btcWalletService.getArbitratorAddressEntry().getPubKey())) {
log.error("Arbitrator received disputeResultMessage. That must never happen.");
return;
}
Optional<Trade> tradeOptional = tradeManager.getTradeById(disputeResult.getTradeId());
String tradeId = disputeResult.getTradeId();
Optional<Dispute> disputeOptional = findDispute(disputeResult);
@ -209,8 +215,14 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
}
return;
}
Dispute dispute = disputeOptional.get();
// verify that arbitrator does not get DisputeResultMessage
if (pubKeyRing.equals(dispute.getAgentPubKeyRing())) {
log.error("Arbitrator received disputeResultMessage. That must never happen.");
return;
}
cleanupRetryMap(uid);
if (!dispute.getChatMessages().contains(chatMessage)) {
dispute.addAndPersistChatMessage(chatMessage);
@ -225,16 +237,16 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
}
dispute.setDisputeResult(disputeResult);
Optional<Trade> tradeOptional = tradeManager.getTradeById(tradeId);
String errorMessage = null;
boolean success = false;
boolean success = true;
boolean requestUpdatedPayoutTx = false;
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
Contract contract = dispute.getContract();
try {
// We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals
// There would be different transactions if both sign and publish (signers: once buyer+arb, once seller+arb)
// The tx publisher is the winner or in case both get 50% the buyer, as the buyer has more inventive to publish the tx as he receives
// more BTC as he has deposited
Contract contract = dispute.getContract();
boolean isBuyer = pubKeyRing.equals(contract.getBuyerPubKeyRing());
DisputeResult.Winner publisher = disputeResult.getWinner();
@ -252,7 +264,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
if ((isBuyer && publisher == DisputeResult.Winner.BUYER)
|| (!isBuyer && publisher == DisputeResult.Winner.SELLER)) {
Transaction payoutTx = null;
MoneroTxWallet payoutTx = null;
if (tradeOptional.isPresent()) {
payoutTx = tradeOptional.get().getPayoutTx();
} else {
@ -262,51 +274,29 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
}
}
if (payoutTx == null) {
if (dispute.getDepositTxSerialized() != null) {
byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
DeterministicKey multiSigKeyPair = btcWalletService.getMultiSigKeyPair(tradeId, multiSigPubKey);
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
dispute.getDepositTxSerialized(),
disputeResult.getArbitratorSignature(),
disputeResult.getBuyerPayoutAmount(),
disputeResult.getSellerPayoutAmount(),
contract.getBuyerPayoutAddressString(),
contract.getSellerPayoutAddressString(),
multiSigKeyPair,
contract.getBuyerMultiSigPubKey(),
contract.getSellerMultiSigPubKey(),
disputeResult.getArbitratorPubKey()
);
Transaction committedDisputedPayoutTx = WalletService.maybeAddSelfTxToWallet(signedDisputedPayoutTx, btcWalletService.getWallet());
tradeWalletService.broadcastTx(committedDisputedPayoutTx, new TxBroadcaster.Callback() {
@Override
public void onSuccess(Transaction transaction) {
// after successful publish we send peer the tx
dispute.setDisputePayoutTxId(transaction.getTxId().toString());
sendPeerPublishedPayoutTxMessage(transaction, dispute, contract);
updateTradeOrOpenOfferManager(tradeId);
}
@Override
public void onFailure(TxBroadcastException exception) {
log.error(exception.getMessage());
}
}, 15);
// gather relevant info
String arbitratorSignedPayoutTxHex = disputeResult.getArbitratorSignedPayoutTxHex();
success = true;
} else {
errorMessage = "DepositTx is null. TradeId = " + tradeId;
if (arbitratorSignedPayoutTxHex != null) {
if (!tradeOptional.isPresent()) throw new RuntimeException("Trade must not be null when trader signs arbitrator's payout tx");
try {
MoneroTxSet txSet = traderSignsDisputePayoutTx(tradeId, arbitratorSignedPayoutTxHex);
onTraderSignedDisputePayoutTx(tradeId, txSet);
} catch (Exception e) {
errorMessage = "Failed to sign dispute payout tx from arbitrator: " + e.getMessage() + ". TradeId = " + tradeId;
log.warn(errorMessage);
success = false;
}
}
} else {
requestUpdatedPayoutTx = true;
}
} else {
log.warn("We already got a payout tx. That might be the case if the other peer did not get the " +
"payout tx and opened a dispute. TradeId = " + tradeId);
dispute.setDisputePayoutTxId(payoutTx.getTxId().toString());
sendPeerPublishedPayoutTxMessage(payoutTx, dispute, contract);
success = true;
}
} else {
log.trace("We don't publish the tx as we are not the winning party.");
@ -314,28 +304,37 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
if (dispute.disputeResultProperty().get() != null && dispute.isClosed()) {
updateTradeOrOpenOfferManager(tradeId);
}
success = true;
}
} catch (TransactionVerificationException e) {
}
// catch (TransactionVerificationException e) {
// errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
// log.error(errorMessage, e);
// success = false;
//
// // We prefer to close the dispute in that case. If there was no deposit tx and a random tx was used
// // we get a TransactionVerificationException. No reason to keep that dispute open...
// updateTradeOrOpenOfferManager(tradeId);
//
// throw new RuntimeException(errorMessage);
// }
// catch (AddressFormatException | WalletException e) {
catch (Exception e) {
errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
log.error(errorMessage, e);
success = false;
// We prefer to close the dispute in that case. If there was no deposit tx and a random tx was used
// we get a TransactionVerificationException. No reason to keep that dispute open...
updateTradeOrOpenOfferManager(tradeId);
updateTradeOrOpenOfferManager(tradeId); // TODO (woodser): only close in case of verification exception?
throw new RuntimeException(errorMessage);
} catch (AddressFormatException | WalletException | SignatureDecodeException e) {
errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
log.error(errorMessage, e);
success = false;
throw new RuntimeException(errorMessage);
} finally {
// We use the chatMessage as we only persist those not the disputeResultMessage.
// If we would use the disputeResultMessage we could not lookup for the msg when we receive the AckMessage.
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), success, errorMessage);
// If dispute opener's peer is co-signer, send updated multisig hex to arbitrator to receive updated payout tx
if (requestUpdatedPayoutTx) sendArbitratorPayoutTxRequest(multisigWallet.getMultisigHex(), dispute, contract);
}
requestPersistence();
@ -366,27 +365,147 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
cleanupRetryMap(uid);
Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
// update multisig wallet
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
dispute.setDisputePayoutTxId(committedDisputePayoutTx.getTxId().toString());
BtcWalletService.printTx("Disputed payoutTx received from peer", committedDisputePayoutTx);
// parse payout tx
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
// System.out.println("LOSER'S VIEW OF MULTISIG WALLET (SHOULD INCLUDE PAYOUT TX):\n" + multisigWallet.getTxs());
// if (multisigWallet.getTxs().size() != 3) throw new RuntimeException("Loser's multisig wallet does not include record of payout tx");
// Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
requestPersistence();
}
// Arbitrator receives updated multisig hex from dispute opener's peer (if co-signer) and returns updated payout tx to be signed and published
private void onArbitratorPayoutTxRequest(ArbitratorPayoutTxRequest request) {
String tradeId = request.getTradeId();
Dispute dispute = findDispute(request.getDispute().getTradeId(), request.getDispute().getTraderId()).get();
DisputeResult disputeResult = dispute.getDisputeResultProperty().get();
Contract contract = dispute.getContract();
// verify sender is co-signer and receiver is arbitrator
System.out.println("Any of these null???"); // TODO (woodser): NPE if dispute opener's peer-as-cosigner's ticket is closed first
System.out.println(disputeResult);
System.out.println(disputeResult.getWinner());
System.out.println(contract.getBuyerNodeAddress());
System.out.println(contract.getSellerNodeAddress());
boolean senderIsWinner = (disputeResult.getWinner() == Winner.BUYER && contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress())) || (disputeResult.getWinner() == Winner.SELLER && contract.getSellerNodeAddress().equals(request.getSenderNodeAddress()));
boolean senderIsCosigner = senderIsWinner || disputeResult.isLoserPublisher();
boolean receiverIsArbitrator = pubKeyRing.equals(dispute.getAgentPubKeyRing());
System.out.println("TESTING PUB KEY RINGS");
System.out.println(pubKeyRing);
System.out.println(dispute.getAgentPubKeyRing());
System.out.println("Receiver is arbitrator: " + receiverIsArbitrator);
if (!senderIsCosigner) {
log.warn("Received ArbitratorPayoutTxRequest but sender is not co-signer for trade id " + tradeId);
return;
}
if (!receiverIsArbitrator) {
log.warn("Received ArbitratorPayoutTxRequest but receiver is not arbitrator for trade id " + tradeId);
return;
}
// update arbitrator's multisig wallet with co-signer's multisig hex
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
try {
multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex()));
} catch (Exception e) {
log.warn("Failed to import multisig hex from payout co-signer for trade id " + tradeId);
return;
}
// create updated payout tx
MoneroTxWallet payoutTx = arbitratorCreatesDisputedPayoutTx(contract, dispute, disputeResult, multisigWallet);
System.out.println("Arbitrator created updated payout tx for co-signer!!!");
System.out.println(payoutTx);
// send updated payout tx to sender
PubKeyRing senderPubKeyRing = contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress()) ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
ArbitratorPayoutTxResponse response = new ArbitratorPayoutTxResponse(
tradeId,
p2PService.getAddress(),
UUID.randomUUID().toString(),
SupportType.ARBITRATION,
payoutTx.getTxSet().getMultisigTxHex());
log.info("Send {} to peer {}. tradeId={}, uid={}", response.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), response.getUid());
p2PService.sendEncryptedDirectMessage(request.getSenderNodeAddress(),
senderPubKeyRing,
response,
new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}",
response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid());
}
@Override
public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid(), errorMessage);
}
}
);
}
// Dispute opener's peer receives updated payout tx after providing updated multisig hex (if co-signer)
private void onArbitratorPayoutTxResponse(ArbitratorPayoutTxResponse response) {
// gather and verify trade info // TODO (woodser): verify response is from arbitrator, etc
String tradeId = response.getTradeId();
// verify and sign dispute payout tx
MoneroTxSet signedPayoutTx = traderSignsDisputePayoutTx(tradeId, response.getArbitratorSignedPayoutTxHex());
// process fully signed payout tx (publish, notify peer, etc)
onTraderSignedDisputePayoutTx(tradeId, signedPayoutTx);
}
private void onTraderSignedDisputePayoutTx(String tradeId, MoneroTxSet txSet) {
// gather trade info
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
if (!disputeOptional.isPresent()) {
log.warn("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId);
return;
}
Dispute dispute = disputeOptional.get();
Contract contract = dispute.getContract();
Trade trade = tradeManager.getTradeById(tradeId).get();
// submit fully signed payout tx to the network
multisigWallet.submitMultisigTxHex(txSet.getMultisigTxHex());
// update state
trade.setPayoutTx(txSet.getTxs().get(0)); // TODO (woodser): is trade.payoutTx() mutually exclusive from dispute payout tx?
trade.setPayoutTxId(txSet.getTxs().get(0).getHash());
trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
dispute.setDisputePayoutTxId(txSet.getTxs().get(0).getHash());
sendPeerPublishedPayoutTxMessage(multisigWallet.getMultisigHex(), txSet.getMultisigTxHex(), dispute, contract);
updateTradeOrOpenOfferManager(tradeId);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Send messages
///////////////////////////////////////////////////////////////////////////////////////////
// winner (or buyer in case of 50/50) sends tx to other peer
private void sendPeerPublishedPayoutTxMessage(Transaction transaction, Dispute dispute, Contract contract) {
private void sendPeerPublishedPayoutTxMessage(String updatedMultisigHex, String payoutTxHex, Dispute dispute, Contract contract) {
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
NodeAddress peersNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerNodeAddress() : contract.getBuyerNodeAddress();
log.trace("sendPeerPublishedPayoutTxMessage to peerAddress {}", peersNodeAddress);
PeerPublishedDisputePayoutTxMessage message = new PeerPublishedDisputePayoutTxMessage(transaction.bitcoinSerialize(),
PeerPublishedDisputePayoutTxMessage message = new PeerPublishedDisputePayoutTxMessage(updatedMultisigHex,
payoutTxHex,
dispute.getTradeId(),
p2PService.getAddress(),
UUID.randomUUID().toString(),
@ -427,4 +546,190 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
}
}
// dispute opener's peer signs payout tx by sending updated multisig hex to arbitrator who returns updated payout tx
private void sendArbitratorPayoutTxRequest(String updatedMultisigHex, Dispute dispute, Contract contract) {
ArbitratorPayoutTxRequest request = new ArbitratorPayoutTxRequest(
dispute,
p2PService.getAddress(),
UUID.randomUUID().toString(),
SupportType.ARBITRATION,
updatedMultisigHex);
log.info("Send {} to peer {}. tradeId={}, uid={}",
request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid());
p2PService.sendEncryptedDirectMessage(contract.getArbitratorNodeAddress(),
dispute.getAgentPubKeyRing(),
request,
new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}",
request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid());
}
@Override
public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid(), errorMessage);
}
}
);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Disputed payout tx signing
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): where to move this common logic?
public static MoneroTxWallet arbitratorCreatesDisputedPayoutTx(Contract contract, Dispute dispute, DisputeResult disputeResult, MoneroWallet multisigWallet) {
//System.out.println("DisputeSummaryWindow.arbitratorSignsDisputedPayoutTx()");
//System.out.println("=== DISPUTE ===");
//System.out.println(dispute);
//System.out.println("=== CONTRACT ===");
//System.out.println(contract); // TODO (woodser): contract should include deposit tx hashes (pre-created then hash shared then contract signed)
//System.out.println("=== DISPUTE RESULT ===");
//System.out.println(disputeResult);
// gather relevant trade info
String buyerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString();
String sellerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null");
Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null");
BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
//System.out.println("buyerPayoutAddress: " + buyerPayoutAddress);
//System.out.println("buyerPayoutAmount: " + buyerPayoutAmount);
// Offer offer = new Offer(contract.getOfferPayload());
// System.out.println("Buyer deposit tx fee: " +
//System.out.println("sellerPayoutAddress: " + sellerPayoutAddress);
//System.out.println("sellerPayoutAmount: " + sellerPayoutAmount);
//System.out.println("Multisig balance: " + multisigWallet.getBalance());
//System.out.println("Multisig unlocked balance: " + multisigWallet.getUnlockedBalance());
//System.out.println("Multisig txs");
//System.out.println(multisigWallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true)));
// create transaction to get fee estimate
if (multisigWallet.isMultisigImportNeeded()) {
log.info("Arbitrator's wallet needs updated multisig hex to create payout tx which means a trader must have already broadcast the payout tx");
return null;
}
// TODO (woodser): include arbitration fee
//System.out.println("Creating feeEstimateTx!");
MoneroTxWallet feeEstimateTx = multisigWallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // reduce payment amount to compute fee of similar tx
.addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // TODO (woodser): support addDestination(addr, amt) without new
.setRelay(false)
);
System.out.println("Created fee estimate tx!");
System.out.println(feeEstimateTx);
//BigInteger estimatedFee = feeEstimateTx.getFee();
// attempt to create payout tx by increasing estimated fee until successful
MoneroTxWallet payoutTx = null;
int numAttempts = 0;
while (payoutTx == null && numAttempts < 50) {
BigInteger feeEstimate = feeEstimateTx.getFee().add(feeEstimateTx.getFee().multiply(BigInteger.valueOf(numAttempts)).divide(BigInteger.valueOf(10))); // add 1/10 of fee until tx is successful
try {
numAttempts++;
payoutTx = multisigWallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // split fee subtracted from each payout amount
.addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // TODO (woodser): support addDestination(addr, amt) without new
.setRelay(false));
} catch (MoneroError e) {
e.printStackTrace();
System.out.println("FAILED TO CREATE PAYOUT TX, ITERATING...");
}
}
if (payoutTx == null) throw new RuntimeException("Failed to generate dispute payout tx");
System.out.println("DISPUTE PAYOUT TX GENERATED ON ATTEMPT " + numAttempts);
System.out.println(payoutTx);
return payoutTx;
}
private MoneroTxSet traderSignsDisputePayoutTx(String tradeId, String payoutTxHex) {
// gather trade info
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
if (!disputeOptional.isPresent()) throw new RuntimeException("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId);
Dispute dispute = disputeOptional.get();
Contract contract = dispute.getContract();
DisputeResult disputeResult = dispute.getDisputeResultProperty().get();
// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null");
// BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getMakerDepositTxId() : trade.getTakerDepositTxId()).getIncomingAmount(); // TODO (woodser): use contract instead of trade to get deposit tx ids when contract has deposit tx ids
// BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getTakerDepositTxId() : trade.getMakerDepositTxId()).getIncomingAmount();
// BigInteger tradeAmount = BigInteger.valueOf(contract.getTradeAmount().value).multiply(ParsingUtils.XMR_SATOSHI_MULTIPLIER);
BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
System.out.println("Buyer payout amount (with multiplier): " + buyerPayoutAmount);
System.out.println("Seller payout amount (with multiplier): " + sellerPayoutAmount);
// parse arbitrator-signed payout tx
MoneroTxSet parsedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
if (parsedTxSet.getTxs() == null || parsedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad arbitrator-signed payout tx"); // TODO (woodser): nack
MoneroTxWallet arbitratorSignedPayoutTx = parsedTxSet.getTxs().get(0);
System.out.println("Parsed arbitrator-signed payout tx:\n" + arbitratorSignedPayoutTx);
// verify payout tx has exactly 2 destinations
if (arbitratorSignedPayoutTx.getOutgoingTransfer() == null || arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations() == null || arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new RuntimeException("Buyer-signed payout tx does not have exactly two destinations");
// get buyer and seller destinations (order not preserved)
boolean buyerFirst = arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().get(0).getAddress().equals(contract.getBuyerPayoutAddressString());
MoneroDestination buyerPayoutDestination = arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 0 : 1);
MoneroDestination sellerPayoutDestination = arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 1 : 0);
// verify payout addresses
if (!buyerPayoutDestination.getAddress().equals(contract.getBuyerPayoutAddressString())) throw new RuntimeException("Buyer payout address does not match contract");
if (!sellerPayoutDestination.getAddress().equals(contract.getSellerPayoutAddressString())) throw new RuntimeException("Seller payout address does not match contract");
// verify change address is multisig's primary address
if (!arbitratorSignedPayoutTx.getChangeAddress().equals(multisigWallet.getPrimaryAddress())) throw new RuntimeException("Change address is not multisig wallet's primary address");
// verify sum of outputs = destination amounts + change amount
if (!arbitratorSignedPayoutTx.getOutputSum().equals(buyerPayoutDestination.getAmount().add(sellerPayoutDestination.getAmount()).add(arbitratorSignedPayoutTx.getChangeAmount()))) throw new RuntimeException("Sum of outputs != destination amounts + change amount");
// verify buyer destination amount is payout amount - 1/2 tx costs
BigInteger txCost = arbitratorSignedPayoutTx.getFee().add(arbitratorSignedPayoutTx.getChangeAmount());
BigInteger expectedBuyerPayout = buyerPayoutAmount.subtract(txCost.divide(BigInteger.valueOf(2)));
System.out.println("Dispute buyer payout amount: " + buyerPayoutAmount);
System.out.println("Tx cost: " + txCost);
System.out.println("Buyer destination payout amount: " + buyerPayoutDestination.getAmount());
// payout amount is dispute payout amount - 1/2 tx cost - deposit tx fee
// TODO (woodser): VERIFY PAYOUT TX AMOUNTS WHICH CONSIDERS FEE IF LONG TRADE, EXACT AMOUNT IF SHORT TRADE
// if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not payout amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
// verify seller destination amount is payout amount - 1/2 tx costs
// BigInteger expectedSellerPayout = sellerPayoutAmount.subtract(txCost.divide(BigInteger.valueOf(2)));
// if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new RuntimeException("Seller destination amount is not payout amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
// TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx)
// update multisig wallet from arbitrator
System.out.println("Updating multisig hex from arbitrator: " + disputeResult.getArbitratorUpdatedMultisigHex());
multisigWallet.importMultisigHex(Arrays.asList(disputeResult.getArbitratorUpdatedMultisigHex()));
// sign arbitrator-signed payout tx
MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex);
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing arbitrator-signed payout tx");
String signedMultisigTxHex = result.getSignedMultisigTxHex();
parsedTxSet.setMultisigTxHex(signedMultisigTxHex);
return parsedTxSet;
}
}

View File

@ -22,9 +22,6 @@ import bisq.core.support.SupportType;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import bisq.common.util.Utilities;
import com.google.protobuf.ByteString;
import lombok.EqualsAndHashCode;
import lombok.Value;
@ -32,16 +29,19 @@ import lombok.Value;
@Value
@EqualsAndHashCode(callSuper = true)
public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessage {
private final byte[] transaction;
private final String updatedMultisigHex;
private final String payoutTxHex;
private final String tradeId;
private final NodeAddress senderNodeAddress;
public PeerPublishedDisputePayoutTxMessage(byte[] transaction,
public PeerPublishedDisputePayoutTxMessage(String updatedMultisigHex,
String payoutTxHex,
String tradeId,
NodeAddress senderNodeAddress,
String uid,
SupportType supportType) {
this(transaction,
this(updatedMultisigHex,
payoutTxHex,
tradeId,
senderNodeAddress,
uid,
@ -54,14 +54,16 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private PeerPublishedDisputePayoutTxMessage(byte[] transaction,
private PeerPublishedDisputePayoutTxMessage(String updatedMultisigHex,
String payoutTxHex,
String tradeId,
NodeAddress senderNodeAddress,
String uid,
int messageVersion,
SupportType supportType) {
super(messageVersion, uid, supportType);
this.transaction = transaction;
this.updatedMultisigHex = updatedMultisigHex;
this.payoutTxHex = payoutTxHex;
this.tradeId = tradeId;
this.senderNodeAddress = senderNodeAddress;
}
@ -70,7 +72,8 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder()
.setPeerPublishedDisputePayoutTxMessage(protobuf.PeerPublishedDisputePayoutTxMessage.newBuilder()
.setTransaction(ByteString.copyFrom(transaction))
.setUpdatedMultisigHex(updatedMultisigHex)
.setPayoutTxHex(payoutTxHex)
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setUid(uid)
@ -80,7 +83,8 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag
public static PeerPublishedDisputePayoutTxMessage fromProto(protobuf.PeerPublishedDisputePayoutTxMessage proto,
int messageVersion) {
return new PeerPublishedDisputePayoutTxMessage(proto.getTransaction().toByteArray(),
return new PeerPublishedDisputePayoutTxMessage(proto.getUpdatedMultisigHex(),
proto.getPayoutTxHex(),
proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getUid(),
@ -96,7 +100,8 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag
@Override
public String toString() {
return "PeerPublishedDisputePayoutTxMessage{" +
"\n transaction=" + Utilities.bytesAsHexString(transaction) +
"\n updatedMultisigHex=" + updatedMultisigHex +
"\n payoutTxHex=" + payoutTxHex +
",\n tradeId='" + tradeId + '\'' +
",\n senderNodeAddress=" + senderNodeAddress +
",\n PeerPublishedDisputePayoutTxMessage.uid='" + uid + '\'' +

View File

@ -18,8 +18,8 @@
package bisq.core.support.dispute.mediation;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
@ -77,7 +77,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
@Inject
public MediationManager(P2PService p2PService,
TradeWalletService tradeWalletService,
BtcWalletService walletService,
XmrWalletService walletService,
WalletsSetup walletsSetup,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
@ -226,7 +226,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
@Nullable
@Override
public NodeAddress getAgentNodeAddress(Dispute dispute) {
return dispute.getContract().getMediatorNodeAddress();
return dispute.getContract().getArbitratorNodeAddress(); // TODO (woodser): mediator becomes and replaces current arbitrator?
}
public void onAcceptMediationResult(Trade trade,

View File

@ -0,0 +1,107 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.support.dispute.messages;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.SupportType;
import bisq.core.support.dispute.Dispute;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import lombok.EqualsAndHashCode;
import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public final class ArbitratorPayoutTxRequest extends DisputeMessage {
private final Dispute dispute;
private final NodeAddress senderNodeAddress;
private final String updatedMultisigHex;
public ArbitratorPayoutTxRequest(Dispute dispute,
NodeAddress senderNodeAddress,
String uid,
SupportType supportType,
String updatedMultisigHex) {
this(dispute,
senderNodeAddress,
uid,
Version.getP2PMessageVersion(),
supportType,
updatedMultisigHex);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private ArbitratorPayoutTxRequest(Dispute dispute,
NodeAddress senderNodeAddress,
String uid,
int messageVersion,
SupportType supportType,
String updatedMultisigHex) {
super(messageVersion, uid, supportType);
this.dispute = dispute;
this.senderNodeAddress = senderNodeAddress;
this.updatedMultisigHex = updatedMultisigHex;
}
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder()
.setArbitratorPayoutTxRequest(protobuf.ArbitratorPayoutTxRequest.newBuilder()
.setUid(uid)
.setDispute(dispute.toProtoMessage())
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setType(SupportType.toProtoMessage(supportType))
.setUpdatedMultisigHex(updatedMultisigHex))
.build();
}
public static ArbitratorPayoutTxRequest fromProto(protobuf.ArbitratorPayoutTxRequest proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new ArbitratorPayoutTxRequest(Dispute.fromProto(proto.getDispute(), coreProtoResolver),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getUid(),
messageVersion,
SupportType.fromProto(proto.getType()),
proto.getUpdatedMultisigHex());
}
@Override
public String getTradeId() {
return dispute.getTradeId();
}
@Override
public String toString() {
return "ArbitratorPayoutTxRequest{" +
"\n dispute=" + dispute +
",\n senderNodeAddress=" + senderNodeAddress +
",\n ArbitratorPayoutTxRequest.uid='" + uid + '\'' +
",\n messageVersion=" + messageVersion +
",\n supportType=" + supportType +
",\n updatedMultisigHex=" + updatedMultisigHex +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,101 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.support.dispute.messages;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.SupportType;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import lombok.EqualsAndHashCode;
import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public final class ArbitratorPayoutTxResponse extends DisputeMessage {
private final String tradeId;
private final NodeAddress senderNodeAddress;
private final String arbitratorSignedPayoutTxHex;
public ArbitratorPayoutTxResponse(String tradeId,
NodeAddress senderNodeAddress,
String uid,
SupportType supportType,
String arbitratorSignedPayoutTxHex) {
this(tradeId,
senderNodeAddress,
uid,
Version.getP2PMessageVersion(),
supportType,
arbitratorSignedPayoutTxHex);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private ArbitratorPayoutTxResponse(String tradeId,
NodeAddress senderNodeAddress,
String uid,
int messageVersion,
SupportType supportType,
String arbitratorSignedPayoutTxHex) {
super(messageVersion, uid, supportType);
this.tradeId = tradeId;
this.senderNodeAddress = senderNodeAddress;
this.arbitratorSignedPayoutTxHex = arbitratorSignedPayoutTxHex;
}
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder()
.setArbitratorPayoutTxResponse(protobuf.ArbitratorPayoutTxResponse.newBuilder()
.setUid(uid)
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setType(SupportType.toProtoMessage(supportType))
.setArbitratorSignedPayoutTxHex(arbitratorSignedPayoutTxHex))
.build();
}
public static ArbitratorPayoutTxResponse fromProto(protobuf.ArbitratorPayoutTxResponse proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new ArbitratorPayoutTxResponse(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getUid(),
messageVersion,
SupportType.fromProto(proto.getType()),
proto.getArbitratorSignedPayoutTxHex());
}
@Override
public String toString() {
return "ArbitratorPayoutTxResponse{" +
"\n tradeId=" + tradeId +
",\n senderNodeAddress=" + senderNodeAddress +
",\n ArbitratorPayoutTxResponse.uid='" + uid + '\'' +
",\n messageVersion=" + messageVersion +
",\n supportType=" + supportType +
",\n updatedMultisigHex=" + arbitratorSignedPayoutTxHex +
"\n} " + super.toString();
}
}

View File

@ -33,16 +33,19 @@ import lombok.Value;
public final class OpenNewDisputeMessage extends DisputeMessage {
private final Dispute dispute;
private final NodeAddress senderNodeAddress;
private final String updatedMultisigHex;
public OpenNewDisputeMessage(Dispute dispute,
NodeAddress senderNodeAddress,
String uid,
SupportType supportType) {
SupportType supportType,
String updatedMultisigHex) {
this(dispute,
senderNodeAddress,
uid,
Version.getP2PMessageVersion(),
supportType);
supportType,
updatedMultisigHex);
}
@ -54,10 +57,12 @@ public final class OpenNewDisputeMessage extends DisputeMessage {
NodeAddress senderNodeAddress,
String uid,
int messageVersion,
SupportType supportType) {
SupportType supportType,
String updatedMultisigHex) {
super(messageVersion, uid, supportType);
this.dispute = dispute;
this.senderNodeAddress = senderNodeAddress;
this.updatedMultisigHex = updatedMultisigHex;
}
@Override
@ -67,7 +72,8 @@ public final class OpenNewDisputeMessage extends DisputeMessage {
.setUid(uid)
.setDispute(dispute.toProtoMessage())
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setType(SupportType.toProtoMessage(supportType)))
.setType(SupportType.toProtoMessage(supportType))
.setUpdatedMultisigHex(updatedMultisigHex))
.build();
}
@ -78,7 +84,8 @@ public final class OpenNewDisputeMessage extends DisputeMessage {
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getUid(),
messageVersion,
SupportType.fromProto(proto.getType()));
SupportType.fromProto(proto.getType()),
proto.getUpdatedMultisigHex());
}
@Override
@ -94,6 +101,7 @@ public final class OpenNewDisputeMessage extends DisputeMessage {
",\n OpenNewDisputeMessage.uid='" + uid + '\'' +
",\n messageVersion=" + messageVersion +
",\n supportType=" + supportType +
",\n updatedMultisigHex=" + updatedMultisigHex +
"\n} " + super.toString();
}
}

View File

@ -18,8 +18,8 @@
package bisq.core.support.dispute.refund;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
@ -71,12 +71,12 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
@Inject
public RefundManager(P2PService p2PService,
TradeWalletService tradeWalletService,
BtcWalletService walletService,
XmrWalletService walletService,
WalletsSetup walletsSetup,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
DaoFacade daoFacade,
DaoFacade daoFacade, // TODO (woodser): remove daoFacade, priceFeedService?
KeyRing keyRing,
RefundDisputeListService refundDisputeListService,
Config config,
@ -232,6 +232,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
@Nullable
@Override
public NodeAddress getAgentNodeAddress(Dispute dispute) {
return dispute.getContract().getRefundAgentNodeAddress();
throw new RuntimeException("Refund manager not used in XMR adapation");
//return dispute.getContract().getRefundAgentNodeAddress();
}
}

View File

@ -0,0 +1,84 @@
package bisq.core.trade;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
import bisq.network.p2p.NodeAddress;
import bisq.common.proto.ProtoUtil;
import org.bitcoinj.core.Coin;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
/**
* Trade in the context of an arbitrator.
*/
@Slf4j
public class ArbitratorTrade extends Trade {
public ArbitratorTrade(Offer offer,
Coin tradeAmount,
Coin txFee,
Coin takerFee,
long tradePrice,
NodeAddress makerNodeAddress,
NodeAddress takerNodeAddress,
NodeAddress arbitratorNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer, tradeAmount, txFee, takerFee, tradePrice, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress, xmrWalletService, processModel, uid);
}
@Override
public Coin getPayoutAmount() {
throw new RuntimeException("Arbitrator does not have a payout amount");
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.Tradable toProtoMessage() {
return protobuf.Tradable.newBuilder()
.setArbitratorTrade(protobuf.ArbitratorTrade.newBuilder()
.setTrade((protobuf.Trade) super.toProtoMessage()))
.build();
}
public static Tradable fromProto(protobuf.ArbitratorTrade arbitratorTradeProto,
XmrWalletService xmrWalletService,
CoreProtoResolver coreProtoResolver) {
protobuf.Trade proto = arbitratorTradeProto.getTrade();
ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver);
String uid = ProtoUtil.stringOrNullFromProto(proto.getUid());
if (uid == null) {
uid = UUID.randomUUID().toString();
}
return fromProto(new ArbitratorTrade(
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getTradePrice(),
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
xmrWalletService,
processModel,
uid),
proto,
coreProtoResolver);
}
@Override
public boolean confirmPermitted() {
throw new RuntimeException("ArbitratorTrade.confirmPermitted() not implemented"); // TODO (woodser): implement
}
}

View File

@ -17,7 +17,7 @@
package bisq.core.trade;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
@ -44,21 +44,19 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
public BuyerAsMakerTrade(Offer offer,
Coin txFee,
Coin takeOfferFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
txFee,
takeOfferFee,
isCurrencyForTakerFeeBtc,
takerNodeAddress,
makerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress,
btcWalletService,
xmrWalletService,
processModel,
uid);
}
@ -76,7 +74,7 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
}
public static Tradable fromProto(protobuf.BuyerAsMakerTrade buyerAsMakerTradeProto,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
CoreProtoResolver coreProtoResolver) {
protobuf.Trade proto = buyerAsMakerTradeProto.getTrade();
ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver);
@ -88,17 +86,19 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getIsCurrencyForTakerFeeBtc(),
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
btcWalletService,
xmrWalletService,
processModel,
uid);
trade.setTradeAmountAsLong(proto.getTradeAmountAsLong());
trade.setTradePrice(proto.getTradePrice());
trade.setTradingPeerNodeAddress(proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null);
trade.setMakerNodeAddress(proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null);
trade.setTakerNodeAddress(proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null);
trade.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
return fromProto(trade,
proto,

View File

@ -17,7 +17,7 @@
package bisq.core.trade;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
@ -45,26 +45,22 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
Coin tradeAmount,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
tradeAmount,
txFee,
takerFee,
isCurrencyForTakerFeeBtc,
tradePrice,
tradingPeerNodeAddress,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress,
btcWalletService,
xmrWalletService,
processModel,
uid);
}
@ -83,7 +79,7 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
}
public static Tradable fromProto(protobuf.BuyerAsTakerTrade buyerAsTakerTradeProto,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
CoreProtoResolver coreProtoResolver) {
protobuf.Trade proto = buyerAsTakerTradeProto.getTrade();
ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver);
@ -96,13 +92,11 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getIsCurrencyForTakerFeeBtc(),
proto.getTradePrice(),
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
btcWalletService,
xmrWalletService,
processModel,
uid),
proto,

View File

@ -17,7 +17,7 @@
package bisq.core.trade;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.trade.protocol.ProcessModel;
@ -37,26 +37,22 @@ public abstract class BuyerTrade extends Trade {
Coin tradeAmount,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
tradeAmount,
txFee,
takerFee,
isCurrencyForTakerFeeBtc,
tradePrice,
tradingPeerNodeAddress,
takerNodeAddress,
makerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress,
btcWalletService,
xmrWalletService,
processModel,
uid);
}
@ -64,21 +60,19 @@ public abstract class BuyerTrade extends Trade {
BuyerTrade(Offer offer,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
txFee,
takerFee,
isCurrencyForTakerFeeBtc,
takerNodeAddress,
makerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress,
btcWalletService,
xmrWalletService,
processModel,
uid);
}

View File

@ -33,8 +33,6 @@ import bisq.common.proto.network.NetworkPayload;
import bisq.common.util.JsonExclude;
import bisq.common.util.Utilities;
import com.google.protobuf.ByteString;
import org.bitcoinj.core.Coin;
import org.apache.commons.lang3.StringUtils;
@ -52,10 +50,9 @@ public final class Contract implements NetworkPayload {
private final OfferPayload offerPayload;
private final long tradeAmount;
private final long tradePrice;
private final String takerFeeTxID;
private final NodeAddress buyerNodeAddress;
private final NodeAddress sellerNodeAddress;
private final NodeAddress mediatorNodeAddress;
private final NodeAddress arbitratorNodeAddress;
private final boolean isBuyerMakerAndSellerTaker;
private final String makerAccountId;
private final String takerAccountId;
@ -67,22 +64,16 @@ public final class Contract implements NetworkPayload {
private final PubKeyRing takerPubKeyRing;
private final String makerPayoutAddressString;
private final String takerPayoutAddressString;
@JsonExclude
private final byte[] makerMultiSigPubKey;
@JsonExclude
private final byte[] takerMultiSigPubKey;
// Added in v1.2.0
private long lockTime;
private final NodeAddress refundAgentNodeAddress;
public Contract(OfferPayload offerPayload,
long tradeAmount,
long tradePrice,
String takerFeeTxID,
NodeAddress buyerNodeAddress,
NodeAddress sellerNodeAddress,
NodeAddress mediatorNodeAddress,
NodeAddress arbitratorNodeAddress,
boolean isBuyerMakerAndSellerTaker,
String makerAccountId,
String takerAccountId,
@ -92,17 +83,13 @@ public final class Contract implements NetworkPayload {
PubKeyRing takerPubKeyRing,
String makerPayoutAddressString,
String takerPayoutAddressString,
byte[] makerMultiSigPubKey,
byte[] takerMultiSigPubKey,
long lockTime,
NodeAddress refundAgentNodeAddress) {
long lockTime) {
this.offerPayload = offerPayload;
this.tradeAmount = tradeAmount;
this.tradePrice = tradePrice;
this.takerFeeTxID = takerFeeTxID;
this.buyerNodeAddress = buyerNodeAddress;
this.sellerNodeAddress = sellerNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
this.makerAccountId = makerAccountId;
this.takerAccountId = takerAccountId;
@ -112,10 +99,7 @@ public final class Contract implements NetworkPayload {
this.takerPubKeyRing = takerPubKeyRing;
this.makerPayoutAddressString = makerPayoutAddressString;
this.takerPayoutAddressString = takerPayoutAddressString;
this.makerMultiSigPubKey = makerMultiSigPubKey;
this.takerMultiSigPubKey = takerMultiSigPubKey;
this.lockTime = lockTime;
this.refundAgentNodeAddress = refundAgentNodeAddress;
String makerPaymentMethodId = makerPaymentAccountPayload.getPaymentMethodId();
String takerPaymentMethodId = takerPaymentAccountPayload.getPaymentMethodId();
@ -137,10 +121,9 @@ public final class Contract implements NetworkPayload {
return new Contract(OfferPayload.fromProto(proto.getOfferPayload()),
proto.getTradeAmount(),
proto.getTradePrice(),
proto.getTakerFeeTxId(),
NodeAddress.fromProto(proto.getBuyerNodeAddress()),
NodeAddress.fromProto(proto.getSellerNodeAddress()),
NodeAddress.fromProto(proto.getMediatorNodeAddress()),
NodeAddress.fromProto(proto.getArbitratorNodeAddress()),
proto.getIsBuyerMakerAndSellerTaker(),
proto.getMakerAccountId(),
proto.getTakerAccountId(),
@ -150,10 +133,7 @@ public final class Contract implements NetworkPayload {
PubKeyRing.fromProto(proto.getTakerPubKeyRing()),
proto.getMakerPayoutAddressString(),
proto.getTakerPayoutAddressString(),
proto.getMakerMultiSigPubKey().toByteArray(),
proto.getTakerMultiSigPubKey().toByteArray(),
proto.getLockTime(),
NodeAddress.fromProto(proto.getRefundAgentNodeAddress()));
proto.getLockTime());
}
@Override
@ -162,10 +142,9 @@ public final class Contract implements NetworkPayload {
.setOfferPayload(offerPayload.toProtoMessage().getOfferPayload())
.setTradeAmount(tradeAmount)
.setTradePrice(tradePrice)
.setTakerFeeTxId(takerFeeTxID)
.setBuyerNodeAddress(buyerNodeAddress.toProtoMessage())
.setSellerNodeAddress(sellerNodeAddress.toProtoMessage())
.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage())
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
.setMakerAccountId(makerAccountId)
.setTakerAccountId(takerAccountId)
@ -175,10 +154,7 @@ public final class Contract implements NetworkPayload {
.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage())
.setMakerPayoutAddressString(makerPayoutAddressString)
.setTakerPayoutAddressString(takerPayoutAddressString)
.setMakerMultiSigPubKey(ByteString.copyFrom(makerMultiSigPubKey))
.setTakerMultiSigPubKey(ByteString.copyFrom(takerMultiSigPubKey))
.setLockTime(lockTime)
.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage())
.build();
}
@ -203,14 +179,6 @@ public final class Contract implements NetworkPayload {
return isBuyerMakerAndSellerTaker ? takerPubKeyRing : makerPubKeyRing;
}
public byte[] getBuyerMultiSigPubKey() {
return isBuyerMakerAndSellerTaker ? makerMultiSigPubKey : takerMultiSigPubKey;
}
public byte[] getSellerMultiSigPubKey() {
return isBuyerMakerAndSellerTaker ? takerMultiSigPubKey : makerMultiSigPubKey;
}
public PaymentAccountPayload getBuyerPaymentAccountPayload() {
return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayload : takerPaymentAccountPayload;
}
@ -296,11 +264,9 @@ public final class Contract implements NetworkPayload {
"\n offerPayload=" + offerPayload +
",\n tradeAmount=" + tradeAmount +
",\n tradePrice=" + tradePrice +
",\n takerFeeTxID='" + takerFeeTxID + '\'' +
",\n buyerNodeAddress=" + buyerNodeAddress +
",\n sellerNodeAddress=" + sellerNodeAddress +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker +
",\n makerAccountId='" + makerAccountId + '\'' +
",\n takerAccountId='" + takerAccountId + '\'' +
@ -310,10 +276,6 @@ public final class Contract implements NetworkPayload {
",\n takerPubKeyRing=" + takerPubKeyRing +
",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' +
",\n takerPayoutAddressString='" + takerPayoutAddressString + '\'' +
",\n makerMultiSigPubKey=" + Utilities.bytesAsHexString(makerMultiSigPubKey) +
",\n takerMultiSigPubKey=" + Utilities.bytesAsHexString(takerMultiSigPubKey) +
",\n buyerMultiSigPubKey=" + Utilities.bytesAsHexString(getBuyerMultiSigPubKey()) +
",\n sellerMultiSigPubKey=" + Utilities.bytesAsHexString(getSellerMultiSigPubKey()) +
",\n lockTime=" + lockTime +
"\n}";
}

View File

@ -17,7 +17,7 @@
package bisq.core.trade;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
@ -44,21 +44,19 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
public SellerAsMakerTrade(Offer offer,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
txFee,
takerFee,
isCurrencyForTakerFeeBtc,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress,
btcWalletService,
xmrWalletService,
processModel,
uid);
}
@ -77,7 +75,7 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
}
public static Tradable fromProto(protobuf.SellerAsMakerTrade sellerAsMakerTradeProto,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
CoreProtoResolver coreProtoResolver) {
protobuf.Trade proto = sellerAsMakerTradeProto.getTrade();
ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver);
@ -89,17 +87,15 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getIsCurrencyForTakerFeeBtc(),
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
btcWalletService,
xmrWalletService,
processModel,
uid);
trade.setTradeAmountAsLong(proto.getTradeAmountAsLong());
trade.setTradePrice(proto.getTradePrice());
trade.setTradingPeerNodeAddress(proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null);
return fromProto(trade,
proto,

View File

@ -17,7 +17,7 @@
package bisq.core.trade;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
@ -45,26 +45,22 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
Coin tradeAmount,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
tradeAmount,
txFee,
takerFee,
isCurrencyForTakerFeeBtc,
tradePrice,
tradingPeerNodeAddress,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress,
btcWalletService,
xmrWalletService,
processModel,
uid);
}
@ -83,7 +79,7 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
}
public static Tradable fromProto(protobuf.SellerAsTakerTrade sellerAsTakerTradeProto,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
CoreProtoResolver coreProtoResolver) {
protobuf.Trade proto = sellerAsTakerTradeProto.getTrade();
ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver);
@ -96,13 +92,11 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getIsCurrencyForTakerFeeBtc(),
proto.getTradePrice(),
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
btcWalletService,
xmrWalletService,
processModel,
uid),
proto,

View File

@ -17,7 +17,7 @@
package bisq.core.trade;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.CurrencyUtil;
import bisq.core.offer.Offer;
import bisq.core.trade.protocol.ProcessModel;
@ -38,26 +38,22 @@ public abstract class SellerTrade extends Trade {
Coin tradeAmount,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
tradeAmount,
txFee,
takerFee,
isCurrencyForTakerFeeBtc,
tradePrice,
tradingPeerNodeAddress,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress,
btcWalletService,
xmrWalletService,
processModel,
uid);
}
@ -65,21 +61,19 @@ public abstract class SellerTrade extends Trade {
SellerTrade(Offer offer,
Coin txFee,
Coin takeOfferFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
txFee,
takeOfferFee,
isCurrencyForTakerFeeBtc,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress,
btcWalletService,
xmrWalletService,
processModel,
uid);
}

View File

@ -17,7 +17,7 @@
package bisq.core.trade;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.OpenOffer;
import bisq.core.proto.CoreProtoResolver;
@ -62,20 +62,20 @@ public final class TradableList<T extends Tradable> extends PersistableListAsObs
public static TradableList<Tradable> fromProto(protobuf.TradableList proto,
CoreProtoResolver coreProtoResolver,
BtcWalletService btcWalletService) {
XmrWalletService xmrWalletService) {
List<Tradable> list = proto.getTradableList().stream()
.map(tradable -> {
switch (tradable.getMessageCase()) {
case OPEN_OFFER:
return OpenOffer.fromProto(tradable.getOpenOffer());
case BUYER_AS_MAKER_TRADE:
return BuyerAsMakerTrade.fromProto(tradable.getBuyerAsMakerTrade(), btcWalletService, coreProtoResolver);
return BuyerAsMakerTrade.fromProto(tradable.getBuyerAsMakerTrade(), xmrWalletService, coreProtoResolver);
case BUYER_AS_TAKER_TRADE:
return BuyerAsTakerTrade.fromProto(tradable.getBuyerAsTakerTrade(), btcWalletService, coreProtoResolver);
return BuyerAsTakerTrade.fromProto(tradable.getBuyerAsTakerTrade(), xmrWalletService, coreProtoResolver);
case SELLER_AS_MAKER_TRADE:
return SellerAsMakerTrade.fromProto(tradable.getSellerAsMakerTrade(), btcWalletService, coreProtoResolver);
return SellerAsMakerTrade.fromProto(tradable.getSellerAsMakerTrade(), xmrWalletService, coreProtoResolver);
case SELLER_AS_TAKER_TRADE:
return SellerAsTakerTrade.fromProto(tradable.getSellerAsTakerTrade(), btcWalletService, coreProtoResolver);
return SellerAsTakerTrade.fromProto(tradable.getSellerAsTakerTrade(), xmrWalletService, coreProtoResolver);
default:
log.error("Unknown messageCase. tradable.getMessageCase() = " + tradable.getMessageCase());
throw new ProtobufferRuntimeException("Unknown messageCase. tradable.getMessageCase() = " +

View File

@ -17,7 +17,7 @@
package bisq.core.trade;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.CurrencyUtil;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
@ -28,8 +28,10 @@ import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.support.dispute.refund.RefundResultState;
import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.ProcessModel;
import bisq.core.trade.protocol.ProcessModelServiceProvider;
import bisq.core.trade.protocol.TradeMessageListener;
import bisq.core.trade.txproof.AssetTxProofResult;
import bisq.core.util.VolumeUtil;
@ -45,12 +47,6 @@ import com.google.protobuf.Message;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
@ -64,7 +60,9 @@ import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@ -78,6 +76,14 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.common.MoneroError;
import monero.daemon.MoneroDaemon;
import monero.daemon.MoneroDaemonRpc;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
/**
* Holds all data which are relevant to the trade, but not those which are only needed in the trade process as shared data between tasks. Those data are
* stored in the task model.
@ -109,26 +115,27 @@ public abstract class Trade implements Tradable, Model {
// taker perspective
TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED), // Not used anymore
// Alternatively the taker could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
TAKER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
// #################### Phase DEPOSIT_PUBLISHED
// We changes order in trade protocol of publishing deposit tx and sending it to the peer.
// Now we send it first to the peer and only if that succeeds we publish it to avoid likelihood of
// failed trades. We do not want to change the order of the enum though so we keep it here as it was originally.
SELLER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
TAKER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
// DEPOSIT_TX_PUBLISHED_MSG
// seller perspective
SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
// taker perspective
TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
// buyer perspective
BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
// maker perspective
MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
// Alternatively the buyer could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
BUYER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
// Alternatively the maker could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
MAKER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
// #################### Phase DEPOSIT_CONFIRMED
@ -291,8 +298,6 @@ public abstract class Trade implements Tradable, Model {
@Getter
private final Offer offer;
@Getter
private final boolean isCurrencyForTakerFeeBtc;
@Getter
private final long txFeeAsLong;
@Getter
private final long takerFeeAsLong;
@ -312,10 +317,6 @@ public abstract class Trade implements Tradable, Model {
@Nullable
@Getter
@Setter
private String depositTxId;
@Nullable
@Getter
@Setter
private String payoutTxId;
@Getter
@Setter
@ -324,8 +325,6 @@ public abstract class Trade implements Tradable, Model {
private long tradePrice;
@Nullable
@Getter
private NodeAddress tradingPeerNodeAddress;
@Getter
private State state = State.PREPARATION;
@Getter
private DisputeState disputeState = DisputeState.NO_DISPUTE;
@ -390,7 +389,7 @@ public abstract class Trade implements Tradable, Model {
@Getter
transient final private Coin takerFee;
@Getter
transient final private BtcWalletService btcWalletService;
transient final private XmrWalletService xmrWalletService;
transient final private ObjectProperty<State> stateProperty = new SimpleObjectProperty<>(state);
transient final private ObjectProperty<Phase> statePhaseProperty = new SimpleObjectProperty<>(state.phase);
@ -399,8 +398,6 @@ public abstract class Trade implements Tradable, Model {
transient final private StringProperty errorMessageProperty = new SimpleStringProperty();
// Mutable
@Nullable
transient private Transaction depositTx;
@Getter
transient private boolean isInitialized;
@ -409,7 +406,7 @@ public abstract class Trade implements Tradable, Model {
transient private Transaction delayedPayoutTx;
@Nullable
transient private Transaction payoutTx;
transient private MoneroTxWallet payoutTx;
@Nullable
transient private Coin tradeAmount;
@ -462,6 +459,33 @@ public abstract class Trade implements Tradable, Model {
transient final private IntegerProperty assetTxProofResultUpdateProperty = new SimpleIntegerProperty();
// Added in XMR integration
private transient List<TradeMessageListener> tradeMessageListeners; // notified on fully validated trade messages
@Getter
@Setter
private NodeAddress makerNodeAddress;
@Getter
@Setter
private NodeAddress takerNodeAddress;
@Getter
@Setter
private PubKeyRing makerPubKeyRing;
@Getter
@Setter
private PubKeyRing takerPubKeyRing;
@Nullable
transient private MoneroTxWallet makerDepositTx;
@Nullable
transient private MoneroTxWallet takerDepositTx;
@Nullable
@Getter
@Setter
private String makerDepositTxId;
@Nullable
@Getter
@Setter
private String takerDepositTxId;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialization
///////////////////////////////////////////////////////////////////////////////////////////
@ -470,62 +494,86 @@ public abstract class Trade implements Tradable, Model {
protected Trade(Offer offer,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
this.offer = offer;
this.txFee = txFee;
this.takerFee = takerFee;
this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc;
this.makerNodeAddress = makerNodeAddress;
this.takerNodeAddress = takerNodeAddress;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
this.refundAgentNodeAddress = refundAgentNodeAddress;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.processModel = processModel;
this.uid = uid;
txFeeAsLong = txFee.value;
takerFeeAsLong = takerFee.value;
takeOfferDate = new Date().getTime();
tradeMessageListeners = new ArrayList<TradeMessageListener>();
}
// TODO (woodser): this constructor has mediator and refund agent (to be removed), otherwise use common
// taker
@SuppressWarnings("NullableProblems")
protected Trade(Offer offer,
Coin tradeAmount,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
this(offer,
txFee,
takerFee,
isCurrencyForTakerFeeBtc,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress,
btcWalletService,
xmrWalletService,
processModel,
uid);
this.tradePrice = tradePrice;
this.tradingPeerNodeAddress = tradingPeerNodeAddress;
setTradeAmount(tradeAmount);
}
// arbitrator
@SuppressWarnings("NullableProblems")
protected Trade(Offer offer,
Coin tradeAmount,
Coin txFee,
Coin takerFee,
long tradePrice,
NodeAddress makerNodeAddress,
NodeAddress takerNodeAddress,
NodeAddress arbitratorNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
this(offer,
txFee,
takerFee,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
xmrWalletService,
processModel,
uid);
this.tradePrice = tradePrice;
setTradeAmount(tradeAmount);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
@ -535,7 +583,6 @@ public abstract class Trade implements Tradable, Model {
public Message toProtoMessage() {
protobuf.Trade.Builder builder = protobuf.Trade.newBuilder()
.setOffer(offer.toProtoMessage())
.setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc)
.setTxFeeAsLong(txFeeAsLong)
.setTakerFeeAsLong(takerFeeAsLong)
.setTakeOfferDate(takeOfferDate)
@ -552,9 +599,9 @@ public abstract class Trade implements Tradable, Model {
.setUid(uid);
Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId);
Optional.ofNullable(depositTxId).ifPresent(builder::setDepositTxId);
Optional.ofNullable(takerDepositTxId).ifPresent(builder::setTakerDepositTxId);
Optional.ofNullable(makerDepositTxId).ifPresent(builder::setMakerDepositTxId);
Optional.ofNullable(payoutTxId).ifPresent(builder::setPayoutTxId);
Optional.ofNullable(tradingPeerNodeAddress).ifPresent(e -> builder.setTradingPeerNodeAddress(tradingPeerNodeAddress.toProtoMessage()));
Optional.ofNullable(contract).ifPresent(e -> builder.setContract(contract.toProtoMessage()));
Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson);
Optional.ofNullable(contractHash).ifPresent(e -> builder.setContractHash(ByteString.copyFrom(contractHash)));
@ -575,7 +622,10 @@ public abstract class Trade implements Tradable, Model {
Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes)));
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.name()));
Optional.ofNullable(makerNodeAddress).ifPresent(e -> builder.setMakerNodeAddress(makerNodeAddress.toProtoMessage()));
Optional.ofNullable(makerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage()));
Optional.ofNullable(takerNodeAddress).ifPresent(e -> builder.setTakerNodeAddress(takerNodeAddress.toProtoMessage()));
Optional.ofNullable(takerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(takerPubKeyRing.toProtoMessage()));
return builder.build();
}
@ -585,7 +635,8 @@ public abstract class Trade implements Tradable, Model {
trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState()));
trade.setTradePeriodState(TradePeriodState.fromProto(proto.getTradePeriodState()));
trade.setTakerFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerFeeTxId()));
trade.setDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getDepositTxId()));
trade.setMakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getMakerDepositTxId()));
trade.setTakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerDepositTxId()));
trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId()));
trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null);
trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
@ -614,6 +665,10 @@ public abstract class Trade implements Tradable, Model {
persistedAssetTxProofResult = null;
}
trade.setAssetTxProofResult(persistedAssetTxProofResult);
trade.setMakerNodeAddress(NodeAddress.fromProto(proto.getMakerNodeAddress()));
trade.setMakerPubKeyRing(proto.hasMakerPubKeyRing() ? PubKeyRing.fromProto(proto.getMakerPubKeyRing()) : null);
trade.setTakerNodeAddress(NodeAddress.fromProto(proto.getTakerNodeAddress()));
trade.setTakerPubKeyRing(proto.hasTakerPubKeyRing() ? PubKeyRing.fromProto(proto.getTakerPubKeyRing()) : null);
trade.chatMessages.addAll(proto.getChatMessageList().stream()
.map(ChatMessage::fromPayloadProto)
@ -647,24 +702,72 @@ public abstract class Trade implements Tradable, Model {
// API
///////////////////////////////////////////////////////////////////////////////////////////
// The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet.
void updateDepositTxFromWallet() {
if (getDepositTx() != null)
applyDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getTxId()));
public void setTradingPeerNodeAddress(NodeAddress peerAddress) {
if (this instanceof MakerTrade) takerNodeAddress = peerAddress;
else if (this instanceof TakerTrade) makerNodeAddress = peerAddress;
else throw new RuntimeException("Must be maker or taker to set peer address");
}
public void applyDepositTx(Transaction tx) {
this.depositTx = tx;
depositTxId = depositTx.getTxId().toString();
setupConfidenceListener();
public NodeAddress getTradingPeerNodeAddress() {
if (this instanceof MakerTrade) return takerNodeAddress;
else if (this instanceof TakerTrade) return makerNodeAddress;
else if (this instanceof ArbitratorTrade) return null;
else throw new RuntimeException("Unknown trade type: " + this.getClass().getName());
}
public void setTradingPeerPubKeyRing(PubKeyRing peerPubKeyRing) {
if (this instanceof MakerTrade) takerPubKeyRing = peerPubKeyRing;
else if (this instanceof TakerTrade) makerPubKeyRing = peerPubKeyRing;
else throw new RuntimeException("Must be maker or taker to set peer address");
}
public PubKeyRing getTradingPeerPubKeyRing() {
if (this instanceof MakerTrade) return takerPubKeyRing;
else if (this instanceof TakerTrade) return makerPubKeyRing;
else if (this instanceof ArbitratorTrade) return null;
else throw new RuntimeException("Unknown trade type: " + this.getClass().getName());
}
// The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet.
void updateDepositTxFromWallet() {
if (getMakerDepositTx() != null && getTakerDepositTx() != null) {
System.out.println(processModel.getProvider().getXmrWalletService());
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(getId());
applyDepositTxs(multisigWallet.getTx(getMakerDepositTxId()), multisigWallet.getTx(getTakerDepositTxId()));
}
}
public void applyDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) {
this.makerDepositTx = makerDepositTx;
this.takerDepositTx = takerDepositTx;
makerDepositTxId = makerDepositTx.getHash();
takerDepositTxId = takerDepositTx.getHash();
//setupConfirmationListener(); // TODO (woodser): listening disabled here, using SetupDepositTxsListener in buyer and seller
if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations
}
}
@Nullable
public Transaction getDepositTx() {
if (depositTx == null) {
depositTx = depositTxId != null ? btcWalletService.getTransaction(depositTxId) : null;
}
return depositTx;
public MoneroTxWallet getTakerDepositTx() {
try {
if (takerDepositTx == null) takerDepositTx = takerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(takerDepositTxId) : null;
return takerDepositTx;
} catch (MoneroError e) {
log.error("Wallet is missing taker deposit tx " + takerDepositTxId);
return null;
}
}
@Nullable
public MoneroTxWallet getMakerDepositTx() {
try {
if (makerDepositTx == null) makerDepositTx = makerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(makerDepositTxId) : null;
return makerDepositTx;
} catch (MoneroError e) {
log.error("Wallet is missing maker deposit tx " + makerDepositTxId);
return null;
}
}
public void applyDelayedPayoutTx(Transaction delayedPayoutTx) {
@ -676,31 +779,31 @@ public abstract class Trade implements Tradable, Model {
this.delayedPayoutTxBytes = delayedPayoutTxBytes;
}
@Nullable
public Transaction getDelayedPayoutTx() {
return getDelayedPayoutTx(processModel.getBtcWalletService());
}
// If called from a not initialized trade (or a closed or failed trade)
// we need to pass the btcWalletService
@Nullable
public Transaction getDelayedPayoutTx(BtcWalletService btcWalletService) {
if (delayedPayoutTx == null) {
if (btcWalletService == null) {
log.warn("btcWalletService is null. You might call that method before the tradeManager has " +
"initialized all trades");
return null;
}
if (delayedPayoutTxBytes == null) {
log.warn("delayedPayoutTxBytes are null");
return null;
}
delayedPayoutTx = btcWalletService.getTxFromSerializedTx(delayedPayoutTxBytes);
}
return delayedPayoutTx;
}
// @Nullable
// public Transaction getDelayedPayoutTx() {
// return getDelayedPayoutTx(processModel.getBtcWalletService());
// }
//
// // If called from a not initialized trade (or a closed or failed trade)
// // we need to pass the xmrWalletService
// @Nullable
// public Transaction getDelayedPayoutTx(XmrWalletService xmrWalletService) {
// if (delayedPayoutTx == null) {
// if (xmrWalletService == null) {
// log.warn("xmrWalletService is null. You might call that method before the tradeManager has " +
// "initialized all trades");
// return null;
// }
//
// if (delayedPayoutTxBytes == null) {
// log.warn("delayedPayoutTxBytes are null");
// return null;
// }
//
// delayedPayoutTx = xmrWalletService.getTxFromSerializedTx(delayedPayoutTxBytes);
// }
// return delayedPayoutTx;
// }
public void addAndPersistChatMessage(ChatMessage chatMessage) {
if (!chatMessages.contains(chatMessage)) {
@ -737,6 +840,26 @@ public abstract class Trade implements Tradable, Model {
public abstract boolean confirmPermitted();
///////////////////////////////////////////////////////////////////////////////////////////
// Listeners
///////////////////////////////////////////////////////////////////////////////////////////
public void addTradeMessageListener(TradeMessageListener listener) {
tradeMessageListeners.add(listener);
}
public void removeTradeMessageListener(TradeMessageListener listener) {
if (!tradeMessageListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered");
}
// notified from TradeProtocol of verified messages
public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) {
for (TradeMessageListener listener : new ArrayList<TradeMessageListener>(tradeMessageListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
listener.onVerifiedTradeMessage(message, sender);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
@ -786,13 +909,6 @@ public abstract class Trade implements Tradable, Model {
tradePeriodStateProperty.set(tradePeriodState);
}
public void setTradingPeerNodeAddress(NodeAddress tradingPeerNodeAddress) {
if (tradingPeerNodeAddress == null)
log.error("tradingPeerAddress=null");
else
this.tradingPeerNodeAddress = tradingPeerNodeAddress;
}
public void setTradeAmount(Coin tradeAmount) {
this.tradeAmount = tradeAmount;
tradeAmountAsLong = tradeAmount.value;
@ -800,9 +916,9 @@ public abstract class Trade implements Tradable, Model {
getTradeVolumeProperty().set(getTradeVolume());
}
public void setPayoutTx(Transaction payoutTx) {
public void setPayoutTx(MoneroTxWallet payoutTx) {
this.payoutTx = payoutTx;
payoutTxId = payoutTx.getTxId().toString();
payoutTxId = payoutTx.getHash();
}
public void setErrorMessage(String errorMessage) {
@ -863,14 +979,19 @@ public abstract class Trade implements Tradable, Model {
private long getTradeStartTime() {
long now = System.currentTimeMillis();
long startTime;
Transaction depositTx = getDepositTx();
if (depositTx != null && getTakeOfferDate() != null) {
if (depositTx.getConfidence().getDepthInBlocks() > 0) {
final MoneroTxWallet takerDepositTx = getTakerDepositTx();
final MoneroTxWallet makerDepositTx = getMakerDepositTx();
if (makerDepositTx != null && takerDepositTx != null && getTakeOfferDate() != null) {
if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
final long tradeTime = getTakeOfferDate().getTime();
// Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime()
long blockTime = depositTx.getIncludedInBestChainAt() != null
? depositTx.getIncludedInBestChainAt().getTime()
: depositTx.getUpdateTime().getTime();
long maxHeight = Math.max(makerDepositTx.getHeight(), takerDepositTx.getHeight());
MoneroDaemon daemonRpc = new MoneroDaemonRpc("http://localhost:38081", "superuser", "abctesting123"); // TODO (woodser): move to common config
long blockTime = daemonRpc.getBlockByHeight(maxHeight).getTimestamp();
// if (depositTx.getConfidence().getDepthInBlocks() > 0) {
// final long tradeTime = getTakeOfferDate().getTime();
// // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime()
// long blockTime = depositTx.getIncludedInBestChainAt() != null ? depositTx.getIncludedInBestChainAt().getTime() : depositTx.getUpdateTime().getTime();
// If block date is in future (Date in Bitcoin blocks can be off by +/- 2 hours) we use our current date.
// If block date is earlier than our trade date we use our trade date.
if (blockTime > now)
@ -881,8 +1002,7 @@ public abstract class Trade implements Tradable, Model {
log.debug("We set the start for the trade period to {}. Trade started at: {}. Block got mined at: {}",
new Date(startTime), new Date(tradeTime), new Date(blockTime));
} else {
log.debug("depositTx not confirmed yet. We don't start counting remaining trade period yet. txId={}",
depositTx.getTxId().toString());
log.debug("depositTx not confirmed yet. We don't start counting remaining trade period yet. makerTxId={}, takerTxId={}", makerDepositTx.getHash(), takerDepositTx.getHash());
startTime = now;
}
} else {
@ -1020,9 +1140,9 @@ public abstract class Trade implements Tradable, Model {
}
@Nullable
public Transaction getPayoutTx() {
public MoneroTxWallet getPayoutTx() {
if (payoutTx == null)
payoutTx = payoutTxId != null ? btcWalletService.getTransaction(payoutTxId) : null;
payoutTx = payoutTxId != null ? xmrWalletService.getWallet().getTx(payoutTxId) : null;
return payoutTx;
}
@ -1038,8 +1158,8 @@ public abstract class Trade implements Tradable, Model {
public boolean isTxChainInvalid() {
return offer.getOfferFeePaymentTxId() == null ||
getTakerFeeTxId() == null ||
getDepositTxId() == null ||
getDepositTx() == null ||
getMakerDepositTxId() == null ||
getTakerDepositTxId() == null ||
getDelayedPayoutTxBytes() == null;
}
@ -1076,31 +1196,31 @@ public abstract class Trade implements Tradable, Model {
return tradeVolumeProperty;
}
private void setupConfidenceListener() {
if (getDepositTx() != null) {
TransactionConfidence transactionConfidence = getDepositTx().getConfidence();
if (transactionConfidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
setConfirmedState();
} else {
ListenableFuture<TransactionConfidence> future = transactionConfidence.getDepthFuture(1);
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(TransactionConfidence result) {
setConfirmedState();
}
@Override
public void onFailure(@NotNull Throwable t) {
t.printStackTrace();
log.error(t.getMessage());
throw new RuntimeException(t);
}
}, MoreExecutors.directExecutor());
}
} else {
log.error("depositTx == null. That must not happen.");
}
}
// private void setupConfidenceListener() {
// if (getDepositTx() != null) {
// TransactionConfidence transactionConfidence = getDepositTx().getConfidence();
// if (transactionConfidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
// setConfirmedState();
// } else {
// ListenableFuture<TransactionConfidence> future = transactionConfidence.getDepthFuture(1);
// Futures.addCallback(future, new FutureCallback<>() {
// @Override
// public void onSuccess(TransactionConfidence result) {
// setConfirmedState();
// }
//
// @Override
// public void onFailure(@NotNull Throwable t) {
// t.printStackTrace();
// log.error(t.getMessage());
// throw new RuntimeException(t);
// }
// }, MoreExecutors.directExecutor());
// }
// } else {
// log.error("depositTx == null. That must not happen.");
// }
// }
private void setConfirmedState() {
// we only apply the state if we are not already further in the process
@ -1108,7 +1228,7 @@ public abstract class Trade implements Tradable, Model {
// As setState is called here from the trade itself we cannot trigger a requestPersistence call.
// But as we get setupConfidenceListener called at startup anyway there is no issue if it would not be
// persisted in case the shutdown routine did not persist the trade.
setState(State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN);
setState(State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN); // TODO (woodser): for xmr this means deposit txs have unlocked after 10 confirmations
}
}
@ -1116,17 +1236,15 @@ public abstract class Trade implements Tradable, Model {
public String toString() {
return "Trade{" +
"\n offer=" + offer +
",\n isCurrencyForTakerFeeBtc=" + isCurrencyForTakerFeeBtc +
",\n txFeeAsLong=" + txFeeAsLong +
",\n takerFeeAsLong=" + takerFeeAsLong +
",\n takeOfferDate=" + takeOfferDate +
",\n processModel=" + processModel +
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
",\n depositTxId='" + depositTxId + '\'' +
",\n takerDepositTxId='" + takerDepositTxId + '\'' +
",\n payoutTxId='" + payoutTxId + '\'' +
",\n tradeAmountAsLong=" + tradeAmountAsLong +
",\n tradePrice=" + tradePrice +
",\n tradingPeerNodeAddress=" + tradingPeerNodeAddress +
",\n state=" + state +
",\n disputeState=" + disputeState +
",\n tradePeriodState=" + tradePeriodState +
@ -1135,9 +1253,7 @@ public abstract class Trade implements Tradable, Model {
",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
",\n takerContractSignature='" + takerContractSignature + '\'' +
",\n makerContractSignature='" + makerContractSignature + '\'' +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
@ -1148,13 +1264,13 @@ public abstract class Trade implements Tradable, Model {
",\n chatMessages=" + chatMessages +
",\n txFee=" + txFee +
",\n takerFee=" + takerFee +
",\n btcWalletService=" + btcWalletService +
",\n xmrWalletService=" + xmrWalletService +
",\n stateProperty=" + stateProperty +
",\n statePhaseProperty=" + statePhaseProperty +
",\n disputeStateProperty=" + disputeStateProperty +
",\n tradePeriodStateProperty=" + tradePeriodStateProperty +
",\n errorMessageProperty=" + errorMessageProperty +
",\n depositTx=" + depositTx +
",\n depositTx=" + takerDepositTx +
",\n delayedPayoutTx=" + delayedPayoutTx +
",\n payoutTx=" + payoutTx +
",\n tradeAmount=" + tradeAmount +
@ -1168,6 +1284,12 @@ public abstract class Trade implements Tradable, Model {
",\n refundAgentPubKeyRing=" + refundAgentPubKeyRing +
",\n refundResultState=" + refundResultState +
",\n refundResultStateProperty=" + refundResultStateProperty +
",\n makerNodeAddress=" + makerNodeAddress +
",\n makerPubKeyRing=" + makerPubKeyRing +
",\n takerNodeAddress=" + takerNodeAddress +
",\n takerPubKeyRing=" + takerPubKeyRing +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
"\n}";
}
}

View File

@ -356,24 +356,25 @@ public class TradeDataValidation {
}
public static void validateDepositInputs(Trade trade) throws InvalidTxException {
// assumption: deposit tx always has 2 inputs, the maker and taker
if (trade == null || trade.getDepositTx() == null || trade.getDepositTx().getInputs().size() != 2) {
throw new InvalidTxException("Deposit transaction is null or has unexpected input count");
}
Transaction depositTx = trade.getDepositTx();
String txIdInput0 = depositTx.getInput(0).getOutpoint().getHash().toString();
String txIdInput1 = depositTx.getInput(1).getOutpoint().getHash().toString();
String contractMakerTxId = trade.getContract().getOfferPayload().getOfferFeePaymentTxId();
String contractTakerTxId = trade.getContract().getTakerFeeTxID();
boolean makerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput0) && contractTakerTxId.equalsIgnoreCase(txIdInput1);
boolean takerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput1) && contractTakerTxId.equalsIgnoreCase(txIdInput0);
if (!makerFirstMatch && !takerFirstMatch) {
String errMsg = "Maker/Taker txId in contract does not match deposit tx input";
log.error(errMsg +
"\nContract Maker tx=" + contractMakerTxId + " Contract Taker tx=" + contractTakerTxId +
"\nDeposit Input0=" + txIdInput0 + " Deposit Input1=" + txIdInput1);
throw new InvalidTxException(errMsg);
}
throw new RuntimeException("TradeDataValidation.validateDepositInputs() needs updated for XMR");
// // assumption: deposit tx always has 2 inputs, the maker and taker
// if (trade == null || trade.getDepositTx() == null || trade.getDepositTx().getInputs().size() != 2) {
// throw new InvalidTxException("Deposit transaction is null or has unexpected input count");
// }
// Transaction depositTx = trade.getDepositTx();
// String txIdInput0 = depositTx.getInput(0).getOutpoint().getHash().toString();
// String txIdInput1 = depositTx.getInput(1).getOutpoint().getHash().toString();
// String contractMakerTxId = trade.getContract().getOfferPayload().getOfferFeePaymentTxId();
// String contractTakerTxId = trade.getContract().getTakerFeeTxID();
// boolean makerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput0) && contractTakerTxId.equalsIgnoreCase(txIdInput1);
// boolean takerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput1) && contractTakerTxId.equalsIgnoreCase(txIdInput0);
// if (!makerFirstMatch && !takerFirstMatch) {
// String errMsg = "Maker/Taker txId in contract does not match deposit tx input";
// log.error(errMsg +
// "\nContract Maker tx=" + contractMakerTxId + " Contract Taker tx=" + contractTakerTxId +
// "\nDeposit Input0=" + txIdInput0 + " Deposit Input1=" + txIdInput1);
// throw new InvalidTxException(errMsg);
// }
}

View File

@ -18,11 +18,12 @@
package bisq.core.trade;
import bisq.core.btc.exceptions.AddressEntryException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
@ -33,7 +34,14 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.handlers.TradeResultHandler;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.UpdateMultisigRequest;
import bisq.core.trade.protocol.ArbitratorProtocol;
import bisq.core.trade.protocol.MakerProtocol;
import bisq.core.trade.protocol.ProcessModel;
import bisq.core.trade.protocol.ProcessModelServiceProvider;
@ -65,8 +73,6 @@ import bisq.common.proto.persistable.PersistedDataHost;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import javax.inject.Inject;
import javax.inject.Named;
@ -107,14 +113,19 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.model.MoneroTxWallet;
public class TradeManager implements PersistedDataHost, DecryptedDirectMessageListener {
private static final Logger log = LoggerFactory.getLogger(TradeManager.class);
private final User user;
@Getter
private final KeyRing keyRing;
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final BsqWalletService bsqWalletService;
private final OfferBookService offerBookService;
private final OpenOfferManager openOfferManager;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
@ -151,8 +162,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
@Inject
public TradeManager(User user,
KeyRing keyRing,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
OfferBookService offerBookService,
OpenOfferManager openOfferManager,
ClosedTradableManager closedTradableManager,
FailedTradesManager failedTradesManager,
@ -170,8 +182,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
@Named(Config.ALLOW_FAULTY_DELAYED_TXS) boolean allowFaultyDelayedTxs) {
this.user = user;
this.keyRing = keyRing;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.bsqWalletService = bsqWalletService;
this.offerBookService = offerBookService;
this.openOfferManager = openOfferManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
@ -222,75 +235,22 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
public void onDirectMessage(DecryptedMessageWithPubKey message, NodeAddress peer) {
NetworkEnvelope networkEnvelope = message.getNetworkEnvelope();
if (networkEnvelope instanceof InputsForDepositTxRequest) {
handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope);
//handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); // ignore bisq requests
} else if (networkEnvelope instanceof InitTradeRequest) {
handleInitTradeRequest((InitTradeRequest) networkEnvelope, peer);
} else if (networkEnvelope instanceof InitMultisigMessage) {
handleMultisigMessage((InitMultisigMessage) networkEnvelope, peer);
} else if (networkEnvelope instanceof MakerReadyToFundMultisigRequest) {
handleMakerReadyToFundMultisigRequest((MakerReadyToFundMultisigRequest) networkEnvelope, peer);
} else if (networkEnvelope instanceof MakerReadyToFundMultisigResponse) {
handleMakerReadyToFundMultisigResponse((MakerReadyToFundMultisigResponse) networkEnvelope, peer);
} else if (networkEnvelope instanceof DepositTxMessage) {
handleDepositTxMessage((DepositTxMessage) networkEnvelope, peer);
} else if (networkEnvelope instanceof UpdateMultisigRequest) {
handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer);
}
}
// The maker received a TakeOfferRequest
private void handleTakeOfferRequest(NodeAddress peer, InputsForDepositTxRequest inputsForDepositTxRequest) {
log.info("Received inputsForDepositTxRequest from {} with tradeId {} and uid {}",
peer, inputsForDepositTxRequest.getTradeId(), inputsForDepositTxRequest.getUid());
try {
Validator.nonEmptyStringOf(inputsForDepositTxRequest.getTradeId());
} catch (Throwable t) {
log.warn("Invalid inputsForDepositTxRequest " + inputsForDepositTxRequest.toString());
return;
}
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(inputsForDepositTxRequest.getTradeId());
if (!openOfferOptional.isPresent()) {
return;
}
OpenOffer openOffer = openOfferOptional.get();
if (openOffer.getState() != OpenOffer.State.AVAILABLE) {
return;
}
Offer offer = openOffer.getOffer();
openOfferManager.reserveOpenOffer(openOffer);
Trade trade;
if (offer.isBuyOffer()) {
trade = new BuyerAsMakerTrade(offer,
Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
openOffer.getArbitratorNodeAddress(),
openOffer.getMediatorNodeAddress(),
openOffer.getRefundAgentNodeAddress(),
btcWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
} else {
trade = new SellerAsMakerTrade(offer,
Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
openOffer.getArbitratorNodeAddress(),
openOffer.getMediatorNodeAddress(),
openOffer.getRefundAgentNodeAddress(),
btcWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
}
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
if (prev != null) {
log.error("We had already an entry with uid {}", trade.getUid());
}
tradableList.add(trade);
initTradeAndProtocol(trade, tradeProtocol);
((MakerProtocol) tradeProtocol).handleTakeOfferRequest(inputsForDepositTxRequest, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
requestPersistence();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle
@ -311,11 +271,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
getObservableList().addListener((ListChangeListener<Trade>) change -> onTradesChanged());
onTradesChanged();
btcWalletService.getAddressEntriesForAvailableBalanceStream()
xmrWalletService.getAddressEntriesForAvailableBalanceStream()
.filter(addressEntry -> addressEntry.getOfferId() != null)
.forEach(addressEntry -> {
log.warn("Swapping pending OFFER_FUNDING entries at startup. offerId={}", addressEntry.getOfferId());
btcWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), AddressEntry.Context.OFFER_FUNDING);
xmrWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), XmrAddressEntry.Context.OFFER_FUNDING);
});
}
@ -367,6 +327,220 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
persistenceManager.requestPersistence();
}
private void handleInitTradeRequest(InitTradeRequest initTradeRequest, NodeAddress peer) {
log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", peer, initTradeRequest.getTradeId(), initTradeRequest.getUid());
try {
Validator.nonEmptyStringOf(initTradeRequest.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitTradeRequest message " + initTradeRequest.toString());
return;
}
System.out.println("RECEIVED INIT REQUEST INFO");
System.out.println("Sender peer node address: " + initTradeRequest.getSenderNodeAddress());
System.out.println("Maker node address: " + initTradeRequest.getMakerNodeAddress());
System.out.println("Taker node adddress: " + initTradeRequest.getTakerNodeAddress());
System.out.println("Arbitrator node address: " + initTradeRequest.getArbitratorNodeAddress());
// handle request as arbitrator
boolean isArbitrator = initTradeRequest.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress());
if (isArbitrator) {
// get offer associated with trade
Offer offer = null;
for (Offer anOffer : offerBookService.getOffers()) {
if (anOffer.getId().equals(initTradeRequest.getTradeId())) {
offer = anOffer;
}
}
if (offer == null) throw new RuntimeException("No offer on the books with trade id: " + initTradeRequest.getTradeId()); // TODO (woodser): proper error handling
Trade trade;
Optional<Trade> tradeOptional = getTradeById(offer.getId());
if (!tradeOptional.isPresent()) {
trade = new ArbitratorTrade(offer,
Coin.valueOf(initTradeRequest.getTradeAmount()),
Coin.valueOf(initTradeRequest.getTxFee()),
Coin.valueOf(initTradeRequest.getTradeFee()),
initTradeRequest.getTradePrice(),
initTradeRequest.getMakerNodeAddress(),
initTradeRequest.getTakerNodeAddress(),
initTradeRequest.getArbitratorNodeAddress(),
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
initTradeAndProtocol(trade, getTradeProtocol(trade));
tradableList.add(trade);
} else {
trade = tradeOptional.get();
}
// TODO (woodser): do this for arbitrator?
// TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
// TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
// if (prev != null) {
// log.error("We had already an entry with uid {}", trade.getUid());
// }
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler?
});
requestPersistence();
}
// handle request as maker
else {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(initTradeRequest.getTradeId());
if (!openOfferOptional.isPresent()) {
return;
}
OpenOffer openOffer = openOfferOptional.get();
if (openOffer.getState() != OpenOffer.State.AVAILABLE) {
return;
}
Offer offer = openOffer.getOffer();
openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator?
Trade trade;
if (offer.isBuyOffer())
trade = new BuyerAsMakerTrade(offer,
Coin.valueOf(initTradeRequest.getTxFee()),
Coin.valueOf(initTradeRequest.getTradeFee()),
initTradeRequest.getMakerNodeAddress(),
initTradeRequest.getTakerNodeAddress(),
initTradeRequest.getArbitratorNodeAddress(),
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
else
trade = new SellerAsMakerTrade(offer,
Coin.valueOf(initTradeRequest.getTxFee()),
Coin.valueOf(initTradeRequest.getTradeFee()),
initTradeRequest.getMakerNodeAddress(),
initTradeRequest.getTakerNodeAddress(),
openOffer.getArbitratorNodeAddress(),
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
if (prev != null) {
log.error("We had already an entry with uid {}", trade.getUid());
}
tradableList.add(trade);
initTradeAndProtocol(trade, tradeProtocol);
((MakerProtocol) tradeProtocol).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
requestPersistence();
}
}
private void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest request, NodeAddress peer) {
log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
try {
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitTradeRequest message " + request.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
((MakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigRequest(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
}
private void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer) {
log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid());
try {
Validator.nonEmptyStringOf(response.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitTradeRequest message " + response.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(response.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
((TakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigResponse(response, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
}
private void handleMultisigMessage(InitMultisigMessage multisigMessage, NodeAddress peer) {
log.info("Received InitMultisigMessage from {} with tradeId {} and uid {}", peer, multisigMessage.getTradeId(), multisigMessage.getUid());
try {
Validator.nonEmptyStringOf(multisigMessage.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitMultisigMessage message " + multisigMessage.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(multisigMessage.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + multisigMessage.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
getTradeProtocol(trade).handleMultisigMessage(multisigMessage, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
}
private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) {
log.info("Received UpdateMultisigRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
try {
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid UpdateMultisigRequest message " + request.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
getTradeProtocol(trade).handleUpdateMultisigRequest(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
}
private void handleDepositTxMessage(DepositTxMessage request, NodeAddress peer) {
log.info("Received DepositTxMessage from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
try {
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitTradeRequest message " + request.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
getTradeProtocol(trade).handleDepositTxMessage(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Take offer
@ -376,14 +550,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
boolean isTakerApiUser,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
if (btcWalletService.isUnconfirmedTransactionsLimitHit() ||
bsqWalletService.isUnconfirmedTransactionsLimitHit()) {
String errorMessage = Res.get("shared.unconfirmedTransactionsLimitReached");
errorMessageHandler.handleErrorMessage(errorMessage);
log.warn(errorMessage);
return;
}
offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser), resultHandler, errorMessageHandler);
}
@ -391,7 +557,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
public void onTakeOffer(Coin amount,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
Coin fundsNeededForTrade,
Offer offer,
@ -413,13 +578,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
amount,
txFee,
takerFee,
isCurrencyForTakerFeeBtc,
tradePrice,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
model.getSelectedMediator(),
model.getSelectedRefundAgent(),
btcWalletService,
P2PService.getMyNodeAddress(), // TODO (woodser): correct taker node address?
model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
} else {
@ -427,13 +590,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
amount,
txFee,
takerFee,
isCurrencyForTakerFeeBtc,
tradePrice,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
model.getSelectedMediator(),
model.getSelectedRefundAgent(),
btcWalletService,
P2PService.getMyNodeAddress(),
model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
}
@ -483,43 +644,42 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
///////////////////////////////////////////////////////////////////////////////////////////
public void onWithdrawRequest(String toAddress,
Coin amount,
Coin fee,
KeyParameter aesKey,
Trade trade,
@Nullable String memo,
ResultHandler resultHandler,
FaultHandler faultHandler) {
String fromAddress = btcWalletService.getOrCreateAddressEntry(trade.getId(),
AddressEntry.Context.TRADE_PAYOUT).getAddressString();
FutureCallback<Transaction> callback = new FutureCallback<>() {
@Override
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
if (transaction != null) {
log.debug("onWithdraw onSuccess tx ID:" + transaction.getTxId().toString());
onTradeCompleted(trade);
trade.setState(Trade.State.WITHDRAW_COMPLETED);
getTradeProtocol(trade).onWithdrawCompleted();
requestPersistence();
resultHandler.handleResult();
}
}
@Override
public void onFailure(@NotNull Throwable t) {
t.printStackTrace();
log.error(t.getMessage());
faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t);
}
};
try {
btcWalletService.sendFunds(fromAddress, toAddress, amount, fee, aesKey,
AddressEntry.Context.TRADE_PAYOUT, memo, callback);
} catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
e.printStackTrace();
log.error(e.getMessage());
faultHandler.handleFault("An exception occurred at requestWithdraw.", e);
Coin amount,
Coin fee,
KeyParameter aesKey,
Trade trade,
@Nullable String memo,
ResultHandler resultHandler,
FaultHandler faultHandler) {
int fromAccountIdx = xmrWalletService.getOrCreateAddressEntry(trade.getId(),
XmrAddressEntry.Context.TRADE_PAYOUT).getAccountIndex();
FutureCallback<MoneroTxWallet> callback = new FutureCallback<MoneroTxWallet>() {
@Override
public void onSuccess(@javax.annotation.Nullable MoneroTxWallet transaction) {
if (transaction != null) {
log.debug("onWithdraw onSuccess tx ID:" + transaction.getHash());
onTradeCompleted(trade);
trade.setState(Trade.State.WITHDRAW_COMPLETED);
getTradeProtocol(trade).onWithdrawCompleted();
requestPersistence();
resultHandler.handleResult();
}
}
@Override
public void onFailure(@NotNull Throwable t) {
t.printStackTrace();
log.error(t.getMessage());
faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t);
}
};
try {
xmrWalletService.sendFunds(fromAccountIdx, toAddress, amount, XmrAddressEntry.Context.TRADE_PAYOUT, callback);
} catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
e.printStackTrace();
log.error(e.getMessage());
faultHandler.handleFault("An exception occurred at requestWithdraw.", e);
}
}
// If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
@ -528,7 +688,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
closedTradableManager.add(trade);
// TODO The address entry should have been removed already. Check and if its the case remove that.
btcWalletService.resetAddressEntriesForPendingTrade(trade.getId());
xmrWalletService.resetAddressEntriesForPendingTrade(trade.getId());
requestPersistence();
}
@ -543,12 +703,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
Trade trade = tradeOptional.get();
trade.setDisputeState(disputeState);
onTradeCompleted(trade);
btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT);
xmrWalletService.swapTradeEntryToAvailableEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT);
requestPersistence();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Trade period state
///////////////////////////////////////////////////////////////////////////////////////////
@ -616,7 +775,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
.map(Trade::getId)
.collect(Collectors.toSet());
tradesIdSet.addAll(failedTradesManager.getTradesStreamWithFundsLockedIn()
.filter(trade -> trade.getDepositTx() != null)
.filter(trade -> trade.getMakerDepositTx() != null || trade.getTakerDepositTx() != null)
.map(trade -> {
log.warn("We found a failed trade with locked up funds. " +
"That should never happen. trade ID=" + trade.getId());
@ -625,19 +784,30 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
.collect(Collectors.toSet()));
tradesIdSet.addAll(closedTradableManager.getTradesStreamWithFundsLockedIn()
.map(trade -> {
Transaction depositTx = trade.getDepositTx();
if (depositTx != null) {
TransactionConfidence confidence = btcWalletService.getConfidenceForTxId(depositTx.getTxId().toString());
if (confidence != null && confidence.getConfidenceType() != TransactionConfidence.ConfidenceType.BUILDING) {
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId())));
} else {
log.warn("We found a closed trade with locked up funds. " +
"That should never happen. trade ID=" + trade.getId());
}
} else {
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
}
return trade.getId();
MoneroTxWallet makerDepositTx = trade.getMakerDepositTx();
if (makerDepositTx != null) {
if (makerDepositTx.isLocked()) {
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId()))); // TODO (woodser): rename to closedTradeWithLockedDepositTx
} else {
log.warn("We found a closed trade with locked up funds. " +
"That should never happen. trade ID=" + trade.getId());
}
} else {
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
}
MoneroTxWallet takerDepositTx = trade.getTakerDepositTx();
if (takerDepositTx != null) {
if (!takerDepositTx.isConfirmed()) {
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId())));
} else {
log.warn("We found a closed trade with locked up funds. " +
"That should never happen. trade ID=" + trade.getId());
}
} else {
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
}
return trade.getId();
})
.collect(Collectors.toSet()));
@ -671,10 +841,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
if (entries == null)
return false;
btcWalletService.recoverAddressEntry(trade.getId(), entries.first,
AddressEntry.Context.MULTI_SIG);
btcWalletService.recoverAddressEntry(trade.getId(), entries.second,
AddressEntry.Context.TRADE_PAYOUT);
xmrWalletService.recoverAddressEntry(trade.getId(), entries.first,
XmrAddressEntry.Context.MULTI_SIG);
xmrWalletService.recoverAddressEntry(trade.getId(), entries.second,
XmrAddressEntry.Context.TRADE_PAYOUT);
return true;
}

View File

@ -86,38 +86,39 @@ public class TradeUtil {
* @return Tuple2 tuple containing MULTI_SIG, TRADE_PAYOUT addresses for trade
*/
public Tuple2<String, String> getTradeAddresses(Trade trade) {
var contract = trade.getContract();
if (contract == null)
return null;
// Get multisig address
var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing());
var multiSigPubKey = isMyRoleBuyer
? contract.getBuyerMultiSigPubKey()
: contract.getSellerMultiSigPubKey();
if (multiSigPubKey == null)
return null;
var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey);
var multiSigAddress = btcWalletService.getAddressEntryListAsImmutableList().stream()
.filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString))
.findAny()
.orElse(null);
if (multiSigAddress == null)
return null;
// Get payout address
var payoutAddress = isMyRoleBuyer
? contract.getBuyerPayoutAddressString()
: contract.getSellerPayoutAddressString();
var payoutAddressEntry = btcWalletService.getAddressEntryListAsImmutableList().stream()
.filter(e -> Objects.equals(e.getAddressString(), payoutAddress))
.findAny()
.orElse(null);
if (payoutAddressEntry == null)
return null;
return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress);
throw new RuntimeException("TradeUtil.getTradeAddresses() not implemented for XMR");
// var contract = trade.getContract();
// if (contract == null)
// return null;
//
// // Get multisig address
// var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing());
// var multiSigPubKey = isMyRoleBuyer
// ? contract.getBuyerMultiSigPubKey()
// : contract.getSellerMultiSigPubKey();
// if (multiSigPubKey == null)
// return null;
//
// var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey);
// var multiSigAddress = btcWalletService.getAddressEntryListAsImmutableList().stream()
// .filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString))
// .findAny()
// .orElse(null);
// if (multiSigAddress == null)
// return null;
//
// // Get payout address
// var payoutAddress = isMyRoleBuyer
// ? contract.getBuyerPayoutAddressString()
// : contract.getSellerPayoutAddressString();
// var payoutAddressEntry = btcWalletService.getAddressEntryListAsImmutableList().stream()
// .filter(e -> Objects.equals(e.getAddressString(), payoutAddress))
// .findAny()
// .orElse(null);
// if (payoutAddressEntry == null)
// return null;
//
// return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress);
}
public long getRemainingTradeDuration(Trade trade) {

View File

@ -0,0 +1,81 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.common.crypto.KeyRing;
import bisq.common.util.Tuple2;
import java.util.Objects;
public class TradeUtils {
// Returns <MULTI_SIG, TRADE_PAYOUT> if both are AVAILABLE, otherwise null
static Tuple2<String, String> getAvailableAddresses(Trade trade, XmrWalletService xmrWalletService,
KeyRing keyRing) {
var addresses = getTradeAddresses(trade, xmrWalletService, keyRing);
if (addresses == null)
return null;
if (xmrWalletService.getAvailableAddressEntries().stream()
.noneMatch(e -> Objects.equals(e.getAddressString(), addresses.first)))
return null;
if (xmrWalletService.getAvailableAddressEntries().stream()
.noneMatch(e -> Objects.equals(e.getAddressString(), addresses.second)))
return null;
return new Tuple2<>(addresses.first, addresses.second);
}
// Returns <MULTI_SIG, TRADE_PAYOUT> addresses as strings if they're known by the wallet
public static Tuple2<String, String> getTradeAddresses(Trade trade, XmrWalletService xmrWalletService,
KeyRing keyRing) {
var contract = trade.getContract();
if (contract == null)
return null;
// TODO (woodser): xmr multisig does not use pub key
throw new RuntimeException("need to replace btc multisig pub key with xmr");
// Get multisig address
// var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing());
// var multiSigPubKey = isMyRoleBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
// if (multiSigPubKey == null)
// return null;
// var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey);
// var multiSigAddress = xmrWalletService.getAddressEntryListAsImmutableList().stream()
// .filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString))
// .findAny()
// .orElse(null);
// if (multiSigAddress == null)
// return null;
//
// // Get payout address
// var payoutAddress = isMyRoleBuyer ?
// contract.getBuyerPayoutAddressString() : contract.getSellerPayoutAddressString();
// var payoutAddressEntry = xmrWalletService.getAddressEntryListAsImmutableList().stream()
// .filter(e -> Objects.equals(e.getAddressString(), payoutAddress))
// .findAny()
// .orElse(null);
// if (payoutAddressEntry == null)
// return null;
//
// return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress);
}
}

View File

@ -17,8 +17,8 @@
package bisq.core.trade.failed;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.DumpDelayedPayoutTx;
@ -49,7 +49,7 @@ public class FailedTradesManager implements PersistedDataHost {
private final TradableList<Trade> failedTrades = new TradableList<>();
private final KeyRing keyRing;
private final PriceFeedService priceFeedService;
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final CleanupMailboxMessages cleanupMailboxMessages;
private final PersistenceManager<TradableList<Trade>> persistenceManager;
private final TradeUtil tradeUtil;
@ -60,14 +60,14 @@ public class FailedTradesManager implements PersistedDataHost {
@Inject
public FailedTradesManager(KeyRing keyRing,
PriceFeedService priceFeedService,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
PersistenceManager<TradableList<Trade>> persistenceManager,
TradeUtil tradeUtil,
CleanupMailboxMessages cleanupMailboxMessages,
DumpDelayedPayoutTx dumpDelayedPayoutTx) {
this.keyRing = keyRing;
this.priceFeedService = priceFeedService;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.cleanupMailboxMessages = cleanupMailboxMessages;
this.dumpDelayedPayoutTx = dumpDelayedPayoutTx;
this.persistenceManager = persistenceManager;
@ -140,8 +140,8 @@ public class FailedTradesManager implements PersistedDataHost {
return "Addresses not found";
}
StringBuilder blockingTrades = new StringBuilder();
for (var entry : btcWalletService.getAddressEntryListAsImmutableList()) {
if (entry.getContext() == AddressEntry.Context.AVAILABLE)
for (var entry : xmrWalletService.getAddressEntryListAsImmutableList()) {
if (entry.getContext() == XmrAddressEntry.Context.AVAILABLE)
continue;
if (entry.getAddressString() != null &&
(entry.getAddressString().equals(addresses.first) ||

View File

@ -21,9 +21,6 @@ import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import bisq.common.proto.ProtoUtil;
import bisq.common.util.Utilities;
import com.google.protobuf.ByteString;
import java.util.Optional;
@ -37,7 +34,7 @@ import javax.annotation.Nullable;
public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMessage {
private final String buyerPayoutAddress;
private final NodeAddress senderNodeAddress;
private final byte[] buyerSignature;
private final String buyerPayoutTxSigned;
@Nullable
private final String counterCurrencyTxId;
@ -49,14 +46,14 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
public CounterCurrencyTransferStartedMessage(String tradeId,
String buyerPayoutAddress,
NodeAddress senderNodeAddress,
byte[] buyerSignature,
String buyerPayoutTxSigned,
@Nullable String counterCurrencyTxId,
@Nullable String counterCurrencyExtraData,
String uid) {
this(tradeId,
buyerPayoutAddress,
senderNodeAddress,
buyerSignature,
buyerPayoutTxSigned,
counterCurrencyTxId,
counterCurrencyExtraData,
uid,
@ -71,7 +68,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
private CounterCurrencyTransferStartedMessage(String tradeId,
String buyerPayoutAddress,
NodeAddress senderNodeAddress,
byte[] buyerSignature,
String buyerPayoutTxSigned,
@Nullable String counterCurrencyTxId,
@Nullable String counterCurrencyExtraData,
String uid,
@ -79,7 +76,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
super(messageVersion, tradeId, uid);
this.buyerPayoutAddress = buyerPayoutAddress;
this.senderNodeAddress = senderNodeAddress;
this.buyerSignature = buyerSignature;
this.buyerPayoutTxSigned = buyerPayoutTxSigned;
this.counterCurrencyTxId = counterCurrencyTxId;
this.counterCurrencyExtraData = counterCurrencyExtraData;
}
@ -90,7 +87,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
builder.setTradeId(tradeId)
.setBuyerPayoutAddress(buyerPayoutAddress)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setBuyerSignature(ByteString.copyFrom(buyerSignature))
.setBuyerPayoutTxSigned(buyerPayoutTxSigned)
.setUid(uid);
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
@ -104,7 +101,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
return new CounterCurrencyTransferStartedMessage(proto.getTradeId(),
proto.getBuyerPayoutAddress(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getBuyerSignature().toByteArray(),
proto.getBuyerPayoutTxSigned(),
ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyTxId()),
ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData()),
proto.getUid(),
@ -120,7 +117,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
",\n counterCurrencyTxId=" + counterCurrencyTxId +
",\n counterCurrencyExtraData=" + counterCurrencyExtraData +
",\n uid='" + uid + '\'' +
",\n buyerSignature=" + Utilities.bytesAsHexString(buyerSignature) +
",\n buyerPayoutTxSigned=" + buyerPayoutTxSigned +
"\n} " + super.toString();
}
}

View File

@ -21,70 +21,66 @@ import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import bisq.common.util.Utilities;
import bisq.common.proto.ProtoUtil;
import com.google.protobuf.ByteString;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Value;
import javax.annotation.Nullable;
// It is the last message in the take offer phase. We use MailboxMessage instead of DirectMessage to add more tolerance
// in case of network issues and as the message does not trigger further protocol execution.
@EqualsAndHashCode(callSuper = true)
@Value
public final class DepositTxMessage extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final byte[] depositTxWithoutWitnesses;
@Nullable
private final String tradeFeeTxId;
@Nullable
private final String depositTxId;
public DepositTxMessage(String uid,
String tradeId,
NodeAddress senderNodeAddress,
byte[] depositTxWithoutWitnesses) {
this(Version.getP2PMessageVersion(),
uid,
tradeId,
senderNodeAddress,
depositTxWithoutWitnesses);
String tradeFeeTxId,
String depositTxId) {
super(Version.getP2PMessageVersion(), tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.tradeFeeTxId = tradeFeeTxId;
this.depositTxId = depositTxId;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private DepositTxMessage(int messageVersion,
String uid,
String tradeId,
NodeAddress senderNodeAddress,
byte[] depositTxWithoutWitnesses) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.depositTxWithoutWitnesses = depositTxWithoutWitnesses;
}
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder()
.setDepositTxMessage(protobuf.DepositTxMessage.newBuilder()
.setUid(uid)
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setDepositTxWithoutWitnesses(ByteString.copyFrom(depositTxWithoutWitnesses)))
.build();
protobuf.DepositTxMessage.Builder builder = protobuf.DepositTxMessage.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setUid(uid);
Optional.ofNullable(tradeFeeTxId).ifPresent(e -> builder.setTradeFeeTxId(tradeFeeTxId));
Optional.ofNullable(depositTxId).ifPresent(e -> builder.setDepositTxId(depositTxId));
return getNetworkEnvelopeBuilder().setDepositTxMessage(builder).build();
}
public static DepositTxMessage fromProto(protobuf.DepositTxMessage proto, int messageVersion) {
return new DepositTxMessage(messageVersion,
proto.getUid(),
return new DepositTxMessage(proto.getUid(),
proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getDepositTxWithoutWitnesses().toByteArray());
ProtoUtil.stringOrNullFromProto(proto.getTradeFeeTxId()),
ProtoUtil.stringOrNullFromProto(proto.getDepositTxId()));
}
@Override
public String toString() {
return "DepositTxMessage{" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n depositTxWithoutWitnesses=" + Utilities.bytesAsHexString(depositTxWithoutWitnesses) +
",\n tradeFeeTxId=" + tradeFeeTxId +
",\n depositTxId=" + depositTxId +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,106 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Value;
import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public final class InitMultisigMessage extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final long currentDate;
@Nullable
private final String preparedMultisigHex;
@Nullable
private final String madeMultisigHex;
public InitMultisigMessage(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
int messageVersion,
long currentDate,
String preparedMultisigHex,
String madeMultisigHex) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.currentDate = currentDate;
this.preparedMultisigHex = preparedMultisigHex;
this.madeMultisigHex = madeMultisigHex;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.InitMultisigMessage.Builder builder = protobuf.InitMultisigMessage.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid);
Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setInitMultisigMessage(builder).build();
}
public static InitMultisigMessage fromProto(protobuf.InitMultisigMessage proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new InitMultisigMessage(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
messageVersion,
proto.getCurrentDate(),
ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()),
ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
}
@Override
public String toString() {
return "MultisigMessage {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +
",\n preparedMultisigHex='" + preparedMultisigHex +
",\n madeMultisigHex='" + madeMultisigHex +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,171 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
import bisq.common.util.Utilities;
import com.google.protobuf.ByteString;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Value;
import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public final class InitTradeRequest extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final long tradeAmount;
private final long tradePrice;
private final long txFee;
private final long tradeFee;
private final String payoutAddressString;
private final PaymentAccountPayload paymentAccountPayload;
private final PubKeyRing pubKeyRing;
private final String accountId;
@Nullable
private final String tradeFeeTxId;
private final NodeAddress arbitratorNodeAddress;
// added in v 0.6. can be null if we trade with an older peer
@Nullable
private final byte[] accountAgeWitnessSignatureOfOfferId;
private final long currentDate;
// added for XMR integration
private final NodeAddress takerNodeAddress;
private final NodeAddress makerNodeAddress;
public InitTradeRequest(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
long tradeAmount,
long tradePrice,
long txFee,
long tradeFee,
String payoutAddressString,
PaymentAccountPayload paymentAccountPayload,
String accountId,
String tradeFeeTxId,
String uid,
int messageVersion,
@Nullable byte[] accountAgeWitnessSignatureOfOfferId,
long currentDate,
NodeAddress takerNodeAddress,
NodeAddress makerNodeAddress,
NodeAddress arbitratorNodeAddress) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.paymentAccountPayload = paymentAccountPayload;
this.tradeAmount = tradeAmount;
this.tradePrice = tradePrice;
this.txFee = txFee;
this.tradeFee = tradeFee;
this.payoutAddressString = payoutAddressString;
this.accountId = accountId;
this.tradeFeeTxId = tradeFeeTxId;
this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId;
this.currentDate = currentDate;
this.takerNodeAddress = takerNodeAddress;
this.makerNodeAddress = makerNodeAddress;
this.arbitratorNodeAddress = arbitratorNodeAddress;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.InitTradeRequest.Builder builder = protobuf.InitTradeRequest.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setTakerNodeAddress(takerNodeAddress.toProtoMessage())
.setMakerNodeAddress(makerNodeAddress.toProtoMessage())
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
.setTradeAmount(tradeAmount)
.setTradePrice(tradePrice)
.setTxFee(txFee)
.setTradeFee(tradeFee)
.setPayoutAddressString(payoutAddressString)
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setPaymentAccountPayload((protobuf.PaymentAccountPayload) paymentAccountPayload.toProtoMessage())
.setAccountId(accountId)
.setUid(uid);
Optional.ofNullable(tradeFeeTxId).ifPresent(e -> builder.setTradeFeeTxId(tradeFeeTxId));
Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e)));
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setInitTradeRequest(builder).build();
}
public static InitTradeRequest fromProto(protobuf.InitTradeRequest proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new InitTradeRequest(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getTradeAmount(),
proto.getTradePrice(),
proto.getTxFee(),
proto.getTradeFee(),
proto.getPayoutAddressString(),
coreProtoResolver.fromProto(proto.getPaymentAccountPayload()),
proto.getAccountId(),
ProtoUtil.stringOrNullFromProto(proto.getTradeFeeTxId()),
proto.getUid(),
messageVersion,
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()),
proto.getCurrentDate(),
NodeAddress.fromProto(proto.getTakerNodeAddress()),
NodeAddress.fromProto(proto.getMakerNodeAddress()),
NodeAddress.fromProto(proto.getArbitratorNodeAddress()));
}
@Override
public String toString() {
return "InitTradeRequest{" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n tradeAmount=" + tradeAmount +
",\n tradePrice=" + tradePrice +
",\n txFee=" + txFee +
",\n takerFee=" + tradeFee +
",\n payoutAddressString='" + payoutAddressString + '\'' +
",\n pubKeyRing=" + pubKeyRing +
",\n paymentAccountPayload=" + paymentAccountPayload +
",\n paymentAccountPayload='" + accountId + '\'' +
",\n takerFeeTxId='" + tradeFeeTxId + '\'' +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) +
",\n currentDate=" + currentDate +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
import lombok.EqualsAndHashCode;
import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public final class MakerReadyToFundMultisigRequest extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
public MakerReadyToFundMultisigRequest(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
int messageVersion) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.MakerReadyToFundMultisigRequest.Builder builder = protobuf.MakerReadyToFundMultisigRequest.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid);
return getNetworkEnvelopeBuilder().setMakerReadyToFundMultisigRequest(builder).build();
}
public static MakerReadyToFundMultisigRequest fromProto(protobuf.MakerReadyToFundMultisigRequest proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new MakerReadyToFundMultisigRequest(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
messageVersion);
}
@Override
public String toString() {
return "MakerReadyToFundMultisigRequest{" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,118 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Value;
import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public class MakerReadyToFundMultisigResponse extends TradeMessage implements DirectMessage {
@Getter
private final boolean isMakerReadyToFundMultisig;
@Getter
@Nullable
private final String makerContractAsJson;
@Getter
@Nullable
private final String makerContractSignature;
@Getter
@Nullable
private final String makerPayoutAddressString;
@Getter
@Nullable
private final PaymentAccountPayload makerPaymentAccountPayload;
@Getter
@Nullable
private final String makerAccountId;
@Getter
private final long currentDate;
public MakerReadyToFundMultisigResponse(String tradeId,
boolean isMakerReadyToFundMultisig,
String uid,
int messageVersion,
String makerContractAsJson,
String makerContractSignature,
String makerPayoutAddressString,
PaymentAccountPayload makerPaymentAccountPayload,
String makerAccountId,
long currentDate) {
super(messageVersion, tradeId, uid);
this.isMakerReadyToFundMultisig = isMakerReadyToFundMultisig;
this.makerContractAsJson = makerContractAsJson;
this.makerContractSignature = makerContractSignature;
this.makerPayoutAddressString = makerPayoutAddressString;
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
this.makerAccountId = makerAccountId;
this.currentDate = currentDate;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.MakerReadyToFundMultisigResponse.Builder builder = protobuf.MakerReadyToFundMultisigResponse.newBuilder()
.setTradeId(tradeId)
.setIsMakerReadyToFundMultisig(isMakerReadyToFundMultisig)
.setCurrentDate(currentDate);
Optional.ofNullable(makerContractAsJson).ifPresent(e -> builder.setMakerContractAsJson(makerContractAsJson));
Optional.ofNullable(makerContractSignature).ifPresent(e -> builder.setMakerContractSignature(makerContractSignature));
Optional.ofNullable(makerPayoutAddressString).ifPresent(e -> builder.setMakerPayoutAddressString(makerPayoutAddressString));
Optional.ofNullable(makerPaymentAccountPayload).ifPresent(e -> builder.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage()));
Optional.ofNullable(makerAccountId).ifPresent(e -> builder.setMakerAccountId(makerAccountId));
return getNetworkEnvelopeBuilder().setMakerReadyToFundMultisigResponse(builder).build();
}
public static MakerReadyToFundMultisigResponse fromProto(protobuf.MakerReadyToFundMultisigResponse proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new MakerReadyToFundMultisigResponse(proto.getTradeId(),
proto.getIsMakerReadyToFundMultisig(),
proto.getUid(),
messageVersion,
proto.getMakerContractAsJson(),
proto.getMakerContractSignature(),
proto.getMakerPayoutAddressString(),
coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()),
proto.getMakerAccountId(),
proto.getCurrentDate());
}
@Override
public String toString() {
return "MakerReadyToFundMultisigResponse{" +
"\n isMakerReadyToFundMultisig=" + isMakerReadyToFundMultisig +
"\n} " + super.toString();
}
}

View File

@ -23,9 +23,6 @@ import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import bisq.common.proto.network.NetworkEnvelope;
import bisq.common.util.Utilities;
import com.google.protobuf.ByteString;
import java.util.Optional;
import java.util.UUID;
@ -40,7 +37,7 @@ import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
private final byte[] payoutTx;
private final String signedMultisigTxHex;
private final NodeAddress senderNodeAddress;
// Added in v1.4.0
@ -48,11 +45,11 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
private final SignedWitness signedWitness;
public PayoutTxPublishedMessage(String tradeId,
byte[] payoutTx,
String signedMultisigTxHex,
NodeAddress senderNodeAddress,
@Nullable SignedWitness signedWitness) {
this(tradeId,
payoutTx,
signedMultisigTxHex,
senderNodeAddress,
signedWitness,
UUID.randomUUID().toString(),
@ -65,13 +62,13 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
///////////////////////////////////////////////////////////////////////////////////////////
private PayoutTxPublishedMessage(String tradeId,
byte[] payoutTx,
String signedMultisigTxHex,
NodeAddress senderNodeAddress,
@Nullable SignedWitness signedWitness,
String uid,
int messageVersion) {
super(messageVersion, tradeId, uid);
this.payoutTx = payoutTx;
this.signedMultisigTxHex = signedMultisigTxHex;
this.senderNodeAddress = senderNodeAddress;
this.signedWitness = signedWitness;
}
@ -80,7 +77,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.PayoutTxPublishedMessage.Builder builder = protobuf.PayoutTxPublishedMessage.newBuilder()
.setTradeId(tradeId)
.setPayoutTx(ByteString.copyFrom(payoutTx))
.setSignedMultisigTxHex(signedMultisigTxHex)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setUid(uid);
Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
@ -95,7 +92,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
SignedWitness.fromProto(protoSignedWitness) :
null;
return new PayoutTxPublishedMessage(proto.getTradeId(),
proto.getPayoutTx().toByteArray(),
proto.getSignedMultisigTxHex(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
signedWitness,
proto.getUid(),
@ -105,7 +102,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
@Override
public String toString() {
return "PayoutTxPublishedMessage{" +
"\n payoutTx=" + Utilities.bytesAsHexString(payoutTx) +
"\n signedMultisigTxHex=" + signedMultisigTxHex +
",\n senderNodeAddress=" + senderNodeAddress +
",\n signedWitness=" + signedWitness +
"\n} " + super.toString();

View File

@ -0,0 +1,99 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Value;
import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public final class UpdateMultisigRequest extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final long currentDate;
@Nullable
private final String updatedMultisigHex;
public UpdateMultisigRequest(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
int messageVersion,
long currentDate,
String updatedMultisigHex) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.currentDate = currentDate;
this.updatedMultisigHex = updatedMultisigHex;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.UpdateMultisigRequest.Builder builder = protobuf.UpdateMultisigRequest.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid);
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setUpdateMultisigRequest(builder).build();
}
public static UpdateMultisigRequest fromProto(protobuf.UpdateMultisigRequest proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new UpdateMultisigRequest(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
messageVersion,
proto.getCurrentDate(),
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
}
@Override
public String toString() {
return "MultisigMessage {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +
",\n updatedMultisigHex='" + updatedMultisigHex +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,99 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Value;
import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public final class UpdateMultisigResponse extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final long currentDate;
@Nullable
private final String updatedMultisigHex;
public UpdateMultisigResponse(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
int messageVersion,
long currentDate,
String updatedMultisigHex) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.currentDate = currentDate;
this.updatedMultisigHex = updatedMultisigHex;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.UpdateMultisigResponse.Builder builder = protobuf.UpdateMultisigResponse.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid);
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setUpdateMultisigResponse(builder).build();
}
public static UpdateMultisigResponse fromProto(protobuf.UpdateMultisigResponse proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new UpdateMultisigResponse(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
messageVersion,
proto.getCurrentDate(),
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
}
@Override
public String toString() {
return "MultisigMessage {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +
",\n updatedMultisigHex='" + updatedMultisigHex +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,96 @@
package bisq.core.trade.protocol;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ArbitratorProtocol extends DisputeProtocol {
private final ArbitratorTrade arbitratorTrade;
public ArbitratorProtocol(ArbitratorTrade trade) {
super(trade);
this.arbitratorTrade = trade;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages
///////////////////////////////////////////////////////////////////////////////////////////
// TODO: new implementation for MakerProtocol
// private void handle(InitTradeRequest message, NodeAddress peer) {
// expect(phase(Trade.Phase.INIT)
// .with(message)
// .from(peer))
// .setup(tasks(ProcessInitTradeRequest.class,
// ApplyFilter.class,
// VerifyPeersAccountAgeWitness.class,
// MakerVerifyTakerFeePayment.class,
// MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
// MakerSendsReadyToFundMultisigResponse.class)
// .withTimeout(30))
// .executeTasks();
// }
public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { // TODO (woodser): update impl to use errorMessageHandler
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
//ApplyFilter.class,
ProcessInitTradeRequest.class))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
throw new RuntimeException("Not implemented");
}
// @Override
// public void handleTakeOfferRequest(InputsForDepositTxRequest message,
// NodeAddress peer,
// ErrorMessageHandler errorMessageHandler) {
// expect(phase(Trade.Phase.INIT)
// .with(message)
// .from(peer))
// .setup(tasks(
// MakerProcessesInputsForDepositTxRequest.class,
// ApplyFilter.class,
// VerifyPeersAccountAgeWitness.class,
// getVerifyPeersFeePaymentClass(),
// MakerSetsLockTime.class,
// MakerCreateAndSignContract.class,
// BuyerAsMakerCreatesAndSignsDepositTx.class,
// BuyerSetupDepositTxListener.class,
// BuyerAsMakerSendsInputsForDepositTxResponse.class).
// using(new TradeTaskRunner(trade,
// () -> handleTaskRunnerSuccess(message),
// errorMessage -> {
// errorMessageHandler.handleErrorMessage(errorMessage);
// handleTaskRunnerFault(message, errorMessage);
// }))
// .withTimeout(30))
// .executeTasks();
// }
///////////////////////////////////////////////////////////////////////////////////////////
// Message dispatcher
///////////////////////////////////////////////////////////////////////////////////////////
// @Override
// protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
// if (message instanceof InitTradeRequest) {
// handleInitTradeRequest((InitTradeRequest) message, peer);
// }
// }
}

View File

@ -21,24 +21,28 @@ import bisq.core.trade.BuyerAsMakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx;
import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse;
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
@ -63,33 +67,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
// Handle take offer request
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void handleTakeOfferRequest(InputsForDepositTxRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
getVerifyPeersFeePaymentClass(),
MakerSetsLockTime.class,
MakerCreateAndSignContract.class,
BuyerAsMakerCreatesAndSignsDepositTx.class,
BuyerSetupDepositTxListener.class,
BuyerAsMakerSendsInputsForDepositTxResponse.class).
using(new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess(message),
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(60))
.executeTasks();
}
// TODO (woodser): remove or ignore any unsupported requests
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages Take offer process
@ -143,4 +121,91 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return MakerVerifyTakerFeePayment.class;
}
///////////////////////////////////////////////////////////////////////////////////////////
// MakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
ProcessInitTradeRequest.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
MakerVerifyTakerFeePayment.class,
MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx
MakerSendsReadyToFundMultisigResponse.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
processModel.setTempTradingPeerNodeAddress(sender);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
MakerSendsReadyToFundMultisigResponse.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
processModel.setTempTradingPeerNodeAddress(sender);
// TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
MakerVerifyTakerDepositTx.class,
MakerCreateAndSignContract.class,
MakerCreateAndPublishDepositTx.class,
MakerSetupDepositTxsListener.class).
using(new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess(message),
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
})))
.executeTasks();
}
}

View File

@ -23,10 +23,14 @@ import bisq.core.trade.BuyerAsTakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
@ -35,27 +39,45 @@ import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureRe
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
import bisq.core.trade.protocol.tasks.taker.FundMultisig;
import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest;
import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import java.math.BigInteger;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener;
// TODO (woodser): remove unused request handling
@Slf4j
public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol {
private ResultHandler takeOfferListener;
private Timer initDepositTimer;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -66,6 +88,8 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
Offer offer = checkNotNull(trade.getOffer());
processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
// TODO (woodser): setup deposit and payout listeners on construction for startup like before rebase?
}
@ -73,22 +97,20 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
// Take offer
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): this implementation is duplicated with SellerAsTakerProtocol
@Override
public void onTakeOffer() {
expect(phase(Trade.Phase.INIT)
.with(TakerEvent.TAKE_OFFER))
.setup(tasks(
ApplyFilter.class,
getVerifyPeersFeePaymentClass(),
CreateTakerFeeTx.class,
BuyerAsTakerCreatesDepositTxInputs.class,
TakerSendInputsForDepositTxRequest.class)
.withTimeout(60))
.run(() -> {
processModel.setTempTradingPeerNodeAddress(trade.getTradingPeerNodeAddress());
processModel.getTradeManager().requestPersistence();
})
.executeTasks();
System.out.println("onTakeOffer()");
expect(phase(Trade.Phase.INIT)
.with(TakerEvent.TAKE_OFFER)
.from(trade.getTradingPeerNodeAddress()))
.setup(tasks(
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
TakerSendInitTradeRequests.class)
.withTimeout(30))
.executeTasks();
}
@ -172,4 +194,190 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return TakerVerifyMakerFeePayment.class;
}
///////////////////////////////////////////////////////////////////////////////////////////
// MakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()");
System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig());
processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this
if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling
processModel.setTradeMessage(message);
if (message.isMakerReadyToFundMultisig()) {
createAndFundMultisig(message, takeOfferListener);
} else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests
reserveTrade(message, takeOfferListener);
}
}
private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("BuyerAsTakerProtocol.reserveTrade()");
// define wallet listener which initiates multisig deposit when trade fee tx unlocked
// TODO (woodser): this needs run for reserved trades when client is opened
// TODO (woodser): test initiating multisig when maker offline
MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet();
MoneroWalletListener fundMultisigListener = new MoneroWalletListener() {
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
// get updated offer fee tx
MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId());
// check if tx is unlocked
if (Boolean.FALSE.equals(feeTx.isLocked())) {
System.out.println("TRADE FEE TX IS UNLOCKED!!!");
// stop listening to wallet
wallet.removeListener(this);
// periodically request multisig deposit until successful
Runnable requestMultisigDeposit = new Runnable() {
@Override
public void run() {
if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler);
else initDepositTimer.stop();
}
};
UserThread.execute(requestMultisigDeposit);
initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60);
}
}
};
// run pipeline to publish trade fee tx
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerCreateFeeTx.class,
TakerVerifyMakerFeePayment.class,
//TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx
TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade?
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later?
wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerVerifyMakerFeePayment.class,
TakerSendReadyToFundMultisigRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("TakerProtocolBase.createAndFundMultisig()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("TakerProtocolBase.handleMultisigMessage()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
ProcessInitMultisigMessage.class)
.using(new TradeTaskRunner(trade,
() -> {
System.out.println("handle multisig pipeline completed successfully!");
handleTaskRunnerSuccess(message);
if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) {
processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time
fundMultisig(message, takeOfferListener);
}
},
errorMessage -> {
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
takeOfferListener.handleResult();
})))
.executeTasks();
}
private void fundMultisig(InitMultisigMessage message, ResultHandler handler) {
System.out.println("TakerProtocolBase.fundMultisig()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
FundMultisig.class). // will receive MultisigMessage in response
using(new TradeTaskRunner(trade,
() -> {
System.out.println("MULTISIG WALLET FUNDED!!!!");
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("TakerProtocolBase.handleDepositTxMessage()");
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
TakerProcessesMakerDepositTxMessage.class,
TakerSetupDepositTxsListener.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
}

View File

@ -25,13 +25,12 @@ import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.UpdateMultisigWithTradingPeer;
import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesFinalDelayedPayoutTx;
import bisq.network.p2p.NodeAddress;
@ -100,28 +99,28 @@ public abstract class BuyerProtocol extends DisputeProtocol {
// mailbox message but the stored in mailbox case is not expected and the seller would try to send the message again
// in the hope to reach the buyer directly.
protected void handle(DepositTxAndDelayedPayoutTxMessage message, NodeAddress peer) {
expect(anyPhase(Trade.Phase.TAKER_FEE_PUBLISHED, Trade.Phase.DEPOSIT_PUBLISHED)
.with(message)
.from(peer)
.preCondition(trade.getDepositTx() == null || trade.getDelayedPayoutTx() == null,
() -> {
log.warn("We with a DepositTxAndDelayedPayoutTxMessage but we have already processed the deposit and " +
"delayed payout tx so we ignore the message. This can happen if the ACK message to the peer did not " +
"arrive and the peer repeats sending us the message. We send another ACK msg.");
stopTimeout();
sendAckMessage(message, true, null);
removeMailboxMessageAfterProcessing(message);
}))
.setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
BuyerVerifiesFinalDelayedPayoutTx.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> handleTaskRunnerFault(message, errorMessage))))
.run(() -> processModel.witnessDebugLog(trade))
.executeTasks();
// expect(anyPhase(Trade.Phase.TAKER_FEE_PUBLISHED, Trade.Phase.DEPOSIT_PUBLISHED)
// .with(message)
// .from(peer)
// .preCondition(trade.getDepositTx() == null || trade.getDelayedPayoutTx() == null,
// () -> {
// log.warn("We with a DepositTxAndDelayedPayoutTxMessage but we have already processed the deposit and " +
// "delayed payout tx so we ignore the message. This can happen if the ACK message to the peer did not " +
// "arrive and the peer repeats sending us the message. We send another ACK msg.");
// stopTimeout();
// sendAckMessage(message, true, null);
// removeMailboxMessageAfterProcessing(message);
// }))
// .setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
// BuyerVerifiesFinalDelayedPayoutTx.class)
// .using(new TradeTaskRunner(trade,
// () -> {
// stopTimeout();
// handleTaskRunnerSuccess(message);
// },
// errorMessage -> handleTaskRunnerFault(message, errorMessage))))
// .run(() -> processModel.witnessDebugLog(trade))
// .executeTasks();
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -135,7 +134,8 @@ public abstract class BuyerProtocol extends DisputeProtocol {
.preCondition(trade.confirmPermitted()))
.setup(tasks(ApplyFilter.class,
getVerifyPeersFeePaymentClass(),
BuyerSignPayoutTx.class,
UpdateMultisigWithTradingPeer.class,
BuyerCreateAndSignPayoutTx.class,
BuyerSetupPayoutTxListener.class,
BuyerSendCounterCurrencyTransferStartedMessage.class)
.using(new TradeTaskRunner(trade,
@ -147,10 +147,7 @@ public abstract class BuyerProtocol extends DisputeProtocol {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(event, errorMessage);
})))
.run(() -> {
trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED);
processModel.getTradeManager().requestPersistence();
})
.run(() -> trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED))
.executeTasks();
}
@ -159,12 +156,22 @@ public abstract class BuyerProtocol extends DisputeProtocol {
///////////////////////////////////////////////////////////////////////////////////////////
protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
processModel.setTradeMessage(message);
processModel.setTempTradingPeerNodeAddress(peer);
expect(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.PAYOUT_PUBLISHED)
.with(message)
.from(peer))
.setup(tasks(BuyerProcessPayoutTxPublishedMessage.class))
.executeTasks();
.with(message)
.from(peer))
.setup(tasks(
getVerifyPeersFeePaymentClass(),
BuyerProcessPayoutTxPublishedMessage.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
})))
.executeTasks();
}

View File

@ -24,8 +24,6 @@ import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessPeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.arbitration.PublishedDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.arbitration.SendPeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.mediation.BroadcastMediatedPayoutTx;
import bisq.core.trade.protocol.tasks.mediation.FinalizeMediatedPayoutTx;
import bisq.core.trade.protocol.tasks.mediation.ProcessMediatedPayoutSignatureMessage;
@ -43,7 +41,7 @@ import bisq.common.handlers.ResultHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DisputeProtocol extends TradeProtocol {
public abstract class DisputeProtocol extends TradeProtocol {
enum DisputeEvent implements FluentProtocol.Event {
MEDIATION_RESULT_ACCEPTED,
@ -142,26 +140,26 @@ public class DisputeProtocol extends TradeProtocol {
// Delayed payout tx
///////////////////////////////////////////////////////////////////////////////////////////
public void onPublishDelayedPayoutTx(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
DisputeEvent event = DisputeEvent.ARBITRATION_REQUESTED;
expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED,
Trade.Phase.FIAT_SENT,
Trade.Phase.FIAT_RECEIVED)
.with(event)
.preCondition(trade.getDelayedPayoutTx() != null))
.setup(tasks(PublishedDelayedPayoutTx.class,
SendPeerPublishedDelayedPayoutTxMessage.class)
.using(new TradeTaskRunner(trade,
() -> {
resultHandler.handleResult();
handleTaskRunnerSuccess(event);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(event, errorMessage);
})))
.executeTasks();
}
// public void onPublishDelayedPayoutTx(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
// DisputeEvent event = DisputeEvent.ARBITRATION_REQUESTED;
// expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED,
// Trade.Phase.FIAT_SENT,
// Trade.Phase.FIAT_RECEIVED)
// .with(event)
// .preCondition(trade.getDelayedPayoutTx() != null))
// .setup(tasks(PublishedDelayedPayoutTx.class,
// SendPeerPublishedDelayedPayoutTxMessage.class)
// .using(new TradeTaskRunner(trade,
// () -> {
// resultHandler.handleResult();
// handleTaskRunnerSuccess(event);
// },
// errorMessage -> {
// errorMessageHandler.handleErrorMessage(errorMessage);
// handleTaskRunnerFault(event, errorMessage);
// })))
// .executeTasks();
// }
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -18,14 +18,14 @@
package bisq.core.trade.protocol;
import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface MakerProtocol {
void handleTakeOfferRequest(InputsForDepositTxRequest message,
NodeAddress taker,
ErrorMessageHandler errorMessageHandler);
void handleInitTradeRequest(InitTradeRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
}

View File

@ -33,7 +33,9 @@ import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.TakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.messages.TradeMessage;
@ -69,6 +71,10 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import monero.wallet.model.MoneroTxWallet;
// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
// persist them.
@ -84,9 +90,12 @@ public class ProcessModel implements Model, PersistablePayload {
transient private ProcessModelServiceProvider provider;
transient private TradeManager tradeManager;
transient private Offer offer;
@Setter
transient private Trade trade;
// Transient/Mutable
transient private Transaction takeOfferFeeTx;
@Getter
transient private MoneroTxWallet takeOfferFeeTx;
@Setter
transient private TradeMessage tradeMessage;
@ -107,12 +116,13 @@ public class ProcessModel implements Model, PersistablePayload {
@Getter
transient private Transaction depositTx;
// Persistable Immutable
private final TradingPeer tradingPeer;
private final String offerId;
private final String accountId;
private final PubKeyRing pubKeyRing;
// Persistable Immutable (private setter only used by PB method)
private TradingPeer maker = new TradingPeer();
private TradingPeer taker = new TradingPeer();
private TradingPeer arbitrator = new TradingPeer();
private String offerId;
private String accountId;
private PubKeyRing pubKeyRing;
// Persistable Mutable
@Nullable
@ -154,6 +164,36 @@ public class ProcessModel implements Model, PersistablePayload {
@Setter
private long sellerPayoutAmountFromMediation;
// Added for XMR integration
@Nullable
@Getter
@Setter
private String preparedMultisigHex;
@Nullable
@Getter
@Setter
private String madeMultisigHex;
@Nullable
@Getter
@Setter
private boolean multisigSetupComplete;
@Nullable
@Getter
@Setter
private boolean makerReadyToFundMultisig;
@Getter
@Setter
private boolean multisigDepositInitiated;
@Nullable
@Setter
private String makerPreparedDepositTxId;
@Nullable
@Setter
private String takerPreparedDepositTxId;
@Nullable
transient private MoneroTxWallet buyerSignedPayoutTx;
// We want to indicate the user the state of the message delivery of the
// CounterCurrencyTransferStartedMessage. As well we do an automatic re-send in case it was not ACKed yet.
@ -162,15 +202,17 @@ public class ProcessModel implements Model, PersistablePayload {
private ObjectProperty<MessageState> paymentStartedMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing) {
this(offerId, accountId, pubKeyRing, new TradingPeer());
this(offerId, accountId, pubKeyRing, new TradingPeer(), new TradingPeer(), new TradingPeer());
}
public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing, TradingPeer tradingPeer) {
public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing, TradingPeer arbitrator, TradingPeer maker, TradingPeer taker) {
this.offerId = offerId;
this.accountId = accountId;
this.pubKeyRing = pubKeyRing;
// If tradingPeer was null in persisted data from some error cases we set a new one to not cause nullPointers
this.tradingPeer = tradingPeer != null ? tradingPeer : new TradingPeer();
this.arbitrator = arbitrator != null ? arbitrator : new TradingPeer();
this.maker = maker != null ? maker : new TradingPeer();
this.taker = taker != null ? taker : new TradingPeer();
}
public void applyTransient(ProcessModelServiceProvider provider,
@ -189,7 +231,6 @@ public class ProcessModel implements Model, PersistablePayload {
@Override
public protobuf.ProcessModel toProtoMessage() {
protobuf.ProcessModel.Builder builder = protobuf.ProcessModel.newBuilder()
.setTradingPeer((protobuf.TradingPeer) tradingPeer.toProtoMessage())
.setOfferId(offerId)
.setAccountId(accountId)
.setPubKeyRing(pubKeyRing.toProtoMessage())
@ -199,24 +240,31 @@ public class ProcessModel implements Model, PersistablePayload {
.setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name())
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation);
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage()));
Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradingPeer) taker.toProtoMessage()));
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradingPeer) arbitrator.toProtoMessage()));
Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId);
Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature)));
Optional.ofNullable(preparedDepositTx).ifPresent(e -> builder.setPreparedDepositTx(ByteString.copyFrom(preparedDepositTx)));
Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(
ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class)));
Optional.ofNullable(makerPreparedDepositTxId).ifPresent(e -> builder.setMakerPreparedDepositTxId(makerPreparedDepositTxId));
Optional.ofNullable(takerPreparedDepositTxId).ifPresent(e -> builder.setTakerPreparedDepositTxId(takerPreparedDepositTxId));
Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class)));
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey)));
Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage()));
Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e)));
Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete));
Optional.ofNullable(makerReadyToFundMultisig).ifPresent(e -> builder.setMakerReadyToFundMultisig(makerReadyToFundMultisig));
Optional.ofNullable(multisigDepositInitiated).ifPresent(e -> builder.setMultisigSetupComplete(multisigDepositInitiated));
return builder.build();
}
public static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResolver coreProtoResolver) {
TradingPeer tradingPeer = TradingPeer.fromProto(proto.getTradingPeer(), coreProtoResolver);
TradingPeer arbitrator = TradingPeer.fromProto(proto.getArbitrator(), coreProtoResolver);
TradingPeer maker = TradingPeer.fromProto(proto.getMaker(), coreProtoResolver);
TradingPeer taker = TradingPeer.fromProto(proto.getTaker(), coreProtoResolver);
PubKeyRing pubKeyRing = PubKeyRing.fromProto(proto.getPubKeyRing());
ProcessModel processModel = new ProcessModel(proto.getOfferId(), proto.getAccountId(), pubKeyRing, tradingPeer);
ProcessModel processModel = new ProcessModel(proto.getOfferId(), proto.getAccountId(), pubKeyRing, arbitrator, maker, taker);
processModel.setChangeOutputValue(proto.getChangeOutputValue());
processModel.setUseSavingsWallet(proto.getUseSavingsWallet());
processModel.setFundsNeededForTradeAsLong(proto.getFundsNeededForTradeAsLong());
@ -226,7 +274,6 @@ public class ProcessModel implements Model, PersistablePayload {
// nullable
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
processModel.setPreparedDepositTx(ProtoUtil.byteArrayOrNullFromProto(proto.getPreparedDepositTx()));
List<RawTransactionInput> rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ?
null : proto.getRawTransactionInputsList().stream()
.map(RawTransactionInput::fromProto).collect(Collectors.toList());
@ -235,6 +282,13 @@ public class ProcessModel implements Model, PersistablePayload {
processModel.setMyMultiSigPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getMyMultiSigPubKey()));
processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null);
processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
processModel.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete());
processModel.setMakerReadyToFundMultisig(proto.getMakerReadyToFundMultisig());
processModel.setMultisigDepositInitiated(proto.getMultisigDepositInitiated());
processModel.setMakerPreparedDepositTxId(proto.getMakerPreparedDepositTxId());
processModel.setTakerPreparedDepositTxId(proto.getTakerPreparedDepositTxId());
String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState());
MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString);
@ -252,9 +306,9 @@ public class ProcessModel implements Model, PersistablePayload {
public void onComplete() {
}
public void setTakeOfferFeeTx(Transaction takeOfferFeeTx) {
public void setTakeOfferFeeTx(MoneroTxWallet takeOfferFeeTx) {
this.takeOfferFeeTx = takeOfferFeeTx;
takeOfferFeeTxId = takeOfferFeeTx.getTxId().toString();
takeOfferFeeTxId = takeOfferFeeTx.getHash();
}
@Nullable
@ -271,14 +325,11 @@ public class ProcessModel implements Model, PersistablePayload {
return Coin.valueOf(fundsNeededForTradeAsLong);
}
public Transaction resolveTakeOfferFeeTx(Trade trade) {
if (takeOfferFeeTx == null) {
if (!trade.isCurrencyForTakerFeeBtc())
takeOfferFeeTx = getBsqWalletService().getTransaction(takeOfferFeeTxId);
else
takeOfferFeeTx = getBtcWalletService().getTransaction(takeOfferFeeTxId);
}
return takeOfferFeeTx;
public MoneroTxWallet resolveTakeOfferFeeTx(Trade trade) {
if (takeOfferFeeTx == null) {
takeOfferFeeTx = provider.getXmrWalletService().getWallet().getTx(takeOfferFeeTxId);
}
return takeOfferFeeTx;
}
public NodeAddress getMyNodeAddress() {
@ -299,6 +350,21 @@ public class ProcessModel implements Model, PersistablePayload {
}
}
public void setTradingPeer(TradingPeer peer) {
if (trade == null) throw new RuntimeException("Cannot set trading peer because trade is null");
else if (trade instanceof MakerTrade) taker = peer;
else if (trade instanceof TakerTrade) maker = peer;
else throw new RuntimeException("Must be maker or taker to set trading peer");
}
public TradingPeer getTradingPeer() {
if (trade == null) throw new RuntimeException("Cannot get trading peer because trade is null");
else if (trade instanceof MakerTrade) return taker;
else if (trade instanceof TakerTrade) return maker;
else if (trade instanceof ArbitratorTrade) return null;
else throw new RuntimeException("Unknown trade type: " + trade.getClass().getName());
}
void setDepositTxSentAckMessage(AckMessage ackMessage) {
MessageState messageState = ackMessage.isSuccess() ?
MessageState.ACKNOWLEDGED :
@ -381,4 +447,13 @@ public class ProcessModel implements Model, PersistablePayload {
public DaoFacade getDaoFacade() {
return provider.getDaoFacade();
}
public void setBuyerSignedPayoutTx(MoneroTxWallet buyerSignedPayoutTx) {
this.buyerSignedPayoutTx = buyerSignedPayoutTx;
}
@Nullable
public MoneroTxWallet getBuyerSignedPayoutTx() {
return buyerSignedPayoutTx;
}
}

View File

@ -21,6 +21,7 @@ import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.offer.OpenOfferManager;
@ -44,6 +45,7 @@ public class ProcessModelServiceProvider {
private final OpenOfferManager openOfferManager;
private final P2PService p2PService;
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final BsqWalletService bsqWalletService;
private final TradeWalletService tradeWalletService;
private final DaoFacade daoFacade;
@ -61,6 +63,7 @@ public class ProcessModelServiceProvider {
public ProcessModelServiceProvider(OpenOfferManager openOfferManager,
P2PService p2PService,
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
TradeWalletService tradeWalletService,
DaoFacade daoFacade,
@ -73,10 +76,10 @@ public class ProcessModelServiceProvider {
MediatorManager mediatorManager,
RefundAgentManager refundAgentManager,
KeyRing keyRing) {
this.openOfferManager = openOfferManager;
this.p2PService = p2PService;
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.bsqWalletService = bsqWalletService;
this.tradeWalletService = tradeWalletService;
this.daoFacade = daoFacade;

View File

@ -21,25 +21,28 @@ package bisq.core.trade.protocol;
import bisq.core.trade.SellerAsMakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse;
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
@ -60,37 +63,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
}
///////////////////////////////////////////////////////////////////////////////////////////
// Handle take offer request
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void handleTakeOfferRequest(InputsForDepositTxRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
getVerifyPeersFeePaymentClass(),
MakerSetsLockTime.class,
MakerCreateAndSignContract.class,
SellerAsMakerCreatesUnsignedDepositTx.class,
SellerAsMakerSendsInputsForDepositTxResponse.class)
.using(new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess(message),
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(60))
.executeTasks();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages Take offer process
///////////////////////////////////////////////////////////////////////////////////////////
@ -110,12 +82,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
.executeTasks();
}
// We keep the handler here in as well to make it more transparent which messages we expect
@Override
protected void handle(DelayedPayoutTxSignatureResponse message, NodeAddress peer) {
super.handle(message, peer);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message when buyer has clicked payment started button
@ -159,4 +125,88 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return MakerVerifyTakerFeePayment.class;
}
///////////////////////////////////////////////////////////////////////////////////////////
// MakerProtocol TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
ProcessInitTradeRequest.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
MakerVerifyTakerFeePayment.class,
MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx
MakerSendsReadyToFundMultisigResponse.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
processModel.setTempTradingPeerNodeAddress(sender);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
MakerSendsReadyToFundMultisigResponse.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
processModel.setTempTradingPeerNodeAddress(sender);
// TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
MakerVerifyTakerDepositTx.class,
MakerCreateAndSignContract.class,
MakerCreateAndPublishDepositTx.class,
MakerSetupDepositTxsListener.class).
using(new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess(message),
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
})))
.executeTasks();
}
}

View File

@ -22,35 +22,56 @@ import bisq.core.offer.Offer;
import bisq.core.trade.SellerAsTakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
import bisq.core.trade.protocol.tasks.taker.FundMultisig;
import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest;
import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import java.math.BigInteger;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener;
// TODO (woodser): remove unused request handling
@Slf4j
public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol {
private ResultHandler takeOfferListener;
private Timer initDepositTimer;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -69,17 +90,17 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
@Override
public void onTakeOffer() {
expect(phase(Trade.Phase.INIT)
.with(TakerEvent.TAKE_OFFER)
.from(trade.getTradingPeerNodeAddress()))
.setup(tasks(
ApplyFilter.class,
getVerifyPeersFeePaymentClass(),
CreateTakerFeeTx.class,
SellerAsTakerCreatesDepositTxInputs.class,
TakerSendInputsForDepositTxRequest.class)
.withTimeout(60))
.executeTasks();
System.out.println("onTakeOffer()");
expect(phase(Trade.Phase.INIT)
.with(TakerEvent.TAKE_OFFER)
.from(trade.getTradingPeerNodeAddress()))
.setup(tasks(
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
TakerSendInitTradeRequests.class)
.withTimeout(30))
.executeTasks();
}
@ -105,12 +126,6 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
.executeTasks();
}
// We keep the handler here in as well to make it more transparent which messages we expect
@Override
protected void handle(DelayedPayoutTxSignatureResponse message, NodeAddress peer) {
super.handle(message, peer);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message when buyer has clicked payment started button
@ -154,4 +169,189 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return TakerVerifyMakerFeePayment.class;
}
///////////////////////////////////////////////////////////////////////////////////////////
// TakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()");
System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig());
processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this
if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling
processModel.setTradeMessage(message);
if (message.isMakerReadyToFundMultisig()) {
createAndFundMultisig(message, takeOfferListener);
} else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests
reserveTrade(message, takeOfferListener);
}
}
private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("BuyerAsTakerProtocol.reserveTrade()");
// define wallet listener which initiates multisig deposit when trade fee tx unlocked
// TODO (woodser): this needs run for reserved trades when client is opened
// TODO (woodser): test initiating multisig when maker offline
MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet();
MoneroWalletListener fundMultisigListener = new MoneroWalletListener() {
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
// get updated offer fee tx
MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId());
// check if tx is unlocked
if (Boolean.FALSE.equals(feeTx.isLocked())) {
System.out.println("TRADE FEE TX IS UNLOCKED!!!");
// stop listening to wallet
wallet.removeListener(this);
// periodically request multisig deposit until successful
Runnable requestMultisigDeposit = new Runnable() {
@Override
public void run() {
if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler);
else initDepositTimer.stop();
}
};
UserThread.execute(requestMultisigDeposit);
initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60);
}
}
};
// run pipeline to publish trade fee tx
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerCreateFeeTx.class,
TakerVerifyMakerFeePayment.class,
//TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx
TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade?
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later?
wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerVerifyMakerFeePayment.class,
TakerSendReadyToFundMultisigRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("TakerProtocolBase.createAndFundMultisig()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("TakerProtocolBase.handleMultisigMessage()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
ProcessInitMultisigMessage.class)
.using(new TradeTaskRunner(trade,
() -> {
System.out.println("handle multisig pipeline completed successfully!");
handleTaskRunnerSuccess(message);
if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) {
processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time
fundMultisig(message, takeOfferListener);
}
},
errorMessage -> {
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
takeOfferListener.handleResult();
})))
.executeTasks();
}
private void fundMultisig(InitMultisigMessage message, ResultHandler handler) {
System.out.println("TakerProtocolBase.fundMultisig()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
FundMultisig.class). // will receive MultisigMessage in response
using(new TradeTaskRunner(trade,
() -> {
System.out.println("MULTISIG WALLET FUNDED!!!!");
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("TakerProtocolBase.handleDepositTxMessage()");
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
TakerProcessesMakerDepositTxMessage.class,
TakerSetupDepositTxsListener.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
}

View File

@ -20,19 +20,12 @@ package bisq.core.trade.protocol;
import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSignAndPublishPayoutTx;
import bisq.network.p2p.NodeAddress;
@ -67,31 +60,6 @@ public abstract class SellerProtocol extends DisputeProtocol {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages
///////////////////////////////////////////////////////////////////////////////////////////
protected void handle(DelayedPayoutTxSignatureResponse message, NodeAddress peer) {
expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
.from(peer))
.setup(tasks(SellerProcessDelayedPayoutTxSignatureResponse.class,
SellerFinalizesDelayedPayoutTx.class,
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
SellerPublishesDepositTx.class,
SellerPublishesTradeStatistics.class))
.run(() -> {
// We stop timeout here and don't start a new one as the
// SellerSendsDepositTxAndDelayedPayoutTxMessage repeats the send the message and has it's own
// timeout if it never succeeds.
stopTimeout();
//TODO still needed? If so move to witness domain
processModel.witnessDebugLog(trade);
})
.executeTasks();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message when buyer has clicked payment started button
///////////////////////////////////////////////////////////////////////////////////////////
@ -132,22 +100,18 @@ public abstract class SellerProtocol extends DisputeProtocol {
.setup(tasks(
ApplyFilter.class,
getVerifyPeersFeePaymentClass(),
SellerSignAndFinalizePayoutTx.class,
SellerBroadcastPayoutTx.class,
SellerSignAndPublishPayoutTx.class,
// SellerSignAndFinalizePayoutTx.class,
// SellerBroadcastPayoutTx.class,
SellerSendPayoutTxPublishedMessage.class)
.using(new TradeTaskRunner(trade,
() -> {
resultHandler.handleResult();
handleTaskRunnerSuccess(event);
},
(errorMessage) -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(event, errorMessage);
})))
.run(() -> {
trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT);
processModel.getTradeManager().requestPersistence();
})
.using(new TradeTaskRunner(trade, () -> {
resultHandler.handleResult();
handleTaskRunnerSuccess(event);
}, (errorMessage) -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(event, errorMessage);
})))
.run(() -> trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT))
.executeTasks();
}
@ -156,12 +120,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer);
log.info("Received {} from {} with tradeId {} and uid {}",
message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid());
if (message instanceof DelayedPayoutTxSignatureResponse) {
handle((DelayedPayoutTxSignatureResponse) message, peer);
} else if (message instanceof CounterCurrencyTransferStartedMessage) {
if (message instanceof CounterCurrencyTransferStartedMessage) {
handle((CounterCurrencyTransferStartedMessage) message, peer);
}
}

View File

@ -17,10 +17,20 @@
package bisq.core.trade.protocol;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface TakerProtocol {
void onTakeOffer();
enum TakerEvent implements FluentProtocol.Event {
TAKE_OFFER
}
}
// TODO (woodser): update after rebase
//åvoid takeAvailableOffer(ResultHandler handler);
void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
}

Some files were not shown because too many files have changed in this diff Show More