ui: Start describing xmr bid states.

This commit is contained in:
tecnovert 2021-10-18 20:48:48 +02:00
parent b99fc48e89
commit bf00f80b4d
No known key found for this signature in database
GPG key ID: 8ED6D8750C4E3F93
15 changed files with 542 additions and 373 deletions

View file

@ -23,7 +23,6 @@ import sqlalchemy as sa
import collections
import concurrent.futures
from enum import IntEnum, auto
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.orm.session import close_all_sessions
@ -93,314 +92,30 @@ from .explorers import (
ExplorerBitAps,
ExplorerChainz,
)
from .types import (
SEQUENCE_LOCK_BLOCKS,
SEQUENCE_LOCK_TIME,
ABS_LOCK_BLOCKS,
ABS_LOCK_TIME)
import basicswap.config as cfg
import basicswap.network as bsn
import basicswap.protocols.atomic_swap_1 as atomic_swap_1
class MessageTypes(IntEnum):
OFFER = auto()
BID = auto()
BID_ACCEPT = auto()
XMR_OFFER = auto()
XMR_BID_FL = auto()
XMR_BID_SPLIT = auto()
XMR_BID_ACCEPT_LF = auto()
XMR_BID_TXN_SIGS_FL = auto()
XMR_BID_LOCK_SPEND_TX_LF = auto()
XMR_BID_LOCK_RELEASE_LF = auto()
OFFER_REVOKE = auto()
class SwapTypes(IntEnum):
SELLER_FIRST = auto()
BUYER_FIRST = auto()
SELLER_FIRST_2MSG = auto()
BUYER_FIRST_2MSG = auto()
XMR_SWAP = auto()
class OfferStates(IntEnum):
OFFER_SENT = auto()
OFFER_RECEIVED = auto()
OFFER_ABANDONED = auto()
class BidStates(IntEnum):
BID_SENT = auto()
BID_RECEIVING = auto() # Partially received
BID_RECEIVED = auto()
BID_RECEIVING_ACC = auto() # Partially received accept message
BID_ACCEPTED = auto() # BidAcceptMessage received/sent
SWAP_INITIATED = auto() # Initiate txn validated
SWAP_PARTICIPATING = auto() # Participate txn validated
SWAP_COMPLETED = auto() # All swap txns spent
XMR_SWAP_SCRIPT_COIN_LOCKED = auto()
XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX = auto()
XMR_SWAP_NOSCRIPT_COIN_LOCKED = auto()
XMR_SWAP_LOCK_RELEASED = auto()
XMR_SWAP_SCRIPT_TX_REDEEMED = auto()
XMR_SWAP_NOSCRIPT_TX_REDEEMED = auto()
XMR_SWAP_NOSCRIPT_TX_RECOVERED = auto()
XMR_SWAP_FAILED_REFUNDED = auto()
XMR_SWAP_FAILED_SWIPED = auto()
XMR_SWAP_FAILED = auto()
SWAP_DELAYING = auto()
SWAP_TIMEDOUT = auto()
BID_ABANDONED = auto() # Bid will no longer be processed
BID_ERROR = auto() # An error occurred
BID_STALLED_FOR_TEST = auto()
class TxStates(IntEnum):
TX_NONE = auto()
TX_SENT = auto()
TX_CONFIRMED = auto()
TX_REDEEMED = auto()
TX_REFUNDED = auto()
class TxTypes(IntEnum):
ITX = auto()
PTX = auto()
ITX_REDEEM = auto()
ITX_REFUND = auto()
PTX_REDEEM = auto()
PTX_REFUND = auto()
XMR_SWAP_A_LOCK = auto()
XMR_SWAP_A_LOCK_SPEND = auto()
XMR_SWAP_A_LOCK_REFUND = auto()
XMR_SWAP_A_LOCK_REFUND_SPEND = auto()
XMR_SWAP_A_LOCK_REFUND_SWIPE = auto()
XMR_SWAP_B_LOCK = auto()
class EventTypes(IntEnum):
ACCEPT_BID = auto()
ACCEPT_XMR_BID = auto()
SIGN_XMR_SWAP_LOCK_TX_A = auto()
SEND_XMR_SWAP_LOCK_TX_A = auto()
SEND_XMR_SWAP_LOCK_TX_B = auto()
SEND_XMR_LOCK_RELEASE = auto()
REDEEM_XMR_SWAP_LOCK_TX_A = auto() # Follower
REDEEM_XMR_SWAP_LOCK_TX_B = auto() # Leader
RECOVER_XMR_SWAP_LOCK_TX_B = auto()
class EventLogTypes(IntEnum):
FAILED_TX_B_LOCK_PUBLISH = auto()
LOCK_TX_A_PUBLISHED = auto()
LOCK_TX_B_PUBLISHED = auto()
FAILED_TX_B_SPEND = auto()
LOCK_TX_A_SEEN = auto()
LOCK_TX_A_CONFIRMED = auto()
LOCK_TX_B_SEEN = auto()
LOCK_TX_B_CONFIRMED = auto()
DEBUG_TWEAK_APPLIED = auto()
FAILED_TX_B_REFUND = auto()
LOCK_TX_B_INVALID = auto()
LOCK_TX_A_REFUND_TX_PUBLISHED = auto()
LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED = auto()
LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED = auto()
LOCK_TX_B_REFUND_TX_PUBLISHED = auto()
SYSTEM_WARNING = auto()
LOCK_TX_A_SPEND_TX_PUBLISHED = auto()
LOCK_TX_B_SPEND_TX_PUBLISHED = auto()
class XmrSplitMsgTypes(IntEnum):
BID = auto()
BID_ACCEPT = auto()
class DebugTypes(IntEnum):
BID_STOP_AFTER_COIN_A_LOCK = auto()
BID_DONT_SPEND_COIN_A_LOCK_REFUND = auto()
CREATE_INVALID_COIN_B_LOCK = auto()
BUYER_STOP_AFTER_ITX = auto()
MAKE_INVALID_PTX = auto()
def strOfferState(state):
if state == OfferStates.OFFER_SENT:
return 'Sent'
if state == OfferStates.OFFER_RECEIVED:
return 'Received'
if state == OfferStates.OFFER_ABANDONED:
return 'Abandoned'
return 'Unknown'
def strBidState(state):
if state == BidStates.BID_SENT:
return 'Sent'
if state == BidStates.BID_RECEIVING:
return 'Receiving'
if state == BidStates.BID_RECEIVING_ACC:
return 'Receiving accept'
if state == BidStates.BID_RECEIVED:
return 'Received'
if state == BidStates.BID_ACCEPTED:
return 'Accepted'
if state == BidStates.SWAP_INITIATED:
return 'Initiated'
if state == BidStates.SWAP_PARTICIPATING:
return 'Participating'
if state == BidStates.SWAP_COMPLETED:
return 'Completed'
if state == BidStates.SWAP_TIMEDOUT:
return 'Timed-out'
if state == BidStates.BID_ABANDONED:
return 'Abandoned'
if state == BidStates.BID_STALLED_FOR_TEST:
return 'Stalled (debug)'
if state == BidStates.BID_ERROR:
return 'Error'
if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
return 'Script coin locked'
if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
return 'Script coin spend tx valid'
if state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED:
return 'Scriptless coin locked'
if state == BidStates.XMR_SWAP_LOCK_RELEASED:
return 'Script coin lock released'
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
return 'Script tx redeemed'
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
return 'Scriptless tx redeemed'
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED:
return 'Scriptless tx recovered'
if state == BidStates.XMR_SWAP_FAILED_REFUNDED:
return 'Failed, refunded'
if state == BidStates.XMR_SWAP_FAILED_SWIPED:
return 'Failed, swiped'
if state == BidStates.XMR_SWAP_FAILED:
return 'Failed'
if state == BidStates.SWAP_DELAYING:
return 'Delaying'
return 'Unknown'
def strTxState(state):
if state == TxStates.TX_NONE:
return 'None'
if state == TxStates.TX_SENT:
return 'Sent'
if state == TxStates.TX_CONFIRMED:
return 'Confirmed'
if state == TxStates.TX_REDEEMED:
return 'Redeemed'
if state == TxStates.TX_REFUNDED:
return 'Refunded'
return 'Unknown'
def strTxType(tx_type):
if tx_type == TxTypes.XMR_SWAP_A_LOCK:
return 'Chain A Lock Tx'
if tx_type == TxTypes.XMR_SWAP_A_LOCK_SPEND:
return 'Chain A Lock Spend Tx'
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND:
return 'Chain A Lock Refund Tx'
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND:
return 'Chain A Lock Refund Spend Tx'
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE:
return 'Chain A Lock Refund Swipe Tx'
if tx_type == TxTypes.XMR_SWAP_B_LOCK:
return 'Chain B Lock Tx'
return 'Unknown'
def getLockName(lock_type):
if lock_type == SEQUENCE_LOCK_BLOCKS:
return 'Sequence lock, blocks'
if lock_type == SEQUENCE_LOCK_TIME:
return 'Sequence lock, time'
if lock_type == ABS_LOCK_BLOCKS:
return 'blocks'
if lock_type == ABS_LOCK_TIME:
return 'time'
def describeEventEntry(event_type, event_msg):
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
return 'Failed to publish lock tx B'
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
return 'Failed to publish lock tx B'
if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED:
return 'Lock tx A published'
if event_type == EventLogTypes.LOCK_TX_B_PUBLISHED:
return 'Lock tx B published'
if event_type == EventLogTypes.FAILED_TX_B_SPEND:
return 'Failed to publish lock tx B spend'
if event_type == EventLogTypes.LOCK_TX_A_SEEN:
return 'Lock tx A seen in chain'
if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED:
return 'Lock tx A confirmed in chain'
if event_type == EventLogTypes.LOCK_TX_B_SEEN:
return 'Lock tx B seen in chain'
if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED:
return 'Lock tx B confirmed in chain'
if event_type == EventLogTypes.DEBUG_TWEAK_APPLIED:
return 'Debug tweak applied ' + event_msg
if event_type == EventLogTypes.FAILED_TX_B_REFUND:
return 'Failed to publish lock tx B refund'
if event_type == EventLogTypes.LOCK_TX_B_INVALID:
return 'Detected invalid lock Tx B'
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED:
return 'Lock tx A refund tx published'
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED:
return 'Lock tx A refund spend tx published'
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED:
return 'Lock tx A refund swipe tx published'
if event_type == EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED:
return 'Lock tx B refund tx published'
if event_type == EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED:
return 'Lock tx A spend tx published'
if event_type == EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED:
return 'Lock tx B spend tx published'
if event_type == EventLogTypes.SYSTEM_WARNING:
return 'Warning: ' + event_msg
def getVoutByAddress(txjs, p2sh):
for o in txjs['vout']:
try:
if p2sh in o['scriptPubKey']['addresses']:
return o['n']
except Exception:
pass
raise ValueError('Address output not found in txn')
def getVoutByP2WSH(txjs, p2wsh_hex):
for o in txjs['vout']:
try:
if p2wsh_hex == o['scriptPubKey']['hex']:
return o['n']
except Exception:
pass
raise ValueError('P2WSH output not found in txn')
def replaceAddrPrefix(addr, coin_type, chain_name, addr_type='pubkey_address'):
return encodeAddress(bytes((chainparams[coin_type][chain_name][addr_type],)) + decodeAddress(addr)[1:])
def getOfferProofOfFundsHash(offer_msg, offer_addr):
# TODO: Hash must not include proof_of_funds sig if it exists in offer_msg
h = hashlib.sha256()
h.update(offer_addr.encode('utf-8'))
offer_bytes = offer_msg.SerializeToString()
h.update(offer_bytes)
return h.digest()
from .basicswap_util import (
SEQUENCE_LOCK_TIME,
ABS_LOCK_BLOCKS,
ABS_LOCK_TIME,
MessageTypes,
SwapTypes,
OfferStates,
BidStates,
TxStates,
TxTypes,
EventTypes,
EventLogTypes,
XmrSplitMsgTypes,
DebugTypes,
strBidState,
describeEventEntry,
getVoutByAddress,
getVoutByP2WSH,
replaceAddrPrefix,
getOfferProofOfFundsHash,
getLastBidState)
def threadPollChainState(swap_client, coin_type):
@ -2213,7 +1928,12 @@ class BasicSwap(BaseApp):
assert(bid), 'Bid not found: {}.'.format(bid_id.hex())
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex())
assert(bid.expire_at > now), 'Bid expired'
assert(bid.state == BidStates.BID_RECEIVED), 'Wrong bid state: {}'.format(str(BidStates(bid.state)))
last_bid_state = bid.state
if last_bid_state == BidStates.SWAP_DELAYING:
last_bid_state = getLastBidState(bid.states)
assert(last_bid_state == BidStates.BID_RECEIVED), 'Wrong bid state: {}'.format(str(BidStates(last_bid_state)))
offer, xmr_offer = self.getXmrOffer(bid.offer_id)
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
@ -3989,7 +3709,6 @@ class BasicSwap(BaseApp):
self.log.info('Received valid bid %s for xmr offer %s', bid.bid_id.hex(), bid.offer_id.hex())
bid.setState(BidStates.BID_RECEIVED)
self.saveBidInSession(bid.bid_id, bid, session, xmr_swap)
# Auto accept bid if set and no other non-abandoned bid for this order exists
if offer.auto_accept_bids:
@ -4001,6 +3720,9 @@ class BasicSwap(BaseApp):
delay = random.randrange(self.min_delay_event, self.max_delay_event)
self.log.info('Auto accepting xmr bid %s in %d seconds', bid.bid_id.hex(), delay)
self.createEventInSession(delay, EventTypes.ACCEPT_XMR_BID, bid.bid_id, session)
bid.setState(BidStates.SWAP_DELAYING)
self.saveBidInSession(bid.bid_id, bid, session, xmr_swap)
def receiveXmrBidAccept(self, bid, session):
# Follower receiving MSG1F and MSG2F

338
basicswap/basicswap_util.py Normal file
View file

@ -0,0 +1,338 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import struct
import hashlib
from enum import IntEnum, auto
from .util import (
encodeAddress,
decodeAddress,
)
from .chainparams import (
chainparams,
)
SEQUENCE_LOCK_BLOCKS = 1
SEQUENCE_LOCK_TIME = 2
ABS_LOCK_BLOCKS = 3
ABS_LOCK_TIME = 4
class MessageTypes(IntEnum):
OFFER = auto()
BID = auto()
BID_ACCEPT = auto()
XMR_OFFER = auto()
XMR_BID_FL = auto()
XMR_BID_SPLIT = auto()
XMR_BID_ACCEPT_LF = auto()
XMR_BID_TXN_SIGS_FL = auto()
XMR_BID_LOCK_SPEND_TX_LF = auto()
XMR_BID_LOCK_RELEASE_LF = auto()
OFFER_REVOKE = auto()
class SwapTypes(IntEnum):
SELLER_FIRST = auto()
BUYER_FIRST = auto()
SELLER_FIRST_2MSG = auto()
BUYER_FIRST_2MSG = auto()
XMR_SWAP = auto()
class OfferStates(IntEnum):
OFFER_SENT = auto()
OFFER_RECEIVED = auto()
OFFER_ABANDONED = auto()
class BidStates(IntEnum):
BID_SENT = auto()
BID_RECEIVING = auto() # Partially received
BID_RECEIVED = auto()
BID_RECEIVING_ACC = auto() # Partially received accept message
BID_ACCEPTED = auto() # BidAcceptMessage received/sent
SWAP_INITIATED = auto() # Initiate txn validated
SWAP_PARTICIPATING = auto() # Participate txn validated
SWAP_COMPLETED = auto() # All swap txns spent
XMR_SWAP_SCRIPT_COIN_LOCKED = auto()
XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX = auto()
XMR_SWAP_NOSCRIPT_COIN_LOCKED = auto()
XMR_SWAP_LOCK_RELEASED = auto()
XMR_SWAP_SCRIPT_TX_REDEEMED = auto()
XMR_SWAP_NOSCRIPT_TX_REDEEMED = auto()
XMR_SWAP_NOSCRIPT_TX_RECOVERED = auto()
XMR_SWAP_FAILED_REFUNDED = auto()
XMR_SWAP_FAILED_SWIPED = auto()
XMR_SWAP_FAILED = auto()
SWAP_DELAYING = auto()
SWAP_TIMEDOUT = auto()
BID_ABANDONED = auto() # Bid will no longer be processed
BID_ERROR = auto() # An error occurred
BID_STALLED_FOR_TEST = auto()
BID_STATE_UNKNOWN = auto()
class TxStates(IntEnum):
TX_NONE = auto()
TX_SENT = auto()
TX_CONFIRMED = auto()
TX_REDEEMED = auto()
TX_REFUNDED = auto()
class TxTypes(IntEnum):
ITX = auto()
PTX = auto()
ITX_REDEEM = auto()
ITX_REFUND = auto()
PTX_REDEEM = auto()
PTX_REFUND = auto()
XMR_SWAP_A_LOCK = auto()
XMR_SWAP_A_LOCK_SPEND = auto()
XMR_SWAP_A_LOCK_REFUND = auto()
XMR_SWAP_A_LOCK_REFUND_SPEND = auto()
XMR_SWAP_A_LOCK_REFUND_SWIPE = auto()
XMR_SWAP_B_LOCK = auto()
class EventTypes(IntEnum):
ACCEPT_BID = auto()
ACCEPT_XMR_BID = auto()
SIGN_XMR_SWAP_LOCK_TX_A = auto()
SEND_XMR_SWAP_LOCK_TX_A = auto()
SEND_XMR_SWAP_LOCK_TX_B = auto()
SEND_XMR_LOCK_RELEASE = auto()
REDEEM_XMR_SWAP_LOCK_TX_A = auto() # Follower
REDEEM_XMR_SWAP_LOCK_TX_B = auto() # Leader
RECOVER_XMR_SWAP_LOCK_TX_B = auto()
class EventLogTypes(IntEnum):
FAILED_TX_B_LOCK_PUBLISH = auto()
LOCK_TX_A_PUBLISHED = auto()
LOCK_TX_B_PUBLISHED = auto()
FAILED_TX_B_SPEND = auto()
LOCK_TX_A_SEEN = auto()
LOCK_TX_A_CONFIRMED = auto()
LOCK_TX_B_SEEN = auto()
LOCK_TX_B_CONFIRMED = auto()
DEBUG_TWEAK_APPLIED = auto()
FAILED_TX_B_REFUND = auto()
LOCK_TX_B_INVALID = auto()
LOCK_TX_A_REFUND_TX_PUBLISHED = auto()
LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED = auto()
LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED = auto()
LOCK_TX_B_REFUND_TX_PUBLISHED = auto()
SYSTEM_WARNING = auto()
LOCK_TX_A_SPEND_TX_PUBLISHED = auto()
LOCK_TX_B_SPEND_TX_PUBLISHED = auto()
class XmrSplitMsgTypes(IntEnum):
BID = auto()
BID_ACCEPT = auto()
class DebugTypes(IntEnum):
BID_STOP_AFTER_COIN_A_LOCK = auto()
BID_DONT_SPEND_COIN_A_LOCK_REFUND = auto()
CREATE_INVALID_COIN_B_LOCK = auto()
BUYER_STOP_AFTER_ITX = auto()
MAKE_INVALID_PTX = auto()
def strOfferState(state):
if state == OfferStates.OFFER_SENT:
return 'Sent'
if state == OfferStates.OFFER_RECEIVED:
return 'Received'
if state == OfferStates.OFFER_ABANDONED:
return 'Abandoned'
return 'Unknown'
def strBidState(state):
if state == BidStates.BID_SENT:
return 'Sent'
if state == BidStates.BID_RECEIVING:
return 'Receiving'
if state == BidStates.BID_RECEIVING_ACC:
return 'Receiving accept'
if state == BidStates.BID_RECEIVED:
return 'Received'
if state == BidStates.BID_ACCEPTED:
return 'Accepted'
if state == BidStates.SWAP_INITIATED:
return 'Initiated'
if state == BidStates.SWAP_PARTICIPATING:
return 'Participating'
if state == BidStates.SWAP_COMPLETED:
return 'Completed'
if state == BidStates.SWAP_TIMEDOUT:
return 'Timed-out'
if state == BidStates.BID_ABANDONED:
return 'Abandoned'
if state == BidStates.BID_STALLED_FOR_TEST:
return 'Stalled (debug)'
if state == BidStates.BID_ERROR:
return 'Error'
if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
return 'Script coin locked'
if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
return 'Script coin spend tx valid'
if state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED:
return 'Scriptless coin locked'
if state == BidStates.XMR_SWAP_LOCK_RELEASED:
return 'Script coin lock released'
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
return 'Script tx redeemed'
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
return 'Scriptless tx redeemed'
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED:
return 'Scriptless tx recovered'
if state == BidStates.XMR_SWAP_FAILED_REFUNDED:
return 'Failed, refunded'
if state == BidStates.XMR_SWAP_FAILED_SWIPED:
return 'Failed, swiped'
if state == BidStates.XMR_SWAP_FAILED:
return 'Failed'
if state == BidStates.SWAP_DELAYING:
return 'Delaying'
return 'Unknown'
def strTxState(state):
if state == TxStates.TX_NONE:
return 'None'
if state == TxStates.TX_SENT:
return 'Sent'
if state == TxStates.TX_CONFIRMED:
return 'Confirmed'
if state == TxStates.TX_REDEEMED:
return 'Redeemed'
if state == TxStates.TX_REFUNDED:
return 'Refunded'
return 'Unknown'
def strTxType(tx_type):
if tx_type == TxTypes.XMR_SWAP_A_LOCK:
return 'Chain A Lock Tx'
if tx_type == TxTypes.XMR_SWAP_A_LOCK_SPEND:
return 'Chain A Lock Spend Tx'
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND:
return 'Chain A Lock Refund Tx'
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND:
return 'Chain A Lock Refund Spend Tx'
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE:
return 'Chain A Lock Refund Swipe Tx'
if tx_type == TxTypes.XMR_SWAP_B_LOCK:
return 'Chain B Lock Tx'
return 'Unknown'
def getLockName(lock_type):
if lock_type == SEQUENCE_LOCK_BLOCKS:
return 'Sequence lock, blocks'
if lock_type == SEQUENCE_LOCK_TIME:
return 'Sequence lock, time'
if lock_type == ABS_LOCK_BLOCKS:
return 'blocks'
if lock_type == ABS_LOCK_TIME:
return 'time'
def describeEventEntry(event_type, event_msg):
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
return 'Failed to publish lock tx B'
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
return 'Failed to publish lock tx B'
if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED:
return 'Lock tx A published'
if event_type == EventLogTypes.LOCK_TX_B_PUBLISHED:
return 'Lock tx B published'
if event_type == EventLogTypes.FAILED_TX_B_SPEND:
return 'Failed to publish lock tx B spend'
if event_type == EventLogTypes.LOCK_TX_A_SEEN:
return 'Lock tx A seen in chain'
if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED:
return 'Lock tx A confirmed in chain'
if event_type == EventLogTypes.LOCK_TX_B_SEEN:
return 'Lock tx B seen in chain'
if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED:
return 'Lock tx B confirmed in chain'
if event_type == EventLogTypes.DEBUG_TWEAK_APPLIED:
return 'Debug tweak applied ' + event_msg
if event_type == EventLogTypes.FAILED_TX_B_REFUND:
return 'Failed to publish lock tx B refund'
if event_type == EventLogTypes.LOCK_TX_B_INVALID:
return 'Detected invalid lock Tx B'
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED:
return 'Lock tx A refund tx published'
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED:
return 'Lock tx A refund spend tx published'
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED:
return 'Lock tx A refund swipe tx published'
if event_type == EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED:
return 'Lock tx B refund tx published'
if event_type == EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED:
return 'Lock tx A spend tx published'
if event_type == EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED:
return 'Lock tx B spend tx published'
if event_type == EventLogTypes.SYSTEM_WARNING:
return 'Warning: ' + event_msg
def getVoutByAddress(txjs, p2sh):
for o in txjs['vout']:
try:
if p2sh in o['scriptPubKey']['addresses']:
return o['n']
except Exception:
pass
raise ValueError('Address output not found in txn')
def getVoutByP2WSH(txjs, p2wsh_hex):
for o in txjs['vout']:
try:
if p2wsh_hex == o['scriptPubKey']['hex']:
return o['n']
except Exception:
pass
raise ValueError('P2WSH output not found in txn')
def replaceAddrPrefix(addr, coin_type, chain_name, addr_type='pubkey_address'):
return encodeAddress(bytes((chainparams[coin_type][chain_name][addr_type],)) + decodeAddress(addr)[1:])
def getOfferProofOfFundsHash(offer_msg, offer_addr):
# TODO: Hash must not include proof_of_funds sig if it exists in offer_msg
h = hashlib.sha256()
h.update(offer_addr.encode('utf-8'))
offer_bytes = offer_msg.SerializeToString()
h.update(offer_bytes)
return h.digest()
def getLastBidState(packed_states):
num_states = len(packed_states) // 12
if num_states < 2:
return BidStates.BID_STATE_UNKNOWN
return struct.unpack_from('<i', packed_states[(num_states - 2) * 12:])[0]
try:
num_states = len(packed_states) // 12
if num_states < 2:
return BidStates.BID_STATE_UNKNOWN
return struct.unpack_from('<i', packed_states[(num_states - 2) * 12:])[0]
except Exception:
return BidStates.BID_STATE_UNKNOWN

View file

@ -22,7 +22,7 @@ from .chainparams import (
chainparams,
Coins,
)
from .basicswap import (
from .basicswap_util import (
SwapTypes,
strOfferState,
strBidState,

View file

@ -62,7 +62,7 @@ from .contrib.test_framework.script import (
SegwitV0SignatureHash,
hash160)
from .types import (
from .basicswap_util import (
SEQUENCE_LOCK_BLOCKS,
SEQUENCE_LOCK_TIME)

View file

@ -10,7 +10,7 @@ import urllib.parse
from .util import (
toBool,
)
from .basicswap import (
from .basicswap_util import (
strBidState,
SwapTypes,
)

View file

@ -16,7 +16,7 @@
<tr><td>Swap</td><td>{{ data.amt_from }} {{ data.ticker_from }} for {{ data.amt_to }} {{ data.ticker_to }}</td></tr>
{% endif %}
<tr><td>Bid State</td><td>{{ data.bid_state }}</td></tr>
<tr><td>StateDescription </td><td>{{ data.state_description }}</td></tr>
<tr><td>State Description </td><td>{{ data.state_description }}</td></tr>
<tr><td>ITX State</td><td>{{ data.itx_state }}</td></tr>
<tr><td>PTX State</td><td>{{ data.ptx_state }}</td></tr>
<tr><td>Offer</td><td><a href="/offer/{{ data.offer_id }}">{{ data.offer_id }}</a></td></tr>

View file

@ -16,7 +16,7 @@
<tr><td>Swap</td><td>{{ data.amt_from }} {{ data.ticker_from }} for {{ data.amt_to }} {{ data.ticker_to }}</td></tr>
{% endif %}
<tr><td>Bid State</td><td>{{ data.bid_state }}</td></tr>
<tr><td>StateDescription </td><td>{{ data.state_description }}</td></tr>
<tr><td>State Description </td><td>{{ data.state_description }}</td></tr>
<tr><td>Offer</td><td><a href="/offer/{{ data.offer_id }}">{{ data.offer_id }}</a></td></tr>
<tr><td>Address From</td><td>{{ data.addr_from }}</td></tr>
<tr><td>Created At</td><td>{{ data.created_at }}</td></tr>

View file

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
SEQUENCE_LOCK_BLOCKS = 1
SEQUENCE_LOCK_TIME = 2
ABS_LOCK_BLOCKS = 3
ABS_LOCK_TIME = 4

View file

@ -12,7 +12,8 @@ from .util import (
from .chainparams import (
Coins,
)
from .basicswap import (
from .basicswap_util import (
SEQUENCE_LOCK_TIME,
SwapTypes,
BidStates,
TxStates,
@ -20,9 +21,7 @@ from .basicswap import (
strTxType,
strBidState,
strTxState,
)
from .types import (
SEQUENCE_LOCK_TIME,
getLastBidState,
)
PAGE_LIMIT = 50
@ -164,6 +163,42 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
state_description = 'Bid abandoned'
elif bid.state == BidStates.BID_ERROR:
state_description = bid.state_note
elif offer.swap_type == SwapTypes.XMR_SWAP:
if bid.state == BidStates.BID_RECEIVING:
# Offerer receiving bid from bidder
state_description = 'Waiting for bid to be fully received'
elif bid.state == BidStates.BID_RECEIVED:
# Offerer received bid from bidder
# TODO: Manual vs automatic
state_description = 'Bid must be accepted'
elif bid.state == BidStates.BID_RECEIVING_ACC:
state_description = 'Receiving accepted bid message'
elif bid.state == BidStates.BID_ACCEPTED:
state_description = 'Offerer has accepted bid, waiting for bidder to respond'
elif bid.state == BidStates.SWAP_DELAYING:
last_state = getLastBidState(bid.states)
if last_state == BidStates.BID_RECEIVED:
state_description = 'Delaying before accepting bid'
elif last_state == BidStates.BID_RECEIVING_ACC:
state_description = 'Delaying before responding to accepted bid'
elif last_state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
state_description = f'Delaying before spending from {ticker_to} lock tx'
elif last_state == BidStates.BID_ACCEPTED:
state_description = f'Delaying before sending {ticker_from} lock tx'
else:
state_description = 'Delaying before automated action'
elif bid.state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
state_description = f'Waiting for {ticker_from} lock tx to confirm in chain'
elif bid.state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
state_description = f'Waiting for {ticker_to} lock tx to confirm in chain'
elif bid.state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED:
state_description = f'Waiting for offerer to unlock {ticker_from} lock tx'
elif bid.state == BidStates.XMR_SWAP_LOCK_RELEASED:
state_description = f'Waiting for bidder to spend from {ticker_from} lock tx'
elif bid.state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
state_description = f'Waiting for offerer to spend from {ticker_to} lock tx'
elif bid.state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
state_description = f'Waiting for {ticker_to} lock tx spend tx to confirm in chain'
data = {
'amt_from': ci_from.format_amount(bid.amount),

View file

@ -13,46 +13,3 @@ Features still required (of many):
- More swap protocols
- Manual method to set wallet seeds from particl mnemonic
- prepare script tries to load seeds automatically, btc versions < 0.21 require a fully synced chain
## Seller first protocol:
Seller sends the 1st transaction.
1. Seller posts offer.
- smsg from seller to network
coin-from
coin-to
amount-from
rate
min-amount
time-valid
2. Buyer posts bid:
- smsg from buyer to seller
offerid
amount
proof-of-funds
address_to_buyer
time-valid
3. Seller accepts bid:
- verifies proof-of-funds
- generates secret
- submits initiate tx to coin-from network
- smsg from seller to buyer
txid
initiatescript (includes pkhash_to_seller as the pkhash_refund)
4. Buyer participates:
- inspects initiate tx in coin-from network
- submits participate tx in coin-to network
5. Seller redeems:
- constructs participatescript
- inspects participate tx in coin-to network
- redeems from participate tx revealing secret
6. Buyer redeems:
- scans coin-to network for seller-redeem tx
- redeems from initiate tx with revealed secret

View file

@ -0,0 +1,43 @@
# Seller first protocol
Seller sends the first transaction.
Both coin types must support scripts.
1. Seller posts offer.
- smsg from seller to network
coin-from
coin-to
amount-from
rate
min-amount
time-valid
2. Buyer posts bid:
- smsg from buyer to seller
offerid
amount
proof-of-funds
address_to_buyer
time-valid
3. Seller accepts bid:
- verifies proof-of-funds
- generates secret
- submits initiate tx to coin-from network
- smsg from seller to buyer
txid
initiatescript (includes pkhash_to_seller as the pkhash_refund)
4. Buyer participates:
- inspects initiate tx in coin-from network
- submits participate tx in coin-to network
5. Seller redeems:
- constructs participatescript
- inspects participate tx in coin-to network
- redeems from participate tx revealing secret
6. Buyer redeems:
- scans coin-to network for seller-redeem tx
- redeems from initiate tx with revealed secret

79
doc/protocols/xmr.md Normal file
View file

@ -0,0 +1,79 @@
# XMR protocol
## WIP
Relies on a One-Time Verifiably Encrypted Signature (OtVES) to function
An OtVES:
- Is a valid signature for key (a) encrypted with a public key (B)
- Can be decrypted into a valid signature for key (a) with the private key (b) to the encrypting public key (B)
- The encrypting private key (b) can be recovered using both the encrypted and decrypted signatures.
Leader - sends the first lock tx.
NOSCRIPT_COIN lock tx:
- sent second
- is sent to a combined key using a private key from each participant.
SCRIPT_COIN lock tx:
- Sent first
- Requires two signatures to spend from.
- Refund to sender txn is presigned for and can only be mined in the future.
- Spending the refund tx reveals the leader's NOSCRIPT_COIN split private key
- Sender withholds signature until NOSCRIPT_COIN lock tx is confirmed.
- spending the spend txn reveals the follower's NOSCRIPT_COIN split private key
```
Offerer (Leader) | Bidder (Follower) |
------------------------------------------------------------------------|-------------------------------------------------------------------------------|
o1. Sends offer | |
- x SCRIPT_COIN for y NOSCRIPT_COIN | |
- sends smsg OfferMessage | |
| b1. Receives offer |
| - validates offer |
| b2. Sends bid |
| - sends smsgs XmrBidMessage + 2x XmrSplitMessage |
| |
o2. Receives bid | |
- validates bid | |
o3. Accepts bid | |
- sends smsgs XmrBidAcceptMessage + 2x XmrSplitMessage | |
| |
| b3. Receives bid accept |
| - validates |
| - signs for lock tx refund |
| - sends smsg XmrBidLockTxSigsMessage |
| |
o4. Receives bidder lock refund tx signatures | |
- sends smsg XmrBidLockSpendTxMessage | |
- full SCRIPT_COIN lock tx | |
- signature to prove leader can sign for split key | |
- submits SCRIPT_COIN lock tx to network | |
| |
| b4. Receives XmrBidLockSpendTxMessage |
| - validates SCRIPT_COIN lock tx and signature |
| - waits for SCRIPT_COIN lock tx to confirm in chain |
| b5. Sends NOSCRIPT_COIN lock tx |
| |
o5. Waits for NOSCRIPT_COIN lock tx to confirm in chain | |
o6. Sends SCRIPT_COIN lock release. | |
- sends smsg XmrBidLockReleaseMessage | |
- includes OtVES ciphertext signature for the SCRIPT_COIN lock | |
spend tx. | |
| |
| b6. Receives offerer OtVES for SCRIPT_COIN lock spend tx. |
| - submits SCRIPT_COIN lock spend tx to network. |
| |
o7. Waits for SCRIPT_COIN lock spend tx. | |
- Extracts the NOSCRIPT_COIN bidders key using the signature | |
o8. Combines the keys to spend from the NOSCRIPT_COIN lock tx | |
- submits NOSCRIPT_COIN lock spend tx to network | |
```
Offerer sent 6 smsgs (2 extra from split messages)
Bidder sent 4 smsgs (2 extra from split messages)

View file

@ -24,15 +24,16 @@ from coincurve.keys import (
from basicswap.ecc_util import i2b
from basicswap.interface_btc import BTCInterface
from basicswap.interface_xmr import XMRInterface
from basicswap.basicswap_util import (
SEQUENCE_LOCK_BLOCKS,
SEQUENCE_LOCK_TIME)
from basicswap.util import (
SerialiseNum,
DeserialiseNum,
make_int,
format_amount,
validate_amount)
from basicswap.types import (
SEQUENCE_LOCK_BLOCKS,
SEQUENCE_LOCK_TIME)
class Test(unittest.TestCase):

View file

@ -32,6 +32,8 @@ from basicswap.basicswap import (
BidStates,
TxStates,
DebugTypes,
)
from basicswap.basicswap_util import (
SEQUENCE_LOCK_BLOCKS,
)
from basicswap.util import (

View file

@ -25,6 +25,8 @@ from basicswap.basicswap import (
SwapTypes,
BidStates,
DebugTypes,
)
from basicswap.basicswap_util import (
SEQUENCE_LOCK_BLOCKS,
)
from basicswap.util import (