2019-07-17 15:12:06 +00:00
# -*- coding: utf-8 -*-
2023-02-14 21:34:01 +00:00
# Copyright (c) 2019-2023 tecnovert
2019-07-17 15:12:06 +00:00
# Distributed under the MIT software license, see the accompanying
2020-10-30 08:55:45 +00:00
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
2019-07-17 15:12:06 +00:00
import os
import re
2021-10-14 20:17:37 +00:00
import sys
2019-07-17 15:12:06 +00:00
import zmq
2022-11-13 21:18:33 +00:00
import copy
2019-08-05 22:04:40 +00:00
import json
2020-11-14 22:13:11 +00:00
import time
2020-12-02 11:24:52 +00:00
import base64
2019-11-09 21:09:22 +00:00
import random
2020-12-15 18:00:44 +00:00
import shutil
2022-11-13 21:18:33 +00:00
import string
2020-12-15 18:00:44 +00:00
import struct
import hashlib
2019-11-09 21:09:22 +00:00
import secrets
2020-11-14 22:13:11 +00:00
import datetime as dt
2021-02-03 14:01:27 +00:00
import threading
2020-11-14 22:13:11 +00:00
import traceback
import sqlalchemy as sa
2020-12-11 08:41:57 +00:00
import collections
2021-10-14 20:17:37 +00:00
import concurrent . futures
2020-11-14 22:13:11 +00:00
2022-12-05 15:04:23 +00:00
from typing import Optional
2020-11-14 22:13:11 +00:00
from sqlalchemy . orm import sessionmaker , scoped_session
2021-02-04 22:42:15 +00:00
from sqlalchemy . orm . session import close_all_sessions
2019-07-26 10:07:25 +00:00
2023-05-11 21:45:06 +00:00
from . interface import Curves
2022-08-08 22:10:37 +00:00
from . interface . part import PARTInterface , PARTInterfaceAnon , PARTInterfaceBlind
from . interface . btc import BTCInterface
from . interface . ltc import LTCInterface
from . interface . nmc import NMCInterface
from . interface . xmr import XMRInterface
2022-08-10 22:02:36 +00:00
from . interface . pivx import PIVXInterface
2022-10-20 20:23:25 +00:00
from . interface . dash import DASHInterface
2022-11-07 20:31:10 +00:00
from . interface . firo import FIROInterface
2022-08-08 22:10:37 +00:00
from . interface . passthrough_btc import PassthroughBTCInterface
2020-10-31 20:08:30 +00:00
2019-07-17 15:12:06 +00:00
from . import __version__
2021-11-13 21:13:54 +00:00
from . rpc_xmr import make_xmr_rpc2_func
2022-10-24 18:49:36 +00:00
from . ui . util import getCoinName
2019-07-17 15:12:06 +00:00
from . util import (
2022-11-11 23:51:30 +00:00
AutomationConstraint ,
LockedCoinError ,
2021-11-12 14:36:10 +00:00
TemporaryError ,
2022-10-12 20:37:35 +00:00
InactiveCoin ,
2020-11-07 11:08:07 +00:00
format_amount ,
2021-01-08 22:12:08 +00:00
format_timestamp ,
2019-07-17 15:12:06 +00:00
DeserialiseNum ,
2023-02-15 21:51:55 +00:00
zeroIfNone ,
2020-10-31 20:08:30 +00:00
make_int ,
2021-10-21 22:47:04 +00:00
ensure ,
2019-07-17 15:12:06 +00:00
)
2022-03-23 22:00:35 +00:00
from . util . script import (
getP2WSH ,
getP2SHScriptForHash ,
)
from . util . address import (
toWIF ,
getKeyID ,
decodeWif ,
decodeAddress ,
pubkeyToAddress ,
)
2019-07-17 15:12:06 +00:00
from . chainparams import (
Coins ,
2022-03-23 22:00:35 +00:00
chainparams ,
2019-07-17 15:12:06 +00:00
)
2019-11-18 20:53:33 +00:00
from . script import (
OpCodes ,
)
2019-07-17 15:12:06 +00:00
from . messages_pb2 import (
OfferMessage ,
BidMessage ,
BidAcceptMessage ,
2020-11-14 22:13:11 +00:00
XmrBidMessage ,
XmrBidAcceptMessage ,
XmrSplitMessage ,
2020-11-15 17:02:46 +00:00
XmrBidLockTxSigsMessage ,
XmrBidLockSpendTxMessage ,
2020-12-10 22:43:36 +00:00
XmrBidLockReleaseMessage ,
2020-12-02 11:24:52 +00:00
OfferRevokeMessage ,
2019-07-17 15:12:06 +00:00
)
2019-07-27 16:00:13 +00:00
from . db import (
CURRENT_DB_VERSION ,
2022-06-06 21:03:31 +00:00
Concepts ,
2019-07-27 16:00:13 +00:00
Base ,
DBKVInt ,
DBKVString ,
Offer ,
Bid ,
SwapTx ,
2022-12-05 15:04:23 +00:00
PrefundedTx ,
2019-07-27 16:00:13 +00:00
PooledAddress ,
SentOffer ,
2019-07-28 19:57:20 +00:00
SmsgAddress ,
2022-06-06 21:03:31 +00:00
Action ,
2020-12-02 11:24:52 +00:00
EventLog ,
2020-11-14 22:13:11 +00:00
XmrOffer ,
XmrSwap ,
XmrSplitData ,
2021-10-14 20:17:37 +00:00
Wallets ,
2022-10-13 20:21:43 +00:00
Notification ,
2021-11-14 23:26:43 +00:00
KnownIdentity ,
2022-05-23 21:51:06 +00:00
AutomationLink ,
AutomationStrategy ,
2019-07-27 16:00:13 +00:00
)
2022-05-23 21:51:06 +00:00
from . db_upgrades import upgradeDatabase , upgradeDatabaseData
2019-11-10 09:10:55 +00:00
from . base import BaseApp
2020-12-10 10:07:26 +00:00
from . explorers import (
ExplorerInsight ,
ExplorerBitAps ,
ExplorerChainz ,
)
import basicswap . config as cfg
2020-12-13 13:43:46 +00:00
import basicswap . network as bsn
2020-12-10 10:07:26 +00:00
import basicswap . protocols . atomic_swap_1 as atomic_swap_1
2022-12-05 15:04:23 +00:00
import basicswap . protocols . xmr_swap_1 as xmr_swap_1
2021-10-18 18:48:48 +00:00
from . basicswap_util import (
2021-12-19 06:59:35 +00:00
KeyTypes ,
2021-12-15 13:41:43 +00:00
TxLockTypes ,
2021-10-20 17:47:49 +00:00
AddressTypes ,
2021-10-18 18:48:48 +00:00
MessageTypes ,
SwapTypes ,
OfferStates ,
BidStates ,
TxStates ,
TxTypes ,
2022-06-06 21:03:31 +00:00
ActionTypes ,
2021-10-18 18:48:48 +00:00
EventLogTypes ,
XmrSplitMsgTypes ,
DebugTypes ,
strBidState ,
describeEventEntry ,
getVoutByAddress ,
getVoutByP2WSH ,
getOfferProofOfFundsHash ,
2021-10-18 20:28:42 +00:00
getLastBidState ,
2022-07-31 17:33:01 +00:00
isActiveBidState ,
NotificationTypes as NT ,
2023-02-15 21:51:55 +00:00
AutomationOverrideOptions ,
VisibilityOverrideOptions ,
2023-06-06 20:03:05 +00:00
inactive_states ,
2022-07-31 17:33:01 +00:00
)
2021-01-28 12:38:28 +00:00
2022-05-23 21:51:06 +00:00
2023-05-12 08:20:52 +00:00
PROTOCOL_VERSION_SECRET_HASH = 1
MINPROTO_VERSION_SECRET_HASH = 1
PROTOCOL_VERSION_ADAPTOR_SIG = 2
MINPROTO_VERSION_ADAPTOR_SIG = 2
2021-10-21 22:47:04 +00:00
non_script_type_coins = ( Coins . XMR , Coins . PART_ANON )
2021-01-28 12:38:28 +00:00
2021-10-20 17:47:49 +00:00
def validOfferStateToReceiveBid ( offer_state ) :
if offer_state == OfferStates . OFFER_RECEIVED :
return True
if offer_state == OfferStates . OFFER_SENT :
return True
return False
2022-09-11 15:16:51 +00:00
def threadPollXMRChainState ( swap_client , coin_type ) :
ci = swap_client . ci ( coin_type )
cc = swap_client . coin_clients [ coin_type ]
2021-02-03 14:01:27 +00:00
while not swap_client . delay_event . is_set ( ) :
try :
2022-09-11 15:16:51 +00:00
new_height = ci . getChainHeight ( )
if new_height != cc [ ' chain_height ' ] :
2022-10-24 18:49:36 +00:00
swap_client . log . debug ( ' New {} block at height: {} ' . format ( ci . ticker ( ) , new_height ) )
2022-09-11 15:16:51 +00:00
with swap_client . mxDB :
cc [ ' chain_height ' ] = new_height
except Exception as e :
2022-10-24 18:49:36 +00:00
swap_client . log . warning ( ' threadPollXMRChainState {} , error: {} ' . format ( ci . ticker ( ) , str ( e ) ) )
2022-09-11 15:16:51 +00:00
swap_client . delay_event . wait ( random . randrange ( 20 , 30 ) ) # random to stagger updates
2022-06-16 12:28:52 +00:00
2022-09-11 15:16:51 +00:00
def threadPollChainState ( swap_client , coin_type ) :
ci = swap_client . ci ( coin_type )
cc = swap_client . coin_clients [ coin_type ]
while not swap_client . delay_event . is_set ( ) :
try :
2022-06-16 12:28:52 +00:00
chain_state = ci . getBlockchainInfo ( )
if chain_state [ ' bestblockhash ' ] != cc [ ' chain_best_block ' ] :
2022-10-24 18:49:36 +00:00
swap_client . log . debug ( ' New {} block at height: {} ' . format ( ci . ticker ( ) , chain_state [ ' blocks ' ] ) )
2022-06-16 12:28:52 +00:00
with swap_client . mxDB :
cc [ ' chain_height ' ] = chain_state [ ' blocks ' ]
cc [ ' chain_best_block ' ] = chain_state [ ' bestblockhash ' ]
2022-08-10 22:02:36 +00:00
if ' mediantime ' in chain_state :
cc [ ' chain_median_time ' ] = chain_state [ ' mediantime ' ]
2021-02-03 14:01:27 +00:00
except Exception as e :
2022-10-24 18:49:36 +00:00
swap_client . log . warning ( ' threadPollChainState {} , error: {} ' . format ( ci . ticker ( ) , str ( e ) ) )
2021-02-03 14:01:27 +00:00
swap_client . delay_event . wait ( random . randrange ( 20 , 30 ) ) # random to stagger updates
2020-12-15 18:00:44 +00:00
class WatchedOutput ( ) : # Watch for spends
__slots__ = ( ' bid_id ' , ' txid_hex ' , ' vout ' , ' tx_type ' , ' swap_type ' )
2020-11-29 11:46:00 +00:00
def __init__ ( self , bid_id , txid_hex , vout , tx_type , swap_type ) :
self . bid_id = bid_id
self . txid_hex = txid_hex
self . vout = vout
self . tx_type = tx_type
self . swap_type = swap_type
2020-11-29 23:05:30 +00:00
class WatchedTransaction ( ) :
2020-12-15 18:00:44 +00:00
# TODO
2020-11-30 14:29:40 +00:00
# Watch for presence in mempool (getrawtransaction)
2020-11-29 23:05:30 +00:00
def __init__ ( self , bid_id , txid_hex , tx_type , swap_type ) :
self . bid_id = bid_id
self . txid_hex = txid_hex
self . tx_type = tx_type
self . swap_type = swap_type
2019-11-10 09:10:55 +00:00
class BasicSwap ( BaseApp ) :
2022-07-31 17:33:01 +00:00
ws_server = None
2023-02-16 20:57:55 +00:00
_read_zmq_queue : bool = True
2022-12-05 15:04:23 +00:00
protocolInterfaces = {
SwapTypes . SELLER_FIRST : atomic_swap_1 . AtomicSwapInterface ( ) ,
SwapTypes . XMR_SWAP : xmr_swap_1 . XmrSwapInterface ( ) ,
}
2022-07-31 17:33:01 +00:00
2019-07-17 15:12:06 +00:00
def __init__ ( self , fp , data_dir , settings , chain , log_name = ' BasicSwap ' ) :
2019-11-10 09:10:55 +00:00
super ( ) . __init__ ( fp , data_dir , settings , chain , log_name )
2019-11-09 21:09:22 +00:00
2020-12-15 18:00:44 +00:00
v = __version__ . split ( ' . ' )
self . _version = struct . pack ( ' >HHH ' , int ( v [ 0 ] ) , int ( v [ 1 ] ) , int ( v [ 2 ] ) )
2019-07-31 12:56:51 +00:00
self . check_progress_seconds = self . settings . get ( ' check_progress_seconds ' , 60 )
self . check_watched_seconds = self . settings . get ( ' check_watched_seconds ' , 60 )
self . check_expired_seconds = self . settings . get ( ' check_expired_seconds ' , 60 * 5 )
2022-06-06 21:03:31 +00:00
self . check_actions_seconds = self . settings . get ( ' check_actions_seconds ' , 10 )
2020-11-14 22:13:11 +00:00
self . check_xmr_swaps_seconds = self . settings . get ( ' check_xmr_swaps_seconds ' , 20 )
2021-11-10 10:44:40 +00:00
self . startup_tries = self . settings . get ( ' startup_tries ' , 21 ) # Seconds waited for will be (x(1 + x+1) / 2
2021-11-26 22:05:04 +00:00
self . debug_ui = self . settings . get ( ' debug_ui ' , False )
2020-11-14 22:13:11 +00:00
self . _last_checked_progress = 0
self . _last_checked_watched = 0
self . _last_checked_expired = 0
2022-06-06 21:03:31 +00:00
self . _last_checked_actions = 0
2020-11-14 22:13:11 +00:00
self . _last_checked_xmr_swaps = 0
2020-12-11 08:41:57 +00:00
self . _possibly_revoked_offers = collections . deque ( [ ] , maxlen = 48 ) # TODO: improve
2021-10-14 20:17:37 +00:00
self . _updating_wallets_info = { }
self . _last_updated_wallets_info = 0
2019-11-09 21:09:22 +00:00
2022-10-13 20:21:43 +00:00
self . _notifications_enabled = self . settings . get ( ' notifications_enabled ' , True )
self . _disabled_notification_types = self . settings . get ( ' disabled_notification_types ' , [ ] )
self . _keep_notifications = self . settings . get ( ' keep_notifications ' , 50 )
self . _show_notifications = self . settings . get ( ' show_notifications ' , 10 )
self . _notifications_cache = { }
2022-11-18 21:31:52 +00:00
self . _is_encrypted = None
self . _is_locked = None
2022-10-13 20:21:43 +00:00
2019-11-09 21:09:22 +00:00
# TODO: Adjust ranges
2020-12-02 11:24:52 +00:00
self . min_delay_event = self . settings . get ( ' min_delay_event ' , 10 )
self . max_delay_event = self . settings . get ( ' max_delay_event ' , 60 )
2022-07-01 14:37:10 +00:00
self . min_delay_event_short = self . settings . get ( ' min_delay_event_short ' , 2 )
self . max_delay_event_short = self . settings . get ( ' max_delay_event_short ' , 30 )
2020-12-02 11:24:52 +00:00
2020-12-06 17:34:56 +00:00
self . min_delay_retry = self . settings . get ( ' min_delay_retry ' , 60 )
self . max_delay_retry = self . settings . get ( ' max_delay_retry ' , 5 * 60 )
2019-11-09 21:09:22 +00:00
2022-10-18 19:01:34 +00:00
self . min_sequence_lock_seconds = self . settings . get ( ' min_sequence_lock_seconds ' , 60 if self . debug else ( 1 * 60 * 60 ) )
2021-01-29 23:45:24 +00:00
self . max_sequence_lock_seconds = self . settings . get ( ' max_sequence_lock_seconds ' , 96 * 60 * 60 )
2023-02-19 19:52:22 +00:00
self . _restrict_unknown_seed_wallets = self . settings . get ( ' restrict_unknown_seed_wallets ' , True )
2021-01-09 13:00:25 +00:00
self . _bid_expired_leeway = 5
2019-07-31 12:56:51 +00:00
self . swaps_in_progress = dict ( )
2021-02-15 13:34:47 +00:00
self . SMSG_SECONDS_IN_HOUR = 60 * 60 # Note: Set smsgsregtestadjust=0 for regtest
2019-07-31 12:56:51 +00:00
2021-02-03 14:01:27 +00:00
self . threads = [ ]
2021-10-14 20:17:37 +00:00
self . thread_pool = concurrent . futures . ThreadPoolExecutor ( max_workers = 4 , thread_name_prefix = ' bsp ' )
2021-02-03 14:01:27 +00:00
2019-07-17 15:12:06 +00:00
# Encode key to match network
wif_prefix = chainparams [ Coins . PART ] [ self . chain ] [ ' key_prefix ' ]
self . network_key = toWIF ( wif_prefix , decodeWif ( self . settings [ ' network_key ' ] ) )
self . network_pubkey = self . settings [ ' network_pubkey ' ]
2020-11-14 22:13:11 +00:00
self . network_addr = pubkeyToAddress ( chainparams [ Coins . PART ] [ self . chain ] [ ' pubkey_address ' ] , bytes . fromhex ( self . network_pubkey ) )
2019-07-17 15:12:06 +00:00
2022-06-15 22:19:06 +00:00
self . db_echo = self . settings . get ( ' db_echo ' , False )
2019-07-31 12:56:51 +00:00
self . sqlite_file = os . path . join ( self . data_dir , ' db {} .sqlite ' . format ( ' ' if self . chain == ' mainnet ' else ( ' _ ' + self . chain ) ) )
2019-07-17 15:12:06 +00:00
db_exists = os . path . exists ( self . sqlite_file )
2021-02-04 22:42:15 +00:00
# HACK: create_all hangs when using tox, unless create_engine is called with echo=True
2019-07-17 15:12:06 +00:00
if not db_exists :
2021-02-04 22:42:15 +00:00
if os . getenv ( ' FOR_TOX ' ) :
self . engine = sa . create_engine ( ' sqlite:/// ' + self . sqlite_file , echo = True )
else :
self . engine = sa . create_engine ( ' sqlite:/// ' + self . sqlite_file )
close_all_sessions ( )
2019-07-17 15:12:06 +00:00
Base . metadata . create_all ( self . engine )
2021-02-04 22:42:15 +00:00
self . engine . dispose ( )
2022-06-15 22:19:06 +00:00
self . engine = sa . create_engine ( ' sqlite:/// ' + self . sqlite_file , echo = self . db_echo )
2019-07-17 15:12:06 +00:00
self . session_factory = sessionmaker ( bind = self . engine , expire_on_commit = False )
2019-07-18 17:53:23 +00:00
session = scoped_session ( self . session_factory )
try :
self . db_version = session . query ( DBKVInt ) . filter_by ( key = ' db_version ' ) . first ( ) . value
except Exception :
2019-07-17 15:12:06 +00:00
self . log . info ( ' First run ' )
self . db_version = CURRENT_DB_VERSION
2019-07-18 17:53:23 +00:00
session . add ( DBKVInt (
key = ' db_version ' ,
value = self . db_version
) )
session . commit ( )
2022-05-23 21:51:06 +00:00
try :
self . db_data_version = session . query ( DBKVInt ) . filter_by ( key = ' db_data_version ' ) . first ( ) . value
except Exception :
self . db_data_version = 0
2019-07-18 17:53:23 +00:00
try :
self . _contract_count = session . query ( DBKVInt ) . filter_by ( key = ' contract_count ' ) . first ( ) . value
except Exception :
self . _contract_count = 0
2020-11-07 11:08:07 +00:00
session . add ( DBKVInt (
key = ' contract_count ' ,
value = self . _contract_count
) )
session . commit ( )
2022-10-13 20:21:43 +00:00
2019-07-18 17:53:23 +00:00
session . close ( )
session . remove ( )
2019-07-17 15:12:06 +00:00
self . zmqContext = zmq . Context ( )
self . zmqSubscriber = self . zmqContext . socket ( zmq . SUB )
self . zmqSubscriber . connect ( self . settings [ ' zmqhost ' ] + ' : ' + str ( self . settings [ ' zmqport ' ] ) )
self . zmqSubscriber . setsockopt_string ( zmq . SUBSCRIBE , ' smsg ' )
2019-07-24 17:26:04 +00:00
for c in Coins :
2021-02-06 22:35:12 +00:00
if c in chainparams :
self . setCoinConnectParams ( c )
2019-07-17 15:12:06 +00:00
2019-08-01 16:21:23 +00:00
if self . chain == ' mainnet ' :
self . coin_clients [ Coins . PART ] [ ' explorers ' ] . append ( ExplorerInsight (
2019-08-05 18:31:02 +00:00
self , Coins . PART ,
' https://explorer.particl.io/particl-insight-api ' ) )
2019-08-01 16:21:23 +00:00
self . coin_clients [ Coins . LTC ] [ ' explorers ' ] . append ( ExplorerBitAps (
2019-08-05 18:31:02 +00:00
self , Coins . LTC ,
2019-08-01 16:21:23 +00:00
' https://api.bitaps.com/ltc/v1/blockchain ' ) )
self . coin_clients [ Coins . LTC ] [ ' explorers ' ] . append ( ExplorerChainz (
2019-08-05 18:31:02 +00:00
self , Coins . LTC ,
2019-08-01 16:21:23 +00:00
' http://chainz.cryptoid.info/ltc/api.dws ' ) )
elif self . chain == ' testnet ' :
self . coin_clients [ Coins . PART ] [ ' explorers ' ] . append ( ExplorerInsight (
2019-08-05 18:31:02 +00:00
self , Coins . PART ,
2019-08-01 16:21:23 +00:00
' https://explorer-testnet.particl.io/particl-insight-api ' ) )
self . coin_clients [ Coins . LTC ] [ ' explorers ' ] . append ( ExplorerBitAps (
2019-08-05 18:31:02 +00:00
self , Coins . LTC ,
2019-08-01 16:21:23 +00:00
' https://api.bitaps.com/ltc/testnet/v1/blockchain ' ) )
2019-08-05 18:31:02 +00:00
# non-segwit
# https://testnet.litecore.io/insight-api
2019-11-09 21:09:22 +00:00
random . seed ( secrets . randbits ( 128 ) )
2020-12-13 13:43:46 +00:00
def finalise ( self ) :
2020-12-15 18:00:44 +00:00
self . log . info ( ' Finalise ' )
with self . mxDB :
self . is_running = False
2021-02-03 14:01:27 +00:00
self . delay_event . set ( )
2020-12-15 18:00:44 +00:00
2020-12-13 13:43:46 +00:00
if self . _network :
self . _network . stopNetwork ( )
self . _network = None
2021-02-03 14:01:27 +00:00
for t in self . threads :
t . join ( )
2021-10-14 20:17:37 +00:00
if sys . version_info [ 1 ] > = 9 :
self . thread_pool . shutdown ( cancel_futures = True )
else :
self . thread_pool . shutdown ( )
2022-01-23 12:00:28 +00:00
self . zmqContext . destroy ( )
2022-11-14 19:47:07 +00:00
self . swaps_in_progress . clear ( )
2021-02-04 22:42:15 +00:00
close_all_sessions ( )
self . engine . dispose ( )
2022-10-13 20:21:43 +00:00
def openSession ( self , session = None ) :
if session :
return session
self . mxDB . acquire ( )
return scoped_session ( self . session_factory )
def closeSession ( self , use_session , commit = True ) :
if commit :
use_session . commit ( )
use_session . close ( )
use_session . remove ( )
self . mxDB . release ( )
2022-12-01 18:51:06 +00:00
def handleSessionErrors ( self , e , session , tag ) :
if self . debug :
self . log . error ( traceback . format_exc ( ) )
self . log . error ( f ' Error: { tag } - { e } ' )
session . rollback ( )
2019-07-31 12:56:51 +00:00
def setCoinConnectParams ( self , coin ) :
2019-07-31 16:38:19 +00:00
# Set anything that does not require the daemon to be running
2019-07-17 15:12:06 +00:00
chain_client_settings = self . getChainClientSettings ( coin )
2019-07-23 14:26:37 +00:00
bindir = os . path . expanduser ( chain_client_settings . get ( ' bindir ' , ' ' ) )
2020-02-01 18:57:20 +00:00
datadir = os . path . expanduser ( chain_client_settings . get ( ' datadir ' , os . path . join ( cfg . TEST_DATADIRS , chainparams [ coin ] [ ' name ' ] ) ) )
2019-07-23 14:26:37 +00:00
2019-07-17 15:12:06 +00:00
connection_type = chain_client_settings . get ( ' connection_type ' , ' none ' )
rpcauth = None
if connection_type == ' rpc ' :
if ' rpcauth ' in chain_client_settings :
rpcauth = chain_client_settings [ ' rpcauth ' ]
2019-07-31 12:25:54 +00:00
self . log . debug ( ' Read %s rpc credentials from json settings ' , coin )
2019-07-17 15:12:06 +00:00
elif ' rpcpassword ' in chain_client_settings :
rpcauth = chain_client_settings [ ' rpcuser ' ] + ' : ' + chain_client_settings [ ' rpcpassword ' ]
2019-07-31 12:25:54 +00:00
self . log . debug ( ' Read %s rpc credentials from json settings ' , coin )
2019-07-17 15:12:06 +00:00
2019-07-22 21:39:00 +00:00
session = scoped_session ( self . session_factory )
try :
last_height_checked = session . query ( DBKVInt ) . filter_by ( key = ' last_height_checked_ ' + chainparams [ coin ] [ ' name ' ] ) . first ( ) . value
except Exception :
last_height_checked = 0
session . close ( )
session . remove ( )
2019-07-17 15:12:06 +00:00
2022-08-10 22:02:36 +00:00
coin_chainparams = chainparams [ coin ]
default_segwit = coin_chainparams . get ( ' has_segwit ' , False )
default_csv = coin_chainparams . get ( ' has_csv ' , True )
2019-07-31 16:38:19 +00:00
self . coin_clients [ coin ] = {
2019-07-17 15:12:06 +00:00
' coin ' : coin ,
2022-08-10 22:02:36 +00:00
' name ' : coin_chainparams [ ' name ' ] ,
2019-07-17 15:12:06 +00:00
' connection_type ' : connection_type ,
2019-07-23 14:26:37 +00:00
' bindir ' : bindir ,
2019-07-17 15:12:06 +00:00
' datadir ' : datadir ,
2020-12-04 23:59:21 +00:00
' rpchost ' : chain_client_settings . get ( ' rpchost ' , ' 127.0.0.1 ' ) ,
2022-08-10 22:02:36 +00:00
' rpcport ' : chain_client_settings . get ( ' rpcport ' , coin_chainparams [ self . chain ] [ ' rpcport ' ] ) ,
2019-07-17 15:12:06 +00:00
' rpcauth ' : rpcauth ,
2019-07-26 21:03:56 +00:00
' blocks_confirmed ' : chain_client_settings . get ( ' blocks_confirmed ' , 6 ) ,
' conf_target ' : chain_client_settings . get ( ' conf_target ' , 2 ) ,
2019-07-17 15:12:06 +00:00
' watched_outputs ' : [ ] ,
' last_height_checked ' : last_height_checked ,
2022-08-10 22:02:36 +00:00
' use_segwit ' : chain_client_settings . get ( ' use_segwit ' , default_segwit ) ,
' use_csv ' : chain_client_settings . get ( ' use_csv ' , default_csv ) ,
2019-07-31 16:53:44 +00:00
' core_version_group ' : chain_client_settings . get ( ' core_version_group ' , 0 ) ,
2019-07-31 16:38:19 +00:00
' pid ' : None ,
2019-08-04 13:22:25 +00:00
' core_version ' : None ,
2019-08-01 16:21:23 +00:00
' explorers ' : [ ] ,
2019-08-05 22:04:40 +00:00
' chain_lookups ' : chain_client_settings . get ( ' chain_lookups ' , ' local ' ) ,
2020-12-04 23:59:21 +00:00
' restore_height ' : chain_client_settings . get ( ' restore_height ' , 0 ) ,
2020-12-22 11:21:25 +00:00
' fee_priority ' : chain_client_settings . get ( ' fee_priority ' , 0 ) ,
2021-02-03 14:01:27 +00:00
# Chain state
' chain_height ' : None ,
' chain_best_block ' : None ,
' chain_median_time ' : None ,
2019-07-17 15:12:06 +00:00
}
2021-02-11 12:57:54 +00:00
if coin == Coins . PART :
2022-04-10 22:11:51 +00:00
self . coin_clients [ coin ] [ ' anon_tx_ring_size ' ] = chain_client_settings . get ( ' anon_tx_ring_size ' , 12 )
2021-02-11 12:57:54 +00:00
self . coin_clients [ Coins . PART_ANON ] = self . coin_clients [ coin ]
2021-11-01 13:52:40 +00:00
self . coin_clients [ Coins . PART_BLIND ] = self . coin_clients [ coin ]
2021-02-11 12:57:54 +00:00
2020-10-31 20:08:30 +00:00
if self . coin_clients [ coin ] [ ' connection_type ' ] == ' rpc ' :
if coin == Coins . XMR :
2021-11-13 21:13:54 +00:00
if chain_client_settings . get ( ' automatically_select_daemon ' , False ) :
self . selectXMRRemoteDaemon ( coin )
2021-01-11 21:48:46 +00:00
self . coin_clients [ coin ] [ ' walletrpchost ' ] = chain_client_settings . get ( ' walletrpchost ' , ' 127.0.0.1 ' )
2020-10-31 20:08:30 +00:00
self . coin_clients [ coin ] [ ' walletrpcport ' ] = chain_client_settings . get ( ' walletrpcport ' , chainparams [ coin ] [ self . chain ] [ ' walletrpcport ' ] )
if ' walletrpcpassword ' in chain_client_settings :
2020-11-07 11:08:07 +00:00
self . coin_clients [ coin ] [ ' walletrpcauth ' ] = ( chain_client_settings [ ' walletrpcuser ' ] , chain_client_settings [ ' walletrpcpassword ' ] )
2020-10-31 20:08:30 +00:00
else :
raise ValueError ( ' Missing XMR wallet rpc credentials. ' )
2022-11-28 17:54:41 +00:00
self . coin_clients [ coin ] [ ' rpcuser ' ] = chain_client_settings . get ( ' rpcuser ' , ' ' )
self . coin_clients [ coin ] [ ' rpcpassword ' ] = chain_client_settings . get ( ' rpcpassword ' , ' ' )
2021-11-13 21:13:54 +00:00
def selectXMRRemoteDaemon ( self , coin ) :
self . log . info ( ' Selecting remote XMR daemon. ' )
chain_client_settings = self . getChainClientSettings ( coin )
remote_daemon_urls = chain_client_settings . get ( ' remote_daemon_urls ' , [ ] )
2022-11-28 17:54:41 +00:00
coin_settings = self . coin_clients [ coin ]
rpchost = coin_settings [ ' rpchost ' ]
rpcport = coin_settings [ ' rpcport ' ]
daemon_login = None
if coin_settings . get ( ' rpcuser ' , ' ' ) != ' ' :
daemon_login = ( coin_settings . get ( ' rpcuser ' , ' ' ) , coin_settings . get ( ' rpcpassword ' , ' ' ) )
2021-11-13 21:13:54 +00:00
current_daemon_url = f ' { rpchost } : { rpcport } '
if current_daemon_url in remote_daemon_urls :
self . log . info ( f ' Trying last used url { rpchost } : { rpcport } . ' )
try :
2022-11-28 17:54:41 +00:00
rpc_cb2 = make_xmr_rpc2_func ( rpcport , daemon_login , rpchost )
2021-11-13 21:13:54 +00:00
test = rpc_cb2 ( ' get_height ' , timeout = 20 ) [ ' height ' ]
return True
except Exception as e :
self . log . warning ( f ' Failed to set XMR remote daemon to { rpchost } : { rpcport } , { e } ' )
random . shuffle ( remote_daemon_urls )
for url in remote_daemon_urls :
self . log . info ( f ' Trying url { url } . ' )
try :
rpchost , rpcport = url . rsplit ( ' : ' , 1 )
2022-11-28 17:54:41 +00:00
rpc_cb2 = make_xmr_rpc2_func ( rpcport , daemon_login , rpchost )
2021-11-13 21:13:54 +00:00
test = rpc_cb2 ( ' get_height ' , timeout = 20 ) [ ' height ' ]
2022-11-28 17:54:41 +00:00
coin_settings [ ' rpchost ' ] = rpchost
coin_settings [ ' rpcport ' ] = rpcport
2021-11-13 21:13:54 +00:00
data = {
' rpchost ' : rpchost ,
' rpcport ' : rpcport ,
}
self . editSettings ( self . coin_clients [ coin ] [ ' name ' ] , data )
return True
except Exception as e :
self . log . warning ( f ' Failed to set XMR remote daemon to { url } , { e } ' )
raise ValueError ( ' Failed to select a working XMR daemon url. ' )
2022-11-13 21:18:33 +00:00
def isCoinActive ( self , coin ) :
use_coinid = coin
interface_ind = ' interface '
if coin == Coins . PART_ANON :
use_coinid = Coins . PART
interface_ind = ' interface_anon '
if coin == Coins . PART_BLIND :
use_coinid = Coins . PART
interface_ind = ' interface_blind '
if use_coinid not in self . coin_clients :
raise ValueError ( ' Unknown coinid {} ' . format ( int ( coin ) ) )
return interface_ind in self . coin_clients [ use_coinid ]
2020-12-10 14:37:26 +00:00
def ci ( self , coin ) : # Coin interface
2022-10-12 20:37:35 +00:00
use_coinid = coin
interface_ind = ' interface '
2021-02-11 12:57:54 +00:00
if coin == Coins . PART_ANON :
2022-10-12 20:37:35 +00:00
use_coinid = Coins . PART
interface_ind = ' interface_anon '
2021-11-01 13:52:40 +00:00
if coin == Coins . PART_BLIND :
2022-10-12 20:37:35 +00:00
use_coinid = Coins . PART
interface_ind = ' interface_blind '
if use_coinid not in self . coin_clients :
raise ValueError ( ' Unknown coinid {} ' . format ( int ( coin ) ) )
if interface_ind not in self . coin_clients [ use_coinid ] :
raise InactiveCoin ( int ( coin ) )
return self . coin_clients [ use_coinid ] [ interface_ind ]
2020-11-14 22:13:11 +00:00
2022-12-05 15:04:23 +00:00
def pi ( self , protocol_ind ) :
if protocol_ind not in self . protocolInterfaces :
raise ValueError ( ' Unknown protocol_ind {} ' . format ( int ( protocol_ind ) ) )
return self . protocolInterfaces [ protocol_ind ]
2020-10-31 20:08:30 +00:00
def createInterface ( self , coin ) :
if coin == Coins . PART :
2021-01-29 23:45:24 +00:00
return PARTInterface ( self . coin_clients [ coin ] , self . chain , self )
2020-10-31 20:08:30 +00:00
elif coin == Coins . BTC :
2021-01-29 23:45:24 +00:00
return BTCInterface ( self . coin_clients [ coin ] , self . chain , self )
2020-10-31 20:08:30 +00:00
elif coin == Coins . LTC :
2021-01-29 23:45:24 +00:00
return LTCInterface ( self . coin_clients [ coin ] , self . chain , self )
2020-11-07 11:08:07 +00:00
elif coin == Coins . NMC :
2021-01-29 23:45:24 +00:00
return NMCInterface ( self . coin_clients [ coin ] , self . chain , self )
2020-10-31 20:08:30 +00:00
elif coin == Coins . XMR :
2021-01-29 23:45:24 +00:00
xmr_i = XMRInterface ( self . coin_clients [ coin ] , self . chain , self )
2020-11-21 13:16:27 +00:00
chain_client_settings = self . getChainClientSettings ( coin )
xmr_i . setWalletFilename ( chain_client_settings [ ' walletfile ' ] )
return xmr_i
2022-08-10 22:02:36 +00:00
elif coin == Coins . PIVX :
return PIVXInterface ( self . coin_clients [ coin ] , self . chain , self )
2022-10-20 20:23:25 +00:00
elif coin == Coins . DASH :
return DASHInterface ( self . coin_clients [ coin ] , self . chain , self )
2022-11-07 20:31:10 +00:00
elif coin == Coins . FIRO :
return FIROInterface ( self . coin_clients [ coin ] , self . chain , self )
2020-10-31 20:08:30 +00:00
else :
raise ValueError ( ' Unknown coin type ' )
2021-02-04 22:42:15 +00:00
def createPassthroughInterface ( self , coin ) :
2021-01-26 19:25:33 +00:00
if coin == Coins . BTC :
2021-02-04 22:42:15 +00:00
return PassthroughBTCInterface ( self . coin_clients [ coin ] , self . chain )
2021-01-26 19:25:33 +00:00
else :
raise ValueError ( ' Unknown coin type ' )
2019-07-31 16:38:19 +00:00
def setCoinRunParams ( self , coin ) :
cc = self . coin_clients [ coin ]
2020-11-07 11:08:07 +00:00
if coin == Coins . XMR :
return
2019-07-31 16:38:19 +00:00
if cc [ ' connection_type ' ] == ' rpc ' and cc [ ' rpcauth ' ] is None :
chain_client_settings = self . getChainClientSettings ( coin )
authcookiepath = os . path . join ( self . getChainDatadirPath ( coin ) , ' .cookie ' )
2019-07-31 19:09:05 +00:00
2019-07-31 19:22:07 +00:00
pidfilename = cc [ ' name ' ]
2022-11-07 20:31:10 +00:00
if cc [ ' name ' ] in ( ' bitcoin ' , ' litecoin ' , ' namecoin ' , ' dash ' , ' firo ' ) :
2019-07-31 19:22:07 +00:00
pidfilename + = ' d '
2022-06-16 13:37:32 +00:00
2019-07-31 19:09:05 +00:00
pidfilepath = os . path . join ( self . getChainDatadirPath ( coin ) , pidfilename + ' .pid ' )
2019-07-31 16:38:19 +00:00
self . log . debug ( ' Reading %s rpc credentials from auth cookie %s ' , coin , authcookiepath )
# Wait for daemon to start
# Test pids to ensure authcookie is read for the correct process
2019-07-31 16:53:44 +00:00
datadir_pid = - 1
2019-07-31 16:38:19 +00:00
for i in range ( 20 ) :
try :
2022-06-16 13:37:32 +00:00
# Workaround for mismatched pid file name in litecoin 0.21.2
2022-07-15 14:38:05 +00:00
# Also set with pid= in .conf
2022-06-16 13:37:32 +00:00
# TODO: Remove
2022-07-15 14:38:05 +00:00
if cc [ ' name ' ] == ' litecoin ' and ( not os . path . exists ( pidfilepath ) ) and \
2022-06-16 13:37:32 +00:00
os . path . exists ( os . path . join ( self . getChainDatadirPath ( coin ) , ' bitcoind.pid ' ) ) :
pidfilepath = os . path . join ( self . getChainDatadirPath ( coin ) , ' bitcoind.pid ' )
2019-07-31 16:38:19 +00:00
with open ( pidfilepath , ' rb ' ) as fp :
datadir_pid = int ( fp . read ( ) . decode ( ' utf-8 ' ) )
2022-07-31 18:01:49 +00:00
assert ( datadir_pid == cc [ ' pid ' ] ) , ' Mismatched pid '
assert ( os . path . exists ( authcookiepath ) )
2022-08-10 21:58:53 +00:00
break
except Exception as e :
if self . debug :
self . log . warning ( ' Error, iteration %d : %s ' , i , str ( e ) )
2022-07-15 14:38:05 +00:00
self . delay_event . wait ( 0.5 )
2019-07-31 16:38:19 +00:00
try :
2020-12-10 14:37:26 +00:00
if os . name != ' nt ' or cc [ ' core_version_group ' ] > 17 : # Litecoin on windows doesn't write a pid file
2022-07-31 18:01:49 +00:00
assert ( datadir_pid == cc [ ' pid ' ] ) , ' Mismatched pid '
2019-07-31 16:38:19 +00:00
with open ( authcookiepath , ' rb ' ) as fp :
cc [ ' rpcauth ' ] = fp . read ( ) . decode ( ' utf-8 ' )
2020-12-03 23:46:01 +00:00
except Exception as e :
self . log . error ( ' Unable to read authcookie for %s , %s , datadir pid %d , daemon pid %s . Error: %s ' , str ( coin ) , authcookiepath , datadir_pid , cc [ ' pid ' ] , str ( e ) )
2019-07-31 16:38:19 +00:00
raise ValueError ( ' Error, terminating ' )
2020-11-07 11:08:07 +00:00
def createCoinInterface ( self , coin ) :
if self . coin_clients [ coin ] [ ' connection_type ' ] == ' rpc ' :
self . coin_clients [ coin ] [ ' interface ' ] = self . createInterface ( coin )
2021-02-11 12:57:54 +00:00
if coin == Coins . PART :
self . coin_clients [ coin ] [ ' interface_anon ' ] = PARTInterfaceAnon ( self . coin_clients [ coin ] , self . chain , self )
2021-11-01 13:52:40 +00:00
self . coin_clients [ coin ] [ ' interface_blind ' ] = PARTInterfaceBlind ( self . coin_clients [ coin ] , self . chain , self )
2021-02-04 22:42:15 +00:00
elif self . coin_clients [ coin ] [ ' connection_type ' ] == ' passthrough ' :
self . coin_clients [ coin ] [ ' interface ' ] = self . createPassthroughInterface ( coin )
2020-11-07 11:08:07 +00:00
2019-07-17 15:12:06 +00:00
def start ( self ) :
2020-12-08 18:56:05 +00:00
self . log . info ( ' Starting BasicSwap %s , database v %d \n \n ' , __version__ , self . db_version )
2019-07-23 22:33:27 +00:00
self . log . info ( ' sqlalchemy version %s ' , sa . __version__ )
2021-02-14 13:06:46 +00:00
self . log . info ( ' timezone offset: %d ( %s ) ' , time . timezone , time . tzname [ 0 ] )
2019-07-17 15:12:06 +00:00
2022-05-23 21:51:06 +00:00
upgradeDatabase ( self , self . db_version )
upgradeDatabaseData ( self , self . db_data_version )
2019-07-17 15:12:06 +00:00
2019-07-23 22:33:27 +00:00
for c in Coins :
2021-02-07 10:01:58 +00:00
if c not in chainparams :
2021-02-06 22:35:12 +00:00
continue
2019-07-31 16:38:19 +00:00
self . setCoinRunParams ( c )
2020-11-07 11:08:07 +00:00
self . createCoinInterface ( c )
2019-07-23 22:33:27 +00:00
if self . coin_clients [ c ] [ ' connection_type ' ] == ' rpc ' :
2022-12-12 22:12:28 +00:00
if c == Coins . BTC :
self . waitForDaemonRPC ( c , with_wallet = False )
if len ( self . callcoinrpc ( c , ' listwallets ' ) ) > = 1 :
self . waitForDaemonRPC ( c )
else :
self . waitForDaemonRPC ( c )
2020-12-04 17:06:50 +00:00
ci = self . ci ( c )
core_version = ci . getDaemonVersion ( )
self . log . info ( ' %s Core version %d ' , ci . coin_name ( ) , core_version )
2019-08-04 13:22:25 +00:00
self . coin_clients [ c ] [ ' core_version ' ] = core_version
2019-07-17 15:12:06 +00:00
2022-12-05 15:04:23 +00:00
thread_func = threadPollXMRChainState if c == Coins . XMR else threadPollChainState
t = threading . Thread ( target = thread_func , args = ( self , c ) )
2021-02-03 14:01:27 +00:00
self . threads . append ( t )
t . start ( )
2019-07-31 12:56:51 +00:00
if c == Coins . PART :
2020-12-04 17:06:50 +00:00
self . coin_clients [ c ] [ ' have_spent_index ' ] = ci . haveSpentIndex ( )
2020-11-29 23:05:30 +00:00
2021-06-28 21:56:45 +00:00
try :
# Sanity checks
rv = self . callcoinrpc ( c , ' extkey ' )
if ' result ' in rv and ' No keys to list. ' in rv [ ' result ' ] :
raise ValueError ( ' No keys loaded. ' )
if self . callcoinrpc ( c , ' getstakinginfo ' ) [ ' enabled ' ] is not False :
self . log . warning ( ' %s staking is not disabled. ' , ci . coin_name ( ) )
except Exception as e :
self . log . error ( ' Sanity checks failed: %s ' , str ( e ) )
2020-12-13 13:43:46 +00:00
2020-12-04 17:06:50 +00:00
elif c == Coins . XMR :
2022-11-16 22:36:13 +00:00
try :
ci . ensureWalletExists ( )
except Exception as e :
self . log . warning ( ' Can \' t open XMR wallet, could be locked. ' )
continue
2020-12-04 17:06:50 +00:00
2020-12-11 10:41:15 +00:00
self . checkWalletSeed ( c )
2019-07-31 12:56:51 +00:00
2020-12-13 13:43:46 +00:00
if ' p2p_host ' in self . settings :
network_key = self . getNetworkKey ( 1 )
2020-12-15 18:00:44 +00:00
self . _network = bsn . Network ( self . settings [ ' p2p_host ' ] , self . settings [ ' p2p_port ' ] , network_key , self )
2020-12-13 13:43:46 +00:00
self . _network . startNetwork ( )
2023-02-16 20:57:55 +00:00
self . log . debug ( ' network_key %s \n network_pubkey %s \n network_addr %s ' ,
self . network_key , self . network_pubkey , self . network_addr )
ro = self . callrpc ( ' smsglocalkeys ' )
found = False
for k in ro [ ' smsg_keys ' ] :
if k [ ' address ' ] == self . network_addr :
found = True
break
if not found :
self . log . info ( ' Importing network key to SMSG ' )
self . callrpc ( ' smsgimportprivkey ' , [ self . network_key , ' basicswap offers ' ] )
ro = self . callrpc ( ' smsglocalkeys ' , [ ' anon ' , ' - ' , self . network_addr ] )
ensure ( ro [ ' result ' ] == ' Success. ' , ' smsglocalkeys failed ' )
# TODO: Ensure smsg is enabled for the active wallet.
# Initialise locked state
_ , _ = self . getLockedState ( )
# Re-load in-progress bids
self . loadFromDB ( )
# Scan inbox
# TODO: Redundant? small window for zmq messages to go unnoticed during startup?
# options = {'encoding': 'hex'}
options = { ' encoding ' : ' none ' }
ro = self . callrpc ( ' smsginbox ' , [ ' unread ' , ' ' , options ] )
nm = 0
for msg in ro [ ' messages ' ] :
# TODO: Remove workaround for smsginbox bug
get_msg = self . callrpc ( ' smsg ' , [ msg [ ' msgid ' ] , { ' encoding ' : ' hex ' , ' setread ' : True } ] )
self . processMsg ( get_msg )
nm + = 1
self . log . info ( ' Scanned %d unread messages. ' , nm )
2019-07-17 15:12:06 +00:00
2023-02-17 21:14:17 +00:00
def stopDaemon ( self , coin ) - > None :
2020-12-03 23:46:01 +00:00
if coin == Coins . XMR :
return
2019-07-31 16:38:19 +00:00
num_tries = 10
authcookiepath = os . path . join ( self . getChainDatadirPath ( coin ) , ' .cookie ' )
stopping = False
try :
for i in range ( num_tries ) :
rv = self . callcoincli ( coin , ' stop ' , timeout = 10 )
self . log . debug ( ' Trying to stop %s ' , str ( coin ) )
stopping = True
time . sleep ( i + 1 )
except Exception as ex :
2022-08-17 22:21:32 +00:00
str_ex = str ( ex )
2022-10-13 22:49:58 +00:00
if ' Could not connect ' in str_ex or ' Could not locate RPC credentials ' in str_ex or ' couldn \' t connect to server ' in str_ex :
2019-07-31 16:38:19 +00:00
if stopping :
for i in range ( 30 ) :
# The lock file doesn't get deleted
# Using .cookie is a temporary workaround, will only work if rpc password is unset.
# TODO: Query lock on .lock properly
if os . path . exists ( authcookiepath ) :
self . log . debug ( ' Waiting on .cookie file %s ' , str ( coin ) )
time . sleep ( i + 1 )
time . sleep ( 4 ) # Extra time to settle
return
self . log . error ( ' stopDaemon %s ' , str ( ex ) )
2021-12-16 08:44:10 +00:00
self . log . error ( traceback . format_exc ( ) )
2019-07-31 16:38:19 +00:00
raise ValueError ( ' Could not stop {} ' . format ( str ( coin ) ) )
2023-02-17 21:14:17 +00:00
def stopDaemons ( self ) - > None :
2022-11-11 23:51:30 +00:00
for c in self . activeCoins ( ) :
2019-07-31 16:38:19 +00:00
chain_client_settings = self . getChainClientSettings ( c )
2022-11-11 23:51:30 +00:00
if chain_client_settings [ ' manage_daemon ' ] is True :
2019-07-31 16:38:19 +00:00
self . stopDaemon ( c )
2023-02-17 21:14:17 +00:00
def waitForDaemonRPC ( self , coin_type , with_wallet = True ) - > None :
2021-11-10 10:44:40 +00:00
for i in range ( self . startup_tries ) :
2019-07-17 15:12:06 +00:00
if not self . is_running :
return
try :
2022-06-04 20:41:24 +00:00
self . coin_clients [ coin_type ] [ ' interface ' ] . testDaemonRPC ( with_wallet )
2019-07-17 16:24:54 +00:00
return
2019-07-17 15:12:06 +00:00
except Exception as ex :
2019-07-25 09:29:48 +00:00
self . log . warning ( ' Can \' t connect to %s RPC: %s . Trying again in %d second/s. ' , coin_type , str ( ex ) , ( 1 + i ) )
2019-07-17 15:12:06 +00:00
time . sleep ( 1 + i )
2019-07-23 22:33:27 +00:00
self . log . error ( ' Can \' t connect to %s RPC, exiting. ' , coin_type )
2021-11-10 10:44:40 +00:00
self . stopRunning ( 1 ) # systemd will try to restart the process if fail_code != 0
2019-07-17 15:12:06 +00:00
2023-02-19 19:52:22 +00:00
def checkCoinsReady ( self , coin_from , coin_to ) - > None :
2021-02-04 22:42:15 +00:00
check_coins = ( coin_from , coin_to )
2019-07-31 18:21:41 +00:00
for c in check_coins :
2023-02-19 19:52:22 +00:00
ci = self . ci ( c )
if self . _restrict_unknown_seed_wallets and not ci . knownWalletSeed ( ) :
raise ValueError ( ' {} has an unexpected wallet seed and " restrict_unknown_seed_wallets " is enabled. ' . format ( ci . coin_name ( ) ) )
2019-07-31 18:21:41 +00:00
if self . coin_clients [ c ] [ ' connection_type ' ] != ' rpc ' :
continue
2021-02-04 22:42:15 +00:00
if c == Coins . XMR :
continue # TODO
2023-02-19 19:52:22 +00:00
synced = round ( ci . getBlockchainInfo ( ) [ ' verificationprogress ' ] , 3 )
2021-02-04 22:42:15 +00:00
if synced < 1.0 :
2023-02-19 19:52:22 +00:00
raise ValueError ( ' {} chain is still syncing, currently at {} . ' . format ( ci . coin_name ( ) , synced ) )
2019-07-31 18:21:41 +00:00
2023-02-17 21:14:17 +00:00
def isSystemUnlocked ( self ) - > bool :
2023-02-16 20:57:55 +00:00
# TODO - Check all active coins
ci = self . ci ( Coins . PART )
return not ci . isWalletLocked ( )
2023-02-17 21:14:17 +00:00
def checkSystemStatus ( self ) - > None :
2022-11-11 23:51:30 +00:00
ci = self . ci ( Coins . PART )
if ci . isWalletLocked ( ) :
raise LockedCoinError ( Coins . PART )
def activeCoins ( self ) :
for c in Coins :
if c not in chainparams :
continue
chain_client_settings = self . getChainClientSettings ( c )
if self . coin_clients [ c ] [ ' connection_type ' ] == ' rpc ' :
yield c
2023-02-17 21:14:17 +00:00
def changeWalletPasswords ( self , old_password , new_password , coin = None ) - > None :
2022-11-16 22:36:13 +00:00
# Only the main wallet password is changed for monero, avoid issues by preventing until active swaps are complete
2022-11-12 20:17:49 +00:00
if len ( self . swaps_in_progress ) > 0 :
raise ValueError ( ' Can \' t change passwords while swaps are in progress ' )
2022-11-17 22:58:14 +00:00
if old_password == new_password :
raise ValueError ( ' Passwords must differ ' )
2022-11-18 21:31:52 +00:00
if len ( new_password ) < 4 :
raise ValueError ( ' New password is too short ' )
# Unlock wallets to ensure they all have the same password.
2022-11-11 23:51:30 +00:00
for c in self . activeCoins ( ) :
2022-11-18 21:31:52 +00:00
if coin and c != coin :
continue
2022-11-11 23:51:30 +00:00
ci = self . ci ( c )
try :
ci . unlockWallet ( old_password )
except Exception as e :
raise ValueError ( ' Failed to unlock {} ' . format ( ci . coin_name ( ) ) )
for c in self . activeCoins ( ) :
2022-11-18 21:31:52 +00:00
if coin and c != coin :
continue
2022-11-11 23:51:30 +00:00
self . ci ( c ) . changeWalletPassword ( old_password , new_password )
2022-11-18 21:31:52 +00:00
# Update cached state
if coin is None or coin == Coins . PART :
self . _is_encrypted , self . _is_locked = self . ci ( Coins . PART ) . isWalletEncryptedLocked ( )
2023-02-17 21:14:17 +00:00
def unlockWallets ( self , password , coin = None ) - > None :
2023-02-16 20:57:55 +00:00
self . _read_zmq_queue = False
2022-11-11 23:51:30 +00:00
for c in self . activeCoins ( ) :
2022-11-18 21:31:52 +00:00
if coin and c != coin :
continue
2022-11-11 23:51:30 +00:00
self . ci ( c ) . unlockWallet ( password )
2022-11-18 21:31:52 +00:00
if c == Coins . PART :
self . _is_locked = False
2022-11-11 23:51:30 +00:00
2023-02-16 20:57:55 +00:00
self . loadFromDB ( )
self . _read_zmq_queue = True
2023-02-17 21:14:17 +00:00
def lockWallets ( self , coin = None ) - > None :
2023-02-16 20:57:55 +00:00
self . _read_zmq_queue = False
self . swaps_in_progress . clear ( )
2022-11-11 23:51:30 +00:00
for c in self . activeCoins ( ) :
2022-11-18 21:31:52 +00:00
if coin and c != coin :
continue
2022-11-11 23:51:30 +00:00
self . ci ( c ) . lockWallet ( )
2022-11-18 21:31:52 +00:00
if c == Coins . PART :
self . _is_locked = True
2023-02-16 20:57:55 +00:00
self . _read_zmq_queue = True
2022-11-11 23:51:30 +00:00
2023-02-17 21:14:17 +00:00
def initialiseWallet ( self , coin_type , raise_errors = False ) - > None :
2020-12-04 17:06:50 +00:00
if coin_type == Coins . PART :
return
2020-12-03 23:46:01 +00:00
ci = self . ci ( coin_type )
2022-11-16 22:36:13 +00:00
db_key_coin_name = ci . coin_name ( ) . lower ( )
2020-12-03 23:46:01 +00:00
self . log . info ( ' Initialising {} wallet. ' . format ( ci . coin_name ( ) ) )
if coin_type == Coins . XMR :
key_view = self . getWalletKey ( coin_type , 1 , for_ed25519 = True )
key_spend = self . getWalletKey ( coin_type , 2 , for_ed25519 = True )
ci . initialiseWallet ( key_view , key_spend )
2020-12-04 17:06:50 +00:00
root_address = ci . getAddressFromKeys ( key_view , key_spend )
2022-11-16 22:36:13 +00:00
key_str = ' main_wallet_addr_ ' + db_key_coin_name
2020-12-04 17:06:50 +00:00
self . setStringKV ( key_str , root_address )
2020-12-03 23:46:01 +00:00
return
root_key = self . getWalletKey ( coin_type , 1 )
2022-11-08 14:43:28 +00:00
root_hash = ci . getSeedHash ( root_key )
2022-10-13 21:16:43 +00:00
try :
ci . initialiseWallet ( root_key )
except Exception as e :
# < 0.21: sethdseed cannot set a new HD seed while still in Initial Block Download.
2022-10-13 22:49:58 +00:00
self . log . error ( ' initialiseWallet failed: {} ' . format ( str ( e ) ) )
2022-10-13 21:16:43 +00:00
if raise_errors :
raise e
2022-11-16 22:36:13 +00:00
return
try :
session = self . openSession ( )
key_str = ' main_wallet_seedid_ ' + db_key_coin_name
self . setStringKV ( key_str , root_hash . hex ( ) , session )
# Clear any saved addresses
self . clearStringKV ( ' receive_addr_ ' + db_key_coin_name , session )
self . clearStringKV ( ' stealth_addr_ ' + db_key_coin_name , session )
2020-12-03 23:46:01 +00:00
2022-11-16 22:36:13 +00:00
coin_id = int ( coin_type )
info_type = 1 # wallet
query_str = f ' DELETE FROM wallets WHERE coin_id = { coin_id } AND balance_type = { info_type } '
session . execute ( query_str )
finally :
self . closeSession ( session )
2020-12-04 17:06:50 +00:00
2022-12-05 15:04:23 +00:00
def updateIdentityBidState ( self , session , address : str , bid ) - > None :
2021-11-14 23:26:43 +00:00
identity_stats = session . query ( KnownIdentity ) . filter_by ( address = address ) . first ( )
if not identity_stats :
2023-02-26 18:14:00 +00:00
identity_stats = KnownIdentity ( active_ind = 1 , address = address , created_at = self . getTime ( ) )
2021-11-14 23:26:43 +00:00
if bid . state == BidStates . SWAP_COMPLETED :
if bid . was_sent :
identity_stats . num_sent_bids_successful = zeroIfNone ( identity_stats . num_sent_bids_successful ) + 1
else :
identity_stats . num_recv_bids_successful = zeroIfNone ( identity_stats . num_recv_bids_successful ) + 1
2023-03-08 22:53:54 +00:00
elif bid . state in ( BidStates . BID_ERROR , BidStates . XMR_SWAP_FAILED_REFUNDED , BidStates . XMR_SWAP_FAILED_SWIPED , BidStates . XMR_SWAP_FAILED , BidStates . SWAP_TIMEDOUT ) :
2021-11-14 23:26:43 +00:00
if bid . was_sent :
identity_stats . num_sent_bids_failed = zeroIfNone ( identity_stats . num_sent_bids_failed ) + 1
else :
identity_stats . num_recv_bids_failed = zeroIfNone ( identity_stats . num_recv_bids_failed ) + 1
2023-02-26 18:14:00 +00:00
identity_stats . updated_at = self . getTime ( )
2021-11-14 23:26:43 +00:00
session . add ( identity_stats )
2022-12-05 15:04:23 +00:00
def setIntKVInSession ( self , str_key : str , int_val : int , session ) - > None :
2021-01-29 23:45:24 +00:00
kv = session . query ( DBKVInt ) . filter_by ( key = str_key ) . first ( )
if not kv :
kv = DBKVInt ( key = str_key , value = int_val )
else :
kv . value = int_val
session . add ( kv )
2022-12-05 15:04:23 +00:00
def setIntKV ( self , str_key : str , int_val : int ) - > None :
2020-12-04 17:06:50 +00:00
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2021-01-29 23:45:24 +00:00
self . setIntKVInSession ( str_key , int_val , session )
2020-12-04 17:06:50 +00:00
session . commit ( )
2021-01-11 21:48:46 +00:00
finally :
2020-12-04 17:06:50 +00:00
session . close ( )
session . remove ( )
self . mxDB . release ( )
2022-12-05 15:04:23 +00:00
def setStringKV ( self , str_key : str , str_val : str , session = None ) - > None :
2022-11-16 22:36:13 +00:00
try :
use_session = self . openSession ( session )
kv = use_session . query ( DBKVString ) . filter_by ( key = str_key ) . first ( )
if not kv :
kv = DBKVString ( key = str_key , value = str_val )
else :
kv . value = str_val
use_session . add ( kv )
finally :
if session is None :
self . closeSession ( use_session )
2020-12-04 17:06:50 +00:00
2023-05-10 15:50:40 +00:00
def getStringKV ( self , str_key : str , session = None ) - > Optional [ str ] :
2020-12-04 17:06:50 +00:00
try :
2023-05-10 15:50:40 +00:00
use_session = self . openSession ( session )
v = use_session . query ( DBKVString ) . filter_by ( key = str_key ) . first ( )
2020-12-04 17:06:50 +00:00
if not v :
return None
return v . value
finally :
2023-05-10 15:50:40 +00:00
if session is None :
self . closeSession ( use_session , commit = False )
2019-07-22 21:39:00 +00:00
2022-12-05 15:04:23 +00:00
def clearStringKV ( self , str_key : str , str_val : str ) - > None :
2022-11-16 22:36:13 +00:00
with self . mxDB :
try :
session = scoped_session ( self . session_factory )
2023-02-16 20:57:55 +00:00
session . execute ( ' DELETE FROM kv_string WHERE key = :key ' , { ' key ' : str_key } )
2022-11-16 22:36:13 +00:00
session . commit ( )
finally :
session . close ( )
session . remove ( )
2022-12-05 15:04:23 +00:00
def getPreFundedTx ( self , linked_type : int , linked_id : bytes , tx_type : int , session = None ) - > Optional [ bytes ] :
try :
use_session = self . openSession ( session )
tx = use_session . query ( PrefundedTx ) . filter_by ( linked_type = linked_type , linked_id = linked_id , tx_type = tx_type , used_by = None ) . first ( )
if not tx :
return None
tx . used_by = linked_id
use_session . add ( tx )
return tx . tx_data
finally :
if session is None :
self . closeSession ( use_session )
2019-08-05 18:31:02 +00:00
def activateBid ( self , session , bid ) :
if bid . bid_id in self . swaps_in_progress :
self . log . debug ( ' Bid %s is already in progress ' , bid . bid_id . hex ( ) )
self . log . debug ( ' Loading active bid %s ' , bid . bid_id . hex ( ) )
offer = session . query ( Offer ) . filter_by ( offer_id = bid . offer_id ) . first ( )
2021-10-21 22:47:04 +00:00
if not offer :
raise ValueError ( ' Offer not found ' )
2019-08-05 18:31:02 +00:00
2022-07-03 21:58:16 +00:00
self . loadBidTxns ( bid , session )
2020-12-04 21:30:20 +00:00
if offer . swap_type == SwapTypes . XMR_SWAP :
xmr_swap = session . query ( XmrSwap ) . filter_by ( bid_id = bid . bid_id ) . first ( )
self . watchXmrSwap ( bid , offer , xmr_swap )
else :
self . swaps_in_progress [ bid . bid_id ] = ( bid , offer )
2019-08-05 18:31:02 +00:00
2020-12-04 21:30:20 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
if bid . initiate_tx and bid . initiate_tx . txid :
self . addWatchedOutput ( coin_from , bid . bid_id , bid . initiate_tx . txid . hex ( ) , bid . initiate_tx . vout , BidStates . SWAP_INITIATED )
if bid . participate_tx and bid . participate_tx . txid :
self . addWatchedOutput ( coin_to , bid . bid_id , bid . participate_tx . txid . hex ( ) , bid . participate_tx . vout , BidStates . SWAP_PARTICIPATING )
2019-08-05 18:31:02 +00:00
2020-12-05 11:22:22 +00:00
if self . coin_clients [ coin_from ] [ ' last_height_checked ' ] < 1 :
if bid . initiate_tx and bid . initiate_tx . chain_height :
self . coin_clients [ coin_from ] [ ' last_height_checked ' ] = bid . initiate_tx . chain_height
if self . coin_clients [ coin_to ] [ ' last_height_checked ' ] < 1 :
if bid . participate_tx and bid . participate_tx . chain_height :
self . coin_clients [ coin_to ] [ ' last_height_checked ' ] = bid . participate_tx . chain_height
2019-08-05 18:31:02 +00:00
# TODO process addresspool if bid has previously been abandoned
2020-12-04 17:06:50 +00:00
def deactivateBid ( self , session , offer , bid ) :
2019-08-05 18:31:02 +00:00
# Remove from in progress
2020-11-29 11:46:00 +00:00
self . log . debug ( ' Removing bid from in-progress: %s ' , bid . bid_id . hex ( ) )
2019-08-05 18:31:02 +00:00
self . swaps_in_progress . pop ( bid . bid_id , None )
2020-12-04 21:30:20 +00:00
bid . in_progress = 0
if session is None :
self . saveBid ( bid . bid_id , bid )
2019-08-05 18:31:02 +00:00
# Remove any watched outputs
self . removeWatchedOutput ( Coins ( offer . coin_from ) , bid . bid_id , None )
self . removeWatchedOutput ( Coins ( offer . coin_to ) , bid . bid_id , None )
if bid . state == BidStates . BID_ABANDONED or bid . state == BidStates . SWAP_COMPLETED :
# Return unused addrs to pool
2020-11-29 11:46:00 +00:00
itx_state = bid . getITxState ( )
ptx_state = bid . getPTxState ( )
if itx_state is not None and itx_state != TxStates . TX_REDEEMED :
2019-08-05 22:04:40 +00:00
self . returnAddressToPool ( bid . bid_id , TxTypes . ITX_REDEEM )
2020-11-29 11:46:00 +00:00
if itx_state is not None and itx_state != TxStates . TX_REFUNDED :
2019-08-05 22:04:40 +00:00
self . returnAddressToPool ( bid . bid_id , TxTypes . ITX_REFUND )
2020-11-29 11:46:00 +00:00
if ptx_state is not None and ptx_state != TxStates . TX_REDEEMED :
2019-08-05 22:04:40 +00:00
self . returnAddressToPool ( bid . bid_id , TxTypes . PTX_REDEEM )
2020-11-29 11:46:00 +00:00
if ptx_state is not None and ptx_state != TxStates . TX_REFUNDED :
2019-08-05 22:04:40 +00:00
self . returnAddressToPool ( bid . bid_id , TxTypes . PTX_REFUND )
2019-08-05 18:31:02 +00:00
2021-01-09 13:00:25 +00:00
try :
2022-10-13 20:21:43 +00:00
use_session = self . openSession ( session )
2021-01-09 13:00:25 +00:00
# Remove any delayed events
if self . debug :
2022-06-06 21:03:31 +00:00
use_session . execute ( ' UPDATE actions SET active_ind = 2 WHERE linked_id = x \' {} \' ' . format ( bid . bid_id . hex ( ) ) )
2021-01-09 13:00:25 +00:00
else :
2022-06-06 21:03:31 +00:00
use_session . execute ( ' DELETE FROM actions WHERE linked_id = x \' {} \' ' . format ( bid . bid_id . hex ( ) ) )
2021-01-09 13:00:25 +00:00
# Unlock locked inputs (TODO)
if offer . swap_type == SwapTypes . XMR_SWAP :
xmr_swap = use_session . query ( XmrSwap ) . filter_by ( bid_id = bid . bid_id ) . first ( )
if xmr_swap :
try :
self . ci ( offer . coin_from ) . unlockInputs ( xmr_swap . a_lock_tx )
except Exception as e :
2021-11-05 22:34:25 +00:00
self . log . debug ( ' unlockInputs failed {} ' . format ( str ( e ) ) )
2021-01-09 13:00:25 +00:00
pass # Invalid parameter, unknown transaction
2021-11-05 22:34:25 +00:00
elif SwapTypes . SELLER_FIRST :
pass # No prevouts are locked
2021-11-14 23:26:43 +00:00
# Update identity stats
2023-03-08 22:53:54 +00:00
if bid . state in ( BidStates . BID_ERROR , BidStates . XMR_SWAP_FAILED_REFUNDED , BidStates . XMR_SWAP_FAILED_SWIPED , BidStates . XMR_SWAP_FAILED , BidStates . SWAP_COMPLETED , BidStates . SWAP_TIMEDOUT ) :
2021-11-14 23:26:43 +00:00
peer_address = offer . addr_from if bid . was_sent else bid . bid_addr
self . updateIdentityBidState ( use_session , peer_address , bid )
2021-01-09 13:00:25 +00:00
finally :
if session is None :
2022-10-13 20:21:43 +00:00
self . closeSession ( use_session )
2021-01-08 22:12:08 +00:00
2023-02-16 20:57:55 +00:00
def loadFromDB ( self ) - > None :
if self . isSystemUnlocked ( ) is False :
self . log . info ( ' Not loading from db. System is locked. ' )
return
2019-07-17 15:12:06 +00:00
self . log . info ( ' Loading data from db ' )
self . mxDB . acquire ( )
2022-12-01 18:51:06 +00:00
self . swaps_in_progress . clear ( )
2019-07-17 15:12:06 +00:00
try :
session = scoped_session ( self . session_factory )
for bid in session . query ( Bid ) :
2020-12-04 21:30:20 +00:00
if bid . in_progress == 1 or ( bid . state and bid . state > BidStates . BID_RECEIVED and bid . state < BidStates . SWAP_COMPLETED ) :
2022-01-01 22:04:17 +00:00
try :
self . activateBid ( session , bid )
except Exception as ex :
2022-10-26 15:47:30 +00:00
self . logException ( f ' Failed to activate bid! Error: { ex } ' )
2022-01-01 22:04:17 +00:00
try :
2022-01-01 22:18:17 +00:00
bid . setState ( BidStates . BID_ERROR , ' Failed to activate ' )
offer = session . query ( Offer ) . filter_by ( offer_id = bid . offer_id ) . first ( )
self . deactivateBid ( session , offer , bid )
2022-01-01 22:04:17 +00:00
except Exception as ex :
2022-10-26 15:47:30 +00:00
self . logException ( f ' Further error deactivating: { ex } ' )
2022-10-13 20:21:43 +00:00
self . buildNotificationsCache ( session )
2019-07-17 15:12:06 +00:00
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2022-07-01 14:37:10 +00:00
def getActiveBidMsgValidTime ( self ) :
return self . SMSG_SECONDS_IN_HOUR * 48
def getAcceptBidMsgValidTime ( self , bid ) :
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2022-07-01 14:37:10 +00:00
smsg_max_valid = self . SMSG_SECONDS_IN_HOUR * 48
smsg_min_valid = self . SMSG_SECONDS_IN_HOUR * 1
bid_valid = ( bid . expire_at - now ) + 10 * 60 # Add 10 minute buffer
return max ( smsg_min_valid , min ( smsg_max_valid , bid_valid ) )
def sendSmsg ( self , addr_from , addr_to , payload_hex , msg_valid ) :
options = { ' decodehex ' : True , ' ttl_is_seconds ' : True }
ro = self . callrpc ( ' smsgsend ' , [ addr_from , addr_to , payload_hex , False , msg_valid , False , options ] )
return bytes . fromhex ( ro [ ' msgid ' ] )
2020-12-04 21:30:20 +00:00
def validateSwapType ( self , coin_from , coin_to , swap_type ) :
if coin_from == Coins . XMR :
2021-12-10 07:50:36 +00:00
raise ValueError ( ' TODO: XMR coin_from ' )
2020-12-04 21:30:20 +00:00
if coin_to == Coins . XMR and swap_type != SwapTypes . XMR_SWAP :
raise ValueError ( ' Invalid swap type for XMR ' )
2021-12-10 07:50:36 +00:00
if coin_from == Coins . PART_ANON :
raise ValueError ( ' TODO: PART_ANON coin_from ' )
if coin_to == Coins . PART_ANON and swap_type != SwapTypes . XMR_SWAP :
raise ValueError ( ' Invalid swap type for PART_ANON ' )
if ( coin_from == Coins . PART_BLIND or coin_to == Coins . PART_BLIND ) and swap_type != SwapTypes . XMR_SWAP :
raise ValueError ( ' Invalid swap type for PART_BLIND ' )
2022-11-07 20:31:10 +00:00
if coin_from in ( Coins . PIVX , Coins . DASH , Coins . FIRO , Coins . NMC ) and swap_type == SwapTypes . XMR_SWAP :
raise ValueError ( ' TODO: {} -> XMR ' . format ( coin_from . name ) )
2020-12-04 21:30:20 +00:00
2022-10-13 20:21:43 +00:00
def notify ( self , event_type , event_data , session = None ) :
show_event = event_type not in self . _disabled_notification_types
2022-07-31 17:33:01 +00:00
if event_type == NT . OFFER_RECEIVED :
self . log . debug ( ' Received new offer %s ' , event_data [ ' offer_id ' ] )
2022-10-13 20:21:43 +00:00
if self . ws_server and show_event :
2022-07-31 17:33:01 +00:00
event_data [ ' event ' ] = ' new_offer '
self . ws_server . send_message_to_all ( json . dumps ( event_data ) )
elif event_type == NT . BID_RECEIVED :
self . log . info ( ' Received valid bid %s for %s offer %s ' , event_data [ ' bid_id ' ] , event_data [ ' type ' ] , event_data [ ' offer_id ' ] )
2022-10-13 20:21:43 +00:00
if self . ws_server and show_event :
2022-07-31 17:33:01 +00:00
event_data [ ' event ' ] = ' new_bid '
self . ws_server . send_message_to_all ( json . dumps ( event_data ) )
elif event_type == NT . BID_ACCEPTED :
self . log . info ( ' Received valid bid accept for %s ' , event_data [ ' bid_id ' ] )
2022-10-13 20:21:43 +00:00
if self . ws_server and show_event :
2022-07-31 17:33:01 +00:00
event_data [ ' event ' ] = ' bid_accepted '
self . ws_server . send_message_to_all ( json . dumps ( event_data ) )
else :
self . log . warning ( f ' Unknown notification { event_type } ' )
2022-10-13 20:21:43 +00:00
try :
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2022-10-13 20:21:43 +00:00
use_session = self . openSession ( session )
use_session . add ( Notification (
active_ind = 1 ,
created_at = now ,
event_type = int ( event_type ) ,
event_data = bytes ( json . dumps ( event_data ) , ' UTF-8 ' ) ,
) )
use_session . execute ( f ' DELETE FROM notifications WHERE record_id NOT IN (SELECT record_id FROM notifications WHERE active_ind=1 ORDER BY created_at ASC LIMIT { self . _keep_notifications } ) ' )
if show_event :
self . _notifications_cache [ now ] = ( event_type , event_data )
while len ( self . _notifications_cache ) > self . _show_notifications :
# dicts preserve insertion order in Python 3.7+
self . _notifications_cache . pop ( next ( iter ( self . _notifications_cache ) ) )
finally :
if session is None :
self . closeSession ( use_session )
def buildNotificationsCache ( self , session ) :
2022-11-14 19:47:07 +00:00
self . _notifications_cache . clear ( )
2023-02-14 21:34:01 +00:00
q = session . execute ( f ' SELECT created_at, event_type, event_data FROM notifications WHERE active_ind = 1 ORDER BY created_at ASC LIMIT { self . _show_notifications } ' )
2022-10-13 20:21:43 +00:00
for entry in q :
self . _notifications_cache [ entry [ 0 ] ] = ( entry [ 1 ] , json . loads ( entry [ 2 ] . decode ( ' UTF-8 ' ) ) )
def getNotifications ( self ) :
rv = [ ]
for k , v in self . _notifications_cache . items ( ) :
2022-10-20 23:50:49 +00:00
rv . append ( ( time . strftime ( ' %d - % m- % y % H: % M: % S ' , time . localtime ( k ) ) , int ( v [ 0 ] ) , v [ 1 ] ) )
2022-10-13 20:21:43 +00:00
return rv
2023-02-14 21:34:01 +00:00
def setIdentityData ( self , filters , data ) :
address = filters [ ' address ' ]
ci = self . ci ( Coins . PART )
ensure ( ci . isValidAddress ( address ) , ' Invalid identity address ' )
try :
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2023-02-14 21:34:01 +00:00
session = self . openSession ( )
2023-02-15 21:51:55 +00:00
q = session . execute ( ' SELECT COUNT(*) FROM knownidentities WHERE address = :address ' , { ' address ' : address } ) . first ( )
2023-02-14 21:34:01 +00:00
if q [ 0 ] < 1 :
2023-02-15 21:51:55 +00:00
session . execute ( ' INSERT INTO knownidentities (active_ind, address, created_at) VALUES (1, :address, :now) ' , { ' address ' : address , ' now ' : now } )
2023-02-14 21:34:01 +00:00
if ' label ' in data :
2023-02-15 21:51:55 +00:00
session . execute ( ' UPDATE knownidentities SET label = :label WHERE address = :address ' , { ' address ' : address , ' label ' : data [ ' label ' ] } )
if ' automation_override ' in data :
new_value : int = 0
data_value = data [ ' automation_override ' ]
if isinstance ( data_value , int ) :
new_value = data_value
elif isinstance ( data_value , str ) :
if data_value . isdigit ( ) :
new_value = int ( data_value )
elif data_value == ' default ' :
new_value = 0
elif data_value == ' always_accept ' :
new_value = int ( AutomationOverrideOptions . ALWAYS_ACCEPT )
elif data_value == ' never_accept ' :
new_value = int ( AutomationOverrideOptions . NEVER_ACCEPT )
else :
raise ValueError ( ' Unknown automation_override value ' )
else :
raise ValueError ( ' Unknown automation_override type ' )
session . execute ( ' UPDATE knownidentities SET automation_override = :new_value WHERE address = :address ' , { ' address ' : address , ' new_value ' : new_value } )
if ' visibility_override ' in data :
new_value : int = 0
data_value = data [ ' visibility_override ' ]
if isinstance ( data_value , int ) :
new_value = data_value
elif isinstance ( data_value , str ) :
if data_value . isdigit ( ) :
new_value = int ( data_value )
elif data_value == ' default ' :
new_value = 0
elif data_value == ' hide ' :
new_value = int ( VisibilityOverrideOptions . HIDE )
elif data_value == ' block ' :
new_value = int ( VisibilityOverrideOptions . BLOCK )
else :
raise ValueError ( ' Unknown visibility_override value ' )
else :
raise ValueError ( ' Unknown visibility_override type ' )
session . execute ( ' UPDATE knownidentities SET visibility_override = :new_value WHERE address = :address ' , { ' address ' : address , ' new_value ' : new_value } )
if ' note ' in data :
session . execute ( ' UPDATE knownidentities SET note = :note WHERE address = :address ' , { ' address ' : address , ' note ' : data [ ' note ' ] } )
2023-02-14 21:34:01 +00:00
finally :
self . closeSession ( session )
2023-02-19 14:31:11 +00:00
def listIdentities ( self , filters = { } ) :
2023-02-14 21:34:01 +00:00
try :
session = self . openSession ( )
query_str = ' SELECT address, label, num_sent_bids_successful, num_recv_bids_successful, ' + \
2023-02-15 21:51:55 +00:00
' num_sent_bids_rejected, num_recv_bids_rejected, num_sent_bids_failed, num_recv_bids_failed, ' + \
' automation_override, visibility_override, note ' + \
2023-02-14 21:34:01 +00:00
' FROM knownidentities ' + \
' WHERE active_ind = 1 '
address = filters . get ( ' address ' , None )
if address is not None :
query_str + = f ' AND address = " { address } " '
sort_dir = filters . get ( ' sort_dir ' , ' DESC ' ) . upper ( )
sort_by = filters . get ( ' sort_by ' , ' created_at ' )
query_str + = f ' ORDER BY { sort_by } { sort_dir } '
limit = filters . get ( ' limit ' , None )
if limit is not None :
query_str + = f ' LIMIT { limit } '
offset = filters . get ( ' offset ' , None )
if offset is not None :
query_str + = f ' OFFSET { offset } '
q = session . execute ( query_str )
rv = [ ]
for row in q :
identity = {
' address ' : row [ 0 ] ,
' label ' : row [ 1 ] ,
' num_sent_bids_successful ' : zeroIfNone ( row [ 2 ] ) ,
' num_recv_bids_successful ' : zeroIfNone ( row [ 3 ] ) ,
' num_sent_bids_rejected ' : zeroIfNone ( row [ 4 ] ) ,
' num_recv_bids_rejected ' : zeroIfNone ( row [ 5 ] ) ,
' num_sent_bids_failed ' : zeroIfNone ( row [ 6 ] ) ,
' num_recv_bids_failed ' : zeroIfNone ( row [ 7 ] ) ,
2023-02-15 21:51:55 +00:00
' automation_override ' : zeroIfNone ( row [ 8 ] ) ,
' visibility_override ' : zeroIfNone ( row [ 9 ] ) ,
' note ' : row [ 10 ] ,
2023-02-14 21:34:01 +00:00
}
rv . append ( identity )
return rv
finally :
2023-02-19 14:31:11 +00:00
self . closeSession ( session , commit = False )
2023-02-14 21:34:01 +00:00
2022-10-13 20:21:43 +00:00
def vacuumDB ( self ) :
try :
session = self . openSession ( )
return session . execute ( ' VACUUM ' )
finally :
self . closeSession ( session )
2019-07-17 15:12:06 +00:00
def validateOfferAmounts ( self , coin_from , coin_to , amount , rate , min_bid_amount ) :
2021-02-11 12:57:54 +00:00
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
2021-10-21 22:47:04 +00:00
ensure ( amount > = min_bid_amount , ' amount < min_bid_amount ' )
ensure ( amount > ci_from . min_amount ( ) , ' From amount below min value for chain ' )
ensure ( amount < ci_from . max_amount ( ) , ' From amount above max value for chain ' )
2019-07-17 15:12:06 +00:00
2021-02-11 12:57:54 +00:00
amount_to = int ( ( amount * rate ) / / ci_from . COIN ( ) )
2021-10-21 22:47:04 +00:00
ensure ( amount_to > ci_to . min_amount ( ) , ' To amount below min value for chain ' )
ensure ( amount_to < ci_to . max_amount ( ) , ' To amount above max value for chain ' )
2019-07-17 15:12:06 +00:00
2022-12-10 23:26:42 +00:00
def validateOfferLockValue ( self , swap_type , coin_from , coin_to , lock_type , lock_value ) :
2022-08-10 22:02:36 +00:00
coin_from_has_csv = self . coin_clients [ coin_from ] [ ' use_csv ' ]
coin_to_has_csv = self . coin_clients [ coin_to ] [ ' use_csv ' ]
2019-07-21 20:10:21 +00:00
if lock_type == OfferMessage . SEQUENCE_LOCK_TIME :
2021-10-21 22:47:04 +00:00
ensure ( lock_value > = self . min_sequence_lock_seconds and lock_value < = self . max_sequence_lock_seconds , ' Invalid lock_value time ' )
2022-12-10 23:26:42 +00:00
if swap_type == SwapTypes . XMR_SWAP :
ensure ( coin_from_has_csv , ' Coin from needs CSV activated. ' )
else :
ensure ( coin_from_has_csv and coin_to_has_csv , ' Both coins need CSV activated. ' )
2019-07-21 20:10:21 +00:00
elif lock_type == OfferMessage . SEQUENCE_LOCK_BLOCKS :
2021-10-21 22:47:04 +00:00
ensure ( lock_value > = 5 and lock_value < = 1000 , ' Invalid lock_value blocks ' )
2022-12-10 23:26:42 +00:00
if swap_type == SwapTypes . XMR_SWAP :
ensure ( coin_from_has_csv , ' Coin from needs CSV activated. ' )
else :
ensure ( coin_from_has_csv and coin_to_has_csv , ' Both coins need CSV activated. ' )
2021-12-15 13:41:43 +00:00
elif lock_type == TxLockTypes . ABS_LOCK_TIME :
2019-07-24 22:59:40 +00:00
# TODO: range?
2022-08-10 22:02:36 +00:00
ensure ( not coin_from_has_csv or not coin_to_has_csv , ' Should use CSV. ' )
2021-10-21 22:47:04 +00:00
ensure ( lock_value > = 4 * 60 * 60 and lock_value < = 96 * 60 * 60 , ' Invalid lock_value time ' )
2021-12-15 13:41:43 +00:00
elif lock_type == TxLockTypes . ABS_LOCK_BLOCKS :
2019-07-24 22:59:40 +00:00
# TODO: range?
2022-08-10 22:02:36 +00:00
ensure ( not coin_from_has_csv or not coin_to_has_csv , ' Should use CSV. ' )
2021-10-21 22:47:04 +00:00
ensure ( lock_value > = 10 and lock_value < = 1000 , ' Invalid lock_value blocks ' )
2019-07-21 20:10:21 +00:00
else :
raise ValueError ( ' Unknown locktype ' )
2021-02-15 13:34:47 +00:00
def validateOfferValidTime ( self , offer_type , coin_from , coin_to , valid_for_seconds ) :
# TODO: adjust
if valid_for_seconds < 10 * 60 :
2021-02-13 22:54:01 +00:00
raise ValueError ( ' Offer TTL too low ' )
if valid_for_seconds > 48 * 60 * 60 :
raise ValueError ( ' Offer TTL too high ' )
2021-02-15 13:34:47 +00:00
def validateBidValidTime ( self , offer_type , coin_from , coin_to , valid_for_seconds ) :
# TODO: adjust
if valid_for_seconds < 10 * 60 :
raise ValueError ( ' Bid TTL too low ' )
if valid_for_seconds > 24 * 60 * 60 :
raise ValueError ( ' Bid TTL too high ' )
2021-11-22 20:24:48 +00:00
def validateBidAmount ( self , offer , bid_amount , bid_rate ) :
ensure ( bid_amount > = offer . min_bid_amount , ' Bid amount below minimum ' )
ensure ( bid_amount < = offer . amount_from , ' Bid amount above offer amount ' )
if not offer . amount_negotiable :
ensure ( offer . amount_from == bid_amount , ' Bid amount must match offer amount. ' )
if not offer . rate_negotiable :
ensure ( offer . rate == bid_rate , ' Bid rate must match offer rate. ' )
2021-10-20 17:47:49 +00:00
def getOfferAddressTo ( self , extra_options ) :
if ' addr_send_to ' in extra_options :
return extra_options [ ' addr_send_to ' ]
return self . network_addr
2019-07-17 15:12:06 +00:00
def postOffer ( self , coin_from , coin_to , amount , rate , min_bid_amount , swap_type ,
2021-12-15 13:41:43 +00:00
lock_type = TxLockTypes . SEQUENCE_LOCK_TIME , lock_value = 48 * 60 * 60 , auto_accept_bids = False , addr_send_from = None , extra_options = { } ) :
2019-07-17 15:12:06 +00:00
# Offer to send offer.amount_from of coin_from in exchange for offer.amount_from * offer.rate of coin_to
2021-10-21 22:47:04 +00:00
ensure ( coin_from != coin_to , ' coin_from == coin_to ' )
2019-07-17 15:12:06 +00:00
try :
coin_from_t = Coins ( coin_from )
2021-02-03 14:01:27 +00:00
ci_from = self . ci ( coin_from_t )
2019-07-17 15:12:06 +00:00
except Exception :
raise ValueError ( ' Unknown coin from type ' )
try :
coin_to_t = Coins ( coin_to )
2021-02-03 14:01:27 +00:00
ci_to = self . ci ( coin_to_t )
2019-07-17 15:12:06 +00:00
except Exception :
raise ValueError ( ' Unknown coin to type ' )
2021-02-13 22:54:01 +00:00
valid_for_seconds = extra_options . get ( ' valid_for_seconds ' , 60 * 60 )
2020-12-04 21:30:20 +00:00
self . validateSwapType ( coin_from_t , coin_to_t , swap_type )
2019-07-17 15:12:06 +00:00
self . validateOfferAmounts ( coin_from_t , coin_to_t , amount , rate , min_bid_amount )
2022-12-10 23:26:42 +00:00
self . validateOfferLockValue ( swap_type , coin_from_t , coin_to_t , lock_type , lock_value )
2021-02-15 13:34:47 +00:00
self . validateOfferValidTime ( swap_type , coin_from_t , coin_to_t , valid_for_seconds )
2019-07-17 15:12:06 +00:00
2021-10-20 17:47:49 +00:00
offer_addr_to = self . getOfferAddressTo ( extra_options )
2019-07-17 15:12:06 +00:00
self . mxDB . acquire ( )
2021-01-11 21:48:46 +00:00
session = None
2019-07-17 15:12:06 +00:00
try :
2023-02-19 19:52:22 +00:00
self . checkCoinsReady ( coin_from_t , coin_to_t )
2021-11-26 01:13:20 +00:00
offer_addr = self . newSMSGAddress ( use_type = AddressTypes . OFFER ) [ 0 ] if addr_send_from is None else addr_send_from
2023-02-26 18:14:00 +00:00
offer_created_at = self . getTime ( )
2020-11-07 11:08:07 +00:00
2020-11-14 22:13:11 +00:00
msg_buf = OfferMessage ( )
2019-07-29 10:14:46 +00:00
2023-05-12 08:20:52 +00:00
msg_buf . protocol_version = PROTOCOL_VERSION_ADAPTOR_SIG if swap_type == SwapTypes . XMR_SWAP else PROTOCOL_VERSION_SECRET_HASH
2019-07-17 15:12:06 +00:00
msg_buf . coin_from = int ( coin_from )
msg_buf . coin_to = int ( coin_to )
2019-07-23 22:33:27 +00:00
msg_buf . amount_from = int ( amount )
2019-07-17 15:12:06 +00:00
msg_buf . rate = int ( rate )
2019-07-23 22:33:27 +00:00
msg_buf . min_bid_amount = int ( min_bid_amount )
2019-07-17 15:12:06 +00:00
2021-02-13 22:54:01 +00:00
msg_buf . time_valid = valid_for_seconds
2019-07-17 15:12:06 +00:00
msg_buf . lock_type = lock_type
msg_buf . lock_value = lock_value
msg_buf . swap_type = swap_type
2021-11-14 23:26:43 +00:00
msg_buf . amount_negotiable = extra_options . get ( ' amount_negotiable ' , False )
msg_buf . rate_negotiable = extra_options . get ( ' rate_negotiable ' , False )
2019-07-17 15:12:06 +00:00
2021-11-21 20:59:39 +00:00
if msg_buf . amount_negotiable or msg_buf . rate_negotiable :
ensure ( auto_accept_bids is False , ' Auto-accept unavailable when amount or rate are variable ' )
2020-12-16 21:19:39 +00:00
if ' from_fee_override ' in extra_options :
msg_buf . fee_rate_from = make_int ( extra_options [ ' from_fee_override ' ] , self . ci ( coin_from ) . exp ( ) )
else :
# TODO: conf_target = ci_from.settings.get('conf_target', 2)
conf_target = 2
if ' from_fee_conf_target ' in extra_options :
conf_target = extra_options [ ' from_fee_conf_target ' ]
fee_rate , fee_src = self . getFeeRateForCoin ( coin_from , conf_target )
if ' from_fee_multiplier_percent ' in extra_options :
fee_rate * = extra_options [ ' fee_multiplier ' ] / 100.0
msg_buf . fee_rate_from = make_int ( fee_rate , self . ci ( coin_from ) . exp ( ) )
if ' to_fee_override ' in extra_options :
msg_buf . fee_rate_to = make_int ( extra_options [ ' to_fee_override ' ] , self . ci ( coin_to ) . exp ( ) )
else :
# TODO: conf_target = ci_to.settings.get('conf_target', 2)
conf_target = 2
if ' to_fee_conf_target ' in extra_options :
conf_target = extra_options [ ' to_fee_conf_target ' ]
fee_rate , fee_src = self . getFeeRateForCoin ( coin_to , conf_target )
if ' to_fee_multiplier_percent ' in extra_options :
fee_rate * = extra_options [ ' fee_multiplier ' ] / 100.0
msg_buf . fee_rate_to = make_int ( fee_rate , self . ci ( coin_to ) . exp ( ) )
2020-11-14 22:13:11 +00:00
if swap_type == SwapTypes . XMR_SWAP :
xmr_offer = XmrOffer ( )
2020-11-27 17:52:26 +00:00
# Delay before the chain a lock refund tx can be mined
2021-02-03 14:01:27 +00:00
xmr_offer . lock_time_1 = ci_from . getExpectedSequence ( lock_type , lock_value )
2020-11-27 17:52:26 +00:00
# Delay before the follower can spend from the chain a lock refund tx
2021-02-03 14:01:27 +00:00
xmr_offer . lock_time_2 = ci_from . getExpectedSequence ( lock_type , lock_value )
2020-11-14 22:13:11 +00:00
2020-12-16 21:19:39 +00:00
xmr_offer . a_fee_rate = msg_buf . fee_rate_from
xmr_offer . b_fee_rate = msg_buf . fee_rate_to # Unused: TODO - Set priority?
2020-11-14 22:13:11 +00:00
2021-01-28 12:38:28 +00:00
proof_of_funds_hash = getOfferProofOfFundsHash ( msg_buf , offer_addr )
proof_addr , proof_sig = self . getProofOfFunds ( coin_from_t , int ( amount ) , proof_of_funds_hash )
2021-10-21 22:47:04 +00:00
# TODO: For now proof_of_funds is just a client side check, may need to be sent with offers in future however.
2021-01-28 12:38:28 +00:00
2019-07-17 15:12:06 +00:00
offer_bytes = msg_buf . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . OFFER ) + offer_bytes . hex ( )
2021-02-15 13:34:47 +00:00
msg_valid = max ( self . SMSG_SECONDS_IN_HOUR * 1 , valid_for_seconds )
2022-07-01 14:37:10 +00:00
offer_id = self . sendSmsg ( offer_addr , offer_addr_to , payload_hex , msg_valid )
2019-07-17 15:12:06 +00:00
2021-01-29 23:45:24 +00:00
security_token = extra_options . get ( ' security_token ' , None )
if security_token is not None and len ( security_token ) != 20 :
raise ValueError ( ' Security token must be 20 bytes long. ' )
2019-07-17 15:12:06 +00:00
session = scoped_session ( self . session_factory )
offer = Offer (
offer_id = offer_id ,
2020-12-04 21:30:20 +00:00
active_ind = 1 ,
2021-11-04 21:49:52 +00:00
protocol_version = msg_buf . protocol_version ,
2019-07-17 15:12:06 +00:00
coin_from = msg_buf . coin_from ,
coin_to = msg_buf . coin_to ,
amount_from = msg_buf . amount_from ,
rate = msg_buf . rate ,
min_bid_amount = msg_buf . min_bid_amount ,
time_valid = msg_buf . time_valid ,
lock_type = int ( msg_buf . lock_type ) ,
lock_value = msg_buf . lock_value ,
swap_type = msg_buf . swap_type ,
2021-11-14 23:26:43 +00:00
amount_negotiable = msg_buf . amount_negotiable ,
rate_negotiable = msg_buf . rate_negotiable ,
2019-07-17 15:12:06 +00:00
2021-10-20 17:47:49 +00:00
addr_to = offer_addr_to ,
2019-07-17 15:12:06 +00:00
addr_from = offer_addr ,
2020-11-07 11:08:07 +00:00
created_at = offer_created_at ,
expire_at = offer_created_at + msg_buf . time_valid ,
2019-07-17 15:12:06 +00:00
was_sent = True ,
2021-01-29 23:45:24 +00:00
security_token = security_token )
2019-07-17 15:12:06 +00:00
offer . setState ( OfferStates . OFFER_SENT )
2020-11-14 22:13:11 +00:00
if swap_type == SwapTypes . XMR_SWAP :
xmr_offer . offer_id = offer_id
session . add ( xmr_offer )
2022-05-31 22:38:50 +00:00
automation_id = extra_options . get ( ' automation_id ' , - 1 )
if automation_id == - 1 and auto_accept_bids :
2022-05-23 21:51:06 +00:00
# Use default strategy
2022-05-31 22:38:50 +00:00
automation_id = 1
if automation_id != - 1 :
2022-05-23 21:51:06 +00:00
auto_link = AutomationLink (
active_ind = 1 ,
2022-06-06 21:03:31 +00:00
linked_type = Concepts . OFFER ,
2022-05-23 21:51:06 +00:00
linked_id = offer_id ,
2022-05-31 22:38:50 +00:00
strategy_id = automation_id ,
2022-05-23 21:51:06 +00:00
created_at = offer_created_at ,
repeat_limit = 1 ,
repeat_count = 0 )
session . add ( auto_link )
2022-12-05 15:04:23 +00:00
if ' prefunded_itx ' in extra_options :
prefunded_tx = PrefundedTx (
active_ind = 1 ,
created_at = offer_created_at ,
linked_type = Concepts . OFFER ,
linked_id = offer_id ,
tx_type = TxTypes . ITX_PRE_FUNDED ,
tx_data = extra_options [ ' prefunded_itx ' ] )
session . add ( prefunded_tx )
2019-07-28 19:57:20 +00:00
session . add ( offer )
2019-07-17 15:12:06 +00:00
session . add ( SentOffer ( offer_id = offer_id ) )
session . commit ( )
2021-01-11 21:48:46 +00:00
2019-07-17 15:12:06 +00:00
finally :
2021-01-11 21:48:46 +00:00
if session :
session . close ( )
session . remove ( )
2019-07-17 15:12:06 +00:00
self . mxDB . release ( )
self . log . info ( ' Sent OFFER %s ' , offer_id . hex ( ) )
return offer_id
2023-02-17 21:14:17 +00:00
def revokeOffer ( self , offer_id , security_token = None ) - > None :
2020-12-02 11:24:52 +00:00
self . log . info ( ' Revoking offer %s ' , offer_id . hex ( ) )
2022-12-13 22:19:38 +00:00
session = self . openSession ( )
2020-12-02 11:24:52 +00:00
try :
offer = session . query ( Offer ) . filter_by ( offer_id = offer_id ) . first ( )
2021-01-30 14:29:07 +00:00
if offer . security_token is not None and offer . security_token != security_token :
2021-01-29 23:45:24 +00:00
raise ValueError ( ' Mismatched security token ' )
2020-12-02 11:24:52 +00:00
msg_buf = OfferRevokeMessage ( )
msg_buf . offer_msg_id = offer_id
signature_enc = self . callcoinrpc ( Coins . PART , ' signmessage ' , [ offer . addr_from , offer_id . hex ( ) + ' _revoke ' ] )
msg_buf . signature = base64 . b64decode ( signature_enc )
msg_bytes = msg_buf . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . OFFER_REVOKE ) + msg_bytes . hex ( )
2022-07-01 14:37:10 +00:00
msg_id = self . sendSmsg ( offer . addr_from , self . network_addr , payload_hex , offer . time_valid )
self . log . debug ( ' Revoked offer %s in msg %s ' , offer_id . hex ( ) , msg_id . hex ( ) )
2020-12-02 11:24:52 +00:00
finally :
2022-12-13 22:19:38 +00:00
self . closeSession ( session , commit = False )
2023-02-17 21:14:17 +00:00
def archiveOffer ( self , offer_id ) - > None :
2022-12-13 22:19:38 +00:00
self . log . info ( ' Archiving offer %s ' , offer_id . hex ( ) )
session = self . openSession ( )
try :
offer = session . query ( Offer ) . filter_by ( offer_id = offer_id ) . first ( )
if offer . active_ind != 1 :
raise ValueError ( ' Offer is not active ' )
offer . active_ind = 3
finally :
self . closeSession ( session )
2020-12-02 11:24:52 +00:00
2023-02-20 22:08:18 +00:00
def editOffer ( self , offer_id , data ) - > None :
self . log . info ( ' Editing offer %s ' , offer_id . hex ( ) )
session = self . openSession ( )
try :
offer = session . query ( Offer ) . filter_by ( offer_id = offer_id ) . first ( )
if ' automation_strat_id ' in data :
new_automation_strat_id = data [ ' automation_strat_id ' ]
link = session . query ( AutomationLink ) . filter_by ( linked_type = Concepts . OFFER , linked_id = offer . offer_id ) . first ( )
if not link :
if new_automation_strat_id > 0 :
link = AutomationLink (
active_ind = 1 ,
linked_type = Concepts . OFFER ,
linked_id = offer_id ,
strategy_id = new_automation_strat_id ,
2023-02-26 18:14:00 +00:00
created_at = self . getTime ( ) )
2023-02-20 22:08:18 +00:00
session . add ( link )
else :
if new_automation_strat_id < 1 :
link . active_ind = 0
else :
link . strategy_id = new_automation_strat_id
link . active_ind = 1
session . add ( link )
finally :
self . closeSession ( session )
2023-02-17 21:14:17 +00:00
def grindForEd25519Key ( self , coin_type , evkey , key_path_base ) - > bytes :
2020-12-03 23:46:01 +00:00
ci = self . ci ( coin_type )
nonce = 1
while True :
key_path = key_path_base + ' / {} ' . format ( nonce )
extkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , evkey , key_path ] ) [ ' key_info ' ] [ ' result ' ]
privkey = decodeWif ( self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , extkey ] ) [ ' key_info ' ] [ ' privkey ' ] )
2021-02-11 12:57:54 +00:00
if ci . verifyKey ( privkey ) :
2020-12-03 23:46:01 +00:00
return privkey
nonce + = 1
if nonce > 1000 :
raise ValueError ( ' grindForEd25519Key failed ' )
2023-02-17 21:14:17 +00:00
def getWalletKey ( self , coin_type , key_num , for_ed25519 = False ) - > bytes :
2020-12-03 23:46:01 +00:00
evkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' account ' , ' default ' , ' true ' ] ) [ ' evkey ' ]
key_path_base = ' 44445555h/1h/ {} / {} ' . format ( int ( coin_type ) , key_num )
if not for_ed25519 :
extkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , evkey , key_path_base ] ) [ ' key_info ' ] [ ' result ' ]
return decodeWif ( self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , extkey ] ) [ ' key_info ' ] [ ' privkey ' ] )
return self . grindForEd25519Key ( coin_type , evkey , key_path_base )
2023-02-17 21:14:17 +00:00
def getPathKey ( self , coin_from , coin_to , offer_created_at : int , contract_count : int , key_no : int , for_ed25519 : bool = False ) - > bytes :
2020-11-14 22:13:11 +00:00
evkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' account ' , ' default ' , ' true ' ] ) [ ' evkey ' ]
ci = self . ci ( coin_to )
days = offer_created_at / / 86400
secs = offer_created_at - days * 86400
key_path_base = ' 44445555h/999999/ {} / {} / {} / {} / {} / {} ' . format ( int ( coin_from ) , int ( coin_to ) , days , secs , contract_count , key_no )
2020-12-03 23:46:01 +00:00
if not for_ed25519 :
2020-11-14 22:13:11 +00:00
extkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , evkey , key_path_base ] ) [ ' key_info ' ] [ ' result ' ]
return decodeWif ( self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , extkey ] ) [ ' key_info ' ] [ ' privkey ' ] )
2020-12-03 23:46:01 +00:00
return self . grindForEd25519Key ( coin_to , evkey , key_path_base )
2020-11-14 22:13:11 +00:00
2020-12-13 13:43:46 +00:00
def getNetworkKey ( self , key_num ) :
evkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' account ' , ' default ' , ' true ' ] ) [ ' evkey ' ]
key_path = ' 44445556h/1h/ {} ' . format ( int ( key_num ) )
extkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , evkey , key_path ] ) [ ' key_info ' ] [ ' result ' ]
return decodeWif ( self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , extkey ] ) [ ' key_info ' ] [ ' privkey ' ] )
2019-07-17 15:12:06 +00:00
def getContractPubkey ( self , date , contract_count ) :
account = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' account ' ] )
# Derive an address to use for a contract
evkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' account ' , ' default ' , ' true ' ] ) [ ' evkey ' ]
# Should the coin path be included?
path = ' 44445555h '
path + = ' / ' + str ( date . year ) + ' / ' + str ( date . month ) + ' / ' + str ( date . day )
path + = ' / ' + str ( contract_count )
extkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , evkey , path ] ) [ ' key_info ' ] [ ' result ' ]
pubkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , extkey ] ) [ ' key_info ' ] [ ' pubkey ' ]
return bytes . fromhex ( pubkey )
def getContractPrivkey ( self , date , contract_count ) :
# Derive an address to use for a contract
evkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' account ' , ' default ' , ' true ' ] ) [ ' evkey ' ]
path = ' 44445555h '
path + = ' / ' + str ( date . year ) + ' / ' + str ( date . month ) + ' / ' + str ( date . day )
path + = ' / ' + str ( contract_count )
extkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , evkey , path ] ) [ ' key_info ' ] [ ' result ' ]
privkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , extkey ] ) [ ' key_info ' ] [ ' privkey ' ]
raw = decodeAddress ( privkey ) [ 1 : ]
if len ( raw ) > 32 :
raw = raw [ : 32 ]
return raw
def getContractSecret ( self , date , contract_count ) :
# Derive a key to use for a contract secret
evkey = self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' account ' , ' default ' , ' true ' ] ) [ ' evkey ' ]
path = ' 44445555h/99999 '
path + = ' / ' + str ( date . year ) + ' / ' + str ( date . month ) + ' / ' + str ( date . day )
path + = ' / ' + str ( contract_count )
return hashlib . sha256 ( bytes ( self . callcoinrpc ( Coins . PART , ' extkey ' , [ ' info ' , evkey , path ] ) [ ' key_info ' ] [ ' result ' ] , ' utf-8 ' ) ) . digest ( )
2019-07-23 17:19:31 +00:00
def getReceiveAddressFromPool ( self , coin_type , bid_id , tx_type ) :
self . log . debug ( ' Get address from pool bid_id {} , type {} , coin {} ' . format ( bid_id . hex ( ) , tx_type , coin_type ) )
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2020-12-06 17:34:56 +00:00
record = session . query ( PooledAddress ) . filter ( sa . and_ ( PooledAddress . coin_type == int ( coin_type ) , PooledAddress . bid_id == None ) ) . first ( ) # noqa: E712,E711
2019-07-23 22:33:27 +00:00
if not record :
2019-07-23 17:19:31 +00:00
address = self . getReceiveAddressForCoin ( coin_type )
record = PooledAddress (
addr = address ,
coin_type = int ( coin_type ) )
record . bid_id = bid_id
record . tx_type = tx_type
addr = record . addr
2022-11-16 22:36:13 +00:00
ensure ( self . ci ( coin_type ) . isAddressMine ( addr ) , ' Pool address not owned by wallet! ' )
2019-07-23 17:19:31 +00:00
session . add ( record )
session . commit ( )
2021-01-11 21:48:46 +00:00
finally :
2019-07-23 17:19:31 +00:00
session . close ( )
session . remove ( )
self . mxDB . release ( )
return addr
def returnAddressToPool ( self , bid_id , tx_type ) :
self . log . debug ( ' Return address to pool bid_id {} , type {} ' . format ( bid_id . hex ( ) , tx_type ) )
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
try :
2019-07-23 22:33:27 +00:00
record = session . query ( PooledAddress ) . filter ( sa . and_ ( PooledAddress . bid_id == bid_id , PooledAddress . tx_type == tx_type ) ) . one ( )
2019-07-23 17:19:31 +00:00
self . log . debug ( ' Returning address to pool addr {} ' . format ( record . addr ) )
record . bid_id = None
session . commit ( )
2019-07-23 22:33:27 +00:00
except Exception as ex :
2019-07-23 17:19:31 +00:00
pass
2021-01-11 21:48:46 +00:00
finally :
2019-07-23 17:19:31 +00:00
session . close ( )
session . remove ( )
self . mxDB . release ( )
2019-07-17 15:12:06 +00:00
def getReceiveAddressForCoin ( self , coin_type ) :
2021-02-06 22:35:12 +00:00
new_addr = self . ci ( coin_type ) . getNewAddress ( self . coin_clients [ coin_type ] [ ' use_segwit ' ] )
2019-07-17 15:12:06 +00:00
self . log . debug ( ' Generated new receive address %s for %s ' , new_addr , str ( coin_type ) )
return new_addr
2019-07-23 22:33:27 +00:00
def getRelayFeeRateForCoin ( self , coin_type ) :
return self . callcoinrpc ( coin_type , ' getnetworkinfo ' ) [ ' relayfee ' ]
2020-12-16 21:19:39 +00:00
def getFeeRateForCoin ( self , coin_type , conf_target = 2 ) :
2021-02-03 14:01:27 +00:00
chain_client_settings = self . getChainClientSettings ( coin_type )
override_feerate = chain_client_settings . get ( ' override_feerate ' , None )
2019-07-23 22:33:27 +00:00
if override_feerate :
2020-12-08 18:23:00 +00:00
self . log . debug ( ' Fee rate override used for %s : %f ' , str ( coin_type ) , override_feerate )
2020-12-16 21:19:39 +00:00
return override_feerate , ' override_feerate '
2020-11-27 22:20:35 +00:00
2020-12-16 21:19:39 +00:00
return self . ci ( coin_type ) . get_fee_rate ( conf_target )
2019-07-17 15:12:06 +00:00
2020-12-05 11:22:22 +00:00
def estimateWithdrawFee ( self , coin_type , fee_rate ) :
if coin_type == Coins . XMR :
self . log . error ( ' TODO: estimateWithdrawFee XMR ' )
return None
tx_vsize = self . getContractSpendTxVSize ( coin_type )
est_fee = ( fee_rate * tx_vsize ) / 1000
return est_fee
2019-07-21 19:39:44 +00:00
def withdrawCoin ( self , coin_type , value , addr_to , subfee ) :
2020-12-05 11:22:22 +00:00
ci = self . ci ( coin_type )
2021-02-15 23:20:24 +00:00
self . log . info ( ' withdrawCoin %s %s to %s %s ' , value , ci . ticker ( ) , addr_to , ' subfee ' if subfee else ' ' )
2021-02-13 22:54:01 +00:00
txid = ci . withdrawCoin ( value , addr_to , subfee )
self . log . debug ( ' In txn: {} ' . format ( txid ) )
return txid
2019-07-17 15:12:06 +00:00
2021-02-06 22:35:12 +00:00
def withdrawParticl ( self , type_from , type_to , value , addr_to , subfee ) :
self . log . info ( ' withdrawParticl %s %s to %s %s %s ' , value , type_from , type_to , addr_to , ' subfee ' if subfee else ' ' )
if type_from == ' plain ' :
type_from = ' part '
if type_to == ' plain ' :
type_to = ' part '
ci = self . ci ( Coins . PART )
2021-02-13 22:54:01 +00:00
txid = ci . sendTypeTo ( type_from , type_to , value , addr_to , subfee )
self . log . debug ( ' In txn: {} ' . format ( txid ) )
return txid
2021-02-06 22:35:12 +00:00
2019-07-17 15:12:06 +00:00
def cacheNewAddressForCoin ( self , coin_type ) :
self . log . debug ( ' cacheNewAddressForCoin %s ' , coin_type )
2019-07-18 17:53:23 +00:00
key_str = ' receive_addr_ ' + chainparams [ coin_type ] [ ' name ' ]
2019-07-17 15:12:06 +00:00
addr = self . getReceiveAddressForCoin ( coin_type )
2020-12-04 17:06:50 +00:00
self . setStringKV ( key_str , addr )
2019-07-17 15:12:06 +00:00
return addr
2021-10-20 18:56:30 +00:00
def getCachedMainWalletAddress ( self , ci ) :
2021-11-24 21:54:31 +00:00
db_key = ' main_wallet_addr_ ' + ci . coin_name ( ) . lower ( )
cached_addr = self . getStringKV ( db_key )
if cached_addr is not None :
return cached_addr
self . log . warning ( f ' Setting { db_key } ' )
main_address = ci . getMainWalletAddress ( )
self . setStringKV ( db_key , main_address )
return main_address
2021-10-20 18:56:30 +00:00
2020-12-11 10:41:15 +00:00
def checkWalletSeed ( self , c ) :
ci = self . ci ( c )
if c == Coins . PART :
2023-02-19 19:52:22 +00:00
ci . setWalletSeedWarning ( False ) # All keys should be be derived from the Particl mnemonic
2020-12-11 10:41:15 +00:00
return True # TODO
if c == Coins . XMR :
2021-10-20 18:56:30 +00:00
expect_address = self . getCachedMainWalletAddress ( ci )
2020-12-11 10:41:15 +00:00
if expect_address is None :
self . log . warning ( ' Can \' t find expected main wallet address for coin {} ' . format ( ci . coin_name ( ) ) )
return False
2022-11-16 22:36:13 +00:00
ci . _have_checked_seed = True
2023-02-19 19:52:22 +00:00
wallet_address : str = ci . getMainWalletAddress ( )
if expect_address == wallet_address :
2020-12-11 10:41:15 +00:00
ci . setWalletSeedWarning ( False )
return True
2023-02-19 19:52:22 +00:00
self . log . warning ( ' Wallet for coin {} not derived from swap seed. \n Expected {} \n Have {} ' . format ( ci . coin_name ( ) , expect_address , wallet_address ) )
2020-12-11 10:41:15 +00:00
return False
2021-02-14 14:53:44 +00:00
expect_seedid = self . getStringKV ( ' main_wallet_seedid_ ' + ci . coin_name ( ) . lower ( ) )
2020-12-11 10:41:15 +00:00
if expect_seedid is None :
self . log . warning ( ' Can \' t find expected wallet seed id for coin {} ' . format ( ci . coin_name ( ) ) )
return False
2022-12-13 05:56:46 +00:00
if c == Coins . BTC and len ( ci . rpc_callback ( ' listwallets ' ) ) < 1 :
2022-12-12 22:12:28 +00:00
self . log . warning ( ' Missing wallet for coin {} ' . format ( ci . coin_name ( ) ) )
return False
2022-10-21 11:00:28 +00:00
if ci . checkExpectedSeed ( expect_seedid ) :
2020-12-11 10:41:15 +00:00
ci . setWalletSeedWarning ( False )
return True
self . log . warning ( ' Wallet for coin {} not derived from swap seed. ' . format ( ci . coin_name ( ) ) )
return False
def reseedWallet ( self , coin_type ) :
self . log . info ( ' reseedWallet %s ' , coin_type )
ci = self . ci ( coin_type )
if ci . knownWalletSeed ( ) :
raise ValueError ( ' {} wallet seed is already derived from the particl mnemonic ' . format ( ci . coin_name ( ) ) )
2022-10-13 21:16:43 +00:00
self . initialiseWallet ( coin_type , raise_errors = True )
2020-12-11 10:41:15 +00:00
# TODO: How to scan pruned blocks?
if not self . checkWalletSeed ( coin_type ) :
if coin_type == Coins . XMR :
raise ValueError ( ' TODO: How to reseed XMR wallet? ' )
else :
raise ValueError ( ' Wallet seed doesn \' t match expected. ' )
2019-07-17 15:12:06 +00:00
def getCachedAddressForCoin ( self , coin_type ) :
self . log . debug ( ' getCachedAddressForCoin %s ' , coin_type )
# TODO: auto refresh after used
2019-07-18 17:53:23 +00:00
key_str = ' receive_addr_ ' + chainparams [ coin_type ] [ ' name ' ]
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
try :
addr = session . query ( DBKVString ) . filter_by ( key = key_str ) . first ( ) . value
except Exception :
addr = self . getReceiveAddressForCoin ( coin_type )
session . add ( DBKVString (
key = key_str ,
value = addr
) )
session . commit ( )
2021-01-11 21:48:46 +00:00
finally :
2019-07-18 17:53:23 +00:00
session . close ( )
session . remove ( )
self . mxDB . release ( )
2019-07-17 15:12:06 +00:00
return addr
2021-02-06 22:35:12 +00:00
def getCachedStealthAddressForCoin ( self , coin_type ) :
self . log . debug ( ' getCachedStealthAddressForCoin %s ' , coin_type )
ci = self . ci ( coin_type )
2021-02-14 14:53:44 +00:00
key_str = ' stealth_addr_ ' + ci . coin_name ( ) . lower ( )
2021-02-06 22:35:12 +00:00
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
try :
addr = session . query ( DBKVString ) . filter_by ( key = key_str ) . first ( ) . value
except Exception :
addr = ci . getNewStealthAddress ( )
self . log . info ( ' Generated new stealth address for %s ' , coin_type )
session . add ( DBKVString (
key = key_str ,
value = addr
) )
session . commit ( )
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
return addr
2021-02-11 12:57:54 +00:00
def getCachedWalletRestoreHeight ( self , ci ) :
self . log . debug ( ' getCachedWalletRestoreHeight %s ' , ci . coin_name ( ) )
2021-02-14 14:53:44 +00:00
key_str = ' restore_height_ ' + ci . coin_name ( ) . lower ( )
2021-02-11 12:57:54 +00:00
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
try :
wrh = session . query ( DBKVInt ) . filter_by ( key = key_str ) . first ( ) . value
except Exception :
wrh = ci . getWalletRestoreHeight ( )
2022-01-01 21:30:32 +00:00
self . log . info ( ' Found restore height for %s , block %d ' , ci . coin_name ( ) , wrh )
2021-02-11 12:57:54 +00:00
session . add ( DBKVInt (
key = key_str ,
value = wrh
) )
session . commit ( )
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
return wrh
def getWalletRestoreHeight ( self , ci ) :
wrh = ci . _restore_height
if wrh is not None :
return wrh
found_height = self . getCachedWalletRestoreHeight ( ci )
ci . setWalletRestoreHeight ( found_height )
return found_height
2019-07-17 15:12:06 +00:00
def getNewContractId ( self ) :
2019-07-18 17:53:23 +00:00
self . mxDB . acquire ( )
try :
self . _contract_count + = 1
session = scoped_session ( self . session_factory )
2023-02-16 20:57:55 +00:00
session . execute ( ' UPDATE kv_int SET value = :value WHERE KEY= " contract_count " ' , { ' value ' : self . _contract_count } )
2019-07-18 17:53:23 +00:00
session . commit ( )
2021-01-11 21:48:46 +00:00
finally :
2019-07-18 17:53:23 +00:00
session . close ( )
session . remove ( )
self . mxDB . release ( )
2019-07-17 15:12:06 +00:00
return self . _contract_count
2022-01-24 21:32:48 +00:00
def getProofOfFunds ( self , coin_type , amount_for , extra_commit_bytes ) :
ci = self . ci ( coin_type )
self . log . debug ( ' getProofOfFunds %s %s ' , ci . coin_name ( ) , ci . format_amount ( amount_for ) )
if self . coin_clients [ coin_type ] [ ' connection_type ' ] != ' rpc ' :
return ( None , None )
2022-11-07 20:31:10 +00:00
return ci . getProofOfFunds ( amount_for , extra_commit_bytes )
2019-07-17 15:12:06 +00:00
2020-12-02 11:24:52 +00:00
def saveBidInSession ( self , bid_id , bid , session , xmr_swap = None , save_in_progress = None ) :
2019-07-28 19:57:20 +00:00
session . add ( bid )
if bid . initiate_tx :
session . add ( bid . initiate_tx )
if bid . participate_tx :
session . add ( bid . participate_tx )
2020-11-15 21:31:59 +00:00
if bid . xmr_a_lock_tx :
session . add ( bid . xmr_a_lock_tx )
2020-11-21 13:16:27 +00:00
if bid . xmr_a_lock_spend_tx :
session . add ( bid . xmr_a_lock_spend_tx )
2020-11-27 17:52:26 +00:00
if bid . xmr_b_lock_tx :
session . add ( bid . xmr_b_lock_tx )
for tx_type , tx in bid . txns . items ( ) :
session . add ( tx )
2020-11-14 22:13:11 +00:00
if xmr_swap is not None :
session . add ( xmr_swap )
2019-07-28 19:57:20 +00:00
2020-12-02 11:24:52 +00:00
if save_in_progress is not None :
if not isinstance ( save_in_progress , Offer ) :
raise ValueError ( ' Must specify offer for save_in_progress ' )
self . swaps_in_progress [ bid_id ] = ( bid , save_in_progress ) # (bid, offer)
2020-11-14 22:13:11 +00:00
def saveBid ( self , bid_id , bid , xmr_swap = None ) :
2019-07-17 15:12:06 +00:00
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2020-11-14 22:13:11 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
2021-01-11 21:48:46 +00:00
finally :
2020-11-14 22:13:11 +00:00
session . close ( )
session . remove ( )
self . mxDB . release ( )
def saveToDB ( self , db_record ) :
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
session . add ( db_record )
2019-07-17 15:12:06 +00:00
session . commit ( )
2021-01-11 21:48:46 +00:00
finally :
2019-07-17 15:12:06 +00:00
session . close ( )
session . remove ( )
self . mxDB . release ( )
2022-06-06 21:03:31 +00:00
def createActionInSession ( self , delay , action_type , linked_id , session ) :
self . log . debug ( ' createAction %d %s ' , action_type , linked_id . hex ( ) )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2022-06-06 21:03:31 +00:00
action = Action (
2020-12-02 11:24:52 +00:00
active_ind = 1 ,
created_at = now ,
trigger_at = now + delay ,
2022-06-06 21:03:31 +00:00
action_type = action_type ,
2020-12-02 11:24:52 +00:00
linked_id = linked_id )
2022-06-06 21:03:31 +00:00
session . add ( action )
2020-11-15 17:02:46 +00:00
2022-06-06 21:03:31 +00:00
def createAction ( self , delay , action_type , linked_id ) :
# self.log.debug('createAction %d %s', action_type, linked_id.hex())
2019-11-09 21:09:22 +00:00
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , action_type , linked_id , session )
2019-11-09 21:09:22 +00:00
session . commit ( )
2021-01-11 21:48:46 +00:00
finally :
2019-11-09 21:09:22 +00:00
session . close ( )
session . remove ( )
self . mxDB . release ( )
2022-06-06 21:03:31 +00:00
def logEvent ( self , linked_type , linked_id , event_type , event_msg , session ) :
2020-12-02 11:24:52 +00:00
entry = EventLog (
active_ind = 1 ,
2023-02-26 18:14:00 +00:00
created_at = self . getTime ( ) ,
2022-06-06 21:03:31 +00:00
linked_type = linked_type ,
linked_id = linked_id ,
2020-12-02 11:24:52 +00:00
event_type = int ( event_type ) ,
event_msg = event_msg )
2021-01-30 14:29:07 +00:00
if session is not None :
session . add ( entry )
return
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
session . add ( entry )
session . commit ( )
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2020-12-02 11:24:52 +00:00
2022-06-06 21:03:31 +00:00
def logBidEvent ( self , bid_id , event_type , event_msg , session ) :
self . log . debug ( ' logBidEvent %s %s ' , bid_id . hex ( ) , event_type )
self . logEvent ( Concepts . BID , bid_id , event_type , event_msg , session )
2020-12-02 11:24:52 +00:00
def countBidEvents ( self , bid , event_type , session ) :
2022-06-06 21:03:31 +00:00
q = session . execute ( ' SELECT COUNT(*) FROM eventlog WHERE linked_type = {} AND linked_id = x \' {} \' AND event_type = {} ' . format ( int ( Concepts . BID ) , bid . bid_id . hex ( ) , int ( event_type ) ) ) . first ( )
2020-12-02 11:24:52 +00:00
return q [ 0 ]
2022-06-06 21:03:31 +00:00
def getEvents ( self , linked_type , linked_id ) :
events = [ ]
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
for entry in session . query ( EventLog ) . filter ( sa . and_ ( EventLog . linked_type == linked_type , EventLog . linked_id == linked_id ) ) :
events . append ( entry )
return events
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2021-01-09 13:00:25 +00:00
def postBid ( self , offer_id , amount , addr_send_from = None , extra_options = { } ) :
2021-11-22 20:24:48 +00:00
# Bid to send bid.amount * bid.rate of coin_to in exchange for bid.amount of coin_from
2021-02-03 14:01:27 +00:00
self . log . debug ( ' postBid %s ' , offer_id . hex ( ) )
2019-07-31 18:49:45 +00:00
2020-12-04 21:30:20 +00:00
offer = self . getOffer ( offer_id )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( offer_id . hex ( ) ) )
2023-02-26 18:14:00 +00:00
ensure ( offer . expire_at > self . getTime ( ) , ' Offer has expired ' )
2020-12-04 21:30:20 +00:00
if offer . swap_type == SwapTypes . XMR_SWAP :
2021-02-15 13:34:47 +00:00
return self . postXmrBid ( offer_id , amount , addr_send_from , extra_options )
valid_for_seconds = extra_options . get ( ' valid_for_seconds ' , 60 * 10 )
self . validateBidValidTime ( offer . swap_type , offer . coin_from , offer . coin_to , valid_for_seconds )
2020-12-04 21:30:20 +00:00
2021-11-14 23:26:43 +00:00
bid_rate = extra_options . get ( ' bid_rate ' , offer . rate )
2021-11-22 20:24:48 +00:00
self . validateBidAmount ( offer , amount , bid_rate )
2021-11-14 23:26:43 +00:00
2019-07-28 19:57:20 +00:00
self . mxDB . acquire ( )
try :
msg_buf = BidMessage ( )
2023-05-12 08:20:52 +00:00
msg_buf . protocol_version = PROTOCOL_VERSION_SECRET_HASH
2019-07-28 19:57:20 +00:00
msg_buf . offer_msg_id = offer_id
2021-02-15 13:34:47 +00:00
msg_buf . time_valid = valid_for_seconds
2019-07-28 19:57:20 +00:00
msg_buf . amount = int ( amount ) # amount of coin_from
2021-11-14 23:26:43 +00:00
msg_buf . rate = bid_rate
2019-07-17 15:12:06 +00:00
2019-07-28 19:57:20 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
2021-11-04 21:49:52 +00:00
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
2019-07-17 15:12:06 +00:00
2023-02-19 19:52:22 +00:00
self . checkCoinsReady ( coin_from , coin_to )
2019-07-31 18:21:41 +00:00
2022-01-01 20:55:39 +00:00
amount_to = int ( ( msg_buf . amount * bid_rate ) / / ci_from . COIN ( ) )
2021-01-18 22:52:05 +00:00
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2019-07-28 19:57:20 +00:00
if offer . swap_type == SwapTypes . SELLER_FIRST :
2021-01-28 12:38:28 +00:00
proof_addr , proof_sig = self . getProofOfFunds ( coin_to , amount_to , offer_id )
2019-07-28 19:57:20 +00:00
msg_buf . proof_address = proof_addr
msg_buf . proof_signature = proof_sig
2022-01-01 20:55:39 +00:00
contract_count = self . getNewContractId ( )
msg_buf . pkhash_buyer = getKeyID ( self . getContractPubkey ( dt . datetime . fromtimestamp ( now ) . date ( ) , contract_count ) )
2020-11-14 22:13:11 +00:00
else :
raise ValueError ( ' TODO ' )
2019-07-17 15:12:06 +00:00
2019-07-28 19:57:20 +00:00
bid_bytes = msg_buf . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . BID ) + bid_bytes . hex ( )
2019-07-17 15:12:06 +00:00
2021-11-26 01:13:20 +00:00
bid_addr = self . newSMSGAddress ( use_type = AddressTypes . BID ) [ 0 ] if addr_send_from is None else addr_send_from
2019-08-04 13:22:25 +00:00
options = { ' decodehex ' : True , ' ttl_is_seconds ' : True }
2021-02-15 13:34:47 +00:00
msg_valid = max ( self . SMSG_SECONDS_IN_HOUR * 1 , valid_for_seconds )
2019-07-17 15:12:06 +00:00
2022-07-01 14:37:10 +00:00
bid_id = self . sendSmsg ( bid_addr , offer . addr_from , payload_hex , msg_valid )
2019-07-28 19:57:20 +00:00
bid = Bid (
2021-11-04 21:49:52 +00:00
protocol_version = msg_buf . protocol_version ,
2021-02-15 13:34:47 +00:00
active_ind = 1 ,
2019-07-28 19:57:20 +00:00
bid_id = bid_id ,
offer_id = offer_id ,
amount = msg_buf . amount ,
2021-11-14 23:26:43 +00:00
rate = msg_buf . rate ,
2019-07-28 19:57:20 +00:00
pkhash_buyer = msg_buf . pkhash_buyer ,
proof_address = msg_buf . proof_address ,
created_at = now ,
contract_count = contract_count ,
2021-01-18 22:52:05 +00:00
amount_to = amount_to ,
2019-07-28 19:57:20 +00:00
expire_at = now + msg_buf . time_valid ,
bid_addr = bid_addr ,
was_sent = True ,
2021-11-04 21:49:52 +00:00
chain_a_height_start = ci_from . getChainHeight ( ) ,
chain_b_height_start = ci_to . getChainHeight ( ) ,
2019-07-28 19:57:20 +00:00
)
bid . setState ( BidStates . BID_SENT )
2019-07-17 15:12:06 +00:00
2021-01-11 21:48:46 +00:00
try :
session = scoped_session ( self . session_factory )
self . saveBidInSession ( bid_id , bid , session )
session . commit ( )
finally :
session . close ( )
session . remove ( )
2019-07-17 15:12:06 +00:00
2019-07-28 19:57:20 +00:00
self . log . info ( ' Sent BID %s ' , bid_id . hex ( ) )
return bid_id
finally :
self . mxDB . release ( )
2019-07-17 15:12:06 +00:00
def getOffer ( self , offer_id , sent = False ) :
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
return session . query ( Offer ) . filter_by ( offer_id = offer_id ) . first ( )
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2022-11-08 20:30:28 +00:00
def setTxBlockInfoFromHeight ( self , ci , tx , height ) :
try :
tx . block_height = height
block_header = ci . getBlockHeaderFromHeight ( height )
tx . block_hash = bytes . fromhex ( block_header [ ' hash ' ] )
tx . block_time = block_header [ ' time ' ] # Or median_time?
except Exception as e :
self . log . warning ( f ' setTxBlockInfoFromHeight failed { e } ' )
2020-11-21 13:16:27 +00:00
def loadBidTxns ( self , bid , session ) :
2020-11-27 17:52:26 +00:00
bid . txns = { }
2020-11-21 13:16:27 +00:00
for stx in session . query ( SwapTx ) . filter ( sa . and_ ( SwapTx . bid_id == bid . bid_id ) ) :
if stx . tx_type == TxTypes . ITX :
bid . initiate_tx = stx
elif stx . tx_type == TxTypes . PTX :
bid . participate_tx = stx
elif stx . tx_type == TxTypes . XMR_SWAP_A_LOCK :
bid . xmr_a_lock_tx = stx
elif stx . tx_type == TxTypes . XMR_SWAP_A_LOCK_SPEND :
bid . xmr_a_lock_spend_tx = stx
elif stx . tx_type == TxTypes . XMR_SWAP_B_LOCK :
bid . xmr_b_lock_tx = stx
else :
2020-11-27 17:52:26 +00:00
bid . txns [ stx . tx_type ] = stx
2020-11-30 14:29:40 +00:00
2020-12-04 21:30:20 +00:00
def getXmrBidFromSession ( self , session , bid_id , sent = False ) :
bid = session . query ( Bid ) . filter_by ( bid_id = bid_id ) . first ( )
xmr_swap = None
if bid :
xmr_swap = session . query ( XmrSwap ) . filter_by ( bid_id = bid_id ) . first ( )
self . loadBidTxns ( bid , session )
return bid , xmr_swap
2020-11-14 22:13:11 +00:00
def getXmrBid ( self , bid_id , sent = False ) :
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2020-12-04 21:30:20 +00:00
return self . getXmrBidFromSession ( session , bid_id , sent )
2020-11-14 22:13:11 +00:00
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2020-12-04 21:30:20 +00:00
def getXmrOfferFromSession ( self , session , offer_id , sent = False ) :
offer = session . query ( Offer ) . filter_by ( offer_id = offer_id ) . first ( )
xmr_offer = None
if offer :
xmr_offer = session . query ( XmrOffer ) . filter_by ( offer_id = offer_id ) . first ( )
return offer , xmr_offer
2020-11-14 22:13:11 +00:00
def getXmrOffer ( self , offer_id , sent = False ) :
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2020-12-04 21:30:20 +00:00
return self . getXmrOfferFromSession ( session , offer_id , sent )
2020-11-14 22:13:11 +00:00
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2022-07-03 21:58:16 +00:00
def getBid ( self , bid_id , session = None ) :
2019-07-17 15:12:06 +00:00
try :
2022-10-13 20:21:43 +00:00
use_session = self . openSession ( session )
2022-07-03 21:58:16 +00:00
bid = use_session . query ( Bid ) . filter_by ( bid_id = bid_id ) . first ( )
2019-07-27 16:00:13 +00:00
if bid :
2022-07-03 21:58:16 +00:00
self . loadBidTxns ( bid , use_session )
2019-07-27 16:00:13 +00:00
return bid
2019-07-17 15:12:06 +00:00
finally :
2022-07-03 21:58:16 +00:00
if session is None :
2022-10-13 20:21:43 +00:00
self . closeSession ( use_session , commit = False )
2019-07-17 15:12:06 +00:00
2022-07-03 21:58:16 +00:00
def getBidAndOffer ( self , bid_id , session = None ) :
2019-07-17 15:12:06 +00:00
try :
2022-10-13 20:21:43 +00:00
use_session = self . openSession ( session )
2022-07-03 21:58:16 +00:00
bid = use_session . query ( Bid ) . filter_by ( bid_id = bid_id ) . first ( )
2020-12-06 17:34:56 +00:00
offer = None
2019-07-27 16:00:13 +00:00
if bid :
2022-07-03 21:58:16 +00:00
offer = use_session . query ( Offer ) . filter_by ( offer_id = bid . offer_id ) . first ( )
self . loadBidTxns ( bid , use_session )
2020-12-06 17:34:56 +00:00
return bid , offer
2019-07-17 15:12:06 +00:00
finally :
2022-07-03 21:58:16 +00:00
if session is None :
2022-10-13 20:21:43 +00:00
self . closeSession ( use_session , commit = False )
2019-07-17 15:12:06 +00:00
2020-12-12 12:45:30 +00:00
def getXmrBidAndOffer ( self , bid_id , list_events = True ) :
2020-12-08 18:23:00 +00:00
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2020-12-12 12:45:30 +00:00
xmr_swap = None
offer = None
xmr_offer = None
events = [ ]
2020-12-08 18:23:00 +00:00
2020-12-12 12:45:30 +00:00
bid = session . query ( Bid ) . filter_by ( bid_id = bid_id ) . first ( )
if bid :
offer = session . query ( Offer ) . filter_by ( offer_id = bid . offer_id ) . first ( )
if offer and offer . swap_type == SwapTypes . XMR_SWAP :
xmr_swap = session . query ( XmrSwap ) . filter_by ( bid_id = bid . bid_id ) . first ( )
xmr_offer = session . query ( XmrOffer ) . filter_by ( offer_id = bid . offer_id ) . first ( )
2022-07-03 21:58:16 +00:00
self . loadBidTxns ( bid , session )
2021-01-30 14:29:07 +00:00
if list_events :
events = self . list_bid_events ( bid . bid_id , session )
2020-12-12 12:45:30 +00:00
return bid , xmr_swap , offer , xmr_offer , events
2020-12-08 18:23:00 +00:00
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2021-11-14 23:26:43 +00:00
def getIdentity ( self , address ) :
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
identity = session . query ( KnownIdentity ) . filter_by ( address = address ) . first ( )
return identity
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2020-12-12 12:45:30 +00:00
def list_bid_events ( self , bid_id , session ) :
query_str = ' SELECT created_at, event_type, event_msg FROM eventlog ' + \
2022-06-06 21:03:31 +00:00
' WHERE active_ind = 1 AND linked_type = {} AND linked_id = x \' {} \' ' . format ( Concepts . BID , bid_id . hex ( ) )
2021-01-11 21:48:46 +00:00
q = session . execute ( query_str )
2020-12-12 12:45:30 +00:00
events = [ ]
for row in q :
events . append ( { ' at ' : row [ 0 ] , ' desc ' : describeEventEntry ( row [ 1 ] , row [ 2 ] ) } )
2021-01-08 22:12:08 +00:00
2022-06-06 21:03:31 +00:00
query_str = ' SELECT created_at, trigger_at FROM actions ' + \
2021-01-08 22:12:08 +00:00
' WHERE active_ind = 1 AND linked_id = x \' {} \' ' . format ( bid_id . hex ( ) )
2021-01-11 21:48:46 +00:00
q = session . execute ( query_str )
2021-01-08 22:12:08 +00:00
for row in q :
2021-01-12 06:48:40 +00:00
events . append ( { ' at ' : row [ 0 ] , ' desc ' : ' Delaying until: {} ' . format ( format_timestamp ( row [ 1 ] , with_seconds = True ) ) } )
2021-01-08 22:12:08 +00:00
2020-12-12 12:45:30 +00:00
return events
2019-07-17 15:12:06 +00:00
def acceptBid ( self , bid_id ) :
self . log . info ( ' Accepting bid %s ' , bid_id . hex ( ) )
bid , offer = self . getBidAndOffer ( bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found ' )
ensure ( offer , ' Offer not found ' )
2019-07-17 15:12:06 +00:00
# Ensure bid is still valid
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2021-10-21 22:47:04 +00:00
ensure ( bid . expire_at > now , ' Bid expired ' )
ensure ( bid . state == BidStates . BID_RECEIVED , ' Wrong bid state: {} ' . format ( str ( BidStates ( bid . state ) ) ) )
2021-01-11 21:48:46 +00:00
if offer . swap_type == SwapTypes . XMR_SWAP :
return self . acceptXmrBid ( bid_id )
2019-07-17 15:12:06 +00:00
if bid . contract_count is None :
bid . contract_count = self . getNewContractId ( )
coin_from = Coins ( offer . coin_from )
2021-02-03 14:01:27 +00:00
ci_from = self . ci ( coin_from )
2019-07-17 15:12:06 +00:00
bid_date = dt . datetime . fromtimestamp ( bid . created_at ) . date ( )
secret = self . getContractSecret ( bid_date , bid . contract_count )
secret_hash = hashlib . sha256 ( secret ) . digest ( )
pubkey_refund = self . getContractPubkey ( bid_date , bid . contract_count )
pkhash_refund = getKeyID ( pubkey_refund )
2019-08-05 18:31:02 +00:00
if bid . initiate_tx is not None :
2019-11-09 21:09:22 +00:00
self . log . warning ( ' Initiate txn %s already exists for bid %s ' , bid . initiate_tx . txid , bid_id . hex ( ) )
2019-08-05 18:31:02 +00:00
txid = bid . initiate_tx . txid
script = bid . initiate_tx . script
2019-07-24 22:59:40 +00:00
else :
2021-12-15 13:41:43 +00:00
if offer . lock_type < TxLockTypes . ABS_LOCK_BLOCKS :
2021-02-03 14:01:27 +00:00
sequence = ci_from . getExpectedSequence ( offer . lock_type , offer . lock_value )
2020-12-10 10:07:26 +00:00
script = atomic_swap_1 . buildContractScript ( sequence , secret_hash , bid . pkhash_buyer , pkhash_refund )
2019-07-24 22:59:40 +00:00
else :
2021-12-15 13:41:43 +00:00
if offer . lock_type == TxLockTypes . ABS_LOCK_BLOCKS :
2021-10-23 14:00:32 +00:00
lock_value = self . callcoinrpc ( coin_from , ' getblockcount ' ) + offer . lock_value
2019-08-05 18:31:02 +00:00
else :
2023-02-26 18:14:00 +00:00
lock_value = self . getTime ( ) + offer . lock_value
2019-11-09 21:09:22 +00:00
self . log . debug ( ' Initiate %s lock_value %d %d ' , coin_from , offer . lock_value , lock_value )
2020-12-10 10:07:26 +00:00
script = atomic_swap_1 . buildContractScript ( lock_value , secret_hash , bid . pkhash_buyer , pkhash_refund , OpCodes . OP_CHECKLOCKTIMEVERIFY )
2019-07-17 15:12:06 +00:00
2019-08-05 18:31:02 +00:00
p2sh = self . callcoinrpc ( Coins . PART , ' decodescript ' , [ script . hex ( ) ] ) [ ' p2sh ' ]
2019-07-17 15:12:06 +00:00
2019-08-05 18:31:02 +00:00
bid . pkhash_seller = pkhash_refund
2019-07-17 15:12:06 +00:00
2022-12-05 15:04:23 +00:00
prefunded_tx = self . getPreFundedTx ( Concepts . OFFER , offer . offer_id , TxTypes . ITX_PRE_FUNDED )
txn = self . createInitiateTxn ( coin_from , bid_id , bid , script , prefunded_tx )
2019-07-17 15:12:06 +00:00
2019-08-05 18:31:02 +00:00
# Store the signed refund txn in case wallet is locked when refund is possible
refund_txn = self . createRefundTxn ( coin_from , txn , offer , bid , script )
bid . initiate_txn_refund = bytes . fromhex ( refund_txn )
2019-07-17 15:12:06 +00:00
2022-07-04 20:29:49 +00:00
txid = ci_from . publishTx ( bytes . fromhex ( txn ) )
2021-02-13 22:54:01 +00:00
self . log . debug ( ' Submitted initiate txn %s to %s chain for bid %s ' , txid , ci_from . coin_name ( ) , bid_id . hex ( ) )
2019-08-05 18:31:02 +00:00
bid . initiate_tx = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . ITX ,
txid = bytes . fromhex ( txid ) ,
2021-11-05 22:34:25 +00:00
tx_data = bytes . fromhex ( txn ) ,
2019-08-05 18:31:02 +00:00
script = script ,
)
bid . setITxState ( TxStates . TX_SENT )
2022-11-08 21:07:58 +00:00
self . logEvent ( Concepts . BID , bid . bid_id , EventLogTypes . ITX_PUBLISHED , ' ' , None )
2019-07-17 15:12:06 +00:00
2019-08-05 18:31:02 +00:00
# Check non-bip68 final
try :
2022-07-04 20:29:49 +00:00
txid = ci_from . publishTx ( bid . initiate_txn_refund )
2019-08-05 18:31:02 +00:00
self . log . error ( ' Submit refund_txn unexpectedly worked: ' + txid )
except Exception as ex :
if ' non-BIP68-final ' not in str ( ex ) and ' non-final ' not in str ( ex ) :
self . log . error ( ' Submit refund_txn unexpected error ' + str ( ex ) )
2019-07-17 15:12:06 +00:00
if txid is not None :
msg_buf = BidAcceptMessage ( )
msg_buf . bid_msg_id = bid_id
msg_buf . initiate_txid = bytes . fromhex ( txid )
msg_buf . contract_script = bytes ( script )
bid_bytes = msg_buf . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . BID_ACCEPT ) + bid_bytes . hex ( )
2022-07-01 14:37:10 +00:00
msg_valid = self . getAcceptBidMsgValidTime ( bid )
bid . accept_msg_id = self . sendSmsg ( offer . addr_from , bid . bid_addr , payload_hex , msg_valid )
2019-07-17 15:12:06 +00:00
2022-07-01 14:37:10 +00:00
self . log . info ( ' Sent BID_ACCEPT %s ' , bid . accept_msg_id . hex ( ) )
2019-07-17 15:12:06 +00:00
bid . setState ( BidStates . BID_ACCEPTED )
self . saveBid ( bid_id , bid )
self . swaps_in_progress [ bid_id ] = ( bid , offer )
2021-02-15 13:34:47 +00:00
def postXmrBid ( self , offer_id , amount , addr_send_from = None , extra_options = { } ) :
2021-11-22 20:24:48 +00:00
# Bid to send bid.amount * bid.rate of coin_to in exchange for bid.amount of coin_from
2020-11-14 22:13:11 +00:00
# Send MSG1L F -> L
2021-02-03 14:01:27 +00:00
self . log . debug ( ' postXmrBid %s ' , offer_id . hex ( ) )
2020-11-14 22:13:11 +00:00
self . mxDB . acquire ( )
try :
offer , xmr_offer = self . getXmrOffer ( offer_id )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( offer_id . hex ( ) ) )
2023-02-26 18:14:00 +00:00
ensure ( offer . expire_at > self . getTime ( ) , ' Offer has expired ' )
2020-11-14 22:13:11 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
2022-06-06 21:03:31 +00:00
valid_for_seconds = extra_options . get ( ' valid_for_seconds ' , 60 * 10 )
2021-11-14 23:26:43 +00:00
bid_rate = extra_options . get ( ' bid_rate ' , offer . rate )
2022-06-06 21:03:31 +00:00
amount_to = int ( ( int ( amount ) * bid_rate ) / / ci_from . COIN ( ) )
2021-11-14 23:26:43 +00:00
2022-06-06 21:03:31 +00:00
if not ( self . debug and extra_options . get ( ' debug_skip_validation ' , False ) ) :
self . validateBidValidTime ( offer . swap_type , offer . coin_from , offer . coin_to , valid_for_seconds )
self . validateBidAmount ( offer , amount , bid_rate )
2020-11-14 22:13:11 +00:00
2023-02-19 19:52:22 +00:00
self . checkCoinsReady ( coin_from , coin_to )
2022-01-01 20:55:39 +00:00
balance_to = ci_to . getSpendableBalance ( )
ensure ( balance_to > amount_to , ' {} spendable balance is too low: {} ' . format ( ci_to . coin_name ( ) , ci_to . format_amount ( balance_to ) ) )
2020-11-15 17:02:46 +00:00
msg_buf = XmrBidMessage ( )
2023-05-12 08:20:52 +00:00
msg_buf . protocol_version = PROTOCOL_VERSION_ADAPTOR_SIG
2020-11-15 17:02:46 +00:00
msg_buf . offer_msg_id = offer_id
2021-02-15 13:34:47 +00:00
msg_buf . time_valid = valid_for_seconds
2020-11-29 23:05:30 +00:00
msg_buf . amount = int ( amount ) # Amount of coin_from
2021-11-14 23:26:43 +00:00
msg_buf . rate = bid_rate
2020-11-15 17:02:46 +00:00
address_out = self . getReceiveAddressFromPool ( coin_from , offer_id , TxTypes . XMR_SWAP_A_LOCK )
2021-11-01 13:52:40 +00:00
if coin_from == Coins . PART_BLIND :
addrinfo = ci_from . rpc_callback ( ' getaddressinfo ' , [ address_out ] )
msg_buf . dest_af = bytes . fromhex ( addrinfo [ ' pubkey ' ] )
else :
msg_buf . dest_af = ci_from . decodeAddress ( address_out )
2020-11-15 17:02:46 +00:00
2023-02-26 18:14:00 +00:00
bid_created_at = self . getTime ( )
2020-11-14 22:13:11 +00:00
if offer . swap_type != SwapTypes . XMR_SWAP :
raise ValueError ( ' TODO ' )
# Follower to leader
xmr_swap = XmrSwap ( )
xmr_swap . contract_count = self . getNewContractId ( )
2020-11-15 17:02:46 +00:00
xmr_swap . dest_af = msg_buf . dest_af
2021-02-11 12:57:54 +00:00
2023-05-11 21:45:06 +00:00
for_ed25519 = True if ci_to . curve_type ( ) == Curves . ed25519 else False
2021-12-19 06:59:35 +00:00
kbvf = self . getPathKey ( coin_from , coin_to , bid_created_at , xmr_swap . contract_count , KeyTypes . KBVF , for_ed25519 )
kbsf = self . getPathKey ( coin_from , coin_to , bid_created_at , xmr_swap . contract_count , KeyTypes . KBSF , for_ed25519 )
2020-11-14 22:13:11 +00:00
2021-12-19 06:59:35 +00:00
kaf = self . getPathKey ( coin_from , coin_to , bid_created_at , xmr_swap . contract_count , KeyTypes . KAF )
2020-11-14 22:13:11 +00:00
2020-11-21 13:16:27 +00:00
xmr_swap . vkbvf = kbvf
xmr_swap . pkbvf = ci_to . getPubkey ( kbvf )
xmr_swap . pkbsf = ci_to . getPubkey ( kbsf )
2020-11-14 22:13:11 +00:00
xmr_swap . pkaf = ci_from . getPubkey ( kaf )
2023-05-11 21:45:06 +00:00
if ci_to . curve_type ( ) == Curves . ed25519 :
2021-02-11 12:57:54 +00:00
xmr_swap . kbsf_dleag = ci_to . proveDLEAG ( kbsf )
2023-05-11 21:45:06 +00:00
xmr_swap . pkasf = xmr_swap . kbsf_dleag [ 0 : 33 ]
msg_buf . kbsf_dleag = xmr_swap . kbsf_dleag [ : 16000 ]
elif ci_to . curve_type ( ) == Curves . secp256k1 :
for i in range ( 10 ) :
xmr_swap . kbsf_dleag = ci_to . signRecoverable ( kbsf , ' proof kbsf owned for swap ' )
pk_recovered = ci_to . verifySigAndRecover ( xmr_swap . kbsf_dleag , ' proof kbsf owned for swap ' )
if pk_recovered == xmr_swap . pkbsf :
break
self . log . debug ( ' kbsl recovered pubkey mismatch, retrying. ' )
assert ( pk_recovered == xmr_swap . pkbsf )
xmr_swap . pkasf = xmr_swap . pkbsf
msg_buf . kbsf_dleag = xmr_swap . kbsf_dleag
2021-02-11 12:57:54 +00:00
else :
2023-05-11 21:45:06 +00:00
raise ValueError ( ' Unknown curve ' )
2022-07-31 18:01:49 +00:00
assert ( xmr_swap . pkasf == ci_from . getPubkey ( kbsf ) )
2020-11-14 22:13:11 +00:00
msg_buf . pkaf = xmr_swap . pkaf
msg_buf . kbvf = kbvf
bid_bytes = msg_buf . SerializeToString ( )
2020-11-21 13:16:27 +00:00
payload_hex = str . format ( ' {:02x} ' , MessageTypes . XMR_BID_FL ) + bid_bytes . hex ( )
2020-11-14 22:13:11 +00:00
2021-11-26 01:13:20 +00:00
bid_addr = self . newSMSGAddress ( use_type = AddressTypes . BID ) [ 0 ] if addr_send_from is None else addr_send_from
2020-11-14 22:13:11 +00:00
options = { ' decodehex ' : True , ' ttl_is_seconds ' : True }
2021-02-15 13:34:47 +00:00
msg_valid = max ( self . SMSG_SECONDS_IN_HOUR * 1 , valid_for_seconds )
2022-07-01 14:37:10 +00:00
xmr_swap . bid_id = self . sendSmsg ( bid_addr , offer . addr_from , payload_hex , msg_valid )
2020-11-14 22:13:11 +00:00
2023-05-11 21:45:06 +00:00
if ci_to . curve_type ( ) == Curves . ed25519 :
2021-02-11 12:57:54 +00:00
msg_buf2 = XmrSplitMessage (
msg_id = xmr_swap . bid_id ,
msg_type = XmrSplitMsgTypes . BID ,
sequence = 2 ,
dleag = xmr_swap . kbsf_dleag [ 16000 : 32000 ]
)
msg_bytes = msg_buf2 . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . XMR_BID_SPLIT ) + msg_bytes . hex ( )
2022-07-01 14:37:10 +00:00
xmr_swap . bid_msg_id2 = self . sendSmsg ( bid_addr , offer . addr_from , payload_hex , msg_valid )
2021-02-11 12:57:54 +00:00
msg_buf3 = XmrSplitMessage (
msg_id = xmr_swap . bid_id ,
msg_type = XmrSplitMsgTypes . BID ,
sequence = 3 ,
dleag = xmr_swap . kbsf_dleag [ 32000 : ]
)
msg_bytes = msg_buf3 . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . XMR_BID_SPLIT ) + msg_bytes . hex ( )
2022-07-01 14:37:10 +00:00
xmr_swap . bid_msg_id3 = self . sendSmsg ( bid_addr , offer . addr_from , payload_hex , msg_valid )
2020-11-14 22:13:11 +00:00
bid = Bid (
2021-11-04 21:49:52 +00:00
protocol_version = msg_buf . protocol_version ,
2021-02-15 13:34:47 +00:00
active_ind = 1 ,
2020-11-14 22:13:11 +00:00
bid_id = xmr_swap . bid_id ,
offer_id = offer_id ,
amount = msg_buf . amount ,
2021-11-14 23:26:43 +00:00
rate = msg_buf . rate ,
2020-11-14 22:13:11 +00:00
created_at = bid_created_at ,
contract_count = xmr_swap . contract_count ,
2021-11-22 20:24:48 +00:00
amount_to = ( msg_buf . amount * msg_buf . rate ) / / ci_from . COIN ( ) ,
2020-11-14 22:13:11 +00:00
expire_at = bid_created_at + msg_buf . time_valid ,
bid_addr = bid_addr ,
was_sent = True ,
)
2021-11-04 21:49:52 +00:00
bid . chain_a_height_start = ci_from . getChainHeight ( )
bid . chain_b_height_start = ci_to . getChainHeight ( )
wallet_restore_height = self . getWalletRestoreHeight ( ci_to )
if bid . chain_b_height_start < wallet_restore_height :
bid . chain_b_height_start = wallet_restore_height
self . log . warning ( ' XMR swap restore height clamped to {} ' . format ( wallet_restore_height ) )
2020-11-14 22:13:11 +00:00
bid . setState ( BidStates . BID_SENT )
2021-01-11 21:48:46 +00:00
try :
session = scoped_session ( self . session_factory )
self . saveBidInSession ( xmr_swap . bid_id , bid , session , xmr_swap )
session . commit ( )
finally :
session . close ( )
session . remove ( )
2020-11-14 22:13:11 +00:00
2020-11-21 13:16:27 +00:00
self . log . info ( ' Sent XMR_BID_FL %s ' , xmr_swap . bid_id . hex ( ) )
2020-11-14 22:13:11 +00:00
return xmr_swap . bid_id
finally :
self . mxDB . release ( )
def acceptXmrBid ( self , bid_id ) :
# MSG1F and MSG2F L -> F
self . log . info ( ' Accepting xmr bid %s ' , bid_id . hex ( ) )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-14 22:13:11 +00:00
self . mxDB . acquire ( )
try :
bid , xmr_swap = self . getXmrBid ( bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( bid . expire_at > now , ' Bid expired ' )
2021-10-18 18:48:48 +00:00
last_bid_state = bid . state
if last_bid_state == BidStates . SWAP_DELAYING :
last_bid_state = getLastBidState ( bid . states )
2021-10-21 22:47:04 +00:00
ensure ( last_bid_state == BidStates . BID_RECEIVED , ' Wrong bid state: {} ' . format ( str ( BidStates ( last_bid_state ) ) ) )
2020-11-14 22:13:11 +00:00
offer , xmr_offer = self . getXmrOffer ( bid . offer_id )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( offer . expire_at > now , ' Offer has expired ' )
2020-11-14 22:13:11 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
if xmr_swap . contract_count is None :
xmr_swap . contract_count = self . getNewContractId ( )
2023-05-11 21:45:06 +00:00
for_ed25519 = True if ci_to . curve_type ( ) == Curves . ed25519 else False
2021-12-19 06:59:35 +00:00
kbvl = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KBVL , for_ed25519 )
kbsl = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KBSL , for_ed25519 )
2020-11-14 22:13:11 +00:00
2021-12-19 06:59:35 +00:00
kal = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KAL )
2020-11-14 22:13:11 +00:00
2020-11-21 13:16:27 +00:00
xmr_swap . vkbvl = kbvl
xmr_swap . pkbvl = ci_to . getPubkey ( kbvl )
xmr_swap . pkbsl = ci_to . getPubkey ( kbsl )
xmr_swap . vkbv = ci_to . sumKeys ( kbvl , xmr_swap . vkbvf )
2021-11-01 13:52:40 +00:00
ensure ( ci_to . verifyKey ( xmr_swap . vkbv ) , ' Invalid key, vkbv ' )
2020-11-21 13:16:27 +00:00
xmr_swap . pkbv = ci_to . sumPubkeys ( xmr_swap . pkbvl , xmr_swap . pkbvf )
xmr_swap . pkbs = ci_to . sumPubkeys ( xmr_swap . pkbsl , xmr_swap . pkbsf )
2020-11-14 22:13:11 +00:00
xmr_swap . pkal = ci_from . getPubkey ( kal )
# MSG2F
2022-12-05 22:45:35 +00:00
pi = self . pi ( SwapTypes . XMR_SWAP )
xmr_swap . a_lock_tx_script = pi . genScriptLockTxScript ( ci_from , xmr_swap . pkal , xmr_swap . pkaf )
prefunded_tx = self . getPreFundedTx ( Concepts . OFFER , bid . offer_id , TxTypes . ITX_PRE_FUNDED )
if prefunded_tx :
xmr_swap . a_lock_tx = pi . promoteMockTx ( ci_from , prefunded_tx , xmr_swap . a_lock_tx_script )
else :
xmr_swap . a_lock_tx = ci_from . createSCLockTx (
bid . amount ,
xmr_swap . a_lock_tx_script , xmr_swap . vkbv
)
xmr_swap . a_lock_tx = ci_from . fundSCLockTx ( xmr_swap . a_lock_tx , xmr_offer . a_fee_rate , xmr_swap . vkbv )
2020-11-14 22:13:11 +00:00
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_tx_id = ci_from . getTxid ( xmr_swap . a_lock_tx )
2020-11-21 13:16:27 +00:00
a_lock_tx_dest = ci_from . getScriptDest ( xmr_swap . a_lock_tx_script )
2020-11-14 22:13:11 +00:00
2022-11-07 20:31:10 +00:00
xmr_swap . a_lock_refund_tx , xmr_swap . a_lock_refund_tx_script , xmr_swap . a_swap_refund_value = ci_from . createSCLockRefundTx (
2020-11-14 22:13:11 +00:00
xmr_swap . a_lock_tx , xmr_swap . a_lock_tx_script ,
2020-12-10 22:43:36 +00:00
xmr_swap . pkal , xmr_swap . pkaf ,
xmr_offer . lock_time_1 , xmr_offer . lock_time_2 ,
2021-11-01 13:52:40 +00:00
xmr_offer . a_fee_rate , xmr_swap . vkbv
2020-11-14 22:13:11 +00:00
)
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_refund_tx_id = ci_from . getTxid ( xmr_swap . a_lock_refund_tx )
2020-11-14 22:13:11 +00:00
2021-11-01 13:52:40 +00:00
prevout_amount = ci_from . getLockTxSwapOutputValue ( bid , xmr_swap )
xmr_swap . al_lock_refund_tx_sig = ci_from . signTx ( kal , xmr_swap . a_lock_refund_tx , 0 , xmr_swap . a_lock_tx_script , prevout_amount )
v = ci_from . verifyTxSig ( xmr_swap . a_lock_refund_tx , xmr_swap . al_lock_refund_tx_sig , xmr_swap . pkal , 0 , xmr_swap . a_lock_tx_script , prevout_amount )
ensure ( v , ' Invalid coin A lock refund tx leader sig ' )
2020-11-14 22:13:11 +00:00
2020-11-27 17:52:26 +00:00
pkh_refund_to = ci_from . decodeAddress ( self . getReceiveAddressForCoin ( coin_from ) )
2022-11-07 20:31:10 +00:00
xmr_swap . a_lock_refund_spend_tx = ci_from . createSCLockRefundSpendTx (
2020-11-14 22:13:11 +00:00
xmr_swap . a_lock_refund_tx , xmr_swap . a_lock_refund_tx_script ,
2020-11-27 17:52:26 +00:00
pkh_refund_to ,
2021-11-01 13:52:40 +00:00
xmr_offer . a_fee_rate , xmr_swap . vkbv
2020-11-14 22:13:11 +00:00
)
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_refund_spend_tx_id = ci_from . getTxid ( xmr_swap . a_lock_refund_spend_tx )
# Double check txns before sending
self . log . debug ( ' Bid: {} - Double checking chain A lock txns are valid before sending bid accept. ' . format ( bid_id . hex ( ) ) )
check_lock_tx_inputs = False # TODO: check_lock_tx_inputs without txindex
2022-11-07 20:31:10 +00:00
_ , xmr_swap . a_lock_tx_vout = ci_from . verifySCLockTx (
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_tx ,
xmr_swap . a_lock_tx_script ,
bid . amount ,
xmr_swap . pkal ,
xmr_swap . pkaf ,
xmr_offer . a_fee_rate ,
check_lock_tx_inputs ,
xmr_swap . vkbv )
2022-11-07 20:31:10 +00:00
_ , _ , lock_refund_vout = ci_from . verifySCLockRefundTx (
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_refund_tx ,
xmr_swap . a_lock_tx ,
xmr_swap . a_lock_refund_tx_script ,
xmr_swap . a_lock_tx_id ,
xmr_swap . a_lock_tx_vout ,
xmr_offer . lock_time_1 ,
xmr_swap . a_lock_tx_script ,
xmr_swap . pkal ,
xmr_swap . pkaf ,
xmr_offer . lock_time_2 ,
bid . amount ,
xmr_offer . a_fee_rate ,
xmr_swap . vkbv )
2022-11-07 20:31:10 +00:00
ci_from . verifySCLockRefundSpendTx (
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_refund_spend_tx , xmr_swap . a_lock_refund_tx ,
xmr_swap . a_lock_refund_tx_id , xmr_swap . a_lock_refund_tx_script ,
xmr_swap . pkal ,
lock_refund_vout , xmr_swap . a_swap_refund_value , xmr_offer . a_fee_rate ,
xmr_swap . vkbv )
2020-11-14 22:13:11 +00:00
msg_buf = XmrBidAcceptMessage ( )
msg_buf . bid_msg_id = bid_id
msg_buf . pkal = xmr_swap . pkal
msg_buf . kbvl = kbvl
2023-05-11 21:45:06 +00:00
if ci_to . curve_type ( ) == Curves . ed25519 :
xmr_swap . kbsl_dleag = ci_to . proveDLEAG ( kbsl )
2021-02-11 12:57:54 +00:00
msg_buf . kbsl_dleag = xmr_swap . kbsl_dleag [ : 16000 ]
2023-05-11 21:45:06 +00:00
elif ci_to . curve_type ( ) == Curves . secp256k1 :
for i in range ( 10 ) :
xmr_swap . kbsl_dleag = ci_to . signRecoverable ( kbsl , ' proof kbsl owned for swap ' )
pk_recovered = ci_to . verifySigAndRecover ( xmr_swap . kbsl_dleag , ' proof kbsl owned for swap ' )
if pk_recovered == xmr_swap . pkbsl :
break
self . log . debug ( ' kbsl recovered pubkey mismatch, retrying. ' )
assert ( pk_recovered == xmr_swap . pkbsl )
2021-02-11 12:57:54 +00:00
msg_buf . kbsl_dleag = xmr_swap . kbsl_dleag
2023-05-11 21:45:06 +00:00
else :
raise ValueError ( ' Unknown curve ' )
2020-11-14 22:13:11 +00:00
# MSG2F
msg_buf . a_lock_tx = xmr_swap . a_lock_tx
msg_buf . a_lock_tx_script = xmr_swap . a_lock_tx_script
msg_buf . a_lock_refund_tx = xmr_swap . a_lock_refund_tx
msg_buf . a_lock_refund_tx_script = xmr_swap . a_lock_refund_tx_script
msg_buf . a_lock_refund_spend_tx = xmr_swap . a_lock_refund_spend_tx
msg_buf . al_lock_refund_tx_sig = xmr_swap . al_lock_refund_tx_sig
msg_bytes = msg_buf . SerializeToString ( )
2020-11-21 13:16:27 +00:00
payload_hex = str . format ( ' {:02x} ' , MessageTypes . XMR_BID_ACCEPT_LF ) + msg_bytes . hex ( )
2022-07-01 14:37:10 +00:00
msg_valid = self . getAcceptBidMsgValidTime ( bid )
bid . accept_msg_id = self . sendSmsg ( offer . addr_from , bid . bid_addr , payload_hex , msg_valid )
2020-11-14 22:13:11 +00:00
xmr_swap . bid_accept_msg_id = bid . accept_msg_id
2023-05-11 21:45:06 +00:00
if ci_to . curve_type ( ) == Curves . ed25519 :
2021-02-11 12:57:54 +00:00
msg_buf2 = XmrSplitMessage (
msg_id = bid_id ,
msg_type = XmrSplitMsgTypes . BID_ACCEPT ,
sequence = 2 ,
dleag = xmr_swap . kbsl_dleag [ 16000 : 32000 ]
)
msg_bytes = msg_buf2 . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . XMR_BID_SPLIT ) + msg_bytes . hex ( )
2022-07-01 14:37:10 +00:00
xmr_swap . bid_accept_msg_id2 = self . sendSmsg ( offer . addr_from , bid . bid_addr , payload_hex , msg_valid )
2021-02-11 12:57:54 +00:00
msg_buf3 = XmrSplitMessage (
msg_id = bid_id ,
msg_type = XmrSplitMsgTypes . BID_ACCEPT ,
sequence = 3 ,
dleag = xmr_swap . kbsl_dleag [ 32000 : ]
)
msg_bytes = msg_buf3 . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . XMR_BID_SPLIT ) + msg_bytes . hex ( )
2022-07-01 14:37:10 +00:00
xmr_swap . bid_accept_msg_id3 = self . sendSmsg ( offer . addr_from , bid . bid_addr , payload_hex , msg_valid )
2020-11-14 22:13:11 +00:00
bid . setState ( BidStates . BID_ACCEPTED )
2021-01-11 21:48:46 +00:00
self . saveBid ( bid_id , bid , xmr_swap = xmr_swap )
2020-11-14 22:13:11 +00:00
2020-11-15 17:02:46 +00:00
# Add to swaps_in_progress only when waiting on txns
2020-11-21 13:16:27 +00:00
self . log . info ( ' Sent XMR_BID_ACCEPT_LF %s ' , bid_id . hex ( ) )
2020-11-14 22:13:11 +00:00
return bid_id
finally :
self . mxDB . release ( )
2023-03-08 22:53:54 +00:00
def deactivateBidForReason ( self , bid_id , new_state , session_in = None ) - > None :
2019-07-17 15:12:06 +00:00
try :
2023-03-08 22:53:54 +00:00
session = self . openSession ( session_in )
2019-07-17 15:12:06 +00:00
bid = session . query ( Bid ) . filter_by ( bid_id = bid_id ) . first ( )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found ' )
2019-07-17 15:12:06 +00:00
offer = session . query ( Offer ) . filter_by ( offer_id = bid . offer_id ) . first ( )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found ' )
2019-07-17 15:12:06 +00:00
2023-03-08 22:53:54 +00:00
bid . setState ( new_state )
2020-12-04 17:06:50 +00:00
self . deactivateBid ( session , offer , bid )
session . add ( bid )
2019-07-17 15:12:06 +00:00
session . commit ( )
finally :
2023-03-08 22:53:54 +00:00
if session_in is None :
self . closeSession ( session )
def abandonBid ( self , bid_id : bytes ) - > None :
2023-06-14 15:06:52 +00:00
if not self . debug :
self . log . error ( ' Can \' t abandon bid %s when not in debug mode. ' , bid_id . hex ( ) )
return
2023-03-08 22:53:54 +00:00
self . log . info ( ' Abandoning Bid %s ' , bid_id . hex ( ) )
self . deactivateBidForReason ( bid_id , BidStates . BID_ABANDONED )
def timeoutBid ( self , bid_id : bytes , session_in = None ) - > None :
self . log . info ( ' Bid %s timed-out ' , bid_id . hex ( ) )
self . deactivateBidForReason ( bid_id , BidStates . SWAP_TIMEDOUT )
2019-07-17 15:12:06 +00:00
2022-12-05 15:04:23 +00:00
def setBidError ( self , bid_id , bid , error_str , save_bid = True , xmr_swap = None ) - > None :
2020-11-15 17:02:46 +00:00
self . log . error ( ' Bid %s - Error: %s ' , bid_id . hex ( ) , error_str )
2019-07-23 22:33:27 +00:00
bid . setState ( BidStates . BID_ERROR )
bid . state_note = ' error msg: ' + error_str
2020-11-29 23:05:30 +00:00
if save_bid :
2021-11-01 13:52:40 +00:00
self . saveBid ( bid_id , bid , xmr_swap = xmr_swap )
2019-07-23 22:33:27 +00:00
2022-12-05 15:04:23 +00:00
def createInitiateTxn ( self , coin_type , bid_id , bid , initiate_script , prefunded_tx = None ) - > Optional [ str ] :
2019-07-17 15:12:06 +00:00
if self . coin_clients [ coin_type ] [ ' connection_type ' ] != ' rpc ' :
return None
2021-02-03 14:01:27 +00:00
ci = self . ci ( coin_type )
2019-07-17 15:12:06 +00:00
if self . coin_clients [ coin_type ] [ ' use_segwit ' ] :
2021-11-05 08:55:18 +00:00
addr_to = ci . encode_p2wsh ( getP2WSH ( initiate_script ) )
2019-07-17 15:12:06 +00:00
else :
2021-11-05 08:55:18 +00:00
addr_to = ci . encode_p2sh ( initiate_script )
2019-07-17 15:12:06 +00:00
self . log . debug ( ' Create initiate txn for coin %s to %s for bid %s ' , str ( coin_type ) , addr_to , bid_id . hex ( ) )
2019-07-26 21:03:56 +00:00
2022-12-05 15:04:23 +00:00
if prefunded_tx :
pi = self . pi ( SwapTypes . SELLER_FIRST )
txn_signed = pi . promoteMockTx ( ci , prefunded_tx , initiate_script ) . hex ( )
else :
txn_signed = ci . createRawSignedTransaction ( addr_to , bid . amount )
2019-07-17 15:12:06 +00:00
return txn_signed
def deriveParticipateScript ( self , bid_id , bid , offer ) :
self . log . debug ( ' deriveParticipateScript for bid %s ' , bid_id . hex ( ) )
coin_to = Coins ( offer . coin_to )
2021-02-03 14:01:27 +00:00
ci_to = self . ci ( coin_to )
2019-07-17 15:12:06 +00:00
2020-12-10 14:37:26 +00:00
secret_hash = atomic_swap_1 . extractScriptSecretHash ( bid . initiate_tx . script )
2019-07-17 15:12:06 +00:00
pkhash_seller = bid . pkhash_seller
pkhash_buyer_refund = bid . pkhash_buyer
2019-07-24 22:59:40 +00:00
# Participate txn is locked for half the time of the initiate txn
lock_value = offer . lock_value / / 2
2021-12-15 13:41:43 +00:00
if offer . lock_type < TxLockTypes . ABS_LOCK_BLOCKS :
2021-02-03 14:01:27 +00:00
sequence = ci_to . getExpectedSequence ( offer . lock_type , lock_value )
2020-12-10 10:07:26 +00:00
participate_script = atomic_swap_1 . buildContractScript ( sequence , secret_hash , pkhash_seller , pkhash_buyer_refund )
2019-07-24 22:59:40 +00:00
else :
2019-07-25 09:29:48 +00:00
# Lock from the height or time of the block containing the initiate txn
coin_from = Coins ( offer . coin_from )
2019-07-27 18:51:50 +00:00
initiate_tx_block_hash = self . callcoinrpc ( coin_from , ' getblockhash ' , [ bid . initiate_tx . chain_height , ] )
2019-07-25 12:06:58 +00:00
initiate_tx_block_time = int ( self . callcoinrpc ( coin_from , ' getblock ' , [ initiate_tx_block_hash , ] ) [ ' time ' ] )
2021-12-15 13:41:43 +00:00
if offer . lock_type == TxLockTypes . ABS_LOCK_BLOCKS :
2019-07-25 09:29:48 +00:00
# Walk the coin_to chain back until block time matches
2022-10-11 05:55:35 +00:00
block_header_at = ci_to . getBlockHeaderAt ( initiate_tx_block_time , block_after = True )
cblock_hash = block_header_at [ ' hash ' ]
cblock_height = block_header_at [ ' height ' ]
2019-07-25 09:29:48 +00:00
self . log . debug ( ' Setting lock value from height of block %s %s ' , coin_to , cblock_hash )
contract_lock_value = cblock_height + lock_value
2019-07-24 22:59:40 +00:00
else :
2019-07-25 09:29:48 +00:00
self . log . debug ( ' Setting lock value from time of block %s %s ' , coin_from , initiate_tx_block_hash )
contract_lock_value = initiate_tx_block_time + lock_value
self . log . debug ( ' participate %s lock_value %d %d ' , coin_to , lock_value , contract_lock_value )
2020-12-10 10:07:26 +00:00
participate_script = atomic_swap_1 . buildContractScript ( contract_lock_value , secret_hash , pkhash_seller , pkhash_buyer_refund , OpCodes . OP_CHECKLOCKTIMEVERIFY )
2019-07-27 19:50:50 +00:00
return participate_script
2019-07-17 15:12:06 +00:00
2019-07-27 19:50:50 +00:00
def createParticipateTxn ( self , bid_id , bid , offer , participate_script ) :
2019-07-17 15:12:06 +00:00
self . log . debug ( ' createParticipateTxn ' )
offer_id = bid . offer_id
coin_to = Coins ( offer . coin_to )
if self . coin_clients [ coin_to ] [ ' connection_type ' ] != ' rpc ' :
return None
2021-02-03 14:01:27 +00:00
ci = self . ci ( coin_to )
2019-07-17 15:12:06 +00:00
amount_to = bid . amount_to
# Check required?
2022-07-31 18:01:49 +00:00
assert ( amount_to == ( bid . amount * bid . rate ) / / self . ci ( offer . coin_from ) . COIN ( ) )
2019-07-17 15:12:06 +00:00
2021-01-30 14:29:07 +00:00
if bid . debug_ind == DebugTypes . MAKE_INVALID_PTX :
amount_to - = 1
self . log . debug ( ' bid %s : Make invalid PTx for testing: %d . ' , bid_id . hex ( ) , bid . debug_ind )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . DEBUG_TWEAK_APPLIED , ' ind {} ' . format ( bid . debug_ind ) , None )
2021-01-30 14:29:07 +00:00
2019-07-17 15:12:06 +00:00
if self . coin_clients [ coin_to ] [ ' use_segwit ' ] :
2019-07-27 19:50:50 +00:00
p2wsh = getP2WSH ( participate_script )
2021-11-05 08:55:18 +00:00
addr_to = ci . encode_p2wsh ( p2wsh )
2019-07-17 15:12:06 +00:00
else :
2021-11-05 08:55:18 +00:00
addr_to = ci . encode_p2sh ( participate_script )
2019-07-17 15:12:06 +00:00
2022-08-16 18:52:43 +00:00
txn_signed = ci . createRawSignedTransaction ( addr_to , amount_to )
2019-07-17 15:12:06 +00:00
2019-07-27 19:50:50 +00:00
refund_txn = self . createRefundTxn ( coin_to , txn_signed , offer , bid , participate_script , tx_type = TxTypes . PTX_REFUND )
2019-07-17 15:12:06 +00:00
bid . participate_txn_refund = bytes . fromhex ( refund_txn )
2021-10-23 14:00:32 +00:00
chain_height = self . callcoinrpc ( coin_to , ' getblockcount ' )
2019-07-17 15:12:06 +00:00
txjs = self . callcoinrpc ( coin_to , ' decoderawtransaction ' , [ txn_signed ] )
txid = txjs [ ' txid ' ]
if self . coin_clients [ coin_to ] [ ' use_segwit ' ] :
vout = getVoutByP2WSH ( txjs , p2wsh . hex ( ) )
else :
vout = getVoutByAddress ( txjs , addr_to )
self . addParticipateTxn ( bid_id , bid , coin_to , txid , vout , chain_height )
2019-07-27 19:50:50 +00:00
bid . participate_tx . script = participate_script
2021-11-05 22:34:25 +00:00
bid . participate_tx . tx_data = bytes . fromhex ( txn_signed )
2019-07-17 15:12:06 +00:00
return txn_signed
def getContractSpendTxVSize ( self , coin_type , redeem = True ) :
tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes
if coin_type == Coins . PART :
tx_vsize + = 204 if redeem else 187
if self . coin_clients [ coin_type ] [ ' use_segwit ' ] :
tx_vsize + = 143 if redeem else 134
else :
tx_vsize + = 323 if redeem else 287
return tx_vsize
def createRedeemTxn ( self , coin_type , bid , for_txn_type = ' participate ' , addr_redeem_out = None , fee_rate = None ) :
self . log . debug ( ' createRedeemTxn for coin %s ' , str ( coin_type ) )
2021-02-03 14:01:27 +00:00
ci = self . ci ( coin_type )
2019-07-17 15:12:06 +00:00
if for_txn_type == ' participate ' :
2019-07-27 19:50:50 +00:00
prev_txnid = bid . participate_tx . txid . hex ( )
prev_n = bid . participate_tx . vout
txn_script = bid . participate_tx . script
2019-07-17 15:12:06 +00:00
prev_amount = bid . amount_to
else :
2019-07-27 17:26:06 +00:00
prev_txnid = bid . initiate_tx . txid . hex ( )
prev_n = bid . initiate_tx . vout
2019-07-27 16:00:13 +00:00
txn_script = bid . initiate_tx . script
2019-07-17 15:12:06 +00:00
prev_amount = bid . amount
if self . coin_clients [ coin_type ] [ ' use_segwit ' ] :
prev_p2wsh = getP2WSH ( txn_script )
script_pub_key = prev_p2wsh . hex ( )
else :
script_pub_key = getP2SHScriptForHash ( getKeyID ( txn_script ) ) . hex ( )
prevout = {
' txid ' : prev_txnid ,
' vout ' : prev_n ,
' scriptPubKey ' : script_pub_key ,
' redeemScript ' : txn_script . hex ( ) ,
2021-02-03 14:01:27 +00:00
' amount ' : ci . format_amount ( prev_amount ) }
2019-07-17 15:12:06 +00:00
bid_date = dt . datetime . fromtimestamp ( bid . created_at ) . date ( )
wif_prefix = chainparams [ Coins . PART ] [ self . chain ] [ ' key_prefix ' ]
pubkey = self . getContractPubkey ( bid_date , bid . contract_count )
privkey = toWIF ( wif_prefix , self . getContractPrivkey ( bid_date , bid . contract_count ) )
secret = bid . recovered_secret
if secret is None :
secret = self . getContractSecret ( bid_date , bid . contract_count )
2021-10-21 22:47:04 +00:00
ensure ( len ( secret ) == 32 , ' Bad secret length ' )
2019-07-17 15:12:06 +00:00
if self . coin_clients [ coin_type ] [ ' connection_type ' ] != ' rpc ' :
return None
if fee_rate is None :
2020-12-18 21:04:06 +00:00
fee_rate , fee_src = self . getFeeRateForCoin ( coin_type )
2019-07-17 15:12:06 +00:00
tx_vsize = self . getContractSpendTxVSize ( coin_type )
tx_fee = ( fee_rate * tx_vsize ) / 1000
2021-01-18 22:52:05 +00:00
self . log . debug ( ' Redeem tx fee %s , rate %s ' , ci . format_amount ( tx_fee , conv_int = True , r = 1 ) , str ( fee_rate ) )
2019-07-17 15:12:06 +00:00
2021-01-18 22:52:05 +00:00
amount_out = prev_amount - ci . make_int ( tx_fee , r = 1 )
2021-10-21 22:47:04 +00:00
ensure ( amount_out > 0 , ' Amount out <= 0 ' )
2019-07-17 15:12:06 +00:00
if addr_redeem_out is None :
2019-07-23 17:19:31 +00:00
addr_redeem_out = self . getReceiveAddressFromPool ( coin_type , bid . bid_id , TxTypes . PTX_REDEEM if for_txn_type == ' participate ' else TxTypes . ITX_REDEEM )
2022-07-31 18:01:49 +00:00
assert ( addr_redeem_out is not None )
2019-07-17 15:12:06 +00:00
self . log . debug ( ' addr_redeem_out %s ' , addr_redeem_out )
2023-03-23 12:15:47 +00:00
redeem_txn = ci . createRedeemTxn ( prevout , addr_redeem_out , amount_out )
2019-07-17 15:12:06 +00:00
options = { }
if self . coin_clients [ coin_type ] [ ' use_segwit ' ] :
options [ ' force_segwit ' ] = True
2023-03-23 12:15:47 +00:00
2019-07-17 15:12:06 +00:00
redeem_sig = self . callcoinrpc ( Coins . PART , ' createsignaturewithkey ' , [ redeem_txn , prevout , privkey , ' ALL ' , options ] )
2023-03-23 12:15:47 +00:00
2019-07-17 15:12:06 +00:00
if coin_type == Coins . PART or self . coin_clients [ coin_type ] [ ' use_segwit ' ] :
witness_stack = [
2023-03-23 12:15:47 +00:00
bytes . fromhex ( redeem_sig ) ,
pubkey ,
secret ,
bytes ( ( 1 , ) ) ,
txn_script ]
redeem_txn = ci . setTxSignature ( bytes . fromhex ( redeem_txn ) , witness_stack ) . hex ( )
2019-07-17 15:12:06 +00:00
else :
script = format ( len ( redeem_sig ) / / 2 , ' 02x ' ) + redeem_sig
script + = format ( 33 , ' 02x ' ) + pubkey . hex ( )
script + = format ( 32 , ' 02x ' ) + secret . hex ( )
script + = format ( OpCodes . OP_1 , ' 02x ' )
script + = format ( OpCodes . OP_PUSHDATA1 , ' 02x ' ) + format ( len ( txn_script ) , ' 02x ' ) + txn_script . hex ( )
2023-03-23 12:15:47 +00:00
redeem_txn = ci . setTxScriptSig ( bytes . fromhex ( redeem_txn ) , 0 , bytes . fromhex ( script ) ) . hex ( )
2019-07-17 15:12:06 +00:00
ro = self . callcoinrpc ( Coins . PART , ' verifyrawtransaction ' , [ redeem_txn , [ prevout ] ] )
2021-10-21 22:47:04 +00:00
ensure ( ro [ ' inputs_valid ' ] is True , ' inputs_valid is false ' )
2021-11-01 13:52:40 +00:00
# outputs_valid will be false if not a Particl txn
# ensure(ro['complete'] is True, 'complete is false')
2021-10-21 22:47:04 +00:00
ensure ( ro [ ' validscripts ' ] == 1 , ' validscripts != 1 ' )
2019-07-17 15:12:06 +00:00
if self . debug :
# Check fee
2022-08-10 22:02:36 +00:00
if ci . get_connection_type ( ) == ' rpc ' :
2019-07-17 15:12:06 +00:00
redeem_txjs = self . callcoinrpc ( coin_type , ' decoderawtransaction ' , [ redeem_txn ] )
2022-08-10 22:02:36 +00:00
if ci . using_segwit ( ) :
self . log . debug ( ' vsize paid, actual vsize %d %d ' , tx_vsize , redeem_txjs [ ' vsize ' ] )
ensure ( tx_vsize > = redeem_txjs [ ' vsize ' ] , ' underpaid fee ' )
else :
self . log . debug ( ' size paid, actual size %d %d ' , tx_vsize , redeem_txjs [ ' size ' ] )
ensure ( tx_vsize > = redeem_txjs [ ' size ' ] , ' underpaid fee ' )
2019-07-17 15:12:06 +00:00
redeem_txjs = self . callcoinrpc ( Coins . PART , ' decoderawtransaction ' , [ redeem_txn ] )
self . log . debug ( ' Have valid redeem txn %s for contract %s tx %s ' , redeem_txjs [ ' txid ' ] , for_txn_type , prev_txnid )
return redeem_txn
2019-07-24 22:59:40 +00:00
def createRefundTxn ( self , coin_type , txn , offer , bid , txn_script , addr_refund_out = None , tx_type = TxTypes . ITX_REFUND ) :
2021-11-27 15:58:58 +00:00
self . log . debug ( ' createRefundTxn for coin %s ' , Coins ( coin_type ) . name )
2019-07-17 15:12:06 +00:00
if self . coin_clients [ coin_type ] [ ' connection_type ' ] != ' rpc ' :
return None
txjs = self . callcoinrpc ( Coins . PART , ' decoderawtransaction ' , [ txn ] )
if self . coin_clients [ coin_type ] [ ' use_segwit ' ] :
p2wsh = getP2WSH ( txn_script )
vout = getVoutByP2WSH ( txjs , p2wsh . hex ( ) )
else :
2021-11-05 08:55:18 +00:00
addr_to = self . ci ( Coins . PART ) . encode_p2sh ( txn_script )
2019-07-17 15:12:06 +00:00
vout = getVoutByAddress ( txjs , addr_to )
bid_date = dt . datetime . fromtimestamp ( bid . created_at ) . date ( )
wif_prefix = chainparams [ Coins . PART ] [ self . chain ] [ ' key_prefix ' ]
pubkey = self . getContractPubkey ( bid_date , bid . contract_count )
privkey = toWIF ( wif_prefix , self . getContractPrivkey ( bid_date , bid . contract_count ) )
prev_amount = txjs [ ' vout ' ] [ vout ] [ ' value ' ]
prevout = {
' txid ' : txjs [ ' txid ' ] ,
' vout ' : vout ,
' scriptPubKey ' : txjs [ ' vout ' ] [ vout ] [ ' scriptPubKey ' ] [ ' hex ' ] ,
' redeemScript ' : txn_script . hex ( ) ,
' amount ' : prev_amount }
2019-07-24 22:59:40 +00:00
lock_value = DeserialiseNum ( txn_script , 64 )
2023-03-23 12:15:47 +00:00
sequence : int = 1
2021-12-15 13:41:43 +00:00
if offer . lock_type < TxLockTypes . ABS_LOCK_BLOCKS :
2019-07-24 22:59:40 +00:00
sequence = lock_value
2019-07-17 15:12:06 +00:00
2020-12-18 21:04:06 +00:00
fee_rate , fee_src = self . getFeeRateForCoin ( coin_type )
2019-07-17 15:12:06 +00:00
tx_vsize = self . getContractSpendTxVSize ( coin_type , False )
tx_fee = ( fee_rate * tx_vsize ) / 1000
2021-01-18 22:52:05 +00:00
ci = self . ci ( coin_type )
self . log . debug ( ' Refund tx fee %s , rate %s ' , ci . format_amount ( tx_fee , conv_int = True , r = 1 ) , str ( fee_rate ) )
2019-07-17 15:12:06 +00:00
2021-01-18 22:52:05 +00:00
amount_out = ci . make_int ( prev_amount , r = 1 ) - ci . make_int ( tx_fee , r = 1 )
2019-10-04 18:23:33 +00:00
if amount_out < = 0 :
raise ValueError ( ' Refund amount out <= 0 ' )
2019-07-17 15:12:06 +00:00
if addr_refund_out is None :
2019-07-23 17:19:31 +00:00
addr_refund_out = self . getReceiveAddressFromPool ( coin_type , bid . bid_id , tx_type )
2021-10-21 22:47:04 +00:00
ensure ( addr_refund_out is not None , ' addr_refund_out is null ' )
2019-07-17 15:12:06 +00:00
self . log . debug ( ' addr_refund_out %s ' , addr_refund_out )
2023-03-23 12:15:47 +00:00
locktime : int = 0
2021-12-15 13:41:43 +00:00
if offer . lock_type == TxLockTypes . ABS_LOCK_BLOCKS or offer . lock_type == TxLockTypes . ABS_LOCK_TIME :
2023-03-23 12:15:47 +00:00
locktime = lock_value
refund_txn = ci . createRefundTxn ( prevout , addr_refund_out , amount_out , locktime , sequence )
2019-07-24 22:59:40 +00:00
2019-07-17 15:12:06 +00:00
options = { }
if self . coin_clients [ coin_type ] [ ' use_segwit ' ] :
options [ ' force_segwit ' ] = True
refund_sig = self . callcoinrpc ( Coins . PART , ' createsignaturewithkey ' , [ refund_txn , prevout , privkey , ' ALL ' , options ] )
if coin_type == Coins . PART or self . coin_clients [ coin_type ] [ ' use_segwit ' ] :
witness_stack = [
2023-03-23 12:15:47 +00:00
bytes . fromhex ( refund_sig ) ,
pubkey ,
b ' ' ,
txn_script ]
refund_txn = ci . setTxSignature ( bytes . fromhex ( refund_txn ) , witness_stack ) . hex ( )
2019-07-17 15:12:06 +00:00
else :
script = format ( len ( refund_sig ) / / 2 , ' 02x ' ) + refund_sig
script + = format ( 33 , ' 02x ' ) + pubkey . hex ( )
script + = format ( OpCodes . OP_0 , ' 02x ' )
script + = format ( OpCodes . OP_PUSHDATA1 , ' 02x ' ) + format ( len ( txn_script ) , ' 02x ' ) + txn_script . hex ( )
2023-03-23 12:15:47 +00:00
refund_txn = ci . setTxScriptSig ( bytes . fromhex ( refund_txn ) , 0 , bytes . fromhex ( script ) ) . hex ( )
2019-07-17 15:12:06 +00:00
ro = self . callcoinrpc ( Coins . PART , ' verifyrawtransaction ' , [ refund_txn , [ prevout ] ] )
2021-10-21 22:47:04 +00:00
ensure ( ro [ ' inputs_valid ' ] is True , ' inputs_valid is false ' )
2021-11-01 13:52:40 +00:00
# outputs_valid will be false if not a Particl txn
# ensure(ro['complete'] is True, 'complete is false')
2021-10-21 22:47:04 +00:00
ensure ( ro [ ' validscripts ' ] == 1 , ' validscripts != 1 ' )
2019-07-17 15:12:06 +00:00
if self . debug :
# Check fee
2022-08-10 22:02:36 +00:00
if ci . get_connection_type ( ) == ' rpc ' :
2019-07-17 15:12:06 +00:00
refund_txjs = self . callcoinrpc ( coin_type , ' decoderawtransaction ' , [ refund_txn ] )
2022-08-10 22:02:36 +00:00
if ci . using_segwit ( ) :
self . log . debug ( ' vsize paid, actual vsize %d %d ' , tx_vsize , refund_txjs [ ' vsize ' ] )
ensure ( tx_vsize > = refund_txjs [ ' vsize ' ] , ' underpaid fee ' )
else :
self . log . debug ( ' size paid, actual size %d %d ' , tx_vsize , refund_txjs [ ' size ' ] )
ensure ( tx_vsize > = refund_txjs [ ' size ' ] , ' underpaid fee ' )
2019-07-17 15:12:06 +00:00
refund_txjs = self . callcoinrpc ( Coins . PART , ' decoderawtransaction ' , [ refund_txn ] )
self . log . debug ( ' Have valid refund txn %s for contract tx %s ' , refund_txjs [ ' txid ' ] , txjs [ ' txid ' ] )
return refund_txn
def initiateTxnConfirmed ( self , bid_id , bid , offer ) :
self . log . debug ( ' initiateTxnConfirmed for bid %s ' , bid_id . hex ( ) )
bid . setState ( BidStates . SWAP_INITIATED )
2019-07-27 18:51:50 +00:00
bid . setITxState ( TxStates . TX_CONFIRMED )
2019-07-17 15:12:06 +00:00
2021-01-30 14:29:07 +00:00
if bid . debug_ind == DebugTypes . BUYER_STOP_AFTER_ITX :
2021-11-27 15:58:58 +00:00
self . log . debug ( ' bid %s : Abandoning bid for testing: %d , %s . ' , bid_id . hex ( ) , bid . debug_ind , DebugTypes ( bid . debug_ind ) . name )
2021-01-30 14:29:07 +00:00
bid . setState ( BidStates . BID_ABANDONED )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . DEBUG_TWEAK_APPLIED , ' ind {} ' . format ( bid . debug_ind ) , None )
2021-01-30 14:29:07 +00:00
return # Bid saved in checkBidState
2019-07-17 15:12:06 +00:00
# Seller first mode, buyer participates
2019-07-27 19:50:50 +00:00
participate_script = self . deriveParticipateScript ( bid_id , bid , offer )
2019-07-17 15:12:06 +00:00
if bid . was_sent :
2019-08-05 18:31:02 +00:00
if bid . participate_tx is not None :
self . log . warning ( ' Participate txn %s already exists for bid %s ' , bid . participate_tx . txid , bid_id . hex ( ) )
else :
self . log . debug ( ' Preparing participate txn for bid %s ' , bid_id . hex ( ) )
2019-07-17 15:12:06 +00:00
2019-08-05 18:31:02 +00:00
coin_to = Coins ( offer . coin_to )
txn = self . createParticipateTxn ( bid_id , bid , offer , participate_script )
2022-07-04 20:29:49 +00:00
txid = self . ci ( coin_to ) . publishTx ( bytes . fromhex ( txn ) )
2019-08-05 18:31:02 +00:00
self . log . debug ( ' Submitted participate txn %s to %s chain for bid %s ' , txid , chainparams [ coin_to ] [ ' name ' ] , bid_id . hex ( ) )
bid . setPTxState ( TxStates . TX_SENT )
2022-11-08 21:07:58 +00:00
self . logEvent ( Concepts . BID , bid . bid_id , EventLogTypes . PTX_PUBLISHED , ' ' , None )
2019-07-27 19:50:50 +00:00
else :
bid . participate_tx = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . PTX ,
script = participate_script ,
)
2019-07-17 15:12:06 +00:00
2021-01-30 14:29:07 +00:00
# Bid saved in checkBidState
2019-07-17 15:12:06 +00:00
2019-07-20 17:29:48 +00:00
def setLastHeightChecked ( self , coin_type , tx_height ) :
2021-11-01 13:52:40 +00:00
coin_name = self . ci ( coin_type ) . coin_name ( )
2019-07-20 17:29:48 +00:00
if tx_height < 1 :
tx_height = self . lookupChainHeight ( coin_type )
2019-07-17 15:12:06 +00:00
if len ( self . coin_clients [ coin_type ] [ ' watched_outputs ' ] ) == 0 :
self . coin_clients [ coin_type ] [ ' last_height_checked ' ] = tx_height
2021-11-01 13:52:40 +00:00
self . log . debug ( ' Start checking %s chain at height %d ' , coin_name , tx_height )
2019-07-17 15:12:06 +00:00
if self . coin_clients [ coin_type ] [ ' last_height_checked ' ] > tx_height :
self . coin_clients [ coin_type ] [ ' last_height_checked ' ] = tx_height
2021-11-01 13:52:40 +00:00
self . log . debug ( ' Rewind checking of %s chain to height %d ' , coin_name , tx_height )
2019-07-17 15:12:06 +00:00
2019-07-21 16:25:01 +00:00
return tx_height
2019-07-20 17:29:48 +00:00
def addParticipateTxn ( self , bid_id , bid , coin_type , txid_hex , vout , tx_height ) :
# TODO: Check connection type
2019-07-27 19:50:50 +00:00
participate_txn_height = self . setLastHeightChecked ( coin_type , tx_height )
2019-07-20 17:29:48 +00:00
2019-07-27 19:50:50 +00:00
if bid . participate_tx is None :
bid . participate_tx = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . PTX ,
)
bid . participate_tx . txid = bytes . fromhex ( txid_hex )
bid . participate_tx . vout = vout
bid . participate_tx . chain_height = participate_txn_height
# Start checking for spends of participate_txn before fully confirmed
self . log . debug ( ' Watching %s chain for spend of output %s %d ' , chainparams [ coin_type ] [ ' name ' ] , txid_hex , vout )
2019-07-23 15:08:12 +00:00
self . addWatchedOutput ( coin_type , bid_id , txid_hex , vout , BidStates . SWAP_PARTICIPATING )
2019-07-17 15:12:06 +00:00
def participateTxnConfirmed ( self , bid_id , bid , offer ) :
self . log . debug ( ' participateTxnConfirmed for bid %s ' , bid_id . hex ( ) )
bid . setState ( BidStates . SWAP_PARTICIPATING )
2019-07-27 18:51:50 +00:00
bid . setPTxState ( TxStates . TX_CONFIRMED )
2019-07-17 15:12:06 +00:00
# Seller redeems from participate txn
if bid . was_received :
2022-07-04 20:29:49 +00:00
ci_to = self . ci ( offer . coin_to )
txn = self . createRedeemTxn ( ci_to . coin_type ( ) , bid )
txid = ci_to . publishTx ( bytes . fromhex ( txn ) )
self . log . debug ( ' Submitted participate redeem txn %s to %s chain for bid %s ' , txid , ci_to . coin_name ( ) , bid_id . hex ( ) )
2022-11-08 21:07:58 +00:00
self . logEvent ( Concepts . BID , bid . bid_id , EventLogTypes . PTX_REDEEM_PUBLISHED , ' ' , None )
2019-07-17 15:12:06 +00:00
# TX_REDEEMED will be set when spend is detected
# TODO: Wait for depth?
2019-07-19 14:52:44 +00:00
# bid saved in checkBidState
2019-07-17 15:12:06 +00:00
2019-08-05 22:04:40 +00:00
def getAddressBalance ( self , coin_type , address ) :
if self . coin_clients [ coin_type ] [ ' chain_lookups ' ] == ' explorer ' :
explorers = self . coin_clients [ coin_type ] [ ' explorers ' ]
# TODO: random offset into explorers, try blocks
for exp in explorers :
return exp . getBalance ( address )
return self . lookupUnspentByAddress ( coin_type , address , sum_output = True )
2019-07-17 15:12:06 +00:00
def lookupChainHeight ( self , coin_type ) :
2021-10-23 14:00:32 +00:00
return self . callcoinrpc ( coin_type , ' getblockcount ' )
2019-07-17 15:12:06 +00:00
def lookupUnspentByAddress ( self , coin_type , address , sum_output = False , assert_amount = None , assert_txid = None ) :
2019-07-31 18:21:41 +00:00
2021-01-18 22:52:05 +00:00
ci = self . ci ( coin_type )
2019-08-05 22:04:40 +00:00
if self . coin_clients [ coin_type ] [ ' chain_lookups ' ] == ' explorer ' :
explorers = self . coin_clients [ coin_type ] [ ' explorers ' ]
# TODO: random offset into explorers, try blocks
for exp in explorers :
# TODO: ExplorerBitAps use only gettransaction if assert_txid is set
rv = exp . lookupUnspentByAddress ( address )
if assert_amount is not None :
2021-10-21 22:47:04 +00:00
ensure ( rv [ ' value ' ] == int ( assert_amount ) , ' Incorrect output amount in txn {} : {} != {} . ' . format ( assert_txid , rv [ ' value ' ] , int ( assert_amount ) ) )
2019-08-05 22:04:40 +00:00
if assert_txid is not None :
2021-10-21 22:47:04 +00:00
ensure ( rv [ ' txid) ' ] == assert_txid , ' Incorrect txid ' )
2019-08-05 22:04:40 +00:00
return rv
raise ValueError ( ' No explorer for lookupUnspentByAddress {} ' . format ( str ( coin_type ) ) )
if self . coin_clients [ coin_type ] [ ' connection_type ' ] != ' rpc ' :
raise ValueError ( ' No RPC connection for lookupUnspentByAddress {} ' . format ( str ( coin_type ) ) )
2019-07-31 18:21:41 +00:00
2019-07-31 18:49:45 +00:00
if assert_txid is not None :
2019-07-31 18:21:41 +00:00
try :
ro = self . callcoinrpc ( coin_type , ' getmempoolentry ' , [ assert_txid ] )
self . log . debug ( ' Tx %s found in mempool, fee %s ' , assert_txid , ro [ ' fee ' ] )
# TODO: Save info
return None
except Exception :
pass
2021-10-23 14:00:32 +00:00
num_blocks = self . callcoinrpc ( coin_type , ' getblockcount ' )
2019-07-17 15:12:06 +00:00
sum_unspent = 0
self . log . debug ( ' [rm] scantxoutset start ' ) # scantxoutset is slow
2021-11-27 15:58:58 +00:00
ro = self . callcoinrpc ( coin_type , ' scantxoutset ' , [ ' start ' , [ ' addr( {} ) ' . format ( address ) ] ] ) # TODO: Use combo(address) where possible
2019-07-17 15:12:06 +00:00
self . log . debug ( ' [rm] scantxoutset end ' )
for o in ro [ ' unspents ' ] :
if assert_txid and o [ ' txid ' ] != assert_txid :
continue
# Verify amount
if assert_amount :
2021-10-21 22:47:04 +00:00
ensure ( make_int ( o [ ' amount ' ] ) == int ( assert_amount ) , ' Incorrect output amount in txn {} : {} != {} . ' . format ( assert_txid , make_int ( o [ ' amount ' ] ) , int ( assert_amount ) ) )
2019-07-17 15:12:06 +00:00
if not sum_output :
if o [ ' height ' ] > 0 :
n_conf = num_blocks - o [ ' height ' ]
else :
n_conf = - 1
return {
' txid ' : o [ ' txid ' ] ,
' index ' : o [ ' vout ' ] ,
' height ' : o [ ' height ' ] ,
' n_conf ' : n_conf ,
2021-01-18 22:52:05 +00:00
' value ' : ci . make_int ( o [ ' amount ' ] ) ,
2019-07-17 15:12:06 +00:00
}
else :
2021-01-18 22:52:05 +00:00
sum_unspent + = ci . make_int ( o [ ' amount ' ] )
2019-07-17 15:12:06 +00:00
if sum_output :
return sum_unspent
return None
2022-12-11 18:31:43 +00:00
def findTxB ( self , ci_to , xmr_swap , bid , session ) - > bool :
bid_changed = False
# Have to use findTxB instead of relying on the first seen height to detect chain reorgs
found_tx = ci_to . findTxB ( xmr_swap . vkbv , xmr_swap . pkbs , bid . amount_to , ci_to . blocks_confirmed , bid . chain_b_height_start , bid . was_sent )
if isinstance ( found_tx , int ) and found_tx == - 1 :
if self . countBidEvents ( bid , EventLogTypes . LOCK_TX_B_INVALID , session ) < 1 :
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_B_INVALID , ' Detected invalid lock tx B ' , session )
bid_changed = True
elif found_tx is not None :
if bid . xmr_b_lock_tx is None or not bid . xmr_b_lock_tx . chain_height :
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_B_SEEN , ' ' , session )
if bid . xmr_b_lock_tx is None :
self . log . debug ( ' Found {} lock tx in chain ' . format ( ci_to . coin_name ( ) ) )
xmr_swap . b_lock_tx_id = bytes . fromhex ( found_tx [ ' txid ' ] )
bid . xmr_b_lock_tx = SwapTx (
bid_id = bid . bid_id ,
tx_type = TxTypes . XMR_SWAP_B_LOCK ,
txid = xmr_swap . b_lock_tx_id ,
chain_height = found_tx [ ' height ' ] ,
)
bid_changed = True
else :
bid . xmr_b_lock_tx . chain_height = found_tx [ ' height ' ]
bid_changed = True
return bid_changed
2020-11-14 22:13:11 +00:00
def checkXmrBidState ( self , bid_id , bid , offer ) :
2020-11-21 13:16:27 +00:00
rv = False
2020-11-27 17:52:26 +00:00
ci_from = self . ci ( Coins ( offer . coin_from ) )
ci_to = self . ci ( Coins ( offer . coin_to ) )
session = None
try :
self . mxDB . acquire ( )
session = scoped_session ( self . session_factory )
xmr_offer = session . query ( XmrOffer ) . filter_by ( offer_id = offer . offer_id ) . first ( )
2021-10-21 22:47:04 +00:00
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( offer . offer_id . hex ( ) ) )
2020-11-27 17:52:26 +00:00
xmr_swap = session . query ( XmrSwap ) . filter_by ( bid_id = bid . bid_id ) . first ( )
2021-10-21 22:47:04 +00:00
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid . bid_id . hex ( ) ) )
2020-11-27 17:52:26 +00:00
if TxTypes . XMR_SWAP_A_LOCK_REFUND in bid . txns :
refund_tx = bid . txns [ TxTypes . XMR_SWAP_A_LOCK_REFUND ]
if bid . was_received :
if bid . debug_ind == DebugTypes . BID_DONT_SPEND_COIN_A_LOCK_REFUND :
2021-02-03 14:01:27 +00:00
self . log . debug ( ' XMR bid %s : Stalling bid for testing: %d . ' , bid_id . hex ( ) , bid . debug_ind )
bid . setState ( BidStates . BID_STALLED_FOR_TEST )
2020-11-27 17:52:26 +00:00
rv = True
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . DEBUG_TWEAK_APPLIED , ' ind {} ' . format ( bid . debug_ind ) , session )
2020-11-27 17:52:26 +00:00
session . commit ( )
return rv
2020-11-29 23:05:30 +00:00
if TxTypes . XMR_SWAP_A_LOCK_REFUND_SPEND not in bid . txns :
try :
2022-06-28 23:45:06 +00:00
txid_str = ci_from . publishTx ( xmr_swap . a_lock_refund_spend_tx )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED , ' ' , session )
2020-11-29 23:05:30 +00:00
2022-06-28 23:45:06 +00:00
self . log . info ( ' Submitted coin a lock refund spend tx for bid {} , txid {} ' . format ( bid_id . hex ( ) , txid_str ) )
2020-11-29 23:05:30 +00:00
bid . txns [ TxTypes . XMR_SWAP_A_LOCK_REFUND_SPEND ] = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . XMR_SWAP_A_LOCK_REFUND_SPEND ,
2022-06-28 23:45:06 +00:00
txid = bytes . fromhex ( txid_str ) ,
2020-11-29 23:05:30 +00:00
)
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
except Exception as ex :
2021-01-29 23:45:24 +00:00
self . log . debug ( ' Trying to publish coin a lock refund spend tx: %s ' , str ( ex ) )
2020-11-27 17:52:26 +00:00
if bid . was_sent :
if xmr_swap . a_lock_refund_swipe_tx is None :
self . createCoinALockRefundSwipeTx ( ci_from , bid , offer , xmr_swap , xmr_offer )
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
2020-11-29 23:05:30 +00:00
if TxTypes . XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid . txns :
try :
txid = ci_from . publishTx ( xmr_swap . a_lock_refund_swipe_tx )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED , ' ' , session )
2020-11-29 23:05:30 +00:00
self . log . info ( ' Submitted coin a lock refund swipe tx for bid {} ' . format ( bid_id . hex ( ) ) )
bid . txns [ TxTypes . XMR_SWAP_A_LOCK_REFUND_SWIPE ] = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . XMR_SWAP_A_LOCK_REFUND_SWIPE ,
txid = bytes . fromhex ( txid ) ,
)
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
except Exception as ex :
2021-01-29 23:45:24 +00:00
self . log . debug ( ' Trying to publish coin a lock refund swipe tx: %s ' , str ( ex ) )
2020-11-27 17:52:26 +00:00
if BidStates ( bid . state ) == BidStates . XMR_SWAP_NOSCRIPT_TX_RECOVERED :
txid_hex = bid . xmr_b_lock_tx . spend_txid . hex ( )
found_tx = ci_to . findTxnByHash ( txid_hex )
if found_tx is not None :
self . log . info ( ' Found coin b lock recover tx bid %s ' , bid_id . hex ( ) )
rv = True # Remove from swaps_in_progress
bid . setState ( BidStates . XMR_SWAP_FAILED_REFUNDED )
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
return rv
2020-12-06 17:34:56 +00:00
else : # not XMR_SWAP_A_LOCK_REFUND in bid.txns
if len ( xmr_swap . al_lock_refund_tx_sig ) > 0 and len ( xmr_swap . af_lock_refund_tx_sig ) > 0 :
try :
txid = ci_from . publishTx ( xmr_swap . a_lock_refund_tx )
self . log . info ( ' Submitted coin a lock refund tx for bid {} ' . format ( bid_id . hex ( ) ) )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_A_REFUND_TX_PUBLISHED , ' ' , session )
2020-11-27 17:52:26 +00:00
bid . txns [ TxTypes . XMR_SWAP_A_LOCK_REFUND ] = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . XMR_SWAP_A_LOCK_REFUND ,
2020-12-06 17:34:56 +00:00
txid = bytes . fromhex ( txid ) ,
2020-11-27 17:52:26 +00:00
)
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
return rv
2020-12-06 17:34:56 +00:00
except Exception as ex :
if ' Transaction already in block chain ' in str ( ex ) :
self . log . info ( ' Found coin a lock refund tx for bid {} ' . format ( bid_id . hex ( ) ) )
2021-11-01 13:52:40 +00:00
txid = ci_from . getTxid ( xmr_swap . a_lock_refund_tx )
2022-11-14 19:47:07 +00:00
if TxTypes . XMR_SWAP_A_LOCK_REFUND not in bid . txns :
bid . txns [ TxTypes . XMR_SWAP_A_LOCK_REFUND ] = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . XMR_SWAP_A_LOCK_REFUND ,
txid = txid ,
)
2020-12-06 17:34:56 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
return rv
2020-11-27 17:52:26 +00:00
state = BidStates ( bid . state )
2020-11-29 23:05:30 +00:00
if state == BidStates . SWAP_COMPLETED :
rv = True # Remove from swaps_in_progress
elif state == BidStates . XMR_SWAP_FAILED_REFUNDED :
rv = True # Remove from swaps_in_progress
elif state == BidStates . XMR_SWAP_FAILED_SWIPED :
rv = True # Remove from swaps_in_progress
2022-12-11 18:31:43 +00:00
elif state == BidStates . XMR_SWAP_FAILED :
if bid . was_sent and bid . xmr_b_lock_tx :
if self . countQueuedActions ( session , bid_id , ActionTypes . RECOVER_XMR_SWAP_LOCK_TX_B ) < 1 :
delay = random . randrange ( self . min_delay_event , self . max_delay_event )
self . log . info ( ' Recovering xmr swap chain B lock tx for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
self . createActionInSession ( delay , ActionTypes . RECOVER_XMR_SWAP_LOCK_TX_B , bid_id , session )
session . commit ( )
2022-07-03 21:58:16 +00:00
elif state == BidStates . XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX :
2020-11-27 17:52:26 +00:00
if bid . xmr_a_lock_tx is None :
return rv
2020-11-21 13:16:27 +00:00
# TODO: Timeout waiting for transactions
2020-12-10 14:37:26 +00:00
bid_changed = False
2022-11-07 20:31:10 +00:00
if offer . coin_from == Coins . FIRO :
lock_tx_chain_info = ci_from . getLockTxHeightFiro ( bid . xmr_a_lock_tx . txid , xmr_swap . a_lock_tx_script , bid . amount , bid . chain_a_height_start )
else :
a_lock_tx_addr = ci_from . getSCLockScriptAddress ( xmr_swap . a_lock_tx_script )
lock_tx_chain_info = ci_from . getLockTxHeight ( bid . xmr_a_lock_tx . txid , a_lock_tx_addr , bid . amount , bid . chain_a_height_start )
2020-11-21 13:16:27 +00:00
2021-11-01 13:52:40 +00:00
if lock_tx_chain_info is None :
return rv
2020-11-21 13:16:27 +00:00
2021-11-01 13:52:40 +00:00
if not bid . xmr_a_lock_tx . chain_height and lock_tx_chain_info [ ' height ' ] != 0 :
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_A_SEEN , ' ' , session )
2022-11-08 20:30:28 +00:00
self . setTxBlockInfoFromHeight ( ci_from , bid . xmr_a_lock_tx , lock_tx_chain_info [ ' height ' ] )
2021-01-31 12:26:32 +00:00
2020-12-10 14:37:26 +00:00
bid_changed = True
2021-11-01 13:52:40 +00:00
if bid . xmr_a_lock_tx . chain_height != lock_tx_chain_info [ ' height ' ] and lock_tx_chain_info [ ' height ' ] != 0 :
bid . xmr_a_lock_tx . chain_height = lock_tx_chain_info [ ' height ' ]
2020-12-10 14:37:26 +00:00
bid_changed = True
2021-11-01 13:52:40 +00:00
if lock_tx_chain_info [ ' depth ' ] > = ci_from . blocks_confirmed :
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_A_CONFIRMED , ' ' , session )
2020-11-21 13:16:27 +00:00
bid . xmr_a_lock_tx . setState ( TxStates . TX_CONFIRMED )
bid . setState ( BidStates . XMR_SWAP_SCRIPT_COIN_LOCKED )
2020-12-11 12:08:32 +00:00
bid_changed = True
2020-11-21 13:16:27 +00:00
if bid . was_sent :
2020-12-02 11:24:52 +00:00
delay = random . randrange ( self . min_delay_event , self . max_delay_event )
2020-11-21 13:16:27 +00:00
self . log . info ( ' Sending xmr swap chain B lock tx for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , ActionTypes . SEND_XMR_SWAP_LOCK_TX_B , bid_id , session )
2020-11-30 14:29:40 +00:00
# bid.setState(BidStates.SWAP_DELAYING)
2020-11-21 13:16:27 +00:00
2020-12-10 14:37:26 +00:00
if bid_changed :
2020-12-11 12:08:32 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
2020-11-21 13:16:27 +00:00
session . commit ( )
2020-11-27 17:52:26 +00:00
elif state == BidStates . XMR_SWAP_SCRIPT_COIN_LOCKED :
2022-12-11 18:31:43 +00:00
bid_changed = self . findTxB ( ci_to , xmr_swap , bid , session )
2020-11-21 13:16:27 +00:00
2020-12-09 19:30:21 +00:00
if bid . xmr_b_lock_tx and bid . xmr_b_lock_tx . chain_height is not None and bid . xmr_b_lock_tx . chain_height > 0 :
chain_height = ci_to . getChainHeight ( )
2020-11-27 17:52:26 +00:00
2020-12-09 19:30:21 +00:00
if chain_height - bid . xmr_b_lock_tx . chain_height > = ci_to . blocks_confirmed :
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_B_CONFIRMED , ' ' , session )
2020-12-09 19:30:21 +00:00
bid . xmr_b_lock_tx . setState ( TxStates . TX_CONFIRMED )
bid . setState ( BidStates . XMR_SWAP_NOSCRIPT_COIN_LOCKED )
2020-11-21 13:16:27 +00:00
2020-12-09 19:30:21 +00:00
if bid . was_received :
delay = random . randrange ( self . min_delay_event , self . max_delay_event )
2020-12-10 22:43:36 +00:00
self . log . info ( ' Releasing xmr script coin lock tx for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , ActionTypes . SEND_XMR_LOCK_RELEASE , bid_id , session )
2020-11-21 13:16:27 +00:00
2020-12-09 19:30:21 +00:00
if bid_changed :
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
2020-11-21 13:16:27 +00:00
session . commit ( )
2020-12-10 22:43:36 +00:00
elif state == BidStates . XMR_SWAP_LOCK_RELEASED :
2020-11-27 17:52:26 +00:00
# Wait for script spend tx to confirm
2020-11-21 13:16:27 +00:00
# TODO: Use explorer to get tx / block hash for getrawtransaction
2020-12-06 17:34:56 +00:00
if bid . was_received :
try :
txn_hex = ci_from . getMempoolTx ( xmr_swap . a_lock_spend_tx_id )
self . log . info ( ' Found lock spend txn in %s mempool, %s ' , ci_from . coin_name ( ) , xmr_swap . a_lock_spend_tx_id . hex ( ) )
self . process_XMR_SWAP_A_LOCK_tx_spend ( bid_id , xmr_swap . a_lock_spend_tx_id . hex ( ) , txn_hex )
except Exception as e :
self . log . debug ( ' getrawtransaction lock spend tx failed: %s ' , str ( e ) )
2021-11-12 14:36:10 +00:00
elif state == BidStates . XMR_SWAP_SCRIPT_TX_REDEEMED :
2022-06-06 21:03:31 +00:00
if bid . was_received and self . countQueuedActions ( session , bid_id , ActionTypes . REDEEM_XMR_SWAP_LOCK_TX_B ) < 1 :
2021-11-12 14:36:10 +00:00
bid . setState ( BidStates . SWAP_DELAYING )
delay = random . randrange ( self . min_delay_event , self . max_delay_event )
self . log . info ( ' Redeeming coin b lock tx for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , ActionTypes . REDEEM_XMR_SWAP_LOCK_TX_B , bid_id , session )
2021-11-12 14:36:10 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
2020-12-01 20:45:03 +00:00
elif state == BidStates . XMR_SWAP_NOSCRIPT_TX_REDEEMED :
2020-11-27 17:52:26 +00:00
txid_hex = bid . xmr_b_lock_tx . spend_txid . hex ( )
found_tx = ci_to . findTxnByHash ( txid_hex )
if found_tx is not None :
self . log . info ( ' Found coin b lock spend tx bid %s ' , bid_id . hex ( ) )
rv = True # Remove from swaps_in_progress
bid . setState ( BidStates . SWAP_COMPLETED )
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
2021-12-19 12:15:32 +00:00
elif state == BidStates . XMR_SWAP_SCRIPT_TX_PREREFUND :
if TxTypes . XMR_SWAP_A_LOCK_REFUND in bid . txns :
refund_tx = bid . txns [ TxTypes . XMR_SWAP_A_LOCK_REFUND ]
if refund_tx . block_time is None :
2022-11-07 20:31:10 +00:00
if offer . coin_from == Coins . FIRO :
lock_refund_tx_chain_info = ci_from . getLockTxHeightFiro ( refund_tx . txid , xmr_swap . a_lock_refund_tx_script , 0 , bid . chain_a_height_start )
else :
refund_tx_addr = ci_from . getSCLockScriptAddress ( xmr_swap . a_lock_refund_tx_script )
lock_refund_tx_chain_info = ci_from . getLockTxHeight ( refund_tx . txid , refund_tx_addr , 0 , bid . chain_a_height_start )
2021-12-19 12:15:32 +00:00
2022-10-18 17:59:46 +00:00
if lock_refund_tx_chain_info is not None and lock_refund_tx_chain_info . get ( ' height ' , 0 ) > 0 :
2022-11-08 20:30:28 +00:00
self . setTxBlockInfoFromHeight ( ci_from , refund_tx , lock_refund_tx_chain_info [ ' height ' ] )
2021-12-19 12:15:32 +00:00
2021-12-31 19:34:00 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
session . commit ( )
2020-11-27 17:52:26 +00:00
except Exception as ex :
raise ex
finally :
if session :
2020-11-21 13:16:27 +00:00
session . close ( )
session . remove ( )
2020-11-27 17:52:26 +00:00
self . mxDB . release ( )
2020-12-06 17:34:56 +00:00
2020-11-21 13:16:27 +00:00
return rv
2019-07-17 15:12:06 +00:00
def checkBidState ( self , bid_id , bid , offer ) :
2022-07-31 18:01:49 +00:00
# assert (self.mxDB.locked())
2019-07-17 15:12:06 +00:00
# Return True to remove bid from in-progress list
state = BidStates ( bid . state )
self . log . debug ( ' checkBidState %s %s ' , bid_id . hex ( ) , str ( state ) )
2020-11-14 22:13:11 +00:00
if offer . swap_type == SwapTypes . XMR_SWAP :
return self . checkXmrBidState ( bid_id , bid , offer )
2019-07-19 14:52:44 +00:00
save_bid = False
2019-07-17 15:12:06 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
2021-11-05 08:55:18 +00:00
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
2019-07-17 15:12:06 +00:00
# TODO: Batch calls to scantxoutset
# TODO: timeouts
2021-01-30 14:29:07 +00:00
if state == BidStates . BID_ABANDONED :
self . log . info ( ' Deactivating abandoned bid: %s ' , bid_id . hex ( ) )
return True # Mark bid for archiving
2019-07-17 15:12:06 +00:00
if state == BidStates . BID_ACCEPTED :
# Waiting for initiate txn to be confirmed in 'from' chain
2019-07-27 17:26:06 +00:00
initiate_txnid_hex = bid . initiate_tx . txid . hex ( )
2021-11-05 08:55:18 +00:00
p2sh = ci_from . encode_p2sh ( bid . initiate_tx . script )
2019-07-17 15:12:06 +00:00
index = None
2019-07-20 17:29:48 +00:00
tx_height = None
2019-07-27 17:26:06 +00:00
last_initiate_txn_conf = bid . initiate_tx . conf
2022-11-08 20:30:28 +00:00
ci_from = self . ci ( coin_from )
2019-07-17 15:12:06 +00:00
if coin_from == Coins . PART : # Has txindex
try :
initiate_txn = self . callcoinrpc ( coin_from , ' getrawtransaction ' , [ initiate_txnid_hex , True ] )
# Verify amount
vout = getVoutByAddress ( initiate_txn , p2sh )
2019-07-30 21:06:33 +00:00
2020-10-31 20:08:30 +00:00
out_value = make_int ( initiate_txn [ ' vout ' ] [ vout ] [ ' value ' ] )
2021-10-21 22:47:04 +00:00
ensure ( out_value == int ( bid . amount ) , ' Incorrect output amount in initiate txn {} : {} != {} . ' . format ( initiate_txnid_hex , out_value , int ( bid . amount ) ) )
2019-07-17 15:12:06 +00:00
2019-07-27 17:26:06 +00:00
bid . initiate_tx . conf = initiate_txn [ ' confirmations ' ]
2019-07-20 17:29:48 +00:00
try :
tx_height = initiate_txn [ ' height ' ]
except Exception :
tx_height = - 1
2019-07-17 15:12:06 +00:00
index = vout
except Exception :
pass
else :
if self . coin_clients [ coin_from ] [ ' use_segwit ' ] :
2021-11-05 08:55:18 +00:00
addr = ci_from . encode_p2wsh ( getP2WSH ( bid . initiate_tx . script ) )
2019-07-17 15:12:06 +00:00
else :
addr = p2sh
2021-11-04 21:49:52 +00:00
found = ci_from . getLockTxHeight ( bytes . fromhex ( initiate_txnid_hex ) , addr , bid . amount , bid . chain_a_height_start , find_index = True )
2019-07-17 15:12:06 +00:00
if found :
2021-11-04 21:49:52 +00:00
bid . initiate_tx . conf = found [ ' depth ' ]
2019-07-17 15:12:06 +00:00
index = found [ ' index ' ]
2019-07-20 17:29:48 +00:00
tx_height = found [ ' height ' ]
2019-07-17 15:12:06 +00:00
2019-07-27 17:26:06 +00:00
if bid . initiate_tx . conf != last_initiate_txn_conf :
2019-07-19 14:52:44 +00:00
save_bid = True
2019-07-27 17:26:06 +00:00
if bid . initiate_tx . conf is not None :
self . log . debug ( ' initiate_txnid %s confirms %d ' , initiate_txnid_hex , bid . initiate_tx . conf )
2019-07-17 15:12:06 +00:00
2022-10-26 15:47:30 +00:00
if bid . initiate_tx . vout is None and tx_height > 0 :
2019-07-27 17:26:06 +00:00
bid . initiate_tx . vout = index
2019-07-17 15:12:06 +00:00
# Start checking for spends of initiate_txn before fully confirmed
2019-07-27 18:51:50 +00:00
bid . initiate_tx . chain_height = self . setLastHeightChecked ( coin_from , tx_height )
2022-11-08 20:30:28 +00:00
self . setTxBlockInfoFromHeight ( ci_from , bid . initiate_tx , tx_height )
2019-07-27 17:26:06 +00:00
self . addWatchedOutput ( coin_from , bid_id , initiate_txnid_hex , bid . initiate_tx . vout , BidStates . SWAP_INITIATED )
2019-07-27 18:51:50 +00:00
if bid . getITxState ( ) is None or bid . getITxState ( ) < TxStates . TX_SENT :
bid . setITxState ( TxStates . TX_SENT )
2019-07-21 16:25:01 +00:00
save_bid = True
2019-07-17 15:12:06 +00:00
2019-07-27 17:26:06 +00:00
if bid . initiate_tx . conf > = self . coin_clients [ coin_from ] [ ' blocks_confirmed ' ] :
2019-07-17 15:12:06 +00:00
self . initiateTxnConfirmed ( bid_id , bid , offer )
2019-07-19 14:52:44 +00:00
save_bid = True
2019-07-17 15:12:06 +00:00
2019-07-24 22:59:40 +00:00
# Bid times out if buyer doesn't see tx in chain within INITIATE_TX_TIMEOUT seconds
2019-07-27 17:26:06 +00:00
if bid . initiate_tx is None and \
2023-02-26 18:14:00 +00:00
bid . state_time + atomic_swap_1 . INITIATE_TX_TIMEOUT < self . getTime ( ) :
2019-07-17 15:12:06 +00:00
self . log . info ( ' Swap timed out waiting for initiate tx for bid %s ' , bid_id . hex ( ) )
2021-01-09 13:00:25 +00:00
bid . setState ( BidStates . SWAP_TIMEDOUT , ' Timed out waiting for initiate tx ' )
2019-07-17 15:12:06 +00:00
self . saveBid ( bid_id , bid )
return True # Mark bid for archiving
elif state == BidStates . SWAP_INITIATED :
# Waiting for participate txn to be confirmed in 'to' chain
if self . coin_clients [ coin_to ] [ ' use_segwit ' ] :
2021-11-05 08:55:18 +00:00
addr = ci_to . encode_p2wsh ( getP2WSH ( bid . participate_tx . script ) )
2019-07-17 15:12:06 +00:00
else :
2021-11-05 08:55:18 +00:00
addr = ci_to . encode_p2sh ( bid . participate_tx . script )
2019-07-17 15:12:06 +00:00
2021-11-04 21:49:52 +00:00
ci_to = self . ci ( coin_to )
2021-12-15 13:41:43 +00:00
participate_txid = None if bid . participate_tx is None or bid . participate_tx . txid is None else bid . participate_tx . txid
found = ci_to . getLockTxHeight ( participate_txid , addr , bid . amount_to , bid . chain_b_height_start , find_index = True )
2019-07-17 15:12:06 +00:00
if found :
2021-11-04 21:49:52 +00:00
if bid . participate_tx . conf != found [ ' depth ' ] :
2019-07-19 14:52:44 +00:00
save_bid = True
2021-11-04 21:49:52 +00:00
bid . participate_tx . conf = found [ ' depth ' ]
2019-07-17 15:12:06 +00:00
index = found [ ' index ' ]
2019-07-27 19:50:50 +00:00
if bid . participate_tx is None or bid . participate_tx . txid is None :
2019-07-17 15:12:06 +00:00
self . log . debug ( ' Found bid %s participate txn %s in chain %s ' , bid_id . hex ( ) , found [ ' txid ' ] , coin_to )
2019-07-20 17:29:48 +00:00
self . addParticipateTxn ( bid_id , bid , coin_to , found [ ' txid ' ] , found [ ' index ' ] , found [ ' height ' ] )
2019-07-27 18:51:50 +00:00
bid . setPTxState ( TxStates . TX_SENT )
2019-07-19 14:52:44 +00:00
save_bid = True
2022-11-08 20:30:28 +00:00
if found [ ' height ' ] > 0 and bid . participate_tx . block_height is None :
self . setTxBlockInfoFromHeight ( ci_to , bid . participate_tx , found [ ' height ' ] )
2019-07-17 15:12:06 +00:00
2019-07-27 19:50:50 +00:00
if bid . participate_tx . conf is not None :
self . log . debug ( ' participate txid %s confirms %d ' , bid . participate_tx . txid . hex ( ) , bid . participate_tx . conf )
if bid . participate_tx . conf > = self . coin_clients [ coin_to ] [ ' blocks_confirmed ' ] :
2019-07-17 15:12:06 +00:00
self . participateTxnConfirmed ( bid_id , bid , offer )
2019-07-19 14:52:44 +00:00
save_bid = True
2019-07-17 15:12:06 +00:00
elif state == BidStates . SWAP_PARTICIPATING :
# Waiting for initiate txn spend
pass
2019-11-18 21:30:31 +00:00
elif state == BidStates . BID_ERROR :
# Wait for user input
pass
2019-07-17 15:12:06 +00:00
else :
self . log . warning ( ' checkBidState unknown state %s ' , state )
if state > BidStates . BID_ACCEPTED :
# Wait for spend of all known swap txns
2022-07-20 22:27:22 +00:00
itx_state = bid . getITxState ( )
ptx_state = bid . getPTxState ( )
if ( itx_state is None or itx_state > = TxStates . TX_REDEEMED ) and \
( ptx_state is None or ptx_state > = TxStates . TX_REDEEMED ) :
2019-07-17 15:12:06 +00:00
self . log . info ( ' Swap completed for bid %s ' , bid_id . hex ( ) )
2019-07-23 17:19:31 +00:00
2022-07-20 22:27:22 +00:00
self . returnAddressToPool ( bid_id , TxTypes . ITX_REFUND if itx_state == TxStates . TX_REDEEMED else TxTypes . PTX_REDEEM )
self . returnAddressToPool ( bid_id , TxTypes . ITX_REFUND if ptx_state == TxStates . TX_REDEEMED else TxTypes . PTX_REDEEM )
2019-07-23 17:19:31 +00:00
2019-07-17 15:12:06 +00:00
bid . setState ( BidStates . SWAP_COMPLETED )
self . saveBid ( bid_id , bid )
return True # Mark bid for archiving
2019-07-19 14:52:44 +00:00
if save_bid :
self . saveBid ( bid_id , bid )
2022-07-20 22:27:22 +00:00
if bid . debug_ind == DebugTypes . SKIP_LOCK_TX_REFUND :
return False # Bid is still active
2019-07-17 15:12:06 +00:00
# Try refund, keep trying until sent tx is spent
2022-07-20 22:27:22 +00:00
if bid . getITxState ( ) in ( TxStates . TX_SENT , TxStates . TX_CONFIRMED ) \
2019-07-17 15:12:06 +00:00
and bid . initiate_txn_refund is not None :
try :
2022-07-04 20:29:49 +00:00
txid = ci_from . publishTx ( bid . initiate_txn_refund )
2019-07-17 15:12:06 +00:00
self . log . debug ( ' Submitted initiate refund txn %s to %s chain for bid %s ' , txid , chainparams [ coin_from ] [ ' name ' ] , bid_id . hex ( ) )
2022-11-08 21:07:58 +00:00
self . logEvent ( Concepts . BID , bid . bid_id , EventLogTypes . ITX_REFUND_PUBLISHED , ' ' , None )
2019-07-17 15:12:06 +00:00
# State will update when spend is detected
2019-07-27 17:26:06 +00:00
except Exception as ex :
2021-12-15 13:41:43 +00:00
if ' non-BIP68-final ' not in str ( ex ) and ' non-final ' not in str ( ex ) :
2019-07-27 17:26:06 +00:00
self . log . warning ( ' Error trying to submit initiate refund txn: %s ' , str ( ex ) )
2021-12-15 13:41:43 +00:00
2022-07-20 22:27:22 +00:00
if bid . getPTxState ( ) in ( TxStates . TX_SENT , TxStates . TX_CONFIRMED ) \
2019-07-17 15:12:06 +00:00
and bid . participate_txn_refund is not None :
try :
2022-07-04 20:29:49 +00:00
txid = ci_to . publishTx ( bid . participate_txn_refund )
2019-07-17 15:12:06 +00:00
self . log . debug ( ' Submitted participate refund txn %s to %s chain for bid %s ' , txid , chainparams [ coin_to ] [ ' name ' ] , bid_id . hex ( ) )
2022-11-08 21:07:58 +00:00
self . logEvent ( Concepts . BID , bid . bid_id , EventLogTypes . PTX_REFUND_PUBLISHED , ' ' , None )
2019-07-17 15:12:06 +00:00
# State will update when spend is detected
2019-07-27 17:26:06 +00:00
except Exception as ex :
2021-12-15 13:41:43 +00:00
if ' non-BIP68-final ' not in str ( ex ) and ' non-final ' not in str ( ex ) :
2019-07-27 17:26:06 +00:00
self . log . warning ( ' Error trying to submit participate refund txn: %s ' , str ( ex ) )
2019-07-17 15:12:06 +00:00
return False # Bid is still active
def extractSecret ( self , coin_type , bid , spend_in ) :
try :
if coin_type == Coins . PART or self . coin_clients [ coin_type ] [ ' use_segwit ' ] :
2021-10-21 22:47:04 +00:00
ensure ( len ( spend_in [ ' txinwitness ' ] ) == 5 , ' Bad witness size ' )
2019-07-17 15:12:06 +00:00
return bytes . fromhex ( spend_in [ ' txinwitness ' ] [ 2 ] )
else :
script_sig = spend_in [ ' scriptSig ' ] [ ' asm ' ] . split ( ' ' )
2021-10-21 22:47:04 +00:00
ensure ( len ( script_sig ) == 5 , ' Bad witness size ' )
2019-07-17 15:12:06 +00:00
return bytes . fromhex ( script_sig [ 2 ] )
except Exception :
return None
2020-11-29 11:46:00 +00:00
def addWatchedOutput ( self , coin_type , bid_id , txid_hex , vout , tx_type , swap_type = None ) :
2019-07-23 15:08:12 +00:00
self . log . debug ( ' Adding watched output %s bid %s tx %s type %s ' , coin_type , bid_id . hex ( ) , txid_hex , tx_type )
2020-12-05 11:22:22 +00:00
watched = self . coin_clients [ coin_type ] [ ' watched_outputs ' ]
for wo in watched :
if wo . bid_id == bid_id and wo . txid_hex == txid_hex and wo . vout == vout :
self . log . debug ( ' Output already being watched. ' )
return
watched . append ( WatchedOutput ( bid_id , txid_hex , vout , tx_type , swap_type ) )
2019-07-23 15:08:12 +00:00
2019-07-17 15:12:06 +00:00
def removeWatchedOutput ( self , coin_type , bid_id , txid_hex ) :
# Remove all for bid if txid is None
self . log . debug ( ' removeWatchedOutput %s %s %s ' , str ( coin_type ) , bid_id . hex ( ) , txid_hex )
old_len = len ( self . coin_clients [ coin_type ] [ ' watched_outputs ' ] )
for i in range ( old_len - 1 , - 1 , - 1 ) :
wo = self . coin_clients [ coin_type ] [ ' watched_outputs ' ] [ i ]
2020-11-29 11:46:00 +00:00
if wo . bid_id == bid_id and ( txid_hex is None or wo . txid_hex == txid_hex ) :
2019-07-17 15:12:06 +00:00
del self . coin_clients [ coin_type ] [ ' watched_outputs ' ] [ i ]
2020-11-29 11:46:00 +00:00
self . log . debug ( ' Removed watched output %s %s %s ' , str ( coin_type ) , bid_id . hex ( ) , wo . txid_hex )
2019-07-17 15:12:06 +00:00
def initiateTxnSpent ( self , bid_id , spend_txid , spend_n , spend_txn ) :
self . log . debug ( ' Bid %s initiate txn spent by %s %d ' , bid_id . hex ( ) , spend_txid , spend_n )
if bid_id in self . swaps_in_progress :
bid = self . swaps_in_progress [ bid_id ] [ 0 ]
offer = self . swaps_in_progress [ bid_id ] [ 1 ]
2019-07-27 18:51:50 +00:00
bid . initiate_tx . spend_txid = bytes . fromhex ( spend_txid )
bid . initiate_tx . spend_n = spend_n
2019-07-17 15:12:06 +00:00
spend_in = spend_txn [ ' vin ' ] [ spend_n ]
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
secret = self . extractSecret ( coin_from , bid , spend_in )
if secret is None :
self . log . info ( ' Bid %s initiate txn refunded by %s %d ' , bid_id . hex ( ) , spend_txid , spend_n )
# TODO: Wait for depth?
2019-07-27 18:51:50 +00:00
bid . setITxState ( TxStates . TX_REFUNDED )
2019-07-17 15:12:06 +00:00
else :
self . log . info ( ' Bid %s initiate txn redeemed by %s %d ' , bid_id . hex ( ) , spend_txid , spend_n )
# TODO: Wait for depth?
2019-07-27 18:51:50 +00:00
bid . setITxState ( TxStates . TX_REDEEMED )
2019-07-17 15:12:06 +00:00
2019-07-27 17:26:06 +00:00
self . removeWatchedOutput ( coin_from , bid_id , bid . initiate_tx . txid . hex ( ) )
2019-07-17 15:12:06 +00:00
self . saveBid ( bid_id , bid )
def participateTxnSpent ( self , bid_id , spend_txid , spend_n , spend_txn ) :
self . log . debug ( ' Bid %s participate txn spent by %s %d ' , bid_id . hex ( ) , spend_txid , spend_n )
# TODO: More SwapTypes
if bid_id in self . swaps_in_progress :
bid = self . swaps_in_progress [ bid_id ] [ 0 ]
offer = self . swaps_in_progress [ bid_id ] [ 1 ]
2019-07-27 19:50:50 +00:00
bid . participate_tx . spend_txid = bytes . fromhex ( spend_txid )
bid . participate_tx . spend_n = spend_n
2019-07-17 15:12:06 +00:00
spend_in = spend_txn [ ' vin ' ] [ spend_n ]
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
secret = self . extractSecret ( coin_to , bid , spend_in )
if secret is None :
self . log . info ( ' Bid %s participate txn refunded by %s %d ' , bid_id . hex ( ) , spend_txid , spend_n )
# TODO: Wait for depth?
2019-07-27 18:51:50 +00:00
bid . setPTxState ( TxStates . TX_REFUNDED )
2019-07-17 15:12:06 +00:00
else :
self . log . debug ( ' Secret %s extracted from participate spend %s %d ' , secret . hex ( ) , spend_txid , spend_n )
bid . recovered_secret = secret
# TODO: Wait for depth?
2019-07-27 18:51:50 +00:00
bid . setPTxState ( TxStates . TX_REDEEMED )
2019-07-17 15:12:06 +00:00
2021-01-30 14:29:07 +00:00
if bid . was_sent :
2021-11-27 15:58:58 +00:00
if bid . debug_ind == DebugTypes . DONT_SPEND_ITX :
self . log . debug ( ' bid %s : Abandoning bid for testing: %d , %s . ' , bid_id . hex ( ) , bid . debug_ind , DebugTypes ( bid . debug_ind ) . name )
bid . setState ( BidStates . BID_ABANDONED )
self . logBidEvent ( bid . bid_id , EventLogTypes . DEBUG_TWEAK_APPLIED , ' ind {} ' . format ( bid . debug_ind ) , None )
else :
2022-07-03 21:58:16 +00:00
delay = random . randrange ( self . min_delay_event_short , self . max_delay_event_short )
self . log . info ( ' Redeeming ITX for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
self . createAction ( delay , ActionTypes . REDEEM_ITX , bid_id )
2019-07-17 15:12:06 +00:00
# TODO: Wait for depth? new state SWAP_TXI_REDEEM_SENT?
2019-07-27 19:50:50 +00:00
self . removeWatchedOutput ( coin_to , bid_id , bid . participate_tx . txid . hex ( ) )
2019-07-17 15:12:06 +00:00
self . saveBid ( bid_id , bid )
2020-12-06 17:34:56 +00:00
def process_XMR_SWAP_A_LOCK_tx_spend ( self , bid_id , spend_txid_hex , spend_txn_hex ) :
2020-11-29 23:05:30 +00:00
self . log . debug ( ' Detected spend of XMR swap coin a lock tx for bid %s ' , bid_id . hex ( ) )
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2020-12-04 21:30:20 +00:00
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-29 23:05:30 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-29 23:05:30 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
state = BidStates ( bid . state )
spending_txid = bytes . fromhex ( spend_txid_hex )
if spending_txid == xmr_swap . a_lock_spend_tx_id :
2020-12-10 22:43:36 +00:00
if state == BidStates . XMR_SWAP_LOCK_RELEASED :
2020-12-06 17:34:56 +00:00
xmr_swap . a_lock_spend_tx = bytes . fromhex ( spend_txn_hex )
2020-11-29 23:05:30 +00:00
bid . setState ( BidStates . XMR_SWAP_SCRIPT_TX_REDEEMED ) # TODO: Wait for confirmation?
if not bid . was_received :
bid . setState ( BidStates . SWAP_COMPLETED )
else :
# Could already be processed if spend was detected in the mempool
self . log . warning ( ' Coin a lock tx spend ignored due to bid state for bid {} ' . format ( bid_id . hex ( ) ) )
elif spending_txid == xmr_swap . a_lock_refund_tx_id :
2020-11-30 14:29:40 +00:00
self . log . debug ( ' Coin a lock tx spent by lock refund tx. ' )
2021-11-12 14:36:10 +00:00
bid . setState ( BidStates . XMR_SWAP_SCRIPT_TX_PREREFUND )
2021-12-16 12:26:21 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_A_REFUND_TX_SEEN , ' ' , session )
2020-11-29 23:05:30 +00:00
else :
self . setBidError ( bid . bid_id , bid , ' Unexpected txn spent coin a lock tx: {} ' . format ( spend_txid_hex ) , save_bid = False )
2020-12-02 11:24:52 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2020-11-29 23:05:30 +00:00
session . commit ( )
except Exception as ex :
2022-10-26 15:47:30 +00:00
self . logException ( f ' process_XMR_SWAP_A_LOCK_tx_spend { ex } ' )
2020-11-29 23:05:30 +00:00
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
def process_XMR_SWAP_A_LOCK_REFUND_tx_spend ( self , bid_id , spend_txid_hex , spend_txn ) :
self . log . debug ( ' Detected spend of XMR swap coin a lock refund tx for bid %s ' , bid_id . hex ( ) )
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2020-12-04 21:30:20 +00:00
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-29 23:05:30 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-29 23:05:30 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
state = BidStates ( bid . state )
spending_txid = bytes . fromhex ( spend_txid_hex )
if spending_txid == xmr_swap . a_lock_refund_spend_tx_id :
self . log . info ( ' Found coin a lock refund spend tx, bid {} ' . format ( bid_id . hex ( ) ) )
2021-12-16 12:26:21 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_A_REFUND_SPEND_TX_SEEN , ' ' , session )
2020-11-29 23:05:30 +00:00
if bid . was_sent :
xmr_swap . a_lock_refund_spend_tx = bytes . fromhex ( spend_txn [ ' hex ' ] ) # Replace with fully signed tx
if TxTypes . XMR_SWAP_A_LOCK_REFUND_SPEND not in bid . txns :
bid . txns [ TxTypes . XMR_SWAP_A_LOCK_REFUND_SPEND ] = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . XMR_SWAP_A_LOCK_REFUND_SPEND ,
txid = xmr_swap . a_lock_refund_spend_tx_id ,
)
if bid . xmr_b_lock_tx is not None :
2020-12-02 11:24:52 +00:00
delay = random . randrange ( self . min_delay_event , self . max_delay_event )
2020-11-29 23:05:30 +00:00
self . log . info ( ' Recovering xmr swap chain B lock tx for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , ActionTypes . RECOVER_XMR_SWAP_LOCK_TX_B , bid_id , session )
2020-11-29 23:05:30 +00:00
else :
2022-07-19 22:24:14 +00:00
# Other side refunded before swap lock tx was sent
bid . setState ( BidStates . XMR_SWAP_FAILED )
2020-11-29 23:05:30 +00:00
if bid . was_received :
if not bid . was_sent :
bid . setState ( BidStates . XMR_SWAP_FAILED_REFUNDED )
else :
self . log . info ( ' Coin a lock refund spent by unknown tx, bid {} ' . format ( bid_id . hex ( ) ) )
2020-12-01 20:45:03 +00:00
bid . setState ( BidStates . XMR_SWAP_FAILED_SWIPED )
2020-11-29 23:05:30 +00:00
2020-12-02 11:24:52 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2020-11-29 23:05:30 +00:00
session . commit ( )
except Exception as ex :
2022-10-26 15:47:30 +00:00
self . logException ( f ' process_XMR_SWAP_A_LOCK_REFUND_tx_spend { ex } ' )
2020-11-29 23:05:30 +00:00
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2020-11-29 11:46:00 +00:00
def processSpentOutput ( self , coin_type , watched_output , spend_txid_hex , spend_n , spend_txn ) :
if watched_output . swap_type == SwapTypes . XMR_SWAP :
2020-11-29 23:05:30 +00:00
if watched_output . tx_type == TxTypes . XMR_SWAP_A_LOCK :
2020-12-06 17:34:56 +00:00
self . process_XMR_SWAP_A_LOCK_tx_spend ( watched_output . bid_id , spend_txid_hex , spend_txn [ ' hex ' ] )
2020-11-29 23:05:30 +00:00
elif watched_output . tx_type == TxTypes . XMR_SWAP_A_LOCK_REFUND :
self . process_XMR_SWAP_A_LOCK_REFUND_tx_spend ( watched_output . bid_id , spend_txid_hex , spend_txn )
2020-11-29 11:46:00 +00:00
self . removeWatchedOutput ( coin_type , watched_output . bid_id , watched_output . txid_hex )
return
if watched_output . tx_type == BidStates . SWAP_PARTICIPATING :
self . participateTxnSpent ( watched_output . bid_id , spend_txid_hex , spend_n , spend_txn )
else :
self . initiateTxnSpent ( watched_output . bid_id , spend_txid_hex , spend_n , spend_txn )
2019-07-17 15:12:06 +00:00
def checkForSpends ( self , coin_type , c ) :
2022-07-31 18:01:49 +00:00
# assert (self.mxDB.locked())
2020-11-29 11:46:00 +00:00
self . log . debug ( ' checkForSpends %s ' , coin_type )
2019-07-17 15:12:06 +00:00
2021-11-03 21:20:19 +00:00
# TODO: Check for spends on watchonly txns where possible
2021-11-01 13:52:40 +00:00
if ' have_spent_index ' in self . coin_clients [ coin_type ] and self . coin_clients [ coin_type ] [ ' have_spent_index ' ] :
2019-07-17 15:12:06 +00:00
# TODO: batch getspentinfo
for o in c [ ' watched_outputs ' ] :
found_spend = None
try :
2020-11-29 11:46:00 +00:00
found_spend = self . callcoinrpc ( Coins . PART , ' getspentinfo ' , [ { ' txid ' : o . txid_hex , ' index ' : o . vout } ] )
2019-07-27 17:26:06 +00:00
except Exception as ex :
if ' Unable to get spent info ' not in str ( ex ) :
self . log . warning ( ' getspentinfo %s ' , str ( ex ) )
2019-07-17 15:12:06 +00:00
if found_spend is not None :
2020-11-29 11:46:00 +00:00
self . log . debug ( ' Found spend in spentindex %s %d in %s %d ' , o . txid_hex , o . vout , found_spend [ ' txid ' ] , found_spend [ ' index ' ] )
2019-07-17 15:12:06 +00:00
spend_txid = found_spend [ ' txid ' ]
spend_n = found_spend [ ' index ' ]
spend_txn = self . callcoinrpc ( Coins . PART , ' getrawtransaction ' , [ spend_txid , True ] )
2020-11-29 11:46:00 +00:00
self . processSpentOutput ( coin_type , o , spend_txid , spend_n , spend_txn )
2019-07-17 15:12:06 +00:00
else :
2022-08-10 22:02:36 +00:00
ci = self . ci ( coin_type )
chain_blocks = ci . getChainHeight ( )
2019-07-17 15:12:06 +00:00
last_height_checked = c [ ' last_height_checked ' ]
self . log . debug ( ' chain_blocks, last_height_checked %s %s ' , chain_blocks , last_height_checked )
while last_height_checked < chain_blocks :
block_hash = self . callcoinrpc ( coin_type , ' getblockhash ' , [ last_height_checked + 1 ] )
2020-12-06 17:34:56 +00:00
try :
2022-08-10 22:02:36 +00:00
block = ci . getBlockWithTxns ( block_hash )
2020-12-06 17:34:56 +00:00
except Exception as e :
if ' Block not available (pruned data) ' in str ( e ) :
# TODO: Better solution?
bci = self . callcoinrpc ( coin_type , ' getblockchaininfo ' )
self . log . error ( ' Coin %s last_height_checked %d set to pruneheight %d ' , self . ci ( coin_type ) . coin_name ( ) , last_height_checked , bci [ ' pruneheight ' ] )
last_height_checked = bci [ ' pruneheight ' ]
continue
2022-08-10 22:02:36 +00:00
else :
2022-10-26 15:47:30 +00:00
self . logException ( f ' getblock error { e } ' )
2022-08-10 22:02:36 +00:00
break
2019-07-17 15:12:06 +00:00
for tx in block [ ' tx ' ] :
for i , inp in enumerate ( tx [ ' vin ' ] ) :
for o in c [ ' watched_outputs ' ] :
inp_txid = inp . get ( ' txid ' , None )
if inp_txid is None : # Coinbase
continue
2020-11-29 11:46:00 +00:00
if inp_txid == o . txid_hex and inp [ ' vout ' ] == o . vout :
self . log . debug ( ' Found spend from search %s %d in %s %d ' , o . txid_hex , o . vout , tx [ ' txid ' ] , i )
self . processSpentOutput ( coin_type , o , tx [ ' txid ' ] , i , tx )
2019-07-17 15:12:06 +00:00
last_height_checked + = 1
if c [ ' last_height_checked ' ] != last_height_checked :
c [ ' last_height_checked ' ] = last_height_checked
2019-07-22 21:39:00 +00:00
self . setIntKV ( ' last_height_checked_ ' + chainparams [ coin_type ] [ ' name ' ] , last_height_checked )
2019-07-17 15:12:06 +00:00
2023-02-16 20:57:55 +00:00
def expireMessages ( self ) - > None :
if self . _is_locked is True :
self . log . debug ( ' Not expiring messages while system locked ' )
return
2019-07-17 15:12:06 +00:00
self . mxDB . acquire ( )
2022-09-11 15:16:51 +00:00
rpc_conn = None
2019-07-17 15:12:06 +00:00
try :
2022-09-11 15:16:51 +00:00
ci_part = self . ci ( Coins . PART )
rpc_conn = ci_part . open_rpc ( )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2019-07-17 15:12:06 +00:00
options = { ' encoding ' : ' none ' }
2022-09-11 15:16:51 +00:00
ro = ci_part . json_request ( rpc_conn , ' smsginbox ' , [ ' all ' , ' ' , options ] )
2021-01-11 21:48:46 +00:00
num_messages = 0
num_removed = 0
2019-07-17 15:12:06 +00:00
for msg in ro [ ' messages ' ] :
2022-10-12 18:48:26 +00:00
try :
num_messages + = 1
expire_at = msg [ ' sent ' ] + msg [ ' ttl ' ]
if expire_at < now :
options = { ' encoding ' : ' none ' , ' delete ' : True }
del_msg = ci_part . json_request ( rpc_conn , ' smsg ' , [ msg [ ' msgid ' ] , options ] )
num_removed + = 1
except Exception as e :
if self . debug :
self . log . error ( traceback . format_exc ( ) )
continue
2021-01-11 21:48:46 +00:00
if num_messages + num_removed > 0 :
2021-01-29 23:45:24 +00:00
self . log . info ( ' Expired {} / {} messages. ' . format ( num_removed , num_messages ) )
2019-07-17 15:12:06 +00:00
2021-01-29 23:45:24 +00:00
self . log . debug ( ' TODO: Expire records from db ' )
2019-07-17 15:12:06 +00:00
2019-11-09 21:09:22 +00:00
finally :
2022-09-11 15:16:51 +00:00
if rpc_conn :
ci_part . close_rpc ( rpc_conn )
2019-11-09 21:09:22 +00:00
self . mxDB . release ( )
2023-03-08 22:53:54 +00:00
def checkAcceptedBids ( self ) - > None :
# Check for bids stuck as accepted (not yet in-progress)
if self . _is_locked is True :
self . log . debug ( ' Not checking accepted bids while system locked ' )
return
now : int = self . getTime ( )
session = self . openSession ( )
grace_period : int = 60 * 60
try :
query_str = ' SELECT bid_id FROM bids ' + \
' WHERE active_ind = 1 AND state = :accepted_state AND expire_at + :grace_period <= :now '
q = session . execute ( query_str , { ' accepted_state ' : int ( BidStates . BID_ACCEPTED ) , ' now ' : now , ' grace_period ' : grace_period } )
for row in q :
bid_id = row [ 0 ]
self . log . info ( ' Timing out bid {} . ' . format ( bid_id . hex ( ) ) )
self . timeoutBid ( bid_id , session )
finally :
self . closeSession ( session )
def countQueuedActions ( self , session , bid_id , action_type ) - > int :
2022-06-08 20:21:46 +00:00
q = session . query ( Action ) . filter ( sa . and_ ( Action . active_ind == 1 , Action . linked_id == bid_id , Action . action_type == int ( action_type ) ) )
return q . count ( )
2022-06-06 21:03:31 +00:00
def checkQueuedActions ( self ) :
2019-11-09 21:09:22 +00:00
self . mxDB . acquire ( )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-27 17:52:26 +00:00
session = None
2022-12-01 18:51:06 +00:00
reload_in_progress = False
2019-11-09 21:09:22 +00:00
try :
session = scoped_session ( self . session_factory )
2022-06-06 21:03:31 +00:00
q = session . query ( Action ) . filter ( sa . and_ ( Action . active_ind == 1 , Action . trigger_at < = now ) )
2019-11-09 21:09:22 +00:00
for row in q :
2020-11-15 21:31:59 +00:00
try :
2022-06-06 21:03:31 +00:00
if row . action_type == ActionTypes . ACCEPT_BID :
2020-11-15 21:31:59 +00:00
self . acceptBid ( row . linked_id )
2022-06-06 21:03:31 +00:00
elif row . action_type == ActionTypes . ACCEPT_XMR_BID :
2020-12-04 21:30:20 +00:00
self . acceptXmrBid ( row . linked_id )
2022-06-06 21:03:31 +00:00
elif row . action_type == ActionTypes . SIGN_XMR_SWAP_LOCK_TX_A :
2020-11-15 21:31:59 +00:00
self . sendXmrBidTxnSigsFtoL ( row . linked_id , session )
2022-06-06 21:03:31 +00:00
elif row . action_type == ActionTypes . SEND_XMR_SWAP_LOCK_TX_A :
2020-11-15 21:31:59 +00:00
self . sendXmrBidCoinALockTx ( row . linked_id , session )
2022-06-06 21:03:31 +00:00
elif row . action_type == ActionTypes . SEND_XMR_SWAP_LOCK_TX_B :
2020-11-21 13:16:27 +00:00
self . sendXmrBidCoinBLockTx ( row . linked_id , session )
2022-06-06 21:03:31 +00:00
elif row . action_type == ActionTypes . SEND_XMR_LOCK_RELEASE :
2020-12-10 22:43:36 +00:00
self . sendXmrBidLockRelease ( row . linked_id , session )
2022-06-06 21:03:31 +00:00
elif row . action_type == ActionTypes . REDEEM_XMR_SWAP_LOCK_TX_A :
2020-11-21 13:16:27 +00:00
self . redeemXmrBidCoinALockTx ( row . linked_id , session )
2022-06-06 21:03:31 +00:00
elif row . action_type == ActionTypes . REDEEM_XMR_SWAP_LOCK_TX_B :
2020-11-21 13:16:27 +00:00
self . redeemXmrBidCoinBLockTx ( row . linked_id , session )
2022-06-06 21:03:31 +00:00
elif row . action_type == ActionTypes . RECOVER_XMR_SWAP_LOCK_TX_B :
2020-11-27 17:52:26 +00:00
self . recoverXmrBidCoinBLockTx ( row . linked_id , session )
2022-07-01 14:37:10 +00:00
elif row . action_type == ActionTypes . SEND_XMR_SWAP_LOCK_SPEND_MSG :
self . sendXmrBidCoinALockSpendTxMsg ( row . linked_id , session )
2022-07-03 21:58:16 +00:00
elif row . action_type == ActionTypes . REDEEM_ITX :
atomic_swap_1 . redeemITx ( self , row . linked_id , session )
2020-11-15 21:31:59 +00:00
else :
self . log . warning ( ' Unknown event type: %d ' , row . event_type )
except Exception as ex :
2022-10-26 15:47:30 +00:00
self . logException ( f ' checkQueuedActions failed: { ex } ' )
2020-12-01 20:45:03 +00:00
if self . debug :
2023-02-16 20:57:55 +00:00
session . execute ( ' UPDATE actions SET active_ind = 2 WHERE trigger_at <= :now ' , { ' now ' : now } )
2020-12-01 20:45:03 +00:00
else :
2023-02-16 20:57:55 +00:00
session . execute ( ' DELETE FROM actions WHERE trigger_at <= :now ' , { ' now ' : now } )
2019-11-09 21:09:22 +00:00
session . commit ( )
2022-12-01 18:51:06 +00:00
except Exception as ex :
self . handleSessionErrors ( ex , session , ' checkQueuedActions ' )
reload_in_progress = True
2019-07-17 15:12:06 +00:00
finally :
2020-11-27 17:52:26 +00:00
if session :
session . close ( )
session . remove ( )
2019-07-17 15:12:06 +00:00
self . mxDB . release ( )
2022-12-01 18:51:06 +00:00
if reload_in_progress :
self . loadFromDB ( )
2020-11-14 22:13:11 +00:00
def checkXmrSwaps ( self ) :
self . mxDB . acquire ( )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-14 22:13:11 +00:00
ttl_xmr_split_messages = 60 * 60
2020-12-04 21:30:20 +00:00
session = None
2020-11-14 22:13:11 +00:00
try :
session = scoped_session ( self . session_factory )
q = session . query ( Bid ) . filter ( Bid . state == BidStates . BID_RECEIVING )
for bid in q :
2021-01-11 21:48:46 +00:00
q = session . execute ( ' SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x \' {} \' AND msg_type = {} ' . format ( bid . bid_id . hex ( ) , XmrSplitMsgTypes . BID ) ) . first ( )
2020-11-14 22:13:11 +00:00
num_segments = q [ 0 ]
if num_segments > 1 :
try :
self . receiveXmrBid ( bid , session )
2020-11-15 17:02:46 +00:00
except Exception as ex :
self . log . info ( ' Verify xmr bid {} failed: {} ' . format ( bid . bid_id . hex ( ) , str ( ex ) ) )
2022-06-08 20:21:46 +00:00
if self . debug :
self . log . error ( traceback . format_exc ( ) )
2020-11-15 17:02:46 +00:00
bid . setState ( BidStates . BID_ERROR , ' Failed validation: ' + str ( ex ) )
2020-11-14 22:13:11 +00:00
session . add ( bid )
2021-01-08 22:12:08 +00:00
self . updateBidInProgress ( bid )
2020-11-14 22:13:11 +00:00
continue
if bid . created_at + ttl_xmr_split_messages < now :
self . log . debug ( ' Expiring partially received bid: {} ' . format ( bid . bid_id . hex ( ) ) )
bid . setState ( BidStates . BID_ERROR , ' Timed out ' )
session . add ( bid )
q = session . query ( Bid ) . filter ( Bid . state == BidStates . BID_RECEIVING_ACC )
for bid in q :
2021-01-11 21:48:46 +00:00
q = session . execute ( ' SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x \' {} \' AND msg_type = {} ' . format ( bid . bid_id . hex ( ) , XmrSplitMsgTypes . BID_ACCEPT ) ) . first ( )
2020-11-14 22:13:11 +00:00
num_segments = q [ 0 ]
if num_segments > 1 :
try :
self . receiveXmrBidAccept ( bid , session )
2020-11-15 17:02:46 +00:00
except Exception as ex :
2022-07-31 17:33:01 +00:00
if self . debug :
self . log . error ( traceback . format_exc ( ) )
2020-11-15 17:02:46 +00:00
self . log . info ( ' Verify xmr bid accept {} failed: {} ' . format ( bid . bid_id . hex ( ) , str ( ex ) ) )
bid . setState ( BidStates . BID_ERROR , ' Failed accept validation: ' + str ( ex ) )
2020-11-14 22:13:11 +00:00
session . add ( bid )
2021-01-11 21:48:46 +00:00
self . updateBidInProgress ( bid )
2020-11-14 22:13:11 +00:00
continue
if bid . created_at + ttl_xmr_split_messages < now :
self . log . debug ( ' Expiring partially received bid accept: {} ' . format ( bid . bid_id . hex ( ) ) )
bid . setState ( BidStates . BID_ERROR , ' Timed out ' )
session . add ( bid )
# Expire old records
q = session . query ( XmrSplitData ) . filter ( XmrSplitData . created_at + ttl_xmr_split_messages < now )
q . delete ( synchronize_session = False )
2020-11-15 17:02:46 +00:00
2020-11-14 22:13:11 +00:00
session . commit ( )
finally :
2020-12-04 21:30:20 +00:00
if session :
session . close ( )
session . remove ( )
2020-11-14 22:13:11 +00:00
self . mxDB . release ( )
2019-07-17 15:12:06 +00:00
def processOffer ( self , msg ) :
offer_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
offer_data = OfferMessage ( )
offer_data . ParseFromString ( offer_bytes )
# Validate data
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2019-07-17 15:12:06 +00:00
coin_from = Coins ( offer_data . coin_from )
2021-02-03 14:01:27 +00:00
ci_from = self . ci ( coin_from )
2019-07-17 15:12:06 +00:00
coin_to = Coins ( offer_data . coin_to )
2021-02-03 14:01:27 +00:00
ci_to = self . ci ( coin_to )
2021-10-21 22:47:04 +00:00
ensure ( offer_data . coin_from != offer_data . coin_to , ' coin_from == coin_to ' )
2019-07-17 15:12:06 +00:00
2020-12-04 21:30:20 +00:00
self . validateSwapType ( coin_from , coin_to , offer_data . swap_type )
2019-07-17 15:12:06 +00:00
self . validateOfferAmounts ( coin_from , coin_to , offer_data . amount_from , offer_data . rate , offer_data . min_bid_amount )
2022-12-10 23:26:42 +00:00
self . validateOfferLockValue ( offer_data . swap_type , coin_from , coin_to , offer_data . lock_type , offer_data . lock_value )
2021-02-15 13:34:47 +00:00
self . validateOfferValidTime ( offer_data . swap_type , coin_from , coin_to , offer_data . time_valid )
2019-07-17 15:12:06 +00:00
2021-10-21 22:47:04 +00:00
ensure ( msg [ ' sent ' ] + offer_data . time_valid > = now , ' Offer expired ' )
2019-07-17 15:12:06 +00:00
if offer_data . swap_type == SwapTypes . SELLER_FIRST :
2023-05-12 08:20:52 +00:00
ensure ( offer_data . protocol_version > = MINPROTO_VERSION_SECRET_HASH , ' Invalid protocol version ' )
2021-10-21 22:47:04 +00:00
ensure ( len ( offer_data . proof_address ) == 0 , ' Unexpected data ' )
ensure ( len ( offer_data . proof_signature ) == 0 , ' Unexpected data ' )
ensure ( len ( offer_data . pkhash_seller ) == 0 , ' Unexpected data ' )
ensure ( len ( offer_data . secret_hash ) == 0 , ' Unexpected data ' )
2019-07-17 15:12:06 +00:00
elif offer_data . swap_type == SwapTypes . BUYER_FIRST :
raise ValueError ( ' TODO ' )
2020-11-07 11:08:07 +00:00
elif offer_data . swap_type == SwapTypes . XMR_SWAP :
2023-05-12 08:20:52 +00:00
ensure ( offer_data . protocol_version > = MINPROTO_VERSION_ADAPTOR_SIG , ' Invalid protocol version ' )
2021-10-21 22:47:04 +00:00
ensure ( coin_from not in non_script_type_coins , ' Invalid coin from type ' )
2022-12-08 01:22:18 +00:00
ensure ( ci_from . has_segwit ( ) , ' Coin from must support segwit ' )
2022-06-08 20:21:46 +00:00
ensure ( len ( offer_data . proof_address ) == 0 , ' Unexpected data ' )
ensure ( len ( offer_data . proof_signature ) == 0 , ' Unexpected data ' )
ensure ( len ( offer_data . pkhash_seller ) == 0 , ' Unexpected data ' )
ensure ( len ( offer_data . secret_hash ) == 0 , ' Unexpected data ' )
2019-07-17 15:12:06 +00:00
else :
raise ValueError ( ' Unknown swap type {} . ' . format ( offer_data . swap_type ) )
offer_id = bytes . fromhex ( msg [ ' msgid ' ] )
2020-12-11 08:41:57 +00:00
if self . isOfferRevoked ( offer_id , msg [ ' from ' ] ) :
raise ValueError ( ' Offer has been revoked {} . ' . format ( offer_id . hex ( ) ) )
2019-07-17 15:12:06 +00:00
session = scoped_session ( self . session_factory )
2021-01-11 21:48:46 +00:00
try :
2021-10-20 17:47:49 +00:00
# Offers must be received on the public network_addr or manually created addresses
if msg [ ' to ' ] != self . network_addr :
# Double check active_ind, shouldn't be possible to receive message if not active
query_str = ' SELECT COUNT(addr_id) FROM smsgaddresses WHERE addr = " {} " AND use_type = {} AND active_ind = 1 ' . format ( msg [ ' to ' ] , AddressTypes . RECV_OFFER )
rv = session . execute ( query_str ) . first ( )
if rv [ 0 ] < 1 :
raise ValueError ( ' Offer received on incorrect address ' )
2021-01-11 21:48:46 +00:00
# Check for sent
existing_offer = self . getOffer ( offer_id )
if existing_offer is None :
offer = Offer (
offer_id = offer_id ,
active_ind = 1 ,
2021-11-04 21:49:52 +00:00
protocol_version = offer_data . protocol_version ,
2021-01-11 21:48:46 +00:00
coin_from = offer_data . coin_from ,
coin_to = offer_data . coin_to ,
amount_from = offer_data . amount_from ,
rate = offer_data . rate ,
min_bid_amount = offer_data . min_bid_amount ,
time_valid = offer_data . time_valid ,
lock_type = int ( offer_data . lock_type ) ,
lock_value = offer_data . lock_value ,
swap_type = offer_data . swap_type ,
2021-11-14 23:26:43 +00:00
amount_negotiable = offer_data . amount_negotiable ,
rate_negotiable = offer_data . rate_negotiable ,
2021-01-11 21:48:46 +00:00
2021-10-20 17:47:49 +00:00
addr_to = msg [ ' to ' ] ,
2021-01-11 21:48:46 +00:00
addr_from = msg [ ' from ' ] ,
created_at = msg [ ' sent ' ] ,
expire_at = msg [ ' sent ' ] + offer_data . time_valid ,
was_sent = False )
offer . setState ( OfferStates . OFFER_RECEIVED )
session . add ( offer )
if offer . swap_type == SwapTypes . XMR_SWAP :
xmr_offer = XmrOffer ( )
xmr_offer . offer_id = offer_id
2021-02-03 14:01:27 +00:00
xmr_offer . lock_time_1 = ci_from . getExpectedSequence ( offer_data . lock_type , offer_data . lock_value )
xmr_offer . lock_time_2 = ci_from . getExpectedSequence ( offer_data . lock_type , offer_data . lock_value )
2021-01-11 21:48:46 +00:00
xmr_offer . a_fee_rate = offer_data . fee_rate_from
xmr_offer . b_fee_rate = offer_data . fee_rate_to
session . add ( xmr_offer )
2022-10-13 20:21:43 +00:00
self . notify ( NT . OFFER_RECEIVED , { ' offer_id ' : offer_id . hex ( ) } , session )
2021-01-11 21:48:46 +00:00
else :
existing_offer . setState ( OfferStates . OFFER_RECEIVED )
session . add ( existing_offer )
session . commit ( )
finally :
session . close ( )
session . remove ( )
2019-07-17 15:12:06 +00:00
2020-12-02 11:24:52 +00:00
def processOfferRevoke ( self , msg ) :
2021-10-21 22:47:04 +00:00
ensure ( msg [ ' to ' ] == self . network_addr , ' Message received on wrong address ' )
2020-12-02 11:24:52 +00:00
msg_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
msg_data = OfferRevokeMessage ( )
msg_data . ParseFromString ( msg_bytes )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-12-02 11:24:52 +00:00
try :
2023-02-16 22:09:16 +00:00
session = self . openSession ( )
2020-12-02 11:24:52 +00:00
2020-12-06 17:34:56 +00:00
if len ( msg_data . offer_msg_id ) != 28 :
raise ValueError ( ' Invalid msg_id length ' )
2020-12-11 08:41:57 +00:00
if len ( msg_data . signature ) != 65 :
raise ValueError ( ' Invalid signature length ' )
2020-12-06 17:34:56 +00:00
2020-12-02 11:24:52 +00:00
offer = session . query ( Offer ) . filter_by ( offer_id = msg_data . offer_msg_id ) . first ( )
if offer is None :
2020-12-11 08:41:57 +00:00
self . storeOfferRevoke ( msg_data . offer_msg_id , msg_data . signature )
2023-02-16 22:09:16 +00:00
# Offer may not have been received yet, or involved an inactive coin on this node.
self . log . debug ( ' Offer not found to revoke: {} ' . format ( msg_data . offer_msg_id . hex ( ) ) )
return
2020-12-02 11:24:52 +00:00
if offer . expire_at < = now :
2023-02-16 22:09:16 +00:00
self . log . debug ( ' Offer is already expired, no need to revoke: {} ' . format ( msg_data . offer_msg_id . hex ( ) ) )
return
2020-12-02 11:24:52 +00:00
signature_enc = base64 . b64encode ( msg_data . signature ) . decode ( ' utf-8 ' )
passed = self . callcoinrpc ( Coins . PART , ' verifymessage ' , [ offer . addr_from , signature_enc , msg_data . offer_msg_id . hex ( ) + ' _revoke ' ] )
2021-10-21 22:47:04 +00:00
ensure ( passed is True , ' Signature invalid ' )
2020-12-02 11:24:52 +00:00
offer . active_ind = 2
# TODO: Remove message, or wait for expire
session . add ( offer )
finally :
2023-02-16 22:09:16 +00:00
self . closeSession ( session )
2020-12-04 21:30:20 +00:00
2022-06-08 20:21:46 +00:00
def getCompletedAndActiveBidsValue ( self , offer , session ) :
bids = [ ]
total_value = 0
2022-06-11 21:13:12 +00:00
q = session . execute (
''' SELECT bid_id, amount, state FROM bids
JOIN bidstates ON bidstates . state_id = bids . state AND ( bidstates . state_id = { 1 } OR bidstates . in_progress > 0 )
WHERE bids . active_ind = 1 AND bids . offer_id = x \' {0} \'
UNION
SELECT bid_id , amount , state FROM bids
JOIN actions ON actions . linked_id = bids . bid_id AND actions . active_ind = 1 AND ( actions . action_type = { 2 } OR actions . action_type = { 3 } )
WHERE bids . active_ind = 1 AND bids . offer_id = x \' {0} \'
''' .format(offer.offer_id.hex(), BidStates.SWAP_COMPLETED, ActionTypes.ACCEPT_XMR_BID, ActionTypes.ACCEPT_BID))
2022-06-08 20:21:46 +00:00
for row in q :
bid_id , amount , state = row
2022-06-11 21:13:12 +00:00
bids . append ( ( bid_id , amount , state ) )
total_value + = amount
2022-06-08 20:21:46 +00:00
return bids , total_value
2023-02-15 21:51:55 +00:00
def evaluateKnownIdentityForAutoAccept ( self , strategy , identity_stats ) - > bool :
if identity_stats :
if identity_stats . automation_override == AutomationOverrideOptions . NEVER_ACCEPT :
raise AutomationConstraint ( ' From address is marked never accept ' )
if identity_stats . automation_override == AutomationOverrideOptions . ALWAYS_ACCEPT :
return True
if strategy . only_known_identities :
if not identity_stats :
raise AutomationConstraint ( ' Unknown bidder ' )
# TODO: More options
if identity_stats . num_recv_bids_successful < 1 :
raise AutomationConstraint ( ' Bidder has too few successful swaps ' )
if identity_stats . num_recv_bids_successful < = identity_stats . num_recv_bids_failed :
raise AutomationConstraint ( ' Bidder has too many failed swaps ' )
return True
2022-05-23 21:51:06 +00:00
def shouldAutoAcceptBid ( self , offer , bid , session = None ) :
try :
2022-10-13 20:21:43 +00:00
use_session = self . openSession ( session )
2022-05-23 21:51:06 +00:00
2022-06-06 21:03:31 +00:00
link = use_session . query ( AutomationLink ) . filter_by ( active_ind = 1 , linked_type = Concepts . OFFER , linked_id = offer . offer_id ) . first ( )
2022-05-23 21:51:06 +00:00
if not link :
return False
2022-06-04 20:41:24 +00:00
strategy = use_session . query ( AutomationStrategy ) . filter_by ( active_ind = 1 , record_id = link . strategy_id ) . first ( )
2022-05-23 21:51:06 +00:00
opts = json . loads ( strategy . data . decode ( ' utf-8 ' ) )
self . log . debug ( ' Evaluating against strategy {} ' . format ( strategy . record_id ) )
2022-06-06 21:03:31 +00:00
if not offer . amount_negotiable :
2022-05-23 21:51:06 +00:00
if bid . amount != offer . amount_from :
2022-06-08 20:21:46 +00:00
raise AutomationConstraint ( ' Need exact amount match ' )
2022-05-23 21:51:06 +00:00
2022-06-06 21:03:31 +00:00
if bid . amount < offer . min_bid_amount :
2022-06-08 20:21:46 +00:00
raise AutomationConstraint ( ' Bid amount below offer minimum ' )
2022-06-06 21:03:31 +00:00
if opts . get ( ' exact_rate_only ' , False ) is True :
if bid . rate != offer . rate :
2022-06-08 20:21:46 +00:00
raise AutomationConstraint ( ' Need exact rate match ' )
2022-06-06 21:03:31 +00:00
2022-06-29 11:02:32 +00:00
active_bids , total_bids_value = self . getCompletedAndActiveBidsValue ( offer , use_session )
2022-06-08 20:21:46 +00:00
2023-02-17 20:51:49 +00:00
total_bids_value_multiplier = opts . get ( ' total_bids_value_multiplier ' , 1.0 )
if total_bids_value_multiplier > 0.0 :
if total_bids_value + bid . amount > offer . amount_from * total_bids_value_multiplier :
2023-02-17 21:14:17 +00:00
raise AutomationConstraint ( ' Over remaining offer value {} ' . format ( offer . amount_from * total_bids_value_multiplier - total_bids_value ) )
2022-06-08 20:21:46 +00:00
num_not_completed = 0
for active_bid in active_bids :
2022-06-11 21:13:12 +00:00
if active_bid [ 2 ] != BidStates . SWAP_COMPLETED :
2022-06-08 20:21:46 +00:00
num_not_completed + = 1
max_concurrent_bids = opts . get ( ' max_concurrent_bids ' , 1 )
if num_not_completed > = max_concurrent_bids :
raise AutomationConstraint ( ' Already have {} bids to complete ' . format ( num_not_completed ) )
2022-05-23 21:51:06 +00:00
2023-02-15 21:51:55 +00:00
identity_stats = use_session . query ( KnownIdentity ) . filter_by ( address = bid . bid_addr ) . first ( )
self . evaluateKnownIdentityForAutoAccept ( strategy , identity_stats )
2022-05-23 21:51:06 +00:00
2022-06-22 20:51:39 +00:00
self . logEvent ( Concepts . BID ,
bid . bid_id ,
EventLogTypes . AUTOMATION_ACCEPTING_BID ,
' ' ,
use_session )
2022-05-23 21:51:06 +00:00
return True
2022-06-08 20:21:46 +00:00
except AutomationConstraint as e :
self . log . info ( ' Not auto accepting bid {} , {} ' . format ( bid . bid_id . hex ( ) , str ( e ) ) )
if self . debug :
2023-02-17 23:47:44 +00:00
self . logEvent ( Concepts . BID ,
2022-06-08 20:21:46 +00:00
bid . bid_id ,
EventLogTypes . AUTOMATION_CONSTRAINT ,
str ( e ) ,
use_session )
return False
2022-06-04 20:41:24 +00:00
except Exception as e :
2022-10-26 15:47:30 +00:00
self . logException ( f ' shouldAutoAcceptBid { e } ' )
2022-06-04 20:41:24 +00:00
return False
2022-05-23 21:51:06 +00:00
finally :
if session is None :
2022-10-13 20:21:43 +00:00
self . closeSession ( use_session )
2022-05-23 21:51:06 +00:00
2019-07-17 15:12:06 +00:00
def processBid ( self , msg ) :
self . log . debug ( ' Processing bid msg %s ' , msg [ ' msgid ' ] )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2019-07-17 15:12:06 +00:00
bid_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
bid_data = BidMessage ( )
bid_data . ParseFromString ( bid_bytes )
# Validate data
2023-05-12 08:20:52 +00:00
ensure ( bid_data . protocol_version > = MINPROTO_VERSION_SECRET_HASH , ' Invalid protocol version ' )
2021-10-21 22:47:04 +00:00
ensure ( len ( bid_data . offer_msg_id ) == 28 , ' Bad offer_id length ' )
2019-07-17 15:12:06 +00:00
offer_id = bid_data . offer_msg_id
offer = self . getOffer ( offer_id , sent = True )
2021-10-21 22:47:04 +00:00
ensure ( offer and offer . was_sent , ' Unknown offer ' )
2019-07-17 15:12:06 +00:00
2021-10-21 22:47:04 +00:00
ensure ( offer . state == OfferStates . OFFER_RECEIVED , ' Bad offer state ' )
ensure ( msg [ ' to ' ] == offer . addr_from , ' Received on incorrect address ' )
ensure ( now < = offer . expire_at , ' Offer expired ' )
2021-02-15 13:34:47 +00:00
self . validateBidValidTime ( offer . swap_type , offer . coin_from , offer . coin_to , bid_data . time_valid )
2021-10-21 22:47:04 +00:00
ensure ( now < = msg [ ' sent ' ] + bid_data . time_valid , ' Bid expired ' )
2021-11-22 20:24:48 +00:00
self . validateBidAmount ( offer , bid_data . amount , bid_data . rate )
2021-11-14 23:26:43 +00:00
2020-12-11 08:41:57 +00:00
# TODO: Allow higher bids
2022-07-31 18:01:49 +00:00
# assert (bid_data.rate != offer['data'].rate), 'Bid rate mismatch'
2019-07-17 15:12:06 +00:00
coin_to = Coins ( offer . coin_to )
2021-11-04 21:49:52 +00:00
ci_from = self . ci ( offer . coin_from )
ci_to = self . ci ( coin_to )
2021-01-18 22:52:05 +00:00
2021-11-22 20:24:48 +00:00
amount_to = int ( ( bid_data . amount * bid_data . rate ) / / ci_from . COIN ( ) )
2019-07-17 15:12:06 +00:00
swap_type = offer . swap_type
if swap_type == SwapTypes . SELLER_FIRST :
2021-10-21 22:47:04 +00:00
ensure ( len ( bid_data . pkhash_buyer ) == 20 , ' Bad pkhash_buyer length ' )
2019-07-17 15:12:06 +00:00
2022-11-07 20:31:10 +00:00
sum_unspent = ci_to . verifyProofOfFunds ( bid_data . proof_address , bid_data . proof_signature , offer_id )
2021-01-18 22:52:05 +00:00
self . log . debug ( ' Proof of funds %s %s ' , bid_data . proof_address , self . ci ( coin_to ) . format_amount ( sum_unspent ) )
2021-10-21 22:47:04 +00:00
ensure ( sum_unspent > = amount_to , ' Proof of funds failed ' )
2019-07-17 15:12:06 +00:00
elif swap_type == SwapTypes . BUYER_FIRST :
raise ValueError ( ' TODO ' )
else :
raise ValueError ( ' Unknown swap type {} . ' . format ( swap_type ) )
bid_id = bytes . fromhex ( msg [ ' msgid ' ] )
bid = self . getBid ( bid_id )
if bid is None :
bid = Bid (
2021-02-15 13:34:47 +00:00
active_ind = 1 ,
2019-07-17 15:12:06 +00:00
bid_id = bid_id ,
offer_id = offer_id ,
2021-11-04 21:49:52 +00:00
protocol_version = bid_data . protocol_version ,
2019-07-17 15:12:06 +00:00
amount = bid_data . amount ,
2021-11-14 23:26:43 +00:00
rate = bid_data . rate ,
2019-07-17 15:12:06 +00:00
pkhash_buyer = bid_data . pkhash_buyer ,
created_at = msg [ ' sent ' ] ,
2021-01-18 22:52:05 +00:00
amount_to = amount_to ,
2019-07-17 15:12:06 +00:00
expire_at = msg [ ' sent ' ] + bid_data . time_valid ,
bid_addr = msg [ ' from ' ] ,
was_received = True ,
2021-11-04 21:49:52 +00:00
chain_a_height_start = ci_from . getChainHeight ( ) ,
chain_b_height_start = ci_to . getChainHeight ( ) ,
2019-07-17 15:12:06 +00:00
)
else :
2021-10-21 22:47:04 +00:00
ensure ( bid . state == BidStates . BID_SENT , ' Wrong bid state: {} ' . format ( str ( BidStates ( bid . state ) ) ) )
2019-07-17 15:12:06 +00:00
bid . created_at = msg [ ' sent ' ]
bid . expire_at = msg [ ' sent ' ] + bid_data . time_valid
bid . was_received = True
if len ( bid_data . proof_address ) > 0 :
bid . proof_address = bid_data . proof_address
bid . setState ( BidStates . BID_RECEIVED )
self . saveBid ( bid_id , bid )
2022-07-31 17:33:01 +00:00
self . notify ( NT . BID_RECEIVED , { ' type ' : ' atomic ' , ' bid_id ' : bid_id . hex ( ) , ' offer_id ' : bid_data . offer_msg_id . hex ( ) } )
2019-07-17 15:12:06 +00:00
2022-05-23 21:51:06 +00:00
if self . shouldAutoAcceptBid ( offer , bid ) :
delay = random . randrange ( self . min_delay_event , self . max_delay_event )
self . log . info ( ' Auto accepting bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createAction ( delay , ActionTypes . ACCEPT_BID , bid_id )
2019-07-17 15:12:06 +00:00
def processBidAccept ( self , msg ) :
self . log . debug ( ' Processing bid accepted msg %s ' , msg [ ' msgid ' ] )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2019-07-17 15:12:06 +00:00
bid_accept_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
bid_accept_data = BidAcceptMessage ( )
bid_accept_data . ParseFromString ( bid_accept_bytes )
2021-10-21 22:47:04 +00:00
ensure ( len ( bid_accept_data . bid_msg_id ) == 28 , ' Bad bid_msg_id length ' )
ensure ( len ( bid_accept_data . initiate_txid ) == 32 , ' Bad initiate_txid length ' )
ensure ( len ( bid_accept_data . contract_script ) < 100 , ' Bad contract_script length ' )
2019-07-17 15:12:06 +00:00
self . log . debug ( ' for bid %s ' , bid_accept_data . bid_msg_id . hex ( ) )
bid_id = bid_accept_data . bid_msg_id
bid , offer = self . getBidAndOffer ( bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid is not None and bid . was_sent is True , ' Unknown bidid ' )
ensure ( offer , ' Offer not found ' + bid . offer_id . hex ( ) )
2019-07-17 15:12:06 +00:00
2021-10-21 22:47:04 +00:00
ensure ( bid . expire_at > now + self . _bid_expired_leeway , ' Bid expired ' )
2023-03-08 23:23:18 +00:00
ensure ( msg [ ' to ' ] == bid . bid_addr , ' Received on incorrect address ' )
ensure ( msg [ ' from ' ] == offer . addr_from , ' Sent from incorrect address ' )
coin_from = Coins ( offer . coin_from )
ci_from = self . ci ( coin_from )
2019-07-17 15:12:06 +00:00
if bid . state > = BidStates . BID_ACCEPTED :
if bid . was_received : # Sent to self
self . log . info ( ' Received valid bid accept %s for bid %s sent to self ' , bid . accept_msg_id . hex ( ) , bid_id . hex ( ) )
return
2021-01-18 22:52:05 +00:00
raise ValueError ( ' Wrong bid state: {} ' . format ( str ( BidStates ( bid . state ) ) ) )
2019-07-17 15:12:06 +00:00
2021-12-15 13:41:43 +00:00
use_csv = True if offer . lock_type < TxLockTypes . ABS_LOCK_BLOCKS else False
2019-07-24 22:59:40 +00:00
# TODO: Verify script without decoding?
decoded_script = self . callcoinrpc ( Coins . PART , ' decodescript ' , [ bid_accept_data . contract_script . hex ( ) ] )
lock_check_op = ' OP_CHECKSEQUENCEVERIFY ' if use_csv else ' OP_CHECKLOCKTIMEVERIFY '
2019-10-02 20:34:03 +00:00
prog = re . compile ( r ' OP_IF OP_SIZE 32 OP_EQUALVERIFY OP_SHA256 ( \ w+) OP_EQUALVERIFY OP_DUP OP_HASH160 ( \ w+) OP_ELSE ( \ d+) {} OP_DROP OP_DUP OP_HASH160 ( \ w+) OP_ENDIF OP_EQUALVERIFY OP_CHECKSIG ' . format ( lock_check_op ) )
2019-07-24 22:59:40 +00:00
rr = prog . match ( decoded_script [ ' asm ' ] )
if not rr :
raise ValueError ( ' Bad script ' )
scriptvalues = rr . groups ( )
2019-07-17 15:12:06 +00:00
2021-10-21 22:47:04 +00:00
ensure ( len ( scriptvalues [ 0 ] ) == 64 , ' Bad secret_hash length ' )
ensure ( bytes . fromhex ( scriptvalues [ 1 ] ) == bid . pkhash_buyer , ' pkhash_buyer mismatch ' )
2019-07-24 22:59:40 +00:00
2019-07-25 09:29:48 +00:00
script_lock_value = int ( scriptvalues [ 2 ] )
2019-07-24 22:59:40 +00:00
if use_csv :
2021-02-03 14:01:27 +00:00
expect_sequence = ci_from . getExpectedSequence ( offer . lock_type , offer . lock_value )
2021-10-21 22:47:04 +00:00
ensure ( script_lock_value == expect_sequence , ' sequence mismatch ' )
2019-07-24 22:59:40 +00:00
else :
2021-12-15 13:41:43 +00:00
if offer . lock_type == TxLockTypes . ABS_LOCK_BLOCKS :
2022-10-25 21:22:09 +00:00
block_header_from = ci_from . getBlockHeaderAt ( now )
2022-10-11 05:55:35 +00:00
chain_height_at_bid_creation = block_header_from [ ' height ' ]
ensure ( script_lock_value < = chain_height_at_bid_creation + offer . lock_value + atomic_swap_1 . ABS_LOCK_BLOCKS_LEEWAY , ' script lock height too high ' )
ensure ( script_lock_value > = chain_height_at_bid_creation + offer . lock_value - atomic_swap_1 . ABS_LOCK_BLOCKS_LEEWAY , ' script lock height too low ' )
2019-07-25 09:29:48 +00:00
else :
2022-10-25 21:22:09 +00:00
ensure ( script_lock_value < = now + offer . lock_value + atomic_swap_1 . INITIATE_TX_TIMEOUT , ' script lock time too high ' )
ensure ( script_lock_value > = now + offer . lock_value - atomic_swap_1 . ABS_LOCK_TIME_LEEWAY , ' script lock time too low ' )
2019-07-24 22:59:40 +00:00
2021-10-21 22:47:04 +00:00
ensure ( len ( scriptvalues [ 3 ] ) == 40 , ' pkhash_refund bad length ' )
2019-07-17 15:12:06 +00:00
2021-10-21 22:47:04 +00:00
ensure ( bid . accept_msg_id is None , ' Bid already accepted ' )
2019-07-17 15:12:06 +00:00
bid . accept_msg_id = bytes . fromhex ( msg [ ' msgid ' ] )
2019-07-27 16:00:13 +00:00
bid . initiate_tx = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . ITX ,
txid = bid_accept_data . initiate_txid ,
script = bid_accept_data . contract_script ,
)
2019-07-17 15:12:06 +00:00
bid . pkhash_seller = bytes . fromhex ( scriptvalues [ 3 ] )
bid . setState ( BidStates . BID_ACCEPTED )
2019-07-27 18:51:50 +00:00
bid . setITxState ( TxStates . TX_NONE )
2019-07-17 15:12:06 +00:00
2022-07-31 17:33:01 +00:00
bid . offer_id . hex ( )
2019-07-17 15:12:06 +00:00
self . saveBid ( bid_id , bid )
self . swaps_in_progress [ bid_id ] = ( bid , offer )
2022-07-31 17:33:01 +00:00
self . notify ( NT . BID_ACCEPTED , { ' bid_id ' : bid_id . hex ( ) } )
2019-07-17 15:12:06 +00:00
2020-11-14 22:13:11 +00:00
def receiveXmrBid ( self , bid , session ) :
self . log . debug ( ' Receiving xmr bid %s ' , bid . bid_id . hex ( ) )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-14 22:13:11 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = True )
2021-10-21 22:47:04 +00:00
ensure ( offer and offer . was_sent , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-14 22:13:11 +00:00
xmr_swap = session . query ( XmrSwap ) . filter_by ( bid_id = bid . bid_id ) . first ( )
2021-10-21 22:47:04 +00:00
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid . bid_id . hex ( ) ) )
2020-11-14 22:13:11 +00:00
2020-12-04 21:30:20 +00:00
ci_from = self . ci ( Coins ( offer . coin_from ) )
ci_to = self . ci ( Coins ( offer . coin_to ) )
2020-11-14 22:13:11 +00:00
2023-05-11 21:45:06 +00:00
if ci_to . curve_type ( ) == Curves . ed25519 :
2021-02-11 12:57:54 +00:00
if len ( xmr_swap . kbsf_dleag ) < ci_to . lengthDLEAG ( ) :
q = session . query ( XmrSplitData ) . filter ( sa . and_ ( XmrSplitData . bid_id == bid . bid_id , XmrSplitData . msg_type == XmrSplitMsgTypes . BID ) ) . order_by ( XmrSplitData . msg_sequence . asc ( ) )
for row in q :
2023-03-08 23:23:18 +00:00
ensure ( row . addr_to == offer . addr_from , ' Received on incorrect address, segment_id {} ' . format ( row . record_id ) )
ensure ( row . addr_from == bid . bid_addr , ' Sent from incorrect address, segment_id {} ' . format ( row . record_id ) )
2021-02-11 12:57:54 +00:00
xmr_swap . kbsf_dleag + = row . dleag
if not ci_to . verifyDLEAG ( xmr_swap . kbsf_dleag ) :
raise ValueError ( ' Invalid DLEAG proof. ' )
# Extract pubkeys from MSG1L DLEAG
xmr_swap . pkasf = xmr_swap . kbsf_dleag [ 0 : 33 ]
if not ci_from . verifyPubkey ( xmr_swap . pkasf ) :
raise ValueError ( ' Invalid coin a pubkey. ' )
xmr_swap . pkbsf = xmr_swap . kbsf_dleag [ 33 : 33 + 32 ]
if not ci_to . verifyPubkey ( xmr_swap . pkbsf ) :
raise ValueError ( ' Invalid coin b pubkey. ' )
2023-05-11 21:45:06 +00:00
elif ci_to . curve_type ( ) == Curves . secp256k1 :
xmr_swap . pkasf = ci_to . verifySigAndRecover ( xmr_swap . kbsf_dleag , ' proof kbsf owned for swap ' )
2021-02-11 12:57:54 +00:00
if not ci_from . verifyPubkey ( xmr_swap . pkasf ) :
raise ValueError ( ' Invalid coin a pubkey. ' )
xmr_swap . pkbsf = xmr_swap . pkasf
2023-05-11 21:45:06 +00:00
else :
raise ValueError ( ' Unknown curve ' )
2020-11-14 22:13:11 +00:00
2021-11-01 13:52:40 +00:00
ensure ( ci_to . verifyKey ( xmr_swap . vkbvf ) , ' Invalid key, vkbvf ' )
ensure ( ci_from . verifyPubkey ( xmr_swap . pkaf ) , ' Invalid pubkey, pkaf ' )
2020-11-14 22:13:11 +00:00
2022-10-13 20:21:43 +00:00
self . notify ( NT . BID_RECEIVED , { ' type ' : ' xmr ' , ' bid_id ' : bid . bid_id . hex ( ) , ' offer_id ' : bid . offer_id . hex ( ) } , session )
2020-12-04 21:30:20 +00:00
2020-11-14 22:13:11 +00:00
bid . setState ( BidStates . BID_RECEIVED )
2022-05-23 21:51:06 +00:00
if self . shouldAutoAcceptBid ( offer , bid , session ) :
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 )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , ActionTypes . ACCEPT_XMR_BID , bid . bid_id , session )
2022-05-23 21:51:06 +00:00
bid . setState ( BidStates . SWAP_DELAYING )
2021-10-18 18:48:48 +00:00
self . saveBidInSession ( bid . bid_id , bid , session , xmr_swap )
2020-12-04 21:30:20 +00:00
2020-11-14 22:13:11 +00:00
def receiveXmrBidAccept ( self , bid , session ) :
2020-11-15 21:31:59 +00:00
# Follower receiving MSG1F and MSG2F
2020-11-14 22:13:11 +00:00
self . log . debug ( ' Receiving xmr bid accept %s ' , bid . bid_id . hex ( ) )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-14 22:13:11 +00:00
offer , xmr_offer = self . getXmrOffer ( bid . offer_id , sent = True )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-14 22:13:11 +00:00
xmr_swap = session . query ( XmrSwap ) . filter_by ( bid_id = bid . bid_id ) . first ( )
2021-10-21 22:47:04 +00:00
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid . bid_id . hex ( ) ) )
2021-02-11 12:57:54 +00:00
ci_from = self . ci ( offer . coin_from )
ci_to = self . ci ( offer . coin_to )
2023-05-11 21:45:06 +00:00
if ci_to . curve_type ( ) == Curves . ed25519 :
2021-02-11 12:57:54 +00:00
if len ( xmr_swap . kbsl_dleag ) < ci_to . lengthDLEAG ( ) :
q = session . query ( XmrSplitData ) . filter ( sa . and_ ( XmrSplitData . bid_id == bid . bid_id , XmrSplitData . msg_type == XmrSplitMsgTypes . BID_ACCEPT ) ) . order_by ( XmrSplitData . msg_sequence . asc ( ) )
for row in q :
2023-03-08 23:23:18 +00:00
ensure ( row . addr_to == bid . bid_addr , ' Received on incorrect address, segment_id {} ' . format ( row . record_id ) )
ensure ( row . addr_from == offer . addr_from , ' Sent from incorrect address, segment_id {} ' . format ( row . record_id ) )
2021-02-11 12:57:54 +00:00
xmr_swap . kbsl_dleag + = row . dleag
if not ci_to . verifyDLEAG ( xmr_swap . kbsl_dleag ) :
raise ValueError ( ' Invalid DLEAG proof. ' )
# Extract pubkeys from MSG1F DLEAG
xmr_swap . pkasl = xmr_swap . kbsl_dleag [ 0 : 33 ]
if not ci_from . verifyPubkey ( xmr_swap . pkasl ) :
raise ValueError ( ' Invalid coin a pubkey. ' )
xmr_swap . pkbsl = xmr_swap . kbsl_dleag [ 33 : 33 + 32 ]
if not ci_to . verifyPubkey ( xmr_swap . pkbsl ) :
raise ValueError ( ' Invalid coin b pubkey. ' )
2023-05-11 21:45:06 +00:00
elif ci_to . curve_type ( ) == Curves . secp256k1 :
xmr_swap . pkasl = ci_to . verifySigAndRecover ( xmr_swap . kbsl_dleag , ' proof kbsl owned for swap ' )
2021-02-11 12:57:54 +00:00
if not ci_from . verifyPubkey ( xmr_swap . pkasl ) :
raise ValueError ( ' Invalid coin a pubkey. ' )
xmr_swap . pkbsl = xmr_swap . pkasl
2023-05-11 21:45:06 +00:00
else :
raise ValueError ( ' Unknown curve ' )
2020-11-14 22:13:11 +00:00
2021-11-01 13:52:40 +00:00
# vkbv and vkbvl are verified in processXmrBidAccept
2020-11-21 13:16:27 +00:00
xmr_swap . pkbv = ci_to . sumPubkeys ( xmr_swap . pkbvl , xmr_swap . pkbvf )
xmr_swap . pkbs = ci_to . sumPubkeys ( xmr_swap . pkbsl , xmr_swap . pkbsf )
2020-11-14 22:13:11 +00:00
if not ci_from . verifyPubkey ( xmr_swap . pkal ) :
raise ValueError ( ' Invalid pubkey. ' )
2020-12-10 10:07:26 +00:00
if xmr_swap . pkbvl == xmr_swap . pkbvf :
raise ValueError ( ' Duplicate scriptless view pubkey. ' )
if xmr_swap . pkbsl == xmr_swap . pkbsf :
raise ValueError ( ' Duplicate scriptless spend pubkey. ' )
if xmr_swap . pkal == xmr_swap . pkaf :
raise ValueError ( ' Duplicate script spend pubkey. ' )
2022-07-01 14:37:10 +00:00
bid . setState ( BidStates . BID_ACCEPTED ) # XMR
2020-11-14 22:13:11 +00:00
self . saveBidInSession ( bid . bid_id , bid , session , xmr_swap )
2022-10-13 20:21:43 +00:00
self . notify ( NT . BID_ACCEPTED , { ' bid_id ' : bid . bid_id . hex ( ) } , session )
2020-11-14 22:13:11 +00:00
2020-12-02 11:24:52 +00:00
delay = random . randrange ( self . min_delay_event , self . max_delay_event )
2020-11-15 17:02:46 +00:00
self . log . info ( ' Responding to xmr bid accept %s in %d seconds ' , bid . bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , ActionTypes . SIGN_XMR_SWAP_LOCK_TX_A , bid . bid_id , session )
2020-11-15 17:02:46 +00:00
2020-11-14 22:13:11 +00:00
def processXmrBid ( self , msg ) :
2020-11-21 13:16:27 +00:00
# MSG1L
2020-11-14 22:13:11 +00:00
self . log . debug ( ' Processing xmr bid msg %s ' , msg [ ' msgid ' ] )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-14 22:13:11 +00:00
bid_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
bid_data = XmrBidMessage ( )
bid_data . ParseFromString ( bid_bytes )
# Validate data
2023-05-12 08:20:52 +00:00
ensure ( bid_data . protocol_version > = MINPROTO_VERSION_ADAPTOR_SIG , ' Invalid protocol version ' )
2021-10-21 22:47:04 +00:00
ensure ( len ( bid_data . offer_msg_id ) == 28 , ' Bad offer_id length ' )
2020-11-14 22:13:11 +00:00
offer_id = bid_data . offer_msg_id
offer , xmr_offer = self . getXmrOffer ( offer_id , sent = True )
2021-10-21 22:47:04 +00:00
ensure ( offer and offer . was_sent , ' Offer not found: {} . ' . format ( offer_id . hex ( ) ) )
2023-05-12 08:20:52 +00:00
ensure ( offer . swap_type == SwapTypes . XMR_SWAP , ' Bid/offer swap type mismatch ' )
2021-10-21 22:47:04 +00:00
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( offer_id . hex ( ) ) )
2021-02-15 13:34:47 +00:00
2021-02-11 12:57:54 +00:00
ci_from = self . ci ( offer . coin_from )
ci_to = self . ci ( offer . coin_to )
2020-11-14 22:13:11 +00:00
2021-10-20 17:47:49 +00:00
if not validOfferStateToReceiveBid ( offer . state ) :
raise ValueError ( ' Bad offer state ' )
2021-10-21 22:47:04 +00:00
ensure ( msg [ ' to ' ] == offer . addr_from , ' Received on incorrect address ' )
ensure ( now < = offer . expire_at , ' Offer expired ' )
2021-02-15 13:34:47 +00:00
self . validateBidValidTime ( offer . swap_type , offer . coin_from , offer . coin_to , bid_data . time_valid )
2021-10-21 22:47:04 +00:00
ensure ( now < = msg [ ' sent ' ] + bid_data . time_valid , ' Bid expired ' )
2021-01-31 12:26:32 +00:00
2021-11-22 20:24:48 +00:00
self . validateBidAmount ( offer , bid_data . amount , bid_data . rate )
2021-11-14 23:26:43 +00:00
2021-10-21 22:47:04 +00:00
ensure ( ci_to . verifyKey ( bid_data . kbvf ) , ' Invalid chain B follower view key ' )
ensure ( ci_from . verifyPubkey ( bid_data . pkaf ) , ' Invalid chain A follower public key ' )
2020-11-14 22:13:11 +00:00
bid_id = bytes . fromhex ( msg [ ' msgid ' ] )
bid , xmr_swap = self . getXmrBid ( bid_id )
if bid is None :
bid = Bid (
2021-02-15 13:34:47 +00:00
active_ind = 1 ,
2020-11-14 22:13:11 +00:00
bid_id = bid_id ,
offer_id = offer_id ,
2021-11-04 21:49:52 +00:00
protocol_version = bid_data . protocol_version ,
2020-11-14 22:13:11 +00:00
amount = bid_data . amount ,
2021-11-14 23:26:43 +00:00
rate = bid_data . rate ,
2020-11-14 22:13:11 +00:00
created_at = msg [ ' sent ' ] ,
2021-11-22 20:24:48 +00:00
amount_to = ( bid_data . amount * bid_data . rate ) / / ci_from . COIN ( ) ,
2020-11-14 22:13:11 +00:00
expire_at = msg [ ' sent ' ] + bid_data . time_valid ,
bid_addr = msg [ ' from ' ] ,
was_received = True ,
2021-11-04 21:49:52 +00:00
chain_a_height_start = ci_from . getChainHeight ( ) ,
chain_b_height_start = ci_to . getChainHeight ( ) ,
2020-11-14 22:13:11 +00:00
)
xmr_swap = XmrSwap (
bid_id = bid_id ,
2020-11-15 17:02:46 +00:00
dest_af = bid_data . dest_af ,
2020-11-14 22:13:11 +00:00
pkaf = bid_data . pkaf ,
vkbvf = bid_data . kbvf ,
2020-11-21 13:16:27 +00:00
pkbvf = ci_to . getPubkey ( bid_data . kbvf ) ,
2020-11-14 22:13:11 +00:00
kbsf_dleag = bid_data . kbsf_dleag ,
)
2021-02-11 12:57:54 +00:00
wallet_restore_height = self . getWalletRestoreHeight ( ci_to )
2021-11-04 21:49:52 +00:00
if bid . chain_b_height_start < wallet_restore_height :
bid . chain_b_height_start = wallet_restore_height
2021-02-11 12:57:54 +00:00
self . log . warning ( ' XMR swap restore height clamped to {} ' . format ( wallet_restore_height ) )
2020-11-14 22:13:11 +00:00
else :
2021-10-21 22:47:04 +00:00
ensure ( bid . state == BidStates . BID_SENT , ' Wrong bid state: {} ' . format ( str ( BidStates ( bid . state ) ) ) )
2022-11-14 19:47:07 +00:00
# Don't update bid.created_at, it's been used to derive kaf
2020-11-14 22:13:11 +00:00
bid . expire_at = msg [ ' sent ' ] + bid_data . time_valid
bid . was_received = True
bid . setState ( BidStates . BID_RECEIVING )
self . log . info ( ' Receiving xmr bid %s for offer %s ' , bid_id . hex ( ) , bid_data . offer_msg_id . hex ( ) )
self . saveBid ( bid_id , bid , xmr_swap = xmr_swap )
2021-02-11 12:57:54 +00:00
if offer . coin_to != Coins . XMR :
with self . mxDB :
try :
session = scoped_session ( self . session_factory )
self . receiveXmrBid ( bid , session )
session . commit ( )
finally :
session . close ( )
session . remove ( )
2020-11-14 22:13:11 +00:00
def processXmrBidAccept ( self , msg ) :
2020-11-15 17:02:46 +00:00
# F receiving MSG1F and MSG2F
2020-11-14 22:13:11 +00:00
self . log . debug ( ' Processing xmr bid accept msg %s ' , msg [ ' msgid ' ] )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-14 22:13:11 +00:00
msg_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
msg_data = XmrBidAcceptMessage ( )
msg_data . ParseFromString ( msg_bytes )
2021-10-21 22:47:04 +00:00
ensure ( len ( msg_data . bid_msg_id ) == 28 , ' Bad bid_msg_id length ' )
2020-11-14 22:13:11 +00:00
self . log . debug ( ' for bid %s ' , msg_data . bid_msg_id . hex ( ) )
bid , xmr_swap = self . getXmrBid ( msg_data . bid_msg_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( msg_data . bid_msg_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( msg_data . bid_msg_id . hex ( ) ) )
2020-11-14 22:13:11 +00:00
offer , xmr_offer = self . getXmrOffer ( bid . offer_id , sent = True )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2023-03-08 23:23:18 +00:00
ensure ( msg [ ' to ' ] == bid . bid_addr , ' Received on incorrect address ' )
ensure ( msg [ ' from ' ] == offer . addr_from , ' Sent from incorrect address ' )
2021-02-11 12:57:54 +00:00
ci_from = self . ci ( offer . coin_from )
ci_to = self . ci ( offer . coin_to )
2020-11-14 22:13:11 +00:00
2020-11-15 17:02:46 +00:00
try :
xmr_swap . pkal = msg_data . pkal
xmr_swap . vkbvl = msg_data . kbvl
2021-11-01 13:52:40 +00:00
ensure ( ci_to . verifyKey ( xmr_swap . vkbvl ) , ' Invalid key, vkbvl ' )
xmr_swap . vkbv = ci_to . sumKeys ( xmr_swap . vkbvl , xmr_swap . vkbvf )
ensure ( ci_to . verifyKey ( xmr_swap . vkbv ) , ' Invalid key, vkbv ' )
2020-11-21 13:16:27 +00:00
xmr_swap . pkbvl = ci_to . getPubkey ( msg_data . kbvl )
2020-11-15 17:02:46 +00:00
xmr_swap . kbsl_dleag = msg_data . kbsl_dleag
xmr_swap . a_lock_tx = msg_data . a_lock_tx
xmr_swap . a_lock_tx_script = msg_data . a_lock_tx_script
xmr_swap . a_lock_refund_tx = msg_data . a_lock_refund_tx
xmr_swap . a_lock_refund_tx_script = msg_data . a_lock_refund_tx_script
xmr_swap . a_lock_refund_spend_tx = msg_data . a_lock_refund_spend_tx
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_refund_spend_tx_id = ci_from . getTxid ( xmr_swap . a_lock_refund_spend_tx )
2020-11-15 17:02:46 +00:00
xmr_swap . al_lock_refund_tx_sig = msg_data . al_lock_refund_tx_sig
2021-11-01 13:52:40 +00:00
# TODO: check_lock_tx_inputs without txindex
2020-11-29 23:05:30 +00:00
check_a_lock_tx_inputs = False
2022-11-07 20:31:10 +00:00
xmr_swap . a_lock_tx_id , xmr_swap . a_lock_tx_vout = ci_from . verifySCLockTx (
2020-11-15 17:02:46 +00:00
xmr_swap . a_lock_tx , xmr_swap . a_lock_tx_script ,
bid . amount ,
xmr_swap . pkal , xmr_swap . pkaf ,
2020-12-10 22:43:36 +00:00
xmr_offer . a_fee_rate ,
2021-11-01 13:52:40 +00:00
check_a_lock_tx_inputs , xmr_swap . vkbv )
2020-11-21 13:16:27 +00:00
a_lock_tx_dest = ci_from . getScriptDest ( xmr_swap . a_lock_tx_script )
2020-11-15 17:02:46 +00:00
2022-11-07 20:31:10 +00:00
xmr_swap . a_lock_refund_tx_id , xmr_swap . a_swap_refund_value , lock_refund_vout = ci_from . verifySCLockRefundTx (
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_refund_tx , xmr_swap . a_lock_tx , xmr_swap . a_lock_refund_tx_script ,
2020-11-29 11:46:00 +00:00
xmr_swap . a_lock_tx_id , xmr_swap . a_lock_tx_vout , xmr_offer . lock_time_1 , xmr_swap . a_lock_tx_script ,
2020-12-10 22:43:36 +00:00
xmr_swap . pkal , xmr_swap . pkaf ,
2020-11-15 17:02:46 +00:00
xmr_offer . lock_time_2 ,
2021-11-01 13:52:40 +00:00
bid . amount , xmr_offer . a_fee_rate , xmr_swap . vkbv )
2020-11-15 17:02:46 +00:00
2022-11-07 20:31:10 +00:00
ci_from . verifySCLockRefundSpendTx (
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_refund_spend_tx , xmr_swap . a_lock_refund_tx ,
2020-11-29 23:05:30 +00:00
xmr_swap . a_lock_refund_tx_id , xmr_swap . a_lock_refund_tx_script ,
2020-11-15 17:02:46 +00:00
xmr_swap . pkal ,
2021-11-01 13:52:40 +00:00
lock_refund_vout , xmr_swap . a_swap_refund_value , xmr_offer . a_fee_rate , xmr_swap . vkbv )
2020-11-14 22:13:11 +00:00
2021-01-29 23:45:24 +00:00
self . log . info ( ' Checking leader \' s lock refund tx signature ' )
2021-11-01 13:52:40 +00:00
prevout_amount = ci_from . getLockTxSwapOutputValue ( bid , xmr_swap )
v = ci_from . verifyTxSig ( xmr_swap . a_lock_refund_tx , xmr_swap . al_lock_refund_tx_sig , xmr_swap . pkal , 0 , xmr_swap . a_lock_tx_script , prevout_amount )
ensure ( v , ' Invalid coin A lock refund tx leader sig ' )
2020-11-15 17:02:46 +00:00
2023-03-08 22:53:54 +00:00
allowed_states = [ BidStates . BID_SENT , BidStates . BID_RECEIVED ]
if bid . was_sent and offer . was_sent :
2023-03-08 23:23:18 +00:00
allowed_states . append ( BidStates . BID_ACCEPTED ) # TODO: Split BID_ACCEPTED into received and sent
2023-03-08 22:53:54 +00:00
ensure ( bid . state in allowed_states , ' Invalid state for bid {} ' . format ( bid . state ) )
2020-11-15 17:02:46 +00:00
bid . setState ( BidStates . BID_RECEIVING_ACC )
self . saveBid ( bid . bid_id , bid , xmr_swap = xmr_swap )
2021-02-11 12:57:54 +00:00
if offer . coin_to != Coins . XMR :
with self . mxDB :
try :
session = scoped_session ( self . session_factory )
self . receiveXmrBidAccept ( bid , session )
session . commit ( )
finally :
session . close ( )
session . remove ( )
2020-11-15 17:02:46 +00:00
except Exception as ex :
if self . debug :
2021-12-16 08:44:10 +00:00
self . log . error ( traceback . format_exc ( ) )
2021-11-01 13:52:40 +00:00
self . setBidError ( bid . bid_id , bid , str ( ex ) , xmr_swap = xmr_swap )
2020-11-15 17:02:46 +00:00
2020-11-29 23:05:30 +00:00
def watchXmrSwap ( self , bid , offer , xmr_swap ) :
self . log . debug ( ' XMR swap in progress, bid %s ' , bid . bid_id . hex ( ) )
self . swaps_in_progress [ bid . bid_id ] = ( bid , offer )
2020-12-06 17:34:56 +00:00
coin_from = Coins ( offer . coin_from )
2021-11-04 21:49:52 +00:00
self . setLastHeightChecked ( coin_from , bid . chain_a_height_start )
2020-12-06 17:34:56 +00:00
self . addWatchedOutput ( coin_from , bid . bid_id , bid . xmr_a_lock_tx . txid . hex ( ) , bid . xmr_a_lock_tx . vout , TxTypes . XMR_SWAP_A_LOCK , SwapTypes . XMR_SWAP )
2021-11-01 13:52:40 +00:00
lock_refund_vout = self . ci ( coin_from ) . getLockRefundTxSwapOutput ( xmr_swap )
self . addWatchedOutput ( coin_from , bid . bid_id , xmr_swap . a_lock_refund_tx_id . hex ( ) , lock_refund_vout , TxTypes . XMR_SWAP_A_LOCK_REFUND , SwapTypes . XMR_SWAP )
2020-12-04 21:30:20 +00:00
bid . in_progress = 1
2020-11-29 23:05:30 +00:00
2020-11-15 17:02:46 +00:00
def sendXmrBidTxnSigsFtoL ( self , bid_id , session ) :
# F -> L: Sending MSG3L
self . log . debug ( ' Signing xmr bid lock txns %s ' , bid_id . hex ( ) )
2020-12-04 21:30:20 +00:00
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-15 17:02:46 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-15 17:02:46 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
2020-11-14 22:13:11 +00:00
2020-11-15 17:02:46 +00:00
try :
2021-12-19 06:59:35 +00:00
kaf = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KAF )
2020-11-15 17:02:46 +00:00
2021-11-01 13:52:40 +00:00
prevout_amount = ci_from . getLockRefundTxSwapOutputValue ( bid , xmr_swap )
xmr_swap . af_lock_refund_spend_tx_esig = ci_from . signTxOtVES ( kaf , xmr_swap . pkasl , xmr_swap . a_lock_refund_spend_tx , 0 , xmr_swap . a_lock_refund_tx_script , prevout_amount )
2022-11-14 19:47:07 +00:00
2021-11-01 13:52:40 +00:00
prevout_amount = ci_from . getLockTxSwapOutputValue ( bid , xmr_swap )
xmr_swap . af_lock_refund_tx_sig = ci_from . signTx ( kaf , xmr_swap . a_lock_refund_tx , 0 , xmr_swap . a_lock_tx_script , prevout_amount )
2020-11-15 17:02:46 +00:00
2022-12-05 15:04:23 +00:00
xmr_swap_1 . addLockRefundSigs ( self , xmr_swap , ci_from )
2020-11-27 17:52:26 +00:00
2020-11-15 17:02:46 +00:00
msg_buf = XmrBidLockTxSigsMessage (
bid_msg_id = bid_id ,
af_lock_refund_spend_tx_esig = xmr_swap . af_lock_refund_spend_tx_esig ,
af_lock_refund_tx_sig = xmr_swap . af_lock_refund_tx_sig
)
2020-11-21 13:16:27 +00:00
msg_bytes = msg_buf . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . XMR_BID_TXN_SIGS_FL ) + msg_bytes . hex ( )
2020-11-15 17:02:46 +00:00
2022-07-01 14:37:10 +00:00
msg_valid = self . getActiveBidMsgValidTime ( )
xmr_swap . coin_a_lock_tx_sigs_l_msg_id = self . sendSmsg ( bid . bid_addr , offer . addr_from , payload_hex , msg_valid )
2020-11-15 17:02:46 +00:00
2020-11-15 21:31:59 +00:00
self . log . info ( ' Sent XMR_BID_TXN_SIGS_FL %s ' , xmr_swap . coin_a_lock_tx_sigs_l_msg_id . hex ( ) )
2020-11-15 17:02:46 +00:00
2021-11-01 13:52:40 +00:00
a_lock_tx_id = ci_from . getTxid ( xmr_swap . a_lock_tx )
2020-11-29 23:05:30 +00:00
a_lock_tx_vout = ci_from . getTxOutputPos ( xmr_swap . a_lock_tx , xmr_swap . a_lock_tx_script )
2021-02-13 22:54:01 +00:00
self . log . debug ( ' Waiting for lock txn %s to %s chain for bid %s ' , a_lock_tx_id . hex ( ) , ci_from . coin_name ( ) , bid_id . hex ( ) )
2022-11-14 19:47:07 +00:00
if bid . xmr_a_lock_tx is None :
bid . xmr_a_lock_tx = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . XMR_SWAP_A_LOCK ,
txid = a_lock_tx_id ,
vout = a_lock_tx_vout ,
)
2020-11-21 13:16:27 +00:00
bid . xmr_a_lock_tx . setState ( TxStates . TX_NONE )
2022-07-01 14:37:10 +00:00
bid . setState ( BidStates . XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS )
2020-11-29 23:05:30 +00:00
self . watchXmrSwap ( bid , offer , xmr_swap )
2020-12-04 21:30:20 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
2020-11-15 17:02:46 +00:00
except Exception as ex :
if self . debug :
2021-12-16 08:44:10 +00:00
self . log . error ( traceback . format_exc ( ) )
2020-11-15 17:02:46 +00:00
def sendXmrBidCoinALockTx ( self , bid_id , session ) :
2022-07-01 14:37:10 +00:00
# Offerer/Leader. Send coin A lock tx
2020-11-15 17:02:46 +00:00
self . log . debug ( ' Sending coin A lock tx for xmr bid %s ' , bid_id . hex ( ) )
2020-12-04 21:30:20 +00:00
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-15 17:02:46 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-15 17:02:46 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
2021-12-19 06:59:35 +00:00
kal = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KAL )
2020-11-15 17:02:46 +00:00
2022-07-01 14:37:10 +00:00
# Prove leader can sign for kal, sent in MSG4F
xmr_swap . kal_sig = ci_from . signCompact ( kal , ' proof key owned for swap ' )
# Create Script lock spend tx
2022-11-07 20:31:10 +00:00
xmr_swap . a_lock_spend_tx = ci_from . createSCLockSpendTx (
2020-11-15 17:02:46 +00:00
xmr_swap . a_lock_tx , xmr_swap . a_lock_tx_script ,
xmr_swap . dest_af ,
2021-11-01 13:52:40 +00:00
xmr_offer . a_fee_rate , xmr_swap . vkbv )
2020-11-15 17:02:46 +00:00
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_spend_tx_id = ci_from . getTxid ( xmr_swap . a_lock_spend_tx )
prevout_amount = ci_from . getLockTxSwapOutputValue ( bid , xmr_swap )
xmr_swap . al_lock_spend_tx_esig = ci_from . signTxOtVES ( kal , xmr_swap . pkasf , xmr_swap . a_lock_spend_tx , 0 , xmr_swap . a_lock_tx_script , prevout_amount )
2020-11-15 17:02:46 +00:00
2022-07-01 14:37:10 +00:00
delay = random . randrange ( self . min_delay_event_short , self . max_delay_event_short )
self . log . info ( ' Sending lock spend tx message for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
self . createActionInSession ( delay , ActionTypes . SEND_XMR_SWAP_LOCK_SPEND_MSG , bid_id , session )
2020-11-15 17:02:46 +00:00
2020-11-15 21:31:59 +00:00
# publishalocktx
2023-03-08 22:53:54 +00:00
if bid . xmr_a_lock_tx and bid . xmr_a_lock_tx . state :
if bid . xmr_a_lock_tx . state > = TxStates . TX_SENT :
raise ValueError ( ' Lock tx has already been sent {} ' . format ( bid . xmr_a_lock_tx . txid . hex ( ) ) )
2020-11-15 21:31:59 +00:00
lock_tx_signed = ci_from . signTxWithWallet ( xmr_swap . a_lock_tx )
txid_hex = ci_from . publishTx ( lock_tx_signed )
2020-11-29 23:05:30 +00:00
vout_pos = ci_from . getTxOutputPos ( xmr_swap . a_lock_tx , xmr_swap . a_lock_tx_script )
2021-02-13 22:54:01 +00:00
self . log . debug ( ' Submitted lock txn %s to %s chain for bid %s ' , txid_hex , ci_from . coin_name ( ) , bid_id . hex ( ) )
2020-11-29 23:05:30 +00:00
2022-11-14 19:47:07 +00:00
if bid . xmr_a_lock_tx is None :
bid . xmr_a_lock_tx = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . XMR_SWAP_A_LOCK ,
txid = bytes . fromhex ( txid_hex ) ,
vout = vout_pos ,
)
2020-11-15 21:31:59 +00:00
bid . xmr_a_lock_tx . setState ( TxStates . TX_SENT )
2020-11-21 13:16:27 +00:00
bid . setState ( BidStates . XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX )
2020-11-29 23:05:30 +00:00
self . watchXmrSwap ( bid , offer , xmr_swap )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_A_PUBLISHED , ' ' , session )
2020-12-08 18:23:00 +00:00
2020-12-04 21:30:20 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap )
2020-11-21 13:16:27 +00:00
def sendXmrBidCoinBLockTx ( self , bid_id , session ) :
# Follower sending coin B lock tx
self . log . debug ( ' Sending coin B lock tx for xmr bid %s ' , bid_id . hex ( ) )
2020-12-04 21:30:20 +00:00
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-21 13:16:27 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2022-11-14 19:47:07 +00:00
ci_from = self . ci ( Coins ( offer . coin_from ) )
ci_to = self . ci ( Coins ( offer . coin_to ) )
2020-11-21 13:16:27 +00:00
2022-12-11 18:31:43 +00:00
if self . findTxB ( ci_to , xmr_swap , bid , session ) is True :
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
return
if bid . xmr_b_lock_tx :
self . log . warning ( ' Coin B lock tx {} exists for xmr bid {} ' . format ( bid . xmr_b_lock_tx . b_lock_tx_id , bid_id . hex ( ) ) )
return
2020-11-27 17:52:26 +00:00
if bid . debug_ind == DebugTypes . BID_STOP_AFTER_COIN_A_LOCK :
2021-02-03 14:01:27 +00:00
self . log . debug ( ' XMR bid %s : Stalling bid for testing: %d . ' , bid_id . hex ( ) , bid . debug_ind )
bid . setState ( BidStates . BID_STALLED_FOR_TEST )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . DEBUG_TWEAK_APPLIED , ' ind {} ' . format ( bid . debug_ind ) , session )
2022-07-19 22:24:14 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2020-11-27 17:52:26 +00:00
return
2022-12-02 11:58:26 +00:00
unlock_time = 0
2022-12-11 18:31:43 +00:00
if bid . debug_ind in ( DebugTypes . CREATE_INVALID_COIN_B_LOCK , DebugTypes . B_LOCK_TX_MISSED_SEND ) :
2020-11-27 17:52:26 +00:00
bid . amount_to - = int ( bid . amount_to * 0.1 )
2021-01-30 16:23:04 +00:00
self . log . debug ( ' XMR bid %s : Debug %d - Reducing lock b txn amount by 10 %% to %s . ' , bid_id . hex ( ) , bid . debug_ind , ci_to . format_amount ( bid . amount_to ) )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . DEBUG_TWEAK_APPLIED , ' ind {} ' . format ( bid . debug_ind ) , session )
2022-12-02 11:58:26 +00:00
if bid . debug_ind == DebugTypes . SEND_LOCKED_XMR :
unlock_time = 10000
self . log . debug ( ' XMR bid %s : Debug %d - Sending locked XMR. ' , bid_id . hex ( ) , bid . debug_ind )
self . logBidEvent ( bid . bid_id , EventLogTypes . DEBUG_TWEAK_APPLIED , ' ind {} ' . format ( bid . debug_ind ) , session )
2022-12-11 18:31:43 +00:00
2020-11-30 14:29:40 +00:00
try :
2022-12-20 20:19:01 +00:00
b_lock_tx_id = ci_to . publishBLockTx ( xmr_swap . vkbv , xmr_swap . pkbs , bid . amount_to , xmr_offer . b_fee_rate , unlock_time = unlock_time )
2022-12-11 18:31:43 +00:00
if bid . debug_ind == DebugTypes . B_LOCK_TX_MISSED_SEND :
self . log . debug ( ' XMR bid %s : Debug %d - Losing xmr lock tx %s . ' , bid_id . hex ( ) , bid . debug_ind , b_lock_tx_id . hex ( ) )
self . logBidEvent ( bid . bid_id , EventLogTypes . DEBUG_TWEAK_APPLIED , ' ind {} ' . format ( bid . debug_ind ) , session )
raise TemporaryError ( ' Fail for debug event ' )
2020-11-30 14:29:40 +00:00
except Exception as ex :
2022-12-08 01:22:18 +00:00
if self . debug :
self . log . error ( traceback . format_exc ( ) )
2020-12-02 11:24:52 +00:00
error_msg = ' publishBLockTx failed for bid {} with error {} ' . format ( bid_id . hex ( ) , str ( ex ) )
2020-12-08 18:23:00 +00:00
num_retries = self . countBidEvents ( bid , EventLogTypes . FAILED_TX_B_LOCK_PUBLISH , session )
2020-12-02 11:24:52 +00:00
if num_retries > 0 :
error_msg + = ' , retry no. {} ' . format ( num_retries )
self . log . error ( error_msg )
2021-11-01 13:52:40 +00:00
if num_retries < 5 and ( ci_to . is_transient_error ( ex ) or self . is_transient_error ( ex ) ) :
2020-12-02 11:24:52 +00:00
delay = random . randrange ( self . min_delay_retry , self . max_delay_retry )
2020-11-30 14:29:40 +00:00
self . log . info ( ' Retrying sending xmr swap chain B lock tx for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , ActionTypes . SEND_XMR_SWAP_LOCK_TX_B , bid_id , session )
2020-11-30 14:29:40 +00:00
else :
self . setBidError ( bid_id , bid , ' publishBLockTx failed: ' + str ( ex ) , save_bid = False )
2020-12-02 11:24:52 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2021-11-01 13:52:40 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . FAILED_TX_B_LOCK_PUBLISH , str ( ex ) , session )
2020-11-30 14:29:40 +00:00
return
2020-11-21 13:16:27 +00:00
2021-02-13 22:54:01 +00:00
self . log . debug ( ' Submitted lock txn %s to %s chain for bid %s ' , b_lock_tx_id . hex ( ) , ci_to . coin_name ( ) , bid_id . hex ( ) )
2020-11-21 13:16:27 +00:00
bid . xmr_b_lock_tx = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . XMR_SWAP_B_LOCK ,
txid = b_lock_tx_id ,
)
2022-12-08 01:22:18 +00:00
xmr_swap . b_lock_tx_id = b_lock_tx_id
2020-11-21 13:16:27 +00:00
bid . xmr_b_lock_tx . setState ( TxStates . TX_NONE )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_B_PUBLISHED , ' ' , session )
2020-11-21 13:16:27 +00:00
2020-12-02 11:24:52 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2020-11-21 13:16:27 +00:00
2020-12-10 22:43:36 +00:00
def sendXmrBidLockRelease ( self , bid_id , session ) :
2020-11-21 13:16:27 +00:00
# Leader sending lock tx a release secret (MSG5F)
self . log . debug ( ' Sending bid secret for xmr bid %s ' , bid_id . hex ( ) )
2020-12-04 21:30:20 +00:00
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-21 13:16:27 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-21 13:16:27 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
2020-12-10 22:43:36 +00:00
msg_buf = XmrBidLockReleaseMessage (
2020-11-21 13:16:27 +00:00
bid_msg_id = bid_id ,
2020-12-10 22:43:36 +00:00
al_lock_spend_tx_esig = xmr_swap . al_lock_spend_tx_esig )
2020-11-21 13:16:27 +00:00
msg_bytes = msg_buf . SerializeToString ( )
2020-12-10 22:43:36 +00:00
payload_hex = str . format ( ' {:02x} ' , MessageTypes . XMR_BID_LOCK_RELEASE_LF ) + msg_bytes . hex ( )
2020-11-21 13:16:27 +00:00
2022-07-01 14:37:10 +00:00
msg_valid = self . getActiveBidMsgValidTime ( )
xmr_swap . coin_a_lock_release_msg_id = self . sendSmsg ( offer . addr_from , bid . bid_addr , payload_hex , msg_valid )
2020-11-21 13:16:27 +00:00
2020-12-10 22:43:36 +00:00
bid . setState ( BidStates . XMR_SWAP_LOCK_RELEASED )
2020-12-02 11:24:52 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2020-11-21 13:16:27 +00:00
def redeemXmrBidCoinALockTx ( self , bid_id , session ) :
# Follower redeeming A lock tx
self . log . debug ( ' Redeeming coin A lock tx for xmr bid %s ' , bid_id . hex ( ) )
2020-12-04 21:30:20 +00:00
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-21 13:16:27 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-21 13:16:27 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
2023-05-11 21:45:06 +00:00
for_ed25519 = True if ci_to . curve_type ( ) == Curves . ed25519 else False
2021-12-19 06:59:35 +00:00
kbsf = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KBSF , for_ed25519 )
kaf = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KAF )
2020-11-21 13:16:27 +00:00
al_lock_spend_sig = ci_from . decryptOtVES ( kbsf , xmr_swap . al_lock_spend_tx_esig )
2021-11-01 13:52:40 +00:00
prevout_amount = ci_from . getLockTxSwapOutputValue ( bid , xmr_swap )
v = ci_from . verifyTxSig ( xmr_swap . a_lock_spend_tx , al_lock_spend_sig , xmr_swap . pkal , 0 , xmr_swap . a_lock_tx_script , prevout_amount )
2021-10-21 22:47:04 +00:00
ensure ( v , ' Invalid coin A lock tx spend tx leader sig ' )
2020-11-21 13:16:27 +00:00
2021-11-01 13:52:40 +00:00
af_lock_spend_sig = ci_from . signTx ( kaf , xmr_swap . a_lock_spend_tx , 0 , xmr_swap . a_lock_tx_script , prevout_amount )
v = ci_from . verifyTxSig ( xmr_swap . a_lock_spend_tx , af_lock_spend_sig , xmr_swap . pkaf , 0 , xmr_swap . a_lock_tx_script , prevout_amount )
2021-10-21 22:47:04 +00:00
ensure ( v , ' Invalid coin A lock tx spend tx follower sig ' )
2020-11-21 13:16:27 +00:00
witness_stack = [
b ' ' ,
al_lock_spend_sig ,
af_lock_spend_sig ,
xmr_swap . a_lock_tx_script ,
]
xmr_swap . a_lock_spend_tx = ci_from . setTxSignature ( xmr_swap . a_lock_spend_tx , witness_stack )
txid = bytes . fromhex ( ci_from . publishTx ( xmr_swap . a_lock_spend_tx ) )
2021-02-13 22:54:01 +00:00
self . log . debug ( ' Submitted lock spend txn %s to %s chain for bid %s ' , txid . hex ( ) , ci_from . coin_name ( ) , bid_id . hex ( ) )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_A_SPEND_TX_PUBLISHED , ' ' , session )
2020-11-21 13:16:27 +00:00
bid . xmr_a_lock_spend_tx = SwapTx (
bid_id = bid_id ,
tx_type = TxTypes . XMR_SWAP_A_LOCK_SPEND ,
txid = txid ,
)
bid . xmr_a_lock_spend_tx . setState ( TxStates . TX_NONE )
2020-12-02 11:24:52 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2020-11-21 13:16:27 +00:00
def redeemXmrBidCoinBLockTx ( self , bid_id , session ) :
# Leader redeeming B lock tx
self . log . debug ( ' Redeeming coin B lock tx for xmr bid %s ' , bid_id . hex ( ) )
2020-12-04 21:30:20 +00:00
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-21 13:16:27 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-21 13:16:27 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
2021-11-12 14:36:10 +00:00
try :
chain_height = ci_to . getChainHeight ( )
lock_tx_depth = ( chain_height - bid . xmr_b_lock_tx . chain_height ) + 1
if lock_tx_depth < ci_to . depth_spendable ( ) :
raise TemporaryError ( f ' Chain B lock tx depth { lock_tx_depth } < required for spending. ' )
2020-11-21 13:16:27 +00:00
2021-11-12 14:36:10 +00:00
# Extract the leader's decrypted signature and use it to recover the follower's privatekey
xmr_swap . al_lock_spend_tx_sig = ci_from . extractLeaderSig ( xmr_swap . a_lock_spend_tx )
2020-11-21 13:16:27 +00:00
2021-11-12 14:36:10 +00:00
kbsf = ci_from . recoverEncKey ( xmr_swap . al_lock_spend_tx_esig , xmr_swap . al_lock_spend_tx_sig , xmr_swap . pkasf )
2022-07-31 18:01:49 +00:00
assert ( kbsf is not None )
2021-11-12 14:36:10 +00:00
2023-05-11 21:45:06 +00:00
for_ed25519 = True if ci_to . curve_type ( ) == Curves . ed25519 else False
2021-12-19 06:59:35 +00:00
kbsl = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KBSL , for_ed25519 )
2021-11-12 14:36:10 +00:00
vkbs = ci_to . sumKeys ( kbsl , kbsf )
2020-11-21 13:16:27 +00:00
2021-10-23 14:00:32 +00:00
if coin_to == Coins . XMR :
address_to = self . getCachedMainWalletAddress ( ci_to )
2023-02-16 20:57:55 +00:00
elif coin_to in ( Coins . PART_BLIND , Coins . PART_ANON ) :
2021-10-23 14:00:32 +00:00
address_to = self . getCachedStealthAddressForCoin ( coin_to )
2022-12-08 01:22:18 +00:00
else :
address_to = self . getReceiveAddressFromPool ( coin_to , bid_id , TxTypes . XMR_SWAP_B_LOCK_SPEND )
2021-11-04 21:49:52 +00:00
txid = ci_to . spendBLockTx ( xmr_swap . b_lock_tx_id , address_to , xmr_swap . vkbv , vkbs , bid . amount_to , xmr_offer . b_fee_rate , bid . chain_b_height_start )
2020-12-06 17:34:56 +00:00
self . log . debug ( ' Submitted lock B spend txn %s to %s chain for bid %s ' , txid . hex ( ) , ci_to . coin_name ( ) , bid_id . hex ( ) )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_B_SPEND_TX_PUBLISHED , ' ' , session )
2020-12-06 17:34:56 +00:00
except Exception as ex :
error_msg = ' spendBLockTx failed for bid {} with error {} ' . format ( bid_id . hex ( ) , str ( ex ) )
num_retries = self . countBidEvents ( bid , EventLogTypes . FAILED_TX_B_SPEND , session )
if num_retries > 0 :
error_msg + = ' , retry no. {} ' . format ( num_retries )
self . log . error ( error_msg )
2021-11-01 13:52:40 +00:00
if num_retries < 100 and ( ci_to . is_transient_error ( ex ) or self . is_transient_error ( ex ) ) :
2020-12-06 17:34:56 +00:00
delay = random . randrange ( self . min_delay_retry , self . max_delay_retry )
self . log . info ( ' Retrying sending xmr swap chain B spend tx for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , ActionTypes . REDEEM_XMR_SWAP_LOCK_TX_B , bid_id , session )
2020-12-06 17:34:56 +00:00
else :
self . setBidError ( bid_id , bid , ' spendBLockTx failed: ' + str ( ex ) , save_bid = False )
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2021-11-01 13:52:40 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . FAILED_TX_B_SPEND , str ( ex ) , session )
2020-12-06 17:34:56 +00:00
return
2020-11-21 13:16:27 +00:00
bid . xmr_b_lock_tx . spend_txid = txid
2020-12-01 20:45:03 +00:00
bid . setState ( BidStates . XMR_SWAP_NOSCRIPT_TX_REDEEMED )
2020-11-30 14:29:40 +00:00
# TODO: Why does using bid.txns error here?
2020-12-02 11:24:52 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2020-11-15 17:02:46 +00:00
2020-11-27 17:52:26 +00:00
def recoverXmrBidCoinBLockTx ( self , bid_id , session ) :
# Follower recovering B lock tx
self . log . debug ( ' Recovering coin B lock tx for xmr bid %s ' , bid_id . hex ( ) )
2020-12-04 21:30:20 +00:00
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-27 17:52:26 +00:00
2020-12-04 21:30:20 +00:00
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2020-11-27 17:52:26 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
# Extract the follower's decrypted signature and use it to recover the leader's privatekey
af_lock_refund_spend_tx_sig = ci_from . extractFollowerSig ( xmr_swap . a_lock_refund_spend_tx )
kbsl = ci_from . recoverEncKey ( xmr_swap . af_lock_refund_spend_tx_esig , af_lock_refund_spend_tx_sig , xmr_swap . pkasl )
2022-07-31 18:01:49 +00:00
assert ( kbsl is not None )
2020-11-27 17:52:26 +00:00
2023-05-11 21:45:06 +00:00
for_ed25519 = True if ci_to . curve_type ( ) == Curves . ed25519 else False
2021-12-19 06:59:35 +00:00
kbsf = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KBSF , for_ed25519 )
2020-11-27 17:52:26 +00:00
vkbs = ci_to . sumKeys ( kbsl , kbsf )
2021-01-31 12:26:32 +00:00
try :
2022-01-01 23:42:49 +00:00
if offer . coin_to == Coins . XMR :
address_to = self . getCachedMainWalletAddress ( ci_to )
2022-12-08 01:22:18 +00:00
elif coin_to == Coins . PART_BLIND :
2022-01-01 23:42:49 +00:00
address_to = self . getCachedStealthAddressForCoin ( coin_to )
2022-12-08 01:22:18 +00:00
else :
address_to = self . getReceiveAddressFromPool ( coin_to , bid_id , TxTypes . XMR_SWAP_B_LOCK_REFUND )
2021-11-04 21:49:52 +00:00
txid = ci_to . spendBLockTx ( xmr_swap . b_lock_tx_id , address_to , xmr_swap . vkbv , vkbs , bid . amount_to , xmr_offer . b_fee_rate , bid . chain_b_height_start )
2021-01-31 12:26:32 +00:00
self . log . debug ( ' Submitted lock B refund txn %s to %s chain for bid %s ' , txid . hex ( ) , ci_to . coin_name ( ) , bid_id . hex ( ) )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . LOCK_TX_B_REFUND_TX_PUBLISHED , ' ' , session )
2021-01-31 12:26:32 +00:00
except Exception as ex :
# TODO: Make min-conf 10?
error_msg = ' spendBLockTx refund failed for bid {} with error {} ' . format ( bid_id . hex ( ) , str ( ex ) )
num_retries = self . countBidEvents ( bid , EventLogTypes . FAILED_TX_B_REFUND , session )
if num_retries > 0 :
error_msg + = ' , retry no. {} ' . format ( num_retries )
self . log . error ( error_msg )
str_error = str ( ex )
2021-11-01 13:52:40 +00:00
if num_retries < 100 and ( ci_to . is_transient_error ( ex ) or self . is_transient_error ( ex ) ) :
2021-01-31 12:26:32 +00:00
delay = random . randrange ( self . min_delay_retry , self . max_delay_retry )
self . log . info ( ' Retrying sending xmr swap chain B refund tx for bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createActionInSession ( delay , ActionTypes . RECOVER_XMR_SWAP_LOCK_TX_B , bid_id , session )
2021-01-31 12:26:32 +00:00
else :
self . setBidError ( bid_id , bid , ' spendBLockTx for refund failed: ' + str ( ex ) , save_bid = False )
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2021-09-02 20:42:26 +00:00
self . logBidEvent ( bid . bid_id , EventLogTypes . FAILED_TX_B_REFUND , str_error , session )
2021-01-31 12:26:32 +00:00
return
2020-11-27 17:52:26 +00:00
bid . xmr_b_lock_tx . spend_txid = txid
bid . setState ( BidStates . XMR_SWAP_NOSCRIPT_TX_RECOVERED )
2020-12-02 11:24:52 +00:00
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2020-11-27 17:52:26 +00:00
2022-07-01 14:37:10 +00:00
def sendXmrBidCoinALockSpendTxMsg ( self , bid_id , session ) :
# Send MSG4F L -> F
self . log . debug ( ' Sending coin A lock spend tx msg for xmr bid %s ' , bid_id . hex ( ) )
bid , xmr_swap = self . getXmrBidFromSession ( session , bid_id )
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
offer , xmr_offer = self . getXmrOfferFromSession ( session , bid . offer_id , sent = False )
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ci_from = self . ci ( offer . coin_from )
msg_buf = XmrBidLockSpendTxMessage (
bid_msg_id = bid_id ,
a_lock_spend_tx = xmr_swap . a_lock_spend_tx ,
kal_sig = xmr_swap . kal_sig )
msg_bytes = msg_buf . SerializeToString ( )
payload_hex = str . format ( ' {:02x} ' , MessageTypes . XMR_BID_LOCK_SPEND_TX_LF ) + msg_bytes . hex ( )
msg_valid = self . getActiveBidMsgValidTime ( )
xmr_swap . coin_a_lock_refund_spend_tx_msg_id = self . sendSmsg ( offer . addr_from , bid . bid_addr , payload_hex , msg_valid )
bid . setState ( BidStates . XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX )
self . saveBidInSession ( bid_id , bid , session , xmr_swap , save_in_progress = offer )
2020-11-15 17:02:46 +00:00
def processXmrBidCoinALockSigs ( self , msg ) :
2020-11-15 21:31:59 +00:00
# Leader processing MSG3L
2020-11-15 17:02:46 +00:00
self . log . debug ( ' Processing xmr coin a follower lock sigs msg %s ' , msg [ ' msgid ' ] )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-15 17:02:46 +00:00
msg_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
msg_data = XmrBidLockTxSigsMessage ( )
msg_data . ParseFromString ( msg_bytes )
2021-10-21 22:47:04 +00:00
ensure ( len ( msg_data . bid_msg_id ) == 28 , ' Bad bid_msg_id length ' )
2020-11-15 17:02:46 +00:00
bid_id = msg_data . bid_msg_id
bid , xmr_swap = self . getXmrBid ( bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-15 17:02:46 +00:00
offer , xmr_offer = self . getXmrOffer ( bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2023-03-08 23:23:18 +00:00
ensure ( msg [ ' to ' ] == offer . addr_from , ' Received on incorrect address ' )
ensure ( msg [ ' from ' ] == bid . bid_addr , ' Sent from incorrect address ' )
2020-11-15 17:02:46 +00:00
coin_from = Coins ( offer . coin_from )
coin_to = Coins ( offer . coin_to )
ci_from = self . ci ( coin_from )
ci_to = self . ci ( coin_to )
try :
2023-03-08 22:53:54 +00:00
allowed_states = [ BidStates . BID_ACCEPTED , ]
if bid . was_sent and offer . was_sent :
allowed_states . append ( BidStates . XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS )
ensure ( bid . state in allowed_states , ' Invalid state for bid {} ' . format ( bid . state ) )
2020-11-15 17:02:46 +00:00
xmr_swap . af_lock_refund_spend_tx_esig = msg_data . af_lock_refund_spend_tx_esig
xmr_swap . af_lock_refund_tx_sig = msg_data . af_lock_refund_tx_sig
2023-05-11 21:45:06 +00:00
for_ed25519 = True if ci_to . curve_type ( ) == Curves . ed25519 else False
2021-12-19 06:59:35 +00:00
kbsl = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KBSL , for_ed25519 )
kal = self . getPathKey ( coin_from , coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KAL )
2020-11-27 17:52:26 +00:00
2020-11-15 17:02:46 +00:00
xmr_swap . af_lock_refund_spend_tx_sig = ci_from . decryptOtVES ( kbsl , xmr_swap . af_lock_refund_spend_tx_esig )
2021-11-01 13:52:40 +00:00
prevout_amount = ci_from . getLockRefundTxSwapOutputValue ( bid , xmr_swap )
al_lock_refund_spend_tx_sig = ci_from . signTx ( kal , xmr_swap . a_lock_refund_spend_tx , 0 , xmr_swap . a_lock_refund_tx_script , prevout_amount )
2020-11-27 17:52:26 +00:00
self . log . debug ( ' Setting lock refund spend tx sigs ' )
witness_stack = [
b ' ' ,
al_lock_refund_spend_tx_sig ,
xmr_swap . af_lock_refund_spend_tx_sig ,
bytes ( ( 1 , ) ) ,
xmr_swap . a_lock_refund_tx_script ,
]
signed_tx = ci_from . setTxSignature ( xmr_swap . a_lock_refund_spend_tx , witness_stack )
2021-10-21 22:47:04 +00:00
ensure ( signed_tx , ' setTxSignature failed ' )
2020-11-27 17:52:26 +00:00
xmr_swap . a_lock_refund_spend_tx = signed_tx
2020-11-15 17:02:46 +00:00
2021-11-01 13:52:40 +00:00
v = ci_from . verifyTxSig ( xmr_swap . a_lock_refund_spend_tx , xmr_swap . af_lock_refund_spend_tx_sig , xmr_swap . pkaf , 0 , xmr_swap . a_lock_refund_tx_script , prevout_amount )
2021-10-21 22:47:04 +00:00
ensure ( v , ' Invalid signature for lock refund spend txn ' )
2022-12-05 15:04:23 +00:00
xmr_swap_1 . addLockRefundSigs ( self , xmr_swap , ci_from )
2020-11-15 17:02:46 +00:00
2020-12-02 11:24:52 +00:00
delay = random . randrange ( self . min_delay_event , self . max_delay_event )
2020-11-15 17:02:46 +00:00
self . log . info ( ' Sending coin A lock tx for xmr bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createAction ( delay , ActionTypes . SEND_XMR_SWAP_LOCK_TX_A , bid_id )
2020-11-15 17:02:46 +00:00
2022-07-01 14:37:10 +00:00
bid . setState ( BidStates . XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS )
2020-11-15 17:02:46 +00:00
self . saveBid ( bid_id , bid , xmr_swap = xmr_swap )
except Exception as ex :
if self . debug :
2021-12-16 08:44:10 +00:00
self . log . error ( traceback . format_exc ( ) )
2020-11-15 17:02:46 +00:00
self . setBidError ( bid_id , bid , str ( ex ) )
2020-11-14 22:13:11 +00:00
2020-11-21 13:16:27 +00:00
def processXmrBidLockSpendTx ( self , msg ) :
# Follower receiving MSG4F
self . log . debug ( ' Processing xmr bid lock spend tx msg %s ' , msg [ ' msgid ' ] )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-21 13:16:27 +00:00
msg_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
msg_data = XmrBidLockSpendTxMessage ( )
msg_data . ParseFromString ( msg_bytes )
2021-10-21 22:47:04 +00:00
ensure ( len ( msg_data . bid_msg_id ) == 28 , ' Bad bid_msg_id length ' )
2020-11-21 13:16:27 +00:00
bid_id = msg_data . bid_msg_id
bid , xmr_swap = self . getXmrBid ( bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-21 13:16:27 +00:00
offer , xmr_offer = self . getXmrOffer ( bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2023-03-08 23:23:18 +00:00
ensure ( msg [ ' to ' ] == bid . bid_addr , ' Received on incorrect address ' )
ensure ( msg [ ' from ' ] == offer . addr_from , ' Sent from incorrect address ' )
2020-12-04 21:30:20 +00:00
ci_from = self . ci ( Coins ( offer . coin_from ) )
ci_to = self . ci ( Coins ( offer . coin_to ) )
2020-11-21 13:16:27 +00:00
try :
xmr_swap . a_lock_spend_tx = msg_data . a_lock_spend_tx
2021-11-01 13:52:40 +00:00
xmr_swap . a_lock_spend_tx_id = ci_from . getTxid ( xmr_swap . a_lock_spend_tx )
2020-12-10 22:43:36 +00:00
xmr_swap . kal_sig = msg_data . kal_sig
2020-11-21 13:16:27 +00:00
2022-11-07 20:31:10 +00:00
ci_from . verifySCLockSpendTx (
2020-11-21 13:16:27 +00:00
xmr_swap . a_lock_spend_tx ,
xmr_swap . a_lock_tx , xmr_swap . a_lock_tx_script ,
2021-11-01 13:52:40 +00:00
xmr_swap . dest_af , xmr_offer . a_fee_rate , xmr_swap . vkbv )
2020-11-21 13:16:27 +00:00
2022-11-07 20:31:10 +00:00
ci_from . verifyCompactSig ( xmr_swap . pkal , ' proof key owned for swap ' , xmr_swap . kal_sig )
2020-11-21 13:16:27 +00:00
2022-11-14 19:47:07 +00:00
if bid . state == BidStates . XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS :
bid . setState ( BidStates . XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX )
bid . setState ( BidStates . XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX )
else :
self . log . warning ( ' processXmrBidLockSpendTx bid {} unexpected state {} ' . format ( bid_id . hex ( ) , bid . state ) )
2020-11-21 13:16:27 +00:00
self . saveBid ( bid_id , bid , xmr_swap = xmr_swap )
except Exception as ex :
if self . debug :
2021-12-16 08:44:10 +00:00
self . log . error ( traceback . format_exc ( ) )
2020-11-21 13:16:27 +00:00
self . setBidError ( bid_id , bid , str ( ex ) )
# Update copy of bid in swaps_in_progress
self . swaps_in_progress [ bid_id ] = ( bid , offer )
2020-11-14 22:13:11 +00:00
def processXmrSplitMessage ( self , msg ) :
self . log . debug ( ' Processing xmr split msg %s ' , msg [ ' msgid ' ] )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-14 22:13:11 +00:00
msg_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
msg_data = XmrSplitMessage ( )
msg_data . ParseFromString ( msg_bytes )
# Validate data
2021-10-21 22:47:04 +00:00
ensure ( len ( msg_data . msg_id ) == 28 , ' Bad msg_id length ' )
2020-11-14 22:13:11 +00:00
if msg_data . msg_type == XmrSplitMsgTypes . BID or msg_data . msg_type == XmrSplitMsgTypes . BID_ACCEPT :
2021-01-11 21:48:46 +00:00
try :
session = scoped_session ( self . session_factory )
q = session . execute ( ' SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x \' {} \' AND msg_type = {} AND msg_sequence = {} ' . format ( msg_data . msg_id . hex ( ) , msg_data . msg_type , msg_data . sequence ) ) . first ( )
num_exists = q [ 0 ]
if num_exists > 0 :
self . log . warning ( ' Ignoring duplicate xmr_split_data entry: ( {} , {} , {} ) ' . format ( msg_data . msg_id . hex ( ) , msg_data . msg_type , msg_data . sequence ) )
return
dbr = XmrSplitData ( )
2023-03-08 23:23:18 +00:00
dbr . addr_from = msg [ ' from ' ]
dbr . addr_to = msg [ ' to ' ]
2021-01-11 21:48:46 +00:00
dbr . bid_id = msg_data . msg_id
dbr . msg_type = msg_data . msg_type
dbr . msg_sequence = msg_data . sequence
dbr . dleag = msg_data . dleag
dbr . created_at = now
session . add ( dbr )
session . commit ( )
finally :
session . close ( )
session . remove ( )
2020-11-14 22:13:11 +00:00
2020-12-10 22:43:36 +00:00
def processXmrLockReleaseMessage ( self , msg ) :
2020-11-21 13:16:27 +00:00
self . log . debug ( ' Processing xmr secret msg %s ' , msg [ ' msgid ' ] )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-21 13:16:27 +00:00
msg_bytes = bytes . fromhex ( msg [ ' hex ' ] [ 2 : - 2 ] )
2020-12-10 22:43:36 +00:00
msg_data = XmrBidLockReleaseMessage ( )
2020-11-21 13:16:27 +00:00
msg_data . ParseFromString ( msg_bytes )
# Validate data
2021-10-21 22:47:04 +00:00
ensure ( len ( msg_data . bid_msg_id ) == 28 , ' Bad msg_id length ' )
2020-11-21 13:16:27 +00:00
bid_id = msg_data . bid_msg_id
bid , xmr_swap = self . getXmrBid ( bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found: {} . ' . format ( bid_id . hex ( ) ) )
ensure ( xmr_swap , ' XMR swap not found: {} . ' . format ( bid_id . hex ( ) ) )
2020-11-21 13:16:27 +00:00
offer , xmr_offer = self . getXmrOffer ( bid . offer_id , sent = False )
2021-10-21 22:47:04 +00:00
ensure ( offer , ' Offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
ensure ( xmr_offer , ' XMR offer not found: {} . ' . format ( bid . offer_id . hex ( ) ) )
2023-03-08 23:23:18 +00:00
ensure ( msg [ ' to ' ] == bid . bid_addr , ' Received on incorrect address ' )
ensure ( msg [ ' from ' ] == offer . addr_from , ' Sent from incorrect address ' )
2020-12-10 22:43:36 +00:00
ci_from = self . ci ( Coins ( offer . coin_from ) )
2020-11-21 13:16:27 +00:00
2020-12-10 22:43:36 +00:00
xmr_swap . al_lock_spend_tx_esig = msg_data . al_lock_spend_tx_esig
try :
2021-11-01 13:52:40 +00:00
prevout_amount = ci_from . getLockTxSwapOutputValue ( bid , xmr_swap )
2020-12-10 22:43:36 +00:00
v = ci_from . verifyTxOtVES (
xmr_swap . a_lock_spend_tx , xmr_swap . al_lock_spend_tx_esig ,
2021-11-01 13:52:40 +00:00
xmr_swap . pkal , xmr_swap . pkasf , 0 , xmr_swap . a_lock_tx_script , prevout_amount )
ensure ( v , ' verifyTxOtVES failed for chain a lock tx leader esig ' )
2020-12-12 12:45:30 +00:00
except Exception as ex :
2020-12-10 22:43:36 +00:00
if self . debug :
2021-12-16 08:44:10 +00:00
self . log . error ( traceback . format_exc ( ) )
2020-12-10 22:43:36 +00:00
self . setBidError ( bid_id , bid , str ( ex ) )
self . swaps_in_progress [ bid_id ] = ( bid , offer )
return
2020-11-21 13:16:27 +00:00
2020-12-02 11:24:52 +00:00
delay = random . randrange ( self . min_delay_event , self . max_delay_event )
2020-11-21 13:16:27 +00:00
self . log . info ( ' Redeeming coin A lock tx for xmr bid %s in %d seconds ' , bid_id . hex ( ) , delay )
2022-06-06 21:03:31 +00:00
self . createAction ( delay , ActionTypes . REDEEM_XMR_SWAP_LOCK_TX_A , bid_id )
2020-11-21 13:16:27 +00:00
2020-12-10 22:43:36 +00:00
bid . setState ( BidStates . XMR_SWAP_LOCK_RELEASED )
2020-11-21 13:16:27 +00:00
self . saveBid ( bid_id , bid , xmr_swap = xmr_swap )
self . swaps_in_progress [ bid_id ] = ( bid , offer )
2019-07-17 15:12:06 +00:00
def processMsg ( self , msg ) :
self . mxDB . acquire ( )
try :
msg_type = int ( msg [ ' hex ' ] [ : 2 ] , 16 )
rv = None
if msg_type == MessageTypes . OFFER :
self . processOffer ( msg )
2023-02-16 20:57:55 +00:00
elif msg_type == MessageTypes . OFFER_REVOKE :
self . processOfferRevoke ( msg )
# TODO: When changing from wallet keys (encrypted/locked) handle swap messages while locked
2019-07-17 15:12:06 +00:00
elif msg_type == MessageTypes . BID :
self . processBid ( msg )
elif msg_type == MessageTypes . BID_ACCEPT :
self . processBidAccept ( msg )
2020-11-21 13:16:27 +00:00
elif msg_type == MessageTypes . XMR_BID_FL :
2020-11-14 22:13:11 +00:00
self . processXmrBid ( msg )
2020-11-21 13:16:27 +00:00
elif msg_type == MessageTypes . XMR_BID_ACCEPT_LF :
2020-11-14 22:13:11 +00:00
self . processXmrBidAccept ( msg )
2020-11-15 17:02:46 +00:00
elif msg_type == MessageTypes . XMR_BID_TXN_SIGS_FL :
self . processXmrBidCoinALockSigs ( msg )
2020-11-21 13:16:27 +00:00
elif msg_type == MessageTypes . XMR_BID_LOCK_SPEND_TX_LF :
self . processXmrBidLockSpendTx ( msg )
2020-11-14 22:13:11 +00:00
elif msg_type == MessageTypes . XMR_BID_SPLIT :
self . processXmrSplitMessage ( msg )
2020-12-10 22:43:36 +00:00
elif msg_type == MessageTypes . XMR_BID_LOCK_RELEASE_LF :
self . processXmrLockReleaseMessage ( msg )
2019-07-17 15:12:06 +00:00
2022-10-12 20:37:35 +00:00
except InactiveCoin as ex :
self . log . info ( ' Ignoring message involving inactive coin {} , type {} ' . format ( Coins ( ex . coinid ) . name , MessageTypes ( msg_type ) . name ) )
2019-07-17 15:12:06 +00:00
except Exception as ex :
self . log . error ( ' processMsg %s ' , str ( ex ) )
2020-11-15 17:02:46 +00:00
if self . debug :
2021-12-16 08:44:10 +00:00
self . log . error ( traceback . format_exc ( ) )
2022-06-06 21:03:31 +00:00
self . logEvent ( Concepts . NETWORK_MESSAGE ,
bytes . fromhex ( msg [ ' msgid ' ] ) ,
EventLogTypes . ERROR ,
str ( ex ) ,
None )
2019-07-17 15:12:06 +00:00
finally :
self . mxDB . release ( )
def processZmqSmsg ( self ) :
message = self . zmqSubscriber . recv ( )
clear = self . zmqSubscriber . recv ( )
if message [ 0 ] == 3 : # Paid smsg
2019-11-09 21:09:22 +00:00
return # TODO: Switch to paid?
2019-07-17 15:12:06 +00:00
msg_id = message [ 2 : ]
options = { ' encoding ' : ' hex ' , ' setread ' : True }
2020-12-06 17:34:56 +00:00
num_tries = 5
for i in range ( num_tries + 1 ) :
try :
msg = self . callrpc ( ' smsg ' , [ msg_id . hex ( ) , options ] )
break
except Exception as e :
if ' Unknown message id ' in str ( e ) and i < num_tries :
time . sleep ( 1 )
else :
raise e
2019-07-17 15:12:06 +00:00
self . processMsg ( msg )
def update ( self ) :
try :
2023-02-16 20:57:55 +00:00
if self . _read_zmq_queue :
message = self . zmqSubscriber . recv ( flags = zmq . NOBLOCK )
if message == b ' smsg ' :
self . processZmqSmsg ( )
2019-07-27 17:26:06 +00:00
except zmq . Again as ex :
2019-07-17 15:12:06 +00:00
pass
2019-07-27 17:26:06 +00:00
except Exception as ex :
2022-10-26 15:47:30 +00:00
self . logException ( f ' smsg zmq { ex } ' )
2019-07-17 15:12:06 +00:00
self . mxDB . acquire ( )
try :
# TODO: Wait for blocks / txns, would need to check multiple coins
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2020-11-14 22:13:11 +00:00
if now - self . _last_checked_progress > = self . check_progress_seconds :
2019-07-17 15:12:06 +00:00
to_remove = [ ]
for bid_id , v in self . swaps_in_progress . items ( ) :
2019-07-23 22:33:27 +00:00
try :
if self . checkBidState ( bid_id , v [ 0 ] , v [ 1 ] ) is True :
2020-11-29 11:46:00 +00:00
to_remove . append ( ( bid_id , v [ 0 ] , v [ 1 ] ) )
2019-07-23 22:33:27 +00:00
except Exception as ex :
2020-11-15 17:02:46 +00:00
if self . debug :
2021-12-16 08:44:10 +00:00
self . log . error ( ' checkBidState %s ' , traceback . format_exc ( ) )
2021-09-02 20:42:26 +00:00
if self . is_transient_error ( ex ) :
self . log . warning ( ' checkBidState %s %s ' , bid_id . hex ( ) , str ( ex ) )
self . logBidEvent ( bid_id , EventLogTypes . SYSTEM_WARNING , ' No connection to daemon ' , session = None )
else :
self . log . error ( ' checkBidState %s %s ' , bid_id . hex ( ) , str ( ex ) )
self . setBidError ( bid_id , v [ 0 ] , str ( ex ) )
2019-07-23 22:33:27 +00:00
2020-11-29 11:46:00 +00:00
for bid_id , bid , offer in to_remove :
2020-12-04 17:06:50 +00:00
self . deactivateBid ( None , offer , bid )
2020-11-14 22:13:11 +00:00
self . _last_checked_progress = now
2019-07-17 15:12:06 +00:00
2020-11-14 22:13:11 +00:00
if now - self . _last_checked_watched > = self . check_watched_seconds :
2019-07-17 15:12:06 +00:00
for k , c in self . coin_clients . items ( ) :
2021-11-01 13:52:40 +00:00
if k == Coins . PART_ANON or k == Coins . PART_BLIND :
2021-02-15 13:34:47 +00:00
continue
2019-07-17 15:12:06 +00:00
if len ( c [ ' watched_outputs ' ] ) > 0 :
self . checkForSpends ( k , c )
2020-11-14 22:13:11 +00:00
self . _last_checked_watched = now
2019-07-17 15:12:06 +00:00
2020-11-14 22:13:11 +00:00
if now - self . _last_checked_expired > = self . check_expired_seconds :
2019-07-17 15:12:06 +00:00
self . expireMessages ( )
2023-03-08 22:53:54 +00:00
self . checkAcceptedBids ( )
2020-11-14 22:13:11 +00:00
self . _last_checked_expired = now
2019-11-09 21:09:22 +00:00
2022-06-06 21:03:31 +00:00
if now - self . _last_checked_actions > = self . check_actions_seconds :
self . checkQueuedActions ( )
self . _last_checked_actions = now
2020-11-14 22:13:11 +00:00
if now - self . _last_checked_xmr_swaps > = self . check_xmr_swaps_seconds :
self . checkXmrSwaps ( )
self . _last_checked_xmr_swaps = now
2019-11-09 21:09:22 +00:00
2019-07-27 17:26:06 +00:00
except Exception as ex :
2022-10-26 15:47:30 +00:00
self . logException ( f ' update { ex } ' )
2019-07-17 15:12:06 +00:00
finally :
self . mxDB . release ( )
2019-08-05 18:31:02 +00:00
def manualBidUpdate ( self , bid_id , data ) :
self . log . info ( ' Manually updating bid %s ' , bid_id . hex ( ) )
self . mxDB . acquire ( )
try :
bid , offer = self . getBidAndOffer ( bid_id )
2021-10-21 22:47:04 +00:00
ensure ( bid , ' Bid not found {} ' . format ( bid_id . hex ( ) ) )
ensure ( offer , ' Offer not found {} ' . format ( bid . offer_id . hex ( ) ) )
2019-08-05 18:31:02 +00:00
has_changed = False
if bid . state != data [ ' bid_state ' ] :
bid . setState ( data [ ' bid_state ' ] )
self . log . debug ( ' Set state to %s ' , strBidState ( bid . state ) )
has_changed = True
2021-12-15 13:41:43 +00:00
if bid . debug_ind != data [ ' debug_ind ' ] :
if bid . debug_ind is None and data [ ' debug_ind ' ] == - 1 :
pass # Already unset
else :
self . log . debug ( ' Bid %s Setting debug flag: %s ' , bid_id . hex ( ) , data [ ' debug_ind ' ] )
bid . debug_ind = data [ ' debug_ind ' ]
has_changed = True
2021-12-18 23:45:17 +00:00
if data [ ' kbs_other ' ] is not None :
2022-12-05 15:04:23 +00:00
return xmr_swap_1 . recoverNoScriptTxnWithKey ( self , bid_id , data [ ' kbs_other ' ] )
2021-12-18 23:45:17 +00:00
2019-08-05 18:31:02 +00:00
if has_changed :
session = scoped_session ( self . session_factory )
try :
2020-12-04 17:06:50 +00:00
activate_bid = False
2022-06-11 21:13:12 +00:00
if bid . state and isActiveBidState ( bid . state ) :
activate_bid = True
2020-12-04 17:06:50 +00:00
if activate_bid :
2019-08-05 18:31:02 +00:00
self . activateBid ( session , bid )
else :
2020-12-04 17:06:50 +00:00
self . deactivateBid ( session , offer , bid )
self . saveBidInSession ( bid_id , bid , session )
session . commit ( )
2019-08-05 18:31:02 +00:00
finally :
session . close ( )
session . remove ( )
else :
raise ValueError ( ' No changes ' )
finally :
self . mxDB . release ( )
2022-11-13 21:18:33 +00:00
def editGeneralSettings ( self , data ) :
self . log . info ( ' Updating general settings ' )
settings_changed = False
suggest_reboot = False
settings_copy = copy . deepcopy ( self . settings )
with self . mxDB :
if ' debug ' in data :
new_value = data [ ' debug ' ]
ensure ( type ( new_value ) == bool , ' New debug value not boolean ' )
if settings_copy . get ( ' debug ' , False ) != new_value :
self . debug = new_value
settings_copy [ ' debug ' ] = new_value
settings_changed = True
if ' debug_ui ' in data :
new_value = data [ ' debug_ui ' ]
ensure ( type ( new_value ) == bool , ' New debug_ui value not boolean ' )
if settings_copy . get ( ' debug_ui ' , False ) != new_value :
self . debug_ui = new_value
settings_copy [ ' debug_ui ' ] = new_value
settings_changed = True
if ' show_chart ' in data :
new_value = data [ ' show_chart ' ]
ensure ( type ( new_value ) == bool , ' New show_chart value not boolean ' )
if settings_copy . get ( ' show_chart ' , True ) != new_value :
settings_copy [ ' show_chart ' ] = new_value
settings_changed = True
if ' chart_api_key ' in data :
new_value = data [ ' chart_api_key ' ]
ensure ( type ( new_value ) == str , ' New chart_api_key value not a string ' )
ensure ( len ( new_value ) < = 128 , ' New chart_api_key value too long ' )
if all ( c in string . hexdigits for c in new_value ) :
if settings_copy . get ( ' chart_api_key ' , ' ' ) != new_value :
settings_copy [ ' chart_api_key ' ] = new_value
if ' chart_api_key_enc ' in settings_copy :
settings_copy . pop ( ' chart_api_key_enc ' )
settings_changed = True
else :
# Encode value as hex to avoid escaping
new_value = new_value . encode ( ' utf-8 ' ) . hex ( )
if settings_copy . get ( ' chart_api_key_enc ' , ' ' ) != new_value :
settings_copy [ ' chart_api_key_enc ' ] = new_value
if ' chart_api_key ' in settings_copy :
settings_copy . pop ( ' chart_api_key ' )
settings_changed = True
if settings_changed :
settings_path = os . path . join ( self . data_dir , cfg . CONFIG_FILENAME )
settings_path_new = settings_path + ' .new '
shutil . copyfile ( settings_path , settings_path + ' .last ' )
with open ( settings_path_new , ' w ' ) as fp :
json . dump ( settings_copy , fp , indent = 4 )
shutil . move ( settings_path_new , settings_path )
self . settings = settings_copy
return settings_changed , suggest_reboot
2019-08-05 22:04:40 +00:00
def editSettings ( self , coin_name , data ) :
2022-11-13 21:18:33 +00:00
self . log . info ( f ' Updating settings { coin_name } ' )
settings_changed = False
suggest_reboot = False
settings_copy = copy . deepcopy ( self . settings )
2020-12-22 11:21:25 +00:00
with self . mxDB :
2022-11-13 21:18:33 +00:00
settings_cc = settings_copy [ ' chainclients ' ] [ coin_name ]
2019-08-05 22:04:40 +00:00
if ' lookups ' in data :
2021-01-18 22:52:05 +00:00
if settings_cc . get ( ' chain_lookups ' , ' local ' ) != data [ ' lookups ' ] :
2020-12-22 11:21:25 +00:00
settings_changed = True
2021-01-18 22:52:05 +00:00
settings_cc [ ' chain_lookups ' ] = data [ ' lookups ' ]
2020-12-22 11:21:25 +00:00
for coin , cc in self . coin_clients . items ( ) :
if cc [ ' name ' ] == coin_name :
cc [ ' chain_lookups ' ] = data [ ' lookups ' ]
break
2021-11-13 21:13:54 +00:00
for setting in ( ' manage_daemon ' , ' rpchost ' , ' rpcport ' , ' automatically_select_daemon ' ) :
if setting not in data :
continue
if settings_cc . get ( setting ) != data [ setting ] :
settings_changed = True
suggest_reboot = True
settings_cc [ setting ] = data [ setting ]
if ' remotedaemonurls ' in data :
remotedaemonurls_in = data [ ' remotedaemonurls ' ] . split ( ' \n ' )
remotedaemonurls = set ( )
for url in remotedaemonurls_in :
if url . count ( ' : ' ) > 0 :
remotedaemonurls . add ( url . strip ( ) )
if set ( settings_cc . get ( ' remote_daemon_urls ' , [ ] ) ) != remotedaemonurls :
settings_cc [ ' remote_daemon_urls ' ] = list ( remotedaemonurls )
settings_changed = True
suggest_reboot = True
2020-12-22 11:21:25 +00:00
if ' fee_priority ' in data :
new_fee_priority = data [ ' fee_priority ' ]
2021-10-21 22:47:04 +00:00
ensure ( new_fee_priority > = 0 and new_fee_priority < 4 , ' Invalid priority ' )
2020-12-22 11:21:25 +00:00
2021-01-18 22:52:05 +00:00
if settings_cc . get ( ' fee_priority ' , 0 ) != new_fee_priority :
settings_changed = True
settings_cc [ ' fee_priority ' ] = new_fee_priority
for coin , cc in self . coin_clients . items ( ) :
if cc [ ' name ' ] == coin_name :
cc [ ' fee_priority ' ] = new_fee_priority
2022-11-13 21:18:33 +00:00
if self . isCoinActive ( coin ) :
self . ci ( coin ) . setFeePriority ( new_fee_priority )
2021-01-18 22:52:05 +00:00
break
if ' conf_target ' in data :
new_conf_target = data [ ' conf_target ' ]
2021-10-21 22:47:04 +00:00
ensure ( new_conf_target > = 1 and new_conf_target < 33 , ' Invalid conf_target ' )
2021-01-18 22:52:05 +00:00
if settings_cc . get ( ' conf_target ' , 2 ) != new_conf_target :
2020-12-22 11:21:25 +00:00
settings_changed = True
2021-01-18 22:52:05 +00:00
settings_cc [ ' conf_target ' ] = new_conf_target
2020-12-22 11:21:25 +00:00
for coin , cc in self . coin_clients . items ( ) :
if cc [ ' name ' ] == coin_name :
2021-01-18 22:52:05 +00:00
cc [ ' conf_target ' ] = new_conf_target
2022-11-13 21:18:33 +00:00
if self . isCoinActive ( coin ) :
self . ci ( coin ) . setConfTarget ( new_conf_target )
2020-12-22 11:21:25 +00:00
break
2022-04-10 22:11:51 +00:00
if ' anon_tx_ring_size ' in data :
new_anon_tx_ring_size = data [ ' anon_tx_ring_size ' ]
ensure ( new_anon_tx_ring_size > = 3 and new_anon_tx_ring_size < 33 , ' Invalid anon_tx_ring_size ' )
if settings_cc . get ( ' anon_tx_ring_size ' , 12 ) != new_anon_tx_ring_size :
settings_changed = True
settings_cc [ ' anon_tx_ring_size ' ] = new_anon_tx_ring_size
for coin , cc in self . coin_clients . items ( ) :
if cc [ ' name ' ] == coin_name :
cc [ ' anon_tx_ring_size ' ] = new_anon_tx_ring_size
2022-11-13 21:18:33 +00:00
if self . isCoinActive ( coin ) :
self . ci ( coin ) . setAnonTxRingSize ( new_anon_tx_ring_size )
2022-04-10 22:11:51 +00:00
break
2020-12-22 11:21:25 +00:00
if settings_changed :
2020-02-01 18:57:20 +00:00
settings_path = os . path . join ( self . data_dir , cfg . CONFIG_FILENAME )
2022-11-13 21:18:33 +00:00
settings_path_new = settings_path + ' .new '
2019-08-05 22:04:40 +00:00
shutil . copyfile ( settings_path , settings_path + ' .last ' )
2022-11-13 21:18:33 +00:00
with open ( settings_path_new , ' w ' ) as fp :
json . dump ( settings_copy , fp , indent = 4 )
shutil . move ( settings_path_new , settings_path )
self . settings = settings_copy
2021-11-13 21:13:54 +00:00
return settings_changed , suggest_reboot
2019-08-05 22:04:40 +00:00
2021-02-14 10:12:41 +00:00
def enableCoin ( self , coin_name ) :
self . log . info ( ' Enabling coin %s ' , coin_name )
coin_id = self . getCoinIdFromName ( coin_name )
if coin_id in ( Coins . PART , Coins . PART_BLIND , Coins . PART_ANON ) :
raise ValueError ( ' Invalid coin ' )
settings_cc = self . settings [ ' chainclients ' ] [ coin_name ]
if ' connection_type_prev ' not in settings_cc :
raise ValueError ( ' Can \' t find previous value. ' )
settings_cc [ ' connection_type ' ] = settings_cc [ ' connection_type_prev ' ]
del settings_cc [ ' connection_type_prev ' ]
if ' manage_daemon_prev ' in settings_cc :
settings_cc [ ' manage_daemon ' ] = settings_cc [ ' manage_daemon_prev ' ]
del settings_cc [ ' manage_daemon_prev ' ]
if ' manage_wallet_daemon_prev ' in settings_cc :
settings_cc [ ' manage_wallet_daemon ' ] = settings_cc [ ' manage_wallet_daemon_prev ' ]
del settings_cc [ ' manage_wallet_daemon_prev ' ]
settings_path = os . path . join ( self . data_dir , cfg . CONFIG_FILENAME )
shutil . copyfile ( settings_path , settings_path + ' .last ' )
with open ( settings_path , ' w ' ) as fp :
json . dump ( self . settings , fp , indent = 4 )
# Client must be restarted
def disableCoin ( self , coin_name ) :
self . log . info ( ' Disabling coin %s ' , coin_name )
coin_id = self . getCoinIdFromName ( coin_name )
if coin_id in ( Coins . PART , Coins . PART_BLIND , Coins . PART_ANON ) :
raise ValueError ( ' Invalid coin ' )
settings_cc = self . settings [ ' chainclients ' ] [ coin_name ]
if settings_cc [ ' connection_type ' ] != ' rpc ' :
raise ValueError ( ' Already disabled. ' )
settings_cc [ ' manage_daemon_prev ' ] = settings_cc [ ' manage_daemon ' ]
settings_cc [ ' manage_daemon ' ] = False
settings_cc [ ' connection_type_prev ' ] = settings_cc [ ' connection_type ' ]
settings_cc [ ' connection_type ' ] = ' none '
if ' manage_wallet_daemon ' in settings_cc :
settings_cc [ ' manage_wallet_daemon_prev ' ] = settings_cc [ ' manage_wallet_daemon ' ]
settings_cc [ ' manage_wallet_daemon ' ] = False
settings_path = os . path . join ( self . data_dir , cfg . CONFIG_FILENAME )
shutil . copyfile ( settings_path , settings_path + ' .last ' )
with open ( settings_path , ' w ' ) as fp :
json . dump ( self . settings , fp , indent = 4 )
# Client must be restarted
2019-07-17 15:12:06 +00:00
def getSummary ( self , opts = None ) :
num_watched_outputs = 0
for c , v in self . coin_clients . items ( ) :
2021-11-14 23:47:49 +00:00
if c in ( Coins . PART_ANON , Coins . PART_BLIND ) :
continue
2019-07-17 15:12:06 +00:00
num_watched_outputs + = len ( v [ ' watched_outputs ' ] )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2022-06-14 22:25:17 +00:00
q_str = ''' SELECT
2023-02-17 21:14:17 +00:00
COUNT ( CASE WHEN b . was_sent THEN 1 ELSE NULL END ) AS count_sent ,
2023-05-10 15:50:40 +00:00
COUNT ( CASE WHEN b . was_sent AND b . state = { } AND b . expire_at > { } AND o . expire_at > { } THEN 1 ELSE NULL END ) AS count_sent_active ,
2023-02-17 21:14:17 +00:00
COUNT ( CASE WHEN b . was_received THEN 1 ELSE NULL END ) AS count_received ,
COUNT ( CASE WHEN b . was_received AND b . state = { } AND b . expire_at > { } AND o . expire_at > { } THEN 1 ELSE NULL END ) AS count_available
FROM bids b
JOIN offers o ON b . offer_id = o . offer_id
2023-05-10 15:50:40 +00:00
WHERE b . active_ind = 1 ''' .format(BidStates.BID_RECEIVED, now, now, BidStates.BID_RECEIVED, now, now)
2022-06-14 22:25:17 +00:00
q = self . engine . execute ( q_str ) . first ( )
bids_sent = q [ 0 ]
2023-05-10 15:50:40 +00:00
bids_sent_active = q [ 1 ]
bids_received = q [ 2 ]
bids_available = q [ 3 ]
2022-06-14 22:25:17 +00:00
q_str = ''' SELECT
COUNT ( CASE WHEN expire_at > { } THEN 1 ELSE NULL END ) AS count_active ,
2023-05-10 15:50:40 +00:00
COUNT ( CASE WHEN was_sent THEN 1 ELSE NULL END ) AS count_sent ,
COUNT ( CASE WHEN was_sent AND expire_at > { } THEN 1 ELSE NULL END ) AS count_sent_active
FROM offers WHERE active_ind = 1 ''' .format(now, now)
2022-06-14 22:25:17 +00:00
q = self . engine . execute ( q_str ) . first ( )
2019-07-17 15:12:06 +00:00
num_offers = q [ 0 ]
2022-06-14 22:25:17 +00:00
num_sent_offers = q [ 1 ]
2023-05-10 15:50:40 +00:00
num_sent_active_offers = q [ 2 ]
2019-07-17 15:12:06 +00:00
rv = {
' network ' : self . chain ,
' num_swapping ' : len ( self . swaps_in_progress ) ,
' num_network_offers ' : num_offers ,
' num_sent_offers ' : num_sent_offers ,
2023-05-10 15:50:40 +00:00
' num_sent_active_offers ' : num_sent_active_offers ,
2019-07-17 15:12:06 +00:00
' num_recv_bids ' : bids_received ,
' num_sent_bids ' : bids_sent ,
2023-05-10 15:50:40 +00:00
' num_sent_active_bids ' : bids_sent_active ,
2022-06-14 22:25:17 +00:00
' num_available_bids ' : bids_available ,
2019-07-17 15:12:06 +00:00
' num_watched_outputs ' : num_watched_outputs ,
}
return rv
2022-07-05 22:46:37 +00:00
def getBlockchainInfo ( self , coin ) :
2020-12-04 17:06:50 +00:00
ci = self . ci ( coin )
2020-11-07 11:08:07 +00:00
2022-07-05 22:46:37 +00:00
try :
blockchaininfo = ci . getBlockchainInfo ( )
2021-02-06 22:35:12 +00:00
2022-07-05 22:46:37 +00:00
rv = {
' version ' : self . coin_clients [ coin ] [ ' core_version ' ] ,
' name ' : ci . coin_name ( ) ,
' blocks ' : blockchaininfo [ ' blocks ' ] ,
2022-10-11 05:55:35 +00:00
' synced ' : ' {:.2f} ' . format ( round ( 100 * blockchaininfo [ ' verificationprogress ' ] , 2 ) ) ,
2022-07-05 22:46:37 +00:00
}
2021-02-06 22:35:12 +00:00
2022-07-06 12:35:35 +00:00
if ' known_block_count ' in blockchaininfo :
rv [ ' known_block_count ' ] = blockchaininfo [ ' known_block_count ' ]
if ' bootstrapping ' in blockchaininfo :
rv [ ' bootstrapping ' ] = blockchaininfo [ ' bootstrapping ' ]
2022-07-05 22:46:37 +00:00
return rv
except Exception as e :
self . log . warning ( ' getWalletInfo failed with: %s ' , str ( e ) )
2019-07-17 15:12:06 +00:00
2022-07-05 22:46:37 +00:00
def getWalletInfo ( self , coin ) :
ci = self . ci ( coin )
2021-10-14 20:17:37 +00:00
2022-07-05 22:46:37 +00:00
try :
walletinfo = ci . getWalletInfo ( )
scale = chainparams [ coin ] [ ' decimal_places ' ]
rv = {
' deposit_address ' : self . getCachedAddressForCoin ( coin ) ,
' balance ' : format_amount ( make_int ( walletinfo [ ' balance ' ] , scale ) , scale ) ,
' unconfirmed ' : format_amount ( make_int ( walletinfo . get ( ' unconfirmed_balance ' ) , scale ) , scale ) ,
' expected_seed ' : ci . knownWalletSeed ( ) ,
2022-11-11 23:51:30 +00:00
' encrypted ' : walletinfo [ ' encrypted ' ] ,
' locked ' : walletinfo [ ' locked ' ] ,
2022-07-05 22:46:37 +00:00
}
if coin == Coins . PART :
rv [ ' stealth_address ' ] = self . getCachedStealthAddressForCoin ( Coins . PART )
rv [ ' anon_balance ' ] = walletinfo [ ' anon_balance ' ]
rv [ ' anon_pending ' ] = walletinfo [ ' unconfirmed_anon ' ] + walletinfo [ ' immature_anon_balance ' ]
rv [ ' blind_balance ' ] = walletinfo [ ' blind_balance ' ]
rv [ ' blind_unconfirmed ' ] = walletinfo [ ' unconfirmed_blind ' ]
elif coin == Coins . XMR :
rv [ ' main_address ' ] = self . getCachedMainWalletAddress ( ci )
return rv
except Exception as e :
self . log . warning ( ' getWalletInfo failed with: %s ' , str ( e ) )
def addWalletInfoRecord ( self , coin , info_type , wi ) :
coin_id = int ( coin )
2021-10-14 20:17:37 +00:00
self . mxDB . acquire ( )
try :
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2021-10-14 20:17:37 +00:00
session = scoped_session ( self . session_factory )
2022-07-05 22:46:37 +00:00
session . add ( Wallets ( coin_id = coin , balance_type = info_type , wallet_data = json . dumps ( wi ) , created_at = now ) )
query_str = f ' DELETE FROM wallets WHERE (coin_id = { coin_id } AND balance_type = { info_type } ) AND record_id NOT IN (SELECT record_id FROM wallets WHERE coin_id = { coin_id } AND balance_type = { info_type } ORDER BY created_at DESC LIMIT 3 ) '
2021-10-14 20:17:37 +00:00
session . execute ( query_str )
session . commit ( )
except Exception as e :
2022-07-05 22:46:37 +00:00
self . log . error ( f ' addWalletInfoRecord { e } ' )
2021-10-14 20:17:37 +00:00
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2023-02-17 23:47:44 +00:00
def updateWalletInfo ( self , coin ) - > None :
2022-07-05 22:46:37 +00:00
# Store wallet info to db so it's available after startup
try :
bi = self . getBlockchainInfo ( coin )
if bi :
self . addWalletInfoRecord ( coin , 0 , bi )
# monero-wallet-rpc is slow/unresponsive while syncing
wi = self . getWalletInfo ( coin )
if wi :
self . addWalletInfoRecord ( coin , 1 , wi )
except Exception as e :
self . log . error ( f ' updateWalletInfo { e } ' )
finally :
self . _updating_wallets_info [ int ( coin ) ] = False
2022-09-23 20:07:41 +00:00
def updateWalletsInfo ( self , force_update = False , only_coin = None , wait_for_complete = False ) :
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2021-10-14 20:17:37 +00:00
if not force_update and now - self . _last_updated_wallets_info < 30 :
return
for c in Coins :
if only_coin is not None and c != only_coin :
continue
if c not in chainparams :
continue
2022-09-23 20:07:41 +00:00
cc = self . coin_clients [ c ]
if cc [ ' connection_type ' ] == ' rpc ' :
if not force_update and now - cc . get ( ' last_updated_wallet_info ' , 0 ) < 30 :
return
2023-02-26 18:14:00 +00:00
cc [ ' last_updated_wallet_info ' ] = self . getTime ( )
2021-10-14 20:17:37 +00:00
self . _updating_wallets_info [ int ( c ) ] = True
2022-09-23 20:07:41 +00:00
handle = self . thread_pool . submit ( self . updateWalletInfo , c )
if wait_for_complete :
try :
handle . result ( timeout = 10 )
except Exception as e :
self . log . error ( f ' updateWalletInfo { e } ' )
2021-10-14 20:17:37 +00:00
2019-07-17 15:12:06 +00:00
def getWalletsInfo ( self , opts = None ) :
rv = { }
2022-11-11 23:51:30 +00:00
for c in self . activeCoins ( ) :
key = chainparams [ c ] [ ' ticker ' ] if opts . get ( ' ticker_key ' , False ) else c
try :
rv [ key ] = self . getWalletInfo ( c )
rv [ key ] . update ( self . getBlockchainInfo ( c ) )
except Exception as ex :
rv [ key ] = { ' name ' : getCoinName ( c ) , ' error ' : str ( ex ) }
2019-07-17 15:12:06 +00:00
return rv
2021-10-14 20:17:37 +00:00
def getCachedWalletsInfo ( self , opts = None ) :
rv = { }
# Requires? self.mxDB.acquire()
try :
session = scoped_session ( self . session_factory )
2022-01-23 12:00:28 +00:00
where_str = ' '
if opts is not None and ' coin_id ' in opts :
where_str = ' WHERE coin_id = {} ' . format ( opts [ ' coin_id ' ] )
2022-07-05 22:46:37 +00:00
inner_str = f ' SELECT coin_id, balance_type, MAX(created_at) as max_created_at FROM wallets { where_str } GROUP BY coin_id, balance_type '
query_str = ' SELECT a.coin_id, a.balance_type, wallet_data, created_at FROM wallets a, ( {} ) b WHERE a.coin_id = b.coin_id AND a.balance_type = b.balance_type AND a.created_at = b.max_created_at ' . format ( inner_str )
2021-10-14 20:17:37 +00:00
q = session . execute ( query_str )
for row in q :
coin_id = row [ 0 ]
2021-11-09 15:26:26 +00:00
if self . coin_clients [ coin_id ] [ ' connection_type ' ] != ' rpc ' :
# Skip cached info if coin was disabled
continue
2022-07-05 22:46:37 +00:00
wallet_data = json . loads ( row [ 2 ] )
if row [ 1 ] == 1 :
wallet_data [ ' lastupdated ' ] = row [ 3 ]
wallet_data [ ' updating ' ] = self . _updating_wallets_info . get ( coin_id , False )
2021-10-14 20:17:37 +00:00
2022-07-05 22:46:37 +00:00
# Ensure the latest deposit address is displayed
q = session . execute ( ' SELECT value FROM kv_string WHERE key = " receive_addr_ {} " ' . format ( chainparams [ coin_id ] [ ' name ' ] ) )
for row in q :
wallet_data [ ' deposit_address ' ] = row [ 0 ]
2021-10-14 20:17:37 +00:00
2022-07-05 22:46:37 +00:00
if coin_id in rv :
rv [ coin_id ] . update ( wallet_data )
else :
rv [ coin_id ] = wallet_data
2021-10-14 20:17:37 +00:00
finally :
session . close ( )
session . remove ( )
2022-01-23 12:00:28 +00:00
if opts is not None and ' coin_id ' in opts :
return rv
2022-11-11 23:51:30 +00:00
for c in self . activeCoins ( ) :
coin_id = int ( c )
if coin_id not in rv :
rv [ coin_id ] = {
' name ' : getCoinName ( c ) ,
' no_data ' : True ,
' updating ' : self . _updating_wallets_info . get ( coin_id , False ) ,
}
2021-10-14 20:17:37 +00:00
return rv
2019-07-17 15:12:06 +00:00
def countAcceptedBids ( self , offer_id = None ) :
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
if offer_id :
2021-01-11 21:48:46 +00:00
q = session . execute ( ' SELECT COUNT(*) FROM bids WHERE state >= {} AND offer_id = x \' {} \' ' . format ( BidStates . BID_ACCEPTED , offer_id . hex ( ) ) ) . first ( )
2019-07-17 15:12:06 +00:00
else :
2021-01-11 21:48:46 +00:00
q = session . execute ( ' SELECT COUNT(*) FROM bids WHERE state >= {} ' . format ( BidStates . BID_ACCEPTED ) ) . first ( )
2019-07-17 15:12:06 +00:00
return q [ 0 ]
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2022-06-16 12:28:52 +00:00
def listOffers ( self , sent = False , filters = { } , with_bid_info = False ) :
2019-07-17 15:12:06 +00:00
self . mxDB . acquire ( )
try :
rv = [ ]
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2019-07-17 15:12:06 +00:00
session = scoped_session ( self . session_factory )
2022-06-16 12:28:52 +00:00
if with_bid_info :
subquery = session . query ( sa . func . sum ( Bid . amount ) . label ( ' completed_bid_amount ' ) ) . filter ( sa . and_ ( Bid . offer_id == Offer . offer_id , Bid . state == BidStates . SWAP_COMPLETED ) ) . correlate ( Offer ) . scalar_subquery ( )
q = session . query ( Offer , subquery )
else :
q = session . query ( Offer )
2022-06-15 22:19:06 +00:00
2019-07-17 15:12:06 +00:00
if sent :
2022-06-15 22:19:06 +00:00
q = q . filter ( Offer . was_sent == True ) # noqa: E712
2022-11-18 21:34:57 +00:00
active_state = filters . get ( ' active ' , ' any ' )
if active_state == ' active ' :
q = q . filter ( Offer . expire_at > now , Offer . active_ind == 1 )
elif active_state == ' expired ' :
q = q . filter ( Offer . expire_at < = now )
elif active_state == ' revoked ' :
q = q . filter ( Offer . active_ind != 1 )
2019-07-17 15:12:06 +00:00
else :
2022-06-15 22:19:06 +00:00
q = q . filter ( sa . and_ ( Offer . expire_at > now , Offer . active_ind == 1 ) )
2019-07-29 10:14:46 +00:00
2020-11-14 22:13:11 +00:00
filter_offer_id = filters . get ( ' offer_id ' , None )
if filter_offer_id is not None :
q = q . filter ( Offer . offer_id == filter_offer_id )
2019-07-29 10:14:46 +00:00
filter_coin_from = filters . get ( ' coin_from ' , None )
if filter_coin_from and filter_coin_from > - 1 :
q = q . filter ( Offer . coin_from == int ( filter_coin_from ) )
filter_coin_to = filters . get ( ' coin_to ' , None )
if filter_coin_to and filter_coin_to > - 1 :
q = q . filter ( Offer . coin_to == int ( filter_coin_to ) )
2023-02-14 21:34:01 +00:00
2023-01-11 08:28:57 +00:00
filter_include_sent = filters . get ( ' include_sent ' , None )
2023-02-14 21:34:01 +00:00
if filter_include_sent is not None and filter_include_sent is not True :
2023-01-11 08:28:57 +00:00
q = q . filter ( Offer . was_sent == False ) # noqa: E712
2019-07-29 10:14:46 +00:00
2019-08-01 16:21:23 +00:00
order_dir = filters . get ( ' sort_dir ' , ' desc ' )
2021-01-08 18:35:39 +00:00
order_by = filters . get ( ' sort_by ' , ' created_at ' )
2019-08-01 16:21:23 +00:00
if order_by == ' created_at ' :
q = q . order_by ( Offer . created_at . desc ( ) if order_dir == ' desc ' else Offer . created_at . asc ( ) )
elif order_by == ' rate ' :
q = q . order_by ( Offer . rate . desc ( ) if order_dir == ' desc ' else Offer . rate . asc ( ) )
2019-07-31 08:41:35 +00:00
limit = filters . get ( ' limit ' , None )
if limit is not None :
q = q . limit ( limit )
offset = filters . get ( ' offset ' , None )
if offset is not None :
q = q . offset ( offset )
2019-07-17 15:12:06 +00:00
for row in q :
2022-06-16 12:28:52 +00:00
offer = row [ 0 ] if with_bid_info else row
2021-02-15 21:49:18 +00:00
# Show offers for enabled coins only
try :
2022-06-16 12:28:52 +00:00
ci_from = self . ci ( offer . coin_from )
ci_to = self . ci ( offer . coin_to )
2021-02-15 21:49:18 +00:00
except Exception as e :
continue
2022-06-16 12:28:52 +00:00
if with_bid_info :
rv . append ( ( offer , 0 if row [ 1 ] is None else row [ 1 ] ) )
else :
rv . append ( offer )
2019-07-17 15:12:06 +00:00
return rv
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2023-06-06 20:03:05 +00:00
def activeBidsQueryStr ( self , now : int , offer_table : str = ' offers ' , bids_table : str = ' bids ' ) :
offers_inset = f ' AND { offer_table } .expire_at > { now } ' if offer_table != ' ' else ' '
inactive_states_str = ' , ' . join ( [ str ( int ( s ) ) for s in inactive_states ] )
return f ' ( { bids_table } .state NOT IN ( { inactive_states_str } ) AND ( { bids_table } .state > { BidStates . BID_RECEIVED } OR ( { bids_table } .expire_at > { now } { offers_inset } ))) '
2023-02-14 21:34:01 +00:00
def listBids ( self , sent = False , offer_id = None , for_html = False , filters = { } ) :
2019-07-17 15:12:06 +00:00
self . mxDB . acquire ( )
try :
rv = [ ]
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2019-07-17 15:12:06 +00:00
session = scoped_session ( self . session_factory )
2019-07-28 19:57:20 +00:00
2023-02-14 21:34:01 +00:00
query_str = ' SELECT bids.created_at, bids.expire_at, bids.bid_id, bids.offer_id, bids.amount, bids.state, bids.was_received, tx1.state, tx2.state, offers.coin_from, bids.rate, bids.bid_addr FROM bids ' + \
2021-02-03 14:01:27 +00:00
' LEFT JOIN offers ON offers.offer_id = bids.offer_id ' + \
2019-07-28 19:57:20 +00:00
' LEFT JOIN transactions AS tx1 ON tx1.bid_id = bids.bid_id AND tx1.tx_type = {} ' . format ( TxTypes . ITX ) + \
' LEFT JOIN transactions AS tx2 ON tx2.bid_id = bids.bid_id AND tx2.tx_type = {} ' . format ( TxTypes . PTX )
2021-02-15 13:34:47 +00:00
query_str + = ' WHERE bids.active_ind = 1 '
filter_bid_id = filters . get ( ' bid_id ' , None )
if filter_bid_id is not None :
query_str + = ' AND bids.bid_id = x \' {} \' ' . format ( filter_bid_id . hex ( ) )
2019-07-17 15:12:06 +00:00
if offer_id is not None :
2021-02-15 13:34:47 +00:00
query_str + = ' AND bids.offer_id = x \' {} \' ' . format ( offer_id . hex ( ) )
2019-07-17 15:12:06 +00:00
elif sent :
2021-02-15 13:34:47 +00:00
query_str + = ' AND bids.was_sent = 1 '
2019-07-17 15:12:06 +00:00
else :
2021-02-15 13:34:47 +00:00
query_str + = ' AND bids.was_received = 1 '
2021-11-25 13:01:47 +00:00
2022-06-14 22:25:17 +00:00
bid_state_ind = filters . get ( ' bid_state_ind ' , - 1 )
if bid_state_ind != - 1 :
query_str + = ' AND bids.state = {} ' . format ( bid_state_ind )
2023-02-14 21:34:01 +00:00
with_available_or_active = filters . get ( ' with_available_or_active ' , False )
2022-06-14 22:25:17 +00:00
with_expired = filters . get ( ' with_expired ' , True )
2023-02-14 21:34:01 +00:00
if with_available_or_active :
2023-06-06 20:03:05 +00:00
query_str + = ' AND ' + self . activeBidsQueryStr ( now )
2023-02-14 21:34:01 +00:00
else :
if with_expired is not True :
2023-02-17 21:14:17 +00:00
query_str + = ' AND bids.expire_at > {} AND offers.expire_at > {} ' . format ( now , now )
2022-06-14 22:25:17 +00:00
2021-11-25 13:01:47 +00:00
sort_dir = filters . get ( ' sort_dir ' , ' DESC ' ) . upper ( )
sort_by = filters . get ( ' sort_by ' , ' created_at ' )
query_str + = f ' ORDER BY bids. { sort_by } { sort_dir } '
limit = filters . get ( ' limit ' , None )
if limit is not None :
query_str + = f ' LIMIT { limit } '
offset = filters . get ( ' offset ' , None )
if offset is not None :
query_str + = f ' OFFSET { offset } '
2020-12-04 21:30:20 +00:00
2021-01-11 21:48:46 +00:00
q = session . execute ( query_str )
2019-07-17 15:12:06 +00:00
for row in q :
rv . append ( row )
return rv
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
def listSwapsInProgress ( self , for_html = False ) :
self . mxDB . acquire ( )
try :
rv = [ ]
for k , v in self . swaps_in_progress . items ( ) :
2019-07-31 08:41:35 +00:00
rv . append ( ( k , v [ 0 ] . offer_id . hex ( ) , v [ 0 ] . state , v [ 0 ] . getITxState ( ) , v [ 0 ] . getPTxState ( ) ) )
2019-07-17 15:12:06 +00:00
return rv
finally :
self . mxDB . release ( )
def listWatchedOutputs ( self ) :
self . mxDB . acquire ( )
try :
rv = [ ]
rv_heights = [ ]
for c , v in self . coin_clients . items ( ) :
2021-11-14 23:47:49 +00:00
if c in ( Coins . PART_ANON , Coins . PART_BLIND ) : # exclude duplicates
continue
2019-07-25 21:15:35 +00:00
if self . coin_clients [ c ] [ ' connection_type ' ] == ' rpc ' :
rv_heights . append ( ( c , v [ ' last_height_checked ' ] ) )
2019-07-17 15:12:06 +00:00
for o in v [ ' watched_outputs ' ] :
2020-11-29 11:46:00 +00:00
rv . append ( ( c , o . bid_id , o . txid_hex , o . vout , o . tx_type ) )
2019-07-17 15:12:06 +00:00
return ( rv , rv_heights )
finally :
self . mxDB . release ( )
2023-03-18 17:28:25 +00:00
def listAllSMSGAddresses ( self , filters = { } ) :
query_str = ' SELECT addr_id, addr, use_type, active_ind, created_at, note, pubkey FROM smsgaddresses '
query_str + = ' WHERE active_ind = 1 '
query_data = { }
if ' addr_id ' in filters :
query_str + = ' AND addr_id = :addr_id '
query_data [ ' addr_id ' ] = filters [ ' addr_id ' ]
if ' addressnote ' in filters :
query_str + = ' AND note LIKE :note '
query_data [ ' note ' ] = ' % ' + filters [ ' addressnote ' ] + ' % '
if ' addr_type ' in filters and filters [ ' addr_type ' ] > - 1 :
query_str + = ' AND use_type = :addr_type '
query_data [ ' addr_type ' ] = filters [ ' addr_type ' ]
sort_dir = filters . get ( ' sort_dir ' , ' DESC ' ) . upper ( )
sort_by = filters . get ( ' sort_by ' , ' created_at ' )
query_str + = f ' ORDER BY { sort_by } { sort_dir } '
limit = filters . get ( ' limit ' , None )
if limit is not None :
query_str + = f ' LIMIT { limit } '
offset = filters . get ( ' offset ' , None )
if offset is not None :
query_str + = f ' OFFSET { offset } '
2021-10-19 18:59:18 +00:00
try :
2023-03-18 17:28:25 +00:00
session = self . openSession ( )
2021-10-19 18:59:18 +00:00
rv = [ ]
2023-03-18 17:28:25 +00:00
q = session . execute ( query_str , query_data )
2021-10-19 18:59:18 +00:00
for row in q :
rv . append ( {
' id ' : row [ 0 ] ,
' addr ' : row [ 1 ] ,
' type ' : row [ 2 ] ,
' active_ind ' : row [ 3 ] ,
' created_at ' : row [ 4 ] ,
' note ' : row [ 5 ] ,
2021-10-20 17:47:49 +00:00
' pubkey ' : row [ 6 ] ,
2021-10-19 18:59:18 +00:00
} )
return rv
finally :
2023-03-18 17:28:25 +00:00
self . closeSession ( session , commit = False )
def listSmsgAddresses ( self , use_type_str ) :
if use_type_str == ' offer_send_from ' :
use_type = AddressTypes . OFFER
elif use_type_str == ' offer_send_to ' :
use_type = AddressTypes . SEND_OFFER
elif use_type_str == ' bid ' :
use_type = AddressTypes . BID
else :
raise ValueError ( ' Unknown address type ' )
try :
session = self . openSession ( )
rv = [ ]
q = session . execute ( ' SELECT sa.addr, ki.label FROM smsgaddresses AS sa LEFT JOIN knownidentities AS ki ON sa.addr = ki.address WHERE sa.use_type = {} AND sa.active_ind = 1 ORDER BY sa.addr_id DESC ' . format ( use_type ) )
for row in q :
rv . append ( ( row [ 0 ] , row [ 1 ] ) )
return rv
finally :
self . closeSession ( session , commit = False )
2021-10-19 18:59:18 +00:00
2022-05-23 21:51:06 +00:00
def listAutomationStrategies ( self , filters = { } ) :
try :
2023-02-19 14:31:11 +00:00
session = self . openSession ( )
2022-05-23 21:51:06 +00:00
rv = [ ]
query_str = ' SELECT strats.record_id, strats.label, strats.type_ind FROM automationstrategies AS strats '
query_str + = ' WHERE strats.active_ind = 1 '
2022-05-31 22:38:50 +00:00
type_ind = filters . get ( ' type_ind ' , None )
if type_ind is not None :
query_str + = f ' AND strats.type_ind = { type_ind } '
2022-05-23 21:51:06 +00:00
sort_dir = filters . get ( ' sort_dir ' , ' DESC ' ) . upper ( )
sort_by = filters . get ( ' sort_by ' , ' created_at ' )
query_str + = f ' ORDER BY strats. { sort_by } { sort_dir } '
limit = filters . get ( ' limit ' , None )
if limit is not None :
query_str + = f ' LIMIT { limit } '
offset = filters . get ( ' offset ' , None )
if offset is not None :
query_str + = f ' OFFSET { offset } '
q = session . execute ( query_str )
for row in q :
rv . append ( row )
return rv
finally :
2023-02-19 14:31:11 +00:00
self . closeSession ( session , commit = False )
2022-05-23 21:51:06 +00:00
2023-02-17 23:47:44 +00:00
def getAutomationStrategy ( self , strategy_id : int ) :
2022-05-23 21:51:06 +00:00
try :
2023-02-17 23:47:44 +00:00
session = self . openSession ( )
2022-05-23 21:51:06 +00:00
return session . query ( AutomationStrategy ) . filter_by ( record_id = strategy_id ) . first ( )
finally :
2023-02-17 23:47:44 +00:00
self . closeSession ( session , commit = False )
def updateAutomationStrategy ( self , strategy_id : int , data , note : str ) - > None :
try :
session = self . openSession ( )
strategy = session . query ( AutomationStrategy ) . filter_by ( record_id = strategy_id ) . first ( )
strategy . data = json . dumps ( data ) . encode ( ' utf-8 ' )
strategy . note = note
session . add ( strategy )
finally :
self . closeSession ( session )
2022-05-23 21:51:06 +00:00
2023-02-19 14:31:11 +00:00
def getLinkedStrategy ( self , linked_type : int , linked_id ) :
2022-05-31 22:38:50 +00:00
try :
2023-02-19 14:31:11 +00:00
session = self . openSession ( )
2022-05-31 22:38:50 +00:00
query_str = ' SELECT links.strategy_id, strats.label FROM automationlinks links ' + \
' LEFT JOIN automationstrategies strats ON strats.record_id = links.strategy_id ' + \
' WHERE links.linked_type = {} AND links.linked_id = x \' {} \' AND links.active_ind = 1 ' . format ( int ( linked_type ) , linked_id . hex ( ) )
q = session . execute ( query_str ) . first ( )
return q
finally :
2023-02-19 14:31:11 +00:00
self . closeSession ( session , commit = False )
2022-05-31 22:38:50 +00:00
2021-11-26 01:13:20 +00:00
def newSMSGAddress ( self , use_type = AddressTypes . RECV_OFFER , addressnote = None , session = None ) :
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2021-10-19 18:59:18 +00:00
try :
2022-10-13 20:21:43 +00:00
use_session = self . openSession ( session )
2021-11-26 01:13:20 +00:00
v = use_session . query ( DBKVString ) . filter_by ( key = ' smsg_chain_id ' ) . first ( )
if not v :
smsg_account = self . callrpc ( ' extkey ' , [ ' deriveAccount ' , ' smsg keys ' , ' 78900 ' ] )
smsg_account_id = smsg_account [ ' account ' ]
self . log . info ( f ' Creating smsg keys account { smsg_account_id } ' )
extkey = self . callrpc ( ' extkey ' )
# Disable receiving on all chains
smsg_chain_id = None
extkey = self . callrpc ( ' extkey ' , [ ' account ' , smsg_account_id ] )
for c in extkey [ ' chains ' ] :
rv = self . callrpc ( ' extkey ' , [ ' options ' , c [ ' id ' ] , ' receive_on ' , ' false ' ] )
if c [ ' function ' ] == ' active_external ' :
smsg_chain_id = c [ ' id ' ]
if not smsg_chain_id :
raise ValueError ( ' External chain not found. ' )
use_session . add ( DBKVString (
key = ' smsg_chain_id ' ,
value = smsg_chain_id ) )
else :
smsg_chain_id = v . value
smsg_chain = self . callrpc ( ' extkey ' , [ ' key ' , smsg_chain_id ] )
num_derives = int ( smsg_chain [ ' num_derives ' ] )
new_addr = self . callrpc ( ' deriverangekeys ' , [ num_derives , num_derives , smsg_chain_id , False , True ] ) [ 0 ]
num_derives + = 1
rv = self . callrpc ( ' extkey ' , [ ' options ' , smsg_chain_id , ' num_derives ' , str ( num_derives ) ] )
2021-10-20 17:47:49 +00:00
addr_info = self . callrpc ( ' getaddressinfo ' , [ new_addr ] )
2021-10-19 18:59:18 +00:00
self . callrpc ( ' smsgaddlocaladdress ' , [ new_addr ] ) # Enable receiving smsgs
2023-02-16 20:57:55 +00:00
self . callrpc ( ' smsglocalkeys ' , [ ' anon ' , ' - ' , new_addr ] )
2021-10-19 18:59:18 +00:00
2021-11-26 01:13:20 +00:00
use_session . add ( SmsgAddress ( addr = new_addr , use_type = use_type , active_ind = 1 , created_at = now , note = addressnote , pubkey = addr_info [ ' pubkey ' ] ) )
2021-10-20 17:47:49 +00:00
return new_addr , addr_info [ ' pubkey ' ]
finally :
2021-11-26 01:13:20 +00:00
if session is None :
2022-10-13 20:21:43 +00:00
self . closeSession ( use_session )
2021-10-20 17:47:49 +00:00
2023-02-17 23:47:44 +00:00
def addSMSGAddress ( self , pubkey_hex : str , addressnote : str = None ) - > None :
2021-10-20 17:47:49 +00:00
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
2023-02-26 18:14:00 +00:00
now : int = self . getTime ( )
2021-10-20 17:47:49 +00:00
ci = self . ci ( Coins . PART )
add_addr = ci . pubkey_to_address ( bytes . fromhex ( pubkey_hex ) )
self . callrpc ( ' smsgaddaddress ' , [ add_addr , pubkey_hex ] )
2023-02-16 20:57:55 +00:00
self . callrpc ( ' smsglocalkeys ' , [ ' anon ' , ' - ' , add_addr ] )
2021-10-20 17:47:49 +00:00
session . add ( SmsgAddress ( addr = add_addr , use_type = AddressTypes . SEND_OFFER , active_ind = 1 , created_at = now , note = addressnote , pubkey = pubkey_hex ) )
session . commit ( )
return add_addr
2021-10-19 18:59:18 +00:00
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2023-02-17 23:47:44 +00:00
def editSMSGAddress ( self , address : str , active_ind : int , addressnote : str ) - > None :
2021-10-19 18:59:18 +00:00
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
mode = ' - ' if active_ind == 0 else ' + '
self . callrpc ( ' smsglocalkeys ' , [ ' recv ' , mode , address ] )
2023-02-16 20:57:55 +00:00
session . execute ( ' UPDATE smsgaddresses SET active_ind = :active_ind, note = :note WHERE addr = :addr ' , { ' active_ind ' : active_ind , ' note ' : addressnote , ' addr ' : address } )
2021-10-19 18:59:18 +00:00
session . commit ( )
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2020-11-27 17:52:26 +00:00
def createCoinALockRefundSwipeTx ( self , ci , bid , offer , xmr_swap , xmr_offer ) :
self . log . debug ( ' Creating %s lock refund swipe tx ' , ci . coin_name ( ) )
pkh_dest = ci . decodeAddress ( self . getReceiveAddressForCoin ( ci . coin_type ( ) ) )
2022-11-07 20:31:10 +00:00
spend_tx = ci . createSCLockRefundSpendToFTx (
2020-11-27 17:52:26 +00:00
xmr_swap . a_lock_refund_tx , xmr_swap . a_lock_refund_tx_script ,
pkh_dest ,
2021-11-01 13:52:40 +00:00
xmr_offer . a_fee_rate , xmr_swap . vkbv )
2020-11-27 17:52:26 +00:00
2021-12-19 06:59:35 +00:00
vkaf = self . getPathKey ( offer . coin_from , offer . coin_to , bid . created_at , xmr_swap . contract_count , KeyTypes . KAF )
2021-11-01 13:52:40 +00:00
prevout_amount = ci . getLockRefundTxSwapOutputValue ( bid , xmr_swap )
sig = ci . signTx ( vkaf , spend_tx , 0 , xmr_swap . a_lock_refund_tx_script , prevout_amount )
2020-11-27 17:52:26 +00:00
witness_stack = [
sig ,
b ' ' ,
xmr_swap . a_lock_refund_tx_script ,
]
xmr_swap . a_lock_refund_swipe_tx = ci . setTxSignature ( spend_tx , witness_stack )
def setBidDebugInd ( self , bid_id , debug_ind ) :
self . log . debug ( ' Bid %s Setting debug flag: %s ' , bid_id . hex ( ) , debug_ind )
bid = self . getBid ( bid_id )
bid . debug_ind = debug_ind
2022-07-20 22:27:22 +00:00
# Update in memory copy. TODO: Improve
bid_in_progress = self . swaps_in_progress . get ( bid_id , None )
if bid_in_progress :
bid_in_progress [ 0 ] . debug_ind = debug_ind
2020-11-27 17:52:26 +00:00
self . saveBid ( bid_id , bid )
2020-12-11 08:41:57 +00:00
def storeOfferRevoke ( self , offer_id , sig ) :
self . log . debug ( ' Storing revoke request for offer: %s ' , offer_id . hex ( ) )
for pair in self . _possibly_revoked_offers :
if offer_id == pair [ 0 ] :
return False
self . _possibly_revoked_offers . appendleft ( ( offer_id , sig ) )
return True
def isOfferRevoked ( self , offer_id , offer_addr_from ) :
for pair in self . _possibly_revoked_offers :
if offer_id == pair [ 0 ] :
signature_enc = base64 . b64encode ( pair [ 1 ] ) . decode ( ' utf-8 ' )
passed = self . callcoinrpc ( Coins . PART , ' verifymessage ' , [ offer_addr_from , signature_enc , offer_id . hex ( ) + ' _revoke ' ] )
return True if passed is True else False # _possibly_revoked_offers should not contain duplicates
return False
2020-12-15 18:00:44 +00:00
2021-01-08 22:12:08 +00:00
def updateBidInProgress ( self , bid ) :
swap_in_progress = self . swaps_in_progress . get ( bid . bid_id , None )
if swap_in_progress is None :
return
self . swaps_in_progress [ bid . bid_id ] = ( bid , swap_in_progress [ 1 ] )
2021-12-05 23:06:34 +00:00
def getAddressLabel ( self , addresses ) :
self . mxDB . acquire ( )
try :
session = scoped_session ( self . session_factory )
rv = [ ]
for a in addresses :
v = session . query ( KnownIdentity ) . filter_by ( address = a ) . first ( )
2021-12-10 07:50:36 +00:00
rv . append ( ' ' if ( not v or not v . label ) else v . label )
2021-12-05 23:06:34 +00:00
return rv
finally :
session . close ( )
session . remove ( )
self . mxDB . release ( )
2020-12-15 18:00:44 +00:00
def add_connection ( self , host , port , peer_pubkey ) :
self . log . info ( ' add_connection %s %d %s ' , host , port , peer_pubkey . hex ( ) )
self . _network . add_connection ( host , port , peer_pubkey )
2020-12-18 21:04:06 +00:00
def get_network_info ( self ) :
if not self . _network :
return { ' Error ' : ' Not Initialised ' }
return self . _network . get_info ( )
2021-11-21 20:59:39 +00:00
2022-11-18 21:31:52 +00:00
def getLockedState ( self ) :
if self . _is_encrypted is None or self . _is_locked is None :
self . _is_encrypted , self . _is_locked = self . ci ( Coins . PART ) . isWalletEncryptedLocked ( )
return self . _is_encrypted , self . _is_locked
2022-08-03 21:59:57 +00:00
def lookupRates ( self , coin_from , coin_to , output_array = False ) :
2021-11-22 20:24:48 +00:00
self . log . debug ( ' lookupRates {} , {} ' . format ( coin_from , coin_to ) )
2022-07-28 15:01:11 +00:00
2022-08-03 21:59:57 +00:00
rate_sources = self . settings . get ( ' rate_sources ' , { } )
ci_from = self . ci ( int ( coin_from ) )
ci_to = self . ci ( int ( coin_to ) )
name_from = ci_from . chainparams ( ) [ ' name ' ]
name_to = ci_to . chainparams ( ) [ ' name ' ]
2022-11-15 21:50:36 +00:00
exchange_name_from = ci_from . getExchangeName ( ' coingecko.com ' )
exchange_name_to = ci_to . getExchangeName ( ' coingecko.com ' )
2022-08-03 21:59:57 +00:00
ticker_from = ci_from . chainparams ( ) [ ' ticker ' ]
ticker_to = ci_to . chainparams ( ) [ ' ticker ' ]
headers = { ' Connection ' : ' close ' }
2023-02-26 20:42:44 +00:00
rv = { }
2022-03-26 22:08:15 +00:00
2023-02-26 20:42:44 +00:00
if rate_sources . get ( ' coingecko.com ' , True ) :
try :
url = ' https://api.coingecko.com/api/v3/simple/price?ids= {} , {} &vs_currencies=usd,btc ' . format ( exchange_name_from , exchange_name_to )
self . log . debug ( f ' lookupRates: { url } ' )
start = time . time ( )
js = json . loads ( self . readURL ( url , timeout = 10 , headers = headers ) )
js [ ' time_taken ' ] = time . time ( ) - start
rate = float ( js [ exchange_name_from ] [ ' usd ' ] ) / float ( js [ exchange_name_to ] [ ' usd ' ] )
js [ ' rate_inferred ' ] = ci_to . format_amount ( rate , conv_int = True , r = 1 )
rv [ ' coingecko ' ] = js
except Exception as e :
rv [ ' coingecko_error ' ] = str ( e )
if self . debug :
self . log . error ( traceback . format_exc ( ) )
if exchange_name_from != name_from :
js [ name_from ] = js [ exchange_name_from ]
js . pop ( exchange_name_from )
if exchange_name_to != name_to :
js [ name_to ] = js [ exchange_name_to ]
js . pop ( exchange_name_to )
if rate_sources . get ( ' bittrex.com ' , True ) :
bittrex_api_v3 = ' https://api.bittrex.com/v3 '
try :
exchange_ticker_to = ci_to . getExchangeTicker ( ' bittrex.com ' )
exchange_ticker_from = ci_from . getExchangeTicker ( ' bittrex.com ' )
USDT_coins = ( Coins . FIRO , )
# TODO: How to compare USDT pairs with BTC pairs
if ci_from . coin_type ( ) in USDT_coins :
raise ValueError ( ' No BTC pair ' )
if ci_to . coin_type ( ) in USDT_coins :
raise ValueError ( ' No BTC pair ' )
if ci_from . coin_type ( ) == Coins . BTC :
pair = f ' { exchange_ticker_to } - { exchange_ticker_from } '
url = f ' { bittrex_api_v3 } /markets/ { pair } /ticker '
2022-07-28 15:01:11 +00:00
self . log . debug ( f ' lookupRates: { url } ' )
start = time . time ( )
2023-02-26 20:42:44 +00:00
js = json . loads ( self . readURL ( url , timeout = 10 , headers = headers ) )
2022-07-28 15:01:11 +00:00
js [ ' time_taken ' ] = time . time ( ) - start
2023-02-26 20:42:44 +00:00
js [ ' pair ' ] = pair
try :
rate_inverted = ci_from . make_int ( 1.0 / float ( js [ ' lastTradeRate ' ] ) , r = 1 )
js [ ' rate_inferred ' ] = ci_to . format_amount ( rate_inverted )
except Exception as e :
self . log . warning ( ' lookupRates error: %s ' , str ( e ) )
js [ ' rate_inferred ' ] = ' error '
js [ ' from_btc ' ] = 1.0
js [ ' to_btc ' ] = js [ ' lastTradeRate ' ]
rv [ ' bittrex ' ] = js
elif ci_to . coin_type ( ) == Coins . BTC :
pair = f ' { exchange_ticker_from } - { exchange_ticker_to } '
url = f ' { bittrex_api_v3 } /markets/ { pair } /ticker '
self . log . debug ( f ' lookupRates: { url } ' )
start = time . time ( )
js = json . loads ( self . readURL ( url , timeout = 10 , headers = headers ) )
js [ ' time_taken ' ] = time . time ( ) - start
js [ ' pair ' ] = pair
js [ ' rate_last ' ] = js [ ' lastTradeRate ' ]
js [ ' from_btc ' ] = js [ ' lastTradeRate ' ]
js [ ' to_btc ' ] = 1.0
rv [ ' bittrex ' ] = js
else :
pair = f ' { exchange_ticker_from } -BTC '
url = f ' { bittrex_api_v3 } /markets/ { pair } /ticker '
self . log . debug ( f ' lookupRates: { url } ' )
start = time . time ( )
js_from = json . loads ( self . readURL ( url , timeout = 10 , headers = headers ) )
js_from [ ' time_taken ' ] = time . time ( ) - start
js_from [ ' pair ' ] = pair
2021-11-22 20:24:48 +00:00
2023-02-26 20:42:44 +00:00
pair = f ' { exchange_ticker_to } -BTC '
url = f ' { bittrex_api_v3 } /markets/ { pair } /ticker '
self . log . debug ( f ' lookupRates: { url } ' )
start = time . time ( )
js_to = json . loads ( self . readURL ( url , timeout = 10 , headers = headers ) )
js_to [ ' time_taken ' ] = time . time ( ) - start
js_to [ ' pair ' ] = pair
2022-08-03 21:59:57 +00:00
2023-02-26 20:42:44 +00:00
try :
rate_inferred = float ( js_from [ ' lastTradeRate ' ] ) / float ( js_to [ ' lastTradeRate ' ] )
rate_inferred = ci_to . format_amount ( rate , conv_int = True , r = 1 )
except Exception as e :
rate_inferred = ' error '
rv [ ' bittrex ' ] = {
' from ' : js_from ,
' to ' : js_to ,
' rate_inferred ' : rate_inferred ,
' from_btc ' : js_from [ ' lastTradeRate ' ] ,
' to_btc ' : js_to [ ' lastTradeRate ' ]
}
except Exception as e :
rv [ ' bittrex_error ' ] = str ( e )
if self . debug :
self . log . error ( traceback . format_exc ( ) )
if output_array :
def format_float ( f ) :
return ' {:.12f} ' . format ( f ) . rstrip ( ' 0 ' ) . rstrip ( ' . ' )
rv_array = [ ]
if ' coingecko_error ' in rv :
rv_array . append ( ( ' coingecko.com ' , ' error ' , rv [ ' coingecko_error ' ] ) )
if ' coingecko ' in rv :
js = rv [ ' coingecko ' ]
rv_array . append ( (
' coingecko.com ' ,
ticker_from ,
ticker_to ,
format_float ( float ( js [ name_from ] [ ' usd ' ] ) ) ,
format_float ( float ( js [ name_to ] [ ' usd ' ] ) ) ,
format_float ( float ( js [ name_from ] [ ' btc ' ] ) ) ,
format_float ( float ( js [ name_to ] [ ' btc ' ] ) ) ,
format_float ( float ( js [ ' rate_inferred ' ] ) ) ,
) )
if ' bittrex_error ' in rv :
rv_array . append ( ( ' bittrex.com ' , ' error ' , rv [ ' bittrex_error ' ] ) )
if ' bittrex ' in rv :
js = rv [ ' bittrex ' ]
rate = js [ ' rate_last ' ] if ' rate_last ' in js else js [ ' rate_inferred ' ]
rv_array . append ( (
' bittrex.com ' ,
ticker_from ,
ticker_to ,
' ' ,
' ' ,
format_float ( float ( js [ ' from_btc ' ] ) ) ,
format_float ( float ( js [ ' to_btc ' ] ) ) ,
format_float ( float ( rate ) )
) )
return rv_array
2021-11-21 20:59:39 +00:00
2023-02-26 20:42:44 +00:00
return rv
2023-05-10 15:50:40 +00:00
def setFilters ( self , prefix , filters ) :
try :
session = self . openSession ( )
key_str = ' saved_filters_ ' + prefix
value_str = json . dumps ( filters )
self . setStringKV ( key_str , value_str , session )
finally :
self . closeSession ( session )
def getFilters ( self , prefix ) :
try :
session = self . openSession ( )
key_str = ' saved_filters_ ' + prefix
value_str = self . getStringKV ( key_str , session )
return None if not value_str else json . loads ( value_str )
finally :
self . closeSession ( session , commit = False )
def clearFilters ( self , prefix ) :
try :
session = self . openSession ( )
key_str = ' saved_filters_ ' + prefix
query_str = ' DELETE FROM kv_string WHERE key = :key_str '
session . execute ( query_str , { ' key_str ' : key_str } )
finally :
self . closeSession ( session )