mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Release/v1.2.0 (#3532)
* New trade protocol (#3333) * Remove arbitration key, cleanup * Add BuyerAsMakerProcessDepositTxAndDelayedPayoutTxMessage * Adopt trade protocol - Add handler for DepositTxAndDelayedPayoutTxMessage - Change handler for DepositTxPublishedMessage - Add MakerSetsLockTime - Rename MakerProcessPayDepositRequest to MakerProcessPayDepositRequest - Rename MakerSendPublishDepositTxRequest to MakerSendsProvideInputsForDepositTxMessage - Rename DepositTxPublishedMessage to DelayedPayoutTxSignatureRequest - Rename MakerProcessDepositTxPublishedMessage to MakerAsBuyerProcessSignDelayedPayoutTxMessage * Remove arbitratorKey * Add new classes * Add new message classes * Add new task classes * Renamed classed (no functional change yet) * Add lockTime * Add delayedPayoutTxSignature field * Add useReimbursementModel field * Add new classes * Add setting.preferences.useReimbursementModel * Apply renamed classes (new classes not added yet) * Add useReimbursementModel * Add preferences param * Add new methods, cleanup * Add daoFacade param, apply renaming * Add delayedPayoutTx, lockTime and delayedPayoutTxId - Support daoFacade param * Remove DirectMessage interface * Rename emergencySignAndPublishPayoutTx method, add new one for 2of2 MS * Apply new protocol * Apply new protocol * Add renaming (no functional change yet) * Add new messages, apply renaming * Remove unneeded P2SHMultiSigOutputScript * Remove PREFERRED_PROJECT_CODE_STYLE * Refactor: Rename class * Use InputsForDepositTxRequest instead of TradeMessage in handleTakeOfferRequest * Do not sign deposit tx if maker is seller We change behaviour that the maker as seller does not send the pre signed deposit tx to the taker as the seller has more to lose and he wants to control the creation process of the delayed payout tx. * Apply new trade protocol to seller as maker version * Apply new trade protocol Delayed payout tx are now working for all scenarios but we use a small hack to get around an issue with not receiving confirmations and the peers tx. We add a tiny output to both peers, so we see the tx and confirmation. Without that only the publisher sees the tx and confirmations are not displayed. Need further work to get that working without that extra outputs. * Set TRADE_PROTOCOL_VERSION to 2 * Add PeerPublishedDelayedPayoutTxMessage We need add the delayed payout tx to the wallet once the peer publishes it. We will not see the confidence as we do not receive or sent funds from our address. Same is with dispute payouts where one peer does not receive anything. Then the confidence is not set. It seems that is a restriction in BitcoinJ or it requires some extra handling. We set the confidence indicator invisible in the dispute case and that might be an acceptable option here as well. * Add refund agent domain * Add refundAgentNodeAddress * Apply refund domain * Add refund views * Apply refundAgent domain * Support refundAgent * Remove useReimbursementModel field We dont need in the offer anymore the decision if reimbursement or arbitration is chosen. * Apply refundAgent payout * Handle tx info and balances * Remove mediation activation * Add new tac accepted flag for v1.2.0 and adjust text * Fix params for test classes * Signed witness trading (#3334) * Added basic UI for account signing for arbitrators * Add domain layer for signed account age witnesses (credits ManfredKarrer and oscarguindzberg) * Remove testing gridlines * Arbitrator sign accountAgeWitnesses Automatically filter to only sign accounts that - have chargeback risk - bought BTC - was winner in dispute * Handle chargebackrisk by currency * Check winners only for closed disputes * Show sign status of paymentaccounts in AccountsView * Rename service to accountAgeWitnessService * Refactor: Move account signing helpers to AccountAgeWitnessService * Refactor: rename hasSignedWitness to myHasSignedWitness * Show if witness is signed in offerbook view * Use witness sign age for age comparison * Refactor: rename to isTaker... to isMyTaker... * Allow trading with signed witnesses * Use witness age for showing account age icon * Move AccountAgeRestrictions into AccountAgeWitnessService * Handle trade limit of unverified accounts as normal case * Avoid optional as argument * Set trade limit depending on trade direction * Avoid optional arguments * Add text for seller as signer * Seller with signer privilege signs buyer witness * Fix merge issues * Remove explicit check for risky offers * Remove sellers explicit account age check * Add limit check based on common accountAgeWitness function * Fix arbitrator key event handling * Filter accounts on tradelimit instead of maturity * Fix test * Buyer sign seller account Add SIGNED_ACCOUNT_AGE_WITNESS capability * Fix checks for signing at end of trade Get correct valid accounts for offer * Rename BuyerDataItem -> TraderDataItem * Arbitrator sign both parties in a buyer payout dispute * Only sign unsigned accountAgeWitnesses * Remove unused code * Add demo for material design icons * Use different account age limits for sell/buy * Fix signing interface for arbitrator * Add signing state column to offer book * Add signing state to fiat accounts overview * Add signing state to selected fiat account * Fix popover padding * Add account signing state to peer info popup * Retrieve only unsigned witnesses for arbitrator to sign * Accounts signed by arbitrators are signers * Disable test due to travis issues * Improve witness handling (#3342) * Fix comparison * Add display strings for witness sign state * Fix immaturity check * Use accountAgeWitness age for non risky payment methods * Show information about non risky account types * Fix peer info icon account age text * Complete new trade protocol (#3340) * Improve handling of adding tx to wallet * Add delayedPayoutTx to dispute * Fix test * Use RECIPIENT_BTC_ADDRESS from DAO for trade fee * Set lockTime to 10 days for altcoins, 20 days others. - Devmode uses 1 block * Fix params * Update text * Update docs * Update logging if (log.isDebugEnabled()) only matches if logLevel is debug not if it is INFO * Remove log * Remove arbitrator checks * Remove arbitrator address - It works not if not legacy arbitrator is registered. We cannot remove too much from arbitration as we would risk to break account signing and display of old arbitration cases. Though if testing time permits we should try to clean out more of arbitration domain what is not needed anymore. * Use account signing state in accounts view (#3365) * Add account signing icons to signing state in account display * Remove unnecessary "." that caused layout issues in the past * Add additional warning in the received payment popup for account signer * Fix Revolut padding issues for currencies * Hide signing icon for non-high-risk payment methods * Add correct icon state and info text for account signing state * Remove not implemented notification part * Test self signing witnesses * Change verified account limit factor to 0.5 * Account Signing: Add information popups for signing state (#3374) * Add account signing icons to signing state in account display * Remove not implemented notification part * Hide time since signing column when not needed * Remove fiat rounding popup as feature was introduced a long time ago already * Add information popups for new signed states (only shown once for user) and minor clean-ups * Update core/src/main/resources/i18n/displayStrings.properties Co-Authored-By: sqrrm <sqrrm@users.noreply.github.com> * Account Signing: Improve signed state notificaton (#3388) * Remove new badge from Altcoin instant feature * Remove new badge from percentage user deposit feature * Fix line break issues in received payment confirmation popup * Check if received payload fulfills signing state condition and not any personal witness * Show additional badge for account sections to guide user to check out new signing states * Fix account signing state in offer book (#3390) * Account Signing: Fix verified usage (#3392) * Rename witnessHash -> accountAgeWitnessHash * Add enum for SignedWitness verification method * Fix usage of isValidAccountAgeWitness * Revert icon for signstate change * Account signing: add signing state to payment account selection (#3403) * Clean up dead code parts * Add account signing state to payment account selection * Account signing: revert dev date setting for trusted accounts (#3404) * Revert temporary value for dev testing * Only enable button if there are accounts to be signed * Add trade limit exceptions (#3406) * Remove dead code * Add trade limit exception for accounts signed by arbitrator * Update translations to adapt to new unified delay (#3409) * NTP: Fix a couple of UI issues in the New Trade Protocol (#3410) * Add badge support for refund agent (new arbitrator) tickets * Fix translation typo * Clean up arbitrator issues in translation * Only show refund agent label to support staff Every user should still see this role as arbitration * NTP: Improve differentiation between mediation and new arbitration (#3414) * Clean up property exposure * Improve differentiation between mediation and arbitration cases * Go to new refund view if it is no mediation and not open mediation notification if refund is already in progress * Don't sign filtered accounts * NTP: merge with master (#3420) * Temporarily disable onion host for @KanoczTomas's BTC node * Add Ergo (ERG) without Bouncy Castle dependency. See #3195. * List CTSCoin (CTSC) * Tweak the English name of Japan Bank Transfer payment method * Add mediator prefix to trade statistics * List Faircoin (FAIR) * List uPlexa (UPX) * Remove not used private methods from BisqEnvironment * Add onInitP2pNetwork and onInitWallet to BisqSetupListener - Rename BisqSetupCompleteListener to BisqSetupListener - Add onInitP2pNetwork and onInitWallet to BisqSetupListener - make onInitP2pNetwork and onInitWallet default so no impl. required * Start server at onInitWallet and add wallet password handler - Add onInitWallet to HttpApiMain and start http server there - Add onRequestWalletPassword to BisqSetupListener - Override setupHandlers in HttpApiHeadlessApp and adjust setRequestWalletPasswordHandler (impl. missing) - Add onRequestWalletPassword to HttpApiMain * Add combination (Blockstream.info + Mempool.space) block explorer * Revert "Temporarily disable onion host for @KanoczTomas's BTC node" This reverts commitd3335208bb
. * Temporarily disable KanoczTomas btcnode on both onion and clearnet * Refactor BisqApp - update scene size calculation * Refactor BisqApp - update error popup message build * Refactor BisqApp - move icon load into ImageUtil * Remove unused Utilities * Increase minimum TX fee to 2 sats/vByte to fix #3106 (#3387) * Fix mistakes in English source (#3386) * Fix broken placeholders * Replace non existing pending trades screen with open trades screen * Update core/src/main/resources/i18n/displayStrings.properties Co-Authored-By: Steve Jain <mfiver@gmail.com> * Update message in failed trade popup * Refactor BisqEnvironment * Account Signing: Improve arbitrator signing flow (#3421) * Pre-select a point of time 2 months in the past So all arbitrator signed payment accounts will have their limits lifted completely * Only show payment methods with high chargeback risk to be signed * Show connected Bitcoin network peer info * List Ndau (XND) - Official project URL: https://ndau.io/ - Official block explorer URL: https://explorer.service.ndau.tech * List Animecoin (ANI) * Apply rule to not allow BSQ outputs after BTC output for regular txs (#3413) * Apply rule to not allow BSQ outputs after BTC output for regular txs * Enforce exactly 1 BSQ output for vote reveal tx * Fix missing balance and button state update * Refactor isBtcOutputOfBurnFeeTx method and add comments and TODOs No functional change. * Handle asset listing fee in custom method We need to enforce a BSQ change output As this is just tx creation code it has no consequences for the hard fork. * Use getPreparedBurnFeeTxForAssetListing * Update comments to not use dust output values * Fix missing balance and button state update * Use same method for asset listing fee and proof of burn Use same method for asset listing fee and proof of burn as tx structure is same. Update comments to be more general. * Use getPreparedProofOfBurnTx * Require mandatory BSQ change output for proposal fee tx. We had in the doc stated that we require a mandatory BSQ change output but it was not enforced in the implementation, causing similar issues as in Asset listing and proof of burn txs. * Add fix for not correctly handled issuance tx * Use new method for issuance tx // For issuance txs we also require a BSQ change output before the issuance output gets added. There was a // minor bug with the old version that multiple inputs would have caused an exception in case there was no // change output (e.g. inputs of 21 and 6 BSQ for BSQ fee of 21 BSQ would have caused that only 1 input was used // and then caused an error as we enforced a change output. This new version handles such cases correctly. * Handle all possible blind vote fee transactions * Move check for invalid opReturn output up * Add dust check at final sign method * Fix incorrect comments * Refactor - Remove requireChangeOutput param which is always false - Remove method which is used only by one caller - Cleanup * Add comment * Fix comments, rename methods * Move code of isBlindVoteBurnedFeeOutput to isBtcOutputOfBurnFeeTx * Update account signing strings for v1.2 release (#3435) * Update account signing strings for v1.2 release * Add minor corrections from ripcurlx review * Adjust tradeLimitDueAccountAgeRestriction string So that it describes why an account isn't signed (in general) instead of why it wasn't signed by an arbitrator. * Account Signing/NTP: More improvements and fixes (#3436) * Select the the correct sub view when a dispute is created * Require capability REFUND_AGENT to receive RefundAgent Messages * Remove unused return type for account signing * Add new feature popup for account signing and new trade protocol * Return void from account signing * Fix bug with not updating vote result table at vote result block * NTP: improve backwards compatibility for mediation (#3439) * Improve readability of offer update * Add type safeguard for dispute lists * Set not existing dispute support type for clients < 1.2.0 from message support type * Enable handling of mediation cases for old trade protocol disputes in 1.2.0 clients * Remove unnecessary forEach * Use correct formatter and add missing value for placeholder * Bump version number * Add sign all checkbox. Fix list entry display (#3450) * Add sign all checkbox. Fix list entry display * Add summary to log and clipboard * Use safe version for seednodes (#3452) * Apply shutdown and memory check again To not risk issues with the release and seed nodes we merge back the old code base for handling memory check and shutdowns. The newly added changes for cross connecting between seed nodes cause out of memory issues and require more work and testing before it can be used. * Revert code change for periodic updates between seed nodes. The periodic updates code caused out of memory issues and require more work and testing before it can be used. * Arbitrator republish signedWitnesses on startup (#3448) * Arbitrator republish signedWitnesses on startup * Keep republish internal to SignedWitnessService * Improve new feature popup for ntp and account signing (#3453) * Do not commit delayedPayoutTx to avoid publishing at restart Fixes https://github.com/bisq-network/bisq/issues/3463 BitcoinJ publishes automatically committed transactions. We committed it to the wallet to be able to access it later after a restart. We stored the txId in Trade and used that to request the tx from the wallet (as it was committed). Now we store the bitcoin serialized bytes of the tx and do not commit the tx before broadcasting it (if a trader opens refund agent ticket). * [1.2.0] Update client resources (#3456) * Update bitcoinj checkpoint file * Update data stores * Update translations * [1.2.0] Improve new feature popup (#3465) * Improve layout of new feature popup * Extract external hyperlinks into component to make it easier to update * Comment in necessary showAgain check * Add Raspberry Pi to build process (#3466) * Add Raspberry Pi to build process * Rename deploy variable to improve readability * Update informational prompt upon creating fiat account with account signing details (#3467) * Update informational prompt upon creating fiat account with account signing details * Fix wrong buyer limit for first 30 days * Set delayedPayoutTxBytes when setting delayedPayoutTx Fixes https://github.com/bisq-network/bisq/issues/3473 The delayedPayoutTx is not committed to the wallet as long it is not published. The seller who creates the delayedPayoutTx has not stored the delayedPayoutTxBytes which caused a nullpointer after restart. * Minor updates (#3474) * Remove unnecessary log statement This seems to be a left over log statement from debugging. * Use a small delay for MakerSetsLockTime on regtest When testing on regtest, not in devmode, we want a relatively short delay to be able to test the delay period. * Clarify payment limits up to 30 days after signing * Update RECIPIENT_BTC_ADDRESS for regtest (#3478) Use an address that is owned by the regtest wallet in the dao-setup.zip file. This allows for easily verifying BTC trading fees are sent to this address correctly. Also, it helps verify spending of the time lock payout. * Remove btc nodes from Manfred Karrer (#3480) * Avoid null objects (#3481) * Avoid null objects * Remove check for type Historical data can be arbitration instead of mediation (arbitration was fallback at last update), so we need to tolerate the incorrect type here. Is only for tickets from pre 1.2. * Display appropriate account age info header Depending on charge back risk type, accounts should show accountAgeWitness age or time since signing * Set amount for delayed payout tx to 0 (#3471) We have shown the spent funds from the deposit tx to the bisq donation address before. But that was incorrect from the wallet perspective and would have lead to incorrect summary of all transaction amounts. We set it now to 0 as we are not spending funds nor receiving any in our wallet. * Check for result phase at activate method Fixes https://github.com/bisq-network/bisq/issues/3487 * Only show warning for risky payment menthods (#3497) * Fix style issues with dark mode (#3495) * Addresses issues mentioned in https://github.com/bisq-network/bisq/issues/3482#issuecomment-546812730 (#3496) * Clean up trade statistics from duplicate entries (#3476) * Clean up trade statistics from duplicate entries At software updates we added new entries to the extraMap which caused duplicate entries (if one if the traders was on the new and the other on the old version or at republishing). We set it now json exclude so avoid that in future and clean up the map. * Avoid repeated calls to addPersistableNetworkPayloadFromInitialRequest For trade stat cleanup we don't want to apply it multiple times as it is a bit expensive. We get from each seed node the initial data response and would pollute with the second response our map again and if our node is a seed node, the seed node itself could not get into a clean state and would continue pollution other nodes. * Refactor Remove not used param Rename method Inline method Cleanups * Change unsigned to N/A * [1.2.0] Update data stores and adding SignedWitnessStore (#3494) * Update data stores and adding SignedWitnessStore * Update translations * Update cleaned TradeStatistics2Store and changes in other stores * VoteResultView update results on any block in result phase Avoid updating the result more than once per result phase but make sure it's done if activated during the result phase * [1.2.0] Format maker fee for BTC and BSQ correctly (#3498) * Format maker fee for BTC and BSQ correctly * Update tests * Only automatically open popup if result wasn't accepted and disable action button when being accepted (#3503) * Fix tradestatistics (#3469) * Remove delayed re-publishing of tradeStatistics This was done earlier when only maker was publishing trade statistics. Now both traders do it so we get already higher resilience. * Remove unused method Forgot in prev. commit to remove also the method. * Remove support for TradeStatistics2.ARBITRATOR_ADDRESS * Add comment and set ARBITRATOR_ADDRESS deprecated * Remove setting of arbitrator data from makers side The 2 arbitrator related fields in Trade are only set by the maker and not used anymore for reading, so it can be removed. The whole arbitrator domain should be cleaned out some day, but because of backward compatibility issues it id difficult to do it entirely at release date. With release after v 1.2. when no old offers are out anymore we are able to clean up that domain. * Remove dev log * Update translations * [1.2.0] Improve dispute section (#3504) * Improve wording for mediation summary and add specific next steps for refund agent case * Select the first dispute case when entering the support section * Revert to SNAPSHOT version * Fix but with initialRequestApplied (#3512) * Fix resource name (#3514) * Remove minor version number in news popup * Fix copy SignedWitnessStore db script * Not show payment account details for blocked offers * Use age of accountAgeWitness as basis for sell limits * Bump version number * Revert to SNAPSHOT version * Merge v1.2.0/v1.2.1 with master (#3521) * List Krypton (ZOD) * Temporarily disable onion host for @KanoczTomas's BTC node * Add Ergo (ERG) without Bouncy Castle dependency. See #3195. * List CTSCoin (CTSC) * Tweak the English name of Japan Bank Transfer payment method * List Animecoin (ANI) * Add mediator prefix to trade statistics * List Faircoin (FAIR) * List uPlexa (UPX) * Remove not used private methods from BisqEnvironment * Add onInitP2pNetwork and onInitWallet to BisqSetupListener - Rename BisqSetupCompleteListener to BisqSetupListener - Add onInitP2pNetwork and onInitWallet to BisqSetupListener - make onInitP2pNetwork and onInitWallet default so no impl. required * Start server at onInitWallet and add wallet password handler - Add onInitWallet to HttpApiMain and start http server there - Add onRequestWalletPassword to BisqSetupListener - Override setupHandlers in HttpApiHeadlessApp and adjust setRequestWalletPasswordHandler (impl. missing) - Add onRequestWalletPassword to HttpApiMain * Add combination (Blockstream.info + Mempool.space) block explorer * Revert "Temporarily disable onion host for @KanoczTomas's BTC node" This reverts commitd3335208bb
. * Temporarily disable KanoczTomas btcnode on both onion and clearnet * Refactor BisqApp - update scene size calculation * Refactor BisqApp - update error popup message build * Refactor BisqApp - move icon load into ImageUtil * Remove unused Utilities * Increase minimum TX fee to 2 sats/vByte to fix #3106 (#3387) * Fix mistakes in English source (#3386) * Fix broken placeholders * Replace non existing pending trades screen with open trades screen * Update core/src/main/resources/i18n/displayStrings.properties Co-Authored-By: Steve Jain <mfiver@gmail.com> * Update message in failed trade popup * Refactor BisqEnvironment * List Ndau (XND) - Official project URL: https://ndau.io/ - Official block explorer URL: https://explorer.service.ndau.tech * Show connected Bitcoin network peer info * Not show payment account details for blocked offers (#3425) * Add GitHub issue template for user reported bugs (#3454) * Add issue template with steps to reproduce and actual/expected behavior * Fix typo in .github/ISSUE_TEMPLATE.md * Fix wrong auto merge * Add CapabilityRequiringPayload to TradeStatistics2 With v1.2.0 we changed the way how the hash is created. To not create too heavy load for seed nodes from requests from old nodes we use the SIGNED_ACCOUNT_AGE_WITNESS capability to send trade statistics only to new nodes. As trade statistics are only used for informational purpose it will not have any critical issue for the old nodes beside that they don't see the latest trades. * Fix tradestat hash issue (#3529) * Recreate hash from protobuf data To ensure all data are using the new hash method (excluding extraMap) we do not use the hash field from the protobug data but pass null which causes to create the hash new based on the new hash method. * Add filter.toString method and log filter in case of wrong signature We have atm a invalid filter (prob. some dev polluted a test filter to mainnet) * Change log level, add log * Refactor: Move code to dump method * Add TRADE_STATISTICS_HASH_UPDATE capability We changed the hash method in 1.2.0 and that requires update to 1.2.2 for handling it correctly, otherwise the seed nodes have to process too much data. * Add logs for size of data exchange messages * Add more data in log * Improve logs * Fix wrong msg in log, cahnge log level * Add check for depositTxId not empty * Remove check for duplicates As we recreate the hash for all trade stat objects we don't need that check anymore. * Add logs * Temporarily remove this part of the statistics It prevents merging with master because through auto merge a duplication of this part of the code is happening and prevents Travis from succeeding
This commit is contained in:
parent
2967702db1
commit
b976570426
@ -1,6 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
@ -274,7 +274,7 @@ configure(project(':desktop')) {
|
||||
apply plugin: 'witness'
|
||||
apply from: '../gradle/witness/gradle-witness.gradle'
|
||||
|
||||
version = '1.1.7-SNAPSHOT'
|
||||
version = '1.2.1-SNAPSHOT'
|
||||
|
||||
mainClassName = 'bisq.desktop.app.BisqAppMain'
|
||||
|
||||
|
@ -35,9 +35,10 @@ public enum Capability {
|
||||
RECEIVE_BSQ_BLOCK, // Signaling that node which wants to receive BSQ blocks (DAO lite node)
|
||||
@Deprecated DAO_STATE, // Not required anymore as no old clients out there not having that support
|
||||
|
||||
//TODO can be set deprecated after v1.1.6 as we enforce update there
|
||||
BUNDLE_OF_ENVELOPES, // Supports bundling of messages if many messages are sent in short interval
|
||||
@Deprecated BUNDLE_OF_ENVELOPES, // Supports bundling of messages if many messages are sent in short interval
|
||||
|
||||
SIGNED_ACCOUNT_AGE_WITNESS, // Supports the signed account age witness feature
|
||||
MEDIATION // Supports mediation feature
|
||||
MEDIATION, // Supports mediation feature
|
||||
REFUND_AGENT, // Supports refund agents
|
||||
TRADE_STATISTICS_HASH_UPDATE // We changed the hash method in 1.2.0 and that requires update to 1.2.2 for handling it correctly, otherwise the seed nodes have to process too much data.
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ public class Version {
|
||||
// VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update
|
||||
// Therefore all sub versions start again with 1
|
||||
// We use semantic versioning with major, minor and patch
|
||||
public static final String VERSION = "1.1.7";
|
||||
public static final String VERSION = "1.2.1";
|
||||
|
||||
public static int getMajorVersion(String version) {
|
||||
return getSubVersion(version, 0);
|
||||
@ -73,7 +73,7 @@ public class Version {
|
||||
// The version no. for the objects sent over the network. A change will break the serialization of old objects.
|
||||
// If objects are used for both network and database the network version is applied.
|
||||
// VERSION = 0.5.0 -> P2P_NETWORK_VERSION = 1
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
// With version 1.2.1 we change to version 2 (new trade protocol)
|
||||
public static final int P2P_NETWORK_VERSION = 1;
|
||||
|
||||
// The version no. of the serialized data stored to disc. A change will break the serialization of old objects.
|
||||
@ -84,7 +84,8 @@ public class Version {
|
||||
// A taker will check the version of the offers to see if his version is compatible.
|
||||
// Offers created with the old version will become invalid and have to be canceled.
|
||||
// VERSION = 0.5.0 -> TRADE_PROTOCOL_VERSION = 1
|
||||
public static final int TRADE_PROTOCOL_VERSION = 1;
|
||||
// Version 1.2.1 -> TRADE_PROTOCOL_VERSION = 2
|
||||
public static final int TRADE_PROTOCOL_VERSION = 2;
|
||||
private static int p2pMessageVersion;
|
||||
|
||||
public static final String BSQ_TX_VERSION = "1";
|
||||
|
@ -36,9 +36,9 @@ message NetworkEnvelope {
|
||||
CloseConnectionMessage close_connection_message = 15;
|
||||
PrefixedSealedAndSignedMessage prefixed_sealed_and_signed_message = 16;
|
||||
|
||||
PayDepositRequest pay_deposit_request = 17;
|
||||
PublishDepositTxRequest publish_deposit_tx_request = 18;
|
||||
DepositTxPublishedMessage deposit_tx_published_message = 19;
|
||||
InputsForDepositTxRequest inputs_for_deposit_tx_request = 17;
|
||||
InputsForDepositTxResponse inputs_for_deposit_tx_response = 18;
|
||||
DepositTxMessage deposit_tx_message = 19;
|
||||
CounterCurrencyTransferStartedMessage counter_currency_transfer_started_message = 20;
|
||||
PayoutTxPublishedMessage payout_tx_published_message = 21;
|
||||
|
||||
@ -70,6 +70,11 @@ message NetworkEnvelope {
|
||||
BundleOfEnvelopes bundle_of_envelopes = 43;
|
||||
MediatedPayoutTxSignatureMessage mediated_payout_tx_signature_message = 44;
|
||||
MediatedPayoutTxPublishedMessage mediated_payout_tx_published_message = 45;
|
||||
|
||||
DelayedPayoutTxSignatureRequest delayed_payout_tx_signature_request = 46;
|
||||
DelayedPayoutTxSignatureResponse delayed_payout_tx_signature_response = 47;
|
||||
DepositTxAndDelayedPayoutTxMessage deposit_tx_and_delayed_payout_tx_message = 48;
|
||||
PeerPublishedDelayedPayoutTxMessage peer_published_delayed_payout_tx_message = 49;
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,6 +149,7 @@ message OfferAvailabilityResponse {
|
||||
string uid = 4;
|
||||
NodeAddress arbitrator = 5;
|
||||
NodeAddress mediator = 6;
|
||||
NodeAddress refund_agent = 7;
|
||||
}
|
||||
|
||||
message RefreshOfferMessage {
|
||||
@ -197,7 +203,7 @@ message PrefixedSealedAndSignedMessage {
|
||||
|
||||
// trade
|
||||
|
||||
message PayDepositRequest {
|
||||
message InputsForDepositTxRequest {
|
||||
string trade_id = 1;
|
||||
NodeAddress sender_node_address = 2;
|
||||
int64 trade_amount = 3;
|
||||
@ -221,9 +227,11 @@ message PayDepositRequest {
|
||||
string uid = 21;
|
||||
bytes account_age_witness_signature_of_offer_id = 22;
|
||||
int64 current_date = 23;
|
||||
repeated NodeAddress accepted_refund_agent_node_addresses = 24;
|
||||
NodeAddress refund_agent_node_address = 25;
|
||||
}
|
||||
|
||||
message PublishDepositTxRequest {
|
||||
message InputsForDepositTxResponse {
|
||||
string trade_id = 1;
|
||||
PaymentAccountPayload maker_payment_account_payload = 2;
|
||||
string maker_account_id = 3;
|
||||
@ -237,13 +245,42 @@ message PublishDepositTxRequest {
|
||||
string uid = 11;
|
||||
bytes account_age_witness_signature_of_prepared_deposit_tx = 12;
|
||||
int64 current_date = 13;
|
||||
int64 lock_time = 14;
|
||||
}
|
||||
|
||||
message DepositTxPublishedMessage {
|
||||
string trade_id = 1;
|
||||
bytes deposit_tx = 2;
|
||||
message DelayedPayoutTxSignatureRequest {
|
||||
string uid = 1;
|
||||
string trade_id = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
bytes delayed_payout_tx = 4;
|
||||
}
|
||||
|
||||
message DelayedPayoutTxSignatureResponse {
|
||||
string uid = 1;
|
||||
string trade_id = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
bytes delayed_payout_tx_signature = 4;
|
||||
}
|
||||
|
||||
message DepositTxAndDelayedPayoutTxMessage {
|
||||
string uid = 1;
|
||||
string trade_id = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
bytes deposit_tx = 4;
|
||||
bytes delayed_payout_tx = 5;
|
||||
}
|
||||
|
||||
message DepositTxMessage {
|
||||
string uid = 1;
|
||||
string trade_id = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
bytes deposit_tx = 4;
|
||||
}
|
||||
|
||||
message PeerPublishedDelayedPayoutTxMessage {
|
||||
string uid = 1;
|
||||
string trade_id = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
string uid = 4;
|
||||
}
|
||||
|
||||
message CounterCurrencyTransferStartedMessage {
|
||||
@ -279,8 +316,8 @@ message MediatedPayoutTxPublishedMessage {
|
||||
|
||||
message MediatedPayoutTxSignatureMessage {
|
||||
string uid = 1;
|
||||
bytes tx_signature = 2;
|
||||
string trade_id = 3;
|
||||
bytes tx_signature = 2;
|
||||
NodeAddress sender_node_address = 4;
|
||||
}
|
||||
|
||||
@ -290,6 +327,7 @@ enum SupportType {
|
||||
ARBITRATION = 0;
|
||||
MEDIATION = 1;
|
||||
TRADE = 2;
|
||||
REFUND = 3;
|
||||
}
|
||||
|
||||
message OpenNewDisputeMessage {
|
||||
@ -457,6 +495,7 @@ message StoragePayload {
|
||||
MailboxStoragePayload mailbox_storage_payload = 6;
|
||||
OfferPayload offer_payload = 7;
|
||||
TempProposalPayload temp_proposal_payload = 8;
|
||||
RefundAgent refund_agent = 9;
|
||||
}
|
||||
}
|
||||
|
||||
@ -550,6 +589,18 @@ message Mediator {
|
||||
map<string, string> extra_data = 9;
|
||||
}
|
||||
|
||||
message RefundAgent {
|
||||
NodeAddress node_address = 1;
|
||||
repeated string language_codes = 2;
|
||||
int64 registration_date = 3;
|
||||
string registration_signature = 4;
|
||||
bytes registration_pub_key = 5;
|
||||
PubKeyRing pub_key_ring = 6;
|
||||
string email_address = 7;
|
||||
string info = 8;
|
||||
map<string, string> extra_data = 9;
|
||||
}
|
||||
|
||||
message Filter {
|
||||
repeated string banned_node_address = 1;
|
||||
repeated string banned_offer_ids = 2;
|
||||
@ -568,6 +619,7 @@ message Filter {
|
||||
string disable_dao_below_version = 15;
|
||||
string disable_trade_below_version = 16;
|
||||
repeated string mediators = 17;
|
||||
repeated string refundAgents = 18;
|
||||
}
|
||||
|
||||
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older
|
||||
@ -670,8 +722,14 @@ message AccountAgeWitness {
|
||||
}
|
||||
|
||||
message SignedWitness {
|
||||
bool signed_by_arbitrator = 1;
|
||||
bytes witness_hash = 2;
|
||||
enum VerificationMethod {
|
||||
PB_ERROR = 0;
|
||||
ARBITRATOR = 1;
|
||||
TRADE = 2;
|
||||
}
|
||||
|
||||
VerificationMethod verification_method = 1;
|
||||
bytes account_age_witness_hash = 2;
|
||||
bytes signature = 3;
|
||||
bytes signer_pub_key = 4;
|
||||
bytes witness_owner_pub_key = 5;
|
||||
@ -708,6 +766,9 @@ message Dispute {
|
||||
bool is_closed = 21;
|
||||
DisputeResult dispute_result = 22;
|
||||
string dispute_payout_tx_id = 23;
|
||||
SupportType support_type = 24;
|
||||
string mediators_dispute_result = 25;
|
||||
string delayed_payout_tx_id = 26;
|
||||
}
|
||||
|
||||
message Attachment {
|
||||
@ -759,7 +820,7 @@ message Contract {
|
||||
int64 trade_amount = 2;
|
||||
int64 trade_price = 3;
|
||||
string taker_fee_tx_id = 4;
|
||||
NodeAddress arbitrator_node_address = 5;
|
||||
reserved 5; // WAS: arbitrator_node_address
|
||||
bool is_buyer_maker_and_seller_taker = 6;
|
||||
string maker_account_id = 7;
|
||||
string taker_account_id = 8;
|
||||
@ -774,6 +835,8 @@ message Contract {
|
||||
bytes maker_multi_sig_pub_key = 17;
|
||||
bytes taker_multi_sig_pub_key = 18;
|
||||
NodeAddress mediator_node_address = 19;
|
||||
int64 lock_time = 20;
|
||||
NodeAddress refund_agent_node_address = 21;
|
||||
}
|
||||
|
||||
message RawTransactionInput {
|
||||
@ -793,6 +856,7 @@ enum AvailabilityResult {
|
||||
NO_MEDIATORS = 7;
|
||||
USER_IGNORED = 8;
|
||||
MISSING_MANDATORY_CAPABILITY = 9;
|
||||
NO_REFUND_AGENTS = 10;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -1074,6 +1138,7 @@ message PersistableEnvelope {
|
||||
UnconfirmedBsqChangeOutputList unconfirmed_bsq_change_output_list = 27;
|
||||
SignedWitnessStore signed_witness_store = 28;
|
||||
MediationDisputeList mediation_dispute_list = 29;
|
||||
RefundDisputeList refund_dispute_list = 30;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1198,6 +1263,7 @@ message OpenOffer {
|
||||
State state = 2;
|
||||
NodeAddress arbitrator_node_address = 3;
|
||||
NodeAddress mediator_node_address = 4;
|
||||
NodeAddress refund_agent_node_address = 5;
|
||||
}
|
||||
|
||||
message Tradable {
|
||||
@ -1220,13 +1286,13 @@ message Trade {
|
||||
MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST = 5;
|
||||
MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 6;
|
||||
TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST = 7;
|
||||
TAKER_PUBLISHED_DEPOSIT_TX = 8;
|
||||
TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 9;
|
||||
TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 10;
|
||||
TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 11;
|
||||
TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 12;
|
||||
MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 13;
|
||||
MAKER_SAW_DEPOSIT_TX_IN_NETWORK = 14;
|
||||
SELLER_PUBLISHED_DEPOSIT_TX = 8;
|
||||
SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 9;
|
||||
SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 10;
|
||||
SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 11;
|
||||
SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 12;
|
||||
BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 13;
|
||||
BUYER_SAW_DEPOSIT_TX_IN_NETWORK = 14;
|
||||
DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN = 15;
|
||||
BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED = 16;
|
||||
BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG = 17;
|
||||
@ -1266,6 +1332,9 @@ message Trade {
|
||||
MEDIATION_REQUESTED = 5;
|
||||
MEDIATION_STARTED_BY_PEER = 6;
|
||||
MEDIATION_CLOSED = 7;
|
||||
REFUND_REQUESTED = 8;
|
||||
REFUND_REQUEST_STARTED_BY_PEER = 9;
|
||||
REFUND_REQUEST_CLOSED = 10;
|
||||
}
|
||||
|
||||
enum TradePeriodState {
|
||||
@ -1305,6 +1374,11 @@ message Trade {
|
||||
string counter_currency_tx_id = 28;
|
||||
repeated ChatMessage chat_message = 29;
|
||||
MediationResultState mediation_result_state = 30;
|
||||
int64 lock_time = 31;
|
||||
bytes delayed_payout_tx_bytes = 32;
|
||||
NodeAddress refund_agent_node_address = 33;
|
||||
PubKeyRing refund_agent_pub_key_ring = 34;
|
||||
RefundResultState refund_result_state = 35;
|
||||
}
|
||||
|
||||
message BuyerAsMakerTrade {
|
||||
@ -1330,8 +1404,8 @@ message ProcessModel {
|
||||
PubKeyRing pub_key_ring = 4;
|
||||
string take_offer_fee_tx_id = 5;
|
||||
bytes payout_tx_signature = 6;
|
||||
repeated NodeAddress taker_accepted_arbitrator_node_addresses = 7;
|
||||
repeated NodeAddress taker_accepted_mediator_node_addresses = 8;
|
||||
reserved 7; // Not used anymore
|
||||
reserved 8; // Not used anymore
|
||||
bytes prepared_deposit_tx = 9;
|
||||
repeated RawTransactionInput raw_transaction_inputs = 10;
|
||||
int64 change_output_value = 11;
|
||||
@ -1376,6 +1450,10 @@ message MediationDisputeList {
|
||||
repeated Dispute dispute = 1;
|
||||
}
|
||||
|
||||
message RefundDisputeList {
|
||||
repeated Dispute dispute = 1;
|
||||
}
|
||||
|
||||
enum MediationResultState {
|
||||
PB_ERROR_MEDIATION_RESULT = 0;
|
||||
UNDEFINED_MEDIATION_RESULT = 1;
|
||||
@ -1395,6 +1473,12 @@ enum MediationResultState {
|
||||
PAYOUT_TX_SEEN_IN_NETWORK = 15;
|
||||
}
|
||||
|
||||
//todo
|
||||
enum RefundResultState {
|
||||
PB_ERROR_REFUND_RESULT = 0;
|
||||
UNDEFINED_REFUND_RESULT = 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Preferences
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -1454,6 +1538,7 @@ message PreferencesPayload {
|
||||
double buyer_security_deposit_as_percent_for_crypto = 52;
|
||||
int32 block_notify_port = 53;
|
||||
int32 css_theme = 54;
|
||||
bool tac_accepted_v120 = 55;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -1474,6 +1559,8 @@ message UserPayload {
|
||||
Mediator registered_mediator = 11;
|
||||
PriceAlertFilter price_alert_filter = 12;
|
||||
repeated MarketAlertFilter market_alert_filters = 13;
|
||||
repeated RefundAgent accepted_refund_agents = 14;
|
||||
RefundAgent registered_refund_agent = 15;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -26,6 +26,7 @@ import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.proto.ProtoUtil;
|
||||
import bisq.common.proto.persistable.PersistableEnvelope;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
@ -46,10 +47,24 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Value
|
||||
public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPayload, PersistableEnvelope,
|
||||
DateTolerantPayload, CapabilityRequiringPayload {
|
||||
|
||||
public enum VerificationMethod {
|
||||
ARBITRATOR,
|
||||
TRADE;
|
||||
|
||||
public static SignedWitness.VerificationMethod fromProto(protobuf.SignedWitness.VerificationMethod method) {
|
||||
return ProtoUtil.enumFromProto(SignedWitness.VerificationMethod.class, method.name());
|
||||
}
|
||||
|
||||
public static protobuf.SignedWitness.VerificationMethod toProtoMessage(SignedWitness.VerificationMethod method) {
|
||||
return protobuf.SignedWitness.VerificationMethod.valueOf(method.name());
|
||||
}
|
||||
}
|
||||
|
||||
private static final long TOLERANCE = TimeUnit.DAYS.toMillis(1);
|
||||
|
||||
private final boolean signedByArbitrator;
|
||||
private final byte[] witnessHash;
|
||||
private final VerificationMethod verificationMethod;
|
||||
private final byte[] accountAgeWitnessHash;
|
||||
private final byte[] signature;
|
||||
private final byte[] signerPubKey;
|
||||
private final byte[] witnessOwnerPubKey;
|
||||
@ -58,15 +73,15 @@ public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPa
|
||||
|
||||
transient private final byte[] hash;
|
||||
|
||||
public SignedWitness(boolean signedByArbitrator,
|
||||
byte[] witnessHash,
|
||||
public SignedWitness(VerificationMethod verificationMethod,
|
||||
byte[] accountAgeWitnessHash,
|
||||
byte[] signature,
|
||||
byte[] signerPubKey,
|
||||
byte[] witnessOwnerPubKey,
|
||||
long date,
|
||||
long tradeAmount) {
|
||||
this.signedByArbitrator = signedByArbitrator;
|
||||
this.witnessHash = witnessHash.clone();
|
||||
this.verificationMethod = verificationMethod;
|
||||
this.accountAgeWitnessHash = accountAgeWitnessHash.clone();
|
||||
this.signature = signature.clone();
|
||||
this.signerPubKey = signerPubKey.clone();
|
||||
this.witnessOwnerPubKey = witnessOwnerPubKey.clone();
|
||||
@ -80,7 +95,7 @@ public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPa
|
||||
// same peer to add more security as if that one would be colluding it would be not detected anyway. The total
|
||||
// number of signed trades with different peers is still available and can be considered more valuable data for
|
||||
// security.
|
||||
byte[] data = Utilities.concatenateByteArrays(witnessHash, signature);
|
||||
byte[] data = Utilities.concatenateByteArrays(accountAgeWitnessHash, signature);
|
||||
data = Utilities.concatenateByteArrays(data, signerPubKey);
|
||||
hash = Hash.getSha256Ripemd160hash(data);
|
||||
}
|
||||
@ -93,8 +108,8 @@ public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPa
|
||||
@Override
|
||||
public protobuf.PersistableNetworkPayload toProtoMessage() {
|
||||
final protobuf.SignedWitness.Builder builder = protobuf.SignedWitness.newBuilder()
|
||||
.setSignedByArbitrator(signedByArbitrator)
|
||||
.setWitnessHash(ByteString.copyFrom(witnessHash))
|
||||
.setVerificationMethod(VerificationMethod.toProtoMessage(verificationMethod))
|
||||
.setAccountAgeWitnessHash(ByteString.copyFrom(accountAgeWitnessHash))
|
||||
.setSignature(ByteString.copyFrom(signature))
|
||||
.setSignerPubKey(ByteString.copyFrom(signerPubKey))
|
||||
.setWitnessOwnerPubKey(ByteString.copyFrom(witnessOwnerPubKey))
|
||||
@ -108,8 +123,9 @@ public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPa
|
||||
}
|
||||
|
||||
public static SignedWitness fromProto(protobuf.SignedWitness proto) {
|
||||
return new SignedWitness(proto.getSignedByArbitrator(),
|
||||
proto.getWitnessHash().toByteArray(),
|
||||
return new SignedWitness(
|
||||
SignedWitness.VerificationMethod.fromProto(proto.getVerificationMethod()),
|
||||
proto.getAccountAgeWitnessHash().toByteArray(),
|
||||
proto.getSignature().toByteArray(),
|
||||
proto.getSignerPubKey().toByteArray(),
|
||||
proto.getWitnessOwnerPubKey().toByteArray(),
|
||||
@ -124,7 +140,7 @@ public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPa
|
||||
|
||||
@Override
|
||||
public boolean isDateInTolerance(Clock clock) {
|
||||
// We don't allow older or newer then 1 day.
|
||||
// We don't allow older or newer than 1 day.
|
||||
// Preventing forward dating is also important to protect against a sophisticated attack
|
||||
return Math.abs(new Date().getTime() - date) <= TOLERANCE;
|
||||
}
|
||||
@ -145,6 +161,9 @@ public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPa
|
||||
return hash;
|
||||
}
|
||||
|
||||
public boolean isSignedByArbitrator() {
|
||||
return verificationMethod == VerificationMethod.ARBITRATOR;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
@ -157,8 +176,8 @@ public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPa
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SignedWitness{" +
|
||||
",\n signedByArbitrator=" + signedByArbitrator +
|
||||
",\n witnessHash=" + Utilities.bytesAsHexString(witnessHash) +
|
||||
",\n verificationMethod=" + verificationMethod +
|
||||
",\n witnessHash=" + Utilities.bytesAsHexString(accountAgeWitnessHash) +
|
||||
",\n signature=" + Utilities.bytesAsHexString(signature) +
|
||||
",\n signerPubKey=" + Utilities.bytesAsHexString(signerPubKey) +
|
||||
",\n witnessOwnerPubKey=" + Utilities.bytesAsHexString(witnessOwnerPubKey) +
|
||||
|
@ -18,23 +18,17 @@
|
||||
package bisq.core.account.sign;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitness;
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.payment.ChargeBackRisk;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeResult;
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
||||
import bisq.core.support.dispute.arbitration.BuyerDataItem;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.network.p2p.BootstrapListener;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.crypto.CryptoException;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
@ -57,30 +51,24 @@ import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Slf4j
|
||||
public class SignedWitnessService {
|
||||
public static final long CHARGEBACK_SAFETY_DAYS = 30;
|
||||
public static final long SIGNER_AGE_DAYS = 30;
|
||||
public static final long SIGNER_AGE = SIGNER_AGE_DAYS * ChronoUnit.DAYS.getDuration().toMillis();
|
||||
|
||||
private final KeyRing keyRing;
|
||||
private final P2PService p2PService;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private final ArbitrationManager arbitrationManager;
|
||||
private final ChargeBackRisk chargeBackRisk;
|
||||
private final User user;
|
||||
|
||||
private final Map<P2PDataStorage.ByteArray, SignedWitness> signedWitnessMap = new HashMap<>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -88,18 +76,14 @@ public class SignedWitnessService {
|
||||
@Inject
|
||||
public SignedWitnessService(KeyRing keyRing,
|
||||
P2PService p2PService,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
ArbitratorManager arbitratorManager,
|
||||
SignedWitnessStorageService signedWitnessStorageService,
|
||||
AppendOnlyDataStoreService appendOnlyDataStoreService,
|
||||
ArbitrationManager arbitrationManager,
|
||||
ChargeBackRisk chargeBackRisk) {
|
||||
User user) {
|
||||
this.keyRing = keyRing;
|
||||
this.p2PService = p2PService;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
this.chargeBackRisk = chargeBackRisk;
|
||||
this.user = user;
|
||||
|
||||
// We need to add that early (before onAllServicesInitialized) as it will be used at startup.
|
||||
appendOnlyDataStoreService.addService(signedWitnessStorageService);
|
||||
@ -121,15 +105,45 @@ public class SignedWitnessService {
|
||||
if (e instanceof SignedWitness)
|
||||
addToMap((SignedWitness) e);
|
||||
});
|
||||
|
||||
if (p2PService.isBootstrapped()) {
|
||||
onBootstrapComplete();
|
||||
} else {
|
||||
p2PService.addP2PServiceListener(new BootstrapListener() {
|
||||
@Override
|
||||
public void onUpdatedDataReceived() {
|
||||
onBootstrapComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void onBootstrapComplete() {
|
||||
if (user.getRegisteredArbitrator() != null) {
|
||||
UserThread.runAfter(this::doRepublishAllSignedWitnesses, 60);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public List<Long> getMyWitnessAgeList(PaymentAccountPayload myPaymentAccountPayload) {
|
||||
AccountAgeWitness accountAgeWitness = accountAgeWitnessService.getMyWitness(myPaymentAccountPayload);
|
||||
/**
|
||||
* List of dates as long when accountAgeWitness was signed
|
||||
*/
|
||||
public List<Long> getVerifiedWitnessDateList(AccountAgeWitness accountAgeWitness) {
|
||||
return getSignedWitnessSet(accountAgeWitness).stream()
|
||||
.filter(this::verifySignature)
|
||||
.map(SignedWitness::getDate)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* List of dates as long when accountAgeWitness was signed
|
||||
* Not verifying that signatures are correct
|
||||
*/
|
||||
public List<Long> getWitnessDateList(AccountAgeWitness accountAgeWitness) {
|
||||
// We do not validate as it would not make sense to cheat one self...
|
||||
return getSignedWitnessSet(accountAgeWitness).stream()
|
||||
.map(SignedWitness::getDate)
|
||||
@ -137,24 +151,26 @@ public class SignedWitnessService {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
public List<Long> getVerifiedWitnessAgeList(AccountAgeWitness accountAgeWitness) {
|
||||
return signedWitnessMap.values().stream()
|
||||
.filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
|
||||
.filter(this::verifySignature)
|
||||
.map(SignedWitness::getDate)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
public boolean isSignedByArbitrator(AccountAgeWitness accountAgeWitness) {
|
||||
return getSignedWitnessSet(accountAgeWitness).stream()
|
||||
.map(SignedWitness::isSignedByArbitrator)
|
||||
.findAny()
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
// Arbitrators sign with EC key
|
||||
public SignedWitness signAccountAgeWitness(Coin tradeAmount,
|
||||
public void signAccountAgeWitness(Coin tradeAmount,
|
||||
AccountAgeWitness accountAgeWitness,
|
||||
ECKey key,
|
||||
PublicKey peersPubKey) {
|
||||
if (isSignedAccountAgeWitness(accountAgeWitness)) {
|
||||
log.warn("Arbitrator trying to sign already signed accountagewitness {}", accountAgeWitness.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
String accountAgeWitnessHashAsHex = Utilities.encodeToHex(accountAgeWitness.getHash());
|
||||
String signatureBase64 = key.signMessage(accountAgeWitnessHashAsHex);
|
||||
SignedWitness signedWitness = new SignedWitness(true,
|
||||
SignedWitness signedWitness = new SignedWitness(SignedWitness.VerificationMethod.ARBITRATOR,
|
||||
accountAgeWitness.getHash(),
|
||||
signatureBase64.getBytes(Charsets.UTF_8),
|
||||
key.getPubKey(),
|
||||
@ -162,15 +178,20 @@ public class SignedWitnessService {
|
||||
new Date().getTime(),
|
||||
tradeAmount.value);
|
||||
publishSignedWitness(signedWitness);
|
||||
return signedWitness;
|
||||
log.info("Arbitrator signed witness {}", signedWitness.toString());
|
||||
}
|
||||
|
||||
// Any peer can sign with DSA key
|
||||
public SignedWitness signAccountAgeWitness(Coin tradeAmount,
|
||||
public void signAccountAgeWitness(Coin tradeAmount,
|
||||
AccountAgeWitness accountAgeWitness,
|
||||
PublicKey peersPubKey) throws CryptoException {
|
||||
if (isSignedAccountAgeWitness(accountAgeWitness)) {
|
||||
log.warn("Trader trying to sign already signed accountagewitness {}", accountAgeWitness.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] signature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), accountAgeWitness.getHash());
|
||||
SignedWitness signedWitness = new SignedWitness(false,
|
||||
SignedWitness signedWitness = new SignedWitness(SignedWitness.VerificationMethod.TRADE,
|
||||
accountAgeWitness.getHash(),
|
||||
signature,
|
||||
keyRing.getSignatureKeyPair().getPublic().getEncoded(),
|
||||
@ -178,7 +199,7 @@ public class SignedWitnessService {
|
||||
new Date().getTime(),
|
||||
tradeAmount.value);
|
||||
publishSignedWitness(signedWitness);
|
||||
return signedWitness;
|
||||
log.info("Trader signed witness {}", signedWitness.toString());
|
||||
}
|
||||
|
||||
public boolean verifySignature(SignedWitness signedWitness) {
|
||||
@ -191,7 +212,7 @@ public class SignedWitnessService {
|
||||
|
||||
private boolean verifySignatureWithECKey(SignedWitness signedWitness) {
|
||||
try {
|
||||
String message = Utilities.encodeToHex(signedWitness.getWitnessHash());
|
||||
String message = Utilities.encodeToHex(signedWitness.getAccountAgeWitnessHash());
|
||||
String signatureBase64 = new String(signedWitness.getSignature(), Charsets.UTF_8);
|
||||
ECKey key = ECKey.fromPublicOnly(signedWitness.getSignerPubKey());
|
||||
if (arbitratorManager.isPublicKeyInList(Utilities.encodeToHex(key.getPubKey()))) {
|
||||
@ -211,7 +232,7 @@ public class SignedWitnessService {
|
||||
private boolean verifySignatureWithDSAKey(SignedWitness signedWitness) {
|
||||
try {
|
||||
PublicKey signaturePubKey = Sig.getPublicKeyFromBytes(signedWitness.getSignerPubKey());
|
||||
Sig.verify(signaturePubKey, signedWitness.getWitnessHash(), signedWitness.getSignature());
|
||||
Sig.verify(signaturePubKey, signedWitness.getAccountAgeWitnessHash(), signedWitness.getSignature());
|
||||
return true;
|
||||
} catch (CryptoException e) {
|
||||
log.warn("verifySignature signedWitness failed. signedWitness={}", signedWitness);
|
||||
@ -220,9 +241,9 @@ public class SignedWitnessService {
|
||||
}
|
||||
}
|
||||
|
||||
public Set<SignedWitness> getSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
|
||||
private Set<SignedWitness> getSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
|
||||
return signedWitnessMap.values().stream()
|
||||
.filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
|
||||
.filter(e -> Arrays.equals(e.getAccountAgeWitnessHash(), accountAgeWitness.getHash()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@ -230,7 +251,7 @@ public class SignedWitnessService {
|
||||
public Set<SignedWitness> getArbitratorsSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
|
||||
return signedWitnessMap.values().stream()
|
||||
.filter(SignedWitness::isSignedByArbitrator)
|
||||
.filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
|
||||
.filter(e -> Arrays.equals(e.getAccountAgeWitnessHash(), accountAgeWitness.getHash()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@ -238,13 +259,13 @@ public class SignedWitnessService {
|
||||
public Set<SignedWitness> getTrustedPeerSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
|
||||
return signedWitnessMap.values().stream()
|
||||
.filter(e -> !e.isSignedByArbitrator())
|
||||
.filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
|
||||
.filter(e -> Arrays.equals(e.getAccountAgeWitnessHash(), accountAgeWitness.getHash()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
// We go one level up by using the signer Key to lookup for SignedWitness objects which contain the signerKey as
|
||||
// witnessOwnerPubKey
|
||||
public Set<SignedWitness> getSignedWitnessSetByOwnerPubKey(byte[] ownerPubKey,
|
||||
private Set<SignedWitness> getSignedWitnessSetByOwnerPubKey(byte[] ownerPubKey,
|
||||
Stack<P2PDataStorage.ByteArray> excluded) {
|
||||
return signedWitnessMap.values().stream()
|
||||
.filter(e -> Arrays.equals(e.getWitnessOwnerPubKey(), ownerPubKey))
|
||||
@ -252,17 +273,27 @@ public class SignedWitnessService {
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public boolean isSignedAccountAgeWitness(AccountAgeWitness accountAgeWitness) {
|
||||
return isSignerAccountAgeWitness(accountAgeWitness, new Date().getTime() + SIGNER_AGE);
|
||||
}
|
||||
|
||||
public boolean isSignerAccountAgeWitness(AccountAgeWitness accountAgeWitness) {
|
||||
return isSignerAccountAgeWitness(accountAgeWitness, new Date().getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the accountAgeWitness has a valid signature from a peer/arbitrator.
|
||||
* @param accountAgeWitness
|
||||
* @return true if accountAgeWitness is valid, false otherwise.
|
||||
* Checks whether the accountAgeWitness has a valid signature from a peer/arbitrator and is allowed to sign
|
||||
* other accounts.
|
||||
*
|
||||
* @param accountAgeWitness accountAgeWitness
|
||||
* @param time time of signing
|
||||
* @return true if accountAgeWitness is allowed to sign at time, false otherwise.
|
||||
*/
|
||||
public boolean isValidAccountAgeWitness(AccountAgeWitness accountAgeWitness) {
|
||||
private boolean isSignerAccountAgeWitness(AccountAgeWitness accountAgeWitness, long time) {
|
||||
Stack<P2PDataStorage.ByteArray> excludedPubKeys = new Stack<>();
|
||||
long now = new Date().getTime();
|
||||
Set<SignedWitness> signedWitnessSet = getSignedWitnessSet(accountAgeWitness);
|
||||
for (SignedWitness signedWitness : signedWitnessSet) {
|
||||
if (isValidSignedWitnessInternal(signedWitness, now, excludedPubKeys)) {
|
||||
if (isValidSignerWitnessInternal(signedWitness, time, excludedPubKeys)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -272,12 +303,13 @@ public class SignedWitnessService {
|
||||
|
||||
/**
|
||||
* Helper to isValidAccountAgeWitness(accountAgeWitness)
|
||||
*
|
||||
* @param signedWitness the signedWitness to validate
|
||||
* @param childSignedWitnessDateMillis the date the child SignedWitness was signed or current time if it is a leave.
|
||||
* @param excludedPubKeys stack to prevent recursive loops
|
||||
* @return true if signedWitness is valid, false otherwise.
|
||||
*/
|
||||
private boolean isValidSignedWitnessInternal(SignedWitness signedWitness,
|
||||
private boolean isValidSignerWitnessInternal(SignedWitness signedWitness,
|
||||
long childSignedWitnessDateMillis,
|
||||
Stack<P2PDataStorage.ByteArray> excludedPubKeys) {
|
||||
if (!verifySignature(signedWitness)) {
|
||||
@ -299,7 +331,7 @@ public class SignedWitnessService {
|
||||
// Iterate over signedWitness signers
|
||||
Set<SignedWitness> signerSignedWitnessSet = getSignedWitnessSetByOwnerPubKey(signedWitness.getSignerPubKey(), excludedPubKeys);
|
||||
for (SignedWitness signerSignedWitness : signerSignedWitnessSet) {
|
||||
if (isValidSignedWitnessInternal(signerSignedWitness, signedWitness.getDate(), excludedPubKeys)) {
|
||||
if (isValidSignerWitnessInternal(signerSignedWitness, signedWitness.getDate(), excludedPubKeys)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -311,7 +343,8 @@ public class SignedWitnessService {
|
||||
}
|
||||
|
||||
private boolean verifyDate(SignedWitness signedWitness, long childSignedWitnessDateMillis) {
|
||||
long childSignedWitnessDateMinusChargebackPeriodMillis = Instant.ofEpochMilli(childSignedWitnessDateMillis).minus(CHARGEBACK_SAFETY_DAYS, ChronoUnit.DAYS).toEpochMilli();
|
||||
long childSignedWitnessDateMinusChargebackPeriodMillis = Instant.ofEpochMilli(
|
||||
childSignedWitnessDateMillis).minus(SIGNER_AGE, ChronoUnit.MILLIS).toEpochMilli();
|
||||
long signedWitnessDateMillis = signedWitness.getDate();
|
||||
return signedWitnessDateMillis <= childSignedWitnessDateMinusChargebackPeriodMillis;
|
||||
}
|
||||
@ -322,49 +355,18 @@ public class SignedWitnessService {
|
||||
|
||||
@VisibleForTesting
|
||||
void addToMap(SignedWitness signedWitness) {
|
||||
// TODO: Perhaps filter out all but one signedwitness per accountagewitness
|
||||
signedWitnessMap.putIfAbsent(signedWitness.getHashAsByteArray(), signedWitness);
|
||||
}
|
||||
|
||||
private void publishSignedWitness(SignedWitness signedWitness) {
|
||||
if (!signedWitnessMap.containsKey(signedWitness.getHashAsByteArray())) {
|
||||
log.info("broadcast signed witness {}", signedWitness.toString());
|
||||
p2PService.addPersistableNetworkPayload(signedWitness, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrator signing
|
||||
public List<BuyerDataItem> getBuyerPaymentAccounts(long safeDate, PaymentMethod paymentMethod) {
|
||||
return arbitrationManager.getDisputesAsObservableList().stream()
|
||||
.filter(dispute -> dispute.getContract().getPaymentMethodId().equals(paymentMethod.getId()))
|
||||
.filter(this::hasChargebackRisk)
|
||||
.filter(this::isBuyerWinner)
|
||||
.map(this::getBuyerData)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(buyerDataItem -> buyerDataItem.getAccountAgeWitness().getDate() < safeDate)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
private void doRepublishAllSignedWitnesses() {
|
||||
signedWitnessMap.forEach((e, signedWitness) -> p2PService.addPersistableNetworkPayload(signedWitness, true));
|
||||
}
|
||||
|
||||
private boolean hasChargebackRisk(Dispute dispute) {
|
||||
return chargeBackRisk.hasChargebackRisk(dispute.getContract().getPaymentMethodId(),
|
||||
dispute.getContract().getOfferPayload().getCurrencyCode());
|
||||
}
|
||||
|
||||
private boolean isBuyerWinner(Dispute dispute) {
|
||||
return dispute.getDisputeResultProperty().get().getWinner() == DisputeResult.Winner.BUYER;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BuyerDataItem getBuyerData(Dispute dispute) {
|
||||
PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
|
||||
PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload();
|
||||
Optional<AccountAgeWitness> optionalWitness = accountAgeWitnessService
|
||||
.findWitness(buyerPaymentAccountPaload, buyerPubKeyRing);
|
||||
return optionalWitness.map(witness -> new BuyerDataItem(
|
||||
buyerPaymentAccountPaload,
|
||||
witness,
|
||||
dispute.getContract().getTradeAmount(),
|
||||
dispute.getContract().getSellerPubKeyRing().getSignaturePubKey()))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* 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.account.witness;
|
||||
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferRestrictions;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.trade.Trade;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class AccountAgeRestrictions {
|
||||
private static final long SAFE_ACCOUNT_AGE_DATE = Utilities.getUTCDate(2019, GregorianCalendar.MARCH, 1).getTime();
|
||||
|
||||
public static boolean isMakersAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, Offer offer) {
|
||||
long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMakersAccountAge(offer, new Date());
|
||||
return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
|
||||
}
|
||||
|
||||
public static boolean isTradePeersAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, Trade trade) {
|
||||
long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getTradingPeersAccountAge(trade);
|
||||
return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
|
||||
}
|
||||
|
||||
public static boolean isMyAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, PaymentAccount myPaymentAccount) {
|
||||
long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMyAccountAge(myPaymentAccount.getPaymentAccountPayload());
|
||||
return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
|
||||
}
|
||||
|
||||
public static long getMyTradeLimitAtCreateOffer(AccountAgeWitnessService accountAgeWitnessService,
|
||||
PaymentAccount paymentAccount,
|
||||
String currencyCode,
|
||||
OfferPayload.Direction direction) {
|
||||
if (direction == OfferPayload.Direction.BUY &&
|
||||
PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod(), currencyCode) &&
|
||||
AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, paymentAccount)) {
|
||||
return OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
|
||||
} else {
|
||||
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static long getMyTradeLimitAtTakeOffer(AccountAgeWitnessService accountAgeWitnessService,
|
||||
PaymentAccount paymentAccount,
|
||||
Offer offer,
|
||||
String currencyCode,
|
||||
OfferPayload.Direction direction) {
|
||||
if (direction == OfferPayload.Direction.BUY &&
|
||||
PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod(), currencyCode) &&
|
||||
AccountAgeRestrictions.isMakersAccountAgeImmature(accountAgeWitnessService, offer)) {
|
||||
// Taker is seller
|
||||
return OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
|
||||
} else if (direction == OfferPayload.Direction.SELL &&
|
||||
PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod(), currencyCode) &&
|
||||
AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, paymentAccount)) {
|
||||
// Taker is buyer
|
||||
return OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
|
||||
} else {
|
||||
// Offers with no chargeback risk or mature buyer accounts
|
||||
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode);
|
||||
}
|
||||
}
|
||||
}
|
@ -17,12 +17,22 @@
|
||||
|
||||
package bisq.core.account.witness;
|
||||
|
||||
import bisq.core.account.sign.SignedWitnessService;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.filter.PaymentAccountFilter;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferRestrictions;
|
||||
import bisq.core.payment.AssetAccount;
|
||||
import bisq.core.payment.ChargeBackRisk;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
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.Trade;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
import bisq.core.user.User;
|
||||
@ -43,6 +53,7 @@ import bisq.common.util.MathUtils;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ -52,10 +63,14 @@ import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -65,16 +80,40 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
public class AccountAgeWitnessService {
|
||||
private static final Date RELEASE = Utilities.getUTCDate(2017, GregorianCalendar.NOVEMBER, 11);
|
||||
public static final Date FULL_ACTIVATION = Utilities.getUTCDate(2018, GregorianCalendar.FEBRUARY, 15);
|
||||
private static final long SAFE_ACCOUNT_AGE_DATE = Utilities.getUTCDate(2019, GregorianCalendar.MARCH, 1).getTime();
|
||||
|
||||
public enum AccountAge {
|
||||
UNVERIFIED,
|
||||
LESS_ONE_MONTH,
|
||||
ONE_TO_TWO_MONTHS,
|
||||
TWO_MONTHS_OR_MORE
|
||||
}
|
||||
|
||||
public enum SignState {
|
||||
UNSIGNED(Res.get("offerbook.timeSinceSigning.notSigned")),
|
||||
ARBITRATOR(Res.get("offerbook.timeSinceSigning.info.arbitrator")),
|
||||
PEER_INITIAL(Res.get("offerbook.timeSinceSigning.info.peer")),
|
||||
PEER_LIMIT_LIFTED(Res.get("offerbook.timeSinceSigning.info.peerLimitLifted")),
|
||||
PEER_SIGNER(Res.get("offerbook.timeSinceSigning.info.signer"));
|
||||
|
||||
private String presentation;
|
||||
|
||||
SignState(String presentation) {
|
||||
this.presentation = presentation;
|
||||
}
|
||||
|
||||
public String getPresentation() {
|
||||
return presentation;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final KeyRing keyRing;
|
||||
private final P2PService p2PService;
|
||||
private final User user;
|
||||
private final SignedWitnessService signedWitnessService;
|
||||
private final ChargeBackRisk chargeBackRisk;
|
||||
private final FilterManager filterManager;
|
||||
|
||||
private final Map<P2PDataStorage.ByteArray, AccountAgeWitness> accountAgeWitnessMap = new HashMap<>();
|
||||
|
||||
@ -85,12 +124,20 @@ public class AccountAgeWitnessService {
|
||||
|
||||
|
||||
@Inject
|
||||
public AccountAgeWitnessService(KeyRing keyRing, P2PService p2PService, User user,
|
||||
public AccountAgeWitnessService(KeyRing keyRing,
|
||||
P2PService p2PService,
|
||||
User user,
|
||||
SignedWitnessService signedWitnessService,
|
||||
ChargeBackRisk chargeBackRisk,
|
||||
AccountAgeWitnessStorageService accountAgeWitnessStorageService,
|
||||
AppendOnlyDataStoreService appendOnlyDataStoreService) {
|
||||
AppendOnlyDataStoreService appendOnlyDataStoreService,
|
||||
FilterManager filterManager) {
|
||||
this.keyRing = keyRing;
|
||||
this.p2PService = p2PService;
|
||||
this.user = user;
|
||||
this.signedWitnessService = signedWitnessService;
|
||||
this.chargeBackRisk = chargeBackRisk;
|
||||
this.filterManager = filterManager;
|
||||
|
||||
// We need to add that early (before onAllServicesInitialized) as it will be used at startup.
|
||||
appendOnlyDataStoreService.addService(accountAgeWitnessStorageService);
|
||||
@ -164,7 +211,8 @@ public class AccountAgeWitnessService {
|
||||
return new AccountAgeWitness(hash, new Date().getTime());
|
||||
}
|
||||
|
||||
public Optional<AccountAgeWitness> findWitness(PaymentAccountPayload paymentAccountPayload, PubKeyRing pubKeyRing) {
|
||||
private Optional<AccountAgeWitness> findWitness(PaymentAccountPayload paymentAccountPayload,
|
||||
PubKeyRing pubKeyRing) {
|
||||
byte[] accountInputDataWithSalt = getAccountInputDataWithSalt(paymentAccountPayload);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt,
|
||||
pubKeyRing.getSignaturePubKeyBytes()));
|
||||
@ -172,6 +220,19 @@ public class AccountAgeWitnessService {
|
||||
return getWitnessByHash(hash);
|
||||
}
|
||||
|
||||
private Optional<AccountAgeWitness> findWitness(Offer offer) {
|
||||
final Optional<String> accountAgeWitnessHash = offer.getAccountAgeWitnessHashAsHex();
|
||||
return accountAgeWitnessHash.isPresent() ?
|
||||
getWitnessByHashAsHex(accountAgeWitnessHash.get()) :
|
||||
Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
|
||||
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
|
||||
return (tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPubKeyRing() == null) ?
|
||||
Optional.empty() : findWitness(tradingPeer.getPaymentAccountPayload(), tradingPeer.getPubKeyRing());
|
||||
}
|
||||
|
||||
private Optional<AccountAgeWitness> getWitnessByHash(byte[] hash) {
|
||||
P2PDataStorage.ByteArray hashAsByteArray = new P2PDataStorage.ByteArray(hash);
|
||||
|
||||
@ -186,19 +247,57 @@ public class AccountAgeWitnessService {
|
||||
return getWitnessByHash(Utilities.decodeFromHex(hashAsHex));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Witness age
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public long getAccountAge(AccountAgeWitness accountAgeWitness, Date now) {
|
||||
log.debug("getAccountAge now={}, accountAgeWitness.getDate()={}", now.getTime(), accountAgeWitness.getDate());
|
||||
return now.getTime() - accountAgeWitness.getDate();
|
||||
}
|
||||
|
||||
// Return -1 if no witness found
|
||||
public long getAccountAge(PaymentAccountPayload paymentAccountPayload, PubKeyRing pubKeyRing) {
|
||||
return findWitness(paymentAccountPayload, pubKeyRing)
|
||||
.map(accountAgeWitness -> getAccountAge(accountAgeWitness, new Date()))
|
||||
.orElse(-1L);
|
||||
}
|
||||
|
||||
public AccountAge getAccountAgeCategory(long accountAge) {
|
||||
if (accountAge < TimeUnit.DAYS.toMillis(30)) {
|
||||
public long getAccountAge(Offer offer) {
|
||||
return findWitness(offer)
|
||||
.map(accountAgeWitness -> getAccountAge(accountAgeWitness, new Date()))
|
||||
.orElse(-1L);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Signed age
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Return -1 if not signed
|
||||
public long getWitnessSignAge(AccountAgeWitness accountAgeWitness, Date now) {
|
||||
List<Long> dates = signedWitnessService.getVerifiedWitnessDateList(accountAgeWitness);
|
||||
if (dates.isEmpty()) {
|
||||
return -1L;
|
||||
} else {
|
||||
return now.getTime() - dates.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Return -1 if not signed
|
||||
public long getWitnessSignAge(Offer offer, Date now) {
|
||||
return findWitness(offer)
|
||||
.map(witness -> getWitnessSignAge(witness, now))
|
||||
.orElse(-1L);
|
||||
}
|
||||
|
||||
public AccountAge getPeersAccountAgeCategory(long peersAccountAge) {
|
||||
return getAccountAgeCategory(peersAccountAge);
|
||||
}
|
||||
|
||||
private AccountAge getAccountAgeCategory(long accountAge) {
|
||||
if (accountAge < 0) {
|
||||
return AccountAge.UNVERIFIED;
|
||||
} else if (accountAge < TimeUnit.DAYS.toMillis(30)) {
|
||||
return AccountAge.LESS_ONE_MONTH;
|
||||
} else if (accountAge < TimeUnit.DAYS.toMillis(60)) {
|
||||
return AccountAge.ONE_TO_TWO_MONTHS;
|
||||
@ -207,15 +306,23 @@ public class AccountAgeWitnessService {
|
||||
}
|
||||
}
|
||||
|
||||
private long getTradeLimit(Coin maxTradeLimit, String currencyCode, Optional<AccountAgeWitness> accountAgeWitnessOptional, Date now) {
|
||||
// Checks trade limit based on time since signing of AccountAgeWitness
|
||||
private long getTradeLimit(Coin maxTradeLimit,
|
||||
String currencyCode,
|
||||
AccountAgeWitness accountAgeWitness,
|
||||
AccountAge accountAgeCategory,
|
||||
OfferPayload.Direction direction,
|
||||
PaymentMethod paymentMethod) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
double factor;
|
||||
|
||||
final long accountAge = getAccountAge((accountAgeWitnessOptional.get()), now);
|
||||
AccountAge accountAgeCategory = accountAgeWitnessOptional
|
||||
.map(accountAgeWitness -> getAccountAgeCategory(accountAge))
|
||||
.orElse(AccountAge.LESS_ONE_MONTH);
|
||||
|
||||
boolean isRisky = PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode);
|
||||
if (!isRisky || direction == OfferPayload.Direction.SELL) {
|
||||
// Get age of witness rather than time since signing for non risky payment methods and for selling
|
||||
accountAgeCategory = getAccountAgeCategory(getAccountAge(accountAgeWitness, new Date()));
|
||||
}
|
||||
long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
|
||||
if (direction == OfferPayload.Direction.BUY && isRisky) {
|
||||
// Used only for bying of BTC with risky payment methods
|
||||
switch (accountAgeCategory) {
|
||||
case TWO_MONTHS_OR_MORE:
|
||||
factor = 1;
|
||||
@ -224,24 +331,60 @@ public class AccountAgeWitnessService {
|
||||
factor = 0.5;
|
||||
break;
|
||||
case LESS_ONE_MONTH:
|
||||
case UNVERIFIED:
|
||||
default:
|
||||
factor = 0;
|
||||
}
|
||||
} else {
|
||||
// Used by non risky payment methods and for selling BTC with risky methods
|
||||
switch (accountAgeCategory) {
|
||||
case TWO_MONTHS_OR_MORE:
|
||||
factor = 1;
|
||||
break;
|
||||
case ONE_TO_TWO_MONTHS:
|
||||
factor = 0.5;
|
||||
break;
|
||||
case LESS_ONE_MONTH:
|
||||
case UNVERIFIED:
|
||||
factor = 0.25;
|
||||
break;
|
||||
default:
|
||||
factor = 0;
|
||||
}
|
||||
}
|
||||
if (factor > 0) {
|
||||
limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor);
|
||||
}
|
||||
|
||||
final long limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor);
|
||||
log.debug("accountAgeCategory={}, accountAge={}, limit={}, factor={}, accountAgeWitnessHash={}",
|
||||
log.debug("accountAgeCategory={}, limit={}, factor={}, accountAgeWitnessHash={}",
|
||||
accountAgeCategory,
|
||||
accountAge / TimeUnit.DAYS.toMillis(1) + " days",
|
||||
Coin.valueOf(limit).toFriendlyString(),
|
||||
factor,
|
||||
accountAgeWitnessOptional.map(accountAgeWitness -> Utilities.bytesAsHexString(accountAgeWitness.getHash())).orElse("accountAgeWitnessOptional not present"));
|
||||
Utilities.bytesAsHexString(accountAgeWitness.getHash()));
|
||||
return limit;
|
||||
} else {
|
||||
return maxTradeLimit.value;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Trade limit exceptions
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private boolean isImmature(AccountAgeWitness accountAgeWitness) {
|
||||
return accountAgeWitness.getDate() > SAFE_ACCOUNT_AGE_DATE;
|
||||
}
|
||||
|
||||
public boolean myHasTradeLimitException(PaymentAccount myPaymentAccount) {
|
||||
return hasTradeLimitException(getMyWitness(myPaymentAccount.getPaymentAccountPayload()));
|
||||
}
|
||||
|
||||
// There are no trade limits on accounts that
|
||||
// - are mature
|
||||
// - were signed by an arbitrator
|
||||
private boolean hasTradeLimitException(AccountAgeWitness accountAgeWitness) {
|
||||
return !isImmature(accountAgeWitness) || signedWitnessService.isSignedByArbitrator(accountAgeWitness);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// My witness
|
||||
@ -264,44 +407,27 @@ public class AccountAgeWitnessService {
|
||||
return getAccountAge(getMyWitness(paymentAccountPayload), new Date());
|
||||
}
|
||||
|
||||
public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode) {
|
||||
public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferPayload.Direction
|
||||
direction) {
|
||||
if (paymentAccount == null)
|
||||
return 0;
|
||||
|
||||
Optional<AccountAgeWitness> witnessOptional = Optional.of(getMyWitness(paymentAccount.getPaymentAccountPayload()));
|
||||
return getTradeLimit(paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode),
|
||||
AccountAgeWitness accountAgeWitness = getMyWitness(paymentAccount.getPaymentAccountPayload());
|
||||
Coin maxTradeLimit = paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode);
|
||||
if (hasTradeLimitException(accountAgeWitness)) {
|
||||
return maxTradeLimit.value;
|
||||
}
|
||||
final long accountSignAge = getWitnessSignAge(accountAgeWitness, new Date());
|
||||
AccountAge accountAgeCategory = getAccountAgeCategory(accountSignAge);
|
||||
|
||||
return getTradeLimit(maxTradeLimit,
|
||||
currencyCode,
|
||||
witnessOptional,
|
||||
new Date());
|
||||
accountAgeWitness,
|
||||
accountAgeCategory,
|
||||
direction,
|
||||
paymentAccount.getPaymentMethod());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Peers witness
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Return -1 if witness data is not found (old versions)
|
||||
public long getMakersAccountAge(Offer offer, Date peersCurrentDate) {
|
||||
final Optional<String> accountAgeWitnessHash = offer.getAccountAgeWitnessHashAsHex();
|
||||
final Optional<AccountAgeWitness> witnessByHashAsHex = accountAgeWitnessHash.isPresent() ?
|
||||
getWitnessByHashAsHex(accountAgeWitnessHash.get()) :
|
||||
Optional.empty();
|
||||
return witnessByHashAsHex
|
||||
.map(accountAgeWitness -> getAccountAge(accountAgeWitness, peersCurrentDate))
|
||||
.orElse(-1L);
|
||||
}
|
||||
|
||||
public long getTradingPeersAccountAge(Trade trade) {
|
||||
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
|
||||
if (tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPubKeyRing() == null) {
|
||||
// unexpected
|
||||
return -1;
|
||||
}
|
||||
|
||||
return getAccountAge(tradingPeer.getPaymentAccountPayload(), tradingPeer.getPubKeyRing());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Verification
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -343,7 +469,8 @@ public class AccountAgeWitnessService {
|
||||
return false;
|
||||
|
||||
// Check if the peers trade limit is not less than the trade amount
|
||||
if (!verifyPeersTradeLimit(trade, peersWitness, peersCurrentDate, errorMessageHandler)) {
|
||||
if (!verifyPeersTradeLimit(trade.getOffer(), trade.getTradeAmount(), peersWitness, peersCurrentDate,
|
||||
errorMessageHandler)) {
|
||||
log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload);
|
||||
return false;
|
||||
}
|
||||
@ -351,12 +478,22 @@ public class AccountAgeWitnessService {
|
||||
return verifySignature(peersPubKeyRing.getSignaturePubKey(), nonce, signature, errorMessageHandler);
|
||||
}
|
||||
|
||||
public boolean verifyPeersTradeAmount(Offer offer,
|
||||
Coin tradeAmount,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
checkNotNull(offer);
|
||||
return findWitness(offer)
|
||||
.map(witness -> verifyPeersTradeLimit(offer, tradeAmount, witness, new Date(), errorMessageHandler))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Package scope verification subroutines
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
boolean isDateAfterReleaseDate(long witnessDateAsLong, Date ageWitnessReleaseDate, ErrorMessageHandler errorMessageHandler) {
|
||||
boolean isDateAfterReleaseDate(long witnessDateAsLong,
|
||||
Date ageWitnessReleaseDate,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
// Release date minus 1 day as tolerance for not synced clocks
|
||||
Date releaseDateWithTolerance = new Date(ageWitnessReleaseDate.getTime() - TimeUnit.DAYS.toMillis(1));
|
||||
final Date witnessDate = new Date(witnessDateAsLong);
|
||||
@ -394,16 +531,23 @@ public class AccountAgeWitnessService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean verifyPeersTradeLimit(Trade trade,
|
||||
private boolean verifyPeersTradeLimit(Offer offer,
|
||||
Coin tradeAmount,
|
||||
AccountAgeWitness peersWitness,
|
||||
Date peersCurrentDate,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Offer offer = trade.getOffer();
|
||||
Coin tradeAmount = checkNotNull(trade.getTradeAmount());
|
||||
checkNotNull(offer);
|
||||
final String currencyCode = offer.getCurrencyCode();
|
||||
final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById(offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode);
|
||||
long peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, Optional.of(peersWitness), peersCurrentDate);
|
||||
long peersCurrentTradeLimit = defaultMaxTradeLimit.value;
|
||||
if (!hasTradeLimitException(peersWitness)) {
|
||||
final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate);
|
||||
AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge);
|
||||
OfferPayload.Direction direction = offer.isMyOffer(keyRing) ?
|
||||
offer.getMirroredDirection() : offer.getDirection();
|
||||
peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness,
|
||||
accountAgeCategory, direction, offer.getPaymentMethod());
|
||||
}
|
||||
// Makers current trade limit cannot be smaller than that in the offer
|
||||
boolean result = tradeAmount.value <= peersCurrentTradeLimit;
|
||||
if (!result) {
|
||||
@ -436,4 +580,139 @@ public class AccountAgeWitnessService {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Witness signing
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void arbitratorSignAccountAgeWitness(Coin tradeAmount,
|
||||
AccountAgeWitness accountAgeWitness,
|
||||
ECKey key,
|
||||
PublicKey peersPubKey) {
|
||||
signedWitnessService.signAccountAgeWitness(tradeAmount, accountAgeWitness, key, peersPubKey);
|
||||
}
|
||||
|
||||
public void traderSignPeersAccountAgeWitness(Trade trade) {
|
||||
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
||||
Coin tradeAmount = trade.getTradeAmount();
|
||||
checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
|
||||
PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey();
|
||||
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}", trade.toString());
|
||||
checkNotNull(tradeAmount, "Trade amount must not be null");
|
||||
checkNotNull(peersPubKey, "Peers pub key must not be null");
|
||||
|
||||
try {
|
||||
signedWitnessService.signAccountAgeWitness(tradeAmount, peersWitness, peersPubKey);
|
||||
} catch (CryptoException e) {
|
||||
log.warn("Trader failed to sign witness, exception {}", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrator signing
|
||||
public List<TraderDataItem> getTraderPaymentAccounts(long safeDate, PaymentMethod paymentMethod,
|
||||
List<Dispute> disputes) {
|
||||
return disputes.stream()
|
||||
.filter(dispute -> dispute.getContract().getPaymentMethodId().equals(paymentMethod.getId()))
|
||||
.filter(this::isNotFiltered)
|
||||
.filter(this::hasChargebackRisk)
|
||||
.filter(this::isBuyerWinner)
|
||||
.flatMap(this::getTraderData)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(traderDataItem ->
|
||||
!signedWitnessService.isSignedAccountAgeWitness(traderDataItem.getAccountAgeWitness()))
|
||||
.filter(traderDataItem -> traderDataItem.getAccountAgeWitness().getDate() < safeDate)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean isNotFiltered(Dispute dispute) {
|
||||
boolean isFiltered = filterManager.isNodeAddressBanned(dispute.getContract().getBuyerNodeAddress()) ||
|
||||
filterManager.isNodeAddressBanned(dispute.getContract().getSellerNodeAddress()) ||
|
||||
filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) ||
|
||||
filterManager.isPaymentMethodBanned(
|
||||
PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
|
||||
filterManager.isPeersPaymentAccountDataAreBanned(dispute.getContract().getBuyerPaymentAccountPayload(),
|
||||
new PaymentAccountFilter[1]) ||
|
||||
filterManager.isPeersPaymentAccountDataAreBanned(dispute.getContract().getSellerPaymentAccountPayload(),
|
||||
new PaymentAccountFilter[1]);
|
||||
return !isFiltered;
|
||||
}
|
||||
|
||||
private boolean hasChargebackRisk(Dispute dispute) {
|
||||
return chargeBackRisk.hasChargebackRisk(dispute.getContract().getPaymentMethodId(),
|
||||
dispute.getContract().getOfferPayload().getCurrencyCode());
|
||||
}
|
||||
|
||||
private boolean isBuyerWinner(Dispute dispute) {
|
||||
if (!dispute.isClosed() || dispute.getDisputeResultProperty() == null)
|
||||
return false;
|
||||
return dispute.getDisputeResultProperty().get().getWinner() == DisputeResult.Winner.BUYER;
|
||||
}
|
||||
|
||||
private Stream<TraderDataItem> getTraderData(Dispute dispute) {
|
||||
Coin tradeAmount = dispute.getContract().getTradeAmount();
|
||||
|
||||
PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
|
||||
PubKeyRing sellerPubKeyRing = dispute.getContract().getSellerPubKeyRing();
|
||||
|
||||
PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload();
|
||||
PaymentAccountPayload sellerPaymentAccountPaload = dispute.getContract().getSellerPaymentAccountPayload();
|
||||
|
||||
TraderDataItem buyerData = findWitness(buyerPaymentAccountPaload, buyerPubKeyRing)
|
||||
.map(witness -> new TraderDataItem(
|
||||
buyerPaymentAccountPaload,
|
||||
witness,
|
||||
tradeAmount,
|
||||
sellerPubKeyRing.getSignaturePubKey()))
|
||||
.orElse(null);
|
||||
TraderDataItem sellerData = findWitness(sellerPaymentAccountPaload, sellerPubKeyRing)
|
||||
.map(witness -> new TraderDataItem(
|
||||
sellerPaymentAccountPaload,
|
||||
witness,
|
||||
tradeAmount,
|
||||
buyerPubKeyRing.getSignaturePubKey()))
|
||||
.orElse(null);
|
||||
return Stream.of(buyerData, sellerData);
|
||||
}
|
||||
|
||||
public boolean hasSignedWitness(Offer offer) {
|
||||
return findWitness(offer)
|
||||
.map(signedWitnessService::isSignedAccountAgeWitness)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
public boolean peerHasSignedWitness(Trade trade) {
|
||||
return findTradePeerWitness(trade)
|
||||
.map(signedWitnessService::isSignedAccountAgeWitness)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
public boolean accountIsSigner(AccountAgeWitness accountAgeWitness) {
|
||||
return signedWitnessService.isSignerAccountAgeWitness(accountAgeWitness);
|
||||
}
|
||||
|
||||
public SignState getSignState(Offer offer) {
|
||||
return findWitness(offer)
|
||||
.map(this::getSignState)
|
||||
.orElse(SignState.UNSIGNED);
|
||||
}
|
||||
|
||||
public SignState getSignState(AccountAgeWitness accountAgeWitness) {
|
||||
if (signedWitnessService.isSignedByArbitrator(accountAgeWitness)) {
|
||||
return SignState.ARBITRATOR;
|
||||
} else {
|
||||
final long accountSignAge = getWitnessSignAge(accountAgeWitness, new Date());
|
||||
switch (getAccountAgeCategory(accountSignAge)) {
|
||||
case TWO_MONTHS_OR_MORE:
|
||||
return SignState.PEER_SIGNER;
|
||||
case ONE_TO_TWO_MONTHS:
|
||||
return SignState.PEER_LIMIT_LIFTED;
|
||||
case LESS_ONE_MONTH:
|
||||
return SignState.PEER_INITIAL;
|
||||
case UNVERIFIED:
|
||||
default:
|
||||
return SignState.UNSIGNED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package bisq.core.app;
|
||||
|
||||
import bisq.core.account.sign.SignedWitness;
|
||||
import bisq.core.account.sign.SignedWitnessService;
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.alert.Alert;
|
||||
@ -43,12 +44,15 @@ import bisq.core.notifications.alerts.price.PriceAlert;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.TradeLimits;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.support.dispute.mediation.MediationManager;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.support.traderchat.TraderChatManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.statistics.AssetTradeActivityCheck;
|
||||
@ -61,6 +65,7 @@ import bisq.network.crypto.DecryptedDataTuple;
|
||||
import bisq.network.crypto.EncryptionService;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.peers.keepalive.messages.Ping;
|
||||
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||
|
||||
import bisq.common.ClockWatcher;
|
||||
import bisq.common.Timer;
|
||||
@ -100,6 +105,7 @@ import java.net.Socket;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
@ -143,11 +149,13 @@ public class BisqSetup {
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private final MediatorManager mediatorManager;
|
||||
private final RefundAgentManager refundAgentManager;
|
||||
private final P2PService p2PService;
|
||||
private final TradeManager tradeManager;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
private final ArbitrationManager arbitrationManager;
|
||||
private final MediationManager mediationManager;
|
||||
private final RefundManager refundManager;
|
||||
private final TraderChatManager traderChatManager;
|
||||
private final Preferences preferences;
|
||||
private final User user;
|
||||
@ -183,7 +191,8 @@ public class BisqSetup {
|
||||
private Consumer<String> cryptoSetupFailedHandler, chainFileLockedExceptionHandler,
|
||||
spvFileCorruptedHandler, lockedUpFundsHandler, daoErrorMessageHandler, daoWarnMessageHandler,
|
||||
filterWarningHandler, displaySecurityRecommendationHandler, displayLocalhostHandler,
|
||||
wrongOSArchitectureHandler;
|
||||
wrongOSArchitectureHandler, displaySignedByArbitratorHandler,
|
||||
displaySignedByPeerHandler, displayPeerLimitLiftedHandler, displayPeerSignerHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<Boolean> displayTorNetworkSettingsHandler;
|
||||
@ -225,11 +234,13 @@ public class BisqSetup {
|
||||
PriceFeedService priceFeedService,
|
||||
ArbitratorManager arbitratorManager,
|
||||
MediatorManager mediatorManager,
|
||||
RefundAgentManager refundAgentManager,
|
||||
P2PService p2PService,
|
||||
TradeManager tradeManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
ArbitrationManager arbitrationManager,
|
||||
MediationManager mediationManager,
|
||||
RefundManager refundManager,
|
||||
TraderChatManager traderChatManager,
|
||||
Preferences preferences,
|
||||
User user,
|
||||
@ -269,11 +280,13 @@ public class BisqSetup {
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.refundAgentManager = refundAgentManager;
|
||||
this.p2PService = p2PService;
|
||||
this.tradeManager = tradeManager;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
this.mediationManager = mediationManager;
|
||||
this.refundManager = refundManager;
|
||||
this.traderChatManager = traderChatManager;
|
||||
this.preferences = preferences;
|
||||
this.user = user;
|
||||
@ -313,11 +326,8 @@ public class BisqSetup {
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (log.isDebugEnabled()) {
|
||||
UserThread.runPeriodically(() -> {
|
||||
log.debug("1 second heartbeat");
|
||||
}, 1);
|
||||
}
|
||||
maybeReSyncSPVChain();
|
||||
maybeShowTac();
|
||||
}
|
||||
@ -346,6 +356,7 @@ public class BisqSetup {
|
||||
// in MainViewModel
|
||||
maybeShowSecurityRecommendation();
|
||||
maybeShowLocalhostRunningInfo();
|
||||
maybeShowAccountSigningStateInfo();
|
||||
}
|
||||
|
||||
|
||||
@ -440,10 +451,10 @@ public class BisqSetup {
|
||||
}
|
||||
|
||||
private void maybeShowTac() {
|
||||
if (!preferences.isTacAccepted() && !DevEnv.isDevMode()) {
|
||||
if (!preferences.isTacAcceptedV120() && !DevEnv.isDevMode()) {
|
||||
if (displayTacHandler != null)
|
||||
displayTacHandler.accept(() -> {
|
||||
preferences.setTacAccepted(true);
|
||||
preferences.setTacAcceptedV120(true);
|
||||
step2();
|
||||
});
|
||||
} else {
|
||||
@ -593,9 +604,7 @@ public class BisqSetup {
|
||||
if (allBasicServicesInitialized)
|
||||
checkForLockedUpFunds();
|
||||
},
|
||||
() -> {
|
||||
walletInitialized.set(true);
|
||||
});
|
||||
() -> walletInitialized.set(true));
|
||||
}
|
||||
|
||||
|
||||
@ -637,6 +646,7 @@ public class BisqSetup {
|
||||
|
||||
arbitrationManager.onAllServicesInitialized();
|
||||
mediationManager.onAllServicesInitialized();
|
||||
refundManager.onAllServicesInitialized();
|
||||
traderChatManager.onAllServicesInitialized();
|
||||
|
||||
tradeManager.onAllServicesInitialized();
|
||||
@ -650,6 +660,7 @@ public class BisqSetup {
|
||||
|
||||
arbitratorManager.onAllServicesInitialized();
|
||||
mediatorManager.onAllServicesInitialized();
|
||||
refundAgentManager.onAllServicesInitialized();
|
||||
|
||||
alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) ->
|
||||
displayAlertIfPresent(newValue, false));
|
||||
@ -715,9 +726,7 @@ public class BisqSetup {
|
||||
voteResultService.getVoteResultExceptions().addListener((ListChangeListener<VoteResultException>) c -> {
|
||||
c.next();
|
||||
if (c.wasAdded() && voteResultExceptionHandler != null) {
|
||||
c.getAddedSubList().forEach(e -> {
|
||||
voteResultExceptionHandler.accept(e);
|
||||
});
|
||||
c.getAddedSubList().forEach(e -> voteResultExceptionHandler.accept(e));
|
||||
}
|
||||
});
|
||||
|
||||
@ -741,9 +750,62 @@ public class BisqSetup {
|
||||
}
|
||||
|
||||
private void maybeShowLocalhostRunningInfo() {
|
||||
String key = "bitcoinLocalhostNode";
|
||||
if (bisqEnvironment.isBitcoinLocalhostNodeRunning() && preferences.showAgain(key) &&
|
||||
displayLocalhostHandler != null)
|
||||
displayLocalhostHandler.accept(key);
|
||||
maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, bisqEnvironment.isBitcoinLocalhostNodeRunning());
|
||||
}
|
||||
|
||||
private void maybeShowAccountSigningStateInfo() {
|
||||
String keySignedByArbitrator = "accountSignedByArbitrator";
|
||||
String keySignedByPeer = "accountSignedByPeer";
|
||||
String keyPeerLimitedLifted = "accountLimitLifted";
|
||||
String keyPeerSigner = "accountPeerSigner";
|
||||
|
||||
// check signed witness on startup
|
||||
checkSigningState(AccountAgeWitnessService.SignState.ARBITRATOR, keySignedByArbitrator, displaySignedByArbitratorHandler);
|
||||
checkSigningState(AccountAgeWitnessService.SignState.PEER_INITIAL, keySignedByPeer, displaySignedByPeerHandler);
|
||||
checkSigningState(AccountAgeWitnessService.SignState.PEER_LIMIT_LIFTED, keyPeerLimitedLifted, displayPeerLimitLiftedHandler);
|
||||
checkSigningState(AccountAgeWitnessService.SignState.PEER_SIGNER, keyPeerSigner, displayPeerSignerHandler);
|
||||
|
||||
// check signed witness during runtime
|
||||
p2PService.getP2PDataStorage().addAppendOnlyDataStoreListener(
|
||||
payload -> {
|
||||
maybeTriggerDisplayHandler(keySignedByArbitrator, displaySignedByArbitratorHandler,
|
||||
isSignedWitnessOfMineWithState(payload, AccountAgeWitnessService.SignState.ARBITRATOR));
|
||||
maybeTriggerDisplayHandler(keySignedByPeer, displaySignedByPeerHandler,
|
||||
isSignedWitnessOfMineWithState(payload, AccountAgeWitnessService.SignState.PEER_INITIAL));
|
||||
maybeTriggerDisplayHandler(keyPeerLimitedLifted, displayPeerLimitLiftedHandler,
|
||||
isSignedWitnessOfMineWithState(payload, AccountAgeWitnessService.SignState.PEER_LIMIT_LIFTED));
|
||||
maybeTriggerDisplayHandler(keyPeerSigner, displayPeerSignerHandler,
|
||||
isSignedWitnessOfMineWithState(payload, AccountAgeWitnessService.SignState.PEER_SIGNER));
|
||||
});
|
||||
}
|
||||
|
||||
private void checkSigningState(AccountAgeWitnessService.SignState state,
|
||||
String key, Consumer<String> displayHandler) {
|
||||
boolean signingStateFound = p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().stream()
|
||||
.anyMatch(payload -> isSignedWitnessOfMineWithState(payload, state));
|
||||
|
||||
maybeTriggerDisplayHandler(key, displayHandler, signingStateFound);
|
||||
}
|
||||
|
||||
private boolean isSignedWitnessOfMineWithState(PersistableNetworkPayload payload,
|
||||
AccountAgeWitnessService.SignState state) {
|
||||
if (payload instanceof SignedWitness && user.getPaymentAccounts() != null) {
|
||||
// We know at this point that it is already added to the signed witness list
|
||||
// Check if new signed witness is for one of my own accounts
|
||||
return user.getPaymentAccounts().stream()
|
||||
.filter(a -> PaymentMethod.hasChargebackRisk(a.getPaymentMethod(), a.getTradeCurrencies()))
|
||||
.filter(a -> Arrays.equals(((SignedWitness) payload).getAccountAgeWitnessHash(),
|
||||
accountAgeWitnessService.getMyWitness(a.getPaymentAccountPayload()).getHash()))
|
||||
.anyMatch(a -> accountAgeWitnessService.getSignState(accountAgeWitnessService.getMyWitness(
|
||||
a.getPaymentAccountPayload())).equals(state));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void maybeTriggerDisplayHandler(String key, Consumer<String> displayHandler, boolean signingStateFound) {
|
||||
if (signingStateFound && preferences.showAgain(key) &&
|
||||
displayHandler != null) {
|
||||
displayHandler.accept(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,14 +41,18 @@ import java.io.IOException;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public abstract class ExecutableForAppWithP2p extends BisqExecutable implements UncaughtExceptionHandler {
|
||||
private static final long MAX_MEMORY_MB_DEFAULT = 500;
|
||||
private static final long MAX_MEMORY_MB_DEFAULT = 1200;
|
||||
private static final long CHECK_MEMORY_PERIOD_SEC = 300;
|
||||
private static final long CHECK_SHUTDOWN_SEC = TimeUnit.HOURS.toSeconds(1);
|
||||
private static final long SHUTDOWN_INTERVAL = TimeUnit.HOURS.toMillis(24);
|
||||
private volatile boolean stopped;
|
||||
private final long startTime = System.currentTimeMillis();
|
||||
private static long maxMemory = MAX_MEMORY_MB_DEFAULT;
|
||||
|
||||
public ExecutableForAppWithP2p(String fullName, String scriptName, String version) {
|
||||
@ -120,6 +124,20 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable implements
|
||||
}
|
||||
}
|
||||
|
||||
protected void startShutDownInterval(GracefulShutDownHandler gracefulShutDownHandler) {
|
||||
UserThread.runPeriodically(() -> {
|
||||
if (System.currentTimeMillis() - startTime > SHUTDOWN_INTERVAL) {
|
||||
log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" +
|
||||
"Shut down as node was running longer as {} hours" +
|
||||
"\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n",
|
||||
SHUTDOWN_INTERVAL / 3600000);
|
||||
|
||||
shutDown(gracefulShutDownHandler);
|
||||
}
|
||||
|
||||
}, CHECK_SHUTDOWN_SEC);
|
||||
}
|
||||
|
||||
protected void checkMemory(BisqEnvironment environment, GracefulShutDownHandler gracefulShutDownHandler) {
|
||||
String maxMemoryOption = environment.getProperty(AppOptionKeys.MAX_MEMORY);
|
||||
if (maxMemoryOption != null && !maxMemoryOption.isEmpty()) {
|
||||
@ -153,16 +171,24 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable implements
|
||||
long usedMemory = Profiler.getUsedMemoryInMB();
|
||||
if (usedMemory > maxMemory) {
|
||||
log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" +
|
||||
"We are over our memory limit ({}) and trigger a restart. usedMemory: {} MB. freeMemory: {} MB" +
|
||||
"We are over our memory limit ({}) and trigger a shutdown. usedMemory: {} MB. freeMemory: {} MB" +
|
||||
"\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n",
|
||||
(int) maxMemory, usedMemory, Profiler.getFreeMemoryInMB());
|
||||
restart(environment, gracefulShutDownHandler);
|
||||
shutDown(gracefulShutDownHandler);
|
||||
}
|
||||
}, 5);
|
||||
}
|
||||
}, CHECK_MEMORY_PERIOD_SEC);
|
||||
}
|
||||
|
||||
protected void shutDown(GracefulShutDownHandler gracefulShutDownHandler) {
|
||||
stopped = true;
|
||||
gracefulShutDownHandler.gracefulShutDown(() -> {
|
||||
log.info("Shutdown complete");
|
||||
System.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
protected void restart(BisqEnvironment bisqEnvironment, GracefulShutDownHandler gracefulShutDownHandler) {
|
||||
stopped = true;
|
||||
gracefulShutDownHandler.gracefulShutDown(() -> {
|
||||
@ -180,5 +206,4 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable implements
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
@ -52,6 +54,7 @@ public class Balances {
|
||||
private final OpenOfferManager openOfferManager;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final FailedTradesManager failedTradesManager;
|
||||
private final RefundManager refundManager;
|
||||
|
||||
@Getter
|
||||
private final ObjectProperty<Coin> availableBalance = new SimpleObjectProperty<>();
|
||||
@ -65,17 +68,20 @@ public class Balances {
|
||||
BtcWalletService btcWalletService,
|
||||
OpenOfferManager openOfferManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
FailedTradesManager failedTradesManager) {
|
||||
FailedTradesManager failedTradesManager,
|
||||
RefundManager refundManager) {
|
||||
this.tradeManager = tradeManager;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.failedTradesManager = failedTradesManager;
|
||||
this.refundManager = refundManager;
|
||||
}
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
openOfferManager.getObservableList().addListener((ListChangeListener<OpenOffer>) c -> updateBalance());
|
||||
tradeManager.getTradableList().addListener((ListChangeListener<Trade>) change -> updateBalance());
|
||||
refundManager.getDisputesAsObservableList().addListener((ListChangeListener<Dispute>) c -> updateBalance());
|
||||
btcWalletService.addBalanceListener(new BalanceListener() {
|
||||
@Override
|
||||
public void onBalanceChanged(Coin balance, Transaction tx) {
|
||||
|
@ -126,6 +126,25 @@ public class TxFeeEstimationService {
|
||||
return new Tuple2<>(txFee, size);
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getEstimatedFeeAndTxSize(Coin amount,
|
||||
FeeService feeService,
|
||||
BtcWalletService btcWalletService) {
|
||||
Coin txFeePerByte = feeService.getTxFeePerByte();
|
||||
// We start with min taker fee size of 260
|
||||
int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE;
|
||||
try {
|
||||
estimatedTxSize = getEstimatedTxSize(List.of(amount), estimatedTxSize, txFeePerByte, btcWalletService);
|
||||
} catch (InsufficientMoneyException e) {
|
||||
log.info("We cannot do the fee estimation because there are not enough funds in the wallet. This is expected " +
|
||||
"if the user pays from an external wallet. In that case we use an estimated tx size of {} bytes.", estimatedTxSize);
|
||||
}
|
||||
|
||||
Coin txFee = txFeePerByte.multiply(estimatedTxSize);
|
||||
log.info("Fee estimation resulted in a tx size of {} bytes and a tx fee of {} Sat.", estimatedTxSize, txFee.value);
|
||||
|
||||
return new Tuple2<>(txFee, estimatedTxSize);
|
||||
}
|
||||
|
||||
// We start with the initialEstimatedTxSize for a tx with 1 input (260) bytes and get from BitcoinJ a tx back which
|
||||
// contains the required inputs to fund that tx (outputs + miner fee). The miner fee in that case is based on
|
||||
// the assumption that we only need 1 input. Once we receive back the real tx size from the tx BitcoinJ has created
|
||||
|
@ -47,10 +47,6 @@ public class BtcNodes {
|
||||
public List<BtcNode> getProvidedBtcNodes() {
|
||||
return useProvidedBtcNodes() ?
|
||||
Arrays.asList(
|
||||
// ManfredKarrer
|
||||
new BtcNode("btc1.0-2-1.net", "r3dsojfhwcm7x7p6.onion", "159.89.16.222", BtcNode.DEFAULT_PORT, "@manfredkarrer"),
|
||||
new BtcNode("btc2.0-2-1.net", "vlf5i3grro3wux24.onion", "165.227.34.56", BtcNode.DEFAULT_PORT, "@manfredkarrer"),
|
||||
|
||||
// emzy
|
||||
new BtcNode("kirsche.emzy.de", "fz6nsij6jiyuwlsc.onion", "78.47.61.83", BtcNode.DEFAULT_PORT, "@emzy"),
|
||||
new BtcNode("node2.emzy.de", "c6ac4jdfyeiakex2.onion", "62.75.210.81", BtcNode.DEFAULT_PORT, "@emzy"),
|
||||
|
@ -493,6 +493,15 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||
}
|
||||
}
|
||||
|
||||
for (TransactionOutput txo : tx.getOutputs()) {
|
||||
Coin value = txo.getValue();
|
||||
// OpReturn outputs have value 0
|
||||
if (value.isPositive()) {
|
||||
checkArgument(Restrictions.isAboveDust(txo.getValue()),
|
||||
"An output value is below dust limit. Transaction=" + tx);
|
||||
}
|
||||
}
|
||||
|
||||
checkWalletConsistency(wallet);
|
||||
verifyTransaction(tx);
|
||||
printTx("BSQ wallet: Signed Tx", tx);
|
||||
@ -556,11 +565,11 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||
// Tx has as first output BSQ and an optional second BSQ change output.
|
||||
// At that stage we do not have added the BTC inputs so there is no BTC change output here.
|
||||
if (tx.getOutputs().size() == 2) {
|
||||
TransactionOutput bsqChangeOutput = tx.getOutputs().get(1);
|
||||
if (!Restrictions.isAboveDust(bsqChangeOutput.getValue())) {
|
||||
String msg = "BSQ change output is below dust limit. outputValue=" + bsqChangeOutput.getValue().toFriendlyString();
|
||||
Coin bsqChangeOutputValue = tx.getOutputs().get(1).getValue();
|
||||
if (!Restrictions.isAboveDust(bsqChangeOutputValue)) {
|
||||
String msg = "BSQ change output is below dust limit. outputValue=" + bsqChangeOutputValue.value / 100 + " BSQ";
|
||||
log.warn(msg);
|
||||
throw new BsqChangeBelowDustException(msg, bsqChangeOutput.getValue());
|
||||
throw new BsqChangeBelowDustException(msg, bsqChangeOutputValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -573,60 +582,128 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Burn fee tx
|
||||
// Burn fee txs
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction getPreparedTradeFeeTx(Coin fee) throws InsufficientBsqException {
|
||||
daoKillSwitch.assertDaoIsNotDisabled();
|
||||
|
||||
Transaction tx = new Transaction(params);
|
||||
addInputsAndChangeOutputForTx(tx, fee, bsqCoinSelector);
|
||||
return tx;
|
||||
}
|
||||
|
||||
// We create a tx with Bsq inputs for the fee and optional BSQ change output.
|
||||
// As the fee amount will be missing in the output those BSQ fees are burned.
|
||||
public Transaction getPreparedProposalTx(Coin fee, boolean requireChangeOutput) throws InsufficientBsqException {
|
||||
return getPreparedBurnFeeTx(fee, requireChangeOutput);
|
||||
public Transaction getPreparedProposalTx(Coin fee) throws InsufficientBsqException {
|
||||
return getPreparedTxWithMandatoryBsqChangeOutput(fee);
|
||||
}
|
||||
|
||||
public Transaction getPreparedBurnFeeTx(Coin fee) throws InsufficientBsqException {
|
||||
return getPreparedBurnFeeTx(fee, false);
|
||||
public Transaction getPreparedIssuanceTx(Coin fee) throws InsufficientBsqException {
|
||||
return getPreparedTxWithMandatoryBsqChangeOutput(fee);
|
||||
}
|
||||
|
||||
private Transaction getPreparedBurnFeeTx(Coin fee, boolean requireChangeOutput) throws InsufficientBsqException {
|
||||
public Transaction getPreparedProofOfBurnTx(Coin fee) throws InsufficientBsqException {
|
||||
return getPreparedTxWithMandatoryBsqChangeOutput(fee);
|
||||
}
|
||||
|
||||
public Transaction getPreparedBurnFeeTxForAssetListing(Coin fee) throws InsufficientBsqException {
|
||||
return getPreparedTxWithMandatoryBsqChangeOutput(fee);
|
||||
}
|
||||
|
||||
// We need to require one BSQ change output as we could otherwise not be able to distinguish between 2
|
||||
// structurally same transactions where only the BSQ fee is different. In case of asset listing fee and proof of
|
||||
// burn it is a user input, so it is not know to the parser, instead we derive the burned fee from the parser.
|
||||
|
||||
// In case of proposal fee we could derive it from the params.
|
||||
|
||||
// For issuance txs we also require a BSQ change output before the issuance output gets added. There was a
|
||||
// minor bug with the old version that multiple inputs would have caused an exception in case there was no
|
||||
// change output (e.g. inputs of 21 and 6 BSQ for BSQ fee of 21 BSQ would have caused that only 1 input was used
|
||||
// and then caused an error as we enforced a change output. This new version handles such cases correctly.
|
||||
|
||||
// Examples for the structurally indistinguishable transactions:
|
||||
// Case 1: 10 BSQ fee to burn
|
||||
// In: 17 BSQ
|
||||
// Out: BSQ change 7 BSQ -> valid BSQ
|
||||
// Out: OpReturn
|
||||
// Miner fee: 1000 sat (10 BSQ burned)
|
||||
|
||||
// Case 2: 17 BSQ fee to burn
|
||||
// In: 17 BSQ
|
||||
// Out: burned BSQ change 7 BSQ -> BTC (7 BSQ burned)
|
||||
// Out: OpReturn
|
||||
// Miner fee: 1000 sat (10 BSQ burned)
|
||||
|
||||
private Transaction getPreparedTxWithMandatoryBsqChangeOutput(Coin fee) throws InsufficientBsqException {
|
||||
daoKillSwitch.assertDaoIsNotDisabled();
|
||||
final Transaction tx = new Transaction(params);
|
||||
addInputsAndChangeOutputForTx(tx, fee, bsqCoinSelector, requireChangeOutput);
|
||||
// printTx("getPreparedFeeTx", tx);
|
||||
|
||||
Transaction tx = new Transaction(params);
|
||||
// We look for inputs covering out BSQ fee we want to pay.
|
||||
CoinSelection coinSelection = bsqCoinSelector.select(fee, wallet.calculateAllSpendCandidates());
|
||||
try {
|
||||
Coin change = bsqCoinSelector.getChange(fee, coinSelection);
|
||||
if (change.isZero() || Restrictions.isDust(change)) {
|
||||
// If change is zero or below dust we increase required input amount to enforce a BSQ change output.
|
||||
// All outputs after that are considered BTC and therefore would be burned BSQ if BSQ is left from what
|
||||
// we use for miner fee.
|
||||
|
||||
Coin minDustThreshold = Coin.valueOf(preferences.getIgnoreDustThreshold());
|
||||
Coin increasedRequiredInput = fee.add(minDustThreshold);
|
||||
coinSelection = bsqCoinSelector.select(increasedRequiredInput, wallet.calculateAllSpendCandidates());
|
||||
change = bsqCoinSelector.getChange(fee, coinSelection);
|
||||
|
||||
log.warn("We increased required input as change output was zero or dust: New change value={}", change);
|
||||
String info = "Available BSQ balance=" + coinSelection.valueGathered.value / 100 + " BSQ. " +
|
||||
"Intended fee to burn=" + fee.value / 100 + " BSQ. " +
|
||||
"Please increase your balance to at least " + (coinSelection.valueGathered.value + minDustThreshold.value) / 100 + " BSQ.";
|
||||
checkArgument(coinSelection.valueGathered.compareTo(fee) > 0,
|
||||
"This transaction require a change output of at least " + minDustThreshold.value / 100 + " BSQ (dust limit). " +
|
||||
info);
|
||||
|
||||
checkArgument(!Restrictions.isDust(change),
|
||||
"This transaction would create a dust output of " + change.value / 100 + " BSQ. " +
|
||||
"It requires a change output of at least " + minDustThreshold.value / 100 + " BSQ (dust limit). " +
|
||||
info);
|
||||
}
|
||||
|
||||
coinSelection.gathered.forEach(tx::addInput);
|
||||
tx.addOutput(change, getChangeAddress());
|
||||
|
||||
return tx;
|
||||
|
||||
} catch (InsufficientMoneyException e) {
|
||||
log.error("coinSelection.gathered={}", coinSelection.gathered);
|
||||
throw new InsufficientBsqException(e.missing);
|
||||
}
|
||||
}
|
||||
|
||||
private void addInputsAndChangeOutputForTx(Transaction tx,
|
||||
Coin fee,
|
||||
BsqCoinSelector bsqCoinSelector,
|
||||
boolean requireChangeOutput)
|
||||
BsqCoinSelector bsqCoinSelector)
|
||||
throws InsufficientBsqException {
|
||||
Coin requiredInput;
|
||||
// If our fee is less then dust limit we increase it so we are sure to not get any dust output.
|
||||
if (Restrictions.isDust(fee))
|
||||
requiredInput = Restrictions.getMinNonDustOutput().add(fee);
|
||||
else
|
||||
if (Restrictions.isDust(fee)) {
|
||||
requiredInput = fee.add(Restrictions.getMinNonDustOutput());
|
||||
} else {
|
||||
requiredInput = fee;
|
||||
}
|
||||
|
||||
CoinSelection coinSelection = bsqCoinSelector.select(requiredInput, wallet.calculateAllSpendCandidates());
|
||||
coinSelection.gathered.forEach(tx::addInput);
|
||||
try {
|
||||
// TODO why is fee passed to getChange ???
|
||||
Coin change = bsqCoinSelector.getChange(fee, coinSelection);
|
||||
if (requireChangeOutput) {
|
||||
checkArgument(change.isPositive(),
|
||||
"This transaction requires a mandatory BSQ change output. " +
|
||||
"At least " + Restrictions.getMinNonDustOutput().add(fee).value / 100d + " " +
|
||||
"BSQ is needed for this transaction");
|
||||
}
|
||||
|
||||
if (change.isPositive()) {
|
||||
checkArgument(Restrictions.isAboveDust(change),
|
||||
"The change output of " + change.value / 100d + " BSQ is below the min. dust value of "
|
||||
+ Restrictions.getMinNonDustOutput().value / 100d +
|
||||
". At least " + Restrictions.getMinNonDustOutput().add(fee).value / 100d + " " +
|
||||
"BSQ is needed for this transaction");
|
||||
". At least " + Restrictions.getMinNonDustOutput().add(fee).value / 100d +
|
||||
" BSQ is needed for this transaction");
|
||||
tx.addOutput(change, getChangeAddress());
|
||||
}
|
||||
} catch (InsufficientMoneyException e) {
|
||||
log.error(tx.toString());
|
||||
throw new InsufficientBsqException(e.missing);
|
||||
}
|
||||
}
|
||||
@ -642,7 +719,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||
daoKillSwitch.assertDaoIsNotDisabled();
|
||||
Transaction tx = new Transaction(params);
|
||||
tx.addOutput(new TransactionOutput(params, tx, stake, getUnusedAddress()));
|
||||
addInputsAndChangeOutputForTx(tx, fee.add(stake), bsqCoinSelector, false);
|
||||
addInputsAndChangeOutputForTx(tx, fee.add(stake), bsqCoinSelector);
|
||||
//printTx("getPreparedBlindVoteTx", tx);
|
||||
return tx;
|
||||
}
|
||||
@ -676,7 +753,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
||||
Transaction tx = new Transaction(params);
|
||||
checkArgument(Restrictions.isAboveDust(lockupAmount), "The amount is too low (dust limit).");
|
||||
tx.addOutput(new TransactionOutput(params, tx, lockupAmount, getUnusedAddress()));
|
||||
addInputsAndChangeOutputForTx(tx, lockupAmount, bsqCoinSelector, false);
|
||||
addInputsAndChangeOutputForTx(tx, lockupAmount, bsqCoinSelector);
|
||||
printTx("prepareLockupTx", tx);
|
||||
return tx;
|
||||
}
|
||||
|
@ -408,13 +408,13 @@ public class BtcWalletService extends WalletService {
|
||||
TransactionVerificationException, WalletException, InsufficientMoneyException {
|
||||
// preparedBsqTx has following structure:
|
||||
// inputs [1-n] BSQ inputs
|
||||
// outputs [0-1] BSQ receivers output
|
||||
// outputs [1] BSQ receivers output
|
||||
// outputs [0-1] BSQ change output
|
||||
|
||||
// We add BTC mining fee. Result tx looks like:
|
||||
// inputs [1-n] BSQ inputs
|
||||
// inputs [1-n] BTC inputs
|
||||
// outputs [0-1] BSQ receivers output
|
||||
// outputs [1] BSQ receivers output
|
||||
// outputs [0-1] BSQ change output
|
||||
// outputs [0-1] BTC change output
|
||||
// mining fee: BTC mining fee
|
||||
@ -426,7 +426,7 @@ public class BtcWalletService extends WalletService {
|
||||
|
||||
// preparedBsqTx has following structure:
|
||||
// inputs [1-n] BSQ inputs
|
||||
// outputs [0-1] BSQ receivers output
|
||||
// outputs [1] BSQ receivers output
|
||||
// outputs [0-1] BSQ change output
|
||||
// mining fee: optional burned BSQ fee (only if opReturnData != null)
|
||||
|
||||
@ -1114,4 +1114,55 @@ public class BtcWalletService extends WalletService {
|
||||
protected boolean isDustAttackUtxo(TransactionOutput output) {
|
||||
return output.getValue().value < preferences.getIgnoreDustThreshold();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Refund payoutTx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction createRefundPayoutTx(Coin buyerAmount,
|
||||
Coin sellerAmount,
|
||||
Coin fee,
|
||||
String buyerAddressString,
|
||||
String sellerAddressString)
|
||||
throws AddressFormatException, InsufficientMoneyException, WalletException, TransactionVerificationException {
|
||||
Transaction tx = new Transaction(params);
|
||||
Preconditions.checkArgument(buyerAmount.add(sellerAmount).isPositive(),
|
||||
"The sellerAmount + buyerAmount must be positive.");
|
||||
// buyerAmount can be 0
|
||||
if (buyerAmount.isPositive()) {
|
||||
Preconditions.checkArgument(Restrictions.isAboveDust(buyerAmount),
|
||||
"The buyerAmount is too low (dust limit).");
|
||||
|
||||
tx.addOutput(buyerAmount, Address.fromBase58(params, buyerAddressString));
|
||||
}
|
||||
// sellerAmount can be 0
|
||||
if (sellerAmount.isPositive()) {
|
||||
Preconditions.checkArgument(Restrictions.isAboveDust(sellerAmount),
|
||||
"The sellerAmount is too low (dust limit).");
|
||||
|
||||
tx.addOutput(sellerAmount, Address.fromBase58(params, sellerAddressString));
|
||||
}
|
||||
|
||||
SendRequest sendRequest = SendRequest.forTx(tx);
|
||||
sendRequest.fee = fee;
|
||||
sendRequest.feePerKb = Coin.ZERO;
|
||||
sendRequest.ensureMinRequiredFee = false;
|
||||
sendRequest.aesKey = aesKey;
|
||||
sendRequest.shuffleOutputs = false;
|
||||
sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
|
||||
preferences.getIgnoreDustThreshold());
|
||||
sendRequest.changeAddress = getFreshAddressEntry().getAddress();
|
||||
|
||||
checkNotNull(wallet);
|
||||
wallet.completeTx(sendRequest);
|
||||
|
||||
Transaction resultTx = sendRequest.tx;
|
||||
checkWalletConsistency(wallet);
|
||||
verifyTransaction(resultTx);
|
||||
|
||||
WalletService.printTx("createRefundPayoutTx", resultTx);
|
||||
|
||||
return resultTx;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -104,12 +104,12 @@ public class TxBroadcaster {
|
||||
}
|
||||
|
||||
// We decided the least risky scenario is to commit the tx to the wallet and broadcast it later.
|
||||
// If it's a bsq tx WalletManager.publishAndCommitBsqTx() should have commited the tx to both bsq and btc
|
||||
// If it's a bsq tx WalletManager.publishAndCommitBsqTx() should have committed the tx to both bsq and btc
|
||||
// wallets so the next line causes no effect.
|
||||
// If it's a btc tx, the next line adds the tx to the wallet.
|
||||
wallet.maybeCommitTx(tx);
|
||||
|
||||
Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback<Transaction>() {
|
||||
Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction result) {
|
||||
// We expect that there is still a timeout in our map, otherwise the timeout got triggered
|
||||
@ -119,7 +119,7 @@ public class TxBroadcaster {
|
||||
// before the caller is finished.
|
||||
UserThread.execute(() -> callback.onSuccess(tx));
|
||||
} else {
|
||||
log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout.", txId);
|
||||
log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout. txId={}", txId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.BlockChain;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Context;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
@ -43,6 +44,7 @@ import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.core.TransactionInput;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.core.VerificationException;
|
||||
import org.bitcoinj.core.listeners.NewBestBlockListener;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.crypto.KeyCrypter;
|
||||
@ -102,6 +104,7 @@ public abstract class WalletService {
|
||||
protected final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
|
||||
protected final CopyOnWriteArraySet<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArraySet<>();
|
||||
protected final CopyOnWriteArraySet<BalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
|
||||
@Getter
|
||||
protected Wallet wallet;
|
||||
@Getter
|
||||
protected KeyParameter aesKey;
|
||||
@ -223,7 +226,9 @@ public abstract class WalletService {
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkScriptSig(Transaction transaction, TransactionInput input, int inputIndex) throws TransactionVerificationException {
|
||||
public static void checkScriptSig(Transaction transaction,
|
||||
TransactionInput input,
|
||||
int inputIndex) throws TransactionVerificationException {
|
||||
try {
|
||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||
input.getScriptSig().correctlySpends(transaction, inputIndex, input.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||
@ -245,7 +250,11 @@ public abstract class WalletService {
|
||||
// Sign tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static void signTransactionInput(Wallet wallet, KeyParameter aesKey, Transaction tx, TransactionInput txIn, int index) {
|
||||
public static void signTransactionInput(Wallet wallet,
|
||||
KeyParameter aesKey,
|
||||
Transaction tx,
|
||||
TransactionInput txIn,
|
||||
int index) {
|
||||
KeyBag maybeDecryptingKeyBag = new DecryptingKeyBag(wallet, aesKey);
|
||||
if (txIn.getConnectedOutput() != null) {
|
||||
try {
|
||||
@ -475,7 +484,10 @@ public abstract class WalletService {
|
||||
// Empty complete Wallet
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
|
||||
public void emptyWallet(String toAddress,
|
||||
KeyParameter aesKey,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler)
|
||||
throws InsufficientMoneyException, AddressFormatException {
|
||||
SendRequest sendRequest = SendRequest.emptyWallet(Address.fromBase58(params, toAddress));
|
||||
sendRequest.fee = Coin.ZERO;
|
||||
@ -675,6 +687,45 @@ public abstract class WalletService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param serializedTransaction The serialized transaction to be added to the wallet
|
||||
* @return The transaction we added to the wallet, which is different as the one we passed as argument!
|
||||
* @throws VerificationException
|
||||
*/
|
||||
public static Transaction maybeAddTxToWallet(byte[] serializedTransaction,
|
||||
Wallet wallet,
|
||||
TransactionConfidence.Source source) throws VerificationException {
|
||||
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);
|
||||
}
|
||||
|
||||
public static Transaction maybeAddSelfTxToWallet(Transaction transaction,
|
||||
Wallet wallet) throws VerificationException {
|
||||
return maybeAddTxToWallet(transaction, wallet, TransactionConfidence.Source.SELF);
|
||||
}
|
||||
|
||||
public static Transaction maybeAddTxToWallet(Transaction transaction,
|
||||
Wallet wallet,
|
||||
TransactionConfidence.Source source) throws VerificationException {
|
||||
return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// bisqWalletEventListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -322,11 +322,11 @@ public class AssetService implements DaoSetupService, DaoStateListener {
|
||||
checkArgument(listingFee % 100 == 0, "Fee must be a multiple of 1 BSQ (100 satoshi).");
|
||||
try {
|
||||
// We create a prepared Bsq Tx for the listing fee.
|
||||
final Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTx(Coin.valueOf(listingFee));
|
||||
Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTxForAssetListing(Coin.valueOf(listingFee));
|
||||
byte[] hash = AssetConsensus.getHash(statefulAsset);
|
||||
byte[] opReturnData = AssetConsensus.getOpReturnData(hash);
|
||||
// We add the BTC inputs for the miner fee.
|
||||
final Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
||||
// We sign the BSQ inputs of the final tx.
|
||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
||||
log.info("Asset listing fee tx: " + transaction);
|
||||
|
@ -107,9 +107,9 @@ public enum Param {
|
||||
// but can be also a burner address if we prefer to burn the BTC
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
RECIPIENT_BTC_ADDRESS(BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? "1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7" : // mainnet
|
||||
BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ? "1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7" : // mainnet
|
||||
BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ? "1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7" : // daoBetaNet
|
||||
BisqEnvironment.getBaseCurrencyNetwork().isTestnet() ? "2N4mVTpUZAnhm9phnxB7VrHB4aBhnWrcUrV" : // testnet
|
||||
"mquz1zFmhs7iy8qJTkhY7C9bhJ5S3g8Xim", // regtest or DAO testnet (regtest)
|
||||
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w", // regtest or DAO testnet (regtest)
|
||||
ParamType.ADDRESS),
|
||||
|
||||
// Fee for activating an asset or re-listing after deactivation due lack of trade activity. Fee per day of trial period without activity checks.
|
||||
|
@ -134,11 +134,11 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener {
|
||||
public Transaction burn(String preImageAsString, long amount) throws InsufficientMoneyException, TxException {
|
||||
try {
|
||||
// We create a prepared Bsq Tx for the burn amount
|
||||
final Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTx(Coin.valueOf(amount));
|
||||
Transaction preparedBurnFeeTx = bsqWalletService.getPreparedProofOfBurnTx(Coin.valueOf(amount));
|
||||
byte[] hash = getHashFromPreImage(preImageAsString);
|
||||
byte[] opReturnData = ProofOfBurnConsensus.getOpReturnData(hash);
|
||||
// We add the BTC inputs for the miner fee.
|
||||
final Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
||||
// We sign the BSQ inputs of the final tx.
|
||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
||||
log.info("Proof of burn tx: " + transaction);
|
||||
|
@ -87,8 +87,9 @@ public abstract class BaseProposalFactory<R extends Proposal> {
|
||||
try {
|
||||
Coin fee = ProposalConsensus.getFee(daoStateService, daoStateService.getChainHeight());
|
||||
// We create a prepared Bsq Tx for the proposal fee.
|
||||
boolean requireChangeOutput = proposal instanceof IssuanceProposal;
|
||||
Transaction preparedBurnFeeTx = bsqWalletService.getPreparedProposalTx(fee, requireChangeOutput);
|
||||
Transaction preparedBurnFeeTx = proposal instanceof IssuanceProposal ?
|
||||
bsqWalletService.getPreparedIssuanceTx(fee) :
|
||||
bsqWalletService.getPreparedProposalTx(fee);
|
||||
|
||||
// payload does not have txId at that moment
|
||||
byte[] hashOfPayload = ProposalConsensus.getHashOfPayload(proposal);
|
||||
|
@ -17,7 +17,9 @@
|
||||
|
||||
package bisq.core.dao.node.parser;
|
||||
|
||||
import bisq.core.app.BisqEnvironment;
|
||||
import bisq.core.dao.governance.bond.BondConsensus;
|
||||
import bisq.core.dao.governance.param.Param;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.model.blockchain.OpReturnType;
|
||||
import bisq.core.dao.state.model.blockchain.TxOutput;
|
||||
@ -38,11 +40,57 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Checks if an output is a BSQ output and apply state change.
|
||||
*
|
||||
* With block 602500 (about 4 weeks after v1.2.0 release) we enforce a new rule which represents a
|
||||
* hard fork. Not updated nodes would see an out of sync dao state hash if a relevant transaction would
|
||||
* happen again.
|
||||
* Further (highly unlikely) consequences could be:
|
||||
* If the BSQ output would be sent to a BSQ address the old client would accept that even it is
|
||||
* invalid according to the new rules. But sending such a output would require a manually crafted tx
|
||||
* (not possible in the UI). Worst case a not updated user would buy invalid BSQ but that is not possible as we
|
||||
* enforce update to 1.2.0 for trading a few days after release as that release introduced the new trade protocol
|
||||
* and protection tool. Only of both both traders would have deactivated filter messages they could trade.
|
||||
*
|
||||
* Problem description:
|
||||
* We did not apply the check to not allow BSQ outputs after we had detected a BTC output.
|
||||
* The supported BSQ transactions did not support such cases anyway but we missed an edge case:
|
||||
* A trade fee tx in case when the BTC input matches exactly the BTC output
|
||||
* (or BTC change was <= the miner fee) and the BSQ fee was > the miner fee. Then we
|
||||
* create a change output after the BTC output (using an address from the BTC wallet) and as
|
||||
* available BSQ was >= as spent BSQ it was considered a valid BSQ output.
|
||||
* There have been observed 5 such transactions where 4 got spent later to a BTC address and by that burned
|
||||
* the pending BSQ (spending amount was higher than sending amount). One was still unspent.
|
||||
* The BSQ was sitting in the BTC wallet so not even visible as BSQ to the user.
|
||||
* If the user would have crafted a custom BSQ tx he could have avoided that the full trade fee was burned.
|
||||
*
|
||||
* Not an universal rule:
|
||||
* We cannot enforce the rule that no BSQ output is permitted to all possible transactions because there can be cases
|
||||
* where we need to permit this case.
|
||||
* For instance in case we confiscate a lockupTx we have usually 2 BSQ outputs: The first one is the bond which
|
||||
* should be confiscated and the second one is the BSQ change output.
|
||||
* At confiscating we set the first to TxOutputType.BTC_OUTPUT but we do not want to confiscate
|
||||
* the second BSQ change output as well. So we do not apply the rule that no BSQ is allowed once a BTC output is
|
||||
* found. Theoretically other transactions could be confiscated as well and all BSQ tx which allow > 1 BSQ outputs
|
||||
* would have the same issue as well if the first output gets confiscated.
|
||||
* We also don't enforce the rule for irregular or invalid txs which are usually set and detected at the end of
|
||||
* the tx parsing which is done in the TxParser. Blind vote and LockupTx with invalid OpReturn would be such cases
|
||||
* where we don't want to invalidate the change output (See comments in TxParser).
|
||||
*
|
||||
* Most transactions created in Bisq (Proposal, blind vote and lockup,...) have only 1 or 2 BSQ
|
||||
* outputs but we do not enforce a limit of max. 2 transactions in the parser.
|
||||
* We leave for now that flexibility but it should not be considered as a rule. We might strengthen
|
||||
* it any time if we find a reason for that (e.g. attack risk) and add checks that no more
|
||||
* BSQ outputs are permitted for those txs.
|
||||
* Some transactions like issuance, vote reveal and unlock have exactly 1 BSQ output and that rule
|
||||
* is enforced.
|
||||
*/
|
||||
@Slf4j
|
||||
public class TxOutputParser {
|
||||
private final DaoStateService daoStateService;
|
||||
class TxOutputParser {
|
||||
private static int ACTIVATE_HARD_FORK_1_HEIGHT_MAINNET = 605000;
|
||||
private static int ACTIVATE_HARD_FORK_1_HEIGHT_TESTNET = 1583054;
|
||||
private static int ACTIVATE_HARD_FORK_1_HEIGHT_REGTEST = 1;
|
||||
|
||||
private final DaoStateService daoStateService;
|
||||
// Setters
|
||||
@Getter
|
||||
@Setter
|
||||
@ -66,10 +114,12 @@ public class TxOutputParser {
|
||||
private Optional<TempTxOutput> optionalVoteRevealUnlockStakeOutput = Optional.empty();
|
||||
@Getter
|
||||
private Optional<TempTxOutput> optionalLockupOutput = Optional.empty();
|
||||
private Optional<Integer> optionalOpReturnIndex = Optional.empty();
|
||||
|
||||
// Private
|
||||
private int lockTime;
|
||||
private final List<TempTxOutput> utxoCandidates = new ArrayList<>();
|
||||
private boolean prohibitMoreBsqOutputs = false;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -93,6 +143,8 @@ public class TxOutputParser {
|
||||
|
||||
optionalOpReturnType = getMappedOpReturnType(txOutputType);
|
||||
|
||||
optionalOpReturnType.ifPresent(e -> optionalOpReturnIndex = Optional.of(tempTxOutput.getIndex()));
|
||||
|
||||
// If we have a LOCKUP opReturn output we save the lockTime to apply it later to the LOCKUP output.
|
||||
// We keep that data in that other output as it makes parsing of the UNLOCK tx easier.
|
||||
optionalOpReturnType.filter(opReturnType -> opReturnType == OpReturnType.LOCKUP)
|
||||
@ -100,7 +152,6 @@ public class TxOutputParser {
|
||||
}
|
||||
|
||||
void processTxOutput(TempTxOutput tempTxOutput) {
|
||||
if (!daoStateService.isConfiscatedOutput(tempTxOutput.getKey())) {
|
||||
// We don not expect here an opReturn output as we do not get called on the last output. Any opReturn at
|
||||
// another output index is invalid.
|
||||
if (tempTxOutput.isOpReturnOutput()) {
|
||||
@ -108,6 +159,7 @@ public class TxOutputParser {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!daoStateService.isConfiscatedOutput(tempTxOutput.getKey())) {
|
||||
long txOutputValue = tempTxOutput.getValue();
|
||||
int index = tempTxOutput.getIndex();
|
||||
if (isUnlockBondTx(tempTxOutput.getValue(), index)) {
|
||||
@ -118,8 +170,18 @@ public class TxOutputParser {
|
||||
} else if (isBtcOutputOfBurnFeeTx(tempTxOutput)) {
|
||||
// In case we have the opReturn for a burn fee tx all outputs after 1st output are considered BTC
|
||||
handleBtcOutput(tempTxOutput, index);
|
||||
} else if (isHardForkActivated(tempTxOutput) && isIssuanceCandidateTxOutput(tempTxOutput)) {
|
||||
// After the hard fork activation we fix a bug with a transaction which would have interpreted the
|
||||
// issuance output as BSQ if the availableInputValue was >= issuance amount.
|
||||
// Such a tx was never created but as we don't know if it will happen before activation date we cannot
|
||||
// enforce the bug fix which represents a rule change before the activation date.
|
||||
handleIssuanceCandidateOutput(tempTxOutput);
|
||||
} else if (availableInputValue > 0 && availableInputValue >= txOutputValue) {
|
||||
if (isHardForkActivated(tempTxOutput) && prohibitMoreBsqOutputs) {
|
||||
handleBtcOutput(tempTxOutput, index);
|
||||
} else {
|
||||
handleBsqOutput(tempTxOutput, index, txOutputValue);
|
||||
}
|
||||
} else {
|
||||
handleBtcOutput(tempTxOutput, index);
|
||||
}
|
||||
@ -127,6 +189,9 @@ public class TxOutputParser {
|
||||
log.warn("TxOutput {} is confiscated ", tempTxOutput.getKey());
|
||||
// We only burn that output
|
||||
availableInputValue -= tempTxOutput.getValue();
|
||||
|
||||
// We must not set prohibitMoreBsqOutputs at confiscation transactions as optional
|
||||
// BSQ change output (output 2) must not be confiscated.
|
||||
tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
|
||||
}
|
||||
}
|
||||
@ -139,6 +204,7 @@ public class TxOutputParser {
|
||||
* This sets all outputs to BTC_OUTPUT and doesn't add any txOutputs to the unspentTxOutput map in daoStateService
|
||||
*/
|
||||
void invalidateUTXOCandidates() {
|
||||
// We do not need to apply prohibitMoreBsqOutputs as all spendable outputs are set to BTC_OUTPUT anyway.
|
||||
utxoCandidates.forEach(output -> output.setTxOutputType(TxOutputType.BTC_OUTPUT));
|
||||
}
|
||||
|
||||
@ -171,17 +237,108 @@ public class TxOutputParser {
|
||||
utxoCandidates.add(txOutput);
|
||||
|
||||
bsqOutputFound = true;
|
||||
|
||||
// We do not permit more BSQ outputs after the unlock txo as we don't expect additional BSQ outputs.
|
||||
prohibitMoreBsqOutputs = true;
|
||||
}
|
||||
|
||||
private boolean isBtcOutputOfBurnFeeTx(TempTxOutput tempTxOutput) {
|
||||
// If we get a asset listing or proof of burn tx we have only 1 BSQ output and if the
|
||||
// burned amount is larger than the miner fee we might have a BTC output for receiving the burned funds.
|
||||
// If the burned funds are less than the miner fee a BTC input is used for miner fee and a BTC change output for
|
||||
// the remaining funds. In any case only the first output is BSQ all the others are BTC.
|
||||
return optionalOpReturnType.isPresent() &&
|
||||
(optionalOpReturnType.get() == OpReturnType.ASSET_LISTING_FEE ||
|
||||
optionalOpReturnType.get() == OpReturnType.PROOF_OF_BURN) &&
|
||||
tempTxOutput.getIndex() >= 1;
|
||||
if (optionalOpReturnType.isPresent()) {
|
||||
int index = tempTxOutput.getIndex();
|
||||
switch (optionalOpReturnType.get()) {
|
||||
case UNDEFINED:
|
||||
break;
|
||||
case PROPOSAL:
|
||||
if (isHardForkActivated(tempTxOutput)) {
|
||||
// We enforce a mandatory BSQ change output.
|
||||
// We need that as similar to ASSET_LISTING_FEE and PROOF_OF_BURN
|
||||
// we could not distinguish between 2 structurally same transactions otherwise (only way here
|
||||
// would be to check the proposal fee as that is known from the params).
|
||||
return index >= 1;
|
||||
}
|
||||
break;
|
||||
case COMPENSATION_REQUEST:
|
||||
break;
|
||||
case REIMBURSEMENT_REQUEST:
|
||||
break;
|
||||
case BLIND_VOTE:
|
||||
if (isHardForkActivated(tempTxOutput)) {
|
||||
// After the hard fork activation we fix a bug with a transaction which would have interpreted the
|
||||
// burned vote fee output as BSQ if the vote fee was >= miner fee.
|
||||
// Such a tx was never created but as we don't know if it will happen before activation date we cannot
|
||||
// enforce the bug fix which represents a rule change before the activation date.
|
||||
|
||||
// If it is the vote stake output we return false.
|
||||
if (index == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There must be a vote fee left
|
||||
if (availableInputValue <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Burned BSQ output is last output before opReturn.
|
||||
// We could have also a BSQ change output as last output before opReturn but that will
|
||||
// be detected at blindVoteFee check.
|
||||
// We always have the BSQ change before the burned BSQ output if both are present.
|
||||
checkArgument(optionalOpReturnIndex.isPresent());
|
||||
if (index != optionalOpReturnIndex.get() - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Without checking the fee we would not be able to distinguish between 2 structurally same transactions, one
|
||||
// where the output is burned BSQ and one where it is a BSQ change output.
|
||||
long blindVoteFee = daoStateService.getParamValueAsCoin(Param.BLIND_VOTE_FEE, tempTxOutput.getBlockHeight()).value;
|
||||
return availableInputValue == blindVoteFee;
|
||||
}
|
||||
case VOTE_REVEAL:
|
||||
break;
|
||||
case LOCKUP:
|
||||
break;
|
||||
case ASSET_LISTING_FEE:
|
||||
case PROOF_OF_BURN:
|
||||
// Asset listing fee and proof of burn tx are structurally the same.
|
||||
|
||||
// We need to require one BSQ change output as we could otherwise not be able to distinguish between 2
|
||||
// structurally same transactions where only the BSQ fee is different. In case of asset listing fee and proof of
|
||||
// burn it is a user input, so it is not know to the parser, instead we derive the burned fee from the parser.
|
||||
// In case of proposal fee we could derive it from the params.
|
||||
|
||||
// Case 1: 10 BSQ fee to burn
|
||||
// In: 17 BSQ
|
||||
// Out: BSQ change 7 BSQ -> valid BSQ
|
||||
// Out: OpReturn
|
||||
// Miner fee: 1000 sat (10 BSQ burned)
|
||||
|
||||
|
||||
// Case 2: 17 BSQ fee to burn
|
||||
// In: 17 BSQ
|
||||
// Out: burned BSQ change 7 BSQ -> BTC (7 BSQ burned)
|
||||
// Out: OpReturn
|
||||
// Miner fee: 1000 sat (10 BSQ burned)
|
||||
return index >= 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isIssuanceCandidateTxOutput(TempTxOutput tempTxOutput) {
|
||||
// If we have BSQ left as fee and we are at the second output we interpret it as a compensation request output.
|
||||
return availableInputValue > 0 &&
|
||||
tempTxOutput.getIndex() == 1 &&
|
||||
optionalOpReturnType.isPresent() &&
|
||||
(optionalOpReturnType.get() == OpReturnType.COMPENSATION_REQUEST ||
|
||||
optionalOpReturnType.get() == OpReturnType.REIMBURSEMENT_REQUEST);
|
||||
}
|
||||
|
||||
private void handleIssuanceCandidateOutput(TempTxOutput tempTxOutput) {
|
||||
// We do not permit more BSQ outputs after the issuance candidate.
|
||||
prohibitMoreBsqOutputs = true;
|
||||
|
||||
// We store the candidate but we don't apply the TxOutputType yet as we need to verify the fee after all
|
||||
// outputs are parsed and check the phase. The TxParser will do that....
|
||||
optionalIssuanceCandidate = Optional.of(tempTxOutput);
|
||||
}
|
||||
|
||||
private void handleBsqOutput(TempTxOutput txOutput, int index, long txOutputValue) {
|
||||
@ -201,6 +358,9 @@ public class TxOutputParser {
|
||||
} else if (isFirstOutput && opReturnTypeCandidate == OpReturnType.VOTE_REVEAL) {
|
||||
txOutputType = TxOutputType.VOTE_REVEAL_UNLOCK_STAKE_OUTPUT;
|
||||
optionalVoteRevealUnlockStakeOutput = Optional.of(txOutput);
|
||||
|
||||
// We do not permit more BSQ outputs after the VOTE_REVEAL_UNLOCK_STAKE_OUTPUT.
|
||||
prohibitMoreBsqOutputs = true;
|
||||
} else if (isFirstOutput && opReturnTypeCandidate == OpReturnType.LOCKUP) {
|
||||
txOutputType = TxOutputType.LOCKUP_OUTPUT;
|
||||
|
||||
@ -219,6 +379,12 @@ public class TxOutputParser {
|
||||
}
|
||||
|
||||
private void handleBtcOutput(TempTxOutput txOutput, int index) {
|
||||
if (isHardForkActivated(txOutput)) {
|
||||
txOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
|
||||
|
||||
// For regular transactions we don't permit BSQ outputs after a BTC output was detected.
|
||||
prohibitMoreBsqOutputs = true;
|
||||
} else {
|
||||
// If we have BSQ left as fee and we are at the second output it might be a compensation request output.
|
||||
// We store the candidate but we don't apply the TxOutputType yet as we need to verify the fee after all
|
||||
// outputs are parsed and check the phase. The TxParser will do that....
|
||||
@ -228,10 +394,27 @@ public class TxOutputParser {
|
||||
(optionalOpReturnType.get() == OpReturnType.COMPENSATION_REQUEST ||
|
||||
optionalOpReturnType.get() == OpReturnType.REIMBURSEMENT_REQUEST)) {
|
||||
optionalIssuanceCandidate = Optional.of(txOutput);
|
||||
|
||||
// We do not permit more BSQ outputs after the issuance candidate.
|
||||
prohibitMoreBsqOutputs = true;
|
||||
} else {
|
||||
txOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
|
||||
|
||||
// For regular transactions we don't permit BSQ outputs after a BTC output was detected.
|
||||
prohibitMoreBsqOutputs = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHardForkActivated(TempTxOutput tempTxOutput) {
|
||||
return tempTxOutput.getBlockHeight() >= getActivateHardFork1Height();
|
||||
}
|
||||
|
||||
private int getActivateHardFork1Height() {
|
||||
return BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? ACTIVATE_HARD_FORK_1_HEIGHT_MAINNET :
|
||||
BisqEnvironment.getBaseCurrencyNetwork().isTestnet() ? ACTIVATE_HARD_FORK_1_HEIGHT_TESTNET :
|
||||
ACTIVATE_HARD_FORK_1_HEIGHT_REGTEST;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -217,19 +217,33 @@ public class TxParser {
|
||||
// We need to check if any tempTxOutput is available and if so and the OpReturn data is invalid we
|
||||
// set the output to a BTC output. We must not use `if else` cases here!
|
||||
if (opReturnType != OpReturnType.COMPENSATION_REQUEST && opReturnType != OpReturnType.REIMBURSEMENT_REQUEST) {
|
||||
// We applied already the check to not permit further BSQ outputs after the issuanceCandidate in the
|
||||
// txOutputParser so we don't need to do any additional check here when we change to BTC_OUTPUT.
|
||||
txOutputParser.getOptionalIssuanceCandidate().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
|
||||
}
|
||||
|
||||
if (opReturnType != OpReturnType.BLIND_VOTE) {
|
||||
txOutputParser.getOptionalBlindVoteLockStakeOutput().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
|
||||
txOutputParser.getOptionalBlindVoteLockStakeOutput().ifPresent(tempTxOutput -> {
|
||||
// We cannot apply the rule to not allow BSQ outputs after a BTC output as the 2nd output is an
|
||||
// optional BSQ change output and we don't want to burn that in case the opReturn is invalid.
|
||||
tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
|
||||
});
|
||||
}
|
||||
|
||||
if (opReturnType != OpReturnType.VOTE_REVEAL) {
|
||||
txOutputParser.getOptionalVoteRevealUnlockStakeOutput().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
|
||||
txOutputParser.getOptionalVoteRevealUnlockStakeOutput().ifPresent(tempTxOutput -> {
|
||||
// We do not apply the rule to not allow BSQ outputs after a BTC output here because we expect only
|
||||
// one BSQ output anyway.
|
||||
tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
|
||||
});
|
||||
}
|
||||
|
||||
if (opReturnType != OpReturnType.LOCKUP) {
|
||||
txOutputParser.getOptionalLockupOutput().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
|
||||
txOutputParser.getOptionalLockupOutput().ifPresent(tempTxOutput -> {
|
||||
// We cannot apply the rule to not allow BSQ outputs after a BTC output as the 2nd output is an
|
||||
// optional BSQ change output and we don't want to burn that in case the opReturn is invalid.
|
||||
tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,12 +273,14 @@ public class TxParser {
|
||||
}
|
||||
} else {
|
||||
// This could be a valid compensation request that failed to be included in a block during the
|
||||
// correct phase due to no fault of the user. Better not burn the change as long as the BSQ inputs
|
||||
// correct phase due to no fault of the user. We must not burn the change as long as the BSQ inputs
|
||||
// cover the value of the outputs.
|
||||
// We tolerate such an incorrect tx and do not burn the BSQ
|
||||
tempTx.setTxType(TxType.IRREGULAR);
|
||||
|
||||
// Make sure the optionalIssuanceCandidate is set to BTC
|
||||
// We applied already the check to not permit further BSQ outputs after the issuanceCandidate in the
|
||||
// txOutputParser so we don't need to do any additional check here when we change to BTC_OUTPUT.
|
||||
optionalIssuanceCandidate.ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
|
||||
// Empty Optional case is a possible valid case where a random tx matches our opReturn rules but it is not a
|
||||
// valid BSQ tx.
|
||||
|
@ -98,6 +98,10 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
@Nullable
|
||||
private final List<String> mediators;
|
||||
|
||||
// added in v1.2.0
|
||||
@Nullable
|
||||
private final List<String> refundAgents;
|
||||
|
||||
public Filter(List<String> bannedOfferIds,
|
||||
List<String> bannedNodeAddress,
|
||||
List<PaymentAccountFilter> bannedPaymentAccounts,
|
||||
@ -111,7 +115,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
boolean disableDao,
|
||||
@Nullable String disableDaoBelowVersion,
|
||||
@Nullable String disableTradeBelowVersion,
|
||||
@Nullable List<String> mediators) {
|
||||
@Nullable List<String> mediators,
|
||||
@Nullable List<String> refundAgents) {
|
||||
this.bannedOfferIds = bannedOfferIds;
|
||||
this.bannedNodeAddress = bannedNodeAddress;
|
||||
this.bannedPaymentAccounts = bannedPaymentAccounts;
|
||||
@ -126,6 +131,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
this.disableDaoBelowVersion = disableDaoBelowVersion;
|
||||
this.disableTradeBelowVersion = disableTradeBelowVersion;
|
||||
this.mediators = mediators;
|
||||
this.refundAgents = refundAgents;
|
||||
}
|
||||
|
||||
|
||||
@ -150,7 +156,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
String signatureAsBase64,
|
||||
byte[] ownerPubKeyBytes,
|
||||
@Nullable Map<String, String> extraDataMap,
|
||||
@Nullable List<String> mediators) {
|
||||
@Nullable List<String> mediators,
|
||||
@Nullable List<String> refundAgents) {
|
||||
this(bannedOfferIds,
|
||||
bannedNodeAddress,
|
||||
bannedPaymentAccounts,
|
||||
@ -164,7 +171,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
disableDao,
|
||||
disableDaoBelowVersion,
|
||||
disableTradeBelowVersion,
|
||||
mediators);
|
||||
mediators,
|
||||
refundAgents);
|
||||
this.signatureAsBase64 = signatureAsBase64;
|
||||
this.ownerPubKeyBytes = ownerPubKeyBytes;
|
||||
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
||||
@ -198,6 +206,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
Optional.ofNullable(disableTradeBelowVersion).ifPresent(builder::setDisableTradeBelowVersion);
|
||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||
Optional.ofNullable(mediators).ifPresent(builder::addAllMediators);
|
||||
Optional.ofNullable(refundAgents).ifPresent(builder::addAllRefundAgents);
|
||||
|
||||
return protobuf.StoragePayload.newBuilder().setFilter(builder).build();
|
||||
}
|
||||
@ -221,7 +230,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
proto.getSignatureAsBase64(),
|
||||
proto.getOwnerPubKeyBytes().toByteArray(),
|
||||
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap(),
|
||||
CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()));
|
||||
CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()),
|
||||
CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()));
|
||||
}
|
||||
|
||||
|
||||
@ -240,4 +250,26 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
|
||||
ownerPubKeyBytes = Sig.getPublicKeyBytes(this.ownerPubKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Filter{" +
|
||||
"\n bannedOfferIds=" + bannedOfferIds +
|
||||
",\n bannedNodeAddress=" + bannedNodeAddress +
|
||||
",\n bannedPaymentAccounts=" + bannedPaymentAccounts +
|
||||
",\n bannedCurrencies=" + bannedCurrencies +
|
||||
",\n bannedPaymentMethods=" + bannedPaymentMethods +
|
||||
",\n arbitrators=" + arbitrators +
|
||||
",\n seedNodes=" + seedNodes +
|
||||
",\n priceRelayNodes=" + priceRelayNodes +
|
||||
",\n preventPublicBtcNetwork=" + preventPublicBtcNetwork +
|
||||
",\n btcNodes=" + btcNodes +
|
||||
",\n extraDataMap=" + extraDataMap +
|
||||
",\n disableDao=" + disableDao +
|
||||
",\n disableDaoBelowVersion='" + disableDaoBelowVersion + '\'' +
|
||||
",\n disableTradeBelowVersion='" + disableTradeBelowVersion + '\'' +
|
||||
",\n mediators=" + mediators +
|
||||
",\n refundAgents=" + refundAgents +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
@ -297,7 +297,7 @@ public class FilterManager {
|
||||
ECKey.fromPublicOnly(HEX.decode(pubKeyAsHex)).verifyMessage(getHexFromData(filter), filter.getSignatureAsBase64());
|
||||
return true;
|
||||
} catch (SignatureException e) {
|
||||
log.warn("verifySignature failed");
|
||||
log.warn("verifySignature failed. filter={}", filter);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -26,5 +26,6 @@ public enum AvailabilityResult {
|
||||
NO_ARBITRATORS,
|
||||
NO_MEDIATORS,
|
||||
USER_IGNORED,
|
||||
MISSING_MANDATORY_CAPABILITY
|
||||
MISSING_MANDATORY_CAPABILITY,
|
||||
NO_REFUND_AGENTS
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ import bisq.network.p2p.storage.HashMapChangedListener;
|
||||
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.storage.JsonFileManager;
|
||||
@ -93,11 +92,9 @@ public class OfferBookService {
|
||||
if (data.getProtectedStoragePayload() instanceof OfferPayload) {
|
||||
OfferPayload offerPayload = (OfferPayload) data.getProtectedStoragePayload();
|
||||
Offer offer = new Offer(offerPayload);
|
||||
if (showOffer(offer)) {
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
listener.onAdded(offer);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -135,11 +132,6 @@ public class OfferBookService {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean showOffer(Offer offer) {
|
||||
return !OfferRestrictions.requiresUpdate() ||
|
||||
OfferRestrictions.hasOfferMandatoryCapability(offer, Capability.MEDIATION);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
@ -208,7 +200,6 @@ public class OfferBookService {
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
return offer;
|
||||
})
|
||||
.filter(this::showOffer)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,6 @@
|
||||
|
||||
package bisq.core.offer;
|
||||
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.trade.Trade;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.util.Utilities;
|
||||
@ -41,38 +38,6 @@ public class OfferRestrictions {
|
||||
|
||||
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01");
|
||||
|
||||
public static boolean isOfferRisky(Offer offer) {
|
||||
return offer != null &&
|
||||
offer.isBuyOffer() &&
|
||||
PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) &&
|
||||
isMinTradeAmountRisky(offer);
|
||||
}
|
||||
|
||||
public static boolean isSellOfferRisky(Offer offer) {
|
||||
return offer != null &&
|
||||
PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) &&
|
||||
isMinTradeAmountRisky(offer);
|
||||
}
|
||||
|
||||
public static boolean isTradeRisky(Trade trade) {
|
||||
if (trade == null)
|
||||
return false;
|
||||
|
||||
Offer offer = trade.getOffer();
|
||||
return offer != null &&
|
||||
PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) &&
|
||||
trade.getTradeAmount() != null &&
|
||||
isAmountRisky(trade.getTradeAmount());
|
||||
}
|
||||
|
||||
public static boolean isMinTradeAmountRisky(Offer offer) {
|
||||
return isAmountRisky(offer.getMinAmount());
|
||||
}
|
||||
|
||||
private static boolean isAmountRisky(Coin amount) {
|
||||
return amount.isGreaterThan(TOLERATED_SMALL_TRADE_AMOUNT);
|
||||
}
|
||||
|
||||
static boolean hasOfferMandatoryCapability(Offer offer, Capability mandatoryCapability) {
|
||||
Map<String, String> extraDataMap = offer.getOfferPayload().getExtraDataMap();
|
||||
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.CAPABILITIES)) {
|
||||
|
@ -32,7 +32,6 @@ import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.statistics.ReferralIdService;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.BsqFormatter;
|
||||
import bisq.core.util.CoinUtil;
|
||||
|
||||
@ -326,7 +325,8 @@ public class OfferUtil {
|
||||
public static Map<String, String> getExtraDataMap(AccountAgeWitnessService accountAgeWitnessService,
|
||||
ReferralIdService referralIdService,
|
||||
PaymentAccount paymentAccount,
|
||||
String currencyCode) {
|
||||
String currencyCode,
|
||||
Preferences preferences) {
|
||||
Map<String, String> extraDataMap = new HashMap<>();
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
String myWitnessHashAsHex = accountAgeWitnessService.getMyWitnessHashAsHex(paymentAccount.getPaymentAccountPayload());
|
||||
|
@ -65,6 +65,12 @@ public final class OpenOffer implements Tradable {
|
||||
@Nullable
|
||||
private NodeAddress mediatorNodeAddress;
|
||||
|
||||
// Added v1.2.0
|
||||
@Getter
|
||||
@Setter
|
||||
@Nullable
|
||||
private NodeAddress refundAgentNodeAddress;
|
||||
|
||||
transient private Storage<TradableList<OpenOffer>> storage;
|
||||
|
||||
public OpenOffer(Offer offer, Storage<TradableList<OpenOffer>> storage) {
|
||||
@ -80,11 +86,13 @@ public final class OpenOffer implements Tradable {
|
||||
private OpenOffer(Offer offer,
|
||||
State state,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress) {
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress) {
|
||||
this.offer = offer;
|
||||
this.state = state;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
this.mediatorNodeAddress = mediatorNodeAddress;
|
||||
this.refundAgentNodeAddress = refundAgentNodeAddress;
|
||||
|
||||
if (this.state == State.RESERVED)
|
||||
setState(State.AVAILABLE);
|
||||
@ -98,6 +106,7 @@ public final class OpenOffer implements Tradable {
|
||||
|
||||
Optional.ofNullable(arbitratorNodeAddress).ifPresent(nodeAddress -> builder.setArbitratorNodeAddress(nodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(mediatorNodeAddress).ifPresent(nodeAddress -> builder.setMediatorNodeAddress(nodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(refundAgentNodeAddress).ifPresent(nodeAddress -> builder.setRefundAgentNodeAddress(nodeAddress.toProtoMessage()));
|
||||
|
||||
return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
|
||||
}
|
||||
@ -106,7 +115,8 @@ public final class OpenOffer implements Tradable {
|
||||
return new OpenOffer(Offer.fromProto(proto.getOffer()),
|
||||
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()),
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null);
|
||||
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
|
||||
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
|
||||
}
|
||||
|
||||
|
||||
@ -175,6 +185,7 @@ public final class OpenOffer implements Tradable {
|
||||
",\n state=" + state +
|
||||
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ package bisq.core.offer;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||
@ -29,6 +30,7 @@ import bisq.core.offer.placeoffer.PlaceOfferProtocol;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
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.TradableList;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
||||
@ -51,6 +53,7 @@ import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
@ -104,6 +107,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private final MediatorManager mediatorManager;
|
||||
private final RefundAgentManager refundAgentManager;
|
||||
private final DaoFacade daoFacade;
|
||||
private final Storage<TradableList<OpenOffer>> openOfferTradableListStorage;
|
||||
private final Map<String, OpenOffer> offersToBeEdited = new HashMap<>();
|
||||
private boolean stopped;
|
||||
@ -129,6 +134,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
ArbitratorManager arbitratorManager,
|
||||
MediatorManager mediatorManager,
|
||||
RefundAgentManager refundAgentManager,
|
||||
DaoFacade daoFacade,
|
||||
Storage<TradableList<OpenOffer>> storage) {
|
||||
this.keyRing = keyRing;
|
||||
this.user = user;
|
||||
@ -143,6 +150,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.refundAgentManager = refundAgentManager;
|
||||
this.daoFacade = daoFacade;
|
||||
|
||||
openOfferTradableListStorage = storage;
|
||||
|
||||
@ -339,6 +348,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
offerBookService,
|
||||
arbitratorManager,
|
||||
tradeStatisticsManager,
|
||||
daoFacade,
|
||||
user);
|
||||
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
|
||||
model,
|
||||
@ -577,6 +587,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
AvailabilityResult availabilityResult;
|
||||
NodeAddress arbitratorNodeAddress = null;
|
||||
NodeAddress mediatorNodeAddress = null;
|
||||
NodeAddress refundAgentNodeAddress = null;
|
||||
if (openOfferOptional.isPresent()) {
|
||||
OpenOffer openOffer = openOfferOptional.get();
|
||||
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
|
||||
@ -584,17 +595,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
|
||||
availabilityResult = AvailabilityResult.AVAILABLE;
|
||||
|
||||
List<NodeAddress> acceptedArbitrators = user.getAcceptedArbitratorAddresses();
|
||||
if (acceptedArbitrators != null && !acceptedArbitrators.isEmpty()) {
|
||||
arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, arbitratorManager).getNodeAddress();
|
||||
openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
|
||||
|
||||
mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress();
|
||||
openOffer.setMediatorNodeAddress(mediatorNodeAddress);
|
||||
Capabilities supportedCapabilities = request.getSupportedCapabilities();
|
||||
if (!OfferRestrictions.requiresUpdate() ||
|
||||
(supportedCapabilities != null &&
|
||||
Capabilities.hasMandatoryCapability(supportedCapabilities, Capability.MEDIATION))) {
|
||||
|
||||
refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress();
|
||||
openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress);
|
||||
|
||||
try {
|
||||
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
|
||||
// in trade price between the peers. Also here poor connectivity might cause market price API connection
|
||||
@ -610,16 +616,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
log.warn("Trade price check failed. " + e.getMessage());
|
||||
availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
|
||||
}
|
||||
} else {
|
||||
log.warn("Taker has not mandatory capability MEDIATION");
|
||||
// Because an old peer has not AvailabilityResult.MISSING_MANDATORY_CAPABILITY and we
|
||||
// have not set the UNDEFINED fallback in AvailabilityResult the user will get a null value.
|
||||
availabilityResult = AvailabilityResult.MISSING_MANDATORY_CAPABILITY;
|
||||
}
|
||||
} else {
|
||||
log.warn("acceptedArbitrators is null or empty: acceptedArbitrators=" + acceptedArbitrators);
|
||||
availabilityResult = AvailabilityResult.NO_ARBITRATORS;
|
||||
}
|
||||
} else {
|
||||
availabilityResult = AvailabilityResult.USER_IGNORED;
|
||||
}
|
||||
@ -634,7 +630,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
|
||||
availabilityResult,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress);
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress);
|
||||
log.info("Send {} with offerId {} and uid {} to peer {}",
|
||||
offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(),
|
||||
offerAvailabilityResponse.getUid(), peer);
|
||||
@ -715,14 +712,17 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
private void maybeUpdatePersistedOffers() {
|
||||
// We need to clone to avoid ConcurrentModificationException
|
||||
ArrayList<OpenOffer> openOffersClone = new ArrayList<>(openOffers.getList());
|
||||
openOffersClone.forEach(openOffer -> {
|
||||
Offer originalOffer = openOffer.getOffer();
|
||||
openOffersClone.forEach(originalOpenOffer -> {
|
||||
Offer originalOffer = originalOpenOffer.getOffer();
|
||||
|
||||
OfferPayload originalOfferPayload = originalOffer.getOfferPayload();
|
||||
// We added CAPABILITIES with entry for Capability.MEDIATION in v1.1.6 and want to rewrite a
|
||||
// persisted offer after the user has updated to 1.1.6 so their offer will be accepted by the network.
|
||||
// We added CAPABILITIES with entry for Capability.MEDIATION in v1.1.6 and
|
||||
// Capability.REFUND_AGENT in v1.2.0 and want to rewrite a
|
||||
// persisted offer after the user has updated to 1.2.0 so their offer will be accepted by the network.
|
||||
|
||||
if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION)) {
|
||||
if (originalOfferPayload.getProtocolVersion() < Version.TRADE_PROTOCOL_VERSION ||
|
||||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) ||
|
||||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT)) {
|
||||
// We rewrite our offer with the additional capabilities entry
|
||||
|
||||
Map<String, String> originalExtraDataMap = originalOfferPayload.getExtraDataMap();
|
||||
@ -735,6 +735,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
// We overwrite any entry with our current capabilities
|
||||
updatedExtraDataMap.put(OfferPayload.CAPABILITIES, Capabilities.app.toStringList());
|
||||
|
||||
// We update the trade protocol version
|
||||
int protocolVersion = Version.TRADE_PROTOCOL_VERSION;
|
||||
|
||||
OfferPayload updatedPayload = new OfferPayload(originalOfferPayload.getId(),
|
||||
originalOfferPayload.getDate(),
|
||||
originalOfferPayload.getOwnerNodeAddress(),
|
||||
@ -772,17 +775,17 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
originalOfferPayload.isPrivateOffer(),
|
||||
originalOfferPayload.getHashOfChallenge(),
|
||||
updatedExtraDataMap,
|
||||
originalOfferPayload.getProtocolVersion());
|
||||
protocolVersion);
|
||||
|
||||
// Save states from original data to use the for updated
|
||||
// Save states from original data to use for the updated
|
||||
Offer.State originalOfferState = originalOffer.getState();
|
||||
OpenOffer.State originalOpenOfferState = openOffer.getState();
|
||||
OpenOffer.State originalOpenOfferState = originalOpenOffer.getState();
|
||||
|
||||
// remove old offer
|
||||
originalOffer.setState(Offer.State.REMOVED);
|
||||
openOffer.setState(OpenOffer.State.CANCELED);
|
||||
openOffer.setStorage(openOfferTradableListStorage);
|
||||
openOffers.remove(openOffer);
|
||||
originalOpenOffer.setState(OpenOffer.State.CANCELED);
|
||||
originalOpenOffer.setStorage(openOfferTradableListStorage);
|
||||
openOffers.remove(originalOpenOffer);
|
||||
|
||||
// Create new Offer
|
||||
Offer updatedOffer = new Offer(updatedPayload);
|
||||
@ -794,7 +797,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
updatedOpenOffer.setStorage(openOfferTradableListStorage);
|
||||
openOffers.add(updatedOpenOffer);
|
||||
|
||||
log.info("Converted offer to support new Capability.MEDIATION capability. id={}", originalOffer.getId());
|
||||
log.info("Converted offer to support new Capability.MEDIATION and Capability.REFUND_AGENT capability. id={}", originalOffer.getId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -57,6 +57,13 @@ public class DisputeAgentSelection {
|
||||
TradeStatistics2.MEDIATOR_ADDRESS);
|
||||
}
|
||||
|
||||
public static <T extends DisputeAgent> T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager) {
|
||||
return getLeastUsedDisputeAgent(tradeStatisticsManager,
|
||||
disputeAgentManager,
|
||||
TradeStatistics2.REFUND_AGENT_ADDRESS);
|
||||
}
|
||||
|
||||
private static <T extends DisputeAgent> T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager,
|
||||
String extraMapKey) {
|
||||
|
@ -60,6 +60,12 @@ public class OfferAvailabilityModel implements Model {
|
||||
@Getter
|
||||
private NodeAddress selectedMediator;
|
||||
|
||||
// Added in v1.2.0
|
||||
@Nullable
|
||||
@Setter
|
||||
@Getter
|
||||
private NodeAddress selectedRefundAgent;
|
||||
|
||||
|
||||
public OfferAvailabilityModel(Offer offer,
|
||||
PubKeyRing pubKeyRing,
|
||||
|
@ -57,12 +57,16 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
|
||||
offer.setState(Offer.State.AVAILABLE);
|
||||
|
||||
model.setSelectedArbitrator(offerAvailabilityResponse.getArbitrator());
|
||||
|
||||
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();
|
||||
}
|
||||
model.setSelectedMediator(mediator);
|
||||
|
||||
model.setSelectedRefundAgent(offerAvailabilityResponse.getRefundAgent());
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
offer.setErrorMessage("An error occurred.\n" +
|
||||
|
@ -49,17 +49,23 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
|
||||
@Nullable
|
||||
private final NodeAddress mediator;
|
||||
|
||||
// Added v1.2.0
|
||||
@Nullable
|
||||
private final NodeAddress refundAgent;
|
||||
|
||||
public OfferAvailabilityResponse(String offerId,
|
||||
AvailabilityResult availabilityResult,
|
||||
NodeAddress arbitrator,
|
||||
NodeAddress mediator) {
|
||||
NodeAddress mediator,
|
||||
NodeAddress refundAgent) {
|
||||
this(offerId,
|
||||
availabilityResult,
|
||||
Capabilities.app,
|
||||
Version.getP2PMessageVersion(),
|
||||
UUID.randomUUID().toString(),
|
||||
arbitrator,
|
||||
mediator);
|
||||
mediator,
|
||||
refundAgent);
|
||||
}
|
||||
|
||||
|
||||
@ -73,24 +79,27 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
|
||||
int messageVersion,
|
||||
@Nullable String uid,
|
||||
NodeAddress arbitrator,
|
||||
@Nullable NodeAddress mediator) {
|
||||
@Nullable NodeAddress mediator,
|
||||
@Nullable NodeAddress refundAgent) {
|
||||
super(messageVersion, offerId, uid);
|
||||
this.availabilityResult = availabilityResult;
|
||||
this.supportedCapabilities = supportedCapabilities;
|
||||
this.arbitrator = arbitrator;
|
||||
this.mediator = mediator;
|
||||
this.refundAgent = refundAgent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name()))
|
||||
.setArbitrator(arbitrator.toProtoMessage());
|
||||
.setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name()));
|
||||
|
||||
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
|
||||
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
|
||||
Optional.ofNullable(mediator).ifPresent(e -> builder.setMediator(mediator.toProtoMessage()));
|
||||
Optional.ofNullable(refundAgent).ifPresent(e -> builder.setRefundAgent(refundAgent.toProtoMessage()));
|
||||
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator(arbitrator.toProtoMessage()));
|
||||
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setOfferAvailabilityResponse(builder)
|
||||
@ -103,7 +112,8 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
|
||||
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
|
||||
messageVersion,
|
||||
proto.getUid().isEmpty() ? null : proto.getUid(),
|
||||
NodeAddress.fromProto(proto.getArbitrator()),
|
||||
proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null);
|
||||
proto.hasArbitrator() ? NodeAddress.fromProto(proto.getArbitrator()) : null,
|
||||
proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null,
|
||||
proto.hasRefundAgent() ? NodeAddress.fromProto(proto.getRefundAgent()) : null);
|
||||
}
|
||||
}
|
||||
|
@ -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.dao.DaoFacade;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferBookService;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
@ -48,6 +49,7 @@ public class PlaceOfferModel implements Model {
|
||||
private final OfferBookService offerBookService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final DaoFacade daoFacade;
|
||||
private final User user;
|
||||
|
||||
// Mutable
|
||||
@ -65,6 +67,7 @@ public class PlaceOfferModel implements Model {
|
||||
OfferBookService offerBookService,
|
||||
ArbitratorManager arbitratorManager,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
DaoFacade daoFacade,
|
||||
User user) {
|
||||
this.offer = offer;
|
||||
this.reservedFundsForOffer = reservedFundsForOffer;
|
||||
@ -75,6 +78,7 @@ public class PlaceOfferModel implements Model {
|
||||
this.offerBookService = offerBookService;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.daoFacade = daoFacade;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
|
@ -25,11 +25,10 @@ import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.TxBroadcaster;
|
||||
import bisq.core.btc.wallet.WalletService;
|
||||
import bisq.core.dao.exceptions.DaoDisabledException;
|
||||
import bisq.core.dao.governance.param.Param;
|
||||
import bisq.core.dao.state.model.blockchain.TxType;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.taskrunner.Task;
|
||||
@ -45,7 +44,6 @@ import javax.annotation.Nullable;
|
||||
|
||||
public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
||||
private static final Logger log = LoggerFactory.getLogger(CreateMakerFeeTx.class);
|
||||
private Transaction tradeFeeTx = null;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public CreateMakerFeeTx(TaskRunner taskHandler, PlaceOfferModel model) {
|
||||
@ -62,17 +60,15 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
||||
String id = offer.getId();
|
||||
BtcWalletService walletService = model.getWalletService();
|
||||
|
||||
Arbitrator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(),
|
||||
model.getArbitratorManager());
|
||||
|
||||
Address fundingAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress();
|
||||
Address reservedForTradeAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
Address changeAddress = walletService.getFreshAddressEntry().getAddress();
|
||||
|
||||
final TradeWalletService tradeWalletService = model.getTradeWalletService();
|
||||
TradeWalletService tradeWalletService = model.getTradeWalletService();
|
||||
String feeReceiver = model.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS);
|
||||
|
||||
if (offer.isCurrencyForMakerFeeBtc()) {
|
||||
tradeFeeTx = tradeWalletService.createBtcTradingFeeTx(
|
||||
tradeWalletService.createBtcTradingFeeTx(
|
||||
fundingAddress,
|
||||
reservedForTradeAddress,
|
||||
changeAddress,
|
||||
@ -80,7 +76,7 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
||||
model.isUseSavingsWallet(),
|
||||
offer.getMakerFee(),
|
||||
offer.getTxFee(),
|
||||
arbitrator.getBtcAddress(),
|
||||
feeReceiver,
|
||||
true,
|
||||
new TxBroadcaster.Callback() {
|
||||
@Override
|
||||
@ -113,7 +109,7 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
||||
});
|
||||
} else {
|
||||
final BsqWalletService bsqWalletService = model.getBsqWalletService();
|
||||
Transaction preparedBurnFeeTx = model.getBsqWalletService().getPreparedBurnFeeTx(offer.getMakerFee());
|
||||
Transaction preparedBurnFeeTx = model.getBsqWalletService().getPreparedTradeFeeTx(offer.getMakerFee());
|
||||
Transaction txWithBsqFee = tradeWalletService.completeBsqTradingFeeTx(preparedBurnFeeTx,
|
||||
fundingAddress,
|
||||
reservedForTradeAddress,
|
||||
|
@ -17,11 +17,9 @@
|
||||
|
||||
package bisq.core.payment;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeRestrictions;
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.locale.Country;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferRestrictions;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
@ -41,57 +39,8 @@ import javax.annotation.Nullable;
|
||||
@Slf4j
|
||||
public class PaymentAccountUtil {
|
||||
|
||||
public static boolean isRiskyBuyOfferWithImmatureAccountAge(Offer offer, AccountAgeWitnessService accountAgeWitnessService) {
|
||||
return OfferRestrictions.isOfferRisky(offer) &&
|
||||
AccountAgeRestrictions.isMakersAccountAgeImmature(accountAgeWitnessService, offer);
|
||||
}
|
||||
|
||||
public static boolean isSellOfferAndAllTakerPaymentAccountsForOfferImmature(Offer offer,
|
||||
Collection<PaymentAccount> takerPaymentAccounts,
|
||||
AccountAgeWitnessService accountAgeWitnessService) {
|
||||
if (offer.isBuyOffer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!OfferRestrictions.isSellOfferRisky(offer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PaymentAccount takerPaymentAccount : takerPaymentAccounts) {
|
||||
if (isTakerAccountForOfferMature(offer, takerPaymentAccount, accountAgeWitnessService))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isTakerAccountForOfferMature(Offer offer,
|
||||
PaymentAccount takerPaymentAccount,
|
||||
AccountAgeWitnessService accountAgeWitnessService) {
|
||||
return !PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) ||
|
||||
!OfferRestrictions.isMinTradeAmountRisky(offer) ||
|
||||
(isTakerPaymentAccountValidForOffer(offer, takerPaymentAccount) &&
|
||||
!AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, takerPaymentAccount));
|
||||
}
|
||||
|
||||
public static boolean hasMakerAnyMatureAccountForBuyOffer(Collection<PaymentAccount> makerPaymentAccounts,
|
||||
AccountAgeWitnessService accountAgeWitnessService) {
|
||||
for (PaymentAccount makerPaymentAccount : makerPaymentAccounts) {
|
||||
if (hasMyMatureAccountForBuyOffer(makerPaymentAccount, accountAgeWitnessService))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasMyMatureAccountForBuyOffer(PaymentAccount myPaymentAccount,
|
||||
AccountAgeWitnessService accountAgeWitnessService) {
|
||||
if (myPaymentAccount.selectedTradeCurrency == null)
|
||||
return false;
|
||||
return !PaymentMethod.hasChargebackRisk(myPaymentAccount.getPaymentMethod(),
|
||||
myPaymentAccount.selectedTradeCurrency.getCode()) ||
|
||||
!AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, myPaymentAccount);
|
||||
}
|
||||
|
||||
public static boolean isAnyTakerPaymentAccountValidForOffer(Offer offer, Collection<PaymentAccount> takerPaymentAccounts) {
|
||||
public static boolean isAnyTakerPaymentAccountValidForOffer(Offer offer,
|
||||
Collection<PaymentAccount> takerPaymentAccounts) {
|
||||
for (PaymentAccount takerPaymentAccount : takerPaymentAccounts) {
|
||||
if (isTakerPaymentAccountValidForOffer(offer, takerPaymentAccount))
|
||||
return true;
|
||||
@ -105,11 +54,21 @@ public class PaymentAccountUtil {
|
||||
ObservableList<PaymentAccount> result = FXCollections.observableArrayList();
|
||||
result.addAll(paymentAccounts.stream()
|
||||
.filter(paymentAccount -> isTakerPaymentAccountValidForOffer(offer, paymentAccount))
|
||||
.filter(paymentAccount -> offer.isBuyOffer() || isTakerAccountForOfferMature(offer, paymentAccount, accountAgeWitnessService))
|
||||
.filter(paymentAccount -> isAmountValidForOffer(offer, paymentAccount, accountAgeWitnessService))
|
||||
.collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Return true if paymentAccount can take this offer
|
||||
public static boolean isAmountValidForOffer(Offer offer,
|
||||
PaymentAccount paymentAccount,
|
||||
AccountAgeWitnessService accountAgeWitnessService) {
|
||||
boolean hasChargebackRisk = PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode());
|
||||
boolean hasValidAccountAgeWitness = accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
||||
offer.getCurrencyCode(), offer.getMirroredDirection()) >= offer.getAmount().value;
|
||||
return !hasChargebackRisk || hasValidAccountAgeWitness;
|
||||
}
|
||||
|
||||
// TODO might be used to show more details if we get payment methods updates with diff. limits
|
||||
public static String getInfoForMismatchingPaymentMethodLimits(Offer offer, PaymentAccount paymentAccount) {
|
||||
// dont translate atm as it is not used so far in the UI just for logs
|
||||
|
@ -37,17 +37,17 @@ class PaymentAccounts {
|
||||
private static final Logger log = LoggerFactory.getLogger(PaymentAccounts.class);
|
||||
|
||||
private final Set<PaymentAccount> accounts;
|
||||
private final AccountAgeWitnessService service;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final BiFunction<Offer, PaymentAccount, Boolean> validator;
|
||||
|
||||
PaymentAccounts(Set<PaymentAccount> accounts, AccountAgeWitnessService service) {
|
||||
this(accounts, service, PaymentAccountUtil::isTakerPaymentAccountValidForOffer);
|
||||
PaymentAccounts(Set<PaymentAccount> accounts, AccountAgeWitnessService accountAgeWitnessService) {
|
||||
this(accounts, accountAgeWitnessService, PaymentAccountUtil::isTakerPaymentAccountValidForOffer);
|
||||
}
|
||||
|
||||
PaymentAccounts(Set<PaymentAccount> accounts, AccountAgeWitnessService service,
|
||||
PaymentAccounts(Set<PaymentAccount> accounts, AccountAgeWitnessService accountAgeWitnessService,
|
||||
BiFunction<Offer, PaymentAccount, Boolean> validator) {
|
||||
this.accounts = accounts;
|
||||
this.service = service;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ class PaymentAccounts {
|
||||
}
|
||||
|
||||
private List<PaymentAccount> sortValidAccounts(Offer offer) {
|
||||
Comparator<PaymentAccount> comparator = this::compareByAge;
|
||||
Comparator<PaymentAccount> comparator = this::compareByTradeLimit;
|
||||
return accounts.stream()
|
||||
.filter(account -> validator.apply(offer, account))
|
||||
.sorted(comparator.reversed())
|
||||
@ -78,7 +78,7 @@ class PaymentAccounts {
|
||||
StringBuilder message = new StringBuilder("Valid accounts: \n");
|
||||
for (PaymentAccount account : accounts) {
|
||||
String accountName = account.getAccountName();
|
||||
String witnessHex = service.getMyWitnessHashAsHex(account.getPaymentAccountPayload());
|
||||
String witnessHex = accountAgeWitnessService.getMyWitnessHashAsHex(account.getPaymentAccountPayload());
|
||||
|
||||
message.append("name = ")
|
||||
.append(accountName)
|
||||
@ -91,15 +91,24 @@ class PaymentAccounts {
|
||||
}
|
||||
}
|
||||
|
||||
private int compareByAge(PaymentAccount left, PaymentAccount right) {
|
||||
AccountAgeWitness leftWitness = service.getMyWitness(left.getPaymentAccountPayload());
|
||||
AccountAgeWitness rightWitness = service.getMyWitness(right.getPaymentAccountPayload());
|
||||
// Accounts ranked by trade limit
|
||||
private int compareByTradeLimit(PaymentAccount left, PaymentAccount right) {
|
||||
// Mature accounts count as infinite sign age
|
||||
if (accountAgeWitnessService.myHasTradeLimitException(left)) {
|
||||
return !accountAgeWitnessService.myHasTradeLimitException(right) ? 1 : 0;
|
||||
}
|
||||
if (accountAgeWitnessService.myHasTradeLimitException(right)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
AccountAgeWitness leftWitness = accountAgeWitnessService.getMyWitness(left.getPaymentAccountPayload());
|
||||
AccountAgeWitness rightWitness = accountAgeWitnessService.getMyWitness(right.getPaymentAccountPayload());
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
long leftAge = service.getAccountAge(leftWitness, now);
|
||||
long rightAge = service.getAccountAge(rightWitness, now);
|
||||
long leftSignAge = accountAgeWitnessService.getWitnessSignAge(leftWitness, now);
|
||||
long rightSignAge = accountAgeWitnessService.getWitnessSignAge(rightWitness, now);
|
||||
|
||||
return Long.compare(leftAge, rightAge);
|
||||
return Long.compare(leftSignAge, rightSignAge);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package bisq.core.payment.payload;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.payment.TradeLimits;
|
||||
|
||||
import bisq.common.proto.persistable.PersistablePayload;
|
||||
@ -322,6 +323,15 @@ public final class PaymentMethod implements PersistablePayload, Comparable {
|
||||
return this.equals(BLOCK_CHAINS_INSTANT) || this.equals(BLOCK_CHAINS);
|
||||
}
|
||||
|
||||
public static boolean hasChargebackRisk(PaymentMethod paymentMethod, List<TradeCurrency> tradeCurrencies) {
|
||||
return tradeCurrencies.stream()
|
||||
.anyMatch(tradeCurrency -> hasChargebackRisk(paymentMethod, tradeCurrency.getCode()));
|
||||
}
|
||||
|
||||
public static boolean hasChargebackRisk(PaymentMethod paymentMethod) {
|
||||
return hasChargebackRisk(paymentMethod, CurrencyUtil.getMatureMarketCurrencies());
|
||||
}
|
||||
|
||||
public static boolean hasChargebackRisk(PaymentMethod paymentMethod, String currencyCode) {
|
||||
if (paymentMethod == null)
|
||||
return false;
|
||||
|
@ -19,6 +19,7 @@ package bisq.core.presentation;
|
||||
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
||||
import bisq.core.support.dispute.mediation.MediationManager;
|
||||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ -27,51 +28,39 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class SupportTicketsPresentation {
|
||||
@Getter
|
||||
private final StringProperty numOpenArbitrationTickets = new SimpleStringProperty();
|
||||
@Getter
|
||||
private final BooleanProperty showOpenArbitrationTicketsNotification = new SimpleBooleanProperty();
|
||||
|
||||
@Getter
|
||||
private final StringProperty numOpenMediationTickets = new SimpleStringProperty();
|
||||
@Getter
|
||||
private final BooleanProperty showOpenMediationTicketsNotification = new SimpleBooleanProperty();
|
||||
|
||||
@Getter
|
||||
private final StringProperty numOpenSupportTickets = new SimpleStringProperty();
|
||||
@Getter
|
||||
private final BooleanProperty showOpenSupportTicketsNotification = new SimpleBooleanProperty();
|
||||
|
||||
@org.jetbrains.annotations.NotNull
|
||||
private final ArbitrationManager arbitrationManager;
|
||||
@org.jetbrains.annotations.NotNull
|
||||
private final MediationManager mediationManager;
|
||||
@org.jetbrains.annotations.NotNull
|
||||
private final RefundManager refundManager;
|
||||
|
||||
@Inject
|
||||
public SupportTicketsPresentation(ArbitrationManager arbitrationManager, MediationManager mediationManager) {
|
||||
public SupportTicketsPresentation(ArbitrationManager arbitrationManager,
|
||||
MediationManager mediationManager,
|
||||
RefundManager refundManager) {
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
this.mediationManager = mediationManager;
|
||||
this.refundManager = refundManager;
|
||||
|
||||
arbitrationManager.getNumOpenDisputes().addListener((observable, oldValue, newValue) -> onChange());
|
||||
mediationManager.getNumOpenDisputes().addListener((observable, oldValue, newValue) -> onChange());
|
||||
refundManager.getNumOpenDisputes().addListener((observable, oldValue, newValue) -> onChange());
|
||||
}
|
||||
|
||||
private void onChange() {
|
||||
AtomicInteger openArbitrationDisputes = new AtomicInteger(arbitrationManager.getNumOpenDisputes().get());
|
||||
int arbitrationTickets = openArbitrationDisputes.get();
|
||||
numOpenArbitrationTickets.set(String.valueOf(arbitrationTickets));
|
||||
showOpenArbitrationTicketsNotification.set(arbitrationTickets > 0);
|
||||
int supportTickets = arbitrationManager.getNumOpenDisputes().get() +
|
||||
mediationManager.getNumOpenDisputes().get() +
|
||||
refundManager.getNumOpenDisputes().get();
|
||||
|
||||
AtomicInteger openMediationDisputes = new AtomicInteger(mediationManager.getNumOpenDisputes().get());
|
||||
int mediationTickets = openMediationDisputes.get();
|
||||
numOpenMediationTickets.set(String.valueOf(mediationTickets));
|
||||
showOpenMediationTicketsNotification.set(mediationTickets > 0);
|
||||
|
||||
int supportTickets = arbitrationTickets + mediationTickets;
|
||||
numOpenSupportTickets.set(String.valueOf(supportTickets));
|
||||
showOpenSupportTicketsNotification.set(supportTickets > 0);
|
||||
}
|
||||
|
@ -44,14 +44,19 @@ import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||
import bisq.core.support.dispute.messages.DisputeResultMessage;
|
||||
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
|
||||
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgent;
|
||||
import bisq.core.support.messages.ChatMessage;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.DepositTxPublishedMessage;
|
||||
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.InputsForDepositTxRequest;
|
||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
||||
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
||||
import bisq.core.trade.messages.PayDepositRequest;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PublishDepositTxRequest;
|
||||
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.statistics.TradeStatistics;
|
||||
|
||||
import bisq.network.p2p.AckMessage;
|
||||
@ -89,7 +94,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class CoreNetworkProtoResolver extends CoreProtoResolver implements NetworkProtoResolver {
|
||||
|
||||
@Inject
|
||||
public CoreNetworkProtoResolver() {
|
||||
}
|
||||
@ -134,16 +138,28 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
||||
case PREFIXED_SEALED_AND_SIGNED_MESSAGE:
|
||||
return PrefixedSealedAndSignedMessage.fromProto(proto.getPrefixedSealedAndSignedMessage(), messageVersion);
|
||||
|
||||
case PAY_DEPOSIT_REQUEST:
|
||||
return PayDepositRequest.fromProto(proto.getPayDepositRequest(), this, messageVersion);
|
||||
case DEPOSIT_TX_PUBLISHED_MESSAGE:
|
||||
return DepositTxPublishedMessage.fromProto(proto.getDepositTxPublishedMessage(), messageVersion);
|
||||
case PUBLISH_DEPOSIT_TX_REQUEST:
|
||||
return PublishDepositTxRequest.fromProto(proto.getPublishDepositTxRequest(), this, messageVersion);
|
||||
// trade protocol messages
|
||||
case INPUTS_FOR_DEPOSIT_TX_REQUEST:
|
||||
return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion);
|
||||
case INPUTS_FOR_DEPOSIT_TX_RESPONSE:
|
||||
return InputsForDepositTxResponse.fromProto(proto.getInputsForDepositTxResponse(), this, messageVersion);
|
||||
case DEPOSIT_TX_MESSAGE:
|
||||
return DepositTxMessage.fromProto(proto.getDepositTxMessage(), messageVersion);
|
||||
case DELAYED_PAYOUT_TX_SIGNATURE_REQUEST:
|
||||
return DelayedPayoutTxSignatureRequest.fromProto(proto.getDelayedPayoutTxSignatureRequest(), messageVersion);
|
||||
case DELAYED_PAYOUT_TX_SIGNATURE_RESPONSE:
|
||||
return DelayedPayoutTxSignatureResponse.fromProto(proto.getDelayedPayoutTxSignatureResponse(), messageVersion);
|
||||
case DEPOSIT_TX_AND_DELAYED_PAYOUT_TX_MESSAGE:
|
||||
return DepositTxAndDelayedPayoutTxMessage.fromProto(proto.getDepositTxAndDelayedPayoutTxMessage(), messageVersion);
|
||||
|
||||
case COUNTER_CURRENCY_TRANSFER_STARTED_MESSAGE:
|
||||
return CounterCurrencyTransferStartedMessage.fromProto(proto.getCounterCurrencyTransferStartedMessage(), messageVersion);
|
||||
|
||||
case PAYOUT_TX_PUBLISHED_MESSAGE:
|
||||
return PayoutTxPublishedMessage.fromProto(proto.getPayoutTxPublishedMessage(), messageVersion);
|
||||
case PEER_PUBLISHED_DELAYED_PAYOUT_TX_MESSAGE:
|
||||
return PeerPublishedDelayedPayoutTxMessage.fromProto(proto.getPeerPublishedDelayedPayoutTxMessage(), messageVersion);
|
||||
|
||||
case MEDIATED_PAYOUT_TX_SIGNATURE_MESSAGE:
|
||||
return MediatedPayoutTxSignatureMessage.fromProto(proto.getMediatedPayoutTxSignatureMessage(), messageVersion);
|
||||
case MEDIATED_PAYOUT_TX_PUBLISHED_MESSAGE:
|
||||
@ -236,6 +252,8 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
||||
return Arbitrator.fromProto(proto.getArbitrator());
|
||||
case MEDIATOR:
|
||||
return Mediator.fromProto(proto.getMediator());
|
||||
case REFUND_AGENT:
|
||||
return RefundAgent.fromProto(proto.getRefundAgent());
|
||||
case FILTER:
|
||||
return Filter.fromProto(proto.getFilter());
|
||||
case TRADE_STATISTICS:
|
||||
|
@ -37,6 +37,7 @@ import bisq.core.payment.PaymentAccountList;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationDisputeList;
|
||||
import bisq.core.support.dispute.mediation.MediationDisputeList;
|
||||
import bisq.core.support.dispute.refund.RefundDisputeList;
|
||||
import bisq.core.trade.TradableList;
|
||||
import bisq.core.trade.statistics.TradeStatistics2Store;
|
||||
import bisq.core.user.PreferencesPayload;
|
||||
@ -110,6 +111,10 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
|
||||
return MediationDisputeList.fromProto(proto.getMediationDisputeList(),
|
||||
this,
|
||||
new Storage<>(storageDir, this, corruptedDatabaseFilesHandler));
|
||||
case REFUND_DISPUTE_LIST:
|
||||
return RefundDisputeList.fromProto(proto.getRefundDisputeList(),
|
||||
this,
|
||||
new Storage<>(storageDir, this, corruptedDatabaseFilesHandler));
|
||||
case PREFERENCES_PAYLOAD:
|
||||
return PreferencesPayload.fromProto(proto.getPreferencesPayload(), this);
|
||||
case USER_PAYLOAD:
|
||||
|
@ -37,7 +37,9 @@ public class CoreNetworkCapabilities {
|
||||
Capability.BLIND_VOTE,
|
||||
Capability.DAO_STATE,
|
||||
Capability.BUNDLE_OF_ENVELOPES,
|
||||
Capability.MEDIATION
|
||||
Capability.MEDIATION,
|
||||
Capability.SIGNED_ACCOUNT_AGE_WITNESS,
|
||||
Capability.REFUND_AGENT
|
||||
);
|
||||
|
||||
if (BisqEnvironment.isDaoActivated(bisqEnvironment)) {
|
||||
|
@ -29,6 +29,7 @@ import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationDisputeListService;
|
||||
import bisq.core.support.dispute.mediation.MediationDisputeListService;
|
||||
import bisq.core.support.dispute.refund.RefundDisputeListService;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.failed.FailedTradesManager;
|
||||
@ -63,6 +64,7 @@ public class CorePersistedDataHost {
|
||||
persistedDataHosts.add(injector.getInstance(FailedTradesManager.class));
|
||||
persistedDataHosts.add(injector.getInstance(ArbitrationDisputeListService.class));
|
||||
persistedDataHosts.add(injector.getInstance(MediationDisputeListService.class));
|
||||
persistedDataHosts.add(injector.getInstance(RefundDisputeListService.class));
|
||||
persistedDataHosts.add(injector.getInstance(P2PService.class));
|
||||
|
||||
if (injector.getInstance(Key.get(Boolean.class, Names.named(DaoOptionKeys.DAO_ACTIVATED)))) {
|
||||
|
@ -22,7 +22,8 @@ import bisq.common.proto.ProtoUtil;
|
||||
public enum SupportType {
|
||||
ARBITRATION, // Need to be at index 0 to be the fall back for old clients
|
||||
MEDIATION,
|
||||
TRADE;
|
||||
TRADE,
|
||||
REFUND;
|
||||
|
||||
public static SupportType fromProto(
|
||||
protobuf.SupportType type) {
|
||||
|
@ -48,6 +48,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@ -80,7 +81,7 @@ public final class Dispute implements NetworkPayload {
|
||||
private final String makerContractSignature;
|
||||
@Nullable
|
||||
private final String takerContractSignature;
|
||||
private final PubKeyRing agentPubKeyRing; // arbitrator or mediator
|
||||
private final PubKeyRing agentPubKeyRing; // dispute agent
|
||||
private final boolean isSupportTicket;
|
||||
private final ObservableList<ChatMessage> chatMessages = FXCollections.observableArrayList();
|
||||
private BooleanProperty isClosedProperty = new SimpleBooleanProperty();
|
||||
@ -92,6 +93,16 @@ public final class Dispute implements NetworkPayload {
|
||||
|
||||
transient private Storage<? extends DisputeList> storage;
|
||||
|
||||
// Added v1.2.0
|
||||
private SupportType supportType;
|
||||
// Only used at refundAgent so that he knows how the mediator resolved the case
|
||||
@Setter
|
||||
@Nullable
|
||||
private String mediatorsDisputeResult;
|
||||
@Setter
|
||||
@Nullable
|
||||
private String delayedPayoutTxId;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -114,7 +125,8 @@ public final class Dispute implements NetworkPayload {
|
||||
@Nullable String makerContractSignature,
|
||||
@Nullable String takerContractSignature,
|
||||
PubKeyRing agentPubKeyRing,
|
||||
boolean isSupportTicket) {
|
||||
boolean isSupportTicket,
|
||||
SupportType supportType) {
|
||||
this(tradeId,
|
||||
traderId,
|
||||
disputeOpenerIsBuyer,
|
||||
@ -131,7 +143,8 @@ public final class Dispute implements NetworkPayload {
|
||||
makerContractSignature,
|
||||
takerContractSignature,
|
||||
agentPubKeyRing,
|
||||
isSupportTicket);
|
||||
isSupportTicket,
|
||||
supportType);
|
||||
this.storage = storage;
|
||||
openingDate = new Date().getTime();
|
||||
}
|
||||
@ -157,7 +170,8 @@ public final class Dispute implements NetworkPayload {
|
||||
@Nullable String makerContractSignature,
|
||||
@Nullable String takerContractSignature,
|
||||
PubKeyRing agentPubKeyRing,
|
||||
boolean isSupportTicket) {
|
||||
boolean isSupportTicket,
|
||||
SupportType supportType) {
|
||||
this.tradeId = tradeId;
|
||||
this.traderId = traderId;
|
||||
this.disputeOpenerIsBuyer = disputeOpenerIsBuyer;
|
||||
@ -175,6 +189,7 @@ public final class Dispute implements NetworkPayload {
|
||||
this.takerContractSignature = takerContractSignature;
|
||||
this.agentPubKeyRing = agentPubKeyRing;
|
||||
this.isSupportTicket = isSupportTicket;
|
||||
this.supportType = supportType;
|
||||
|
||||
id = tradeId + "_" + traderId;
|
||||
}
|
||||
@ -210,11 +225,14 @@ public final class Dispute implements NetworkPayload {
|
||||
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
|
||||
Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature);
|
||||
Optional.ofNullable(disputeResultProperty.get()).ifPresent(result -> builder.setDisputeResult(disputeResultProperty.get().toProtoMessage()));
|
||||
Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType)));
|
||||
Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult));
|
||||
Optional.ofNullable(delayedPayoutTxId).ifPresent(result -> builder.setDelayedPayoutTxId(delayedPayoutTxId));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver coreProtoResolver) {
|
||||
final Dispute dispute = new Dispute(proto.getTradeId(),
|
||||
Dispute dispute = new Dispute(proto.getTradeId(),
|
||||
proto.getTraderId(),
|
||||
proto.getDisputeOpenerIsBuyer(),
|
||||
proto.getDisputeOpenerIsMaker(),
|
||||
@ -230,7 +248,8 @@ public final class Dispute implements NetworkPayload {
|
||||
ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()),
|
||||
PubKeyRing.fromProto(proto.getAgentPubKeyRing()),
|
||||
proto.getIsSupportTicket());
|
||||
proto.getIsSupportTicket(),
|
||||
SupportType.fromProto(proto.getSupportType()));
|
||||
|
||||
dispute.chatMessages.addAll(proto.getChatMessageList().stream()
|
||||
.map(ChatMessage::fromPayloadProto)
|
||||
@ -241,6 +260,17 @@ public final class Dispute implements NetworkPayload {
|
||||
if (proto.hasDisputeResult())
|
||||
dispute.disputeResultProperty.set(DisputeResult.fromProto(proto.getDisputeResult()));
|
||||
dispute.disputePayoutTxId = ProtoUtil.stringOrNullFromProto(proto.getDisputePayoutTxId());
|
||||
|
||||
String mediatorsDisputeResult = proto.getMediatorsDisputeResult();
|
||||
if (!mediatorsDisputeResult.isEmpty()) {
|
||||
dispute.setMediatorsDisputeResult(mediatorsDisputeResult);
|
||||
}
|
||||
|
||||
String delayedPayoutTxId = proto.getDelayedPayoutTxId();
|
||||
if (!delayedPayoutTxId.isEmpty()) {
|
||||
dispute.setDelayedPayoutTxId(delayedPayoutTxId);
|
||||
}
|
||||
|
||||
return dispute;
|
||||
}
|
||||
|
||||
@ -258,10 +288,6 @@ public final class Dispute implements NetworkPayload {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMediationDispute() {
|
||||
return !chatMessages.isEmpty() && chatMessages.get(0).getSupportType() == SupportType.MEDIATION;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Setters
|
||||
@ -293,6 +319,9 @@ public final class Dispute implements NetworkPayload {
|
||||
storage.queueUpForSave();
|
||||
}
|
||||
|
||||
public void setSupportType(SupportType supportType) {
|
||||
this.supportType = supportType;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
@ -322,34 +351,37 @@ public final class Dispute implements NetworkPayload {
|
||||
return isClosedProperty.get();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Dispute{" +
|
||||
"tradeId='" + tradeId + '\'' +
|
||||
", id='" + id + '\'' +
|
||||
", traderId=" + traderId +
|
||||
", disputeOpenerIsBuyer=" + disputeOpenerIsBuyer +
|
||||
", disputeOpenerIsMaker=" + disputeOpenerIsMaker +
|
||||
", openingDate=" + openingDate +
|
||||
", traderPubKeyRing=" + traderPubKeyRing +
|
||||
", tradeDate=" + tradeDate +
|
||||
", contract=" + contract +
|
||||
", contractHash=" + Utilities.bytesAsHexString(contractHash) +
|
||||
", depositTxSerialized=" + Utilities.bytesAsHexString(depositTxSerialized) +
|
||||
", payoutTxSerialized not displayed for privacy reasons..." +
|
||||
", depositTxId='" + depositTxId + '\'' +
|
||||
", payoutTxId='" + payoutTxId + '\'' +
|
||||
", contractAsJson='" + contractAsJson + '\'' +
|
||||
", makerContractSignature='" + makerContractSignature + '\'' +
|
||||
", takerContractSignature='" + takerContractSignature + '\'' +
|
||||
", agentPubKeyRing=" + agentPubKeyRing +
|
||||
", isSupportTicket=" + isSupportTicket +
|
||||
", chatMessages=" + chatMessages +
|
||||
", isClosed=" + isClosedProperty.get() +
|
||||
", disputeResult=" + disputeResultProperty.get() +
|
||||
", disputePayoutTxId='" + disputePayoutTxId + '\'' +
|
||||
", isClosedProperty=" + isClosedProperty +
|
||||
", disputeResultProperty=" + disputeResultProperty +
|
||||
'}';
|
||||
"\n tradeId='" + tradeId + '\'' +
|
||||
",\n id='" + id + '\'' +
|
||||
",\n traderId=" + traderId +
|
||||
",\n disputeOpenerIsBuyer=" + disputeOpenerIsBuyer +
|
||||
",\n disputeOpenerIsMaker=" + disputeOpenerIsMaker +
|
||||
",\n traderPubKeyRing=" + traderPubKeyRing +
|
||||
",\n tradeDate=" + tradeDate +
|
||||
",\n contract=" + contract +
|
||||
",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
|
||||
",\n depositTxSerialized=" + Utilities.bytesAsHexString(depositTxSerialized) +
|
||||
",\n payoutTxSerialized=" + Utilities.bytesAsHexString(payoutTxSerialized) +
|
||||
",\n depositTxId='" + depositTxId + '\'' +
|
||||
",\n payoutTxId='" + payoutTxId + '\'' +
|
||||
",\n contractAsJson='" + contractAsJson + '\'' +
|
||||
",\n makerContractSignature='" + makerContractSignature + '\'' +
|
||||
",\n takerContractSignature='" + takerContractSignature + '\'' +
|
||||
",\n agentPubKeyRing=" + agentPubKeyRing +
|
||||
",\n isSupportTicket=" + isSupportTicket +
|
||||
",\n chatMessages=" + chatMessages +
|
||||
",\n isClosedProperty=" + isClosedProperty +
|
||||
",\n disputeResultProperty=" + disputeResultProperty +
|
||||
",\n disputePayoutTxId='" + disputePayoutTxId + '\'' +
|
||||
",\n openingDate=" + openingDate +
|
||||
",\n storage=" + storage +
|
||||
",\n supportType=" + supportType +
|
||||
",\n mediatorsDisputeResult='" + mediatorsDisputeResult + '\'' +
|
||||
",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
@ -55,12 +55,14 @@ import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public abstract class DisputeManager<T extends DisputeList<? extends DisputeList>> extends SupportManager {
|
||||
protected final TradeWalletService tradeWalletService;
|
||||
protected final BtcWalletService walletService;
|
||||
protected final BtcWalletService btcWalletService;
|
||||
protected final TradeManager tradeManager;
|
||||
protected final ClosedTradableManager closedTradableManager;
|
||||
protected final OpenOfferManager openOfferManager;
|
||||
@ -74,7 +76,7 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
|
||||
public DisputeManager(P2PService p2PService,
|
||||
TradeWalletService tradeWalletService,
|
||||
BtcWalletService walletService,
|
||||
BtcWalletService btcWalletService,
|
||||
WalletsSetup walletsSetup,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
@ -84,7 +86,7 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
super(p2PService, walletsSetup);
|
||||
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.walletService = walletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.tradeManager = tradeManager;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.openOfferManager = openOfferManager;
|
||||
@ -157,12 +159,19 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
// 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 getDisputeState_StartedByPeer();
|
||||
|
||||
public abstract void cleanupDisputes();
|
||||
|
||||
protected abstract String getDisputeInfo(Dispute dispute);
|
||||
|
||||
protected abstract String getDisputeIntroForPeer(String disputeInfo);
|
||||
|
||||
protected abstract String getDisputeIntroForDisputeCreator(String disputeInfo);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Delegates for disputeListService
|
||||
@ -236,7 +245,7 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
// Message handler
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// arbitrator receives that from trader who opens dispute
|
||||
// dispute agent receives that from trader who opens dispute
|
||||
protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessage) {
|
||||
T disputeList = getDisputeList();
|
||||
if (disputeList == null) {
|
||||
@ -246,13 +255,17 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
|
||||
String errorMessage = null;
|
||||
Dispute dispute = openNewDisputeMessage.getDispute();
|
||||
|
||||
// Disputes from clients < 1.2.0 always have support type ARBITRATION in dispute as the field didn't exist before
|
||||
dispute.setSupportType(openNewDisputeMessage.getSupportType());
|
||||
|
||||
dispute.setStorage(disputeListService.getStorage());
|
||||
Contract contractFromOpener = dispute.getContract();
|
||||
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getSellerPubKeyRing() : contractFromOpener.getBuyerPubKeyRing();
|
||||
if (isAgent(dispute)) {
|
||||
if (!disputeList.contains(dispute)) {
|
||||
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
||||
if (!storedDisputeOptional.isPresent()) {
|
||||
dispute.setStorage(disputeListService.getStorage());
|
||||
disputeList.add(dispute);
|
||||
errorMessage = sendPeerOpenedDisputeMessage(dispute, contractFromOpener, peersPubKeyRing);
|
||||
} else {
|
||||
@ -270,15 +283,29 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
}
|
||||
|
||||
// We use the ChatMessage not the openNewDisputeMessage for the ACK
|
||||
ObservableList<ChatMessage> messages = openNewDisputeMessage.getDispute().getChatMessages();
|
||||
ObservableList<ChatMessage> messages = dispute.getChatMessages();
|
||||
if (!messages.isEmpty()) {
|
||||
ChatMessage msg = messages.get(0);
|
||||
ChatMessage chatMessage = messages.get(0);
|
||||
PubKeyRing sendersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getBuyerPubKeyRing() : contractFromOpener.getSellerPubKeyRing();
|
||||
sendAckMessage(msg, sendersPubKeyRing, errorMessage == null, errorMessage);
|
||||
sendAckMessage(chatMessage, sendersPubKeyRing, errorMessage == null, errorMessage);
|
||||
}
|
||||
|
||||
// In case of refundAgent we add a message with the mediatorsDisputeSummary. Only visible for refundAgent.
|
||||
if (dispute.getMediatorsDisputeResult() != null) {
|
||||
String mediatorsDisputeResult = Res.get("support.mediatorsDisputeSummary", dispute.getMediatorsDisputeResult());
|
||||
ChatMessage mediatorsDisputeResultMessage = new ChatMessage(
|
||||
getSupportType(),
|
||||
dispute.getTradeId(),
|
||||
pubKeyRing.hashCode(),
|
||||
false,
|
||||
mediatorsDisputeResult,
|
||||
p2PService.getAddress());
|
||||
mediatorsDisputeResultMessage.setSystemMessage(true);
|
||||
dispute.addAndPersistChatMessage(mediatorsDisputeResultMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// not dispute requester receives that from arbitrator
|
||||
// not dispute requester receives that from dispute agent
|
||||
protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDisputeMessage) {
|
||||
T disputeList = getDisputeList();
|
||||
if (disputeList == null) {
|
||||
@ -345,17 +372,19 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
|
||||
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
||||
if (!storedDisputeOptional.isPresent() || reOpen) {
|
||||
String disputeInfo = getDisputeInfo(dispute.isMediationDispute());
|
||||
String disputeInfo = getDisputeInfo(dispute);
|
||||
String disputeMessage = getDisputeIntroForDisputeCreator(disputeInfo);
|
||||
String sysMsg = dispute.isSupportTicket() ?
|
||||
Res.get("support.youOpenedTicket", disputeInfo, Version.VERSION)
|
||||
: Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
|
||||
: disputeMessage;
|
||||
|
||||
String message = Res.get("support.systemMsg", sysMsg);
|
||||
ChatMessage chatMessage = new ChatMessage(
|
||||
getSupportType(),
|
||||
dispute.getTradeId(),
|
||||
pubKeyRing.hashCode(),
|
||||
false,
|
||||
Res.get("support.systemMsg", sysMsg),
|
||||
message,
|
||||
p2PService.getAddress());
|
||||
chatMessage.setSystemMessage(true);
|
||||
dispute.addAndPersistChatMessage(chatMessage);
|
||||
@ -364,15 +393,22 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
}
|
||||
|
||||
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(),
|
||||
|
||||
log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, chatMessage.uid={}",
|
||||
openNewDisputeMessage.getClass().getSimpleName(),
|
||||
agentNodeAddress,
|
||||
openNewDisputeMessage.getTradeId(),
|
||||
openNewDisputeMessage.getUid(),
|
||||
chatMessage.getUid());
|
||||
|
||||
p2PService.sendEncryptedMailboxMessage(agentNodeAddress,
|
||||
dispute.getAgentPubKeyRing(),
|
||||
openNewDisputeMessage,
|
||||
@ -432,7 +468,7 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
}
|
||||
}
|
||||
|
||||
// arbitrator sends that to trading peer when he received openDispute request
|
||||
// dispute agent sends that to trading peer when he received openDispute request
|
||||
private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
|
||||
Contract contractFromOpener,
|
||||
PubKeyRing pubKeyRing) {
|
||||
@ -459,13 +495,17 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
disputeFromOpener.getMakerContractSignature(),
|
||||
disputeFromOpener.getTakerContractSignature(),
|
||||
disputeFromOpener.getAgentPubKeyRing(),
|
||||
disputeFromOpener.isSupportTicket());
|
||||
disputeFromOpener.isSupportTicket(),
|
||||
disputeFromOpener.getSupportType());
|
||||
dispute.setDelayedPayoutTxId(disputeFromOpener.getDelayedPayoutTxId());
|
||||
|
||||
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
||||
if (!storedDisputeOptional.isPresent()) {
|
||||
String disputeInfo = getDisputeInfo(dispute.isMediationDispute());
|
||||
String disputeInfo = getDisputeInfo(dispute);
|
||||
String disputeMessage = getDisputeIntroForPeer(disputeInfo);
|
||||
String sysMsg = dispute.isSupportTicket() ?
|
||||
Res.get("support.peerOpenedTicket", disputeInfo)
|
||||
: Res.get("support.peerOpenedDispute", disputeInfo);
|
||||
Res.get("support.peerOpenedTicket", disputeInfo, Version.VERSION)
|
||||
: disputeMessage;
|
||||
ChatMessage chatMessage = new ChatMessage(
|
||||
getSupportType(),
|
||||
dispute.getTradeId(),
|
||||
@ -485,11 +525,12 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
p2PService.getAddress(),
|
||||
UUID.randomUUID().toString(),
|
||||
getSupportType());
|
||||
log.info("Send {} to peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " +
|
||||
"chatMessage.uid={}",
|
||||
|
||||
log.info("Send {} to peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, chatMessage.uid={}",
|
||||
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
|
||||
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
|
||||
chatMessage.getUid());
|
||||
|
||||
p2PService.sendEncryptedMailboxMessage(peersNodeAddress,
|
||||
peersPubKeyRing,
|
||||
peerOpenedDisputeMessage,
|
||||
@ -546,7 +587,7 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
}
|
||||
}
|
||||
|
||||
// arbitrator send result to trader
|
||||
// dispute agent send result to trader
|
||||
public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String text) {
|
||||
T disputeList = getDisputeList();
|
||||
if (disputeList == null) {
|
||||
@ -690,12 +731,4 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
||||
.filter(e -> e.getTradeId().equals(tradeId))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
private String getDisputeInfo(boolean isMediationDispute) {
|
||||
String role = isMediationDispute ? Res.get("shared.mediator").toLowerCase() :
|
||||
Res.get("shared.arbitrator2").toLowerCase();
|
||||
String link = isMediationDispute ? "https://docs.bisq.network/trading-rules.html#mediation" :
|
||||
"https://bisq.network/docs/exchange/arbitration-system";
|
||||
return Res.get("support.initialInfo", role, role, link);
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.core.support.dispute.arbitration;
|
||||
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.support.SupportType;
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeList;
|
||||
|
||||
@ -33,6 +34,8 @@ import java.util.stream.Collectors;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@Slf4j
|
||||
@ToString
|
||||
/*
|
||||
@ -67,6 +70,9 @@ public final class ArbitrationDisputeList extends DisputeList<ArbitrationDispute
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
|
||||
list.forEach(dispute -> checkArgument(dispute.getSupportType().equals(SupportType.ARBITRATION), "Support type has to be ARBITRATION"));
|
||||
|
||||
return protobuf.PersistableEnvelope.newBuilder().setArbitrationDisputeList(protobuf.ArbitrationDisputeList.newBuilder()
|
||||
.addAllDispute(ProtoUtil.collectionToProto(new ArrayList<>(list)))).build();
|
||||
}
|
||||
@ -77,7 +83,11 @@ public final class ArbitrationDisputeList extends DisputeList<ArbitrationDispute
|
||||
List<Dispute> list = proto.getDisputeList().stream()
|
||||
.map(disputeProto -> Dispute.fromProto(disputeProto, coreProtoResolver))
|
||||
.collect(Collectors.toList());
|
||||
list.forEach(e -> e.setStorage(storage));
|
||||
|
||||
list.forEach(e -> {
|
||||
checkArgument(e.getSupportType().equals(SupportType.ARBITRATION), "Support type has to be ARBITRATION");
|
||||
e.setStorage(storage);
|
||||
});
|
||||
return new ArbitrationDisputeList(storage, list);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ 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.locale.Res;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.support.SupportType;
|
||||
@ -49,6 +51,7 @@ import bisq.network.p2p.SendMailboxMessageListener;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
@ -64,6 +67,8 @@ import java.util.UUID;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
@ -120,9 +125,10 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public NodeAddress getAgentNodeAddress(Dispute dispute) {
|
||||
return dispute.getContract().getArbitratorNodeAddress();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -140,6 +146,22 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
disputeListService.cleanupDisputes(tradeId -> tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.DISPUTE_CLOSED));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisputeInfo(Dispute dispute) {
|
||||
String role = Res.get("shared.arbitrator").toLowerCase();
|
||||
String link = "https://docs.bisq.network/trading-rules.html#legacy-arbitration"; //TODO needs to be created
|
||||
return Res.get("support.initialInfo", role, role, link);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisputeIntroForPeer(String disputeInfo) {
|
||||
return Res.get("support.peerOpenedDispute", disputeInfo, Version.VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisputeIntroForDisputeCreator(String disputeInfo) {
|
||||
return Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Message handler
|
||||
@ -152,7 +174,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
ChatMessage chatMessage = disputeResult.getChatMessage();
|
||||
checkNotNull(chatMessage, "chatMessage must not be null");
|
||||
if (Arrays.equals(disputeResult.getArbitratorPubKey(),
|
||||
walletService.getArbitratorAddressEntry().getPubKey())) {
|
||||
btcWalletService.getArbitratorAddressEntry().getPubKey())) {
|
||||
log.error("Arbitrator received disputeResultMessage. That must never happen.");
|
||||
return;
|
||||
}
|
||||
@ -230,7 +252,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
if (payoutTx == null) {
|
||||
if (dispute.getDepositTxSerialized() != null) {
|
||||
byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
|
||||
DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(tradeId, multiSigPubKey);
|
||||
DeterministicKey multiSigKeyPair = btcWalletService.getMultiSigKeyPair(tradeId, multiSigPubKey);
|
||||
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
|
||||
dispute.getDepositTxSerialized(),
|
||||
disputeResult.getArbitratorSignature(),
|
||||
@ -243,7 +265,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
contract.getSellerMultiSigPubKey(),
|
||||
disputeResult.getArbitratorPubKey()
|
||||
);
|
||||
Transaction committedDisputedPayoutTx = tradeWalletService.addTxToWallet(signedDisputedPayoutTx);
|
||||
Transaction committedDisputedPayoutTx = WalletService.maybeAddSelfTxToWallet(signedDisputedPayoutTx, btcWalletService.getWallet());
|
||||
tradeWalletService.broadcastTx(committedDisputedPayoutTx, new TxBroadcaster.Callback() {
|
||||
@Override
|
||||
public void onSuccess(Transaction transaction) {
|
||||
@ -328,9 +350,11 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
PubKeyRing peersPubKeyRing = isBuyer ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
|
||||
|
||||
cleanupRetryMap(uid);
|
||||
Transaction walletTx = tradeWalletService.addTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction());
|
||||
dispute.setDisputePayoutTxId(walletTx.getHashAsString());
|
||||
BtcWalletService.printTx("Disputed payoutTx received from peer", walletTx);
|
||||
|
||||
Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
|
||||
|
||||
dispute.setDisputePayoutTxId(committedDisputePayoutTx.getHashAsString());
|
||||
BtcWalletService.printTx("Disputed payoutTx received from peer", committedDisputePayoutTx);
|
||||
|
||||
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
|
||||
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
|
||||
|
@ -30,20 +30,20 @@ import lombok.Getter;
|
||||
// TODO consider to move to signed witness domain
|
||||
@Getter
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
public class BuyerDataItem {
|
||||
public class TraderDataItem {
|
||||
private final PaymentAccountPayload paymentAccountPayload;
|
||||
@EqualsAndHashCode.Include
|
||||
private final AccountAgeWitness accountAgeWitness;
|
||||
private final Coin tradeAmount;
|
||||
private final PublicKey sellerPubKey;
|
||||
private final PublicKey peersPubKey;
|
||||
|
||||
public BuyerDataItem(PaymentAccountPayload paymentAccountPayload,
|
||||
public TraderDataItem(PaymentAccountPayload paymentAccountPayload,
|
||||
AccountAgeWitness accountAgeWitness,
|
||||
Coin tradeAmount,
|
||||
PublicKey sellerPubKey) {
|
||||
PublicKey peersPubKey) {
|
||||
this.paymentAccountPayload = paymentAccountPayload;
|
||||
this.accountAgeWitness = accountAgeWitness;
|
||||
this.tradeAmount = tradeAmount;
|
||||
this.sellerPubKey = sellerPubKey;
|
||||
this.peersPubKey = peersPubKey;
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ 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.locale.Res;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.support.SupportType;
|
||||
@ -43,22 +44,22 @@ import bisq.network.p2p.P2PService;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@ -66,14 +67,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
@Singleton
|
||||
public final class MediationManager extends DisputeManager<MediationDisputeList> {
|
||||
|
||||
// The date when mediation is activated
|
||||
private static final Date MEDIATION_ACTIVATED_DATE = Utilities.getUTCDate(2019, GregorianCalendar.SEPTEMBER, 26);
|
||||
|
||||
public static boolean isMediationActivated() {
|
||||
return new Date().after(MEDIATION_ACTIVATED_DATE);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -141,6 +134,22 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisputeInfo(Dispute dispute) {
|
||||
String role = Res.get("shared.mediator").toLowerCase();
|
||||
String link = "https://docs.bisq.network/trading-rules.html#mediation";
|
||||
return Res.get("support.initialInfo", role, role, link);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisputeIntroForPeer(String disputeInfo) {
|
||||
return Res.get("support.peerOpenedDisputeForMediation", disputeInfo, Version.VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisputeIntroForDisputeCreator(String disputeInfo) {
|
||||
return Res.get("support.youOpenedDisputeForMediation", disputeInfo, Version.VERSION);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Message handler
|
||||
@ -205,6 +214,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public NodeAddress getAgentNodeAddress(Dispute dispute) {
|
||||
return dispute.getContract().getMediatorNodeAddress();
|
||||
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.refund;
|
||||
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.support.SupportType;
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeList;
|
||||
|
||||
import bisq.common.proto.ProtoUtil;
|
||||
import bisq.common.storage.Storage;
|
||||
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@Slf4j
|
||||
@ToString
|
||||
/*
|
||||
* Holds a List of refund dispute objects.
|
||||
*
|
||||
* Calls to the List are delegated because this class intercepts the add/remove calls so changes
|
||||
* can be saved to disc.
|
||||
*/
|
||||
public final class RefundDisputeList extends DisputeList<RefundDisputeList> {
|
||||
|
||||
RefundDisputeList(Storage<RefundDisputeList> storage) {
|
||||
super(storage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPersisted() {
|
||||
// We need to use DisputeList as file name to not lose existing disputes which are stored in the DisputeList file
|
||||
RefundDisputeList persisted = storage.initAndGetPersisted(this, "RefundDisputeList", 50);
|
||||
if (persisted != null) {
|
||||
list.addAll(persisted.getList());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private RefundDisputeList(Storage<RefundDisputeList> storage, List<Dispute> list) {
|
||||
super(storage, list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
|
||||
list.forEach(dispute -> checkArgument(dispute.getSupportType().equals(SupportType.REFUND), "Support type has to be REFUND"));
|
||||
|
||||
return protobuf.PersistableEnvelope.newBuilder().setRefundDisputeList(protobuf.RefundDisputeList.newBuilder()
|
||||
.addAllDispute(ProtoUtil.collectionToProto(new ArrayList<>(list)))).build();
|
||||
}
|
||||
|
||||
public static RefundDisputeList fromProto(protobuf.RefundDisputeList proto,
|
||||
CoreProtoResolver coreProtoResolver,
|
||||
Storage<RefundDisputeList> storage) {
|
||||
List<Dispute> list = proto.getDisputeList().stream()
|
||||
.map(disputeProto -> Dispute.fromProto(disputeProto, coreProtoResolver))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
list.forEach(e -> {
|
||||
checkArgument(e.getSupportType().equals(SupportType.REFUND), "Support type has to be REFUND");
|
||||
e.setStorage(storage);
|
||||
});
|
||||
return new RefundDisputeList(storage, list);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.refund;
|
||||
|
||||
import bisq.core.support.dispute.DisputeListService;
|
||||
|
||||
import bisq.common.storage.Storage;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public final class RefundDisputeListService extends DisputeListService<RefundDisputeList> {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public RefundDisputeListService(Storage<RefundDisputeList> storage) {
|
||||
super(storage);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Implement template methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected RefundDisputeList getConcreteDisputeList() {
|
||||
return new RefundDisputeList(storage);
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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.refund;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
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.messages.DisputeResultMessage;
|
||||
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
|
||||
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
|
||||
import bisq.core.support.messages.ChatMessage;
|
||||
import bisq.core.support.messages.SupportMessage;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
|
||||
import bisq.network.p2p.AckMessageSourceType;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public RefundManager(P2PService p2PService,
|
||||
TradeWalletService tradeWalletService,
|
||||
BtcWalletService walletService,
|
||||
WalletsSetup walletsSetup,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
PubKeyRing pubKeyRing,
|
||||
RefundDisputeListService refundDisputeListService) {
|
||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
||||
openOfferManager, pubKeyRing, refundDisputeListService);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Implement template methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public SupportType getSupportType() {
|
||||
return SupportType.REFUND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchMessage(SupportMessage message) {
|
||||
if (canProcessMessage(message)) {
|
||||
log.info("Received {} with tradeId {} and uid {}",
|
||||
message.getClass().getSimpleName(), message.getTradeId(), message.getUid());
|
||||
|
||||
if (message instanceof OpenNewDisputeMessage) {
|
||||
onOpenNewDisputeMessage((OpenNewDisputeMessage) message);
|
||||
} else if (message instanceof PeerOpenedDisputeMessage) {
|
||||
onPeerOpenedDisputeMessage((PeerOpenedDisputeMessage) message);
|
||||
} else if (message instanceof ChatMessage) {
|
||||
onChatMessage((ChatMessage) message);
|
||||
} else if (message instanceof DisputeResultMessage) {
|
||||
onDisputeResultMessage((DisputeResultMessage) message);
|
||||
} else {
|
||||
log.warn("Unsupported message at dispatchMessage. message={}", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Trade.DisputeState getDisputeState_StartedByPeer() {
|
||||
return Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AckMessageSourceType getAckMessageSourceType() {
|
||||
return AckMessageSourceType.REFUND_MESSAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupDisputes() {
|
||||
disputeListService.cleanupDisputes(tradeId -> tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.REFUND_REQUEST_CLOSED));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisputeInfo(Dispute dispute) {
|
||||
String role = Res.get("shared.refundAgent").toLowerCase();
|
||||
String link = "https://docs.bisq.network/trading-rules.html#arbitration";
|
||||
return Res.get("support.initialInfo", role, role, link);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisputeIntroForPeer(String disputeInfo) {
|
||||
return Res.get("support.peerOpenedDispute", disputeInfo, Version.VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisputeIntroForDisputeCreator(String disputeInfo) {
|
||||
return Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Message handler
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
// We get that message at both peers. The dispute object is in context of the trader
|
||||
public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
|
||||
DisputeResult disputeResult = disputeResultMessage.getDisputeResult();
|
||||
String tradeId = disputeResult.getTradeId();
|
||||
ChatMessage chatMessage = disputeResult.getChatMessage();
|
||||
checkNotNull(chatMessage, "chatMessage must not be null");
|
||||
Optional<Dispute> disputeOptional = findDispute(disputeResult);
|
||||
String uid = disputeResultMessage.getUid();
|
||||
if (!disputeOptional.isPresent()) {
|
||||
log.warn("We got a dispute result msg but we don't have a matching dispute. " +
|
||||
"That might happen when we get the disputeResultMessage before the dispute was created. " +
|
||||
"We try again after 2 sec. to apply the disputeResultMessage. TradeId = " + tradeId);
|
||||
if (!delayMsgMap.containsKey(uid)) {
|
||||
// We delay 2 sec. to be sure the comm. msg gets added first
|
||||
Timer timer = UserThread.runAfter(() -> onDisputeResultMessage(disputeResultMessage), 2);
|
||||
delayMsgMap.put(uid, timer);
|
||||
} else {
|
||||
log.warn("We got a dispute result msg after we already repeated to apply the message after a delay. " +
|
||||
"That should never happen. TradeId = " + tradeId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Dispute dispute = disputeOptional.get();
|
||||
cleanupRetryMap(uid);
|
||||
if (!dispute.getChatMessages().contains(chatMessage)) {
|
||||
dispute.addAndPersistChatMessage(chatMessage);
|
||||
} else {
|
||||
log.warn("We got a dispute mail msg what we have already stored. TradeId = " + chatMessage.getTradeId());
|
||||
}
|
||||
dispute.setIsClosed(true);
|
||||
|
||||
if (dispute.disputeResultProperty().get() != null) {
|
||||
log.warn("We got already a dispute result. That should only happen if a dispute needs to be closed " +
|
||||
"again because the first close did not succeed. TradeId = " + tradeId);
|
||||
}
|
||||
|
||||
dispute.setDisputeResult(disputeResult);
|
||||
|
||||
Optional<Trade> tradeOptional = tradeManager.getTradeById(tradeId);
|
||||
if (tradeOptional.isPresent()) {
|
||||
Trade trade = tradeOptional.get();
|
||||
if (trade.getDisputeState() == Trade.DisputeState.REFUND_REQUESTED ||
|
||||
trade.getDisputeState() == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER) {
|
||||
trade.setDisputeState(Trade.DisputeState.REFUND_REQUEST_CLOSED);
|
||||
}
|
||||
} else {
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
|
||||
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
|
||||
}
|
||||
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null);
|
||||
|
||||
// set state after payout as we call swapTradeEntryToAvailableEntry
|
||||
if (tradeManager.getTradeById(tradeId).isPresent()) {
|
||||
tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.REFUND_REQUEST_CLOSED);
|
||||
} else {
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
|
||||
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public NodeAddress getAgentNodeAddress(Dispute dispute) {
|
||||
return dispute.getContract().getRefundAgentNodeAddress();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.refund;
|
||||
|
||||
import bisq.common.proto.ProtoUtil;
|
||||
|
||||
// todo
|
||||
public enum RefundResultState {
|
||||
UNDEFINED_REFUND_RESULT;
|
||||
|
||||
public static RefundResultState fromProto(protobuf.RefundResultState refundResultState) {
|
||||
return ProtoUtil.enumFromProto(RefundResultState.class, refundResultState.name());
|
||||
}
|
||||
|
||||
public static protobuf.RefundResultState toProtoMessage(RefundResultState refundResultState) {
|
||||
return protobuf.RefundResultState.valueOf(refundResultState.name());
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.refund;
|
||||
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeSession;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Slf4j
|
||||
public class RefundSession extends DisputeSession {
|
||||
|
||||
public RefundSession(@Nullable Dispute dispute, boolean isTrader) {
|
||||
super(dispute, isTrader);
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.refund.refundagent;
|
||||
|
||||
import bisq.core.support.dispute.agent.DisputeAgent;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.proto.ProtoUtil;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
@Getter
|
||||
public final class RefundAgent extends DisputeAgent implements CapabilityRequiringPayload {
|
||||
|
||||
public RefundAgent(NodeAddress nodeAddress,
|
||||
PubKeyRing pubKeyRing,
|
||||
List<String> languageCodes,
|
||||
long registrationDate,
|
||||
byte[] registrationPubKey,
|
||||
String registrationSignature,
|
||||
@Nullable String emailAddress,
|
||||
@Nullable String info,
|
||||
@Nullable Map<String, String> extraDataMap) {
|
||||
|
||||
super(nodeAddress,
|
||||
pubKeyRing,
|
||||
languageCodes,
|
||||
registrationDate,
|
||||
registrationPubKey,
|
||||
registrationSignature,
|
||||
emailAddress,
|
||||
info,
|
||||
extraDataMap);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public protobuf.StoragePayload toProtoMessage() {
|
||||
protobuf.RefundAgent.Builder builder = protobuf.RefundAgent.newBuilder()
|
||||
.setNodeAddress(nodeAddress.toProtoMessage())
|
||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||
.addAllLanguageCodes(languageCodes)
|
||||
.setRegistrationDate(registrationDate)
|
||||
.setRegistrationPubKey(ByteString.copyFrom(registrationPubKey))
|
||||
.setRegistrationSignature(registrationSignature);
|
||||
Optional.ofNullable(emailAddress).ifPresent(builder::setEmailAddress);
|
||||
Optional.ofNullable(info).ifPresent(builder::setInfo);
|
||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||
return protobuf.StoragePayload.newBuilder().setRefundAgent(builder).build();
|
||||
}
|
||||
|
||||
public static RefundAgent fromProto(protobuf.RefundAgent proto) {
|
||||
return new RefundAgent(NodeAddress.fromProto(proto.getNodeAddress()),
|
||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||
new ArrayList<>(proto.getLanguageCodesList()),
|
||||
proto.getRegistrationDate(),
|
||||
proto.getRegistrationPubKey().toByteArray(),
|
||||
proto.getRegistrationSignature(),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getEmailAddress()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getInfo()),
|
||||
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RefundAgent{} " + super.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Capabilities getRequiredCapabilities() {
|
||||
return new Capabilities(Capability.REFUND_AGENT);
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.refund.refundagent;
|
||||
|
||||
import bisq.core.app.AppOptionKeys;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.support.dispute.agent.DisputeAgentManager;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class RefundAgentManager extends DisputeAgentManager<RefundAgent> {
|
||||
|
||||
@Inject
|
||||
public RefundAgentManager(KeyRing keyRing,
|
||||
RefundAgentService refundAgentService,
|
||||
User user,
|
||||
FilterManager filterManager,
|
||||
@Named(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||
super(keyRing, refundAgentService, user, filterManager, useDevPrivilegeKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getPubKeyList() {
|
||||
return List.of("02a25798e256b800d7ea71c31098ac9a47cb20892176afdfeb051f5ded382d44af",
|
||||
"0360455d3cffe00ef73cc1284c84eedacc8c5c3374c43f4aac8ffb95f5130b9ef5",
|
||||
"03b0513afbb531bc4551b379eba027feddd33c92b5990fd477b0fa6eff90a5b7db",
|
||||
"03533fd75fda29c351298e50b8ea696656dcb8ce4e263d10618c6901a50450bf0e",
|
||||
"028124436482aa4c61a4bc4097d60c80b09f4285413be3b023a37a0164cbd5d818",
|
||||
"0384fcf883116d8e9469720ed7808cc4141f6dc6a5ed23d76dd48f2f5f255590d7",
|
||||
"029bd318ecee4e212ff06a4396770d600d72e9e0c6532142a428bdb401491e9721",
|
||||
"02e375b4b24d0a858953f7f94666667554d41f78000b9c8a301294223688b29011",
|
||||
"0232c088ae7c070de89d2b6c8d485b34bf0e3b2a964a2c6622f39ca501260c23f7",
|
||||
"033e047f74f2aa1ce41e8c85731f97ab83d448d65dc8518ab3df4474a5d53a3d19",
|
||||
"02f52a8cf373c8cbddb318e523b7f111168bf753fdfb6f8aa81f88c950ede3a5ce",
|
||||
"039784029922c54bcd0f0e7f14530f586053a5f4e596e86b3474cd7404657088ae",
|
||||
"037969f9d5ab2cc609104c6e61323df55428f8f108c11aab7c7b5f953081d39304",
|
||||
"031bd37475b8c5615ac46d6816e791c59d806d72a0bc6739ae94e5fe4545c7f8a6",
|
||||
"021bb92c636feacf5b082313eb071a63dfcd26501a48b3cd248e35438e5afb7daf");
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExpectedInstance(ProtectedStorageEntry data) {
|
||||
return data.getProtectedStoragePayload() instanceof RefundAgent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAcceptedDisputeAgentToUser(RefundAgent disputeAgent) {
|
||||
user.addAcceptedRefundAgent(disputeAgent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeAcceptedDisputeAgentFromUser(ProtectedStorageEntry data) {
|
||||
user.removeAcceptedRefundAgent((RefundAgent) data.getProtectedStoragePayload());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<RefundAgent> getAcceptedDisputeAgentsFromUser() {
|
||||
return user.getAcceptedRefundAgents();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearAcceptedDisputeAgentsAtUser() {
|
||||
user.clearAcceptedRefundAgents();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RefundAgent getRegisteredDisputeAgentFromUser() {
|
||||
return user.getRegisteredRefundAgent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRegisteredDisputeAgentAtUser(RefundAgent disputeAgent) {
|
||||
user.setRegisteredRefundAgent(disputeAgent);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.refund.refundagent;
|
||||
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.support.dispute.agent.DisputeAgentService;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class RefundAgentService extends DisputeAgentService<RefundAgent> {
|
||||
@Inject
|
||||
public RefundAgentService(P2PService p2PService, FilterManager filterManager) {
|
||||
super(p2PService, filterManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<RefundAgent> getDisputeAgentSet(List<String> bannedDisputeAgents) {
|
||||
return p2PService.getDataMap().values().stream()
|
||||
.filter(data -> data.getProtectedStoragePayload() instanceof RefundAgent)
|
||||
.map(data -> (RefundAgent) data.getProtectedStoragePayload())
|
||||
.filter(a -> bannedDisputeAgents == null ||
|
||||
!bannedDisputeAgents.contains(a.getNodeAddress().getFullAddress()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getDisputeAgentsFromFilter() {
|
||||
return filterManager.getFilter() != null ? filterManager.getFilter().getRefundAgents() : new ArrayList<>();
|
||||
}
|
||||
|
||||
public Map<NodeAddress, RefundAgent> getRefundAgents() {
|
||||
return super.getDisputeAgents();
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ package bisq.core.trade;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
import bisq.core.trade.protocol.BuyerAsMakerProtocol;
|
||||
import bisq.core.trade.protocol.MakerProtocol;
|
||||
|
||||
@ -48,6 +48,7 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
||||
boolean isCurrencyForTakerFeeBtc,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
super(offer,
|
||||
@ -56,6 +57,7 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
||||
isCurrencyForTakerFeeBtc,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress,
|
||||
storage,
|
||||
btcWalletService);
|
||||
}
|
||||
@ -84,6 +86,7 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
||||
proto.getIsCurrencyForTakerFeeBtc(),
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
|
||||
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
|
||||
storage,
|
||||
btcWalletService);
|
||||
|
||||
@ -107,7 +110,7 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTakeOfferRequest(TradeMessage message,
|
||||
public void handleTakeOfferRequest(InputsForDepositTxRequest message,
|
||||
NodeAddress taker,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
((MakerProtocol) tradeProtocol).handleTakeOfferRequest(message, taker, errorMessageHandler);
|
||||
|
@ -51,6 +51,7 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
||||
NodeAddress tradingPeerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
super(offer,
|
||||
@ -62,6 +63,7 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
||||
tradingPeerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress,
|
||||
storage,
|
||||
btcWalletService);
|
||||
}
|
||||
@ -94,6 +96,7 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
||||
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
|
||||
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
|
||||
storage,
|
||||
btcWalletService),
|
||||
proto,
|
||||
|
@ -47,6 +47,7 @@ public abstract class BuyerTrade extends Trade {
|
||||
NodeAddress tradingPeerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
super(offer,
|
||||
@ -58,6 +59,7 @@ public abstract class BuyerTrade extends Trade {
|
||||
tradingPeerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress,
|
||||
storage,
|
||||
btcWalletService);
|
||||
}
|
||||
@ -68,6 +70,7 @@ public abstract class BuyerTrade extends Trade {
|
||||
boolean isCurrencyForTakerFeeBtc,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
super(offer,
|
||||
@ -76,6 +79,7 @@ public abstract class BuyerTrade extends Trade {
|
||||
isCurrencyForTakerFeeBtc,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress,
|
||||
storage,
|
||||
btcWalletService);
|
||||
}
|
||||
|
@ -55,7 +55,6 @@ public final class Contract implements NetworkPayload {
|
||||
private final String takerFeeTxID;
|
||||
private final NodeAddress buyerNodeAddress;
|
||||
private final NodeAddress sellerNodeAddress;
|
||||
private final NodeAddress arbitratorNodeAddress;
|
||||
private final NodeAddress mediatorNodeAddress;
|
||||
private final boolean isBuyerMakerAndSellerTaker;
|
||||
private final String makerAccountId;
|
||||
@ -73,13 +72,16 @@ public final class Contract implements NetworkPayload {
|
||||
@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 arbitratorNodeAddress,
|
||||
NodeAddress mediatorNodeAddress,
|
||||
boolean isBuyerMakerAndSellerTaker,
|
||||
String makerAccountId,
|
||||
@ -91,14 +93,15 @@ public final class Contract implements NetworkPayload {
|
||||
String makerPayoutAddressString,
|
||||
String takerPayoutAddressString,
|
||||
byte[] makerMultiSigPubKey,
|
||||
byte[] takerMultiSigPubKey) {
|
||||
byte[] takerMultiSigPubKey,
|
||||
long lockTime,
|
||||
NodeAddress refundAgentNodeAddress) {
|
||||
this.offerPayload = offerPayload;
|
||||
this.tradeAmount = tradeAmount;
|
||||
this.tradePrice = tradePrice;
|
||||
this.takerFeeTxID = takerFeeTxID;
|
||||
this.buyerNodeAddress = buyerNodeAddress;
|
||||
this.sellerNodeAddress = sellerNodeAddress;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
this.mediatorNodeAddress = mediatorNodeAddress;
|
||||
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
|
||||
this.makerAccountId = makerAccountId;
|
||||
@ -111,6 +114,8 @@ public final class Contract implements NetworkPayload {
|
||||
this.takerPayoutAddressString = takerPayoutAddressString;
|
||||
this.makerMultiSigPubKey = makerMultiSigPubKey;
|
||||
this.takerMultiSigPubKey = takerMultiSigPubKey;
|
||||
this.lockTime = lockTime;
|
||||
this.refundAgentNodeAddress = refundAgentNodeAddress;
|
||||
|
||||
String makerPaymentMethodId = makerPaymentAccountPayload.getPaymentMethodId();
|
||||
String takerPaymentMethodId = takerPaymentAccountPayload.getPaymentMethodId();
|
||||
@ -128,7 +133,6 @@ public final class Contract implements NetworkPayload {
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Nullable
|
||||
public static Contract fromProto(protobuf.Contract proto, CoreProtoResolver coreProtoResolver) {
|
||||
return new Contract(OfferPayload.fromProto(proto.getOfferPayload()),
|
||||
proto.getTradeAmount(),
|
||||
@ -136,7 +140,6 @@ public final class Contract implements NetworkPayload {
|
||||
proto.getTakerFeeTxId(),
|
||||
NodeAddress.fromProto(proto.getBuyerNodeAddress()),
|
||||
NodeAddress.fromProto(proto.getSellerNodeAddress()),
|
||||
NodeAddress.fromProto(proto.getArbitratorNodeAddress()),
|
||||
NodeAddress.fromProto(proto.getMediatorNodeAddress()),
|
||||
proto.getIsBuyerMakerAndSellerTaker(),
|
||||
proto.getMakerAccountId(),
|
||||
@ -148,7 +151,9 @@ public final class Contract implements NetworkPayload {
|
||||
proto.getMakerPayoutAddressString(),
|
||||
proto.getTakerPayoutAddressString(),
|
||||
proto.getMakerMultiSigPubKey().toByteArray(),
|
||||
proto.getTakerMultiSigPubKey().toByteArray());
|
||||
proto.getTakerMultiSigPubKey().toByteArray(),
|
||||
proto.getLockTime(),
|
||||
NodeAddress.fromProto(proto.getRefundAgentNodeAddress()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -160,7 +165,6 @@ public final class Contract implements NetworkPayload {
|
||||
.setTakerFeeTxId(takerFeeTxID)
|
||||
.setBuyerNodeAddress(buyerNodeAddress.toProtoMessage())
|
||||
.setSellerNodeAddress(sellerNodeAddress.toProtoMessage())
|
||||
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
|
||||
.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage())
|
||||
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
|
||||
.setMakerAccountId(makerAccountId)
|
||||
@ -173,6 +177,8 @@ public final class Contract implements NetworkPayload {
|
||||
.setTakerPayoutAddressString(takerPayoutAddressString)
|
||||
.setMakerMultiSigPubKey(ByteString.copyFrom(makerMultiSigPubKey))
|
||||
.setTakerMultiSigPubKey(ByteString.copyFrom(takerMultiSigPubKey))
|
||||
.setLockTime(lockTime)
|
||||
.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage())
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -289,8 +295,8 @@ public final class Contract implements NetworkPayload {
|
||||
",\n takerFeeTxID='" + takerFeeTxID + '\'' +
|
||||
",\n buyerNodeAddress=" + buyerNodeAddress +
|
||||
",\n sellerNodeAddress=" + sellerNodeAddress +
|
||||
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
||||
",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker +
|
||||
",\n makerAccountId='" + makerAccountId + '\'' +
|
||||
",\n takerAccountId='" + takerAccountId + '\'' +
|
||||
@ -304,6 +310,7 @@ public final class Contract implements NetworkPayload {
|
||||
",\n takerMultiSigPubKey=" + Utilities.bytesAsHexString(takerMultiSigPubKey) +
|
||||
",\n buyerMultiSigPubKey=" + Utilities.bytesAsHexString(getBuyerMultiSigPubKey()) +
|
||||
",\n sellerMultiSigPubKey=" + Utilities.bytesAsHexString(getSellerMultiSigPubKey()) +
|
||||
",\n lockTime=" + lockTime +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
@ -17,12 +17,12 @@
|
||||
|
||||
package bisq.core.trade;
|
||||
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
|
||||
public interface MakerTrade {
|
||||
void handleTakeOfferRequest(TradeMessage message, NodeAddress peerNodeAddress, ErrorMessageHandler errorMessageHandler);
|
||||
void handleTakeOfferRequest(InputsForDepositTxRequest message, NodeAddress peerNodeAddress, ErrorMessageHandler errorMessageHandler);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ package bisq.core.trade;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
import bisq.core.trade.protocol.MakerProtocol;
|
||||
import bisq.core.trade.protocol.SellerAsMakerProtocol;
|
||||
|
||||
@ -48,9 +48,18 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
||||
boolean isCurrencyForTakerFeeBtc,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
super(offer, txFee, takerFee, isCurrencyForTakerFeeBtc, arbitratorNodeAddress, mediatorNodeAddress, storage, btcWalletService);
|
||||
super(offer,
|
||||
txFee,
|
||||
takerFee,
|
||||
isCurrencyForTakerFeeBtc,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress,
|
||||
storage,
|
||||
btcWalletService);
|
||||
}
|
||||
|
||||
|
||||
@ -78,6 +87,7 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
||||
proto.getIsCurrencyForTakerFeeBtc(),
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
|
||||
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
|
||||
storage,
|
||||
btcWalletService);
|
||||
|
||||
@ -101,7 +111,7 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTakeOfferRequest(TradeMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
|
||||
public void handleTakeOfferRequest(InputsForDepositTxRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
|
||||
((MakerProtocol) tradeProtocol).handleTakeOfferRequest(message, taker, errorMessageHandler);
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
||||
NodeAddress tradingPeerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
super(offer,
|
||||
@ -62,6 +63,7 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
||||
tradingPeerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress,
|
||||
storage,
|
||||
btcWalletService);
|
||||
}
|
||||
@ -94,6 +96,7 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
||||
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
|
||||
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
|
||||
storage,
|
||||
btcWalletService),
|
||||
proto,
|
||||
|
@ -46,6 +46,7 @@ public abstract class SellerTrade extends Trade {
|
||||
NodeAddress tradingPeerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
super(offer,
|
||||
@ -57,6 +58,7 @@ public abstract class SellerTrade extends Trade {
|
||||
tradingPeerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress,
|
||||
storage,
|
||||
btcWalletService);
|
||||
}
|
||||
@ -67,6 +69,7 @@ public abstract class SellerTrade extends Trade {
|
||||
boolean isCurrencyForTakerFeeBtc,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
super(offer,
|
||||
@ -75,6 +78,7 @@ public abstract class SellerTrade extends Trade {
|
||||
isCurrencyForTakerFeeBtc,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress,
|
||||
storage,
|
||||
btcWalletService);
|
||||
}
|
||||
|
@ -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.dao.DaoFacade;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.monetary.Price;
|
||||
@ -34,6 +35,8 @@ import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.support.dispute.mediation.MediationResultState;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.RefundResultState;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.support.messages.ChatMessage;
|
||||
import bisq.core.trade.protocol.ProcessModel;
|
||||
import bisq.core.trade.protocol.TradeProtocol;
|
||||
@ -114,7 +117,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
// maker perspective
|
||||
MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
|
||||
MAKER_SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
|
||||
MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
|
||||
MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED), //todo remove
|
||||
MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
|
||||
|
||||
// taker perspective
|
||||
@ -122,21 +125,21 @@ public abstract class Trade implements Tradable, Model {
|
||||
|
||||
|
||||
// #################### Phase DEPOSIT_PAID
|
||||
TAKER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
|
||||
SELLER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
|
||||
|
||||
|
||||
// DEPOSIT_TX_PUBLISHED_MSG
|
||||
// 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),
|
||||
// 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),
|
||||
|
||||
// maker perspective
|
||||
MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
|
||||
// buyer perspective
|
||||
BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(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),
|
||||
// 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),
|
||||
|
||||
|
||||
// #################### Phase DEPOSIT_CONFIRMED
|
||||
@ -221,7 +224,12 @@ public abstract class Trade implements Tradable, Model {
|
||||
// mediation
|
||||
MEDIATION_REQUESTED,
|
||||
MEDIATION_STARTED_BY_PEER,
|
||||
MEDIATION_CLOSED;
|
||||
MEDIATION_CLOSED,
|
||||
|
||||
// refund
|
||||
REFUND_REQUESTED,
|
||||
REFUND_REQUEST_STARTED_BY_PEER,
|
||||
REFUND_REQUEST_CLOSED;
|
||||
|
||||
public static Trade.DisputeState fromProto(protobuf.Trade.DisputeState disputeState) {
|
||||
return ProtoUtil.enumFromProto(Trade.DisputeState.class, disputeState.name());
|
||||
@ -368,9 +376,14 @@ public abstract class Trade implements Tradable, Model {
|
||||
@Getter
|
||||
transient protected TradeProtocol tradeProtocol;
|
||||
@Nullable
|
||||
transient private Transaction payoutTx;
|
||||
@Nullable
|
||||
transient private Transaction depositTx;
|
||||
|
||||
// Added in v1.2.0
|
||||
@Nullable
|
||||
transient private Transaction delayedPayoutTx;
|
||||
|
||||
@Nullable
|
||||
transient private Transaction payoutTx;
|
||||
@Nullable
|
||||
transient private Coin tradeAmount;
|
||||
|
||||
@ -378,12 +391,33 @@ public abstract class Trade implements Tradable, Model {
|
||||
transient private ObjectProperty<Volume> tradeVolumeProperty;
|
||||
final transient private Set<DecryptedMessageWithPubKey> decryptedMessageWithPubKeySet = new HashSet<>();
|
||||
|
||||
//Added in v1.1.6
|
||||
// Added in v1.1.6
|
||||
@Getter
|
||||
@Nullable
|
||||
private MediationResultState mediationResultState = MediationResultState.UNDEFINED_MEDIATION_RESULT;
|
||||
transient final private ObjectProperty<MediationResultState> mediationResultStateProperty = new SimpleObjectProperty<>(mediationResultState);
|
||||
|
||||
// Added in v1.2.0
|
||||
@Getter
|
||||
@Setter
|
||||
private long lockTime;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private byte[] delayedPayoutTxBytes;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private NodeAddress refundAgentNodeAddress;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private PubKeyRing refundAgentPubKeyRing;
|
||||
@Getter
|
||||
@Nullable
|
||||
private RefundResultState refundResultState = RefundResultState.UNDEFINED_REFUND_RESULT;
|
||||
transient final private ObjectProperty<RefundResultState> refundResultStateProperty = new SimpleObjectProperty<>(refundResultState);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, initialization
|
||||
@ -396,6 +430,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
boolean isCurrencyForTakerFeeBtc,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
this.offer = offer;
|
||||
@ -406,6 +441,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
this.mediatorNodeAddress = mediatorNodeAddress;
|
||||
this.refundAgentNodeAddress = refundAgentNodeAddress;
|
||||
|
||||
txFeeAsLong = txFee.value;
|
||||
takerFeeAsLong = takerFee.value;
|
||||
@ -425,6 +461,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
NodeAddress tradingPeerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
Storage<? extends TradableList> storage,
|
||||
BtcWalletService btcWalletService) {
|
||||
|
||||
@ -434,6 +471,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
isCurrencyForTakerFeeBtc,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress,
|
||||
storage,
|
||||
btcWalletService);
|
||||
this.tradePrice = tradePrice;
|
||||
@ -463,7 +501,8 @@ public abstract class Trade implements Tradable, Model {
|
||||
.setTradePeriodState(Trade.TradePeriodState.toProtoMessage(tradePeriodState))
|
||||
.addAllChatMessage(chatMessages.stream()
|
||||
.map(msg -> msg.toProtoNetworkEnvelope().getChatMessage())
|
||||
.collect(Collectors.toList()));
|
||||
.collect(Collectors.toList()))
|
||||
.setLockTime(lockTime);
|
||||
|
||||
Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId);
|
||||
Optional.ofNullable(depositTxId).ifPresent(builder::setDepositTxId);
|
||||
@ -476,13 +515,17 @@ public abstract class Trade implements Tradable, Model {
|
||||
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
|
||||
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(mediatorNodeAddress).ifPresent(e -> builder.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(refundAgentNodeAddress).ifPresent(e -> builder.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(arbitratorBtcPubKey).ifPresent(e -> builder.setArbitratorBtcPubKey(ByteString.copyFrom(arbitratorBtcPubKey)));
|
||||
Optional.ofNullable(takerPaymentAccountId).ifPresent(builder::setTakerPaymentAccountId);
|
||||
Optional.ofNullable(errorMessage).ifPresent(builder::setErrorMessage);
|
||||
Optional.ofNullable(arbitratorPubKeyRing).ifPresent(e -> builder.setArbitratorPubKeyRing(arbitratorPubKeyRing.toProtoMessage()));
|
||||
Optional.ofNullable(mediatorPubKeyRing).ifPresent(e -> builder.setMediatorPubKeyRing(mediatorPubKeyRing.toProtoMessage()));
|
||||
Optional.ofNullable(refundAgentPubKeyRing).ifPresent(e -> builder.setRefundAgentPubKeyRing(refundAgentPubKeyRing.toProtoMessage()));
|
||||
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
|
||||
Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState)));
|
||||
Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState)));
|
||||
Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes)));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ -502,13 +545,18 @@ public abstract class Trade implements Tradable, Model {
|
||||
trade.setMakerContractSignature(ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()));
|
||||
trade.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
|
||||
trade.setMediatorNodeAddress(proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null);
|
||||
trade.setRefundAgentNodeAddress(proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
|
||||
trade.setArbitratorBtcPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getArbitratorBtcPubKey()));
|
||||
trade.setTakerPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getTakerPaymentAccountId()));
|
||||
trade.setErrorMessage(ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()));
|
||||
trade.setArbitratorPubKeyRing(proto.hasArbitratorPubKeyRing() ? PubKeyRing.fromProto(proto.getArbitratorPubKeyRing()) : null);
|
||||
trade.setMediatorPubKeyRing(proto.hasMediatorPubKeyRing() ? PubKeyRing.fromProto(proto.getMediatorPubKeyRing()) : null);
|
||||
trade.setRefundAgentPubKeyRing(proto.hasRefundAgentPubKeyRing() ? PubKeyRing.fromProto(proto.getRefundAgentPubKeyRing()) : null);
|
||||
trade.setCounterCurrencyTxId(proto.getCounterCurrencyTxId().isEmpty() ? null : proto.getCounterCurrencyTxId());
|
||||
trade.setMediationResultState(MediationResultState.fromProto(proto.getMediationResultState()));
|
||||
trade.setRefundResultState(RefundResultState.fromProto(proto.getRefundResultState()));
|
||||
trade.setDelayedPayoutTxBytes(ProtoUtil.byteArrayOrNullFromProto(proto.getDelayedPayoutTxBytes()));
|
||||
trade.setLockTime(proto.getLockTime());
|
||||
|
||||
trade.chatMessages.addAll(proto.getChatMessageList().stream()
|
||||
.map(ChatMessage::fromPayloadProto)
|
||||
@ -531,6 +579,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
BtcWalletService btcWalletService,
|
||||
BsqWalletService bsqWalletService,
|
||||
TradeWalletService tradeWalletService,
|
||||
DaoFacade daoFacade,
|
||||
TradeManager tradeManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
ReferralIdService referralIdService,
|
||||
@ -540,6 +589,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
ArbitratorManager arbitratorManager,
|
||||
MediatorManager mediatorManager,
|
||||
RefundAgentManager refundAgentManager,
|
||||
KeyRing keyRing,
|
||||
boolean useSavingsWallet,
|
||||
Coin fundsNeededForTrade) {
|
||||
@ -550,6 +600,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
btcWalletService,
|
||||
bsqWalletService,
|
||||
tradeWalletService,
|
||||
daoFacade,
|
||||
referralIdService,
|
||||
user,
|
||||
filterManager,
|
||||
@ -557,6 +608,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
tradeStatisticsManager,
|
||||
arbitratorManager,
|
||||
mediatorManager,
|
||||
refundAgentManager,
|
||||
keyRing,
|
||||
useSavingsWallet,
|
||||
fundsNeededForTrade);
|
||||
@ -572,6 +624,11 @@ public abstract class Trade implements Tradable, Model {
|
||||
persist();
|
||||
});
|
||||
|
||||
refundAgentManager.getDisputeAgentByNodeAddress(refundAgentNodeAddress).ifPresent(refundAgent -> {
|
||||
refundAgentPubKeyRing = refundAgent.getPubKeyRing();
|
||||
persist();
|
||||
});
|
||||
|
||||
createTradeProtocol();
|
||||
|
||||
// If we have already received a msg we apply it.
|
||||
@ -590,11 +647,10 @@ public abstract class Trade implements Tradable, Model {
|
||||
// The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet.
|
||||
void updateDepositTxFromWallet() {
|
||||
if (getDepositTx() != null)
|
||||
setDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getHash()));
|
||||
applyDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getHash()));
|
||||
}
|
||||
|
||||
public void setDepositTx(Transaction tx) {
|
||||
log.debug("setDepositTx " + tx);
|
||||
public void applyDepositTx(Transaction tx) {
|
||||
this.depositTx = tx;
|
||||
depositTxId = depositTx.getHashAsString();
|
||||
setupConfidenceListener();
|
||||
@ -608,6 +664,25 @@ public abstract class Trade implements Tradable, Model {
|
||||
return depositTx;
|
||||
}
|
||||
|
||||
public void applyDelayedPayoutTx(Transaction delayedPayoutTx) {
|
||||
this.delayedPayoutTx = delayedPayoutTx;
|
||||
this.delayedPayoutTxBytes = delayedPayoutTx.bitcoinSerialize();
|
||||
persist();
|
||||
}
|
||||
|
||||
public void applyDelayedPayoutTxBytes(byte[] delayedPayoutTxBytes) {
|
||||
this.delayedPayoutTxBytes = delayedPayoutTxBytes;
|
||||
persist();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Transaction getDelayedPayoutTx() {
|
||||
if (delayedPayoutTx == null) {
|
||||
delayedPayoutTx = delayedPayoutTxBytes != null ? processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxBytes) : null;
|
||||
}
|
||||
return delayedPayoutTx;
|
||||
}
|
||||
|
||||
// We don't need to persist the msg as if we dont apply it it will not be removed from the P2P network and we
|
||||
// will received it again at next startup. Such might happen in edge cases when the user shuts down after we
|
||||
// received the msb but before the init is called.
|
||||
@ -703,6 +778,14 @@ public abstract class Trade implements Tradable, Model {
|
||||
persist();
|
||||
}
|
||||
|
||||
public void setRefundResultState(RefundResultState refundResultState) {
|
||||
boolean changed = this.refundResultState != refundResultState;
|
||||
this.refundResultState = refundResultState;
|
||||
refundResultStateProperty.set(refundResultState);
|
||||
if (changed)
|
||||
persist();
|
||||
}
|
||||
|
||||
|
||||
public void setTradePeriodState(TradePeriodState tradePeriodState) {
|
||||
boolean changed = this.tradePeriodState != tradePeriodState;
|
||||
@ -821,7 +904,12 @@ public abstract class Trade implements Tradable, Model {
|
||||
}
|
||||
|
||||
public boolean isFundsLockedIn() {
|
||||
return isDepositPublished() && !isPayoutPublished() && disputeState != DisputeState.DISPUTE_CLOSED;
|
||||
return isDepositPublished() &&
|
||||
!isPayoutPublished() &&
|
||||
disputeState != DisputeState.DISPUTE_CLOSED &&
|
||||
disputeState != DisputeState.REFUND_REQUESTED &&
|
||||
disputeState != DisputeState.REFUND_REQUEST_STARTED_BY_PEER &&
|
||||
disputeState != DisputeState.REFUND_REQUEST_CLOSED;
|
||||
}
|
||||
|
||||
public boolean isDepositConfirmed() {
|
||||
@ -832,7 +920,6 @@ public abstract class Trade implements Tradable, Model {
|
||||
return getState().getPhase().ordinal() >= Phase.FIAT_SENT.ordinal();
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isFiatReceived() {
|
||||
return getState().getPhase().ordinal() >= Phase.FIAT_RECEIVED.ordinal();
|
||||
}
|
||||
@ -861,6 +948,10 @@ public abstract class Trade implements Tradable, Model {
|
||||
return mediationResultStateProperty;
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<RefundResultState> refundResultStateProperty() {
|
||||
return refundResultStateProperty;
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<TradePeriodState> tradePeriodStateProperty() {
|
||||
return tradePeriodStateProperty;
|
||||
}
|
||||
@ -987,7 +1078,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
",\n isCurrencyForTakerFeeBtc=" + isCurrencyForTakerFeeBtc +
|
||||
",\n txFeeAsLong=" + txFeeAsLong +
|
||||
",\n takerFeeAsLong=" + takerFeeAsLong +
|
||||
",\n takeOfferDate=" + getTakeOfferDate() +
|
||||
",\n takeOfferDate=" + takeOfferDate +
|
||||
",\n processModel=" + processModel +
|
||||
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
|
||||
",\n depositTxId='" + depositTxId + '\'' +
|
||||
@ -1004,10 +1095,14 @@ public abstract class Trade implements Tradable, Model {
|
||||
",\n takerContractSignature='" + takerContractSignature + '\'' +
|
||||
",\n makerContractSignature='" + makerContractSignature + '\'' +
|
||||
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
|
||||
",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
|
||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||
",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
|
||||
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
|
||||
",\n errorMessage='" + errorMessage + '\'' +
|
||||
",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' +
|
||||
",\n chatMessages=" + chatMessages +
|
||||
",\n txFee=" + txFee +
|
||||
",\n takerFee=" + takerFee +
|
||||
",\n storage=" + storage +
|
||||
@ -1018,15 +1113,21 @@ public abstract class Trade implements Tradable, Model {
|
||||
",\n tradePeriodStateProperty=" + tradePeriodStateProperty +
|
||||
",\n errorMessageProperty=" + errorMessageProperty +
|
||||
",\n tradeProtocol=" + tradeProtocol +
|
||||
",\n payoutTx=" + payoutTx +
|
||||
",\n depositTx=" + depositTx +
|
||||
",\n delayedPayoutTx=" + delayedPayoutTx +
|
||||
",\n payoutTx=" + payoutTx +
|
||||
",\n tradeAmount=" + tradeAmount +
|
||||
",\n tradeAmountProperty=" + tradeAmountProperty +
|
||||
",\n tradeVolumeProperty=" + tradeVolumeProperty +
|
||||
",\n decryptedMessageWithPubKeySet=" + decryptedMessageWithPubKeySet +
|
||||
",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
|
||||
",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
|
||||
",\n chatMessages=" + chatMessages +
|
||||
",\n mediationResultState=" + mediationResultState +
|
||||
",\n mediationResultStateProperty=" + mediationResultStateProperty +
|
||||
",\n lockTime=" + lockTime +
|
||||
",\n delayedPayoutTxBytes=" + Utilities.bytesAsHexString(delayedPayoutTxBytes) +
|
||||
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
||||
",\n refundAgentPubKeyRing=" + refundAgentPubKeyRing +
|
||||
",\n refundResultState=" + refundResultState +
|
||||
",\n refundResultStateProperty=" + refundResultStateProperty +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,14 @@ package bisq.core.trade;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.btc.exceptions.AddressEntryException;
|
||||
import bisq.core.btc.exceptions.TxBroadcastException;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
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.dao.DaoFacade;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
@ -32,10 +36,12 @@ import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
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.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.failed.FailedTradesManager;
|
||||
import bisq.core.trade.handlers.TradeResultHandler;
|
||||
import bisq.core.trade.messages.PayDepositRequest;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.statistics.ReferralIdService;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
@ -47,9 +53,9 @@ import bisq.network.p2p.AckMessageSourceType;
|
||||
import bisq.network.p2p.BootstrapListener;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.SendMailboxMessageListener;
|
||||
|
||||
import bisq.common.ClockWatcher;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.FaultHandler;
|
||||
@ -82,7 +88,7 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ -116,6 +122,8 @@ public class TradeManager implements PersistedDataHost {
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private final MediatorManager mediatorManager;
|
||||
private final RefundAgentManager refundAgentManager;
|
||||
private final DaoFacade daoFacade;
|
||||
private final ClockWatcher clockWatcher;
|
||||
|
||||
private final Storage<TradableList<Trade>> tradableListStorage;
|
||||
@ -150,6 +158,8 @@ public class TradeManager implements PersistedDataHost {
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
ArbitratorManager arbitratorManager,
|
||||
MediatorManager mediatorManager,
|
||||
RefundAgentManager refundAgentManager,
|
||||
DaoFacade daoFacade,
|
||||
ClockWatcher clockWatcher,
|
||||
Storage<TradableList<Trade>> storage) {
|
||||
this.user = user;
|
||||
@ -168,6 +178,8 @@ public class TradeManager implements PersistedDataHost {
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.refundAgentManager = refundAgentManager;
|
||||
this.daoFacade = daoFacade;
|
||||
this.clockWatcher = clockWatcher;
|
||||
|
||||
tradableListStorage = storage;
|
||||
@ -176,8 +188,8 @@ public class TradeManager implements PersistedDataHost {
|
||||
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
|
||||
|
||||
// Handler for incoming initial network_messages from taker
|
||||
if (networkEnvelope instanceof PayDepositRequest) {
|
||||
handlePayDepositRequest((PayDepositRequest) networkEnvelope, peerNodeAddress);
|
||||
if (networkEnvelope instanceof InputsForDepositTxRequest) {
|
||||
handlePayDepositRequest((InputsForDepositTxRequest) networkEnvelope, peerNodeAddress);
|
||||
}
|
||||
});
|
||||
|
||||
@ -274,11 +286,6 @@ public class TradeManager implements PersistedDataHost {
|
||||
|
||||
cleanUpAddressEntries();
|
||||
|
||||
// TODO remove once we support Taker side publishing at take offer process
|
||||
// We start later to have better connectivity to the network
|
||||
UserThread.runAfter(() -> tradeStatisticsManager.publishTradeStatistics(tradesForStatistics),
|
||||
30, TimeUnit.SECONDS);
|
||||
|
||||
pendingTradesInitialized.set(true);
|
||||
}
|
||||
|
||||
@ -307,18 +314,18 @@ public class TradeManager implements PersistedDataHost {
|
||||
});
|
||||
}
|
||||
|
||||
private void handlePayDepositRequest(PayDepositRequest payDepositRequest, NodeAddress peer) {
|
||||
private void handlePayDepositRequest(InputsForDepositTxRequest inputsForDepositTxRequest, NodeAddress peer) {
|
||||
log.info("Received PayDepositRequest from {} with tradeId {} and uid {}",
|
||||
peer, payDepositRequest.getTradeId(), payDepositRequest.getUid());
|
||||
peer, inputsForDepositTxRequest.getTradeId(), inputsForDepositTxRequest.getUid());
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(payDepositRequest.getTradeId());
|
||||
Validator.nonEmptyStringOf(inputsForDepositTxRequest.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid requestDepositTxInputsMessage " + payDepositRequest.toString());
|
||||
log.warn("Invalid requestDepositTxInputsMessage " + inputsForDepositTxRequest.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(payDepositRequest.getTradeId());
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(inputsForDepositTxRequest.getTradeId());
|
||||
if (openOfferOptional.isPresent() && openOfferOptional.get().getState() == OpenOffer.State.AVAILABLE) {
|
||||
OpenOffer openOffer = openOfferOptional.get();
|
||||
Offer offer = openOffer.getOffer();
|
||||
@ -326,26 +333,28 @@ public class TradeManager implements PersistedDataHost {
|
||||
Trade trade;
|
||||
if (offer.isBuyOffer())
|
||||
trade = new BuyerAsMakerTrade(offer,
|
||||
Coin.valueOf(payDepositRequest.getTxFee()),
|
||||
Coin.valueOf(payDepositRequest.getTakerFee()),
|
||||
payDepositRequest.isCurrencyForTakerFeeBtc(),
|
||||
Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
|
||||
Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
|
||||
inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
|
||||
openOffer.getArbitratorNodeAddress(),
|
||||
openOffer.getMediatorNodeAddress(),
|
||||
openOffer.getRefundAgentNodeAddress(),
|
||||
tradableListStorage,
|
||||
btcWalletService);
|
||||
else
|
||||
trade = new SellerAsMakerTrade(offer,
|
||||
Coin.valueOf(payDepositRequest.getTxFee()),
|
||||
Coin.valueOf(payDepositRequest.getTakerFee()),
|
||||
payDepositRequest.isCurrencyForTakerFeeBtc(),
|
||||
Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
|
||||
Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
|
||||
inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
|
||||
openOffer.getArbitratorNodeAddress(),
|
||||
openOffer.getMediatorNodeAddress(),
|
||||
openOffer.getRefundAgentNodeAddress(),
|
||||
tradableListStorage,
|
||||
btcWalletService);
|
||||
|
||||
initTrade(trade, trade.getProcessModel().isUseSavingsWallet(), trade.getProcessModel().getFundsNeededForTradeAsLong());
|
||||
tradableList.add(trade);
|
||||
((MakerTrade) trade).handleTakeOfferRequest(payDepositRequest, peer, errorMessage -> {
|
||||
((MakerTrade) trade).handleTakeOfferRequest(inputsForDepositTxRequest, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null)
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
@ -362,6 +371,7 @@ public class TradeManager implements PersistedDataHost {
|
||||
btcWalletService,
|
||||
bsqWalletService,
|
||||
tradeWalletService,
|
||||
daoFacade,
|
||||
this,
|
||||
openOfferManager,
|
||||
referralIdService,
|
||||
@ -371,6 +381,7 @@ public class TradeManager implements PersistedDataHost {
|
||||
tradeStatisticsManager,
|
||||
arbitratorManager,
|
||||
mediatorManager,
|
||||
refundAgentManager,
|
||||
keyRing,
|
||||
useSavingsWallet,
|
||||
fundsNeededForTrade);
|
||||
@ -453,6 +464,7 @@ public class TradeManager implements PersistedDataHost {
|
||||
model.getPeerNodeAddress(),
|
||||
model.getSelectedArbitrator(),
|
||||
model.getSelectedMediator(),
|
||||
model.getSelectedRefundAgent(),
|
||||
tradableListStorage,
|
||||
btcWalletService);
|
||||
else
|
||||
@ -465,6 +477,7 @@ public class TradeManager implements PersistedDataHost {
|
||||
model.getPeerNodeAddress(),
|
||||
model.getSelectedArbitrator(),
|
||||
model.getSelectedMediator(),
|
||||
model.getSelectedRefundAgent(),
|
||||
tradableListStorage,
|
||||
btcWalletService);
|
||||
|
||||
@ -567,6 +580,70 @@ public class TradeManager implements PersistedDataHost {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Publish delayed payout tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void publishDelayedPayoutTx(String tradeId,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
getTradeById(tradeId).ifPresent(trade -> {
|
||||
Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
|
||||
if (delayedPayoutTx != null) {
|
||||
// We have spent the funds from the deposit tx with the delayedPayoutTx
|
||||
btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.MULTI_SIG);
|
||||
// We might receive funds on AddressEntry.Context.TRADE_PAYOUT so we don't swap that
|
||||
|
||||
Transaction committedDelayedPayoutTx = WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, btcWalletService.getWallet());
|
||||
|
||||
tradeWalletService.broadcastTx(committedDelayedPayoutTx, new TxBroadcaster.Callback() {
|
||||
@Override
|
||||
public void onSuccess(Transaction transaction) {
|
||||
log.info("publishDelayedPayoutTx onSuccess " + transaction);
|
||||
NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
PeerPublishedDelayedPayoutTxMessage msg = new PeerPublishedDelayedPayoutTxMessage(UUID.randomUUID().toString(),
|
||||
tradeId,
|
||||
tradingPeerNodeAddress);
|
||||
p2PService.sendEncryptedMailboxMessage(
|
||||
tradingPeerNodeAddress,
|
||||
trade.getProcessModel().getTradingPeer().getPubKeyRing(),
|
||||
msg,
|
||||
new SendMailboxMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
resultHandler.handleResult();
|
||||
log.info("SendMailboxMessageListener onArrived tradeId={} at peer {}",
|
||||
tradeId, tradingPeerNodeAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoredInMailbox() {
|
||||
resultHandler.handleResult();
|
||||
log.info("SendMailboxMessageListener onStoredInMailbox tradeId={} at peer {}",
|
||||
tradeId, tradingPeerNodeAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("SendMailboxMessageListener onFault tradeId={} at peer {}",
|
||||
tradeId, tradingPeerNodeAddress);
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(TxBroadcastException exception) {
|
||||
log.error("publishDelayedPayoutTx onFailure", exception);
|
||||
errorMessageHandler.handleErrorMessage(exception.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.network.p2p.DirectMessage;
|
||||
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;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class DelayedPayoutTxSignatureRequest extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] delayedPayoutTx;
|
||||
|
||||
public DelayedPayoutTxSignatureRequest(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTx) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
delayedPayoutTx);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private DelayedPayoutTxSignatureRequest(int messageVersion,
|
||||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTx) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.delayedPayoutTx = delayedPayoutTx;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setDelayedPayoutTxSignatureRequest(protobuf.DelayedPayoutTxSignatureRequest.newBuilder()
|
||||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx)))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DelayedPayoutTxSignatureRequest fromProto(protobuf.DelayedPayoutTxSignatureRequest proto, int messageVersion) {
|
||||
return new DelayedPayoutTxSignatureRequest(messageVersion,
|
||||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDelayedPayoutTx().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DelayedPayoutTxSignatureRequest{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n delayedPayoutTx=" + Utilities.bytesAsHexString(delayedPayoutTx) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.network.p2p.DirectMessage;
|
||||
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;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class DelayedPayoutTxSignatureResponse extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] delayedPayoutTxSignature;
|
||||
|
||||
public DelayedPayoutTxSignatureResponse(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTxSignature) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
delayedPayoutTxSignature);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private DelayedPayoutTxSignatureResponse(int messageVersion,
|
||||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTxSignature) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.delayedPayoutTxSignature = delayedPayoutTxSignature;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setDelayedPayoutTxSignatureResponse(protobuf.DelayedPayoutTxSignatureResponse.newBuilder()
|
||||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDelayedPayoutTxSignature(ByteString.copyFrom(delayedPayoutTxSignature))
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DelayedPayoutTxSignatureResponse fromProto(protobuf.DelayedPayoutTxSignatureResponse proto, int messageVersion) {
|
||||
return new DelayedPayoutTxSignatureResponse(messageVersion,
|
||||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDelayedPayoutTxSignature().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DelayedPayoutTxSignatureResponse{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n delayedPayoutTxSignature=" + Utilities.bytesAsHexString(delayedPayoutTxSignature) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.network.p2p.MailboxMessage;
|
||||
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;
|
||||
|
||||
// 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 DepositTxAndDelayedPayoutTxMessage extends TradeMessage implements MailboxMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] depositTx;
|
||||
private final byte[] delayedPayoutTx;
|
||||
|
||||
public DepositTxAndDelayedPayoutTxMessage(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] depositTx,
|
||||
byte[] delayedPayoutTx) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
depositTx,
|
||||
delayedPayoutTx);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private DepositTxAndDelayedPayoutTxMessage(int messageVersion,
|
||||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] depositTx,
|
||||
byte[] delayedPayoutTx) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.depositTx = depositTx;
|
||||
this.delayedPayoutTx = delayedPayoutTx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setDepositTxAndDelayedPayoutTxMessage(protobuf.DepositTxAndDelayedPayoutTxMessage.newBuilder()
|
||||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDepositTx(ByteString.copyFrom(depositTx))
|
||||
.setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx)))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DepositTxAndDelayedPayoutTxMessage fromProto(protobuf.DepositTxAndDelayedPayoutTxMessage proto, int messageVersion) {
|
||||
return new DepositTxAndDelayedPayoutTxMessage(messageVersion,
|
||||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDepositTx().toByteArray(),
|
||||
proto.getDelayedPayoutTx().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DepositTxAndDelayedPayoutTxMessage{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
|
||||
",\n delayedPayoutTx=" + Utilities.bytesAsHexString(delayedPayoutTx) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
|
||||
package bisq.core.trade.messages;
|
||||
|
||||
import bisq.network.p2p.MailboxMessage;
|
||||
import bisq.network.p2p.DirectMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
@ -28,64 +28,63 @@ import com.google.protobuf.ByteString;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
|
||||
// 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 DepositTxPublishedMessage extends TradeMessage implements MailboxMessage {
|
||||
private final byte[] depositTx;
|
||||
public final class DepositTxMessage extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] depositTx;
|
||||
|
||||
public DepositTxPublishedMessage(String tradeId,
|
||||
byte[] depositTx,
|
||||
public DepositTxMessage(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
String uid) {
|
||||
this(tradeId,
|
||||
depositTx,
|
||||
senderNodeAddress,
|
||||
byte[] depositTx) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
Version.getP2PMessageVersion());
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
depositTx);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private DepositTxPublishedMessage(String tradeId,
|
||||
byte[] depositTx,
|
||||
NodeAddress senderNodeAddress,
|
||||
private DepositTxMessage(int messageVersion,
|
||||
String uid,
|
||||
int messageVersion) {
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] depositTx) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.depositTx = depositTx;
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.depositTx = depositTx;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setDepositTxPublishedMessage(protobuf.DepositTxPublishedMessage.newBuilder()
|
||||
.setDepositTxMessage(protobuf.DepositTxMessage.newBuilder()
|
||||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setDepositTx(ByteString.copyFrom(depositTx))
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setUid(uid))
|
||||
.setDepositTx(ByteString.copyFrom(depositTx)))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DepositTxPublishedMessage fromProto(protobuf.DepositTxPublishedMessage proto, int messageVersion) {
|
||||
return new DepositTxPublishedMessage(proto.getTradeId(),
|
||||
proto.getDepositTx().toByteArray(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
public static DepositTxMessage fromProto(protobuf.DepositTxMessage proto, int messageVersion) {
|
||||
return new DepositTxMessage(messageVersion,
|
||||
proto.getUid(),
|
||||
messageVersion);
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDepositTx().toByteArray());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DepositTxPublishedMessage{" +
|
||||
"\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
|
||||
",\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n uid='" + uid + '\'' +
|
||||
return "DepositTxMessage{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import bisq.core.btc.model.RawTransactionInput;
|
||||
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;
|
||||
@ -29,7 +30,6 @@ import bisq.common.util.Utilities;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@ -41,7 +41,7 @@ import javax.annotation.Nullable;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class PayDepositRequest extends TradeMessage {
|
||||
public final class InputsForDepositTxRequest extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final long tradeAmount;
|
||||
private final long tradePrice;
|
||||
@ -60,15 +60,18 @@ public final class PayDepositRequest extends TradeMessage {
|
||||
private final String takerFeeTxId;
|
||||
private final List<NodeAddress> acceptedArbitratorNodeAddresses;
|
||||
private final List<NodeAddress> acceptedMediatorNodeAddresses;
|
||||
private final List<NodeAddress> acceptedRefundAgentNodeAddresses;
|
||||
@Nullable
|
||||
private final NodeAddress arbitratorNodeAddress;
|
||||
private final NodeAddress mediatorNodeAddress;
|
||||
private final NodeAddress refundAgentNodeAddress;
|
||||
|
||||
// added in v 0.6. can be null if we trade with an older peer
|
||||
@Nullable
|
||||
private final byte[] accountAgeWitnessSignatureOfOfferId;
|
||||
private final long currentDate;
|
||||
|
||||
public PayDepositRequest(String tradeId,
|
||||
public InputsForDepositTxRequest(String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
long tradeAmount,
|
||||
long tradePrice,
|
||||
@ -86,8 +89,10 @@ public final class PayDepositRequest extends TradeMessage {
|
||||
String takerFeeTxId,
|
||||
List<NodeAddress> acceptedArbitratorNodeAddresses,
|
||||
List<NodeAddress> acceptedMediatorNodeAddresses,
|
||||
List<NodeAddress> acceptedRefundAgentNodeAddresses,
|
||||
NodeAddress arbitratorNodeAddress,
|
||||
NodeAddress mediatorNodeAddress,
|
||||
NodeAddress refundAgentNodeAddress,
|
||||
String uid,
|
||||
int messageVersion,
|
||||
@Nullable byte[] accountAgeWitnessSignatureOfOfferId,
|
||||
@ -110,8 +115,10 @@ public final class PayDepositRequest extends TradeMessage {
|
||||
this.takerFeeTxId = takerFeeTxId;
|
||||
this.acceptedArbitratorNodeAddresses = acceptedArbitratorNodeAddresses;
|
||||
this.acceptedMediatorNodeAddresses = acceptedMediatorNodeAddresses;
|
||||
this.acceptedRefundAgentNodeAddresses = acceptedRefundAgentNodeAddresses;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
this.mediatorNodeAddress = mediatorNodeAddress;
|
||||
this.refundAgentNodeAddress = refundAgentNodeAddress;
|
||||
this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId;
|
||||
this.currentDate = currentDate;
|
||||
}
|
||||
@ -123,7 +130,7 @@ public final class PayDepositRequest extends TradeMessage {
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
protobuf.PayDepositRequest.Builder builder = protobuf.PayDepositRequest.newBuilder()
|
||||
protobuf.InputsForDepositTxRequest.Builder builder = protobuf.InputsForDepositTxRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setTradeAmount(tradeAmount)
|
||||
@ -144,18 +151,21 @@ public final class PayDepositRequest extends TradeMessage {
|
||||
.map(NodeAddress::toProtoMessage).collect(Collectors.toList()))
|
||||
.addAllAcceptedMediatorNodeAddresses(acceptedMediatorNodeAddresses.stream()
|
||||
.map(NodeAddress::toProtoMessage).collect(Collectors.toList()))
|
||||
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
|
||||
.addAllAcceptedRefundAgentNodeAddresses(acceptedRefundAgentNodeAddresses.stream()
|
||||
.map(NodeAddress::toProtoMessage).collect(Collectors.toList()))
|
||||
.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage())
|
||||
.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage())
|
||||
.setUid(uid);
|
||||
|
||||
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
|
||||
Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e)));
|
||||
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
|
||||
builder.setCurrentDate(currentDate);
|
||||
|
||||
return getNetworkEnvelopeBuilder().setPayDepositRequest(builder).build();
|
||||
return getNetworkEnvelopeBuilder().setInputsForDepositTxRequest(builder).build();
|
||||
}
|
||||
|
||||
public static PayDepositRequest fromProto(protobuf.PayDepositRequest proto,
|
||||
public static InputsForDepositTxRequest fromProto(protobuf.InputsForDepositTxRequest proto,
|
||||
CoreProtoResolver coreProtoResolver,
|
||||
int messageVersion) {
|
||||
List<RawTransactionInput> rawTransactionInputs = proto.getRawTransactionInputsList().stream()
|
||||
@ -166,8 +176,10 @@ public final class PayDepositRequest extends TradeMessage {
|
||||
.map(NodeAddress::fromProto).collect(Collectors.toList());
|
||||
List<NodeAddress> acceptedMediatorNodeAddresses = proto.getAcceptedMediatorNodeAddressesList().stream()
|
||||
.map(NodeAddress::fromProto).collect(Collectors.toList());
|
||||
List<NodeAddress> acceptedRefundAgentNodeAddresses = proto.getAcceptedRefundAgentNodeAddressesList().stream()
|
||||
.map(NodeAddress::fromProto).collect(Collectors.toList());
|
||||
|
||||
return new PayDepositRequest(proto.getTradeId(),
|
||||
return new InputsForDepositTxRequest(proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getTradeAmount(),
|
||||
proto.getTradePrice(),
|
||||
@ -185,8 +197,10 @@ public final class PayDepositRequest extends TradeMessage {
|
||||
proto.getTakerFeeTxId(),
|
||||
acceptedArbitratorNodeAddresses,
|
||||
acceptedMediatorNodeAddresses,
|
||||
acceptedRefundAgentNodeAddresses,
|
||||
NodeAddress.fromProto(proto.getArbitratorNodeAddress()),
|
||||
NodeAddress.fromProto(proto.getMediatorNodeAddress()),
|
||||
NodeAddress.fromProto(proto.getRefundAgentNodeAddress()),
|
||||
proto.getUid(),
|
||||
messageVersion,
|
||||
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()),
|
||||
@ -195,7 +209,7 @@ public final class PayDepositRequest extends TradeMessage {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PayDepositRequest{" +
|
||||
return "InputsForDepositTxRequest{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n tradeAmount=" + tradeAmount +
|
||||
",\n tradePrice=" + tradePrice +
|
||||
@ -213,11 +227,12 @@ public final class PayDepositRequest extends TradeMessage {
|
||||
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
|
||||
",\n acceptedArbitratorNodeAddresses=" + acceptedArbitratorNodeAddresses +
|
||||
",\n acceptedMediatorNodeAddresses=" + acceptedMediatorNodeAddresses +
|
||||
",\n acceptedRefundAgentNodeAddresses=" + acceptedRefundAgentNodeAddresses +
|
||||
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||
",\n uid='" + uid + '\'' +
|
||||
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
||||
",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) +
|
||||
",\n currentDate=" + new Date(currentDate) +
|
||||
",\n currentDate=" + currentDate +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ import bisq.core.btc.model.RawTransactionInput;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
|
||||
import bisq.network.p2p.MailboxMessage;
|
||||
import bisq.network.p2p.DirectMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
@ -40,12 +40,9 @@ import lombok.Value;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
// We use a MailboxMessage here because the taker has paid already the trade fee and it could be that
|
||||
// we lost connection to him but we are complete on our side. So even if the peer is offline he can
|
||||
// continue later to complete the deposit tx.
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class PublishDepositTxRequest extends TradeMessage implements MailboxMessage {
|
||||
public final class InputsForDepositTxResponse extends TradeMessage implements DirectMessage {
|
||||
private final PaymentAccountPayload makerPaymentAccountPayload;
|
||||
private final String makerAccountId;
|
||||
private final byte[] makerMultiSigPubKey;
|
||||
@ -60,8 +57,9 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
|
||||
@Nullable
|
||||
private final byte[] accountAgeWitnessSignatureOfPreparedDepositTx;
|
||||
private final long currentDate;
|
||||
private final long lockTime;
|
||||
|
||||
public PublishDepositTxRequest(String tradeId,
|
||||
public InputsForDepositTxResponse(String tradeId,
|
||||
PaymentAccountPayload makerPaymentAccountPayload,
|
||||
String makerAccountId,
|
||||
byte[] makerMultiSigPubKey,
|
||||
@ -73,7 +71,8 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
|
||||
NodeAddress senderNodeAddress,
|
||||
String uid,
|
||||
@Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
|
||||
long currentDate) {
|
||||
long currentDate,
|
||||
long lockTime) {
|
||||
this(tradeId,
|
||||
makerPaymentAccountPayload,
|
||||
makerAccountId,
|
||||
@ -87,7 +86,8 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
|
||||
uid,
|
||||
Version.getP2PMessageVersion(),
|
||||
accountAgeWitnessSignatureOfPreparedDepositTx,
|
||||
currentDate);
|
||||
currentDate,
|
||||
lockTime);
|
||||
}
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private PublishDepositTxRequest(String tradeId,
|
||||
private InputsForDepositTxResponse(String tradeId,
|
||||
PaymentAccountPayload makerPaymentAccountPayload,
|
||||
String makerAccountId,
|
||||
byte[] makerMultiSigPubKey,
|
||||
@ -108,7 +108,8 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
|
||||
String uid,
|
||||
int messageVersion,
|
||||
@Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
|
||||
long currentDate) {
|
||||
long currentDate,
|
||||
long lockTime) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
|
||||
this.makerAccountId = makerAccountId;
|
||||
@ -121,11 +122,12 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.accountAgeWitnessSignatureOfPreparedDepositTx = accountAgeWitnessSignatureOfPreparedDepositTx;
|
||||
this.currentDate = currentDate;
|
||||
this.lockTime = lockTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
final protobuf.PublishDepositTxRequest.Builder builder = protobuf.PublishDepositTxRequest.newBuilder()
|
||||
final protobuf.InputsForDepositTxResponse.Builder builder = protobuf.InputsForDepositTxResponse.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage())
|
||||
.setMakerAccountId(makerAccountId)
|
||||
@ -136,22 +138,23 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
|
||||
.setPreparedDepositTx(ByteString.copyFrom(preparedDepositTx))
|
||||
.addAllMakerInputs(makerInputs.stream().map(RawTransactionInput::toProtoMessage).collect(Collectors.toList()))
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setUid(uid);
|
||||
.setUid(uid)
|
||||
.setLockTime(lockTime);
|
||||
|
||||
Optional.ofNullable(accountAgeWitnessSignatureOfPreparedDepositTx).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfPreparedDepositTx(ByteString.copyFrom(e)));
|
||||
builder.setCurrentDate(currentDate);
|
||||
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setPublishDepositTxRequest(builder)
|
||||
.setInputsForDepositTxResponse(builder)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static PublishDepositTxRequest fromProto(protobuf.PublishDepositTxRequest proto, CoreProtoResolver coreProtoResolver, int messageVersion) {
|
||||
public static InputsForDepositTxResponse fromProto(protobuf.InputsForDepositTxResponse proto, CoreProtoResolver coreProtoResolver, int messageVersion) {
|
||||
List<RawTransactionInput> makerInputs = proto.getMakerInputsList().stream()
|
||||
.map(RawTransactionInput::fromProto)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new PublishDepositTxRequest(proto.getTradeId(),
|
||||
return new InputsForDepositTxResponse(proto.getTradeId(),
|
||||
coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()),
|
||||
proto.getMakerAccountId(),
|
||||
proto.getMakerMultiSigPubKey().toByteArray(),
|
||||
@ -164,13 +167,14 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
|
||||
proto.getUid(),
|
||||
messageVersion,
|
||||
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfPreparedDepositTx()),
|
||||
proto.getCurrentDate());
|
||||
proto.getCurrentDate(),
|
||||
proto.getLockTime());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PublishDepositTxRequest{" +
|
||||
return "InputsForDepositTxResponse{" +
|
||||
"\n makerPaymentAccountPayload=" + makerPaymentAccountPayload +
|
||||
",\n makerAccountId='" + makerAccountId + '\'' +
|
||||
",\n makerMultiSigPubKey=" + Utilities.bytesAsHexString(makerMultiSigPubKey) +
|
||||
@ -183,6 +187,7 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
|
||||
",\n uid='" + uid + '\'' +
|
||||
",\n accountAgeWitnessSignatureOfPreparedDepositTx=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfPreparedDepositTx) +
|
||||
",\n currentDate=" + new Date(currentDate) +
|
||||
",\n lockTime=" + lockTime +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.network.p2p.MailboxMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class PeerPublishedDelayedPayoutTxMessage extends TradeMessage implements MailboxMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
|
||||
public PeerPublishedDelayedPayoutTxMessage(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private PeerPublishedDelayedPayoutTxMessage(int messageVersion,
|
||||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
final protobuf.PeerPublishedDelayedPayoutTxMessage.Builder builder = protobuf.PeerPublishedDelayedPayoutTxMessage.newBuilder();
|
||||
builder.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage());
|
||||
return getNetworkEnvelopeBuilder().setPeerPublishedDelayedPayoutTxMessage(builder).build();
|
||||
}
|
||||
|
||||
public static PeerPublishedDelayedPayoutTxMessage fromProto(protobuf.PeerPublishedDelayedPayoutTxMessage proto, int messageVersion) {
|
||||
return new PeerPublishedDelayedPayoutTxMessage(messageVersion,
|
||||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PeerPublishedDelayedPayoutTxMessage{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
@ -17,7 +17,6 @@
|
||||
|
||||
package bisq.core.trade.messages;
|
||||
|
||||
import bisq.network.p2p.DirectMessage;
|
||||
import bisq.network.p2p.UidMessage;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
@ -29,7 +28,7 @@ import lombok.ToString;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
@ToString
|
||||
public abstract class TradeMessage extends NetworkEnvelope implements DirectMessage, UidMessage {
|
||||
public abstract class TradeMessage extends NetworkEnvelope implements UidMessage {
|
||||
protected final String tradeId;
|
||||
protected final String uid;
|
||||
|
||||
|
@ -19,38 +19,40 @@ package bisq.core.trade.protocol;
|
||||
|
||||
import bisq.core.trade.BuyerAsMakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.DepositTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PayDepositRequest;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
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.PublishTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
||||
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.BuyerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSignPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.network.p2p.MailboxMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol, MakerProtocol {
|
||||
private final BuyerAsMakerTrade buyerAsMakerTrade;
|
||||
@ -68,10 +70,10 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
Trade.Phase phase = trade.getState().getPhase();
|
||||
if (phase == Trade.Phase.TAKER_FEE_PUBLISHED) {
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
|
||||
() -> handleTaskRunnerSuccess("MakerSetupDepositTxListener"),
|
||||
() -> handleTaskRunnerSuccess("BuyerSetupDepositTxListener"),
|
||||
this::handleTaskRunnerFault);
|
||||
|
||||
taskRunner.addTasks(MakerSetupDepositTxListener.class);
|
||||
taskRunner.addTasks(BuyerSetupDepositTxListener.class);
|
||||
taskRunner.run();
|
||||
} else if (trade.isFiatSent() && !trade.isPayoutPublished()) {
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
|
||||
@ -89,23 +91,13 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
|
||||
this.trade = trade;
|
||||
public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
|
||||
super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
|
||||
|
||||
if (networkEnvelope instanceof MailboxMessage) {
|
||||
MailboxMessage mailboxMessage = (MailboxMessage) networkEnvelope;
|
||||
NodeAddress peerNodeAddress = mailboxMessage.getSenderNodeAddress();
|
||||
if (networkEnvelope instanceof TradeMessage) {
|
||||
TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
|
||||
log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
|
||||
tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
|
||||
if (tradeMessage instanceof DepositTxPublishedMessage)
|
||||
handle((DepositTxPublishedMessage) tradeMessage, peerNodeAddress);
|
||||
else if (tradeMessage instanceof PayoutTxPublishedMessage)
|
||||
if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
|
||||
handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
|
||||
} else if (tradeMessage instanceof PayoutTxPublishedMessage) {
|
||||
handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
|
||||
else
|
||||
log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,11 +107,10 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void handleTakeOfferRequest(TradeMessage tradeMessage,
|
||||
public void handleTakeOfferRequest(InputsForDepositTxRequest tradeMessage,
|
||||
NodeAddress peerNodeAddress,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Validator.checkTradeId(processModel.getOfferId(), tradeMessage);
|
||||
checkArgument(tradeMessage instanceof PayDepositRequest);
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
|
||||
|
||||
@ -130,15 +121,16 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
handleTaskRunnerFault(errorMessage);
|
||||
});
|
||||
taskRunner.addTasks(
|
||||
MakerProcessPayDepositRequest.class,
|
||||
MakerProcessesInputsForDepositTxRequest.class,
|
||||
ApplyFilter.class,
|
||||
MakerVerifyTakerAccount.class,
|
||||
VerifyPeersAccountAgeWitness.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
MakerSetsLockTime.class,
|
||||
MakerCreateAndSignContract.class,
|
||||
BuyerAsMakerCreatesAndSignsDepositTx.class,
|
||||
MakerSetupDepositTxListener.class,
|
||||
MakerSendPublishDepositTxRequest.class
|
||||
BuyerSetupDepositTxListener.class,
|
||||
BuyerAsMakerSendsInputsForDepositTxResponse.class
|
||||
);
|
||||
// We don't use a timeout here because if the DepositTxPublishedMessage does not arrive we
|
||||
// get the deposit tx set at MakerSetupDepositTxListener once it is seen in the bitcoin network
|
||||
@ -150,19 +142,35 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
// Incoming message handling
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void handle(DepositTxPublishedMessage tradeMessage, NodeAddress peerNodeAddress) {
|
||||
private void handle(DelayedPayoutTxSignatureRequest tradeMessage, NodeAddress peerNodeAddress) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
|
||||
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(tradeMessage, "handle DepositTxPublishedMessage");
|
||||
handleTaskRunnerSuccess(tradeMessage, "handle DelayedPayoutTxSignatureRequest");
|
||||
},
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
taskRunner.addTasks(
|
||||
MakerProcessDepositTxPublishedMessage.class,
|
||||
MakerVerifyTakerAccount.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
BuyerProcessDelayedPayoutTxSignatureRequest.class,
|
||||
BuyerSignsDelayedPayoutTx.class,
|
||||
BuyerSendsDelayedPayoutTxSignatureResponse.class
|
||||
);
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
private void handle(DepositTxAndDelayedPayoutTxMessage tradeMessage, NodeAddress peerNodeAddress) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
|
||||
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(tradeMessage, "handle DepositTxAndDelayedPayoutTxMessage");
|
||||
},
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
taskRunner.addTasks(
|
||||
BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
|
||||
BuyerVerifiesDelayedPayoutTx.class,
|
||||
PublishTradeStatistics.class
|
||||
);
|
||||
taskRunner.run();
|
||||
@ -191,7 +199,7 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
ApplyFilter.class,
|
||||
MakerVerifyTakerAccount.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
BuyerAsMakerSignPayoutTx.class,
|
||||
BuyerSignPayoutTx.class,
|
||||
BuyerSendCounterCurrencyTransferStartedMessage.class,
|
||||
BuyerSetupPayoutTxListener.class
|
||||
);
|
||||
@ -232,8 +240,10 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
log.info("Received {} from {} with tradeId {} and uid {}",
|
||||
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
|
||||
|
||||
if (tradeMessage instanceof DepositTxPublishedMessage) {
|
||||
handle((DepositTxPublishedMessage) tradeMessage, sender);
|
||||
if (tradeMessage instanceof DelayedPayoutTxSignatureRequest) {
|
||||
handle((DelayedPayoutTxSignatureRequest) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
|
||||
handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof PayoutTxPublishedMessage) {
|
||||
handle((PayoutTxPublishedMessage) tradeMessage, sender);
|
||||
}
|
||||
|
@ -18,38 +18,47 @@
|
||||
package bisq.core.trade.protocol;
|
||||
|
||||
|
||||
import bisq.core.offer.Offer;
|
||||
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.InputsForDepositTxResponse;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PublishDepositTxRequest;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
|
||||
import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSignPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs;
|
||||
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignAndPublishDepositTx;
|
||||
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.TakerProcessPublishDepositTxRequest;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendDepositTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendPayDepositRequest;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerAccount;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
|
||||
|
||||
import bisq.network.p2p.MailboxMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol, TakerProtocol {
|
||||
private final BuyerAsTakerTrade buyerAsTakerTrade;
|
||||
@ -64,9 +73,18 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
|
||||
this.buyerAsTakerTrade = trade;
|
||||
|
||||
processModel.getTradingPeer().setPubKeyRing(trade.getOffer().getPubKeyRing());
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
|
||||
|
||||
if (trade.isFiatSent() && !trade.isPayoutPublished()) {
|
||||
Trade.Phase phase = trade.getState().getPhase();
|
||||
if (phase == Trade.Phase.TAKER_FEE_PUBLISHED) {
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
|
||||
() -> handleTaskRunnerSuccess("BuyerSetupDepositTxListener"),
|
||||
this::handleTaskRunnerFault);
|
||||
|
||||
taskRunner.addTasks(BuyerSetupDepositTxListener.class);
|
||||
taskRunner.run();
|
||||
} else if (trade.isFiatSent() && !trade.isPayoutPublished()) {
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
|
||||
() -> handleTaskRunnerSuccess("BuyerSetupPayoutTxListener"),
|
||||
this::handleTaskRunnerFault);
|
||||
@ -82,22 +100,13 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
|
||||
this.trade = trade;
|
||||
public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
|
||||
super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
|
||||
|
||||
if (networkEnvelope instanceof MailboxMessage) {
|
||||
final NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
|
||||
if (networkEnvelope instanceof TradeMessage) {
|
||||
TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
|
||||
log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
|
||||
tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
|
||||
if (tradeMessage instanceof PublishDepositTxRequest)
|
||||
handle((PublishDepositTxRequest) tradeMessage, peerNodeAddress);
|
||||
else if (tradeMessage instanceof PayoutTxPublishedMessage) {
|
||||
if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
|
||||
handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
|
||||
} else if (tradeMessage instanceof PayoutTxPublishedMessage) {
|
||||
handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
|
||||
} else
|
||||
log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +126,7 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
CreateTakerFeeTx.class,
|
||||
BuyerAsTakerCreatesDepositTxInputs.class,
|
||||
TakerSendPayDepositRequest.class
|
||||
TakerSendInputsForDepositTxRequest.class
|
||||
);
|
||||
|
||||
//TODO if peer does get an error he does not respond and all we get is the timeout now knowing why it failed.
|
||||
@ -131,7 +140,7 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
// Incoming message handling
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
|
||||
private void handle(InputsForDepositTxResponse tradeMessage, NodeAddress sender) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
@ -142,15 +151,50 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
},
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
taskRunner.addTasks(
|
||||
TakerProcessPublishDepositTxRequest.class,
|
||||
TakerProcessesInputsForDepositTxResponse.class,
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerAccount.class,
|
||||
VerifyPeersAccountAgeWitness.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
TakerVerifyAndSignContract.class,
|
||||
TakerPublishFeeTx.class,
|
||||
BuyerAsTakerSignAndPublishDepositTx.class,
|
||||
TakerSendDepositTxPublishedMessage.class,
|
||||
BuyerAsTakerSignsDepositTx.class,
|
||||
BuyerSetupDepositTxListener.class,
|
||||
BuyerAsTakerSendsDepositTxMessage.class
|
||||
);
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
private void handle(DelayedPayoutTxSignatureRequest tradeMessage, NodeAddress sender) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(tradeMessage, "handle DelayedPayoutTxSignatureRequest");
|
||||
},
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
taskRunner.addTasks(
|
||||
BuyerProcessDelayedPayoutTxSignatureRequest.class,
|
||||
BuyerSignsDelayedPayoutTx.class,
|
||||
BuyerSendsDelayedPayoutTxSignatureResponse.class
|
||||
);
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
|
||||
private void handle(DepositTxAndDelayedPayoutTxMessage tradeMessage, NodeAddress peerNodeAddress) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
|
||||
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(tradeMessage, "handle DepositTxAndDelayedPayoutTxMessage");
|
||||
},
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
taskRunner.addTasks(
|
||||
BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
|
||||
BuyerVerifiesDelayedPayoutTx.class,
|
||||
PublishTradeStatistics.class
|
||||
);
|
||||
taskRunner.run();
|
||||
@ -180,7 +224,7 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerAccount.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
BuyerAsMakerSignPayoutTx.class,
|
||||
BuyerSignPayoutTx.class,
|
||||
BuyerSendCounterCurrencyTransferStartedMessage.class,
|
||||
BuyerSetupPayoutTxListener.class
|
||||
);
|
||||
@ -221,8 +265,12 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
log.info("Received {} from {} with tradeId {} and uid {}",
|
||||
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
|
||||
|
||||
if (tradeMessage instanceof PublishDepositTxRequest) {
|
||||
handle((PublishDepositTxRequest) tradeMessage, sender);
|
||||
if (tradeMessage instanceof InputsForDepositTxResponse) {
|
||||
handle((InputsForDepositTxResponse) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof DelayedPayoutTxSignatureRequest) {
|
||||
handle((DelayedPayoutTxSignatureRequest) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
|
||||
handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof PayoutTxPublishedMessage) {
|
||||
handle((PayoutTxPublishedMessage) tradeMessage, sender);
|
||||
}
|
||||
|
@ -18,12 +18,12 @@
|
||||
package bisq.core.trade.protocol;
|
||||
|
||||
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
|
||||
public interface MakerProtocol {
|
||||
void handleTakeOfferRequest(TradeMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
|
||||
void handleTakeOfferRequest(InputsForDepositTxRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import bisq.core.btc.model.RawTransactionInput;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.network.MessageState;
|
||||
import bisq.core.offer.Offer;
|
||||
@ -31,6 +32,7 @@ import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
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.MakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
@ -69,6 +71,10 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
|
||||
// persist them.
|
||||
//todo clean up older fields as well to make most transient
|
||||
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class ProcessModel implements Model, PersistablePayload {
|
||||
@ -78,6 +84,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
transient private BtcWalletService btcWalletService;
|
||||
transient private BsqWalletService bsqWalletService;
|
||||
transient private TradeWalletService tradeWalletService;
|
||||
transient private DaoFacade daoFacade;
|
||||
transient private Offer offer;
|
||||
transient private User user;
|
||||
transient private FilterManager filterManager;
|
||||
@ -85,6 +92,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
transient private TradeStatisticsManager tradeStatisticsManager;
|
||||
transient private ArbitratorManager arbitratorManager;
|
||||
transient private MediatorManager mediatorManager;
|
||||
transient private RefundAgentManager refundAgentManager;
|
||||
transient private KeyRing keyRing;
|
||||
transient private P2PService p2PService;
|
||||
transient private ReferralIdService referralIdService;
|
||||
@ -96,32 +104,29 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
@Setter
|
||||
transient private DecryptedMessageWithPubKey decryptedMessageWithPubKey;
|
||||
|
||||
// Added in v1.2.0
|
||||
@Setter
|
||||
@Nullable
|
||||
transient private byte[] delayedPayoutTxSignature;
|
||||
@Setter
|
||||
@Nullable
|
||||
transient private Transaction preparedDelayedPayoutTx;
|
||||
|
||||
// Persistable Immutable (only set by PB)
|
||||
@Setter
|
||||
// Persistable Immutable (private setter only used by PB method)
|
||||
private TradingPeer tradingPeer = new TradingPeer();
|
||||
@Setter
|
||||
private String offerId;
|
||||
@Setter
|
||||
private String accountId;
|
||||
@Setter
|
||||
private PubKeyRing pubKeyRing;
|
||||
|
||||
// Persistable Mutable
|
||||
@Nullable
|
||||
@Setter
|
||||
@Setter()
|
||||
private String takeOfferFeeTxId;
|
||||
@Nullable
|
||||
@Setter
|
||||
private byte[] payoutTxSignature;
|
||||
@Nullable
|
||||
@Setter
|
||||
private List<NodeAddress> takerAcceptedArbitratorNodeAddresses;
|
||||
@Nullable
|
||||
@Setter
|
||||
private List<NodeAddress> takerAcceptedMediatorNodeAddresses;
|
||||
@Nullable
|
||||
@Setter
|
||||
private byte[] preparedDepositTx;
|
||||
@Nullable
|
||||
@Setter
|
||||
@ -182,14 +187,13 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
|
||||
Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId);
|
||||
Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature)));
|
||||
Optional.ofNullable(takerAcceptedArbitratorNodeAddresses).ifPresent(e -> builder.addAllTakerAcceptedArbitratorNodeAddresses(ProtoUtil.collectionToProto(takerAcceptedArbitratorNodeAddresses)));
|
||||
Optional.ofNullable(takerAcceptedMediatorNodeAddresses).ifPresent(e -> builder.addAllTakerAcceptedMediatorNodeAddresses(ProtoUtil.collectionToProto(takerAcceptedMediatorNodeAddresses)));
|
||||
Optional.ofNullable(preparedDepositTx).ifPresent(e -> builder.setPreparedDepositTx(ByteString.copyFrom(preparedDepositTx)));
|
||||
Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs)));
|
||||
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)));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ -208,14 +212,6 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
// nullable
|
||||
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
|
||||
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
|
||||
List<NodeAddress> takerAcceptedArbitratorNodeAddresses = proto.getTakerAcceptedArbitratorNodeAddressesList().isEmpty() ?
|
||||
null : proto.getTakerAcceptedArbitratorNodeAddressesList().stream()
|
||||
.map(NodeAddress::fromProto).collect(Collectors.toList());
|
||||
List<NodeAddress> takerAcceptedMediatorNodeAddresses = proto.getTakerAcceptedMediatorNodeAddressesList().isEmpty() ?
|
||||
null : proto.getTakerAcceptedMediatorNodeAddressesList().stream()
|
||||
.map(NodeAddress::fromProto).collect(Collectors.toList());
|
||||
processModel.setTakerAcceptedArbitratorNodeAddresses(takerAcceptedArbitratorNodeAddresses);
|
||||
processModel.setTakerAcceptedMediatorNodeAddresses(takerAcceptedMediatorNodeAddresses);
|
||||
processModel.setPreparedDepositTx(ProtoUtil.byteArrayOrNullFromProto(proto.getPreparedDepositTx()));
|
||||
List<RawTransactionInput> rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ?
|
||||
null : proto.getRawTransactionInputsList().stream()
|
||||
@ -243,6 +239,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
BtcWalletService walletService,
|
||||
BsqWalletService bsqWalletService,
|
||||
TradeWalletService tradeWalletService,
|
||||
DaoFacade daoFacade,
|
||||
ReferralIdService referralIdService,
|
||||
User user,
|
||||
FilterManager filterManager,
|
||||
@ -250,6 +247,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
ArbitratorManager arbitratorManager,
|
||||
MediatorManager mediatorManager,
|
||||
RefundAgentManager refundAgentManager,
|
||||
KeyRing keyRing,
|
||||
boolean useSavingsWallet,
|
||||
Coin fundsNeededForTrade) {
|
||||
@ -259,6 +257,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
this.btcWalletService = walletService;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.daoFacade = daoFacade;
|
||||
this.referralIdService = referralIdService;
|
||||
this.user = user;
|
||||
this.filterManager = filterManager;
|
||||
@ -266,6 +265,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.refundAgentManager = refundAgentManager;
|
||||
this.keyRing = keyRing;
|
||||
this.p2PService = p2PService;
|
||||
this.useSavingsWallet = useSavingsWallet;
|
||||
@ -339,4 +339,20 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
public void setPaymentStartedMessageState(MessageState paymentStartedMessageStateProperty) {
|
||||
this.paymentStartedMessageStateProperty.set(paymentStartedMessageStateProperty);
|
||||
}
|
||||
|
||||
private void setTradingPeer(TradingPeer tradingPeer) {
|
||||
this.tradingPeer = tradingPeer;
|
||||
}
|
||||
|
||||
private void setOfferId(String offerId) {
|
||||
this.offerId = offerId;
|
||||
}
|
||||
|
||||
private void setAccountId(String accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
private void setPubKeyRing(PubKeyRing pubKeyRing) {
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
}
|
||||
}
|
||||
|
@ -21,38 +21,42 @@ 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.DepositTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PayDepositRequest;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||
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.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
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.SellerVerifiesPeersAccountAge;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesAndSignsDepositTx;
|
||||
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.MailboxMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@Slf4j
|
||||
public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtocol, MakerProtocol {
|
||||
private final SellerAsMakerTrade sellerAsMakerTrade;
|
||||
@ -73,7 +77,6 @@ public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
() -> handleTaskRunnerSuccess("MakerSetupDepositTxListener"),
|
||||
this::handleTaskRunnerFault);
|
||||
|
||||
taskRunner.addTasks(MakerSetupDepositTxListener.class);
|
||||
taskRunner.run();
|
||||
}
|
||||
}
|
||||
@ -84,23 +87,11 @@ public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
|
||||
this.trade = trade;
|
||||
public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
|
||||
super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
|
||||
|
||||
if (networkEnvelope instanceof MailboxMessage) {
|
||||
NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
|
||||
if (networkEnvelope instanceof TradeMessage) {
|
||||
TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
|
||||
log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
|
||||
tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
|
||||
|
||||
if (tradeMessage instanceof DepositTxPublishedMessage)
|
||||
handle((DepositTxPublishedMessage) tradeMessage, peerNodeAddress);
|
||||
else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage)
|
||||
if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
|
||||
handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
|
||||
else
|
||||
log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,11 +101,10 @@ public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void handleTakeOfferRequest(TradeMessage tradeMessage,
|
||||
public void handleTakeOfferRequest(InputsForDepositTxRequest tradeMessage,
|
||||
NodeAddress sender,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Validator.checkTradeId(processModel.getOfferId(), tradeMessage);
|
||||
checkArgument(tradeMessage instanceof PayDepositRequest);
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
@ -126,20 +116,17 @@ public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
});
|
||||
|
||||
taskRunner.addTasks(
|
||||
MakerProcessPayDepositRequest.class,
|
||||
MakerProcessesInputsForDepositTxRequest.class,
|
||||
ApplyFilter.class,
|
||||
MakerVerifyTakerAccount.class,
|
||||
VerifyPeersAccountAgeWitness.class,
|
||||
SellerVerifiesPeersAccountAge.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
MakerSetsLockTime.class,
|
||||
MakerCreateAndSignContract.class,
|
||||
SellerAsMakerCreatesAndSignsDepositTx.class,
|
||||
MakerSetupDepositTxListener.class,
|
||||
MakerSendPublishDepositTxRequest.class
|
||||
SellerAsMakerCreatesUnsignedDepositTx.class,
|
||||
SellerAsMakerSendsInputsForDepositTxResponse.class
|
||||
);
|
||||
|
||||
// We don't start a timeout because if we don't receive the peers DepositTxPublishedMessage we still
|
||||
// will get set the deposit tx in MakerSetupDepositTxListener once seen in the network
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
@ -148,7 +135,7 @@ public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
// Incoming message handling
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void handle(DepositTxPublishedMessage tradeMessage, NodeAddress sender) {
|
||||
protected void handle(DepositTxMessage tradeMessage, NodeAddress sender) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
@ -159,10 +146,32 @@ public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
|
||||
taskRunner.addTasks(
|
||||
MakerProcessDepositTxPublishedMessage.class,
|
||||
PublishTradeStatistics.class,
|
||||
MakerVerifyTakerAccount.class,
|
||||
MakerVerifyTakerFeePayment.class
|
||||
SellerAsMakerProcessDepositTxMessage.class,
|
||||
SellerAsMakerFinalizesDepositTx.class,
|
||||
SellerCreatesDelayedPayoutTx.class,
|
||||
SellerSendDelayedPayoutTxSignatureRequest.class
|
||||
);
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
private void handle(DelayedPayoutTxSignatureResponse tradeMessage, NodeAddress sender) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsMakerTrade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(tradeMessage, "PublishDepositTxRequest");
|
||||
},
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
|
||||
taskRunner.addTasks(
|
||||
SellerProcessDelayedPayoutTxSignatureResponse.class,
|
||||
SellerSignsDelayedPayoutTx.class,
|
||||
SellerFinalizesDelayedPayoutTx.class,
|
||||
SellerPublishesDepositTx.class,
|
||||
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
PublishTradeStatistics.class
|
||||
);
|
||||
taskRunner.run();
|
||||
}
|
||||
@ -255,8 +264,10 @@ public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
log.info("Received {} from {} with tradeId {} and uid {}",
|
||||
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
|
||||
|
||||
if (tradeMessage instanceof DepositTxPublishedMessage) {
|
||||
handle((DepositTxPublishedMessage) tradeMessage, sender);
|
||||
if (tradeMessage instanceof DepositTxMessage) {
|
||||
handle((DepositTxMessage) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof DelayedPayoutTxSignatureResponse) {
|
||||
handle((DelayedPayoutTxSignatureResponse) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
|
||||
handle((CounterCurrencyTransferStartedMessage) tradeMessage, sender);
|
||||
}
|
||||
|
@ -18,39 +18,46 @@
|
||||
package bisq.core.trade.protocol;
|
||||
|
||||
|
||||
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.PublishDepositTxRequest;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||
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.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
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.SellerVerifiesPeersAccountAge;
|
||||
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.SellerAsTakerSignAndPublishDepositTx;
|
||||
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.TakerProcessPublishDepositTxRequest;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendDepositTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendPayDepositRequest;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerAccount;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
|
||||
|
||||
import bisq.network.p2p.MailboxMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtocol, TakerProtocol {
|
||||
private final SellerAsTakerTrade sellerAsTakerTrade;
|
||||
@ -65,7 +72,8 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
|
||||
this.sellerAsTakerTrade = trade;
|
||||
|
||||
processModel.getTradingPeer().setPubKeyRing(trade.getOffer().getPubKeyRing());
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
|
||||
}
|
||||
|
||||
|
||||
@ -74,22 +82,11 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
|
||||
this.trade = trade;
|
||||
public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
|
||||
super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
|
||||
|
||||
if (networkEnvelope instanceof MailboxMessage) {
|
||||
NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
|
||||
if (networkEnvelope instanceof TradeMessage) {
|
||||
TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
|
||||
log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
|
||||
tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
|
||||
if (tradeMessage instanceof PublishDepositTxRequest)
|
||||
handle((PublishDepositTxRequest) tradeMessage, peerNodeAddress);
|
||||
else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage)
|
||||
if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
|
||||
handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
|
||||
else
|
||||
log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,11 +106,9 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
CreateTakerFeeTx.class,
|
||||
SellerAsTakerCreatesDepositTxInputs.class,
|
||||
TakerSendPayDepositRequest.class
|
||||
TakerSendInputsForDepositTxRequest.class
|
||||
);
|
||||
|
||||
//TODO if peer does get an error he does not respond and all we get is the timeout now knowing why it failed.
|
||||
// We should add an error message the peer sends us in such cases.
|
||||
startTimeout();
|
||||
taskRunner.run();
|
||||
}
|
||||
@ -123,7 +118,7 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
// Incoming message handling
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
|
||||
private void handle(InputsForDepositTxResponse tradeMessage, NodeAddress sender) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
@ -135,16 +130,37 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
|
||||
taskRunner.addTasks(
|
||||
TakerProcessPublishDepositTxRequest.class,
|
||||
TakerProcessesInputsForDepositTxResponse.class,
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerAccount.class,
|
||||
VerifyPeersAccountAgeWitness.class,
|
||||
SellerVerifiesPeersAccountAge.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
TakerVerifyAndSignContract.class,
|
||||
TakerPublishFeeTx.class,
|
||||
SellerAsTakerSignAndPublishDepositTx.class,
|
||||
TakerSendDepositTxPublishedMessage.class,
|
||||
SellerAsTakerSignsDepositTx.class,
|
||||
SellerCreatesDelayedPayoutTx.class,
|
||||
SellerSendDelayedPayoutTxSignatureRequest.class
|
||||
);
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
private void handle(DelayedPayoutTxSignatureResponse tradeMessage, NodeAddress sender) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsTakerTrade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(tradeMessage, "PublishDepositTxRequest");
|
||||
},
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
|
||||
taskRunner.addTasks(
|
||||
SellerProcessDelayedPayoutTxSignatureResponse.class,
|
||||
SellerSignsDelayedPayoutTx.class,
|
||||
SellerFinalizesDelayedPayoutTx.class,
|
||||
SellerPublishesDepositTx.class,
|
||||
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
PublishTradeStatistics.class
|
||||
);
|
||||
taskRunner.run();
|
||||
@ -238,8 +254,10 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
|
||||
log.info("Received {} from {} with tradeId {} and uid {}",
|
||||
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
|
||||
|
||||
if (tradeMessage instanceof PublishDepositTxRequest) {
|
||||
handle((PublishDepositTxRequest) tradeMessage, sender);
|
||||
if (tradeMessage instanceof InputsForDepositTxResponse) {
|
||||
handle((InputsForDepositTxResponse) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof DelayedPayoutTxSignatureResponse) {
|
||||
handle((DelayedPayoutTxSignatureResponse) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
|
||||
handle((CounterCurrencyTransferStartedMessage) tradeMessage, sender);
|
||||
}
|
||||
|
@ -21,11 +21,13 @@ import bisq.core.trade.MakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
||||
import bisq.core.trade.messages.PayDepositRequest;
|
||||
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.mediation.BroadcastMediatedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.mediation.FinalizeMediatedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.mediation.ProcessMediatedPayoutSignatureMessage;
|
||||
@ -59,6 +61,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.core.util.Validator.nonEmptyStringOf;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public abstract class TradeProtocol {
|
||||
@ -80,13 +83,13 @@ public abstract class TradeProtocol {
|
||||
PublicKey signaturePubKey = decryptedMessageWithPubKey.getSignaturePubKey();
|
||||
if (tradingPeerPubKeyRing != null && signaturePubKey.equals(tradingPeerPubKeyRing.getSignaturePubKey())) {
|
||||
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
|
||||
log.trace("handleNewMessage: message = {} from {}", networkEnvelope.getClass().getSimpleName(), peersNodeAddress);
|
||||
if (networkEnvelope instanceof TradeMessage) {
|
||||
TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
|
||||
nonEmptyStringOf(tradeMessage.getTradeId());
|
||||
|
||||
if (tradeMessage.getTradeId().equals(processModel.getOfferId()))
|
||||
if (tradeMessage.getTradeId().equals(processModel.getOfferId())) {
|
||||
doHandleDecryptedMessage(tradeMessage, peersNodeAddress);
|
||||
}
|
||||
} else if (networkEnvelope instanceof AckMessage) {
|
||||
AckMessage ackMessage = (AckMessage) networkEnvelope;
|
||||
if (ackMessage.getSourceType() == AckMessageSourceType.TRADE_MESSAGE &&
|
||||
@ -110,7 +113,7 @@ public abstract class TradeProtocol {
|
||||
|
||||
stateChangeListener = (observable, oldValue, newValue) -> {
|
||||
if (newValue.getPhase() == Trade.Phase.TAKER_FEE_PUBLISHED && trade instanceof MakerTrade)
|
||||
processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer());
|
||||
processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer()));
|
||||
};
|
||||
trade.stateProperty().addListener(stateChangeListener);
|
||||
}
|
||||
@ -206,6 +209,26 @@ public abstract class TradeProtocol {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Peer has published the delayed payout tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void handle(PeerPublishedDelayedPayoutTxMessage tradeMessage, NodeAddress sender) {
|
||||
processModel.setTradeMessage(tradeMessage);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
|
||||
() -> handleTaskRunnerSuccess(tradeMessage, "PeerPublishedDelayedPayoutTxMessage"),
|
||||
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
|
||||
|
||||
taskRunner.addTasks(
|
||||
//todo
|
||||
ProcessPeerPublishedDelayedPayoutTxMessage.class
|
||||
);
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Dispatcher
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -215,6 +238,8 @@ public abstract class TradeProtocol {
|
||||
handle((MediatedPayoutTxSignatureMessage) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof MediatedPayoutTxPublishedMessage) {
|
||||
handle((MediatedPayoutTxPublishedMessage) tradeMessage, sender);
|
||||
} else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) {
|
||||
handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, sender);
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,10 +250,6 @@ public abstract class TradeProtocol {
|
||||
|
||||
public void completed() {
|
||||
cleanup();
|
||||
|
||||
// We only removed earlier the listener here, but then we migth have dangling trades after faults...
|
||||
// so lets remove it at cleanup
|
||||
//processModel.getP2PService().removeDecryptedDirectMessageListener(decryptedDirectMessageListener);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
@ -241,28 +262,33 @@ public abstract class TradeProtocol {
|
||||
|
||||
public void applyMailboxMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Trade trade) {
|
||||
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
|
||||
log.debug("applyMailboxMessage {}", networkEnvelope);
|
||||
if (processModel.getTradingPeer().getPubKeyRing() != null &&
|
||||
decryptedMessageWithPubKey.getSignaturePubKey().equals(processModel.getTradingPeer().getPubKeyRing().getSignaturePubKey())) {
|
||||
processModel.setDecryptedMessageWithPubKey(decryptedMessageWithPubKey);
|
||||
doApplyMailboxMessage(networkEnvelope, trade);
|
||||
|
||||
// This is just a quick fix for the missing handling of the mediation MailboxMessages.
|
||||
// With the new trade protocol that will be refactored further with using doApplyMailboxMessage...
|
||||
if (networkEnvelope instanceof MailboxMessage && networkEnvelope instanceof TradeMessage) {
|
||||
NodeAddress sender = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
|
||||
if (networkEnvelope instanceof MediatedPayoutTxSignatureMessage) {
|
||||
handle((MediatedPayoutTxSignatureMessage) networkEnvelope, sender);
|
||||
} else if (networkEnvelope instanceof MediatedPayoutTxPublishedMessage) {
|
||||
handle((MediatedPayoutTxPublishedMessage) networkEnvelope, sender);
|
||||
}
|
||||
this.trade = trade;
|
||||
TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
|
||||
NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
|
||||
doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
|
||||
}
|
||||
} else {
|
||||
log.error("SignaturePubKey in message does not match the SignaturePubKey we have stored to that trading peer.");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade);
|
||||
protected void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
|
||||
log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
|
||||
tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
|
||||
|
||||
if (tradeMessage instanceof MediatedPayoutTxSignatureMessage) {
|
||||
handle((MediatedPayoutTxSignatureMessage) tradeMessage, peerNodeAddress);
|
||||
} else if (tradeMessage instanceof MediatedPayoutTxPublishedMessage) {
|
||||
handle((MediatedPayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
|
||||
} else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) {
|
||||
handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
|
||||
}
|
||||
}
|
||||
|
||||
protected void startTimeout() {
|
||||
stopTimeout();
|
||||
@ -318,7 +344,7 @@ public abstract class TradeProtocol {
|
||||
sourceUid = ((MailboxMessage) tradeMessage).getUid();
|
||||
} else {
|
||||
// For direct msg we don't have a mandatory uid so we need to cast to get it
|
||||
if (tradeMessage instanceof PayDepositRequest) {
|
||||
if (tradeMessage instanceof InputsForDepositTxRequest) {
|
||||
sourceUid = tradeMessage.getUid();
|
||||
}
|
||||
}
|
||||
|
@ -38,10 +38,23 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
|
||||
// persist them.
|
||||
//todo clean up older fields as well to make most transient
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Setter
|
||||
public final class TradingPeer implements PersistablePayload {
|
||||
// Transient/Mutable
|
||||
// Added in v1.2.0
|
||||
@Setter
|
||||
@Nullable
|
||||
transient private byte[] delayedPayoutTxSignature;
|
||||
@Setter
|
||||
@Nullable
|
||||
transient private byte[] preparedDepositTx;
|
||||
|
||||
// Persistable mutable
|
||||
@Nullable
|
||||
private String accountId;
|
||||
@Nullable
|
||||
@ -75,6 +88,7 @@ public final class TradingPeer implements PersistablePayload {
|
||||
@Nullable
|
||||
private byte[] mediatedPayoutTxSignature;
|
||||
|
||||
|
||||
public TradingPeer() {
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.protocol.tasks;
|
||||
|
||||
import bisq.core.btc.wallet.WalletService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask {
|
||||
@SuppressWarnings({"unused"})
|
||||
public ProcessPeerPublishedDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
PeerPublishedDelayedPayoutTxMessage message = (PeerPublishedDelayedPayoutTxMessage) processModel.getTradeMessage();
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
checkNotNull(message);
|
||||
|
||||
// update to the latest peer address of our peer if the message is correct
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
processModel.removeMailboxMessageAfterProcessing(trade);
|
||||
|
||||
// We add the tx to our wallet.
|
||||
Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
|
||||
WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet());
|
||||
|
||||
// todo trade.setState
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
@ -66,9 +66,9 @@ public class PublishTradeStatistics extends TradeTask {
|
||||
|
||||
NodeAddress mediatorNodeAddress = trade.getMediatorNodeAddress();
|
||||
if (mediatorNodeAddress != null) {
|
||||
// The first 4 chars are sufficient to identify an arbitrator.
|
||||
// The first 4 chars are sufficient to identify an mediator.
|
||||
// For testing with regtest/localhost we use the full address as its localhost and would result in
|
||||
// same values for multiple arbitrators.
|
||||
// same values for multiple mediators.
|
||||
NetworkNode networkNode = model.getProcessModel().getP2PService().getNetworkNode();
|
||||
String address = networkNode instanceof TorNetworkNode ?
|
||||
mediatorNodeAddress.getFullAddress().substring(0, 4) :
|
||||
|
@ -54,10 +54,10 @@ public abstract class SetupPayoutTxListener extends TradeTask {
|
||||
runInterceptHook();
|
||||
if (!trade.isPayoutPublished()) {
|
||||
BtcWalletService walletService = processModel.getBtcWalletService();
|
||||
final String id = processModel.getOffer().getId();
|
||||
String id = processModel.getOffer().getId();
|
||||
Address address = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddress();
|
||||
|
||||
final TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
|
||||
TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
|
||||
if (isInNetwork(confidence)) {
|
||||
applyConfidence(confidence);
|
||||
} else {
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerProcessDelayedPayoutTxSignatureRequest extends TradeTask {
|
||||
@SuppressWarnings({"unused"})
|
||||
public BuyerProcessDelayedPayoutTxSignatureRequest(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
DelayedPayoutTxSignatureRequest message = (DelayedPayoutTxSignatureRequest) processModel.getTradeMessage();
|
||||
checkNotNull(message);
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
byte[] delayedPayoutTxAsBytes = checkNotNull(message.getDelayedPayoutTx());
|
||||
Transaction preparedDelayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxAsBytes);
|
||||
processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx);
|
||||
|
||||
// update to the latest peer address of our peer if the message is correct
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,12 +15,13 @@
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.protocol.tasks.maker;
|
||||
package bisq.core.trade.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.WalletService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.DepositTxPublishedMessage;
|
||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
@ -34,9 +35,9 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class MakerProcessDepositTxPublishedMessage extends TradeTask {
|
||||
public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask {
|
||||
@SuppressWarnings({"unused"})
|
||||
public MakerProcessDepositTxPublishedMessage(TaskRunner taskHandler, Trade trade) {
|
||||
public BuyerProcessDepositTxAndDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@ -44,18 +45,22 @@ public class MakerProcessDepositTxPublishedMessage extends TradeTask {
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
log.debug("current trade state " + trade.getState());
|
||||
DepositTxPublishedMessage message = (DepositTxPublishedMessage) processModel.getTradeMessage();
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
DepositTxAndDelayedPayoutTxMessage message = (DepositTxAndDelayedPayoutTxMessage) processModel.getTradeMessage();
|
||||
checkNotNull(message);
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
checkArgument(message.getDepositTx() != null);
|
||||
|
||||
// To access tx confidence we need to add that tx into our wallet.
|
||||
Transaction txFromSerializedTx = processModel.getBtcWalletService().getTxFromSerializedTx(message.getDepositTx());
|
||||
Transaction depositTx = processModel.getBtcWalletService().getTxFromSerializedTx(message.getDepositTx());
|
||||
// update with full tx
|
||||
Transaction walletTx = processModel.getTradeWalletService().addTxToWallet(txFromSerializedTx);
|
||||
trade.setDepositTx(walletTx);
|
||||
BtcWalletService.printTx("depositTx received from peer", walletTx);
|
||||
Transaction committedDepositTx = WalletService.maybeAddSelfTxToWallet(depositTx, processModel.getBtcWalletService().getWallet());
|
||||
trade.applyDepositTx(committedDepositTx);
|
||||
BtcWalletService.printTx("depositTx received from peer", committedDepositTx);
|
||||
|
||||
// To access tx confidence we need to add that tx into our wallet.
|
||||
byte[] delayedPayoutTxBytes = message.getDelayedPayoutTx();
|
||||
trade.applyDelayedPayoutTxBytes(delayedPayoutTxBytes);
|
||||
BtcWalletService.printTx("delayedPayoutTx received from peer", trade.getDelayedPayoutTx());
|
||||
|
||||
// update to the latest peer address of our peer if the message is correct
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
@ -63,8 +68,8 @@ public class MakerProcessDepositTxPublishedMessage extends TradeTask {
|
||||
processModel.removeMailboxMessageAfterProcessing(trade);
|
||||
|
||||
// If we got already the confirmation we don't want to apply an earlier state
|
||||
if (trade.getState() != Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK)
|
||||
trade.setState(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG);
|
||||
if (trade.getState() != Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK)
|
||||
trade.setState(Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG);
|
||||
|
||||
processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
|
@ -19,6 +19,7 @@ package bisq.core.trade.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.WalletService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
@ -54,9 +55,9 @@ public class BuyerProcessPayoutTxPublishedMessage extends TradeTask {
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
||||
if (trade.getPayoutTx() == null) {
|
||||
Transaction walletTx = processModel.getTradeWalletService().addTxToWallet(message.getPayoutTx());
|
||||
trade.setPayoutTx(walletTx);
|
||||
BtcWalletService.printTx("payoutTx received from peer", walletTx);
|
||||
Transaction committedPayoutTx = WalletService.maybeAddNetworkTxToWallet(message.getPayoutTx(), processModel.getBtcWalletService().getWallet());
|
||||
trade.setPayoutTx(committedPayoutTx);
|
||||
BtcWalletService.printTx("payoutTx received from peer", committedPayoutTx);
|
||||
|
||||
trade.setState(Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.MULTI_SIG);
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.SendDirectMessageListener;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerSendsDelayedPayoutTxSignatureResponse extends TradeTask {
|
||||
@SuppressWarnings({"unused"})
|
||||
public BuyerSendsDelayedPayoutTxSignatureResponse(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
byte[] delayedPayoutTxSignature = checkNotNull(processModel.getDelayedPayoutTxSignature());
|
||||
DelayedPayoutTxSignatureResponse message = new DelayedPayoutTxSignatureResponse(UUID.randomUUID().toString(),
|
||||
processModel.getOfferId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
delayedPayoutTxSignature);
|
||||
|
||||
// todo trade.setState
|
||||
|
||||
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||
peersNodeAddress,
|
||||
processModel.getTradingPeer().getPubKeyRing(),
|
||||
message,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at peer {}. tradeId={}, uid={}",
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
// todo trade.setState
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
|
||||
// todo trade.setState
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
failed(errorMessage);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.protocol.tasks.maker;
|
||||
package bisq.core.trade.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.btc.listeners.AddressConfidenceListener;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
@ -39,13 +39,13 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@Slf4j
|
||||
public class MakerSetupDepositTxListener extends TradeTask {
|
||||
public class BuyerSetupDepositTxListener extends TradeTask {
|
||||
// Use instance fields to not get eaten up by the GC
|
||||
private Subscription tradeStateSubscription;
|
||||
private AddressConfidenceListener confidenceListener;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public MakerSetupDepositTxListener(TaskRunner taskHandler, Trade trade) {
|
||||
public BuyerSetupDepositTxListener(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@ -94,9 +94,9 @@ public class MakerSetupDepositTxListener extends TradeTask {
|
||||
private void applyConfidence(TransactionConfidence confidence) {
|
||||
if (trade.getDepositTx() == null) {
|
||||
Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash());
|
||||
trade.setDepositTx(walletTx);
|
||||
trade.applyDepositTx(walletTx);
|
||||
BtcWalletService.printTx("depositTx received from network", walletTx);
|
||||
trade.setState(Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK);
|
||||
trade.setState(Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK);
|
||||
} else {
|
||||
log.info("We got the deposit tx already set from MakerProcessDepositTxPublishedMessage. tradeId={}, state={}", trade.getId(), trade.getState());
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.protocol.tasks.buyer_as_maker;
|
||||
package bisq.core.trade.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
@ -38,10 +38,10 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerAsMakerSignPayoutTx extends TradeTask {
|
||||
public class BuyerSignPayoutTx extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public BuyerAsMakerSignPayoutTx(TaskRunner taskHandler, Trade trade) {
|
||||
public BuyerSignPayoutTx(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ public class BuyerAsMakerSignPayoutTx extends TradeTask {
|
||||
checkArgument(Arrays.equals(buyerMultiSigPubKey,
|
||||
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
|
||||
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
final byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
|
||||
byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
|
||||
|
||||
byte[] payoutTxSignature = processModel.getTradeWalletService().buyerSignsPayoutTx(
|
||||
trade.getDepositTx(),
|
||||
@ -79,8 +79,7 @@ public class BuyerAsMakerSignPayoutTx extends TradeTask {
|
||||
sellerPayoutAddressString,
|
||||
buyerMultiSigKeyPair,
|
||||
buyerMultiSigPubKey,
|
||||
sellerMultiSigPubKey,
|
||||
trade.getArbitratorBtcPubKey());
|
||||
sellerMultiSigPubKey);
|
||||
processModel.setPayoutTxSignature(payoutTxSignature);
|
||||
|
||||
complete();
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerSignsDelayedPayoutTx extends TradeTask {
|
||||
@SuppressWarnings({"unused"})
|
||||
public BuyerSignsDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
|
||||
BtcWalletService btcWalletService = processModel.getBtcWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
|
||||
byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
DeterministicKey myMultiSigKeyPair = btcWalletService.getMultiSigKeyPair(id, buyerMultiSigPubKey);
|
||||
|
||||
checkArgument(Arrays.equals(buyerMultiSigPubKey,
|
||||
btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
|
||||
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
|
||||
byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(preparedDelayedPayoutTx, myMultiSigKeyPair, buyerMultiSigPubKey, sellerMultiSigPubKey);
|
||||
processModel.setDelayedPayoutTxSignature(delayedPayoutTxSignature);
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,22 +15,23 @@
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.protocol.tasks.seller;
|
||||
package bisq.core.trade.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeRestrictions;
|
||||
import bisq.core.offer.OfferRestrictions;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SellerVerifiesPeersAccountAge extends TradeTask {
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerVerifiesDelayedPayoutTx extends TradeTask {
|
||||
@SuppressWarnings({"unused"})
|
||||
public SellerVerifiesPeersAccountAge(TaskRunner taskHandler, Trade trade) {
|
||||
public BuyerVerifiesDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@ -39,19 +40,12 @@ public class SellerVerifiesPeersAccountAge extends TradeTask {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
boolean isTradeRisky = OfferRestrictions.isTradeRisky(trade);
|
||||
boolean isTradePeersAccountAgeImmature = AccountAgeRestrictions.isTradePeersAccountAgeImmature(
|
||||
processModel.getAccountAgeWitnessService(), trade);
|
||||
log.debug("SellerVerifiesPeersAccountAge isOfferRisky={} isTradePeersAccountAgeImmature={}",
|
||||
isTradeRisky, isTradePeersAccountAgeImmature);
|
||||
if (isTradeRisky &&
|
||||
isTradePeersAccountAgeImmature) {
|
||||
failed("Violation of security restrictions:\n" +
|
||||
" - The peer's account was created after March 1st 2019\n" +
|
||||
" - The trade amount is above 0.01 BTC\n" +
|
||||
" - The payment method for that offer is considered risky for bank chargebacks\n");
|
||||
} else {
|
||||
Transaction depositTx = checkNotNull(trade.getDepositTx());
|
||||
Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
|
||||
if (processModel.getTradeWalletService().verifiesDepositTxAndDelayedPayoutTx(depositTx, delayedPayoutTx)) {
|
||||
complete();
|
||||
} else {
|
||||
failed("DelayedPayoutTx is not spending correctly depositTx");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
@ -57,52 +57,40 @@ public class BuyerAsMakerCreatesAndSignsDepositTx extends TradeTask {
|
||||
BtcWalletService walletService = processModel.getBtcWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
TradingPeer tradingPeer = processModel.getTradingPeer();
|
||||
final Offer offer = trade.getOffer();
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
|
||||
// params
|
||||
final boolean makerIsBuyer = true;
|
||||
|
||||
final byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
|
||||
byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson()));
|
||||
trade.setContractHash(contractHash);
|
||||
log.debug("\n\n------------------------------------------------------------\n"
|
||||
+ "Contract as json\n"
|
||||
+ trade.getContractAsJson()
|
||||
+ "\n------------------------------------------------------------\n");
|
||||
|
||||
final Coin makerInputAmount = offer.getBuyerSecurityDeposit();
|
||||
Coin makerInputAmount = offer.getBuyerSecurityDeposit();
|
||||
Optional<AddressEntry> addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
|
||||
AddressEntry makerMultiSigAddressEntry = addressEntryOptional.get();
|
||||
makerMultiSigAddressEntry.setCoinLockedInMultiSig(makerInputAmount);
|
||||
walletService.saveAddressEntryList();
|
||||
|
||||
final Coin msOutputAmount = makerInputAmount
|
||||
Coin msOutputAmount = makerInputAmount
|
||||
.add(trade.getTxFee())
|
||||
.add(offer.getSellerSecurityDeposit())
|
||||
.add(trade.getTradeAmount());
|
||||
|
||||
final List<RawTransactionInput> takerRawTransactionInputs = tradingPeer.getRawTransactionInputs();
|
||||
|
||||
final long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
|
||||
|
||||
final String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
|
||||
|
||||
final Address makerAddress = walletService.getOrCreateAddressEntry(id,
|
||||
AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
|
||||
final Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
|
||||
|
||||
final byte[] buyerPubKey = processModel.getMyMultiSigPubKey();
|
||||
List<RawTransactionInput> takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
|
||||
long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
|
||||
String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
|
||||
Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
|
||||
byte[] buyerPubKey = processModel.getMyMultiSigPubKey();
|
||||
byte[] sellerPubKey = tradingPeer.getMultiSigPubKey();
|
||||
checkArgument(Arrays.equals(buyerPubKey,
|
||||
makerMultiSigAddressEntry.getPubKey()),
|
||||
"buyerPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
|
||||
final byte[] sellerPubKey = tradingPeer.getMultiSigPubKey();
|
||||
|
||||
final byte[] arbitratorBtcPubKey = trade.getArbitratorBtcPubKey();
|
||||
|
||||
PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().makerCreatesAndSignsDepositTx(
|
||||
makerIsBuyer,
|
||||
PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().buyerAsMakerCreatesAndSignsDepositTx(
|
||||
contractHash,
|
||||
makerInputAmount,
|
||||
msOutputAmount,
|
||||
@ -112,8 +100,7 @@ public class BuyerAsMakerCreatesAndSignsDepositTx extends TradeTask {
|
||||
makerAddress,
|
||||
makerChangeAddress,
|
||||
buyerPubKey,
|
||||
sellerPubKey,
|
||||
arbitratorBtcPubKey);
|
||||
sellerPubKey);
|
||||
|
||||
processModel.setPreparedDepositTx(result.depositTransaction);
|
||||
processModel.setRawTransactionInputs(result.rawMakerInputs);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user