16 Commits

Author SHA1 Message Date
tecnovert
247f23cb4a Integrate Decred with wallet encryption.
dcrwallet requires the password to be entered at the first startup when encrypted.
basicswap-run with --startonlycoin=decred and the WALLET_ENCRYPTION_PWD environment var set can be used for the initial sync.
2024-05-22 09:59:57 +02:00
tecnovert
3e16ea487c ui: Improve rpc page usability for Decred. 2024-05-21 15:24:01 +02:00
tecnovert
c24a20b38d Decred: test_010_txn_size 2024-05-20 18:27:47 +02:00
tecnovert
26eaa2f0b0 Decred: Add to test_xmr_persistent 2024-05-20 16:29:14 +02:00
tecnovert
614fc9ccbd Decred xmr swap tests. 2024-05-19 17:49:53 +02:00
tecnovert
a3f5bc1a5a Decred: Secret hash swap tests. 2024-05-15 11:39:32 +02:00
tecnovert
026b222e90 Decred test_008_gettxout 2024-05-09 02:03:12 +02:00
tecnovert
c640836fbf Decred CSV test. 2024-05-09 02:03:12 +02:00
tecnovert
154c6d6832 Decred sighash and signing. 2024-05-09 02:03:12 +02:00
tecnovert
3db723056f Add Decred transaction to and from bytes. 2024-05-09 02:03:11 +02:00
tecnovert
f1822e1443 Get Decred account key from seed. 2024-05-09 02:03:11 +02:00
tecnovert
89ca350ff2 Add Decred rpc 2024-05-09 02:03:11 +02:00
tecnovert
6afbd55aec tests: Start dcrd 2024-05-09 02:03:10 +02:00
tecnovert
443b7f9c51 prepare: Download, verify and extract Decred binaries 2024-05-09 02:03:10 +02:00
tecnovert
ee239aba0b Encode and decode Decred addresses. 2024-05-09 02:03:10 +02:00
tecnovert
3ba5532fa0 Add Decred chainparams. 2024-05-09 02:03:10 +02:00
59 changed files with 5694 additions and 1117 deletions

View File

@@ -1,3 +1,3 @@
name = "basicswap"
__version__ = "0.13.0"
__version__ = "0.13.1"

File diff suppressed because it is too large Load Diff

View File

@@ -202,6 +202,8 @@ class DebugTypes(IntEnum):
SEND_LOCKED_XMR = auto()
B_LOCK_TX_MISSED_SEND = auto()
DUPLICATE_ACTIONS = auto()
DONT_CONFIRM_PTX = auto()
OFFER_LOCK_2_VALUE_INC = auto()
class NotificationTypes(IntEnum):

View File

@@ -1,17 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-2023 tecnovert
# Copyright (c) 2019-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import threading
from enum import IntEnum
from .util import (
COIN,
make_int,
format_amount,
TemporaryError,
)
XMR_COIN = 10 ** 12
@@ -21,7 +16,7 @@ class Coins(IntEnum):
PART = 1
BTC = 2
LTC = 3
# DCR = 4
DCR = 4
NMC = 5
XMR = 6
PART_BLIND = 7
@@ -155,6 +150,41 @@ chainparams = {
'max_amount': 100000 * COIN,
}
},
Coins.DCR: {
'name': 'decred',
'ticker': 'DCR',
'message_magic': 'Decred Signed Message:\n',
'blocks_target': 60 * 5,
'decimal_places': 8,
'mainnet': {
'rpcport': 9109,
'pubkey_address': 0x073f,
'script_address': 0x071a,
'key_prefix': 0x22de,
'bip44': 42,
'min_amount': 1000,
'max_amount': 100000 * COIN,
},
'testnet': {
'rpcport': 19109,
'pubkey_address': 0x0f21,
'script_address': 0x0efc,
'key_prefix': 0x230e,
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
'name': 'testnet3',
},
'regtest': { # simnet
'rpcport': 18656,
'pubkey_address': 0x0e91,
'script_address': 0x0e6c,
'key_prefix': 0x2307,
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
},
Coins.NMC: {
'name': 'namecoin',
'ticker': 'NMC',
@@ -387,89 +417,3 @@ def getCoinIdFromTicker(ticker):
return ticker_map[ticker.lower()]
except Exception:
raise ValueError('Unknown coin')
class CoinInterface:
def __init__(self, network):
self.setDefaults()
self._network = network
self._mx_wallet = threading.Lock()
def setDefaults(self):
self._unknown_wallet_seed = True
self._restore_height = None
def make_int(self, amount_in: int, r: int = 0) -> int:
return make_int(amount_in, self.exp(), r=r)
def format_amount(self, amount_in, conv_int=False, r=0):
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
return format_amount(amount_int, self.exp())
def coin_name(self) -> str:
coin_chainparams = chainparams[self.coin_type()]
if coin_chainparams.get('use_ticker_as_name', False):
return coin_chainparams['ticker']
return coin_chainparams['name'].capitalize()
def ticker(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
if self._network == 'testnet':
ticker = 't' + ticker
elif self._network == 'regtest':
ticker = 'rt' + ticker
return ticker
def getExchangeTicker(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['ticker']
def getExchangeName(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['name']
def ticker_mainnet(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
return ticker
def min_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['min_amount']
def max_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['max_amount']
def setWalletSeedWarning(self, value: bool) -> None:
self._unknown_wallet_seed = value
def setWalletRestoreHeight(self, value: int) -> None:
self._restore_height = value
def knownWalletSeed(self) -> bool:
return not self._unknown_wallet_seed
def chainparams(self):
return chainparams[self.coin_type()]
def chainparams_network(self):
return chainparams[self.coin_type()][self._network]
def has_segwit(self) -> bool:
return chainparams[self.coin_type()].get('has_segwit', True)
def is_transient_error(self, ex) -> bool:
if isinstance(ex, TemporaryError):
return True
str_error: str = str(ex).lower()
if 'not enough unlocked money' in str_error:
return True
if 'no unlocked balance' in str_error:
return True
if 'transaction was rejected by daemon' in str_error:
return True
if 'invalid unlocked_balance' in str_error:
return True
if 'daemon is busy' in str_error:
return True
if 'timed out' in str_error:
return True
if 'request-sent' in str_error:
return True
return False

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,533 @@
intro = """
blake.py
version 5, 2-Apr-2014
BLAKE is a SHA3 round-3 finalist designed and submitted by
Jean-Philippe Aumasson et al.
At the core of BLAKE is a ChaCha-like mixer, very similar
to that found in the stream cipher, ChaCha8. Besides being
a very good mixer, ChaCha is fast.
References:
http://www.131002.net/blake/
http://csrc.nist.gov/groups/ST/hash/sha-3/index.html
http://en.wikipedia.org/wiki/BLAKE_(hash_function)
This implementation assumes all data is in increments of
whole bytes. (The formal definition of BLAKE allows for
hashing individual bits.) Note too that this implementation
does include the round-3 tweaks where the number of rounds
was increased to 14/16 from 10/14.
This version can be imported into both Python2 (2.6 and 2.7)
and Python3 programs. Python 2.5 requires an older version
of blake.py (version 4).
Here are some comparative times for different versions of
Python:
64-bit:
2.6 6.284s
2.7 6.343s
3.2 7.620s
pypy (2.7) 2.080s
32-bit:
2.5 (32) 15.389s (with psyco)
2.7-32 13.645s
3.2-32 12.574s
One test on a 2.0GHz Core 2 Duo of 10,000 iterations of
BLAKE-256 on a short message produced a time of 5.7 seconds.
Not bad, but if raw speed is what you want, look to the
the C version. It is 40x faster and did the same thing
in 0.13 seconds.
Copyright (c) 2009-2012 by Larry Bugbee, Kent, WA
ALL RIGHTS RESERVED.
blake.py IS EXPERIMENTAL SOFTWARE FOR EDUCATIONAL
PURPOSES ONLY. IT IS MADE AVAILABLE "AS-IS" WITHOUT
WARRANTY OR GUARANTEE OF ANY KIND. USE SIGNIFIES
ACCEPTANCE OF ALL RISK.
To make your learning and experimentation less cumbersome,
blake.py is free for any use.
Enjoy,
Larry Bugbee
March 2011
rev May 2011 - fixed Python version check (tx JP)
rev Apr 2012 - fixed an out-of-order bit set in final()
- moved self-test to a separate test pgm
- this now works with Python2 and Python3
rev Apr 2014 - added test and conversion of string input
to byte string in update() (tx Soham)
- added hexdigest() method.
- now support state 3 so only one call to
final() per instantiation is allowed. all
subsequent calls to final(), digest() or
hexdigest() simply return the stored value.
"""
import struct
from binascii import hexlify, unhexlify
#---------------------------------------------------------------
class BLAKE(object):
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
# initial values, constants and padding
# IVx for BLAKE-x
IV64 = [
0x6A09E667F3BCC908, 0xBB67AE8584CAA73B,
0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1,
0x510E527FADE682D1, 0x9B05688C2B3E6C1F,
0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179,
]
IV48 = [
0xCBBB9D5DC1059ED8, 0x629A292A367CD507,
0x9159015A3070DD17, 0x152FECD8F70E5939,
0x67332667FFC00B31, 0x8EB44A8768581511,
0xDB0C2E0D64F98FA7, 0x47B5481DBEFA4FA4,
]
# note: the values here are the same as the high-order
# half-words of IV64
IV32 = [
0x6A09E667, 0xBB67AE85,
0x3C6EF372, 0xA54FF53A,
0x510E527F, 0x9B05688C,
0x1F83D9AB, 0x5BE0CD19,
]
# note: the values here are the same as the low-order
# half-words of IV48
IV28 = [
0xC1059ED8, 0x367CD507,
0x3070DD17, 0xF70E5939,
0xFFC00B31, 0x68581511,
0x64F98FA7, 0xBEFA4FA4,
]
# constants for BLAKE-64 and BLAKE-48
C64 = [
0x243F6A8885A308D3, 0x13198A2E03707344,
0xA4093822299F31D0, 0x082EFA98EC4E6C89,
0x452821E638D01377, 0xBE5466CF34E90C6C,
0xC0AC29B7C97C50DD, 0x3F84D5B5B5470917,
0x9216D5D98979FB1B, 0xD1310BA698DFB5AC,
0x2FFD72DBD01ADFB7, 0xB8E1AFED6A267E96,
0xBA7C9045F12C7F99, 0x24A19947B3916CF7,
0x0801F2E2858EFC16, 0x636920D871574E69,
]
# constants for BLAKE-32 and BLAKE-28
# note: concatenate and the values are the same as the values
# for the 1st half of C64
C32 = [
0x243F6A88, 0x85A308D3,
0x13198A2E, 0x03707344,
0xA4093822, 0x299F31D0,
0x082EFA98, 0xEC4E6C89,
0x452821E6, 0x38D01377,
0xBE5466CF, 0x34E90C6C,
0xC0AC29B7, 0xC97C50DD,
0x3F84D5B5, 0xB5470917,
]
# the 10 permutations of:0,...15}
SIGMA = [
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15],
[14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3],
[11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4],
[ 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8],
[ 9, 0, 5, 7, 2, 4,10,15,14, 1,11,12, 6, 8, 3,13],
[ 2,12, 6,10, 0,11, 8, 3, 4,13, 7, 5,15,14, 1, 9],
[12, 5, 1,15,14,13, 4,10, 0, 7, 6, 3, 9, 2, 8,11],
[13,11, 7,14,12, 1, 3, 9, 5, 0,15, 4, 8, 6, 2,10],
[ 6,15,14, 9,11, 3, 0, 8,12, 2,13, 7, 1, 4,10, 5],
[10, 2, 8, 4, 7, 6, 1, 5,15,11, 9,14, 3,12,13, 0],
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15],
[14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3],
[11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4],
[ 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8],
[ 9, 0, 5, 7, 2, 4,10,15,14, 1,11,12, 6, 8, 3,13],
[ 2,12, 6,10, 0,11, 8, 3, 4,13, 7, 5,15,14, 1, 9],
[12, 5, 1,15,14,13, 4,10, 0, 7, 6, 3, 9, 2, 8,11],
[13,11, 7,14,12, 1, 3, 9, 5, 0,15, 4, 8, 6, 2,10],
[ 6,15,14, 9,11, 3, 0, 8,12, 2,13, 7, 1, 4,10, 5],
[10, 2, 8, 4, 7, 6, 1, 5,15,11, 9,14, 3,12,13, 0],
]
MASK32BITS = 0xFFFFFFFF
MASK64BITS = 0xFFFFFFFFFFFFFFFF
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
def __init__(self, hashbitlen):
"""
load the hashSate structure (copy hashbitlen...)
hashbitlen: length of the hash output
"""
if hashbitlen not in [224, 256, 384, 512]:
raise Exception('hash length not 224, 256, 384 or 512')
self.hashbitlen = hashbitlen
self.h = [0]*8 # current chain value (initialized to the IV)
self.t = 0 # number of *BITS* hashed so far
self.cache = b'' # cached leftover data not yet compressed
self.salt = [0]*4 # salt (null by default)
self.state = 1 # set to 2 by update and 3 by final
self.nullt = 0 # Boolean value for special case \ell_i=0
# The algorithm is the same for both the 32- and 64- versions
# of BLAKE. The difference is in word size (4 vs 8 bytes),
# blocksize (64 vs 128 bytes), number of rounds (14 vs 16)
# and a few very specific constants.
if (hashbitlen == 224) or (hashbitlen == 256):
# setup for 32-bit words and 64-bit block
self.byte2int = self._fourByte2int
self.int2byte = self._int2fourByte
self.MASK = self.MASK32BITS
self.WORDBYTES = 4
self.WORDBITS = 32
self.BLKBYTES = 64
self.BLKBITS = 512
self.ROUNDS = 14 # was 10 before round 3
self.cxx = self.C32
self.rot1 = 16 # num bits to shift in G
self.rot2 = 12 # num bits to shift in G
self.rot3 = 8 # num bits to shift in G
self.rot4 = 7 # num bits to shift in G
self.mul = 0 # for 32-bit words, 32<<self.mul where self.mul = 0
# 224- and 256-bit versions (32-bit words)
if hashbitlen == 224:
self.h = self.IV28[:]
else:
self.h = self.IV32[:]
elif (hashbitlen == 384) or (hashbitlen == 512):
# setup for 64-bit words and 128-bit block
self.byte2int = self._eightByte2int
self.int2byte = self._int2eightByte
self.MASK = self.MASK64BITS
self.WORDBYTES = 8
self.WORDBITS = 64
self.BLKBYTES = 128
self.BLKBITS = 1024
self.ROUNDS = 16 # was 14 before round 3
self.cxx = self.C64
self.rot1 = 32 # num bits to shift in G
self.rot2 = 25 # num bits to shift in G
self.rot3 = 16 # num bits to shift in G
self.rot4 = 11 # num bits to shift in G
self.mul = 1 # for 64-bit words, 32<<self.mul where self.mul = 1
# 384- and 512-bit versions (64-bit words)
if hashbitlen == 384:
self.h = self.IV48[:]
else:
self.h = self.IV64[:]
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
def _compress(self, block):
byte2int = self.byte2int
mul = self.mul # de-reference these for ...speed? ;-)
cxx = self.cxx
rot1 = self.rot1
rot2 = self.rot2
rot3 = self.rot3
rot4 = self.rot4
MASK = self.MASK
WORDBITS = self.WORDBITS
SIGMA = self.SIGMA
# get message (<<2 is the same as *4 but faster)
m = [byte2int(block[i<<2<<mul:(i<<2<<mul)+(4<<mul)]) for i in range(16)]
# initialization
v = [0]*16
v[ 0: 8] = [self.h[i] for i in range(8)]
v[ 8:16] = [self.cxx[i] for i in range(8)]
v[ 8:12] = [v[8+i] ^ self.salt[i] for i in range(4)]
if self.nullt == 0: # (i>>1 is the same as i/2 but faster)
v[12] = v[12] ^ (self.t & MASK)
v[13] = v[13] ^ (self.t & MASK)
v[14] = v[14] ^ (self.t >> self.WORDBITS)
v[15] = v[15] ^ (self.t >> self.WORDBITS)
# - - - - - - - - - - - - - - - - -
# ready? let's ChaCha!!!
def G(a, b, c, d, i):
va = v[a] # it's faster to deref and reref later
vb = v[b]
vc = v[c]
vd = v[d]
sri = SIGMA[round][i]
sri1 = SIGMA[round][i+1]
va = ((va + vb) + (m[sri] ^ cxx[sri1]) ) & MASK
x = vd ^ va
vd = (x >> rot1) | ((x << (WORDBITS-rot1)) & MASK)
vc = (vc + vd) & MASK
x = vb ^ vc
vb = (x >> rot2) | ((x << (WORDBITS-rot2)) & MASK)
va = ((va + vb) + (m[sri1] ^ cxx[sri]) ) & MASK
x = vd ^ va
vd = (x >> rot3) | ((x << (WORDBITS-rot3)) & MASK)
vc = (vc + vd) & MASK
x = vb ^ vc
vb = (x >> rot4) | ((x << (WORDBITS-rot4)) & MASK)
v[a] = va
v[b] = vb
v[c] = vc
v[d] = vd
for round in range(self.ROUNDS):
# column step
G( 0, 4, 8,12, 0)
G( 1, 5, 9,13, 2)
G( 2, 6,10,14, 4)
G( 3, 7,11,15, 6)
# diagonal step
G( 0, 5,10,15, 8)
G( 1, 6,11,12,10)
G( 2, 7, 8,13,12)
G( 3, 4, 9,14,14)
# - - - - - - - - - - - - - - - - -
# save current hash value (use i&0x3 to get 0,1,2,3,0,1,2,3)
self.h = [self.h[i]^v[i]^v[i+8]^self.salt[i&0x3]
for i in range(8)]
# print 'self.h', [num2hex(h) for h in self.h]
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
def addsalt(self, salt):
""" adds a salt to the hash function (OPTIONAL)
should be called AFTER Init, and BEFORE update
salt: a bytestring, length determined by hashbitlen.
if not of sufficient length, the bytestring
will be assumed to be a big endian number and
prefixed with an appropriate number of null
bytes, and if too large, only the low order
bytes will be used.
if hashbitlen=224 or 256, then salt will be 16 bytes
if hashbitlen=384 or 512, then salt will be 32 bytes
"""
# fail if addsalt() was not called at the right time
if self.state != 1:
raise Exception('addsalt() not called after init() and before update()')
# salt size is to be 4x word size
saltsize = self.WORDBYTES * 4
# if too short, prefix with null bytes. if too long,
# truncate high order bytes
if len(salt) < saltsize:
salt = (chr(0)*(saltsize-len(salt)) + salt)
else:
salt = salt[-saltsize:]
# prep the salt array
self.salt[0] = self.byte2int(salt[ : 4<<self.mul])
self.salt[1] = self.byte2int(salt[ 4<<self.mul: 8<<self.mul])
self.salt[2] = self.byte2int(salt[ 8<<self.mul:12<<self.mul])
self.salt[3] = self.byte2int(salt[12<<self.mul: ])
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
def update(self, data):
""" update the state with new data, storing excess data
as necessary. may be called multiple times and if a
call sends less than a full block in size, the leftover
is cached and will be consumed in the next call
data: data to be hashed (bytestring)
"""
self.state = 2
BLKBYTES = self.BLKBYTES # de-referenced for improved readability
BLKBITS = self.BLKBITS
datalen = len(data)
if not datalen: return
if type(data) == type(u''):
# use either of the next two lines for a proper
# response under both Python2 and Python3
data = data.encode('UTF-8') # converts to byte string
#data = bytearray(data, 'utf-8') # use if want mutable
# This next line works for Py3 but fails under
# Py2 because the Py2 version of bytes() will
# accept only *one* argument. Arrrrgh!!!
#data = bytes(data, 'utf-8') # converts to immutable byte
# string but... under p7
# bytes() wants only 1 arg
# ...a dummy, 2nd argument like encoding=None
# that does nothing would at least allow
# compatibility between Python2 and Python3.
left = len(self.cache)
fill = BLKBYTES - left
# if any cached data and any added new data will fill a
# full block, fill and compress
if left and datalen >= fill:
self.cache = self.cache + data[:fill]
self.t += BLKBITS # update counter
self._compress(self.cache)
self.cache = b''
data = data[fill:]
datalen -= fill
# compress new data until not enough for a full block
while datalen >= BLKBYTES:
self.t += BLKBITS # update counter
self._compress(data[:BLKBYTES])
data = data[BLKBYTES:]
datalen -= BLKBYTES
# cache all leftover bytes until next call to update()
if datalen > 0:
self.cache = self.cache + data[:datalen]
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
def final(self, data=''):
""" finalize the hash -- pad and hash remaining data
returns hashval, the digest
"""
if self.state == 3:
# we have already finalized so simply return the
# previously calculated/stored hash value
return self.hash
if data:
self.update(data)
ZZ = b'\x00'
ZO = b'\x01'
OZ = b'\x80'
OO = b'\x81'
PADDING = OZ + ZZ*128 # pre-formatted padding data
# copy nb. bits hash in total as a 64-bit BE word
# copy nb. bits hash in total as a 128-bit BE word
tt = self.t + (len(self.cache) << 3)
if self.BLKBYTES == 64:
msglen = self._int2eightByte(tt)
else:
low = tt & self.MASK
high = tt >> self.WORDBITS
msglen = self._int2eightByte(high) + self._int2eightByte(low)
# size of block without the words at the end that count
# the number of bits, 55 or 111.
# Note: (((self.WORDBITS/8)*2)+1) equals ((self.WORDBITS>>2)+1)
sizewithout = self.BLKBYTES - ((self.WORDBITS>>2)+1)
if len(self.cache) == sizewithout:
# special case of one padding byte
self.t -= 8
if self.hashbitlen in [224, 384]:
self.update(OZ)
else:
self.update(OO)
else:
if len(self.cache) < sizewithout:
# enough space to fill the block
# use t=0 if no remaining data
if len(self.cache) == 0:
self.nullt=1
self.t -= (sizewithout - len(self.cache)) << 3
self.update(PADDING[:sizewithout - len(self.cache)])
else:
# NOT enough space, need 2 compressions
# ...add marker, pad with nulls and compress
self.t -= (self.BLKBYTES - len(self.cache)) << 3
self.update(PADDING[:self.BLKBYTES - len(self.cache)])
# ...now pad w/nulls leaving space for marker & bit count
self.t -= (sizewithout+1) << 3
self.update(PADDING[1:sizewithout+1]) # pad with zeroes
self.nullt = 1 # raise flag to set t=0 at the next _compress
# append a marker byte
if self.hashbitlen in [224, 384]:
self.update(ZZ)
else:
self.update(ZO)
self.t -= 8
# append the number of bits (long long)
self.t -= self.BLKBYTES
self.update(msglen)
hashval = []
if self.BLKBYTES == 64:
for h in self.h:
hashval.append(self._int2fourByte(h))
else:
for h in self.h:
hashval.append(self._int2eightByte(h))
self.hash = b''.join(hashval)[:self.hashbitlen >> 3]
self.state = 3
return self.hash
digest = final # may use digest() as a synonym for final()
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
def hexdigest(self, data=''):
return hexlify(self.final(data)).decode('UTF-8')
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# utility functions
def _fourByte2int(self, bytestr): # see also long2byt() below
""" convert a 4-byte string to an int (long) """
return struct.unpack('!L', bytestr)[0]
def _eightByte2int(self, bytestr):
""" convert a 8-byte string to an int (long long) """
return struct.unpack('!Q', bytestr)[0]
def _int2fourByte(self, x): # see also long2byt() below
""" convert a number to a 4-byte string, high order
truncation possible (in Python x could be a BIGNUM)
"""
return struct.pack('!L', x)
def _int2eightByte(self, x):
""" convert a number to a 8-byte string, high order
truncation possible (in Python x could be a BIGNUM)
"""
return struct.pack('!Q', x)
#---------------------------------------------------------------
#---------------------------------------------------------------
#---------------------------------------------------------------
def blake_hash(data):
return BLAKE(256).digest(data)

View File

@@ -0,0 +1,37 @@
from blake256 import blake_hash
testVectors = [
["716f6e863f744b9ac22c97ec7b76ea5f5908bc5b2f67c61510bfc4751384ea7a", ""],
["43234ff894a9c0590d0246cfc574eb781a80958b01d7a2fa1ac73c673ba5e311", "a"],
["658c6d9019a1deddbcb3640a066dfd23471553a307ab941fd3e677ba887be329", "ab"],
["1833a9fa7cf4086bd5fda73da32e5a1d75b4c3f89d5c436369f9d78bb2da5c28", "abc"],
["35282468f3b93c5aaca6408582fced36e578f67671ed0741c332d68ac72d7aa2", "abcd"],
["9278d633efce801c6aa62987d7483d50e3c918caed7d46679551eed91fba8904", "abcde"],
["7a17ee5e289845adcafaf6ca1b05c4a281b232a71c7083f66c19ba1d1169a8d4", "abcdef"],
["ee8c7f94ff805cb2e644643010ea43b0222056420917ec70c3da764175193f8f", "abcdefg"],
["7b37c0876d29c5add7800a1823795a82b809fc12f799ff6a4b5e58d52c42b17e", "abcdefgh"],
["bdc514bea74ffbb9c3aa6470b08ceb80a88e313ad65e4a01457bbffd0acc86de", "abcdefghi"],
["12e3afb9739df8d727e93d853faeafc374cc55aedc937e5a1e66f5843b1d4c2e", "abcdefghij"],
["22297d373b751f581944bb26315133f6fda2f0bf60f65db773900f61f81b7e79", "Discard medicine more than two years old."],
["4d48d137bc9cf6d21415b805bf33f59320337d85c673998260e03a02a0d760cd", "He who has a shady past knows that nice guys finish last."],
["beba299e10f93e17d45663a6dc4b8c9349e4f5b9bac0d7832389c40a1b401e5c", "I wouldn't marry him with a ten foot pole."],
["42e082ae7f967781c6cd4e0ceeaeeb19fb2955adbdbaf8c7ec4613ac130071b3", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"],
["207d06b205bfb359df91b48b6fd8aa6e4798b712d1cc5e91a254da9cef8684a3", "The days of the digital watch are numbered. -Tom Stoppard"],
["d56eab6927e371e2148b0788779aaf565d30567af2af822b6be3b90db9767a70", "Nepal premier won't resign."],
["01020709ca7fd10dc7756ce767d508d7206167d300b7a7ed76838a8547a7898c", "For every action there is an equal and opposite government program."],
["5569a6cc6535a66da221d8f6ad25008f28752d0343f3f1d757f1ecc9b1c61536", "His money is twice tainted: 'taint yours and 'taint mine."],
["8ff699b5ac7687c82600e89d0ff6cfa87e7179759184386971feb76fbae9975f", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"],
["f4b3a7c85a418b15ce330fd41ae0254b036ad48dd98aa37f0506a995ba9c6029", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"],
["1ed94bab64fe560ef0983165fcb067e9a8a971c1db8e6fb151ff9a7c7fe877e3", "size: a.out: bad magic"],
["ff15b54992eedf9889f7b4bbb16692881aa01ed10dfc860fdb04785d8185cd3c", "The major problem is with sendmail. -Mark Horton"],
["8a0a7c417a47deec0b6474d8c247da142d2e315113a2817af3de8f45690d8652", "Give me a rock, paper and scissors and I will move the world. CCFestoon"],
["310d263fdab056a930324cdea5f46f9ea70219c1a74b01009994484113222a62", "If the enemy is within range, then so are you."],
["1aaa0903aa4cf872fe494c322a6e535698ea2140e15f26fb6088287aedceb6ba", "It's well we cannot hear the screams/That we create in others' dreams."],
["2eb81bcaa9e9185a7587a1b26299dcfb30f2a58a7f29adb584b969725457ad4f", "You remind me of a TV show, but that's all right: I watch it anyway."],
["c27b1683ef76e274680ab5492e592997b0d9d5ac5a5f4651b6036f64215256af", "C is as portable as Stonehedge!!"],
["3995cce8f32b174c22ffac916124bd095c80205d9d5f1bb08a155ac24b40d6cb", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"],
["496f7063f8bd479bf54e9d87e9ba53e277839ac7fdaecc5105f2879b58ee562f", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"],
["2e0eff918940b01eea9539a02212f33ee84f77fab201f4287aa6167e4a1ed043", "How can you write a big system without C++? -Paul Glick"]]
for vectorSet in testVectors:
assert vectorSet[0] == blake_hash(vectorSet[1]).encode('hex')

View File

@@ -11,7 +11,7 @@ from enum import IntEnum, auto
from sqlalchemy.ext.declarative import declarative_base
CURRENT_DB_VERSION = 23
CURRENT_DB_VERSION = 24
CURRENT_DB_DATA_VERSION = 4
Base = declarative_base()
@@ -127,6 +127,7 @@ class Bid(Base):
amount_to = sa.Column(sa.BigInteger) # amount * offer.rate
pkhash_buyer = sa.Column(sa.LargeBinary)
pkhash_buyer_to = sa.Column(sa.LargeBinary) # Used for the ptx if coin pubkey hashes differ
amount = sa.Column(sa.BigInteger)
rate = sa.Column(sa.BigInteger)
@@ -191,6 +192,11 @@ class Bid(Base):
else:
self.states += pack_state(new_state, now)
def getLockTXBVout(self):
if self.xmr_b_lock_tx:
return self.xmr_b_lock_tx.vout
return None
class SwapTx(Base):
__tablename__ = 'transactions'
@@ -522,3 +528,14 @@ class MessageLink(Base):
msg_type = sa.Column(sa.Integer)
msg_sequence = sa.Column(sa.Integer)
msg_id = sa.Column(sa.LargeBinary)
class CheckedBlock(Base):
__tablename__ = 'checkedblocks'
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
created_at = sa.Column(sa.BigInteger)
coin_type = sa.Column(sa.Integer)
block_height = sa.Column(sa.Integer)
block_hash = sa.Column(sa.LargeBinary)
block_time = sa.Column(sa.BigInteger)

View File

@@ -300,6 +300,18 @@ def upgradeDatabase(self, db_version):
elif current_version == 22:
db_version += 1
session.execute('ALTER TABLE offers ADD COLUMN amount_to INTEGER')
elif current_version == 23:
db_version += 1
session.execute('''
CREATE TABLE checkedblocks (
record_id INTEGER NOT NULL,
created_at BIGINT,
coin_type INTEGER,
block_height INTEGER,
block_hash BLOB,
block_time INTEGER,
PRIMARY KEY (record_id))''')
session.execute('ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB')
if current_version != db_version:
self.db_version = db_version
self.setIntKVInSession('db_version', db_version, session)

View File

@@ -52,5 +52,7 @@ def remove_expired_data(self, time_offset: int = 0):
if num_offers > 0 or num_bids > 0:
self.log.info('Removed data for {} expired offer{} and {} bid{}.'.format(num_offers, 's' if num_offers != 1 else '', num_bids, 's' if num_bids != 1 else ''))
session.execute('DELETE FROM checkedblocks WHERE created_at <= :expired_at', {'expired_at': now - time_offset})
finally:
self.closeSession(session)

View File

@@ -98,6 +98,8 @@ def parse_cmd(cmd: str, type_map: str):
type_ind = type_map[i]
if type_ind == 'i':
typed_params.append(int(param))
elif type_ind == 'f':
typed_params.append(float(param))
elif type_ind == 'b':
typed_params.append(toBool(param))
elif type_ind == 'j':
@@ -265,6 +267,7 @@ class HttpHandler(BaseHTTPRequestHandler):
summary = swap_client.getSummary()
result = None
cmd = ''
coin_type = -1
coin_id = -1
call_type = 'cli'
@@ -282,11 +285,16 @@ class HttpHandler(BaseHTTPRequestHandler):
coin_type = Coins(Coins.XMR)
elif coin_id in (-5,):
coin_type = Coins(Coins.LTC)
elif coin_id in (-6,):
coin_type = Coins(Coins.DCR)
else:
coin_type = Coins(coin_id)
except Exception:
raise ValueError('Unknown Coin Type')
if coin_type in (Coins.DCR,):
call_type = 'http'
try:
cmd = get_data_entry(form_data, 'cmd')
except Exception:
@@ -309,18 +317,24 @@ class HttpHandler(BaseHTTPRequestHandler):
result = json.dumps(rv, indent=4)
else:
if call_type == 'http':
ci = swap_client.ci(coin_type)
method, params = parse_cmd(cmd, type_map)
if coin_id == -5:
rv = swap_client.ci(coin_type).rpc_wallet_mweb(method, params)
if coin_id == -6:
rv = ci.rpc_wallet(method, params)
elif coin_id == -5:
rv = ci.rpc_wallet_mweb(method, params)
else:
rv = swap_client.ci(coin_type).rpc_wallet(method, params)
if coin_type in (Coins.DCR, ):
rv = ci.rpc(method, params)
else:
rv = ci.rpc_wallet(method, params)
if not isinstance(rv, str):
rv = json.dumps(rv, indent=4)
result = cmd + '\n' + rv
else:
result = cmd + '\n' + swap_client.callcoincli(coin_type, cmd)
except Exception as ex:
result = str(ex)
result = cmd + '\n' + str(ex)
if self.server.swap_client.debug is True:
self.server.swap_client.log.error(traceback.format_exc())
@@ -330,6 +344,8 @@ class HttpHandler(BaseHTTPRequestHandler):
with_xmr: bool = any(c[0] == Coins.XMR for c in coins)
coins = [c for c in coins if c[0] != Coins.XMR]
if any(c[0] == Coins.DCR for c in coins):
coins.append((-6, 'Decred Wallet'))
if any(c[0] == Coins.LTC for c in coins):
coins.append((-5, 'Litecoin MWEB Wallet'))
if with_xmr:

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2023 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from enum import IntEnum
class Curves(IntEnum):
secp256k1 = 1
ed25519 = 2

238
basicswap/interface/base.py Normal file
View File

@@ -0,0 +1,238 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import threading
from enum import IntEnum
from basicswap.chainparams import (
chainparams,
)
from basicswap.util import (
ensure,
i2b, b2i,
make_int,
format_amount,
TemporaryError,
)
from basicswap.util.crypto import (
hash160,
)
from basicswap.util.ecc import (
ep,
getSecretInt,
)
from coincurve.dleag import (
verify_secp256k1_point
)
from coincurve.keys import (
PublicKey,
)
class Curves(IntEnum):
secp256k1 = 1
ed25519 = 2
class CoinInterface:
@staticmethod
def watch_blocks_for_scripts() -> bool:
return False
@staticmethod
def compareFeeRates(a, b) -> bool:
return abs(a - b) < 20
def __init__(self, network):
self.setDefaults()
self._network = network
self._mx_wallet = threading.Lock()
def setDefaults(self):
self._unknown_wallet_seed = True
self._restore_height = None
def make_int(self, amount_in: int, r: int = 0) -> int:
return make_int(amount_in, self.exp(), r=r)
def format_amount(self, amount_in, conv_int=False, r=0):
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
return format_amount(amount_int, self.exp())
def coin_name(self) -> str:
coin_chainparams = chainparams[self.coin_type()]
if coin_chainparams.get('use_ticker_as_name', False):
return coin_chainparams['ticker']
return coin_chainparams['name'].capitalize()
def ticker(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
if self._network == 'testnet':
ticker = 't' + ticker
elif self._network == 'regtest':
ticker = 'rt' + ticker
return ticker
def getExchangeTicker(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['ticker']
def getExchangeName(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['name']
def ticker_mainnet(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
return ticker
def min_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['min_amount']
def max_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['max_amount']
def setWalletSeedWarning(self, value: bool) -> None:
self._unknown_wallet_seed = value
def setWalletRestoreHeight(self, value: int) -> None:
self._restore_height = value
def knownWalletSeed(self) -> bool:
return not self._unknown_wallet_seed
def chainparams(self):
return chainparams[self.coin_type()]
def chainparams_network(self):
return chainparams[self.coin_type()][self._network]
def has_segwit(self) -> bool:
return chainparams[self.coin_type()].get('has_segwit', True)
def use_p2shp2wsh(self) -> bool:
# p2sh-p2wsh
return False
def is_transient_error(self, ex) -> bool:
if isinstance(ex, TemporaryError):
return True
str_error: str = str(ex).lower()
if 'not enough unlocked money' in str_error:
return True
if 'no unlocked balance' in str_error:
return True
if 'transaction was rejected by daemon' in str_error:
return True
if 'invalid unlocked_balance' in str_error:
return True
if 'daemon is busy' in str_error:
return True
if 'timed out' in str_error:
return True
if 'request-sent' in str_error:
return True
return False
def setConfTarget(self, new_conf_target: int) -> None:
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
self._conf_target = new_conf_target
def walletRestoreHeight(self) -> int:
return self._restore_height
def get_connection_type(self):
return self._connection_type
def using_segwit(self) -> bool:
# Using btc native segwit
return self._use_segwit
def use_tx_vsize(self) -> bool:
return self._use_segwit
def getLockTxSwapOutputValue(self, bid, xmr_swap) -> int:
return bid.amount
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap) -> int:
return xmr_swap.a_swap_refund_value
def getLockRefundTxSwapOutput(self, xmr_swap) -> int:
# Only one prevout exists
return 0
def checkWallets(self) -> int:
return 1
class AdaptorSigInterface():
def getScriptLockTxDummyWitness(self, script: bytes):
return [
b'',
bytes(72),
bytes(72),
bytes(len(script))
]
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
return [
b'',
bytes(72),
bytes(72),
bytes((1,)),
bytes(len(script))
]
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
return [
bytes(72),
b'',
bytes(len(script))
]
class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
@staticmethod
def curve_type():
return Curves.secp256k1
def getNewSecretKey(self) -> bytes:
return i2b(getSecretInt())
def getPubkey(self, privkey: bytes) -> bytes:
return PublicKey.from_secret(privkey).format()
def pkh(self, pubkey: bytes) -> bytes:
return hash160(pubkey)
def verifyKey(self, k: bytes) -> bool:
i = b2i(k)
return (i < ep.o and i > 0)
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
return verify_secp256k1_point(pubkey_bytes)
def isValidAddressHash(self, address_hash: bytes) -> bool:
hash_len = len(address_hash)
if hash_len == 20:
return True
def isValidPubkey(self, pubkey: bytes) -> bool:
try:
self.verifyPubkey(pubkey)
return True
except Exception:
return False
def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool:
pubkey = PublicKey(pubkey)
return pubkey.verify(sig, signed_hash, hasher=None)
def sumKeys(self, ka: bytes, kb: bytes) -> bytes:
# TODO: Add to coincurve
return i2b((b2i(ka) + b2i(kb)) % ep.o)
def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes:
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()

View File

@@ -5,25 +5,31 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json
import base64
import hashlib
import json
import logging
import traceback
from io import BytesIO
from basicswap.contrib.test_framework import segwit_addr
from basicswap.interface import (
Curves)
from basicswap.basicswap_util import (
getVoutByAddress,
getVoutByScriptPubKey,
)
from basicswap.contrib.test_framework import (
segwit_addr,
)
from basicswap.interface.base import (
Secp256k1Interface,
)
from basicswap.util import (
ensure,
make_int,
b2h, i2b, b2i, i2h)
b2h, i2b, b2i, i2h,
)
from basicswap.util.ecc import (
ep,
pointToCPK, CPKToPoint,
getSecretInt)
)
from basicswap.util.script import (
decodeScriptNum,
getCompactSizeLen,
@@ -37,16 +43,20 @@ from basicswap.util.address import (
decodeAddress,
pubkeyToAddress,
)
from basicswap.util.crypto import (
hash160,
sha256,
)
from coincurve.keys import (
PrivateKey,
PublicKey)
from coincurve.dleag import (
verify_secp256k1_point)
PublicKey,
)
from coincurve.ecdsaotves import (
ecdsaotves_enc_sign,
ecdsaotves_enc_verify,
ecdsaotves_dec_sig,
ecdsaotves_rec_enc_key)
ecdsaotves_rec_enc_key
)
from basicswap.contrib.test_framework.messages import (
COIN,
@@ -55,8 +65,7 @@ from basicswap.contrib.test_framework.messages import (
CTxIn,
CTxInWitness,
CTxOut,
uint256_from_str)
)
from basicswap.contrib.test_framework.script import (
CScript, CScriptOp,
OP_IF, OP_ELSE, OP_ENDIF,
@@ -68,12 +77,12 @@ from basicswap.contrib.test_framework.script import (
OP_HASH160, OP_EQUAL,
SIGHASH_ALL,
SegwitV0SignatureHash,
hash160)
)
from basicswap.basicswap_util import (
TxLockTypes)
TxLockTypes
)
from basicswap.chainparams import CoinInterface, Coins
from basicswap.chainparams import Coins
from basicswap.rpc import make_rpc_func, openrpc
@@ -109,10 +118,58 @@ def find_vout_for_address_from_txobj(tx_obj, addr: str) -> int:
raise RuntimeError("Vout not found for address: txid={}, addr={}".format(tx_obj['txid'], addr))
class BTCInterface(CoinInterface):
@staticmethod
def curve_type():
return Curves.secp256k1
def extractScriptLockScriptValues(script_bytes: bytes) -> (bytes, bytes):
script_len = len(script_bytes)
ensure(script_len == 71, 'Bad script length')
o = 0
ensure_op(script_bytes[o] == OP_2)
ensure_op(script_bytes[o + 1] == 33)
o += 2
pk1 = script_bytes[o: o + 33]
o += 33
ensure_op(script_bytes[o] == 33)
o += 1
pk2 = script_bytes[o: o + 33]
o += 33
ensure_op(script_bytes[o] == OP_2)
ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG)
return pk1, pk2
def extractScriptLockRefundScriptValues(script_bytes: bytes):
script_len = len(script_bytes)
ensure(script_len > 73, 'Bad script length')
ensure_op(script_bytes[0] == OP_IF)
ensure_op(script_bytes[1] == OP_2)
ensure_op(script_bytes[2] == 33)
pk1 = script_bytes[3: 3 + 33]
ensure_op(script_bytes[36] == 33)
pk2 = script_bytes[37: 37 + 33]
ensure_op(script_bytes[70] == OP_2)
ensure_op(script_bytes[71] == OP_CHECKMULTISIG)
ensure_op(script_bytes[72] == OP_ELSE)
o = 73
csv_val, nb = decodeScriptNum(script_bytes, o)
o += nb
ensure(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long
ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
o += 1
ensure_op(script_bytes[o] == OP_DROP)
o += 1
ensure_op(script_bytes[o] == 33)
o += 1
pk3 = script_bytes[o: o + 33]
o += 33
ensure_op(script_bytes[o] == OP_CHECKSIG)
o += 1
ensure_op(script_bytes[o] == OP_ENDIF)
return pk1, pk2, csv_val, pk3
class BTCInterface(Secp256k1Interface):
@staticmethod
def coin_type():
@@ -149,10 +206,6 @@ class BTCInterface(CoinInterface):
rv += output.nValue
return rv
@staticmethod
def compareFeeRates(a, b) -> bool:
return abs(a - b) < 20
@staticmethod
def xmr_swap_a_lock_spend_tx_vsize() -> int:
return 147
@@ -167,7 +220,7 @@ class BTCInterface(CoinInterface):
@staticmethod
def getExpectedSequence(lockType: int, lockVal: int) -> int:
assert (lockVal >= 1), 'Bad lockVal'
ensure(lockVal >= 1, 'Bad lockVal')
if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
return lockVal
if lockType == TxLockTypes.SEQUENCE_LOCK_TIME:
@@ -206,6 +259,23 @@ class BTCInterface(CoinInterface):
self._log = self._sc.log if self._sc and self._sc.log else logging
self._expect_seedid_hex = None
def open_rpc(self, wallet=None):
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
def json_request(self, rpc_conn, method, params):
try:
v = rpc_conn.json_request(method, params)
r = json.loads(v.decode('utf-8'))
except Exception as ex:
traceback.print_exc()
raise ValueError('RPC Server Error ' + str(ex))
if 'error' in r and r['error'] is not None:
raise ValueError('RPC error ' + str(r['error']))
return r['result']
def close_rpc(self, rpc_conn):
rpc_conn.close()
def checkWallets(self) -> int:
wallets = self.rpc('listwallets')
@@ -223,40 +293,6 @@ class BTCInterface(CoinInterface):
return len(wallets)
def using_segwit(self) -> bool:
# Using btc native segwit
return self._use_segwit
def use_p2shp2wsh(self) -> bool:
# p2sh-p2wsh
return False
def get_connection_type(self):
return self._connection_type
def open_rpc(self, wallet=None):
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
def json_request(self, rpc_conn, method, params):
try:
v = rpc_conn.json_request(method, params)
r = json.loads(v.decode('utf-8'))
except Exception as ex:
traceback.print_exc()
raise ValueError('RPC Server Error ' + str(ex))
if 'error' in r and r['error'] is not None:
raise ValueError('RPC error ' + str(r['error']))
return r['result']
def close_rpc(self, rpc_conn):
rpc_conn.close()
def setConfTarget(self, new_conf_target: int) -> None:
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
self._conf_target = new_conf_target
def testDaemonRPC(self, with_wallet=True) -> None:
self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
@@ -285,7 +321,7 @@ class BTCInterface(CoinInterface):
max_tries = 5000
for i in range(max_tries):
prev_block_header = self.rpc('getblock', [last_block_header['previousblockhash']])
prev_block_header = self.rpc('getblockheader', [last_block_header['previousblockhash']])
if prev_block_header['time'] <= time:
return last_block_header if block_after else prev_block_header
@@ -303,9 +339,6 @@ class BTCInterface(CoinInterface):
rv['locked_utxos'] = len(self.rpc_wallet('listlockunspent'))
return rv
def walletRestoreHeight(self) -> int:
return self._restore_height
def getWalletRestoreHeight(self) -> int:
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
@@ -353,18 +386,6 @@ class BTCInterface(CoinInterface):
self._log.debug('validateaddress failed: {}'.format(address))
return False
def isValidAddressHash(self, address_hash: bytes) -> bool:
hash_len = len(address_hash)
if hash_len == 20:
return True
def isValidPubkey(self, pubkey: bytes) -> bool:
try:
self.verifyPubkey(pubkey)
return True
except Exception:
return False
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
addr_info = self.rpc_wallet('getaddressinfo', [address])
if not or_watch_only:
@@ -422,11 +443,11 @@ class BTCInterface(CoinInterface):
return segwit_addr.encode(bech32_prefix, version, pkh)
def pkh_to_address(self, pkh: bytes) -> str:
# pkh is hash160(pk)
# pkh is ripemd160(sha256(pk))
assert (len(pkh) == 20)
prefix = self.chainparams_network()['pubkey_address']
data = bytes((prefix,)) + pkh
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
checksum = sha256(sha256(data))
return b58encode(data + checksum[0:4])
def sh_to_address(self, sh: bytes) -> str:
@@ -452,12 +473,6 @@ class BTCInterface(CoinInterface):
assert (len(pk) == 33)
return self.pkh_to_address(hash160(pk))
def getNewSecretKey(self) -> bytes:
return i2b(getSecretInt())
def getPubkey(self, privkey):
return PublicKey.from_secret(privkey).format()
def getAddressHashFromKey(self, key: bytes) -> bytes:
pk = self.getPubkey(key)
return hash160(pk)
@@ -465,13 +480,6 @@ class BTCInterface(CoinInterface):
def getSeedHash(self, seed) -> bytes:
return self.getAddressHashFromKey(seed)[::-1]
def verifyKey(self, k: bytes) -> bool:
i = b2i(k)
return (i < ep.o and i > 0)
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
return verify_secp256k1_point(pubkey_bytes)
def encodeKey(self, key_bytes: bytes) -> str:
wif_prefix = self.chainparams_network()['key_prefix']
return toWIF(wif_prefix, key_bytes)
@@ -491,13 +499,6 @@ class BTCInterface(CoinInterface):
def decodeKey(self, k: str) -> bytes:
return decodeWif(k)
def sumKeys(self, ka, kb):
# TODO: Add to coincurve
return i2b((b2i(ka) + b2i(kb)) % ep.o)
def sumPubkeys(self, Ka, Kb):
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
# p2wpkh
return CScript([OP_0, pkh])
@@ -508,24 +509,6 @@ class BTCInterface(CoinInterface):
tx.deserialize(BytesIO(tx_bytes))
return tx
def extractScriptLockScriptValues(self, script_bytes: bytes):
script_len = len(script_bytes)
ensure(script_len == 71, 'Bad script length')
o = 0
ensure_op(script_bytes[o] == OP_2)
ensure_op(script_bytes[o + 1] == 33)
o += 2
pk1 = script_bytes[o: o + 33]
o += 33
ensure_op(script_bytes[o] == 33)
o += 1
pk2 = script_bytes[o: o + 33]
o += 33
ensure_op(script_bytes[o] == OP_2)
ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG)
return pk1, pk2
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
tx = CTransaction()
tx.nVersion = self.txVersion()
@@ -535,37 +518,6 @@ class BTCInterface(CoinInterface):
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
return self.fundTx(tx_bytes, feerate)
def extractScriptLockRefundScriptValues(self, script_bytes: bytes):
script_len = len(script_bytes)
ensure(script_len > 73, 'Bad script length')
ensure_op(script_bytes[0] == OP_IF)
ensure_op(script_bytes[1] == OP_2)
ensure_op(script_bytes[2] == 33)
pk1 = script_bytes[3: 3 + 33]
ensure_op(script_bytes[36] == 33)
pk2 = script_bytes[37: 37 + 33]
ensure_op(script_bytes[70] == OP_2)
ensure_op(script_bytes[71] == OP_CHECKMULTISIG)
ensure_op(script_bytes[72] == OP_ELSE)
o = 73
csv_val, nb = decodeScriptNum(script_bytes, o)
o += nb
ensure(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long
ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
o += 1
ensure_op(script_bytes[o] == OP_DROP)
o += 1
ensure_op(script_bytes[o] == 33)
o += 1
pk3 = script_bytes[o: o + 33]
o += 33
ensure_op(script_bytes[o] == OP_CHECKSIG)
o += 1
ensure_op(script_bytes[o] == OP_ENDIF)
return pk1, pk2, csv_val, pk3
def genScriptLockRefundTxScript(self, Kal, Kaf, csv_val) -> CScript:
Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal)
@@ -657,7 +609,7 @@ class BTCInterface(CoinInterface):
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock_refund.vout[locked_n].nValue
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
tx_lock_refund.rehash()
tx_lock_refund_hash_int = tx_lock_refund.sha256
@@ -744,7 +696,7 @@ class BTCInterface(CoinInterface):
ensure(locked_coin == swap_value, 'Bad locked value')
# Check script
A, B = self.extractScriptLockScriptValues(script_out)
A, B = extractScriptLockScriptValues(script_out)
ensure(A == Kal, 'Bad script pubkey')
ensure(B == Kaf, 'Bad script pubkey')
@@ -757,7 +709,7 @@ class BTCInterface(CoinInterface):
for pi in tx.vin:
ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True])
prevout = ptx['vout'][pi.prevout.n]
inputs_value += make_int(prevout['value'])
inputs_value += self.make_int(prevout['value'])
prevout_type = prevout['scriptPubKey']['type']
if prevout_type == 'witness_v0_keyhash':
@@ -812,7 +764,7 @@ class BTCInterface(CoinInterface):
locked_coin = tx.vout[locked_n].nValue
# Check script and values
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
ensure(A == Kal, 'Bad script pubkey')
ensure(B == Kaf, 'Bad script pubkey')
ensure(csv_val == csv_val_expect, 'Bad script csv value')
@@ -924,27 +876,30 @@ class BTCInterface(CoinInterface):
return True
def signTx(self, key_bytes, tx_bytes, input_n, prevout_script, prevout_value):
def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
tx = self.loadTx(tx_bytes)
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
eck = PrivateKey(key_bytes)
return eck.sign(sig_hash, hasher=None) + bytes((SIGHASH_ALL,))
def signTxOtVES(self, key_sign, pubkey_encrypt, tx_bytes, input_n, prevout_script, prevout_value):
def signTxOtVES(self, key_sign: bytes, pubkey_encrypt: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
tx = self.loadTx(tx_bytes)
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash)
def verifyTxOtVES(self, tx_bytes, ct, Ks, Ke, input_n, prevout_script, prevout_value):
def verifyTxOtVES(self, tx_bytes: bytes, ct: bytes, Ks: bytes, Ke: bytes, input_n: int, prevout_script: bytes, prevout_value):
tx = self.loadTx(tx_bytes)
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
return ecdsaotves_enc_verify(Ks, Ke, sig_hash, ct)
def decryptOtVES(self, k, esig):
def decryptOtVES(self, k: bytes, esig: bytes) -> bytes:
return ecdsaotves_dec_sig(k, esig) + bytes((SIGHASH_ALL,))
def recoverEncKey(self, esig, sig, K):
return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type
def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool:
tx = self.loadTx(tx_bytes)
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
@@ -952,11 +907,7 @@ class BTCInterface(CoinInterface):
pubkey = PublicKey(K)
return pubkey.verify(sig[: -1], sig_hash, hasher=None) # Pop the hashtype byte
def verifySig(self, pubkey, signed_hash, sig):
pubkey = PublicKey(pubkey)
return pubkey.verify(sig, signed_hash, hasher=None)
def fundTx(self, tx, feerate):
def fundTx(self, tx: bytes, feerate) -> bytes:
feerate_str = self.format_amount(feerate)
# TODO: unlock unspents if bid cancelled
options = {
@@ -966,7 +917,7 @@ class BTCInterface(CoinInterface):
rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), options])
return bytes.fromhex(rv['hex'])
def listInputs(self, tx_bytes):
def listInputs(self, tx_bytes: bytes):
tx = self.loadTx(tx_bytes)
all_locked = self.rpc_wallet('listlockunspent')
@@ -1018,20 +969,20 @@ class BTCInterface(CoinInterface):
return hash160(K)
def getScriptDest(self, script):
return CScript([OP_0, hashlib.sha256(script).digest()])
return CScript([OP_0, sha256(script)])
def getScriptScriptSig(self, script: bytes) -> bytes:
return bytes()
def getP2SHP2WSHDest(self, script):
script_hash = hashlib.sha256(script).digest()
script_hash = sha256(script)
assert len(script_hash) == 32
p2wsh_hash = hash160(CScript([OP_0, script_hash]))
assert len(p2wsh_hash) == 20
return CScript([OP_HASH160, p2wsh_hash, OP_EQUAL])
def getP2SHP2WSHScriptSig(self, script):
script_hash = hashlib.sha256(script).digest()
script_hash = sha256(script)
assert len(script_hash) == 32
return CScript([CScript([OP_0, script_hash, ]), ])
@@ -1050,7 +1001,7 @@ class BTCInterface(CoinInterface):
def getWalletTransaction(self, txid: bytes):
try:
return bytes.fromhex(self.rpc('gettransaction', [txid.hex()]))
return bytes.fromhex(self.rpc_wallet('gettransaction', [txid.hex()]))
except Exception as ex:
# TODO: filter errors
return None
@@ -1072,11 +1023,11 @@ class BTCInterface(CoinInterface):
tx.wit.vtxinwit.clear()
return tx.serialize()
def extractLeaderSig(self, tx_bytes) -> bytes:
def extractLeaderSig(self, tx_bytes: bytes) -> bytes:
tx = self.loadTx(tx_bytes)
return tx.wit.vtxinwit[0].scriptWitness.stack[1]
def extractFollowerSig(self, tx_bytes) -> bytes:
def extractFollowerSig(self, tx_bytes: bytes) -> bytes:
tx = self.loadTx(tx_bytes)
return tx.wit.vtxinwit[0].scriptWitness.stack[2]
@@ -1099,9 +1050,6 @@ class BTCInterface(CoinInterface):
return bytes.fromhex(self.publishTx(b_lock_tx))
def recoverEncKey(self, esig, sig, K):
return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type
def getTxVSize(self, tx, add_bytes: int = 0, add_witness_bytes: int = 0) -> int:
wsf = self.witnessScaleFactor()
len_full = len(tx.serialize_with_witness()) + add_bytes + add_witness_bytes
@@ -1131,10 +1079,10 @@ class BTCInterface(CoinInterface):
witness_bytes = 109
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
pay_fee = round(fee_rate * vsize / 1000)
self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.')
self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.')
return pay_fee
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
@@ -1149,7 +1097,7 @@ class BTCInterface(CoinInterface):
tx.nVersion = self.txVersion()
script_lock = self.getScriptForPubkeyHash(Kbs)
chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
chain_b_lock_txid_int = b2i(chain_b_lock_txid)
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n),
nSequence=0,
@@ -1171,11 +1119,11 @@ class BTCInterface(CoinInterface):
addr_info = self.rpc_wallet('getaddressinfo', [address])
return addr_info['iswatchonly']
def getSCLockScriptAddress(self, lock_script):
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
lock_tx_dest = self.getScriptDest(lock_script)
return self.encodeScriptDest(lock_tx_dest)
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
# Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True):
@@ -1244,30 +1192,30 @@ class BTCInterface(CoinInterface):
'vout': utxo['vout']})
return rv, chain_height
def withdrawCoin(self, value, addr_to, subfee):
def withdrawCoin(self, value: float, addr_to: str, subfee: bool):
params = [addr_to, value, '', '', subfee, True, self._conf_target]
return self.rpc_wallet('sendtoaddress', params)
def signCompact(self, k, message):
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
def signCompact(self, k, message: str) -> bytes:
message_hash = sha256(bytes(message, 'utf-8'))
privkey = PrivateKey(k)
return privkey.sign_recoverable(message_hash, hasher=None)[:64]
def signRecoverable(self, k, message):
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
def signRecoverable(self, k, message: str) -> bytes:
message_hash = sha256(bytes(message, 'utf-8'))
privkey = PrivateKey(k)
return privkey.sign_recoverable(message_hash, hasher=None)
def verifyCompactSig(self, K, message, sig):
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
def verifyCompactSig(self, K, message: str, sig) -> None:
message_hash = sha256(bytes(message, 'utf-8'))
pubkey = PublicKey(K)
rv = pubkey.verify_compact(sig, message_hash, hasher=None)
assert (rv is True)
def verifySigAndRecover(self, sig, message):
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
def verifySigAndRecover(self, sig, message: str) -> bytes:
message_hash = sha256(bytes(message, 'utf-8'))
pubkey = PublicKey.from_signature_and_message(sig, message_hash, hasher=None)
return pubkey.format()
@@ -1276,7 +1224,7 @@ class BTCInterface(CoinInterface):
message_magic = self.chainparams()['message_magic']
message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8')
message_hash = hashlib.sha256(hashlib.sha256(message_bytes).digest()).digest()
message_hash = sha256(sha256(message_bytes))
signature_bytes = base64.b64decode(signature)
rec_id = (signature_bytes[0] - 27) & 3
signature_bytes = signature_bytes[1:] + bytes((rec_id,))
@@ -1294,40 +1242,6 @@ class BTCInterface(CoinInterface):
def showLockTransfers(self, kbv, Kbs, restore_height):
raise ValueError('Unimplemented')
def getLockTxSwapOutputValue(self, bid, xmr_swap):
return bid.amount
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
return xmr_swap.a_swap_refund_value
def getLockRefundTxSwapOutput(self, xmr_swap):
# Only one prevout exists
return 0
def getScriptLockTxDummyWitness(self, script: bytes):
return [
b'',
bytes(72),
bytes(72),
bytes(len(script))
]
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
return [
b'',
bytes(72),
bytes(72),
bytes((1,)),
bytes(len(script))
]
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
return [
bytes(72),
b'',
bytes(len(script))
]
def getWitnessStackSerialisedLength(self, witness_stack):
length = getCompactSizeLen(len(witness_stack))
for e in witness_stack:
@@ -1430,6 +1344,12 @@ class BTCInterface(CoinInterface):
prove_utxos = [] # TODO: Send specific utxos
return (sign_for_addr, signature, prove_utxos)
def encodeProofUtxos(self, proof_utxos):
packed_utxos = bytes()
for utxo in proof_utxos:
packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big')
return packed_utxos
def decodeProofUtxos(self, msg_utxos):
proof_utxos = []
if len(msg_utxos) > 0:
@@ -1459,7 +1379,7 @@ class BTCInterface(CoinInterface):
return True
return False
def isWalletEncryptedLocked(self):
def isWalletEncryptedLocked(self) -> (bool, bool):
wallet_info = self.rpc_wallet('getwalletinfo')
encrypted = 'unlocked_until' in wallet_info
locked = encrypted and wallet_info['unlocked_until'] <= 0
@@ -1502,7 +1422,7 @@ class BTCInterface(CoinInterface):
return CScript([OP_HASH160, script_hash, OP_EQUAL])
def get_p2wsh_script_pubkey(self, script: bytearray) -> bytearray:
return CScript([OP_0, hashlib.sha256(script).digest()])
return CScript([OP_0, sha256(script)])
def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns
@@ -1515,10 +1435,10 @@ class BTCInterface(CoinInterface):
return {'txid': txid_hex, 'amount': 0, 'height': rv['blockheight']}
return None
def createRedeemTxn(self, prevout, output_addr: str, output_value: int) -> str:
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str:
tx = CTransaction()
tx.nVersion = self.txVersion()
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
prev_txid = b2i(bytes.fromhex(prevout['txid']))
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'])))
pkh = self.decodeAddress(output_addr)
script = self.getScriptForPubkeyHash(pkh)
@@ -1526,11 +1446,11 @@ class BTCInterface(CoinInterface):
tx.rehash()
return tx.serialize().hex()
def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int) -> str:
def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int, txn_script: bytes = None) -> str:
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.nLockTime = locktime
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
prev_txid = b2i(bytes.fromhex(prevout['txid']))
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']), nSequence=sequence,))
pkh = self.decodeAddress(output_addr)
script = self.getScriptForPubkeyHash(pkh)
@@ -1550,6 +1470,30 @@ class BTCInterface(CoinInterface):
tx_vsize += 323 if redeem else 287
return tx_vsize
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
txjs = self.rpc('decoderawtransaction', [txn_hex])
if self.using_segwit():
p2wsh = self.getScriptDest(txn_script)
n = getVoutByScriptPubKey(txjs, p2wsh.hex())
else:
addr_to = self.encode_p2sh(txn_script)
n = getVoutByAddress(txjs, addr_to)
return {
'txid': txjs['txid'],
'vout': n,
'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'],
'redeemScript': txn_script.hex(),
'amount': txjs['vout'][n]['value']
}
def isTxExistsError(self, err_str: str) -> bool:
return 'Transaction already in block chain' in err_str
def isTxNonFinalError(self, err_str: str) -> bool:
return 'non-BIP68-final' in err_str or 'non-final' in err_str
def testBTCInterface():
print('TODO: testBTCInterface')

View File

@@ -66,7 +66,7 @@ class DASHInterface(BTCInterface):
add_bytes = 107
size = len(tx.serialize_with_witness()) + add_bytes
pay_fee = round(fee_rate * size / 1000)
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
return pay_fee
def findTxnByHash(self, txid_hex: str):

View File

@@ -0,0 +1,4 @@
from .dcr import DCRInterface
__all__ = ['DCRInterface',]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,204 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import copy
from enum import IntEnum
from basicswap.util.crypto import blake256
from basicswap.util.integer import decode_compactsize, encode_compactsize
class TxSerializeType(IntEnum):
Full = 0
NoWitness = 1
OnlyWitness = 2
class SigHashType(IntEnum):
SigHashAll = 0x1
SigHashNone = 0x2
SigHashSingle = 0x3
SigHashAnyOneCanPay = 0x80
SigHashMask = 0x1f
class SignatureType(IntEnum):
STEcdsaSecp256k1 = 0
STEd25519 = 1
STSchnorrSecp256k1 = 2
class COutPoint:
__slots__ = ('hash', 'n', 'tree')
def __init__(self, hash=0, n=0, tree=0):
self.hash = hash
self.n = n
self.tree = tree
def get_hash(self) -> bytes:
return self.hash.to_bytes(32, 'big')
class CTxIn:
__slots__ = ('prevout', 'sequence',
'value_in', 'block_height', 'block_index', 'signature_script') # Witness
def __init__(self, prevout=COutPoint(), sequence=0):
self.prevout = prevout
self.sequence = sequence
self.value_in = -1
self.block_height = 0
self.block_index = 0xffffffff
self.signature_script = bytes()
class CTxOut:
__slots__ = ('value', 'version', 'script_pubkey')
def __init__(self, value=0, script_pubkey=bytes()):
self.value = value
self.version = 0
self.script_pubkey = script_pubkey
class CTransaction:
__slots__ = ('hash', 'version', 'vin', 'vout', 'locktime', 'expiry')
def __init__(self, tx=None):
if tx is None:
self.version = 1
self.vin = []
self.vout = []
self.locktime = 0
self.expiry = 0
else:
self.version = tx.version
self.vin = copy.deepcopy(tx.vin)
self.vout = copy.deepcopy(tx.vout)
self.locktime = tx.locktime
self.expiry = tx.expiry
def deserialize(self, data: bytes) -> None:
version = int.from_bytes(data[:4], 'little')
self.version = version & 0xffff
ser_type: int = version >> 16
o = 4
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness:
num_txin, nb = decode_compactsize(data, o)
o += nb
for i in range(num_txin):
txi = CTxIn()
txi.prevout = COutPoint()
txi.prevout.hash = int.from_bytes(data[o:o + 32], 'little')
o += 32
txi.prevout.n = int.from_bytes(data[o:o + 4], 'little')
o += 4
txi.prevout.tree = data[o]
o += 1
txi.sequence = int.from_bytes(data[o:o + 4], 'little')
o += 4
self.vin.append(txi)
num_txout, nb = decode_compactsize(data, o)
o += nb
for i in range(num_txout):
txo = CTxOut()
txo.value = int.from_bytes(data[o:o + 8], 'little')
o += 8
txo.version = int.from_bytes(data[o:o + 2], 'little')
o += 2
script_bytes, nb = decode_compactsize(data, o)
o += nb
txo.script_pubkey = data[o:o + script_bytes]
o += script_bytes
self.vout.append(txo)
self.locktime = int.from_bytes(data[o:o + 4], 'little')
o += 4
self.expiry = int.from_bytes(data[o:o + 4], 'little')
o += 4
if ser_type == TxSerializeType.NoWitness:
return
num_wit_scripts, nb = decode_compactsize(data, o)
o += nb
if ser_type == TxSerializeType.OnlyWitness:
self.vin = [CTxIn() for _ in range(num_wit_scripts)]
else:
if num_wit_scripts != len(self.vin):
raise ValueError('non equal witness and prefix txin quantities')
for i in range(num_wit_scripts):
txi = self.vin[i]
txi.value_in = int.from_bytes(data[o:o + 8], 'little')
o += 8
txi.block_height = int.from_bytes(data[o:o + 4], 'little')
o += 4
txi.block_index = int.from_bytes(data[o:o + 4], 'little')
o += 4
script_bytes, nb = decode_compactsize(data, o)
o += nb
txi.signature_script = data[o:o + script_bytes]
o += script_bytes
def serialize(self, ser_type=TxSerializeType.Full) -> bytes:
data = bytes()
version = (self.version & 0xffff) | (ser_type << 16)
data += version.to_bytes(4, 'little')
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness:
data += encode_compactsize(len(self.vin))
for txi in self.vin:
data += txi.prevout.hash.to_bytes(32, 'little')
data += txi.prevout.n.to_bytes(4, 'little')
data += txi.prevout.tree.to_bytes(1, 'little')
data += txi.sequence.to_bytes(4, 'little')
data += encode_compactsize(len(self.vout))
for txo in self.vout:
data += txo.value.to_bytes(8, 'little')
data += txo.version.to_bytes(2, 'little')
data += encode_compactsize(len(txo.script_pubkey))
data += txo.script_pubkey
data += self.locktime.to_bytes(4, 'little')
data += self.expiry.to_bytes(4, 'little')
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness:
data += encode_compactsize(len(self.vin))
for txi in self.vin:
tc_value_in = txi.value_in & 0xffffffffffffffff # Convert negative values
data += tc_value_in.to_bytes(8, 'little')
data += txi.block_height.to_bytes(4, 'little')
data += txi.block_index.to_bytes(4, 'little')
data += encode_compactsize(len(txi.signature_script))
data += txi.signature_script
return data
def TxHash(self) -> bytes:
return blake256(self.serialize(TxSerializeType.NoWitness))[::-1]
def TxHashWitness(self) -> bytes:
raise ValueError('todo')
def TxHashFull(self) -> bytes:
raise ValueError('todo')
def findOutput(tx, script_pk: bytes):
for i in range(len(tx.vout)):
if tx.vout[i].script_pubkey == script_pk:
return i
return None

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json
import traceback
from basicswap.rpc import Jsonrpc
def callrpc(rpc_port, auth, method, params=[], host='127.0.0.1'):
try:
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
x = Jsonrpc(url)
x.__handler = None
v = x.json_request(method, params)
x.close()
r = json.loads(v.decode('utf-8'))
except Exception as ex:
traceback.print_exc()
raise ValueError('RPC server error ' + str(ex) + ', method: ' + method)
if 'error' in r and r['error'] is not None:
raise ValueError('RPC error ' + str(r['error']))
return r['result']
def openrpc(rpc_port, auth, host='127.0.0.1'):
try:
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
return Jsonrpc(url)
except Exception as ex:
traceback.print_exc()
raise ValueError('RPC error ' + str(ex))
def make_rpc_func(port, auth, host='127.0.0.1'):
port = port
auth = auth
host = host
def rpc_func(method, params=None):
nonlocal port, auth, host
return callrpc(port, auth, method, params, host)
return rpc_func

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
OP_0 = 0x00
OP_DATA_1 = 0x01
OP_1NEGATE = 0x4f
OP_1 = 0x51
OP_IF = 0x63
OP_ELSE = 0x67
OP_ENDIF = 0x68
OP_DROP = 0x75
OP_DUP = 0x76
OP_EQUAL = 0x87
OP_EQUALVERIFY = 0x88
OP_PUSHDATA1 = 0x4c
OP_PUSHDATA2 = 0x4d
OP_PUSHDATA4 = 0x4e
OP_HASH160 = 0xa9
OP_CHECKSIG = 0xac
OP_CHECKMULTISIG = 0xae
OP_CHECKSEQUENCEVERIFY = 0xb2
def push_script_data(data_array: bytearray, data: bytes) -> None:
len_data: int = len(data)
if len_data == 0 or (len_data == 1 and data[0] == 0):
data_array += bytes((OP_0,))
return
if len_data == 1 and data[0] <= 16:
data_array += bytes((OP_1 - 1 + data[0],))
return
if len_data == 1 and data[0] == 0x81:
data_array += bytes((OP_1NEGATE,))
return
if len_data < OP_PUSHDATA1:
data_array += len_data.to_bytes(1, 'little')
elif len_data <= 0xff:
data_array += bytes((OP_PUSHDATA1, len_data))
elif len_data <= 0xffff:
data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, 'little')
else:
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little')
data_array += data

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import select
import subprocess
def createDCRWallet(args, hex_seed, logging, delay_event):
logging.info('Creating DCR wallet')
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w)
try:
while p.poll() is None:
while len(select.select([pipe_r], [], [], 0)[0]) == 1:
buf = os.read(pipe_r, 1024).decode('utf-8')
logging.debug(f'dcrwallet {buf}')
response = None
if 'Use the existing configured private passphrase' in buf:
response = b'y\n'
elif 'Do you want to add an additional layer of encryption' in buf:
response = b'n\n'
elif 'Do you have an existing wallet seed' in buf:
response = b'y\n'
elif 'Enter existing wallet seed' in buf:
response = (hex_seed + '\n').encode('utf-8')
elif 'Seed input successful' in buf:
pass
elif 'Upgrading database from version' in buf:
pass
elif 'Ticket commitments db upgrade done' in buf:
pass
else:
raise ValueError(f'Unexpected output: {buf}')
if response is not None:
p.stdin.write(response)
p.stdin.flush()
delay_event.wait(0.1)
except Exception as e:
logging.error(f'dcrwallet --create failed: {e}')
finally:
if p.poll() is None:
p.terminate()
os.close(pipe_r)
os.close(pipe_w)
p.stdin.close()

View File

@@ -5,8 +5,8 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import random
import hashlib
import random
from .btc import BTCInterface, find_vout_for_address_from_txobj
from basicswap.util import (
@@ -42,9 +42,6 @@ class FIROInterface(BTCInterface):
# No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
def checkWallets(self) -> int:
return 1
def getExchangeName(self, exchange_name):
return 'zcoin'
@@ -52,6 +49,9 @@ class FIROInterface(BTCInterface):
# load with -hdseed= parameter
pass
def checkWallets(self) -> int:
return 1
def getNewAddress(self, use_segwit, label='swap_receive'):
return self.rpc('getnewaddress', [label])
# addr_plain = self.rpc('getnewaddress', [label])
@@ -76,7 +76,7 @@ class FIROInterface(BTCInterface):
return addr_info['ismine']
return addr_info['ismine'] or addr_info['iswatchonly']
def getSCLockScriptAddress(self, lock_script):
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
lock_tx_dest = self.getScriptDest(lock_script)
address = self.encodeScriptDest(lock_tx_dest)
@@ -87,7 +87,7 @@ class FIROInterface(BTCInterface):
return address
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
# Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True):
@@ -201,7 +201,7 @@ class FIROInterface(BTCInterface):
add_bytes = 107
size = len(tx.serialize_with_witness()) + add_bytes
pay_fee = round(fee_rate * size / 1000)
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
return pay_fee
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
@@ -337,7 +337,7 @@ class FIROInterface(BTCInterface):
return
current_height -= 1
def getBlockWithTxns(self, block_hash):
def getBlockWithTxns(self, block_hash: str):
# TODO: Bypass decoderawtransaction and getblockheader
block = self.rpc('getblock', [block_hash, False])
block_header = self.rpc('getblockheader', [block_hash])
@@ -355,9 +355,11 @@ class FIROInterface(BTCInterface):
block_rv = {
'hash': block_hash,
'previousblockhash': block_header['previousblockhash'],
'tx': tx_rv,
'confirmations': block_header['confirmations'],
'height': block_header['height'],
'time': block_header['time'],
'version': block_header['version'],
'merkleroot': block_header['merkleroot'],
}

View File

@@ -13,7 +13,12 @@ from coincurve.keys import (
PublicKey,
PrivateKey,
)
from .btc import BTCInterface, find_vout_for_address_from_txobj, findOutput
from basicswap.interface.btc import (
BTCInterface,
extractScriptLockRefundScriptValues,
findOutput,
find_vout_for_address_from_txobj,
)
from basicswap.rpc import make_rpc_func
from basicswap.chainparams import Coins
from basicswap.interface.contrib.nav_test_framework.mininode import (
@@ -24,7 +29,6 @@ from basicswap.interface.contrib.nav_test_framework.mininode import (
CTransaction,
CTxInWitness,
FromHex,
uint256_from_str,
)
from basicswap.util.crypto import hash160
from basicswap.util.address import (
@@ -33,7 +37,7 @@ from basicswap.util.address import (
encodeAddress,
)
from basicswap.util import (
i2b, i2h,
b2i, i2b, i2h,
ensure,
)
from basicswap.basicswap_util import (
@@ -69,20 +73,20 @@ class NAVInterface(BTCInterface):
# No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
def checkWallets(self) -> int:
return 1
def use_p2shp2wsh(self) -> bool:
# p2sh-p2wsh
return True
def seedToMnemonic(self, key):
def seedToMnemonic(self, key: bytes) -> None:
return Mnemonic('english').to_mnemonic(key)
def initialiseWallet(self, key):
# load with -importmnemonic= parameter
# Load with -importmnemonic= parameter
pass
def checkWallets(self) -> int:
return 1
def getWalletSeedID(self):
return self.rpc('getwalletinfo')['hdmasterkeyid']
@@ -305,7 +309,7 @@ class NAVInterface(BTCInterface):
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str:
tx = CTransaction()
tx.nVersion = self.txVersion()
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
prev_txid = b2i(bytes.fromhex(prevout['txid']))
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
scriptSig=self.getScriptScriptSig(txn_script)))
@@ -319,7 +323,7 @@ class NAVInterface(BTCInterface):
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.nLockTime = locktime
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
prev_txid = b2i(bytes.fromhex(prevout['txid']))
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
nSequence=sequence,
scriptSig=self.getScriptScriptSig(txn_script)))
@@ -415,7 +419,7 @@ class NAVInterface(BTCInterface):
return
current_height -= 1
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
# Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True):
@@ -479,16 +483,18 @@ class NAVInterface(BTCInterface):
block_rv = {
'hash': block_hash,
'previousblockhash': block_header['previousblockhash'],
'tx': tx_rv,
'confirmations': block_header['confirmations'],
'height': block_header['height'],
'time': block_header['time'],
'version': block_header['version'],
'merkleroot': block_header['merkleroot'],
}
return block_rv
def getScriptScriptSig(self, script: bytes) -> bytearray:
def getScriptScriptSig(self, script: bytes) -> bytes:
return self.getP2SHP2WSHScriptSig(script)
def getScriptDest(self, script):
@@ -510,7 +516,7 @@ class NAVInterface(BTCInterface):
tx.vout.append(self.txoType()(output_amount, script_pk))
return tx.serialize()
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
wtx = self.rpc('gettransaction', [chain_b_lock_txid.hex(), ])
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
@@ -524,7 +530,7 @@ class NAVInterface(BTCInterface):
tx = CTransaction()
tx.nVersion = self.txVersion()
chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
chain_b_lock_txid_int = b2i(chain_b_lock_txid)
script_sig = self.getInputScriptForPubkeyHash(self.getPubkeyHash(Kbs))
@@ -676,7 +682,7 @@ class NAVInterface(BTCInterface):
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock_refund.vout[locked_n].nValue
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
tx_lock_refund.rehash()
tx_lock_refund_hash_int = tx_lock_refund.sha256

View File

@@ -7,9 +7,6 @@
from .btc import BTCInterface
from basicswap.chainparams import Coins
from basicswap.util import (
make_int,
)
class NMCInterface(BTCInterface):
@@ -17,7 +14,7 @@ class NMCInterface(BTCInterface):
def coin_type():
return Coins.NMC
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
self._log.debug('[rm] scantxoutset end')
@@ -26,7 +23,7 @@ class NMCInterface(BTCInterface):
if txid and o['txid'] != txid.hex():
continue
# Verify amount
if make_int(o['amount']) != int(bid_amount):
if self.make_int(o['amount']) != int(bid_amount):
self._log.warning('Found output to lock tx address of incorrect value: %s, %s', str(o['amount']), o['txid'])
continue

View File

@@ -14,11 +14,10 @@ from basicswap.contrib.test_framework.messages import (
from basicswap.contrib.test_framework.script import (
CScript,
OP_0,
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG,
)
from basicswap.util import (
ensure,
make_int,
TemporaryError,
)
from basicswap.util.script import (
@@ -27,10 +26,15 @@ from basicswap.util.script import (
getWitnessElementLen,
)
from basicswap.util.address import (
toWIF,
encodeStealthAddress)
encodeStealthAddress,
)
from basicswap.interface.btc import (
BTCInterface,
extractScriptLockScriptValues,
extractScriptLockRefundScriptValues,
)
from basicswap.chainparams import Coins, chainparams
from .btc import BTCInterface
class BalanceTypes(IntEnum):
@@ -74,6 +78,9 @@ class PARTInterface(BTCInterface):
super().__init__(coin_settings, network, swap_client)
self.setAnonTxRingSize(int(coin_settings.get('anon_tx_ring_size', 12)))
def use_tx_vsize(self) -> bool:
return True
def setAnonTxRingSize(self, value):
ensure(value >= 3 and value < 33, 'Invalid anon_tx_ring_size value')
self._anon_tx_ring_size = value
@@ -93,7 +100,7 @@ class PARTInterface(BTCInterface):
index_info = self.rpc('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo')
return index_info['spentindex']
def initialiseWallet(self, key):
def initialiseWallet(self, key: bytes) -> None:
raise ValueError('TODO')
def withdrawCoin(self, value, addr_to, subfee):
@@ -345,21 +352,21 @@ class PARTInterfaceBlind(PARTInterface):
ensure(lock_output_n is not None, 'Output not found in tx')
# Check value
locked_txo_value = make_int(blinded_info['amount'])
locked_txo_value = self.make_int(blinded_info['amount'])
ensure(locked_txo_value == swap_value, 'Bad locked value')
# Check script
lock_txo_scriptpk = bytes.fromhex(lock_tx_obj['vout'][lock_output_n]['scriptPubKey']['hex'])
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
ensure(lock_txo_scriptpk == script_pk, 'Bad output script')
A, B = self.extractScriptLockScriptValues(script_out)
A, B = extractScriptLockScriptValues(script_out)
ensure(A == Kal, 'Bad script leader pubkey')
ensure(B == Kaf, 'Bad script follower pubkey')
# TODO: Check that inputs are unspent, rangeproofs and commitments sum
# Verify fee rate
vsize = lock_tx_obj['vsize']
fee_paid = make_int(lock_tx_obj['vout'][0]['ct_fee'])
fee_paid = self.make_int(lock_tx_obj['vout'][0]['ct_fee'])
fee_rate_paid = fee_paid * 1000 // vsize
@@ -394,13 +401,13 @@ class PARTInterfaceBlind(PARTInterface):
lock_refund_output_n, blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce)
ensure(lock_refund_output_n is not None, 'Output not found in tx')
lock_refund_txo_value = make_int(blinded_info['amount'])
lock_refund_txo_value = self.make_int(blinded_info['amount'])
# Check script
lock_refund_txo_scriptpk = bytes.fromhex(lock_refund_tx_obj['vout'][lock_refund_output_n]['scriptPubKey']['hex'])
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
ensure(lock_refund_txo_scriptpk == script_pk, 'Bad output script')
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
ensure(A == Kal, 'Bad script pubkey')
ensure(B == Kaf, 'Bad script pubkey')
ensure(csv_val == csv_val_expect, 'Bad script csv value')
@@ -415,7 +422,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
# Check value
fee_paid = make_int(lock_refund_tx_obj['vout'][0]['ct_fee'])
fee_paid = self.make_int(lock_refund_tx_obj['vout'][0]['ct_fee'])
ensure(swap_value - lock_refund_txo_value == fee_paid, 'Bad output value')
# Check fee rate
@@ -463,7 +470,7 @@ class PARTInterfaceBlind(PARTInterface):
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script)
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
vsize = self.getTxVSize(self.loadTx(tx_bytes), add_witness_bytes=witness_bytes)
fee_paid = make_int(lock_refund_spend_tx_obj['vout'][0]['ct_fee'])
fee_paid = self.make_int(lock_refund_spend_tx_obj['vout'][0]['ct_fee'])
fee_rate_paid = fee_paid * 1000 // vsize
ensure(self.compareFeeRates(fee_rate_paid, feerate), 'Bad fee rate, expected: {}'.format(feerate))
@@ -527,7 +534,7 @@ class PARTInterfaceBlind(PARTInterface):
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
lock_spend_tx_hex = rv['hex']
lock_spend_tx_obj = self.rpc('decoderawtransaction', [lock_spend_tx_hex])
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
pay_fee = self.make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
# lock_spend_tx_hex does not include the dummy witness stack
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
@@ -599,8 +606,8 @@ class PARTInterfaceBlind(PARTInterface):
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
# Check amount
fee_paid = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
amount_difference = make_int(input_blinded_info['amount']) - make_int(output_blinded_info['amount'])
fee_paid = self.make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
amount_difference = self.make_int(input_blinded_info['amount']) - self.make_int(output_blinded_info['amount'])
ensure(fee_paid == amount_difference, 'Invalid output amount')
# Check fee
@@ -630,7 +637,7 @@ class PARTInterfaceBlind(PARTInterface):
addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
output_pubkey_hex = addr_info['pubkey']
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
# Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance
@@ -688,8 +695,7 @@ class PARTInterfaceBlind(PARTInterface):
else:
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
if not addr_info['iswatchonly']:
wif_prefix = self.chainparams_network()['key_prefix']
wif_scan_key = toWIF(wif_prefix, kbv)
wif_scan_key = self.encodeKey(kbv)
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
@@ -703,7 +709,7 @@ class PARTInterfaceBlind(PARTInterface):
assert (tx['outputs'][0]['stealth_address'] == sx_addr) # Should not be possible
ensure(tx['outputs'][0]['type'] == 'blind', 'Output is not anon')
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
if self.make_int(tx['outputs'][0]['amount']) == cb_swap_value:
height = 0
if tx['confirmations'] > 0:
chain_height = self.rpc('getblockcount')
@@ -714,15 +720,14 @@ class PARTInterfaceBlind(PARTInterface):
return -1
return None
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
Kbv = self.getPubkey(kbv)
Kbs = self.getPubkey(kbs)
sx_addr = self.formatStealthAddress(Kbv, Kbs)
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
if not addr_info['ismine']:
wif_prefix = self.chainparams_network()['key_prefix']
wif_scan_key = toWIF(wif_prefix, kbv)
wif_spend_key = toWIF(wif_prefix, kbs)
wif_scan_key = self.encodeKey(kbv)
wif_spend_key = self.encodeKey(kbs)
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
@@ -741,7 +746,7 @@ class PARTInterfaceBlind(PARTInterface):
raise ValueError('Too many spendable outputs')
utxo = utxos[0]
utxo_sats = make_int(utxo['amount'])
utxo_sats = self.make_int(utxo['amount'])
if spend_actual_balance and utxo_sats != cb_swap_value:
self._log.warning('Spending actual balance {}, not swap value {}.'.format(utxo_sats, cb_swap_value))
@@ -826,8 +831,7 @@ class PARTInterfaceAnon(PARTInterface):
else:
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
if not addr_info['iswatchonly']:
wif_prefix = self.chainparams_network()['key_prefix']
wif_scan_key = toWIF(wif_prefix, kbv)
wif_scan_key = self.encodeKey(kbv)
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
@@ -841,7 +845,7 @@ class PARTInterfaceAnon(PARTInterface):
assert (tx['outputs'][0]['stealth_address'] == sx_addr) # Should not be possible
ensure(tx['outputs'][0]['type'] == 'anon', 'Output is not anon')
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
if self.make_int(tx['outputs'][0]['amount']) == cb_swap_value:
height = 0
if tx['confirmations'] > 0:
chain_height = self.rpc('getblockcount')
@@ -852,15 +856,14 @@ class PARTInterfaceAnon(PARTInterface):
return -1
return None
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
Kbv = self.getPubkey(kbv)
Kbs = self.getPubkey(kbs)
sx_addr = self.formatStealthAddress(Kbv, Kbs)
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
if not addr_info['ismine']:
wif_prefix = self.chainparams_network()['key_prefix']
wif_scan_key = toWIF(wif_prefix, kbv)
wif_spend_key = toWIF(wif_prefix, kbs)
wif_scan_key = self.encodeKey(kbv)
wif_spend_key = self.encodeKey(kbs)
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
@@ -874,7 +877,7 @@ class PARTInterfaceAnon(PARTInterface):
raise ValueError('Too many spendable outputs')
utxo = autxos[0]
utxo_sats = make_int(utxo['amount'])
utxo_sats = self.make_int(utxo['amount'])
if spend_actual_balance and utxo_sats != cb_swap_value:
self._log.warning('Spending actual balance {}, not swap value {}.'.format(utxo_sats, cb_swap_value))

View File

@@ -75,9 +75,11 @@ class PIVXInterface(BTCInterface):
block_rv = {
'hash': block_hash,
'previousblockhash': block_header['previousblockhash'],
'tx': tx_rv,
'confirmations': block_header['confirmations'],
'height': block_header['height'],
'time': block_header['time'],
'version': block_header['version'],
'merkleroot': block_header['merkleroot'],
}
@@ -105,7 +107,7 @@ class PIVXInterface(BTCInterface):
add_bytes = 107
size = len(tx.serialize_with_witness()) + add_bytes
pay_fee = round(fee_rate * size / 1000)
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
return pay_fee
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:

View File

@@ -24,20 +24,21 @@ from coincurve.dleag import (
verify_ed25519_point,
)
from basicswap.interface import (
Curves)
from basicswap.interface.base import (
Curves,
)
from basicswap.util import (
i2b, b2i, b2h,
dumpj,
ensure,
make_int,
TemporaryError)
from basicswap.util.network import (
is_private_ip_address)
from basicswap.rpc_xmr import (
make_xmr_rpc_func,
make_xmr_rpc2_func)
from basicswap.chainparams import XMR_COIN, CoinInterface, Coins
from basicswap.chainparams import XMR_COIN, Coins
from basicswap.interface.base import CoinInterface
class XMRInterface(CoinInterface):
@@ -128,9 +129,6 @@ class XMRInterface(CoinInterface):
self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node ') # non-json endpoint
self.rpc_wallet = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'), default_timeout=self._walletrpctimeout, tag='Wallet ')
def checkWallets(self) -> int:
return 1
def setFeePriority(self, new_priority):
ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value')
self._fee_priority = new_priority
@@ -156,7 +154,7 @@ class XMRInterface(CoinInterface):
pass
self.rpc_wallet('open_wallet', params)
def initialiseWallet(self, key_view, key_spend, restore_height=None):
def initialiseWallet(self, key_view: bytes, key_spend: bytes, restore_height=None) -> None:
with self._mx_wallet:
try:
self.openWallet(self._wallet_filename)
@@ -240,9 +238,6 @@ class XMRInterface(CoinInterface):
rv['locked'] = False
return rv
def walletRestoreHeight(self):
return self._restore_height
def getMainWalletAddress(self) -> str:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
@@ -411,7 +406,7 @@ class XMRInterface(CoinInterface):
return None
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
'''
Notes:
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
@@ -490,7 +485,7 @@ class XMRInterface(CoinInterface):
return {'num_txns': len(rv['fee_list']), 'sum_amount': sum(rv['amount_list']), 'sum_fee': sum(rv['fee_list']), 'sum_weight': sum(rv['weight_list'])}
return rv['tx_hash_list'][0]
value_sats: int = make_int(value, self.exp())
value_sats: int = self.make_int(value)
params = {'destinations': [{'amount': value_sats, 'address': addr_to}], 'do_not_relay': estimate_fee}
if self._fee_priority > 0:
params['priority'] = self._fee_priority

View File

@@ -49,6 +49,9 @@ message BidMessage {
string proof_signature = 8;
bytes proof_utxos = 9; /* 32 byte txid 2 byte vout, repeated */
/* optional */
bytes pkhash_buyer_to = 13; /* When pubkey hash is different on the to-chain */
}
/* For tests */
@@ -65,6 +68,7 @@ message BidAcceptMessage {
bytes bid_msg_id = 1;
bytes initiate_txid = 2;
bytes contract_script = 3;
bytes pkhash_seller = 4;
}
message OfferRevokeMessage {

View File

@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xc0\x04\n\x0cOfferMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x11\n\tcoin_from\x18\x02 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x03 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x06 \x01(\x04\x12\x12\n\ntime_valid\x18\x07 \x01(\x04\x12\x33\n\tlock_type\x18\x08 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\t \x01(\r\x12\x11\n\tswap_type\x18\n \x01(\r\x12\x15\n\rproof_address\x18\x0b \x01(\t\x12\x17\n\x0fproof_signature\x18\x0c \x01(\t\x12\x15\n\rpkhash_seller\x18\r \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\x0e \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0f \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x10 \x01(\x04\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\x12\x13\n\x0bproof_utxos\x18\x13 \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\xce\x01\n\nBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x06 \x01(\x0c\x12\x15\n\rproof_address\x18\x07 \x01(\t\x12\x17\n\x0fproof_signature\x18\x08 \x01(\t\x12\x13\n\x0bproof_utxos\x18\t \x01(\x0c\"s\n\x0f\x42idMessage_test\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x0c\n\x04rate\x18\x05 \x01(\x04\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb7\x01\n\rXmrBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x0c\n\x04pkaf\x18\x06 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x07 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x08 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\t \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x03 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x04 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x05 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x07 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\x08 \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\t \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\n \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"X\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"\x81\x01\n\x13\x41\x44SBidIntentMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xc0\x04\n\x0cOfferMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x11\n\tcoin_from\x18\x02 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x03 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x06 \x01(\x04\x12\x12\n\ntime_valid\x18\x07 \x01(\x04\x12\x33\n\tlock_type\x18\x08 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\t \x01(\r\x12\x11\n\tswap_type\x18\n \x01(\r\x12\x15\n\rproof_address\x18\x0b \x01(\t\x12\x17\n\x0fproof_signature\x18\x0c \x01(\t\x12\x15\n\rpkhash_seller\x18\r \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\x0e \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0f \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x10 \x01(\x04\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\x12\x13\n\x0bproof_utxos\x18\x13 \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\xe7\x01\n\nBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x06 \x01(\x0c\x12\x15\n\rproof_address\x18\x07 \x01(\t\x12\x17\n\x0fproof_signature\x18\x08 \x01(\t\x12\x13\n\x0bproof_utxos\x18\t \x01(\x0c\x12\x17\n\x0fpkhash_buyer_to\x18\r \x01(\x0c\"s\n\x0f\x42idMessage_test\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x0c\n\x04rate\x18\x05 \x01(\x04\"m\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\x12\x15\n\rpkhash_seller\x18\x04 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb7\x01\n\rXmrBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x0c\n\x04pkaf\x18\x06 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x07 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x08 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\t \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x03 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x04 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x05 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x07 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\x08 \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\t \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\n \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"X\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"\x81\x01\n\x13\x41\x44SBidIntentMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -26,29 +26,29 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_start=493
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_end=606
_globals['_BIDMESSAGE']._serialized_start=609
_globals['_BIDMESSAGE']._serialized_end=815
_globals['_BIDMESSAGE_TEST']._serialized_start=817
_globals['_BIDMESSAGE_TEST']._serialized_end=932
_globals['_BIDACCEPTMESSAGE']._serialized_start=934
_globals['_BIDACCEPTMESSAGE']._serialized_end=1020
_globals['_OFFERREVOKEMESSAGE']._serialized_start=1022
_globals['_OFFERREVOKEMESSAGE']._serialized_end=1083
_globals['_BIDREJECTMESSAGE']._serialized_start=1085
_globals['_BIDREJECTMESSAGE']._serialized_end=1144
_globals['_XMRBIDMESSAGE']._serialized_start=1147
_globals['_XMRBIDMESSAGE']._serialized_end=1330
_globals['_XMRSPLITMESSAGE']._serialized_start=1332
_globals['_XMRSPLITMESSAGE']._serialized_end=1416
_globals['_XMRBIDACCEPTMESSAGE']._serialized_start=1419
_globals['_XMRBIDACCEPTMESSAGE']._serialized_end=1675
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_start=1677
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_end=1791
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_start=1793
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_end=1881
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_start=1883
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_end=1960
_globals['_ADSBIDINTENTMESSAGE']._serialized_start=1963
_globals['_ADSBIDINTENTMESSAGE']._serialized_end=2092
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_start=2094
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_end=2206
_globals['_BIDMESSAGE']._serialized_end=840
_globals['_BIDMESSAGE_TEST']._serialized_start=842
_globals['_BIDMESSAGE_TEST']._serialized_end=957
_globals['_BIDACCEPTMESSAGE']._serialized_start=959
_globals['_BIDACCEPTMESSAGE']._serialized_end=1068
_globals['_OFFERREVOKEMESSAGE']._serialized_start=1070
_globals['_OFFERREVOKEMESSAGE']._serialized_end=1131
_globals['_BIDREJECTMESSAGE']._serialized_start=1133
_globals['_BIDREJECTMESSAGE']._serialized_end=1192
_globals['_XMRBIDMESSAGE']._serialized_start=1195
_globals['_XMRBIDMESSAGE']._serialized_end=1378
_globals['_XMRSPLITMESSAGE']._serialized_start=1380
_globals['_XMRSPLITMESSAGE']._serialized_end=1464
_globals['_XMRBIDACCEPTMESSAGE']._serialized_start=1467
_globals['_XMRBIDACCEPTMESSAGE']._serialized_end=1723
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_start=1725
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_end=1839
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_start=1841
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_end=1929
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_start=1931
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_end=2008
_globals['_ADSBIDINTENTMESSAGE']._serialized_start=2011
_globals['_ADSBIDINTENTMESSAGE']._serialized_end=2140
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_start=2142
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_end=2254
# @@protoc_insertion_point(module_scope)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert
# Copyright (c) 2020-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -10,12 +10,15 @@ from basicswap.db import (
from basicswap.util import (
SerialiseNum,
)
from basicswap.util.script import (
decodeScriptNum,
)
from basicswap.script import (
OpCodes,
)
from basicswap.basicswap_util import (
SwapTypes,
EventLogTypes,
SwapTypes,
)
from . import ProtocolInterface
@@ -23,13 +26,13 @@ INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
ABS_LOCK_TIME_LEEWAY = 10 * 60
def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY) -> bytearray:
def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256) -> bytearray:
script = bytearray([
OpCodes.OP_IF,
OpCodes.OP_SIZE,
0x01, 0x20, # 32
OpCodes.OP_EQUALVERIFY,
OpCodes.OP_SHA256,
op_hash,
0x20]) \
+ secret_hash \
+ bytearray([
@@ -54,6 +57,46 @@ def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pk
return script
def verifyContractScript(script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256):
if script[0] != OpCodes.OP_IF or \
script[1] != OpCodes.OP_SIZE or \
script[2] != 0x01 or script[3] != 0x20 or \
script[4] != OpCodes.OP_EQUALVERIFY or \
script[5] != op_hash or \
script[6] != 0x20:
return False, None, None, None, None
o = 7
script_hash = script[o: o + 32]
o += 32
if script[o] != OpCodes.OP_EQUALVERIFY or \
script[o + 1] != OpCodes.OP_DUP or \
script[o + 2] != OpCodes.OP_HASH160 or \
script[o + 3] != 0x14:
return False, script_hash, None, None, None
o += 4
pkh_redeem = script[o: o + 20]
o += 20
if script[o] != OpCodes.OP_ELSE:
return False, script_hash, pkh_redeem, None, None
o += 1
lock_val, nb = decodeScriptNum(script, o)
o += nb
if script[o] != op_lock or \
script[o + 1] != OpCodes.OP_DROP or \
script[o + 2] != OpCodes.OP_DUP or \
script[o + 3] != OpCodes.OP_HASH160 or \
script[o + 4] != 0x14:
return False, script_hash, pkh_redeem, lock_val, None
o += 5
pkh_refund = script[o: o + 20]
o += 20
if script[o] != OpCodes.OP_ENDIF or \
script[o + 1] != OpCodes.OP_EQUALVERIFY or \
script[o + 2] != OpCodes.OP_CHECKSIG:
return False, script_hash, pkh_redeem, lock_val, pkh_refund
return True, script_hash, pkh_redeem, lock_val, pkh_refund
def extractScriptSecretHash(script):
return script[7:39]

View File

@@ -9,7 +9,7 @@ from sqlalchemy.orm import scoped_session
from basicswap.util import (
ensure,
)
from basicswap.interface import Curves
from basicswap.interface.base import Curves
from basicswap.chainparams import (
Coins,
)
@@ -21,13 +21,17 @@ from basicswap.basicswap_util import (
from . import ProtocolInterface
from basicswap.contrib.test_framework.script import (
CScript, CScriptOp,
OP_CHECKMULTISIG)
OP_CHECKMULTISIG
)
def addLockRefundSigs(self, xmr_swap, ci):
self.log.debug('Setting lock refund tx sigs')
witness_stack = [
b'',
witness_stack = []
if ci.coin_type() not in (Coins.DCR, ):
witness_stack += [b'', ]
witness_stack += [
xmr_swap.al_lock_refund_tx_sig,
xmr_swap.af_lock_refund_tx_sig,
xmr_swap.a_lock_tx_script,
@@ -74,7 +78,8 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key):
address_to = self.getCachedStealthAddressForCoin(offer.coin_to)
amount = bid.amount_to
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, spend_actual_balance=True)
lock_tx_vout = bid.getLockTXBVout()
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, amount, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True, lock_tx_vout=lock_tx_vout)
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, txid.hex(), session)
session.commit()

View File

@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert
# Copyright (c) 2020-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import time
import json
import shlex
import urllib
import logging
import traceback
import subprocess
from xmlrpc.client import (
@@ -20,18 +18,6 @@ from xmlrpc.client import (
from .util import jsonDecimal
def waitForRPC(rpc_func, expect_wallet=True, max_tries=7):
for i in range(max_tries + 1):
try:
rpc_func('getwalletinfo' if expect_wallet else 'getblockchaininfo')
return
except Exception as ex:
if i < max_tries:
logging.warning('Can\'t connect to RPC: %s. Retrying in %d second/s.', str(ex), (i + 1))
time.sleep(i + 1)
raise ValueError('waitForRPC failed')
class Jsonrpc():
# __getattr__ complicates extending ServerProxy
def __init__(self, uri, transport=None, encoding=None, verbose=False,

View File

@@ -26,3 +26,5 @@ class OpCodes(IntEnum):
OP_CHECKSIG = 0xac,
OP_CHECKLOCKTIMEVERIFY = 0xb1,
OP_CHECKSEQUENCEVERIFY = 0xb2,
OP_SHA256_DECRED = 0xc0,

View File

@@ -113,7 +113,7 @@
</div>
</div>
</div>
</div>
</div>
<div class="py-0 border-b items-center justify-between -mx-4 mb-6 pb-3 border-gray-400 border-opacity-20">
<div class="w-full md:w-10/12">
<div class="flex flex-wrap -m-3 w-full sm:w-auto px-4 mb-6 sm:mb-0">
@@ -129,7 +129,7 @@
<select class="cursor-not-allowed disabled-select pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="swap_type" id="swap_type" disabled>
{% for a in swap_types %}
<option{% if data.swap_type==a[0] %} selected{% endif %} value="{{ a[0] }}">{{ a[1] }}</option>
{% endfor %}
{% endfor %}
</select>
</div>
</div>
@@ -145,11 +145,11 @@
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Coin You Send:</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Coin You Send:</p>
<div class="custom-select">
<div class="relative">
{{ input_down_arrow_offer_svg | safe }}
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_from" name="coin_from" onchange="set_rate('coin_from');" disabled>
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_from" name="coin_from" disabled>
<option value="-1">Select coin you send</option>
{% for c in coins_from %}
<option{% if data.coin_from==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png">{{ c[1] }}
@@ -162,9 +162,9 @@
</div>
</div>
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Send</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Send</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" onchange="set_rate('amt_from');" readonly>
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" readonly>
</div>
</div>
{% if data.swap_style == 'xmr' %}
@@ -216,11 +216,11 @@
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Coin You Get</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Coin You Get</p>
<div class="custom-select">
<div class="relative">
{{ input_down_arrow_offer_svg | safe }}
<select class="cursor-not-allowed disabled-select select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_to" name="coin_to" onchange="set_rate('coin_to');" disabled>
<select class="cursor-not-allowed disabled-select select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_to" name="coin_to" disabled>
<option value="-1"></option>
{% for c in coins %}
<option{% if data.coin_to==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png">{{ c[1] }}</option>
@@ -233,9 +233,9 @@
</div>
</div>
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Get</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Get</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" onchange="set_rate('amt_to');" readonly>
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" readonly>
</div>
</div>
{% if data.swap_style == 'xmr' and coin_to != '6' %}
@@ -301,7 +301,7 @@
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_rate_svg | safe }}
</div>
<input class="cursor-not-allowed disabled-input pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" onchange="set_rate('rate');" readonly>
<input class="cursor-not-allowed disabled-input pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" readonly>
</div>
</div>
</div>
@@ -426,7 +426,7 @@
<div class="w-full md:w-auto p-1.5">
<button name="step2" type="submit" value="Back" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">
<span>Back</span>
</button>
</button>
</div>
<div class="w-full md:w-auto p-1.5">
<button name="submit_offer" value="Continue" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-green-600 hover:border-green-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">

View File

@@ -153,11 +153,11 @@
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Coin You Send</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Coin You Send</p>
<div class="custom-select">
<div class="relative">
{{ input_down_arrow_offer_svg | safe }}
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_from" name="coin_from" onchange="set_rate('coin_from');" disabled>
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_from" name="coin_from" disabled>
<option value="-1">Select coin you send</option>
{% for c in coins_from %}
<option{% if data.coin_from==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png">{{ c[1] }}</option>
@@ -169,9 +169,9 @@
</div>
</div>
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Send</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Send</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input appearance-none pr-10 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" onchange="set_rate('amt_from');" readonly>
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input appearance-none pr-10 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" readonly>
</div>
</div>
{% if data.swap_style == 'xmr' %}
@@ -213,11 +213,11 @@
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Coin You Get</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Coin You Get</p>
<div class="custom-select">
<div class="relative">
{{ input_down_arrow_offer_svg | safe }}
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_to" name="coin_to" onchange="set_rate('coin_to');" disabled>
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_to" name="coin_to" disabled>
<option value="-1"></option>
{% for c in coins %}
<option{% if data.coin_to==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png">{{ c[1] }}</option>
@@ -228,9 +228,9 @@
</div>
</div>
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Get</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Get</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" onchange="set_rate('amt_to');" readonly>
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" readonly>
</div>
</div>
{% if data.swap_style == 'xmr' and coin_to != '6' %}
@@ -286,7 +286,7 @@
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_rate_svg | safe }}
</div>
<input class="cursor-not-allowed disabled-input pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" onchange="set_rate('rate');" readonly>
<input class="cursor-not-allowed disabled-input pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" readonly>
</div>
</div>
</div>
@@ -321,7 +321,7 @@
<input class="pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="number" name="lockmins" min="10" max="5000" value="{{ data.lockmins }}">
</div>
{% if data.swap_style != 'xmr' %}
<div class="text-sm text-gray-500 mt-1.5">(Participate txn will be locked for half the time.)</div>
<div class="text-sm text-gray-500 mt-1.5">(Participate txn will be locked for half the time.)</div>
{% endif %}
</div>
{% else %}
@@ -442,7 +442,7 @@
<input type="hidden" name="rate_var" value="true">
{% endif %}
</form>
<script src="static/js/new_offer.js"></script>
<script src="static/js/new_offer.js"></script>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
{% include 'header.html' %}
{% from 'style.html' import breadcrumb_line_svg, input_arrow_down_svg %}
{% from 'style.html' import breadcrumb_line_svg, input_arrow_down_svg %}
<div class="container mx-auto">
<section class="p-5 mt-5">
<div class="flex flex-wrap items-center -m-2">
@@ -62,8 +62,8 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6">
<div class="relative">
{{ input_arrow_down_svg| safe }}
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="coin_type">
{{ input_arrow_down_svg| safe }}
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="coin_type" id="coin_type" onchange="set_coin();">
<option value="-1" {% if coin_type==-1 %} selected{% endif %}>Select Coin</option>
{% for c in coins %}
<option value="{{ c[0] }}" {% if coin_type==c[0] %} selected{% endif %}>{{ c[1] }}</option>
@@ -72,21 +72,21 @@
</div>
</td>
<td class="py-3 px-6">
<input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="cmd">
<input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="cmd" id="cmd" oninput="set_method();">
</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6">
<div class="relative">
{{ input_arrow_down_svg| safe }}
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="call_type">
{{ input_arrow_down_svg| safe }}
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="call_type" id="call_type" >
<option value="cli" {% if call_type=="cli" %} selected{% endif %}>CLI</option>
<option value="http" {% if call_type=="http" %} selected{% endif %}>HTTP</option>
</select>
</div>
</td>
<td class="py-3 px-6">
<input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="type_map" title="Convert inputs when using http. Example: 'sibj' 1st parameter is a string, 2nd is converted to an int then boolean and json object or array">
<input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="type_map" id="type_map" title="Convert inputs when using http. Example: 'sifbj' 1st parameter is a string, 2nd is converted to an int, 3rd to float then boolean and json object or array">
</td>
</table>
</div>
@@ -148,7 +148,7 @@
</tr>
</table>
</div>
</div>
</div>
</form>
</div>
</div>
@@ -176,4 +176,28 @@
</div>
{% include 'footer.html' %}
</body>
<script>
function set_method() {
const coin_type = document.getElementById('coin_type').value;
if (coin_type == 4 || coin_type == -6) {
const cmd = document.getElementById('cmd');
const type_map = document.getElementById('type_map');
let method = cmd.value.split(' ')[0];
if (method == 'sendtoaddress') {
type_map.value = 'sf';
}
}
}
function set_coin() {
const coin_type = document.getElementById('coin_type').value;
let call_type = document.getElementById('call_type');
if (coin_type == 4 || coin_type == -6) {
call_type.disabled = true;
call_type.value = 'http';
} else {
call_type.disabled = false;
}
set_method();
}
</script>
</html>

View File

@@ -1,5 +1,5 @@
{% include 'header.html' %}
{% from 'style.html' import select_box_arrow_svg, select_box_class, circular_arrows_svg, circular_error_svg, circular_info_svg, cross_close_svg, breadcrumb_line_svg, withdraw_svg, utxo_groups_svg, create_utxo_svg, red_cross_close_svg, blue_cross_close_svg, circular_update_messages_svg, circular_error_messages_svg %}
{% from 'style.html' import select_box_arrow_svg, select_box_class, circular_arrows_svg, circular_error_svg, circular_info_svg, cross_close_svg, breadcrumb_line_svg, withdraw_svg, utxo_groups_svg, create_utxo_svg, red_cross_close_svg, blue_cross_close_svg, circular_update_messages_svg, circular_error_messages_svg %}
<script src="/static/js/libs//qrcode.js"></script>
<div class="container mx-auto">
<section class="p-5 mt-5">
@@ -27,7 +27,7 @@
</div>
</div>
</section>
{% include 'inc_messages.html' %}
{% include 'inc_messages.html' %}
{% if w.updating %}
<section class="py-4" id="messages_updating" role="alert">
<div class="container px-4 mx-auto">
@@ -42,7 +42,7 @@
<li class="font-semibold text-sm text-blue-500 error_msg"><span class="bold">UPDATING:</span></li>
<li class="font-medium text-sm text-blue-500">Please wait...</li>
</ul>
</div>
</div>
</div>
<div class="w-auto p-2">
<button type="button" class="ms-auto bg-blue-50 text-blue-500 rounded-lg focus:ring-0 focus:ring-blue-400 p-1.5 hover:bg-blue-200 inline-flex items-center justify-center h-8 w-8 focus:outline-none dark:bg-gray-800 dark:text-blue-400 dark:hover:bg-gray-700" data-dismiss-target="#messages_updating" aria-label="Close"><span class="sr-only">Close</span>
@@ -55,7 +55,7 @@
</section>
{% endif %}
{% if w.havedata %}
{% if w.error %}
{% if w.error %}
<section class="py-4" id="messages_error" role="alert">
<div class="container px-4 mx-auto">
<div class="p-6 text-green-800 rounded-lg bg-red-50 border border-red-400 dark:bg-gray-500 dark:text-red-400 rounded-md">
@@ -113,7 +113,7 @@
</tr> {% if w.cid == '1' %} {# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Blind"> </span>Blind Balance: </td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }} (<span class="usd-value"></span>)
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }} (<span class="usd-value"></span>)
{% if w.blind_unconfirmed %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Unconfirmed: +{{ w.blind_unconfirmed }} {{ w.ticker }}</span>
{% endif %}
@@ -136,11 +136,11 @@
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }} (<span class="usd-value"></span>)
{% if w.mweb_pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }} </span>
{% endif %}
{% endif %}
</td>
</tr>
{% endif %}
{# / LTC #}
{# / LTC #}
{% if w.locked_utxos %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Locked Outputs:</td>
@@ -202,9 +202,11 @@
<div class="container mt-5 mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6">
{% if w.cid != '4' %} {# DCR #}
<div class="flex flex-wrap justify-end">
<div class="w-full md:w-auto p-1.5"> <input class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none cursor-pointer" type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"> </div>
</div>
{% endif %}
</div>
</div>
</div>
@@ -369,7 +371,7 @@
</div>
</div>
</div>
{% endif %}
{% endif %}
{# / LTC #}
</div>
</div>
@@ -383,7 +385,7 @@
<script>
// Particl Stealth
var stealthAddress = "{{ w.stealth_address }}";
var qrCodeStealth = new QRCode(document.getElementById("qrcode-stealth"), {
text: stealthAddress,
width: 170,
@@ -399,7 +401,7 @@
<script>
// Litecoin MWEB
var mwebAddress = "{{ w.mweb_address }}";
var qrCodeMWEB = new QRCode(document.getElementById("qrcode-mweb"), {
text: mwebAddress,
width: 170,
@@ -408,14 +410,14 @@
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.L
});
</script>
</script>
{% endif %}
{% if w.cid == '6' %}
{# XMR #}
<script>
// Monero Sub
var moneroSubAddress = "{{ w.deposit_address }}";
var qrCodeMoneroSub = new QRCode(document.getElementById("qrcode-monero-sub"), {
text: moneroSubAddress,
width: 170,
@@ -428,7 +430,7 @@
<script>
// Monero Main
var moneroMainAddress = "{{ w.main_address }}";
var qrCodeMoneroMain = new QRCode(document.getElementById("qrcode-monero-main"), {
text: moneroMainAddress,
width: 170,
@@ -442,7 +444,7 @@
<script>
// Default
var defaultAddress = "{{ w.deposit_address }}";
var qrCodeDepost = new QRCode(document.getElementById("qrcode-deposit"), {
text: defaultAddress,
width: 170,
@@ -462,48 +464,48 @@
document.execCommand('copy');
document.body.removeChild(el);
}
function copyAndShowMessage(elementId) {
const addressElement = document.getElementById(elementId);
if (!addressElement) return;
const addressText = addressElement.innerText.trim();
copyToClipboard(addressText);
addressElement.innerText = 'Copied to clipboard';
const originalWidth = addressElement.offsetWidth;
addressElement.classList.add('copying');
addressElement.parentElement.style.width = `${originalWidth}px`;
setTimeout(function () {
addressElement.innerText = addressText;
addressElement.classList.remove('copying');
addressElement.parentElement.style.width = '';
}, 2000);
}
const stealthAddressElement = document.getElementById('stealth_address');
if (stealthAddressElement) {
stealthAddressElement.addEventListener('click', function () {
copyAndShowMessage('stealth_address');
});
}
const mainDepositAddressElement = document.getElementById('main_deposit_address');
if (mainDepositAddressElement) {
mainDepositAddressElement.addEventListener('click', function () {
copyAndShowMessage('main_deposit_address');
});
}
const moneroMainAddressElement = document.getElementById('monero_main_address');
if (moneroMainAddressElement) {
moneroMainAddressElement.addEventListener('click', function () {
copyAndShowMessage('monero_main_address');
});
}
const moneroSubAddressElement = document.getElementById('monero_sub_address');
if (moneroSubAddressElement) {
moneroSubAddressElement.addEventListener('click', function () {
@@ -572,7 +574,7 @@
var selectedType = typeSelect.value;
var floatBalance;
var calculatedAmount;
switch(selectedType) {
case 'plain':
floatBalance = parseFloat(balance);
@@ -591,7 +593,7 @@
calculatedAmount = floatBalance * percent;
break;
}
amountInput.value = calculatedAmount.toFixed(8);
}
</script>
@@ -606,7 +608,7 @@
var selectedType = typeSelect.value;
var floatBalance;
var calculatedAmount;
switch(selectedType) {
case 'plain':
floatBalance = parseFloat(balance);
@@ -621,7 +623,7 @@
calculatedAmount = floatBalance * percent;
break;
}
amountInput.value = calculatedAmount.toFixed(8);
}
</script>
@@ -633,10 +635,10 @@
var amountInput = document.getElementById('amount');
var floatBalance;
var calculatedAmount;
floatBalance = parseFloat(balance);
calculatedAmount = floatBalance * percent;
if (cid === '6' && percent === 1) {
amountInput.setAttribute('data-hidden', 'true');
amountInput.placeholder = 'Sweep All';
@@ -652,7 +654,7 @@
amountInput.placeholder = '';
amountInput.disabled = false;
}
if (cid === '6' && percent === 1) {
var sweepAllCheckbox = document.getElementById('sweepall');
if (sweepAllCheckbox) {
@@ -665,7 +667,7 @@
}
}
}
</script>
{% endif %}
</div>
@@ -677,7 +679,7 @@
<td class="py-3 px-6"> <input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="sweepall" name="sweepall_{{ w.cid }}" {% if w.wd_sweepall==true %} checked=checked{% endif %}> </td> {% else %} <td class="py-3 px-6 bold">Subtract Fee:</td>
<td class="py-3 px-6"> <input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" name="subfee_{{ w.cid }}" {% if w.wd_subfee==true %} checked=checked{% endif %}> </td>
{% endif %}
<td>
<td>
</td>
</tr>
{% if w.cid == '1' %}
@@ -855,7 +857,7 @@
'DECRED': 'DCR',
'WOWNERO': 'WOW'
};
const getUsdValue = (cryptoValue, coinSymbol) => fetch(`https://min-api.cryptocompare.com/data/price?fsym=${coinSymbol}&tsyms=USD`)
.then(response => response.json())
.then(data => {
@@ -866,16 +868,16 @@
throw new Error(`Invalid exchange rate for ${coinSymbol}`);
}
});
const updateUsdValue = async (cryptoCell, coinFullName, usdValueSpan) => {
const coinSymbol = coinNameToSymbol[coinFullName] || '';
if (!coinSymbol) {
console.error(`Coin symbol not found for full name: ${coinFullName}`);
return;
}
const cryptoValue = parseFloat(cryptoCell.textContent);
if (!isNaN(cryptoValue) && cryptoValue !== 0) {
try {
const usdValue = await getUsdValue(cryptoValue, coinSymbol);
@@ -894,19 +896,19 @@
}
}
};
const calculateTotalUsdValue = async () => {
const coinNameValues = document.querySelectorAll('.coinname-value');
let totalUsdValue = 0;
for (const coinNameValue of coinNameValues) {
const coinFullName = coinNameValue.getAttribute('data-coinname');
const cryptoValue = parseFloat(coinNameValue.textContent);
const coinSymbol = coinNameToSymbol[coinFullName];
if (coinSymbol) {
const usdValueSpan = coinNameValue.querySelector('.usd-value');
if (!isNaN(cryptoValue) && cryptoValue !== 0) {
try {
const usdValue = await getUsdValue(cryptoValue, coinSymbol);
@@ -926,24 +928,24 @@
console.error(`Coin symbol not found for full name: ${coinFullName}`);
}
}
const totalUsdValueElement = document.getElementById('total-usd-value');
if (totalUsdValueElement) {
totalUsdValueElement.textContent = `$${totalUsdValue.toFixed(2)}`;
}
};
document.addEventListener('DOMContentLoaded', () => {
const coinNameValues = document.querySelectorAll('.coinname-value');
for (const coinNameValue of coinNameValues) {
const coinFullName = coinNameValue.getAttribute('data-coinname');
const usdValueSpan = coinNameValue.querySelector('.usd-value');
updateUsdValue(coinNameValue, coinFullName, usdValueSpan);
}
calculateTotalUsdValue();
function set_sweep_all(element) {
let input = document.getElementById('amount');
if (element.checked) {
@@ -952,7 +954,7 @@
input.disabled = false;
}
}
let cb_sweepall = document.getElementById('sweepall');
if (cb_sweepall) {
set_sweep_all(cb_sweepall);
@@ -960,7 +962,7 @@
set_sweep_all(event.currentTarget);
});
}
});
</script>
{% include 'footer.html' %}
@@ -968,11 +970,11 @@
function confirmReseed() {
return confirm("Are you sure?\nBackup your wallet before and after.\nWon't detect used keys.\nShould only be used for new wallets.");
}
function confirmWithdrawal() {
return confirm("Are you sure?");
}
function confirmUTXOResize() {
return confirm("Are you sure?");
}

View File

@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2022-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import hashlib
from basicswap.contrib.segwit_addr import bech32_decode, convertbits, bech32_encode
from basicswap.util.crypto import ripemd160
from basicswap.util.crypto import ripemd160, sha256
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
@@ -68,7 +67,7 @@ def encodeStealthAddress(prefix_byte: int, scan_pubkey: bytes, spend_pubkey: byt
data += bytes((0x00,)) # num prefix bits
b = bytes((prefix_byte,)) + data
b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
b += sha256(sha256(b))[:4]
return b58encode(b)
@@ -83,16 +82,15 @@ def toWIF(prefix_byte: int, b: bytes, compressed: bool = True) -> str:
b = bytes((prefix_byte,)) + b
if compressed:
b += bytes((0x01,))
b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
b += sha256(sha256(b))[:4]
return b58encode(b)
def getKeyID(key_data: bytes) -> bytes:
sha256_hash = hashlib.sha256(key_data).digest()
return ripemd160(sha256_hash)
return ripemd160(sha256(key_data))
def bech32Decode(hrp, addr):
def bech32Decode(hrp: str, addr: str) -> bytes:
hrpgot, data = bech32_decode(addr)
if hrpgot != hrp:
return None
@@ -102,25 +100,26 @@ def bech32Decode(hrp, addr):
return bytes(decoded)
def bech32Encode(hrp, data):
def bech32Encode(hrp: str, data: bytes) -> str:
ret = bech32_encode(hrp, convertbits(data, 8, 5))
if bech32Decode(hrp, ret) is None:
return None
return ret
def decodeAddress(address_str: str):
b58_addr = b58decode(address_str)
if b58_addr is not None:
address = b58_addr[:-4]
checksum = b58_addr[-4:]
assert (hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] == checksum), 'Checksum mismatch'
return b58_addr[:-4]
return None
def decodeAddress(address: str) -> bytes:
addr_data = b58decode(address)
if addr_data is None:
return None
prefixed_data = addr_data[:-4]
checksum = addr_data[-4:]
if sha256(sha256(prefixed_data))[:4] != checksum:
raise ValueError('Checksum mismatch')
return prefixed_data
def encodeAddress(address: bytes) -> str:
checksum = hashlib.sha256(hashlib.sha256(address).digest()).digest()
checksum = sha256(sha256(address))
return b58encode(address + checksum[0:4])

View File

@@ -1,23 +1,41 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2022-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from Crypto.Hash import RIPEMD160, SHA256 # pycryptodome
from basicswap.contrib.blake256.blake256 import blake_hash
from Crypto.Hash import HMAC, RIPEMD160, SHA256, SHA512 # pycryptodome
def sha256(data):
def sha256(data: bytes) -> bytes:
h = SHA256.new()
h.update(data)
return h.digest()
def ripemd160(data):
def sha512(data: bytes) -> bytes:
h = SHA512.new()
h.update(data)
return h.digest()
def ripemd160(data: bytes) -> bytes:
h = RIPEMD160.new()
h.update(data)
return h.digest()
def hash160(s):
return ripemd160(sha256(s))
def blake256(data: bytes) -> bytes:
return blake_hash(data)
def hash160(data: bytes) -> bytes:
return ripemd160(sha256(data))
def hmac_sha512(secret: bytes, data: bytes) -> bytes:
h = HMAC.new(secret, digestmod=SHA512)
h.update(data)
return h.digest()

116
basicswap/util/extkey.py Normal file
View File

@@ -0,0 +1,116 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .crypto import blake256, hash160, hmac_sha512, ripemd160
from coincurve.keys import (
PrivateKey,
PublicKey)
def BIP32Hash(chaincode: bytes, child_no: int, key_data_type: int, keydata: bytes):
return hmac_sha512(chaincode, key_data_type.to_bytes(1, 'big') + keydata + child_no.to_bytes(4, 'big'))
def hash160_dcr(data: bytes) -> bytes:
return ripemd160(blake256(data))
class ExtKeyPair():
__slots__ = ('_depth', '_fingerprint', '_child_no', '_chaincode', '_key', '_pubkey', 'hash_func')
def __init__(self, coin_type=1):
if coin_type == 4:
self.hash_func = hash160_dcr
else:
self.hash_func = hash160
def set_seed(self, seed: bytes) -> None:
hashout: bytes = hmac_sha512(b'Bitcoin seed', seed)
self._key = hashout[:32]
self._pubkey = None
self._chaincode = hashout[32:]
self._depth = 0
self._child_no = 0
self._fingerprint = b'\0' * 4
def has_key(self) -> bool:
return False if self._key is None else True
def neuter(self) -> None:
if self._key is None:
raise ValueError('Already neutered')
self._pubkey = PublicKey.from_secret(self._key).format()
self._key = None
def derive(self, child_no: int):
out = ExtKeyPair()
out._depth = self._depth + 1
out._child_no = child_no
if (child_no >> 31) == 0:
if self._key:
K = PublicKey.from_secret(self._key)
k_encoded = K.format()
else:
K = PublicKey(self._pubkey)
k_encoded = self._pubkey
out._fingerprint = self.hash_func(k_encoded)[:4]
new_hash = BIP32Hash(self._chaincode, child_no, k_encoded[0], k_encoded[1:])
out._chaincode = new_hash[32:]
if self._key:
k = PrivateKey(self._key)
k.add(new_hash[:32], update=True)
out._key = k.secret
out._pubkey = None
else:
K.add(new_hash[:32], update=True)
out._key = None
out._pubkey = K.format()
else:
k = PrivateKey(self._key)
out._fingerprint = self.hash_func(self._pubkey if self._pubkey else PublicKey.from_secret(self._key).format())[:4]
new_hash = BIP32Hash(self._chaincode, child_no, 0, self._key)
out._chaincode = new_hash[32:]
k.add(new_hash[:32], update=True)
out._key = k.secret
out._pubkey = None
out.hash_func = self.hash_func
return out
def encode_v(self) -> bytes:
return self._depth.to_bytes(1, 'big') + \
self._fingerprint + \
self._child_no.to_bytes(4, 'big') + \
self._chaincode + \
b'\x00' + \
self._key
def encode_p(self) -> bytes:
pubkey = PublicKey.from_secret(self._key).format() if self._pubkey is None else self._pubkey
return self._depth.to_bytes(1, 'big') + \
self._fingerprint + \
self._child_no.to_bytes(4, 'big') + \
self._chaincode + \
pubkey
def decode(self, data: bytes) -> None:
if len(data) != 74:
raise ValueError('Unexpected extkey length')
self._depth = data[0]
self._fingerprint = data[1:5]
self._child_no = int.from_bytes(data[5:9], 'big')
self._chaincode = data[9:41]
if data[41] == 0:
self._key = data[42:]
self._pubkey = None
else:
self._key = None
self._pubkey = data[41:]

View File

@@ -5,13 +5,41 @@
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
def decode_varint(b: bytes) -> int:
i = 0
shift = 0
for c in b:
i += (c & 0x7F) << shift
shift += 7
return i
def decode_compactsize(b: bytes, offset: int = 0) -> (int, int):
i = b[offset]
if i < 0xfd:
return i, 1
offset += 1
if i == 0xfd:
return int.from_bytes(b[offset: offset + 2]), 3
if i == 0xfe:
return int.from_bytes(b[offset: offset + 4]), 5
# 0xff
return int.from_bytes(b[offset: offset + 8]), 9
def encode_compactsize(i: int) -> bytes:
if i < 0xfd:
return bytes((i,))
if i <= 0xffff:
return bytes((0xfd,)) + i.to_bytes(2, 'little')
if i <= 0xffffffff:
return bytes((0xfe,)) + i.to_bytes(4, 'little')
return bytes((0xff,)) + i.to_bytes(8, 'little')
def decode_varint(b: bytes, offset: int = 0) -> (int, int):
i: int = 0
num_bytes: int = 0
while True:
c = b[offset + num_bytes]
i += (c & 0x7F) << (num_bytes * 7)
num_bytes += 1
if not c & 0x80:
break
if num_bytes > 8:
raise ValueError('Too many bytes')
return i, num_bytes
def encode_varint(i: int) -> bytes:

View File

@@ -5,37 +5,40 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import sys
import json
import time
import mmap
import stat
import contextlib
import gnupg
import socks
import hashlib
import json
import logging
import mmap
import os
import platform
import random
import shutil
import signal
import socket
import hashlib
import socks
import stat
import sys
import tarfile
import zipfile
import logging
import platform
import contextlib
import threading
import time
import urllib.parse
import zipfile
from urllib.error import ContentTooShortError
from urllib.request import Request, urlopen
from urllib.parse import _splittype
from urllib.request import Request, urlopen
import basicswap.config as cfg
from basicswap import __version__
from basicswap.base import getaddrinfo_tor
from basicswap.basicswap import BasicSwap
from basicswap.chainparams import Coins
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from basicswap import __version__
from basicswap.ui.util import getCoinName
from basicswap.util import toBool
from basicswap.util.rfc2440 import rfc2440_hash_password
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from bin.basicswap_run import startDaemon, startXmrWalletDaemon
PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0')
@@ -64,6 +67,9 @@ FIRO_VERSION_TAG = os.getenv('FIRO_VERSION_TAG', '')
NAV_VERSION = os.getenv('NAV_VERSION', '7.0.3')
NAV_VERSION_TAG = os.getenv('NAV_VERSION_TAG', '')
DCR_VERSION = os.getenv('DCR_VERSION', '1.8.1')
DCR_VERSION_TAG = os.getenv('DCR_VERSION_TAG', '')
GUIX_SSL_CERT_DIR = None
ADD_PUBKEY_URL = os.getenv('ADD_PUBKEY_URL', '')
@@ -83,10 +89,12 @@ known_coins = {
'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)),
'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)),
'navcoin': (NAV_VERSION, NAV_VERSION_TAG, ('nav_builder',)),
'decred': (DCR_VERSION, DCR_VERSION_TAG, ('decred_release',)),
}
disabled_coins = [
'navcoin',
'namecoin', # Needs update
]
expected_key_ids = {
@@ -100,6 +108,7 @@ expected_key_ids = {
'pasta': ('52527BEDABE87984',),
'reuben': ('1290A1D0FA7EE109',),
'nav_builder': ('2782262BF6E7FADB',),
'decred_release': ('6D897EDF518A031D',),
}
USE_PLATFORM = os.getenv('USE_PLATFORM', platform.system())
@@ -160,6 +169,14 @@ BTC_ONION_PORT = int(os.getenv('BTC_ONION_PORT', 8334))
BTC_RPC_USER = os.getenv('BTC_RPC_USER', '')
BTC_RPC_PWD = os.getenv('BTC_RPC_PWD', '')
DCR_RPC_HOST = os.getenv('DCR_RPC_HOST', '127.0.0.1')
DCR_RPC_PORT = int(os.getenv('DCR_RPC_PORT', 9109))
DCR_WALLET_RPC_HOST = os.getenv('DCR_WALLET_RPC_HOST', '127.0.0.1')
DCR_WALLET_RPC_PORT = int(os.getenv('DCR_WALLET_RPC_PORT', 9209))
DCR_WALLET_PWD = os.getenv('DCR_WALLET_PWD', random.randbytes(random.randint(14, 18)).hex())
DCR_RPC_USER = os.getenv('DCR_RPC_USER', 'user')
DCR_RPC_PWD = os.getenv('DCR_RPC_PWD', random.randbytes(random.randint(14, 18)).hex())
NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1')
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698))
@@ -355,16 +372,16 @@ def setConnectionParameters(timeout: int = 5, allow_set_tor: bool = True):
socket.setdefaulttimeout(timeout)
def popConnectionParameters():
def popConnectionParameters() -> None:
if use_tor_proxy:
socket.socket = default_socket
socket.getaddrinfo = default_socket_getaddrinfo
socket.setdefaulttimeout(default_socket_timeout)
def downloadFile(url, path, timeout=5, resume_from=0):
logger.info('Downloading file %s', url)
logger.info('To %s', path)
def downloadFile(url: str, path: str, timeout=5, resume_from=0) -> None:
logger.info(f'Downloading file {url}')
logger.info(f'To {path}')
try:
setConnectionParameters(timeout=timeout)
urlretrieve(url, path, make_reporthook(resume_from), resume_from=resume_from)
@@ -385,13 +402,12 @@ def importPubkeyFromUrls(gpg, pubkeyurls):
try:
logger.info('Importing public key from url: ' + url)
rv = gpg.import_keys(downloadBytes(url))
for key in rv.fingerprints:
gpg.trust_keys(key, 'TRUST_FULLY')
break
except Exception as e:
logging.warning('Import from url failed: %s', str(e))
for key in rv.fingerprints:
gpg.trust_keys(key, 'TRUST_FULLY')
def testTorConnection():
test_url = 'https://check.torproject.org/'
@@ -519,11 +535,25 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
logging.warning('Unable to set file permissions: %s, for %s', str(e), out_path)
return
bins = [coin + 'd', coin + '-cli', coin + '-tx']
versions = version.split('.')
dir_name = 'dashcore' if coin == 'dash' else coin
if int(versions[0]) >= 22 or int(versions[1]) >= 19:
bins.append(coin + '-wallet')
if coin == 'decred':
bins = ['dcrd', 'dcrwallet']
else:
bins = [coin + 'd', coin + '-cli', coin + '-tx']
versions = version.split('.')
if int(versions[0]) >= 22 or int(versions[1]) >= 19:
bins.append(coin + '-wallet')
def get_archive_path(b):
if coin == 'pivx':
return '{}-{}/bin/{}'.format(dir_name, version, b)
elif coin == 'particl' and '_nousb-' in release_path:
return '{}-{}_nousb/bin/{}'.format(dir_name, version + version_tag, b)
elif coin == 'decred':
return '{}-{}-v{}/{}'.format(dir_name, extra_opts['arch_name'], version, b)
else:
return '{}-{}/bin/{}'.format(dir_name, version + version_tag, b)
if 'win32' in BIN_ARCH or 'win64' in BIN_ARCH:
with zipfile.ZipFile(release_path) as fz:
for b in bins:
@@ -531,7 +561,7 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
out_path = os.path.join(bin_dir, b)
if (not os.path.exists(out_path)) or extract_core_overwrite:
with open(out_path, 'wb') as fout:
fout.write(fz.read('{}-{}/bin/{}'.format(dir_name, version, b)))
fout.write(fz.read(get_archive_path(b)))
try:
os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
except Exception as e:
@@ -541,15 +571,7 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
for b in bins:
out_path = os.path.join(bin_dir, b)
if not os.path.exists(out_path) or extract_core_overwrite:
if coin == 'pivx':
filename = '{}-{}/bin/{}'.format(dir_name, version, b)
elif coin == 'particl' and '_nousb-' in release_path:
filename = '{}-{}_nousb/bin/{}'.format(dir_name, version + version_tag, b)
else:
filename = '{}-{}/bin/{}'.format(dir_name, version + version_tag, b)
with open(out_path, 'wb') as fout, ft.extractfile(filename) as fi:
with open(out_path, 'wb') as fout, ft.extractfile(get_archive_path(b)) as fi:
fout.write(fi.read())
try:
os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
@@ -602,6 +624,39 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
assert_path = os.path.join(bin_dir, assert_filename)
if not os.path.exists(assert_path):
downloadFile(assert_url, assert_path)
elif coin == 'decred':
arch_name = BIN_ARCH
if USE_PLATFORM == 'Darwin':
arch_name = 'darwin-amd64'
elif USE_PLATFORM == 'Windows':
arch_name = 'windows-amd64'
else:
machine: str = platform.machine()
if 'arm' in machine:
arch_name = 'linux-arm'
else:
arch_name = 'linux-amd64'
extra_opts['arch_name'] = arch_name
release_filename = '{}-{}-{}.{}'.format(coin, version, arch_name, FILE_EXT)
release_page_url = 'https://github.com/decred/decred-binaries/releases/download/v{}'.format(version)
release_url = release_page_url + '/' + 'decred-{}-v{}.{}'.format(arch_name, version, FILE_EXT)
release_path = os.path.join(bin_dir, release_filename)
if not os.path.exists(release_path):
downloadFile(release_url, release_path)
assert_filename = 'decred-v{}-manifest.txt'.format(version)
assert_url = release_page_url + '/' + assert_filename
assert_path = os.path.join(bin_dir, assert_filename)
if not os.path.exists(assert_path):
downloadFile(assert_url, assert_path)
assert_sig_filename = assert_filename + '.asc'
assert_sig_url = assert_url + '.asc'
assert_sig_path = os.path.join(bin_dir, assert_sig_filename)
if not os.path.exists(assert_sig_path):
downloadFile(assert_sig_url, assert_sig_path)
else:
major_version = int(version.split('.')[0])
@@ -722,6 +777,8 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if coin in ('navcoin', ):
pubkey_filename = '{}_builder.pgp'.format(coin)
elif coin in ('decred', ):
pubkey_filename = '{}_release.pgp'.format(coin)
else:
pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name)
pubkeyurls = [
@@ -863,6 +920,40 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
fp.write(opt_line + '\n')
return
if coin == 'decred':
chainname = 'simnet' if chain == 'regtest' else chain
core_conf_path = os.path.join(data_dir, 'dcrd.conf')
if os.path.exists(core_conf_path):
exitWithError('{} exists'.format(core_conf_path))
with open(core_conf_path, 'w') as fp:
if chain != 'mainnet':
fp.write(chainname + '=1\n')
fp.write('debuglevel=debug\n')
fp.write('notls=1\n')
fp.write('rpclisten={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
fp.write('rpcuser={}\n'.format(core_settings['rpcuser']))
fp.write('rpcpass={}\n'.format(core_settings['rpcpassword']))
wallet_conf_path = os.path.join(data_dir, 'dcrwallet.conf')
if os.path.exists(wallet_conf_path):
exitWithError('{} exists'.format(wallet_conf_path))
with open(wallet_conf_path, 'w') as fp:
if chain != 'mainnet':
fp.write(chainname + '=1\n')
fp.write('debuglevel=debug\n')
fp.write('noservertls=1\n')
fp.write('noclienttls=1\n')
fp.write('rpcconnect={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
fp.write('rpclisten={}:{}\n'.format(core_settings['walletrpchost'], core_settings['walletrpcport']))
fp.write('username={}\n'.format(core_settings['rpcuser']))
fp.write('password={}\n'.format(core_settings['rpcpassword']))
return
core_conf_path = os.path.join(data_dir, coin + '.conf')
if os.path.exists(core_conf_path):
exitWithError('{} exists'.format(core_conf_path))
@@ -1126,13 +1217,13 @@ def printHelp():
def finalise_daemon(d):
logging.info('Interrupting {}'.format(d.pid))
logging.info('Interrupting {}'.format(d.handle.pid))
try:
d.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
d.wait(timeout=120)
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
d.handle.wait(timeout=120)
except Exception as e:
logging.info(f'Error {e} for process {d.pid}')
for fp in (d.stdout, d.stderr, d.stdin):
logging.info(f'Error {e} for process {d.handle.pid}')
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
if fp:
fp.close()
@@ -1153,7 +1244,7 @@ def test_particl_encryption(data_dir, settings, chain, use_tor_proxy):
if coin_settings['manage_daemon']:
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args))
swap_client.setDaemonPID(c, daemons[-1].pid)
swap_client.setDaemonPID(c, daemons[-1].handle.pid)
swap_client.setCoinRunParams(c)
swap_client.createCoinInterface(c)
swap_client.waitForDaemonRPC(c, with_wallet=True)
@@ -1202,6 +1293,8 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
if coin_settings['manage_wallet_daemon']:
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename))
elif c == Coins.DCR:
pass
else:
if coin_settings['manage_daemon']:
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
@@ -1211,10 +1304,23 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())]
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
swap_client.setDaemonPID(c, daemons[-1].pid)
swap_client.setDaemonPID(c, daemons[-1].handle.pid)
swap_client.setCoinRunParams(c)
swap_client.createCoinInterface(c)
if c == Coins.DCR:
if coin_settings['manage_wallet_daemon']:
from basicswap.interface.dcr.util import createDCRWallet
dcr_password = coin_settings['wallet_pwd'] if WALLET_ENCRYPTION_PWD == '' else WALLET_ENCRYPTION_PWD
extra_opts = ['--appdata="{}"'.format(coin_settings['datadir']),
'--pass={}'.format(dcr_password),
]
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
args = [os.path.join(coin_settings['bindir'], filename), '--create'] + extra_opts
hex_seed = swap_client.getWalletKey(Coins.DCR, 1).hex()
createDCRWallet(args, hex_seed, logger, threading.Event())
if c in coins_to_create_wallets_for:
swap_client.waitForDaemonRPC(c, with_wallet=False)
# Create wallet if it doesn't exist yet
@@ -1249,7 +1355,9 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
c = swap_client.getCoinIdFromName(coin_name)
if c in (Coins.PART, ):
continue
swap_client.waitForDaemonRPC(c)
if c not in (Coins.DCR, ):
# initialiseWallet only sets main_wallet_seedid_
swap_client.waitForDaemonRPC(c)
try:
swap_client.initialiseWallet(c, raise_errors=True)
except Exception as e:
@@ -1269,11 +1377,14 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
print('')
for pair in coins_failed_to_initialise:
c, _ = pair
c, e = pair
if c in (Coins.PIVX, ):
print(f'NOTE - Unable to initialise wallet for {getCoinName(c)}. To complete setup click \'Reseed Wallet\' from the ui page once chain is synced.')
else:
print(f'WARNING - Failed to initialise wallet for {getCoinName(c)}')
print(f'WARNING - Failed to initialise wallet for {getCoinName(c)}: {e}')
if 'decred' in with_coins and WALLET_ENCRYPTION_PWD != '':
print('WARNING - dcrwallet requires the password to be entered at the first startup when encrypted.\nPlease use basicswap-run with --startonlycoin=decred and the WALLET_ENCRYPTION_PWD environment var set for the initial sync.')
if particl_wallet_mnemonic is not None:
if particl_wallet_mnemonic:
@@ -1555,7 +1666,19 @@ def main():
'override_feerate': 0.002,
'conf_target': 2,
'core_version_group': 21,
'chain_lookups': 'local',
},
'bitcoin': {
'connection_type': 'rpc' if 'bitcoin' in with_coins else 'none',
'manage_daemon': True if ('bitcoin' in with_coins and BTC_RPC_HOST == '127.0.0.1') else False,
'rpchost': BTC_RPC_HOST,
'rpcport': BTC_RPC_PORT + port_offset,
'onionport': BTC_ONION_PORT + port_offset,
'datadir': os.getenv('BTC_DATA_DIR', os.path.join(data_dir, 'bitcoin')),
'bindir': os.path.join(bin_dir, 'bitcoin'),
'use_segwit': True,
'blocks_confirmed': 1,
'conf_target': 2,
'core_version_group': 22,
},
'litecoin': {
'connection_type': 'rpc' if 'litecoin' in with_coins else 'none',
@@ -1570,21 +1693,26 @@ def main():
'conf_target': 2,
'core_version_group': 21,
'min_relay_fee': 0.00001,
'chain_lookups': 'local',
},
'bitcoin': {
'connection_type': 'rpc' if 'bitcoin' in with_coins else 'none',
'manage_daemon': True if ('bitcoin' in with_coins and BTC_RPC_HOST == '127.0.0.1') else False,
'rpchost': BTC_RPC_HOST,
'rpcport': BTC_RPC_PORT + port_offset,
'onionport': BTC_ONION_PORT + port_offset,
'datadir': os.getenv('BTC_DATA_DIR', os.path.join(data_dir, 'bitcoin')),
'bindir': os.path.join(bin_dir, 'bitcoin'),
'decred': {
'connection_type': 'rpc' if 'decred' in with_coins else 'none',
'manage_daemon': True if ('decred' in with_coins and DCR_RPC_HOST == '127.0.0.1') else False,
'manage_wallet_daemon': True if ('decred' in with_coins and DCR_WALLET_RPC_HOST == '127.0.0.1') else False,
'wallet_pwd': DCR_WALLET_PWD if WALLET_ENCRYPTION_PWD == '' else '',
'rpchost': DCR_RPC_HOST,
'rpcport': DCR_RPC_PORT + port_offset,
'walletrpchost': DCR_WALLET_RPC_HOST,
'walletrpcport': DCR_WALLET_RPC_PORT + port_offset,
'rpcuser': DCR_RPC_USER,
'rpcpassword': DCR_RPC_PWD,
'datadir': os.getenv('DCR_DATA_DIR', os.path.join(data_dir, 'decred')),
'bindir': os.path.join(bin_dir, 'decred'),
'use_csv': True,
'use_segwit': True,
'blocks_confirmed': 1,
'blocks_confirmed': 2,
'conf_target': 2,
'core_version_group': 22,
'chain_lookups': 'local',
'core_type_group': 'dcr',
'min_relay_fee': 0.00001,
},
'namecoin': {
'connection_type': 'rpc' if 'namecoin' in with_coins else 'none',
@@ -1620,6 +1748,7 @@ def main():
'rpctimeout': 60,
'walletrpctimeout': 120,
'walletrpctimeoutlong': 600,
'core_type_group': 'xmr',
},
'pivx': {
'connection_type': 'rpc' if 'pivx' in with_coins else 'none',
@@ -1634,7 +1763,6 @@ def main():
'blocks_confirmed': 1,
'conf_target': 2,
'core_version_group': 17,
'chain_lookups': 'local',
},
'dash': {
'connection_type': 'rpc' if 'dash' in with_coins else 'none',
@@ -1649,7 +1777,6 @@ def main():
'blocks_confirmed': 1,
'conf_target': 2,
'core_version_group': 18,
'chain_lookups': 'local',
},
'firo': {
'connection_type': 'rpc' if 'firo' in with_coins else 'none',
@@ -1665,7 +1792,6 @@ def main():
'conf_target': 2,
'core_version_group': 14,
'min_relay_fee': 0.00001,
'chain_lookups': 'local',
},
'navcoin': {
'connection_type': 'rpc' if 'navcoin' in with_coins else 'none',

View File

@@ -18,6 +18,7 @@ import basicswap.config as cfg
from basicswap import __version__
from basicswap.ui.util import getCoinName
from basicswap.basicswap import BasicSwap
from basicswap.chainparams import chainparams
from basicswap.http_server import HttpThread
from basicswap.contrib.websocket_server import WebsocketServer
@@ -28,18 +29,21 @@ if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout))
swap_client = None
# TODO: deduplicate
known_coins = [
'particl',
'litecoin',
'bitcoin',
'namecoin',
'monero',
'pivx',
'dash',
'firo',
'navcoin',
]
class Daemon:
__slots__ = ('handle', 'files')
def __init__(self, handle, files):
self.handle = handle
self.files = files
def is_known_coin(coin_name: str) -> bool:
for k, v in chainparams.items():
if coin_name == v['name']:
return True
return False
def signal_handler(sig, frame):
@@ -49,7 +53,7 @@ def signal_handler(sig, frame):
swap_client.stopRunning()
def startDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
datadir_path = os.path.expanduser(node_dir)
@@ -71,9 +75,23 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
for line in config_to_add:
fp.write(line + '\n')
args = [daemon_bin, '-datadir=' + datadir_path] + opts
logging.info('Starting node ' + daemon_bin + ' ' + '-datadir=' + node_dir)
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=datadir_path)
args = [daemon_bin, ]
add_datadir: bool = extra_config.get('add_datadir', True)
if add_datadir:
args.append('-datadir=' + datadir_path)
args += opts
logging.info('Starting node ' + daemon_bin + ' ' + (('-datadir=' + node_dir) if add_datadir else ''))
opened_files = []
if extra_config.get('stdout_to_file', False):
stdout_dest = open(os.path.join(datadir_path, extra_config.get('stdout_filename', 'core_stdout.log')), 'w')
opened_files.append(stdout_dest)
stderr_dest = stdout_dest
else:
stdout_dest = subprocess.PIPE
stderr_dest = subprocess.PIPE
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files)
def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
@@ -86,24 +104,27 @@ def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
# return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
file_stdout = open(os.path.join(datadir_path, 'core_stdout.log'), 'w')
file_stderr = open(os.path.join(datadir_path, 'core_stderr.log'), 'w')
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=file_stdout, stderr=file_stderr, cwd=datadir_path)
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=file_stdout, stderr=file_stderr, cwd=datadir_path), [file_stdout, file_stderr])
def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
daemon_bin = os.path.expanduser(os.path.join(bin_dir, wallet_bin))
args = [daemon_bin, '--non-interactive']
needs_rewrite: bool = False
config_to_remove = ['daemon-address=', 'untrusted-daemon=', 'trusted-daemon=', 'proxy=']
data_dir = os.path.expanduser(node_dir)
config_path = os.path.join(data_dir, 'monero_wallet.conf')
args = [daemon_bin, '--non-interactive', '--config-file=' + config_path] + opts
if os.path.exists(config_path):
args += ['--config-file=' + config_path]
with open(config_path) as fp:
for line in fp:
if any(line.startswith(config_line) for config_line in config_to_remove):
logging.warning('Found old config in monero_wallet.conf: {}'.format(line.strip()))
needs_rewrite = True
args += opts
# Remove old config
needs_rewrite: bool = False
config_to_remove = ['daemon-address=', 'untrusted-daemon=', 'trusted-daemon=', 'proxy=']
with open(config_path) as fp:
for line in fp:
if any(line.startswith(config_line) for config_line in config_to_remove):
logging.warning('Found old config in monero_wallet.conf: {}'.format(line.strip()))
needs_rewrite = True
if needs_rewrite:
logging.info('Rewriting monero_wallet.conf')
shutil.copyfile(config_path, config_path + '.last')
@@ -117,7 +138,7 @@ def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
# TODO: return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=data_dir)
wallet_stdout = open(os.path.join(data_dir, 'wallet_stdout.log'), 'w')
wallet_stderr = open(os.path.join(data_dir, 'wallet_stderr.log'), 'w')
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir)
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir), [wallet_stdout, wallet_stderr])
def ws_new_client(client, server):
@@ -148,7 +169,11 @@ def runClient(fp, data_dir, chain, start_only_coins):
pids_path = os.path.join(data_dir, '.pids')
if os.getenv('WALLET_ENCRYPTION_PWD', '') != '':
raise ValueError('Please unset the WALLET_ENCRYPTION_PWD environment variable.')
if 'decred' in start_only_coins:
# Workaround for dcrwallet requiring password for initial startup
logger.warning('Allowing set WALLET_ENCRYPTION_PWD var with --startonlycoin=decred.')
else:
raise ValueError('Please unset the WALLET_ENCRYPTION_PWD environment variable.')
if not os.path.exists(settings_path):
raise ValueError('Settings file not found: ' + str(settings_path))
@@ -186,7 +211,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
swap_client.log.info(f'Starting {display_name} daemon')
filename = 'monerod' + ('.exe' if os.name == 'nt' else '')
daemons.append(startXmrDaemon(v['datadir'], v['bindir'], filename))
pid = daemons[-1].pid
pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid))
if v['manage_wallet_daemon'] is True:
@@ -212,16 +237,46 @@ def runClient(fp, data_dir, chain, start_only_coins):
opts.append('--trusted-daemon' if trusted_daemon else '--untrusted-daemon')
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], filename, opts))
pid = daemons[-1].pid
pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid))
continue
continue # /monero
if c == 'decred':
appdata = v['datadir']
extra_opts = [f'--appdata="{appdata}"', ]
if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon')
filename = 'dcrd' + ('.exe' if os.name == 'nt' else '')
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrd_stdout.log'}
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config))
pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid))
if v['manage_wallet_daemon'] is True:
swap_client.log.info(f'Starting {display_name} wallet daemon')
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
wallet_pwd = v['wallet_pwd']
if wallet_pwd == '':
# Only set when in startonlycoin mode
wallet_pwd = os.getenv('WALLET_ENCRYPTION_PWD', '')
if wallet_pwd != '':
extra_opts.append(f'--pass="{wallet_pwd}"')
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrwallet_stdout.log'}
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config))
pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid))
continue # /decred
if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon')
filename = c + 'd' + ('.exe' if os.name == 'nt' else '')
daemons.append(startDaemon(v['datadir'], v['bindir'], filename))
pid = daemons[-1].pid
pid = daemons[-1].handle.pid
pids.append((c, pid))
swap_client.setDaemonPID(c, pid)
swap_client.log.info('Started {} {}'.format(filename, pid))
@@ -281,18 +336,18 @@ def runClient(fp, data_dir, chain, start_only_coins):
closed_pids = []
for d in daemons:
swap_client.log.info('Interrupting {}'.format(d.pid))
swap_client.log.info('Interrupting {}'.format(d.handle.pid))
try:
d.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
except Exception as e:
swap_client.log.info('Interrupting %d, error %s', d.pid, str(e))
swap_client.log.info('Interrupting %d, error %s', d.handle.pid, str(e))
for d in daemons:
try:
d.wait(timeout=120)
for fp in (d.stdout, d.stderr, d.stdin):
d.handle.wait(timeout=120)
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
if fp:
fp.close()
closed_pids.append(d.pid)
closed_pids.append(d.handle.pid)
except Exception as ex:
swap_client.log.error('Error: {}'.format(ex))
@@ -359,7 +414,7 @@ def main():
continue
if name == 'startonlycoin':
for coin in [s.lower() for s in s[1].split(',')]:
if coin not in known_coins:
if is_known_coin(coin) is False:
raise ValueError(f'Unknown coin: {coin}')
start_only_coins.add(coin)
continue

View File

@@ -0,0 +1,96 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFapILEBEADZxw+4Z8LlqsXCz3j3Ap04SF8zYenlsw123OJZEh9RFERd19bo
+l2RueFqi5vJDGWpXZ+eHxvgevvOO3r0AiIgAByAP7RQQxip4j6M2xnEBdVb9UV5
baO93JcyBRDnII/zh6Zf4pqngiYEz7juySsnVMrE7IFmIdT/WfoGW6FX8/kRXyzf
RTScPZKxIEqwHSlLftlVGSxKL9H+RumEUjPaazLvER1XxtfvcaMGLpatZV3ccqjX
3O+b3plccx0KbMStMtsB0VI+kcaFKg2gIQrbkHKzpDUI2AdaNJJCodM6j3LphBSS
5ZXOknyThpYsxDDyYcncWC9gXrGJfrirO/DPrV1NIj4luBbwyWVT1x9rp2PcUYmG
ZIq0cR4C/mxtlo9OKoyj2cxgoT4WlzlCimRSGtylkWOAx6JQLeKPWt1tZquJB3NT
Jby7x62AyqXhSMnNPDROKL37tkyWehFlAm8KNa6P8R4vctjjJDQ61yw6jskkJaNA
Qz2UNAX+Ztx5KA0Z2HEmJb1jp67EH+3kfAv7R1U51gutzuM7J+vDnNQbwQeuq6os
Y/yssU+OQidLjkojZc7aHz2iym6cw6IlrLTLCnnQQPzAe8CjskrfjwDOejDkPCYO
AkMtgs6/rsJZnCFJ8Pro7NbREt5KT06CPp4nqXNRbtBOHsa1n8wb/M9TQwARAQAB
tCNEZWNyZWQgUmVsZWFzZSA8cmVsZWFzZUBkZWNyZWQub3JnPokCMgQTAQIAHAIb
AwIeAQIXgAUCVqkhhwYLCQgHAwIFFQoJCAsACgkQbfY0qnYIrwRtvA/+JAWw/8cU
xNe5vyWle4uzHakyO25qdH4+TonHbhqyoF2F8BLvkOU3CmtBgXRAZ8Z2jdAczfuJ
u1338BJuHoAIVpvtPzRLLsrrl3LOruiCCYsxm7FKpdYWGanTwpUaHiqHj5LaeIt7
IQjPT3g+uIZ6NsN2RZDzjXZOFD0kZ9EM2b0GqrNpuIQTJafaqGSkOohPiA6b+Sen
7E/XriEo2RWHgNJP7m4xKF0nGDdMxmV0Wrcv6PBJLhZF1RMZSSsFFeTkoHti3113
H9oTKmuw5TUIfYjenGY2rXzkR8xZmCr6BiiUgRFVyVtToG6skLtUvkN3aT0QueDt
u+Lr7QFpM3T9cYqJsg4Gd/9gPUPU6o6r82YlOmB7AQuu99pZ/4KrNIY8saZPRNuS
Q3IHxZQKaCcuzfy65q48QXj9AMX1KSPYZqze51wu8iywfOsq+GC1zw8+gI6alDUl
CjsLxL7MqCR7zHxfmzi7oyNHtqMPdoG4MPFfamoSHgiN1Xck1OKtaVstq0VAmZCp
ixl+e327jwYnF73QtZ7TWrJj6UO1chnGGQzVE1JtHCqzbaVbRVg/8gYClG5aUAjP
99pOv1QquwixuEArcTF91XNjhQYNuOitgSuqCC9b6fCpyXRzG7EB+z86W4J+rwF5
7ATyPKE/rzngiRW0i9KFot5dBFZzyljtPaCJAjgEEwECACIFAlapILECGwMGCwkI
BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEG32NKp2CK8EilQP/0lobzbxfNbQCI++
QGKSwTcy3mkYzIqKWujZQAwVoQk5W2J5cKft1kcS6exFCyj+DeBrWMVPXVQ+YVjC
ODpL9Ewczz0aLOQihFKn5NgJ2epy5+BrpniBH7Frt9v/FVtc0+azhIHg3flvbY9S
KwvNNbnNRI5DwHcBqySs+4m11qtGbVwgz2OdGHAL2XU8aijn3Y38XWzJpJ14xEBl
jOXg7vgIuH7cYWi4S754hsnwb5iS9sN41lXX3D2wwQ/FtuNXDB/EpDJDDi+0vsS6
sk5Rpe3gyT33aG5Vqk1aXk1yI/JRtgJzhXX9CHZ7CGR8ki5Ri7iUXfwGWgA6JARE
8xcKW1gu4CJvvIZGe+8SKeX2g/Xcw4GMaCV4V9ReMiuYCXOOmyg0zwQ/OjJMOxYA
UljYzUs7HIEyqN2u4adPlQhPYgTEYyRFIzsW7dvmmL+YKilT6cKp3GpX2dJiI3kE
AX0d2aKsLpQFNLoA36BqCIrcbXbrpap1HFnFzx9F10blzL57dv8AmU48TShuyQ5T
JeCuILJ7ZYxvcxnjFKYU8Wwwz/L52H0vOdx7gS7lhxL9HMoil9bfWZLu2i1TGGxx
lsKf/QIYW5YzQXsGFieVv95VcZQpdRN/a3yQTwlePftnfQ2eZ0JARAP1IAcqnr64
0aKnLs3WzuI5gBTH2P2B9ix6DDD7uQINBFapILEBEAC4BpeMb7QNk6mfKk2nrkDF
dj2UgigAw2xsnkEUpHG6IufSlTOHF2hiJP7k86lbrIZGfQM9+9WBb2m+kik1Zh2I
53vvXD460ZtGBBzD7UMvAf5BOvrpnX7rmqtjLpGUvPhrQ/6h8LrBH29jCn/8C8yL
fl3B7A11C3YaxRVmR2TBXjMaYpmJ6Qhho4Jbw36/qscnZcPbFKTOs70uYAZD5hT7
PYBQs+496bLiDTjk5SkU2WsSRQG7+IDmTIMC0tzIPKcf1H14lXDAqSWDJsEuF/6g
zPc+Kg3GL0KW0hPHDb5+z5pfzKKBJQxWBwaPjAbGsn/WKFmuLMR6NzXZLoSt/EQq
Bd6Ud9goW+4JKeZhlVEWIZ/C+uNOr5eqEM1qiaEJW3Hrw5lxn4PEYZ2h59VPjM46
jsn5baRx54Wo/4oX8DpDlTyPM8ZOVkXDhHVgkHagygwLkiCQIyH9/htdVyIf0+33
zBS+oIsW8TJgbMaVrb9zy8BFcKfpICIjm5gCdxAb5xtO0pJSiSv4Ga0TTPwhzbHF
f6JpCohrg0ZpTpd2b4ZNOyshpWU8b9tdbSaoP3CQnpa7erzxOSI+xkAJODG+7DAz
qZrs0dMSAzlTG8gKmq5ceVWFA68gwDOZk6ObV4qcLaAakTYMWPzDMYjVD+NiNVJx
OYTBsj9lo8LaJSojNUmXzwARAQABiQIfBBgBAgAJBQJWqSCxAhsMAAoJEG32NKp2
CK8EKJwQAJAKqVuurLX2ApEgeLUVqb18s7kKmDC9MBy11zhmAzH51xrJimzg4j3v
QUjZqmV4iN4wPti/ME5RhwSgE9PeDXupsmGf//pD0YmTIWvOMhj4hASc4l6uNhlo
E2j5tN0A8IZBVQO1PvJdVYi6KJIYZy07qOg79qYQR4yYAXDLZQTlyBefvhVbk0H9
Ds8cC8gH9ag6Yn9t4TCfGhx7NP2j9W29OtnuDFt6GssgUt/1o1WILdMn2DzAdNr7
f6VDCSLKMjc3WQFe1XmrbR/xiH2SqKAOF6UIx++H4p7XPZyBmDcdbGzkputPYey0
tsvEN3ndyNLBsTgzPLALKiiXxvts798fjFWnZFVq1KmcZMj4+4yJLIBTBU1ZW0cC
F3e31qzAEDJmrKcwhN9IzVWAhRhHxpkKc2oADR5Lmq9CcXMOYZF9aS83YlIJKZ8U
WEgna802dzBcckBt2RvYxDYqs4iLLDdHjMGahTM3uieofIUXApUuSAMfTFvq5HSq
0/UU/UekE+NMUn3UtW2XLl9B49aB5bcUtOYtcbIJu8lhHuNXc2+zL/s9zCTsVx5P
XdCmEE81HfSxU/+7yhSxs10ixIporA2OxLYTUkzDDrqlK2N0ENjQp+39eVNTkGJd
15SnkvhqDukr11hTqdIgrwqrHTF6o2mVK79h2AqZqbi5ISCPc/MvuQINBFapIcYB
EACUE78/3F2br1jVCD8w/MC6rfxkleKjdfsafkkI10UPzhhMZAhgX1sXehF8luKC
sbWJ/d92j5dHOy5O8j6WuUfVWZgHQh3HqTBujz3lMYZXC3JCsUPajpQx5VH43JRj
pOz/Pw5Vy9RIS3UJ78vIAC2jqPc0wVknZmQ5JnF6nNGyU0AJYX7kwBM4685avPsI
tdpdix93Z3NEcqmS1B4PF3bCU96gHzAAw7IfCuF6TGzraHV4OPVJxa8GJp4Ziwnn
KD4vZzMx8FYMd8Egw50nsjFDL/DN2cFU512k/RSFFzXJ9y+NwnFwtZ3EobO8kQU0
fFqW4AFyfwsJKBAl8JlhgqzqeepRp1r0y7xmuxKjLxPgnps6ucSYbsqQhBztUCy0
fNW80gdb4kWz9bavY9165ALwCNuGQgLcLziKg4SwKiTJqurFcnAAgBg9X3pBFKJQ
seiarGELGaf/ptVA74auYNeqySJoSRpUe+/9WvX05hcvb786/KpOBBfYPtmWW2tZ
abo1T2FGiECNuqh0BqyxVQ3IVSBAY1IQjIGoXqR1Vh5kxmqW+5HxdlCKRZDBYk/5
ufT2d1GnLPkc+a8dm0PZhfzkM2fYblIpLEwd2w/xZZhmyYnDIkoFZxEu7RzT3t3y
ackos12ymyP4/qsCDf76VJ7KJhAE14MPiUHK0jGfalEZPQARAQABiQQ+BBgBAgAJ
BQJWqSHGAhsCAikJEG32NKp2CK8EwV0gBBkBAgAGBQJWqSHGAAoJEG2Jft9RigMd
GqEP/iIB3E2JpzlKAVBkBu0kQU7CHX7P4zcACayE3buOfzjgzLVk6IdwboH/LYT2
0w+Qwkqo1MV6uTe+831Hd9jRLyEuyxklGliYbXvdGbA+vtpdYcRiVnR61ATUg3Yu
d8MoLsqw+IK61W9e1M2puElKQ6Px/UmJTnfm3OsAnZ2BGJEpYJS1IYkxAKXqMVPE
bdZlMD+8/O9Aq0h5ySW0aIn6GNiUbzPzq9QMviMHR7Nolnw4aangtDUAmlqHO6Gh
3mUaIbPjt19HOFwHnCnv11CZRbyyoXonhZFxOATS13Av+hGg6J8S/gGSiV9fT+53
O1BodBygKJ8Y7JJDFIc/rTt05HaqHbNhucFqCUf5WuDOQabjiWWkgxjUdh050CzK
reYn8TGkTd6OCcmedUB3J4HmbhmwpkBw3ybAWvYhPoFi92/P7FtSThkaSbydnD8V
Men8wy+OuhJtvDnxLc4YGlxWqsW9WelsdxAZZNdSMPDrpmvkXJflmvCiP+4wehvx
Z3R2cgzOZjjYZlrD98IMjNdo87YTs4pxi+mEQNLYrLR404ZbhgTNkdFjKByM6EOl
F5xebblJjaTSLvaCs5p1lLdLTMSD3+1JUMLYRBuLN46ePLlxuDwtgum5hiNes2Ry
DGa/Gln73b/YtuGigcqi7ouesHuhcY0OwFuB4pu/8r+MbceYj3gP/jmZbVn2Yp3Q
qCgQYf/XFiZ5cgXitanBeYPa4A9WNpK2CuF0mtcwaE/vhTki37N9y5OQjpllJ/fL
equFTw7IPHcqcrQ7Fgeaf/lrWgVyNOUWiIJ9OJA4bjAJEMoD+qut8Ci8SPHe14Fr
94xP+ZrM7b9ZmEPvQTAQWip3Gtx3Ydv8U5z4eCeNPU6PnOxEEDvlBsyQp2wG+8ft
pLaidPZgfmdYoqLFIxhQiMUbQiDEmdZdlXCvMdOGF5oCxJDVFTovlaA7VgJMiUJG
hO8TVU+hsH6IeUMoOzCucKN6jlbaYTH9gOm0eclp+cE2BRSPYCh+B9J7uJm7uZR6
mW6uTx+pSyot6/eaJDo/EyBzvYdJK6eIdllFUfAO/S1LD160RPIsnevoYo7Yce3B
Ud2Pasrf+8Ptp/sFHT1hwMCz44SnkK4un9VQ4L0WsMnNQZKGmyyE1+Ydz6iHwKHf
lkGoJOmBU269MNKAh4qmwW9uG+T6Jxc50GLSzBAETtx3MYznEYjkcjCLnlb+x7Yh
HBRWMXiXCQ5atlWig2t/urrkrkSegVNnaP0fOSL71qihX0sRlsvEJCIWktKWVmzt
gcn75IpFzbTVTMtZoOgdFUSDPRSqNHA8hL+Itz2dUqHSktQ7pFK6ogIkvzx+7M7n
O6GuapL0x1I9SeIk9WX9VRrpZmENhlri
=JrM6
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert
# Copyright (c) 2020-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
@@ -35,6 +35,10 @@ LTC_BASE_PORT = 34792
LTC_BASE_RPC_PORT = 35792
LTC_BASE_ZMQ_PORT = 36792
DCR_BASE_PORT = 18555
DCR_BASE_RPC_PORT = 9110
PIVX_BASE_PORT = 34892
PIVX_BASE_RPC_PORT = 35892
PIVX_BASE_ZMQ_PORT = 36892
@@ -108,19 +112,19 @@ def checkForks(ro):
def stopDaemons(daemons):
for d in daemons:
logging.info('Interrupting %d', d.pid)
logging.info('Interrupting %d', d.handle.pid)
try:
d.send_signal(signal.SIGINT)
d.handle.send_signal(signal.SIGINT)
except Exception as e:
logging.info('Interrupting %d, error %s', d.pid, str(e))
logging.info('Interrupting %d, error %s', d.handle.pid, str(e))
for d in daemons:
try:
d.wait(timeout=20)
for fp in (d.stdout, d.stderr, d.stdin):
d.handle.wait(timeout=20)
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
if fp:
fp.close()
except Exception as e:
logging.info('Closing %d, error %s', d.pid, str(e))
logging.info('Closing %d, error %s', d.handle.pid, str(e))
def wait_for_bid(delay_event, swap_client, bid_id, state=None, sent: bool = False, wait_for: int = 20) -> None:
@@ -312,6 +316,20 @@ def make_rpc_func(node_id, base_rpc_port=BASE_RPC_PORT):
return rpc_func
def waitForRPC(rpc_func, delay_event, rpc_command='getwalletinfo', max_tries=7):
for i in range(max_tries + 1):
if delay_event.is_set():
raise ValueError('Test stopped.')
try:
rpc_func(rpc_command)
return
except Exception as ex:
if i < max_tries:
logging.warning('Can\'t connect to RPC: %s. Retrying in %d second/s.', str(ex), (i + 1))
delay_event.wait(i + 1)
raise ValueError('waitForRPC failed')
def extract_states_from_xu_file(file_path, prefix):
states = {}
@@ -375,7 +393,7 @@ def extract_states_from_xu_file(file_path, prefix):
return states
def compare_bid_states(states, expect_states, exact_match=True):
def compare_bid_states(states, expect_states, exact_match: bool = True) -> bool:
for i in range(len(states) - 1, -1, -1):
if states[i][1] == 'Bid Delaying':
@@ -403,3 +421,20 @@ def compare_bid_states(states, expect_states, exact_match=True):
logging.info('Have states: {}'.format(json.dumps(states, indent=4)))
raise e
return True
def compare_bid_states_unordered(states, expect_states, ignore_states=[]) -> bool:
ignore_states.append('Bid Delaying')
for i in range(len(states) - 1, -1, -1):
if states[i][1] in ignore_states:
del states[i]
try:
assert len(states) == len(expect_states)
for state in expect_states:
assert (any(state in s[1] for s in states))
except Exception as e:
logging.info('Expecting states: {}'.format(json.dumps(expect_states, indent=4)))
logging.info('Have states: {}'.format(json.dumps(states, indent=4)))
raise e
return True

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 tecnovert
# Copyright (c) 2020-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -29,6 +29,7 @@ from tests.basicswap.common import (
BASE_PORT, BASE_RPC_PORT,
BTC_BASE_PORT, BTC_BASE_RPC_PORT, BTC_BASE_TOR_PORT,
LTC_BASE_PORT, LTC_BASE_RPC_PORT,
DCR_BASE_PORT, DCR_BASE_RPC_PORT,
PIVX_BASE_PORT,
)
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
@@ -46,6 +47,7 @@ BITCOIN_RPC_PORT_BASE = int(os.getenv('BITCOIN_RPC_PORT_BASE', BTC_BASE_RPC_PORT
BITCOIN_TOR_PORT_BASE = int(os.getenv('BITCOIN_TOR_PORT_BASE', BTC_BASE_TOR_PORT))
LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT))
DECRED_RPC_PORT_BASE = int(os.getenv('DECRED_RPC_PORT_BASE', DCR_BASE_RPC_PORT))
FIRO_BASE_PORT = 34832
FIRO_BASE_RPC_PORT = 35832
@@ -93,11 +95,14 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
os.environ['PART_RPC_PORT'] = str(PARTICL_RPC_PORT_BASE)
os.environ['BTC_RPC_PORT'] = str(BITCOIN_RPC_PORT_BASE)
os.environ['LTC_RPC_PORT'] = str(LITECOIN_RPC_PORT_BASE)
os.environ['DCR_RPC_PORT'] = str(DECRED_RPC_PORT_BASE)
os.environ['FIRO_RPC_PORT'] = str(FIRO_RPC_PORT_BASE)
os.environ['XMR_RPC_USER'] = 'xmr_user'
os.environ['XMR_RPC_PWD'] = 'xmr_pwd'
os.environ['DCR_RPC_PWD'] = 'dcr_pwd'
import bin.basicswap_prepare as prepareSystem
# Hack: Reload module to set env vars as the basicswap_prepare module is initialised if imported from elsewhere earlier
from importlib import reload
@@ -126,9 +131,10 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
with open(config_path) as fs:
settings = json.load(fs)
with open(os.path.join(datadir_path, 'particl', 'particl.conf'), 'r') as fp:
config_filename = os.path.join(datadir_path, 'particl', 'particl.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines()
with open(os.path.join(datadir_path, 'particl', 'particl.conf'), 'w') as fp:
with open(config_filename, 'w') as fp:
for line in lines:
if not line.startswith('staking'):
fp.write(line)
@@ -158,9 +164,10 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
if 'bitcoin' in coins_array:
# Pruned nodes don't provide blocks
with open(os.path.join(datadir_path, 'bitcoin', 'bitcoin.conf'), 'r') as fp:
config_filename = os.path.join(datadir_path, 'bitcoin', 'bitcoin.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines()
with open(os.path.join(datadir_path, 'bitcoin', 'bitcoin.conf'), 'w') as fp:
with open(config_filename, 'w') as fp:
for line in lines:
if not line.startswith('prune'):
fp.write(line)
@@ -188,9 +195,10 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
if 'litecoin' in coins_array:
# Pruned nodes don't provide blocks
with open(os.path.join(datadir_path, 'litecoin', 'litecoin.conf'), 'r') as fp:
config_filename = os.path.join(datadir_path, 'litecoin', 'litecoin.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines()
with open(os.path.join(datadir_path, 'litecoin', 'litecoin.conf'), 'w') as fp:
with open(config_filename, 'w') as fp:
for line in lines:
if not line.startswith('prune'):
fp.write(line)
@@ -213,11 +221,34 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
for opt in EXTRA_CONFIG_JSON.get('ltc{}'.format(node_id), []):
fp.write(opt + '\n')
if 'decred' in coins_array:
# Pruned nodes don't provide blocks
config_filename = os.path.join(datadir_path, 'decred', 'dcrd.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines()
with open(config_filename, 'w') as fp:
for line in lines:
if not line.startswith('prune'):
fp.write(line)
fp.write('listen=127.0.0.1:{}\n'.format(DCR_BASE_PORT + node_id + port_ofs))
fp.write('noseeders=1\n')
fp.write('nodnsseed=1\n')
fp.write('nodiscoverip=1\n')
if node_id == 0:
fp.write('miningaddr=SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH\n')
for ip in range(num_nodes):
if ip != node_id:
fp.write('addpeer=127.0.0.1:{}\n'.format(DCR_BASE_PORT + ip + port_ofs))
config_filename = os.path.join(datadir_path, 'decred', 'dcrwallet.conf')
with open(config_filename, 'a') as fp:
fp.write('enablevoting=1\n')
if 'pivx' in coins_array:
# Pruned nodes don't provide blocks
with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'r') as fp:
config_filename = os.path.join(datadir_path, 'pivx', 'pivx.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines()
with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'w') as fp:
with open(config_filename, 'w') as fp:
for line in lines:
if not line.startswith('prune'):
fp.write(line)
@@ -242,9 +273,10 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
if 'firo' in coins_array:
# Pruned nodes don't provide blocks
with open(os.path.join(datadir_path, 'firo', 'firo.conf'), 'r') as fp:
config_filename = os.path.join(datadir_path, 'firo', 'firo.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines()
with open(os.path.join(datadir_path, 'firo', 'firo.conf'), 'w') as fp:
with open(config_filename, 'w') as fp:
for line in lines:
if not line.startswith('prune'):
fp.write(line)

View File

@@ -41,7 +41,6 @@ from basicswap.util.address import (
)
from basicswap.rpc import (
callrpc_cli,
waitForRPC,
)
from basicswap.contrib.key import (
ECKey,
@@ -67,6 +66,7 @@ from tests.basicswap.common import (
BASE_RPC_PORT,
BASE_ZMQ_PORT,
PREFIX_SECRET_KEY_REGTEST,
waitForRPC,
)
from bin.basicswap_run import startDaemon
@@ -298,7 +298,7 @@ class Test(unittest.TestCase):
except Exception:
callrpc_cli(cfg.BITCOIN_BINDIR, btc_data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].pid)
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].handle.pid)
dash_data_dir = os.path.join(cfg.TEST_DATADIRS, str(DASH_NODE))
'''
@@ -309,7 +309,7 @@ class Test(unittest.TestCase):
callrpc_cli(DASH_BINDIR, dash_data_dir, 'regtest', '-wallet=wallet.dat create', 'dash-wallet')
'''
cls.daemons.append(startDaemon(dash_data_dir, DASH_BINDIR, DASHD))
logging.info('Started %s %d', DASHD, cls.daemons[-1].pid)
logging.info('Started %s %d', DASHD, cls.daemons[-1].handle.pid)
for i in range(NUM_NODES):
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
@@ -319,11 +319,11 @@ class Test(unittest.TestCase):
except Exception:
callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet')
cls.daemons.append(startDaemon(data_dir, cfg.PARTICL_BINDIR, cfg.PARTICLD))
logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].pid)
logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].handle.pid)
for i in range(NUM_NODES):
rpc = make_part_cli_rpc_func(i)
waitForRPC(rpc)
waitForRPC(rpc, delay_event)
if i == 0:
rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb'])
elif i == 1:
@@ -340,23 +340,23 @@ class Test(unittest.TestCase):
with open(settings_path) as fs:
settings = json.load(fs)
fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
cls.swap_clients.append(BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)))
swap_client = cls.swap_clients[-1]
swap_client.setDaemonPID(Coins.BTC, cls.daemons[0].pid)
swap_client.setDaemonPID(Coins.DASH, cls.daemons[1].pid)
swap_client.setDaemonPID(Coins.PART, cls.daemons[2 + i].pid)
sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i))
cls.swap_clients.append(sc)
sc.setDaemonPID(Coins.BTC, cls.daemons[0].handle.pid)
sc.setDaemonPID(Coins.DASH, cls.daemons[1].handle.pid)
sc.setDaemonPID(Coins.PART, cls.daemons[2 + i].handle.pid)
waitForRPC(dashRpc, expect_wallet=False)
waitForRPC(dashRpc, delay_event, rpc_command='getblockchaininfo')
if len(dashRpc('listwallets')) < 1:
dashRpc('createwallet wallet.dat')
swap_client.start()
sc.start()
t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, swap_client)
t = HttpThread(sc.fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, sc)
cls.http_threads.append(t)
t.start()
waitForRPC(dashRpc)
waitForRPC(dashRpc, delay_event)
num_blocks = 500
logging.info('Mining %d dash blocks', num_blocks)
cls.dash_addr = dashRpc('getnewaddress mining_addr')
@@ -372,7 +372,7 @@ class Test(unittest.TestCase):
except Exception:
logging.info('dash: segwit is not active')
waitForRPC(btcRpc)
waitForRPC(btcRpc, delay_event)
cls.btc_addr = btcRpc('getnewaddress mining_addr bech32')
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr)
btcRpc('generatetoaddress {} {}'.format(num_blocks, cls.btc_addr))
@@ -417,6 +417,10 @@ class Test(unittest.TestCase):
stopDaemons(cls.daemons)
cls.http_threads.clear()
cls.swap_clients.clear()
cls.daemons.clear()
super(Test, cls).tearDownClass()
def test_02_part_dash(self):

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,6 @@ from basicswap.util import (
)
from basicswap.rpc import (
callrpc_cli,
waitForRPC,
)
from tests.basicswap.util import (
read_json_api,
@@ -37,6 +36,7 @@ from tests.basicswap.common import (
make_rpc_func,
TEST_HTTP_PORT,
wait_for_offer,
waitForRPC,
)
from basicswap.interface.contrib.firo_test_framework.mininode import (
FromHex,
@@ -137,13 +137,13 @@ class Test(BaseTest):
callrpc_cli(FIRO_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'firo-wallet')
cls.firo_daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(i)), FIRO_BINDIR, FIROD, opts=extra_opts))
logging.info('Started %s %d', FIROD, cls.firo_daemons[-1].pid)
logging.info('Started %s %d', FIROD, cls.firo_daemons[-1].handle.pid)
waitForRPC(make_rpc_func(i, base_rpc_port=FIRO_BASE_RPC_PORT))
waitForRPC(make_rpc_func(i, base_rpc_port=FIRO_BASE_RPC_PORT), test_delay_event)
@classmethod
def addPIDInfo(cls, sc, i):
sc.setDaemonPID(Coins.FIRO, cls.firo_daemons[i].pid)
sc.setDaemonPID(Coins.FIRO, cls.firo_daemons[i].handle.pid)
@classmethod
def prepareExtraCoins(cls):
@@ -180,6 +180,7 @@ class Test(BaseTest):
super(Test, cls).tearDownClass()
stopDaemons(cls.firo_daemons)
cls.firo_daemons.clear()
@classmethod
def addCoinSettings(cls, settings, datadir, node_id):

View File

@@ -29,9 +29,6 @@ from basicswap.util import (
from basicswap.util.address import (
decodeWif,
)
from basicswap.rpc import (
waitForRPC,
)
from tests.basicswap.util import (
read_json_api,
)
@@ -45,6 +42,7 @@ from tests.basicswap.common import (
wait_for_unspent,
wait_for_in_progress,
wait_for_bid_tx_state,
waitForRPC,
)
from basicswap.interface.contrib.nav_test_framework.mininode import (
ToHex,
@@ -159,13 +157,13 @@ class Test(TestFunctions):
data_dir = prepareDataDir(cfg.TEST_DATADIRS, i, 'navcoin.conf', 'nav_', base_p2p_port=NAV_BASE_PORT, base_rpc_port=NAV_BASE_RPC_PORT)
cls.nav_daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, 'nav_' + str(i)), NAV_BINDIR, NAVD, opts=extra_opts))
logging.info('Started %s %d', NAVD, cls.nav_daemons[-1].pid)
logging.info('Started %s %d', NAVD, cls.nav_daemons[-1].handle.pid)
waitForRPC(make_rpc_func(i, base_rpc_port=NAV_BASE_RPC_PORT), max_tries=12)
waitForRPC(make_rpc_func(i, base_rpc_port=NAV_BASE_RPC_PORT), test_delay_event, max_tries=12)
@classmethod
def addPIDInfo(cls, sc, i):
sc.setDaemonPID(Coins.NAV, cls.nav_daemons[i].pid)
sc.setDaemonPID(Coins.NAV, cls.nav_daemons[i].handle.pid)
@classmethod
def sync_blocks(cls, wait_for: int = 20, num_nodes: int = 3) -> None:
@@ -217,6 +215,7 @@ class Test(TestFunctions):
super(Test, cls).tearDownClass()
stopDaemons(cls.nav_daemons)
cls.nav_daemons.clear()
@classmethod
def addCoinSettings(cls, settings, datadir, node_id):
@@ -426,7 +425,7 @@ class Test(TestFunctions):
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
tx_hex = ToHex(tx)
tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex])
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
utxo_pos: int = 0 if tx_funded['changepos'] == 1 else 1
tx_signed = self.callnoderpc('signrawtransaction', [tx_funded['hex'], ])['hex']
self.sync_blocks()
txid = self.callnoderpc('sendrawtransaction', [tx_signed, ])

View File

@@ -31,7 +31,6 @@ from basicswap.util.address import (
from basicswap.rpc import (
callrpc,
callrpc_cli,
waitForRPC,
)
from basicswap.contrib.key import (
ECKey,
@@ -56,6 +55,7 @@ from tests.basicswap.common import (
BTC_BASE_PORT,
BTC_BASE_RPC_PORT,
PREFIX_SECRET_KEY_REGTEST,
waitForRPC,
)
from bin.basicswap_run import startDaemon
@@ -206,12 +206,12 @@ class Test(unittest.TestCase):
callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat -legacy create', 'particl-wallet')
cls.part_daemons.append(startDaemon(os.path.join(TEST_DIR, 'part_' + str(i)), cfg.PARTICL_BINDIR, cfg.PARTICLD))
logging.info('Started %s %d', cfg.PARTICLD, cls.part_daemons[-1].pid)
logging.info('Started %s %d', cfg.PARTICLD, cls.handle.part_daemons[-1].handle.pid)
for i in range(NUM_NODES):
# Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync
rpc = make_rpc_func(i)
waitForRPC(rpc)
waitForRPC(rpc, delay_event)
if i == 0:
rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb'])
elif i == 1:
@@ -230,9 +230,9 @@ class Test(unittest.TestCase):
callrpc_cli(cfg.BITCOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat -legacy create', 'bitcoin-wallet')
cls.btc_daemons.append(startDaemon(os.path.join(TEST_DIR, 'btc_' + str(i)), cfg.BITCOIN_BINDIR, cfg.BITCOIND))
logging.info('Started %s %d', cfg.BITCOIND, cls.part_daemons[-1].pid)
logging.info('Started %s %d', cfg.BITCOIND, cls.handle.part_daemons[-1].handle.pid)
waitForRPC(make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT))
waitForRPC(make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT), delay_event)
logging.info('Preparing swap clients.')
eckey = ECKey()
@@ -248,12 +248,12 @@ class Test(unittest.TestCase):
settings = json.load(fs)
fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i))
sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].pid)
sc.setDaemonPID(Coins.PART, cls.part_daemons[i].pid)
sc.start()
cls.swap_clients.append(sc)
sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].handle.pid)
sc.setDaemonPID(Coins.PART, cls.part_daemons[i].handle.pid)
sc.start()
t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i])
t = HttpThread(sc.fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, sc)
cls.http_threads.append(t)
t.start()
@@ -274,7 +274,7 @@ class Test(unittest.TestCase):
cls.coins_update_thread.start()
except Exception:
traceback.print_exc()
Test.tearDownClass()
cls.tearDownClass()
raise ValueError('setUpClass() failed.')
@classmethod
@@ -303,6 +303,11 @@ class Test(unittest.TestCase):
stopDaemons(cls.part_daemons)
stopDaemons(cls.btc_daemons)
cls.part_daemons.clear()
cls.btc_daemons.clear()
cls.http_threads.clear()
cls.swap_clients.clear()
super(Test, cls).tearDownClass()
def wait_for_num_nodes(self, port, expect_nodes, wait_for=20):

View File

@@ -39,7 +39,6 @@ from basicswap.util.address import (
)
from basicswap.rpc import (
callrpc_cli,
waitForRPC,
)
from basicswap.contrib.key import (
ECKey,
@@ -63,6 +62,7 @@ from tests.basicswap.common import (
BASE_RPC_PORT,
BASE_ZMQ_PORT,
PREFIX_SECRET_KEY_REGTEST,
waitForRPC,
)
from bin.basicswap_run import startDaemon
@@ -276,20 +276,20 @@ class Test(unittest.TestCase):
if os.path.exists(os.path.join(cfg.BITCOIN_BINDIR, 'bitcoin-wallet')):
callrpc_cli(cfg.BITCOIN_BINDIR, btc_data_dir, 'regtest', '-wallet=wallet.dat -legacy create', 'bitcoin-wallet')
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].pid)
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].handle.pid)
cls.daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, str(NMC_NODE)), cfg.NAMECOIN_BINDIR, cfg.NAMECOIND))
logging.info('Started %s %d', cfg.NAMECOIND, cls.daemons[-1].pid)
logging.info('Started %s %d', cfg.NAMECOIND, cls.daemons[-1].handle.pid)
for i in range(NUM_NODES):
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, 'particl-wallet')):
callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat -legacy create', 'particl-wallet')
cls.daemons.append(startDaemon(data_dir, cfg.PARTICL_BINDIR, cfg.PARTICLD))
logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].pid)
logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].handle.pid)
for i in range(NUM_NODES):
rpc = make_part_cli_rpc_func(i)
waitForRPC(rpc)
waitForRPC(rpc, delay_event)
if i == 0:
rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb'])
elif i == 1:
@@ -306,17 +306,19 @@ class Test(unittest.TestCase):
with open(settings_path) as fs:
settings = json.load(fs)
fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
cls.swap_clients.append(BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)))
cls.swap_clients[-1].setDaemonPID(Coins.BTC, cls.daemons[0].pid)
cls.swap_clients[-1].setDaemonPID(Coins.NMC, cls.daemons[1].pid)
cls.swap_clients[-1].setDaemonPID(Coins.PART, cls.daemons[2 + i].pid)
cls.swap_clients[-1].start()
sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i))
cls.swap_clients.append(sc)
t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i])
sc.setDaemonPID(Coins.BTC, cls.daemons[0].handle.pid)
sc.setDaemonPID(Coins.NMC, cls.daemons[1].handle.pid)
sc.setDaemonPID(Coins.PART, cls.daemons[2 + i].handle.pid)
sc.start()
t = HttpThread(sc.fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, sc)
cls.http_threads.append(t)
t.start()
waitForRPC(nmcRpc)
waitForRPC(nmcRpc, delay_event)
num_blocks = 500
logging.info('Mining %d namecoin blocks', num_blocks)
cls.nmc_addr = nmcRpc('getnewaddress mining_addr legacy')
@@ -332,7 +334,7 @@ class Test(unittest.TestCase):
except Exception:
logging.info('nmc: segwit is not active')
waitForRPC(btcRpc)
waitForRPC(btcRpc, delay_event)
cls.btc_addr = btcRpc('getnewaddress mining_addr bech32')
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr)
btcRpc('generatetoaddress {} {}'.format(num_blocks, cls.btc_addr))
@@ -376,6 +378,9 @@ class Test(unittest.TestCase):
c.fp.close()
stopDaemons(cls.daemons)
cls.http_threads.clear()
cls.swap_clients.clear()
cls.daemons.clear()
super(Test, cls).tearDownClass()

View File

@@ -41,7 +41,6 @@ from basicswap.util.address import (
)
from basicswap.rpc import (
callrpc_cli,
waitForRPC,
)
from basicswap.contrib.key import (
ECKey,
@@ -67,6 +66,7 @@ from tests.basicswap.common import (
BASE_RPC_PORT,
BASE_ZMQ_PORT,
PREFIX_SECRET_KEY_REGTEST,
waitForRPC,
)
from bin.basicswap_run import startDaemon
from bin.basicswap_prepare import downloadPIVXParams
@@ -310,9 +310,9 @@ class Test(unittest.TestCase):
except Exception:
callrpc_cli(cfg.BITCOIN_BINDIR, btc_data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].pid)
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].handle.pid)
cls.daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), PIVX_BINDIR, PIVXD))
logging.info('Started %s %d', PIVXD, cls.daemons[-1].pid)
logging.info('Started %s %d', PIVXD, cls.daemons[-1].handle.pid)
for i in range(NUM_NODES):
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
@@ -322,11 +322,11 @@ class Test(unittest.TestCase):
except Exception:
callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet')
cls.daemons.append(startDaemon(data_dir, cfg.PARTICL_BINDIR, cfg.PARTICLD))
logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].pid)
logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].handle.pid)
for i in range(NUM_NODES):
rpc = make_part_cli_rpc_func(i)
waitForRPC(rpc)
waitForRPC(rpc, delay_event)
if i == 0:
rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb'])
elif i == 1:
@@ -343,17 +343,18 @@ class Test(unittest.TestCase):
with open(settings_path) as fs:
settings = json.load(fs)
fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
cls.swap_clients.append(BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)))
cls.swap_clients[-1].setDaemonPID(Coins.BTC, cls.daemons[0].pid)
cls.swap_clients[-1].setDaemonPID(Coins.PIVX, cls.daemons[1].pid)
cls.swap_clients[-1].setDaemonPID(Coins.PART, cls.daemons[2 + i].pid)
cls.swap_clients[-1].start()
sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i))
cls.swap_clients.append(sc)
sc.setDaemonPID(Coins.BTC, cls.daemons[0].handle.pid)
sc.setDaemonPID(Coins.PIVX, cls.daemons[1].handle.pid)
sc.setDaemonPID(Coins.PART, cls.daemons[2 + i].handle.pid)
sc.start()
t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i])
t = HttpThread(sc.fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, sc)
cls.http_threads.append(t)
t.start()
waitForRPC(pivxRpc)
waitForRPC(pivxRpc, delay_event)
num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
logging.info('Mining %d pivx blocks', num_blocks)
cls.pivx_addr = pivxRpc('getnewaddress mining_addr')
@@ -369,7 +370,7 @@ class Test(unittest.TestCase):
except Exception:
logging.info('pivx: segwit is not active')
waitForRPC(btcRpc)
waitForRPC(btcRpc, delay_event)
cls.btc_addr = btcRpc('getnewaddress mining_addr bech32')
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr)
btcRpc('generatetoaddress {} {}'.format(num_blocks, cls.btc_addr))
@@ -410,6 +411,9 @@ class Test(unittest.TestCase):
c.fp.close()
stopDaemons(cls.daemons)
cls.http_threads.clear()
cls.swap_clients.clear()
cls.daemons.clear()
super(Test, cls).tearDownClass()

View File

@@ -49,6 +49,7 @@ from tests.basicswap.common_xmr import (
prepare_nodes,
XMR_BASE_RPC_PORT,
)
from basicswap.interface.dcr.rpc import callrpc as callrpc_dcr
import bin.basicswap_run as runSystem
@@ -61,6 +62,7 @@ UI_PORT = 12700 + PORT_OFS
PARTICL_RPC_PORT_BASE = int(os.getenv('PARTICL_RPC_PORT_BASE', BASE_RPC_PORT))
BITCOIN_RPC_PORT_BASE = int(os.getenv('BITCOIN_RPC_PORT_BASE', BTC_BASE_RPC_PORT))
LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT))
DECRED_WALLET_RPC_PORT_BASE = int(os.getenv('DECRED_WALLET_RPC_PORT_BASE', 9210))
XMR_BASE_RPC_PORT = int(os.getenv('XMR_BASE_RPC_PORT', XMR_BASE_RPC_PORT))
TEST_COINS_LIST = os.getenv('TEST_COINS_LIST', 'bitcoin,monero')
@@ -88,6 +90,11 @@ def callltcrpc(node_id, method, params=[], wallet=None, base_rpc_port=LITECOIN_R
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
def calldcrrpc(node_id, method, params=[], wallet=None, base_rpc_port=DECRED_WALLET_RPC_PORT_BASE):
auth = 'user:dcr_pwd'
return callrpc_dcr(base_rpc_port + node_id, auth, method, params)
def updateThread(cls):
while not cls.delay_event.is_set():
try:
@@ -98,7 +105,7 @@ def updateThread(cls):
cls.delay_event.wait(random.randrange(cls.update_min, cls.update_max))
def updateThreadXmr(cls):
def updateThreadXMR(cls):
xmr_auth = None
if os.getenv('XMR_RPC_USER', '') != '':
xmr_auth = (os.getenv('XMR_RPC_USER', ''), os.getenv('XMR_RPC_PWD', ''))
@@ -108,10 +115,38 @@ def updateThreadXmr(cls):
if cls.xmr_addr is not None:
callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}, auth=xmr_auth)
except Exception as e:
print('updateThreadXmr error', str(e))
print('updateThreadXMR error', str(e))
cls.delay_event.wait(random.randrange(cls.xmr_update_min, cls.xmr_update_max))
def updateThreadDCR(cls):
while not cls.delay_event.is_set():
try:
pass
num_passed: int = 0
for i in range(30):
try:
calldcrrpc(0, 'purchaseticket', [cls.dcr_acc, 0.1, 0])
num_passed += 1
if num_passed >= 5:
break
cls.delay_event.wait(0.1)
except Exception as e:
if 'double spend' in str(e):
pass
else:
logging.warning('updateThreadDCR purchaseticket {}'.format(e))
cls.delay_event.wait(0.5)
try:
if num_passed >= 5:
calldcrrpc(0, 'generate', [1,])
except Exception as e:
logging.warning('updateThreadDCR generate {}'.format(e))
except Exception as e:
print('updateThreadDCR error', str(e))
cls.delay_event.wait(random.randrange(cls.dcr_update_min, cls.dcr_update_max))
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -123,12 +158,18 @@ class Test(unittest.TestCase):
cls.xmr_update_min = int(os.getenv('XMR_UPDATE_THREAD_MIN_WAIT', '1'))
cls.xmr_update_max = cls.xmr_update_min * 4
cls.dcr_update_min = int(os.getenv('DCR_UPDATE_THREAD_MIN_WAIT', '1'))
cls.dcr_update_max = cls.dcr_update_min * 4
cls.delay_event = threading.Event()
cls.update_thread = None
cls.update_thread_xmr = None
cls.update_thread_dcr = None
cls.processes = []
cls.btc_addr = None
cls.xmr_addr = None
cls.dcr_addr = 'SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH'
cls.dcr_acc = None
random.seed(time.time())
@@ -197,13 +238,26 @@ class Test(unittest.TestCase):
have_blocks: int = callltcrpc(0, 'getblockcount')
callltcrpc(0, 'generatetoaddress', [500 - have_blocks, self.ltc_addr], wallet='wallet.dat')
# Lower output split threshold for more stakeable outputs
for i in range(NUM_NODES):
callpartrpc(i, 'walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}])
if 'decred' in TEST_COINS_LIST:
if RESET_TEST:
addr = calldcrrpc(0, 'getnewaddress')
# assert (addr == self.dcr_addr)
self.dcr_acc = calldcrrpc(0, 'getaccount', [self.dcr_addr, ])
addr = calldcrrpc(0, 'generate', [110,])
else:
self.dcr_acc = calldcrrpc(0, 'getaccount', [self.dcr_addr, ])
self.update_thread_dcr = threading.Thread(target=updateThreadDCR, args=(self,))
self.update_thread_dcr.start()
if RESET_TEST:
# Lower output split threshold for more stakeable outputs
for i in range(NUM_NODES):
callpartrpc(i, 'walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}])
self.update_thread = threading.Thread(target=updateThread, args=(self,))
self.update_thread.start()
self.update_thread_xmr = threading.Thread(target=updateThreadXmr, args=(self,))
self.update_thread_xmr = threading.Thread(target=updateThreadXMR, args=(self,))
self.update_thread_xmr.start()
# Wait for height, or sequencelock is thrown off by genesis blocktime
@@ -228,12 +282,15 @@ class Test(unittest.TestCase):
cls.update_thread.join()
if cls.update_thread_xmr:
cls.update_thread_xmr.join()
if cls.update_thread_dcr:
cls.update_thread_dcr.join()
for p in cls.processes:
p.terminate()
for p in cls.processes:
p.join()
cls.update_thread = None
cls.update_thread_xmr = None
cls.update_thread_dcr = None
cls.processes = []
def test_persistent(self):

View File

@@ -13,10 +13,10 @@ from basicswap.db import (
Concepts,
)
from basicswap.basicswap import (
Coins,
SwapTypes,
BidStates,
Coins,
DebugTypes,
SwapTypes,
)
from basicswap.basicswap_util import (
TxLockTypes,
@@ -26,7 +26,7 @@ from basicswap.util import (
make_int,
format_amount,
)
from basicswap.interface import Curves
from basicswap.interface.base import Curves
from tests.basicswap.util import (
read_json_api,
)

View File

@@ -23,13 +23,16 @@ from coincurve.keys import (
PrivateKey)
from basicswap.util import i2b, h2b
from basicswap.util.address import decodeAddress
from basicswap.util.crypto import ripemd160, hash160, blake256
from basicswap.util.extkey import ExtKeyPair
from basicswap.util.integer import encode_varint, decode_varint
from basicswap.util.crypto import ripemd160, hash160
from basicswap.util.network import is_private_ip_address
from basicswap.util.rfc2440 import rfc2440_hash_password
from basicswap.util_xmr import encode_address as xmr_encode_address
from basicswap.interface.btc import BTCInterface
from basicswap.interface.xmr import XMRInterface
from tests.basicswap.util import REQUIRED_SETTINGS
from basicswap.basicswap_util import (
TxLockTypes)
@@ -48,7 +51,6 @@ from basicswap.contrib.test_framework.script import hash160 as hash160_btc
class Test(unittest.TestCase):
REQUIRED_SETTINGS = {'blocks_confirmed': 1, 'conf_target': 1, 'use_segwit': True, 'connection_type': 'rpc'}
def test_serialise_num(self):
def test_case(v, nb=None):
@@ -69,7 +71,7 @@ class Test(unittest.TestCase):
def test_sequence(self):
coin_settings = {'rpcport': 0, 'rpcauth': 'none'}
coin_settings.update(self.REQUIRED_SETTINGS)
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, 'regtest')
@@ -177,7 +179,7 @@ class Test(unittest.TestCase):
def test_ecdsa_otves(self):
coin_settings = {'rpcport': 0, 'rpcauth': 'none'}
coin_settings.update(self.REQUIRED_SETTINGS)
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, 'regtest')
vk_sign = ci.getNewSecretKey()
vk_encrypt = ci.getNewSecretKey()
@@ -200,7 +202,7 @@ class Test(unittest.TestCase):
def test_sign(self):
coin_settings = {'rpcport': 0, 'rpcauth': 'none'}
coin_settings.update(self.REQUIRED_SETTINGS)
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, 'regtest')
vk = ci.getNewSecretKey()
@@ -215,7 +217,7 @@ class Test(unittest.TestCase):
def test_sign_compact(self):
coin_settings = {'rpcport': 0, 'rpcauth': 'none'}
coin_settings.update(self.REQUIRED_SETTINGS)
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, 'regtest')
vk = ci.getNewSecretKey()
@@ -230,7 +232,7 @@ class Test(unittest.TestCase):
def test_sign_recoverable(self):
coin_settings = {'rpcport': 0, 'rpcauth': 'none'}
coin_settings.update(self.REQUIRED_SETTINGS)
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, 'regtest')
vk = ci.getNewSecretKey()
@@ -246,7 +248,7 @@ class Test(unittest.TestCase):
def test_pubkey_to_address(self):
coin_settings = {'rpcport': 0, 'rpcauth': 'none'}
coin_settings.update(self.REQUIRED_SETTINGS)
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, 'regtest')
pk = h2b('02c26a344e7d21bcc6f291532679559f2fd234c881271ff98714855edc753763a6')
addr = ci.pubkey_to_address(pk)
@@ -254,7 +256,7 @@ class Test(unittest.TestCase):
def test_dleag(self):
coin_settings = {'rpcport': 0, 'walletrpcport': 0, 'walletrpcauth': 'none'}
coin_settings.update(self.REQUIRED_SETTINGS)
coin_settings.update(REQUIRED_SETTINGS)
ci = XMRInterface(coin_settings, 'regtest')
@@ -312,12 +314,11 @@ class Test(unittest.TestCase):
assert ('10.00000000' == format_amount(amount_to_recreate, scale_to))
coin_settings = {'rpcport': 0, 'rpcauth': 'none', 'walletrpcport': 0, 'walletrpcauth': 'none'}
coin_settings.update(self.REQUIRED_SETTINGS)
coin_settings.update(REQUIRED_SETTINGS)
ci_xmr = XMRInterface(coin_settings, 'regtest')
ci_btc = BTCInterface(coin_settings, 'regtest')
for i in range(10000):
test_pairs = random.randint(0, 3)
if test_pairs == 0:
ci_from = ci_btc
@@ -423,6 +424,9 @@ class Test(unittest.TestCase):
msg_buf_v2.ParseFromString(serialised_msg)
assert (msg_buf_v2.protocol_version == 2)
assert (msg_buf_v2.time_valid == 1024)
assert (msg_buf_v2.amount == 0)
assert (msg_buf_v2.pkhash_buyer is not None)
assert (len(msg_buf_v2.pkhash_buyer) == 0)
# Decode only the first field
msg_buf_v2.ParseFromString(serialised_msg[:2])
@@ -430,31 +434,35 @@ class Test(unittest.TestCase):
assert (msg_buf_v2.time_valid == 0)
def test_is_private_ip_address(self):
assert (is_private_ip_address('localhost'))
assert (is_private_ip_address('127.0.0.1'))
assert (is_private_ip_address('10.0.0.0'))
assert (is_private_ip_address('172.16.0.0'))
assert (is_private_ip_address('192.168.0.0'))
assert (is_private_ip_address('20.87.245.0') is False)
assert (is_private_ip_address('particl.io') is False)
test_addresses = [
('localhost', True),
('127.0.0.1', True),
('10.0.0.0', True),
('172.16.0.0', True),
('192.168.0.0', True),
('20.87.245.0', False),
('particl.io', False),
]
for addr, is_private in test_addresses:
assert (is_private_ip_address(addr) is is_private)
def test_varint(self):
def test_case(i, expect_length):
test_vectors = [
(0, 1),
(1, 1),
(127, 1),
(128, 2),
(253, 2),
(8321, 2),
(16383, 2),
(16384, 3),
(2097151, 3),
(2097152, 4),
]
for i, expect_length in test_vectors:
b = encode_varint(i)
assert (len(b) == expect_length)
assert (decode_varint(b) == i)
test_case(0, 1)
test_case(1, 1)
test_case(127, 1)
test_case(128, 2)
test_case(253, 2)
test_case(8321, 2)
test_case(16383, 2)
test_case(16384, 3)
test_case(2097151, 3)
test_case(2097152, 4)
assert (decode_varint(b) == (i, expect_length))
def test_base58(self):
kv = edu.get_secret()
@@ -468,6 +476,45 @@ class Test(unittest.TestCase):
addr = xmr_encode_address(Kv, Ks, 4146)
assert (addr.startswith('Wo'))
def test_blake256(self):
test_vectors = [
('716f6e863f744b9ac22c97ec7b76ea5f5908bc5b2f67c61510bfc4751384ea7a', b''),
('7576698ee9cad30173080678e5965916adbb11cb5245d386bf1ffda1cb26c9d7', b'The quick brown fox jumps over the lazy dog'),
]
for expect_hash, data in test_vectors:
assert (blake256(data).hex() == expect_hash)
def test_extkey(self):
test_key = 'XPARHAr37YxmFP8wyjkaHAQWmp84GiyLikL7EL8j9BCx4LkB8Q1Bw5Kr8sA1GA3Ym53zNLcaxxFHr6u81JVTeCaD61c6fKS1YRAuti8Zu5SzJCjh'
test_key_c0 = 'XPARHAt1XMcNYAwP5wEnQXknBAkGSzaetdZt2eoJZehdB4WXfV1xbSjpgHe44AivmumcSejW5KaYx6L5M6MyR1WyXrsWTwaiUEfHq2RrqCfXj3ZW'
test_key_c0_p = 'PPARTKPL4rp5WLnrYP6jZfuRjx6jrmvbsz5QdHofPfFqJdm918mQwdPLq6Dd9TkdbQeKUqjbHWkyzWe7Pftd7itzm7ETEoUMq4cbG4fY9FKH1YSU'
test_key_c0h = 'XPARHAt1XMcNgWbv48LwoQbjs1bC8kCXKomzvJLRT5xmbQ2GKf9e8Vfr1MMcfiWJC34RyDp5HvAfjeiNyLDfkFm1UrRCrPkVC9GGaAWa3nXMWew8'
ek_data = decodeAddress(test_key)[4:]
ek = ExtKeyPair()
ek.decode(ek_data)
assert (ek.encode_v() == ek_data)
m_0 = ek.derive(0)
ek_c0_data = decodeAddress(test_key_c0)[4:]
assert (m_0.encode_v() == ek_c0_data)
child_no: int = 0 | (1 << 31)
m_0h = ek.derive(child_no)
ek_c0h_data = decodeAddress(test_key_c0h)[4:]
assert (m_0h.encode_v() == ek_c0h_data)
ek.neuter()
assert (ek.has_key() is False)
m_0 = ek.derive(0)
ek_c0_p_data = decodeAddress(test_key_c0_p)[4:]
assert (m_0.encode_p() == ek_c0_p_data)
if __name__ == '__main__':
unittest.main()

View File

@@ -13,17 +13,16 @@ $ pytest -v -s tests/basicswap/test_run.py::Test::test_04_ltc_btc
"""
import os
import random
import logging
import unittest
from basicswap.basicswap import (
Coins,
SwapTypes,
BidStates,
TxStates,
Coins,
DebugTypes,
SwapTypes,
TxStates,
)
from basicswap.basicswap_util import (
TxLockTypes,
@@ -40,24 +39,23 @@ from tests.basicswap.util import (
read_json_api,
)
from tests.basicswap.common import (
wait_for_offer,
wait_for_bid,
wait_for_balance,
wait_for_unspent,
wait_for_bid_tx_state,
wait_for_in_progress,
TEST_HTTP_PORT,
LTC_BASE_RPC_PORT,
BTC_BASE_RPC_PORT,
compare_bid_states,
extract_states_from_xu_file,
LTC_BASE_RPC_PORT,
TEST_HTTP_PORT,
wait_for_balance,
wait_for_bid,
wait_for_bid_tx_state,
wait_for_in_progress,
wait_for_offer,
wait_for_unspent,
)
from basicswap.contrib.test_framework.messages import (
ToHex,
CTxIn,
COutPoint,
CTransaction,
CTxIn,
CTxInWitness,
ToHex,
)
from basicswap.contrib.test_framework.script import (
CScript,
@@ -88,10 +86,6 @@ class Test(BaseTest):
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/btc', 'balance', 1000.0)
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/ltc', 'balance', 1000.0)
diagrams_dir = 'doc/protocols/sequence_diagrams'
cls.states_bidder = extract_states_from_xu_file(os.path.join(diagrams_dir, 'bidder.alt.xu'), 'B')
cls.states_offerer = extract_states_from_xu_file(os.path.join(diagrams_dir, 'offerer.alt.xu'), 'O')
# Wait for height, or sequencelock is thrown off by genesis blocktime
cls.waitForParticlHeight(3)
@@ -329,7 +323,8 @@ class Test(BaseTest):
logging.info('---------- Test PART to LTC')
swap_clients = self.swap_clients
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.LTC, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST)
swap_value = 100 * COIN
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.LTC, swap_value, 0.1 * COIN, swap_value, SwapTypes.SELLER_FIRST)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
@@ -341,21 +336,34 @@ class Test(BaseTest):
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
js_0 = read_json_api(1800)
js_1 = read_json_api(1801)
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
# Verify lock tx spends are found in the expected wallets
bid, offer = swap_clients[0].getBidAndOffer(bid_id)
max_fee: int = 1000
itx_spend = bid.initiate_tx.spend_txid.hex()
ci_node_1_from = swap_clients[1].ci(Coins.PART)
wtx = ci_node_1_from.rpc_wallet('gettransaction', [itx_spend,])
assert (swap_value - ci_node_1_from.make_int(wtx['details'][0]['amount']) < max_fee)
ci_node_0_to = swap_clients[0].ci(Coins.LTC)
ptx_spend = bid.participate_tx.spend_txid.hex()
wtx = ci_node_0_to.rpc_wallet('gettransaction', [ptx_spend,])
assert (bid.amount_to - ci_node_0_to.make_int(wtx['details'][0]['amount']) < max_fee)
bid_id_hex = bid_id.hex()
path = f'bids/{bid_id_hex}/states'
offerer_states = read_json_api(1800, path)
bidder_states = read_json_api(1801, path)
assert (compare_bid_states(offerer_states, self.states_offerer[0]) is True)
assert (compare_bid_states(bidder_states, self.states_bidder[0]) is True)
assert (compare_bid_states(offerer_states, self.states_offerer_sh[0]) is True)
assert (compare_bid_states(bidder_states, self.states_bidder_sh[0]) is True)
js_0 = read_json_api(1800)
js_1 = read_json_api(1801)
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
def test_03_ltc_part(self):
logging.info('---------- Test LTC to PART')
@@ -372,8 +380,8 @@ class Test(BaseTest):
wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=30)
js_0 = read_json_api(1800)
js_1 = read_json_api(1801)
@@ -395,8 +403,8 @@ class Test(BaseTest):
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
js_0 = read_json_api(1800)
js_1 = read_json_api(1801)
@@ -419,8 +427,8 @@ class Test(BaseTest):
read_json_api(1801, 'bids/{}'.format(bid_id.hex()), {'abandon': True})
swap_clients[0].acceptBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=30)
js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
@@ -437,7 +445,7 @@ class Test(BaseTest):
offerer_states = read_json_api(1800, path)
bidder_states = read_json_api(1801, path)
assert (compare_bid_states(offerer_states, self.states_offerer[1]) is True)
assert (compare_bid_states(offerer_states, self.states_offerer_sh[1]) is True)
assert (bidder_states[-1][1] == 'Bid Abandoned')
def test_06_self_bid(self):
@@ -455,8 +463,8 @@ class Test(BaseTest):
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
wait_for_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=80)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=30)
js_0 = read_json_api(1800)
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
@@ -504,8 +512,8 @@ class Test(BaseTest):
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
def test_10_bad_ptx(self):
# Invalid PTX sent, swap should stall and ITx and PTx should be reclaimed by senders
@@ -526,7 +534,7 @@ class Test(BaseTest):
swap_clients[0].acceptBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
@@ -543,8 +551,8 @@ class Test(BaseTest):
offerer_states = read_json_api(1800, path)
bidder_states = read_json_api(1801, path)
assert (compare_bid_states(offerer_states, self.states_offerer[1]) is True)
assert (compare_bid_states(bidder_states, self.states_bidder[1]) is True)
assert (compare_bid_states(offerer_states, self.states_offerer_sh[1]) is True)
assert (compare_bid_states(bidder_states, self.states_bidder_sh[1]) is True)
'''
def test_11_refund(self):
@@ -566,7 +574,7 @@ class Test(BaseTest):
swap_clients[0].acceptBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=120)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=30)
js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
@@ -654,8 +662,8 @@ class Test(BaseTest):
offerer_states = read_json_api(1800, path)
bidder_states = read_json_api(1801, path)
assert (compare_bid_states(offerer_states, self.states_offerer[2]) is True)
assert (compare_bid_states(bidder_states, self.states_bidder[2], exact_match=False) is True)
assert (compare_bid_states(offerer_states, self.states_offerer_sh[2]) is True)
assert (compare_bid_states(bidder_states, self.states_bidder_sh[2], exact_match=False) is True)
def test_14_sweep_balance(self):
logging.info('---------- Test sweep balance offer')
@@ -718,8 +726,8 @@ class Test(BaseTest):
wait_for_bid(test_delay_event, swap_clients[2], bid_id)
swap_clients[2].acceptBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
# Verify expected inputs were used
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
@@ -753,8 +761,8 @@ class Test(BaseTest):
wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=30)
def pass_99_delay(self):
logging.info('Delay')

View File

@@ -15,7 +15,6 @@ import logging
import unittest
import traceback
import threading
import subprocess
import basicswap.config as cfg
from basicswap.db import (
@@ -44,7 +43,6 @@ from basicswap.util.address import (
from basicswap.rpc import (
callrpc,
callrpc_cli,
waitForRPC,
)
from basicswap.rpc_xmr import (
callrpc_xmr,
@@ -77,6 +75,7 @@ from tests.basicswap.common import (
wait_for_none_active,
wait_for_balance,
wait_for_unspent,
waitForRPC,
compare_bid_states,
extract_states_from_xu_file,
TEST_HTTP_HOST,
@@ -92,7 +91,7 @@ from tests.basicswap.common import (
from basicswap.db_util import (
remove_expired_data,
)
from bin.basicswap_run import startDaemon, startXmrDaemon
from bin.basicswap_run import startDaemon, startXmrDaemon, startXmrWalletDaemon
logger = logging.getLogger()
@@ -140,29 +139,6 @@ def prepareXmrDataDir(datadir, node_id, conf_file):
fp.write('add-exclusive-node=127.0.0.1:{}\n'.format(XMR_BASE_P2P_PORT + i))
def startXmrWalletRPC(node_dir, bin_dir, wallet_bin, node_id, opts=[]):
daemon_bin = os.path.expanduser(os.path.join(bin_dir, wallet_bin))
data_dir = os.path.expanduser(node_dir)
args = [daemon_bin]
args += ['--non-interactive']
args += ['--daemon-address=127.0.0.1:{}'.format(XMR_BASE_RPC_PORT + node_id)]
args += ['--no-dns']
args += ['--rpc-bind-port={}'.format(XMR_BASE_WALLET_RPC_PORT + node_id)]
args += ['--wallet-dir={}'.format(os.path.join(data_dir, 'wallets'))]
args += ['--log-file={}'.format(os.path.join(data_dir, 'wallet.log'))]
args += ['--rpc-login=test{0}:test_pass{0}'.format(node_id)]
args += ['--shared-ringdb-dir={}'.format(os.path.join(data_dir, 'shared-ringdb'))]
args += ['--allow-mismatched-daemon-version']
args += opts
logging.info('Starting daemon {} --wallet-dir={}'.format(daemon_bin, node_dir))
wallet_stdout = open(os.path.join(data_dir, 'wallet_stdout.log'), 'w')
wallet_stderr = open(os.path.join(data_dir, 'wallet_stderr.log'), 'w')
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir)
def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_coins=set(), cls=None):
basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id))
if not os.path.exists(basicswap_dir):
@@ -358,6 +334,9 @@ class BaseTest(unittest.TestCase):
cls.states_bidder = extract_states_from_xu_file(os.path.join(diagrams_dir, 'ads.bidder.alt.xu'), 'B')
cls.states_offerer = extract_states_from_xu_file(os.path.join(diagrams_dir, 'ads.offerer.alt.xu'), 'O')
cls.states_bidder_sh = extract_states_from_xu_file(os.path.join(diagrams_dir, 'bidder.alt.xu'), 'B')
cls.states_offerer_sh = extract_states_from_xu_file(os.path.join(diagrams_dir, 'offerer.alt.xu'), 'O')
if os.path.isdir(TEST_DIR):
if RESET_TEST:
logging.info('Removing ' + TEST_DIR)
@@ -392,13 +371,13 @@ class BaseTest(unittest.TestCase):
callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet')
cls.part_daemons.append(startDaemon(os.path.join(TEST_DIR, 'part_' + str(i)), cfg.PARTICL_BINDIR, cfg.PARTICLD))
logging.info('Started %s %d', cfg.PARTICLD, cls.part_daemons[-1].pid)
logging.info('Started %s %d', cfg.PARTICLD, cls.part_daemons[-1].handle.pid)
if not cls.restore_instance:
for i in range(NUM_NODES):
# Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync
rpc = make_rpc_func(i)
waitForRPC(rpc)
waitForRPC(rpc, test_delay_event)
if i == 0:
rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb'])
elif i == 1:
@@ -422,9 +401,9 @@ class BaseTest(unittest.TestCase):
callrpc_cli(cfg.BITCOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
cls.btc_daemons.append(startDaemon(os.path.join(TEST_DIR, 'btc_' + str(i)), cfg.BITCOIN_BINDIR, cfg.BITCOIND))
logging.info('Started %s %d', cfg.BITCOIND, cls.part_daemons[-1].pid)
logging.info('Started %s %d', cfg.BITCOIND, cls.part_daemons[-1].handle.pid)
waitForRPC(make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT))
waitForRPC(make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT), test_delay_event)
if cls.start_ltc_nodes:
for i in range(NUM_LTC_NODES):
@@ -434,20 +413,31 @@ class BaseTest(unittest.TestCase):
callrpc_cli(cfg.LITECOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'litecoin-wallet')
cls.ltc_daemons.append(startDaemon(os.path.join(TEST_DIR, 'ltc_' + str(i)), cfg.LITECOIN_BINDIR, cfg.LITECOIND))
logging.info('Started %s %d', cfg.LITECOIND, cls.part_daemons[-1].pid)
logging.info('Started %s %d', cfg.LITECOIND, cls.part_daemons[-1].handle.pid)
waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT))
waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT), test_delay_event)
if cls.start_xmr_nodes:
for i in range(NUM_XMR_NODES):
if not cls.restore_instance:
prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
cls.xmr_daemons.append(startXmrDaemon(os.path.join(TEST_DIR, 'xmr_' + str(i)), cfg.XMR_BINDIR, cfg.XMRD))
logging.info('Started %s %d', cfg.XMRD, cls.xmr_daemons[-1].pid)
node_dir = os.path.join(TEST_DIR, 'xmr_' + str(i))
cls.xmr_daemons.append(startXmrDaemon(node_dir, cfg.XMR_BINDIR, cfg.XMRD))
logging.info('Started %s %d', cfg.XMRD, cls.xmr_daemons[-1].handle.pid)
waitForXMRNode(i)
cls.xmr_daemons.append(startXmrWalletRPC(os.path.join(TEST_DIR, 'xmr_' + str(i)), cfg.XMR_BINDIR, cfg.XMR_WALLET_RPC, i))
opts = [
'--daemon-address=127.0.0.1:{}'.format(XMR_BASE_RPC_PORT + i),
'--no-dns',
'--rpc-bind-port={}'.format(XMR_BASE_WALLET_RPC_PORT + i),
'--wallet-dir={}'.format(os.path.join(node_dir, 'wallets')),
'--log-file={}'.format(os.path.join(node_dir, 'wallet.log')),
'--rpc-login=test{0}:test_pass{0}'.format(i),
'--shared-ringdb-dir={}'.format(os.path.join(node_dir, 'shared-ringdb')),
'--allow-mismatched-daemon-version',
]
cls.xmr_daemons.append(startXmrWalletDaemon(node_dir, cfg.XMR_BINDIR, cfg.XMR_WALLET_RPC, opts=opts))
for i in range(NUM_XMR_NODES):
cls.xmr_wallet_auth.append(('test{0}'.format(i), 'test_pass{0}'.format(i)))
@@ -487,11 +477,12 @@ class BaseTest(unittest.TestCase):
cls.network_pubkey = settings['network_pubkey']
fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i))
sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].pid)
sc.setDaemonPID(Coins.PART, cls.part_daemons[i].pid)
cls.swap_clients.append(sc)
sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].handle.pid)
sc.setDaemonPID(Coins.PART, cls.part_daemons[i].handle.pid)
if cls.start_ltc_nodes:
sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].pid)
sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].handle.pid)
cls.addPIDInfo(sc, i)
sc.start()
@@ -499,9 +490,8 @@ class BaseTest(unittest.TestCase):
# Set XMR main wallet address
xmr_ci = sc.ci(Coins.XMR)
sc.setStringKV('main_wallet_addr_' + xmr_ci.coin_name().lower(), xmr_ci.getMainWalletAddress())
cls.swap_clients.append(sc)
t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i])
t = HttpThread(sc.fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, sc)
cls.http_threads.append(t)
t.start()
# Set future block rewards to nowhere (a random address), so wallet amounts stay constant
@@ -593,7 +583,7 @@ class BaseTest(unittest.TestCase):
except Exception:
traceback.print_exc()
Test.tearDownClass()
cls.tearDownClass()
raise ValueError('setUpClass() failed.')
@classmethod
@@ -1538,7 +1528,7 @@ class Test(BaseTest):
swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800, sent=True)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=30, sent=True)
def test_16_new_subaddress(self):
logging.info('---------- Test that new subaddresses are created')

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Copyright (c) 2022-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
@@ -10,6 +10,9 @@ import urllib
from urllib.request import urlopen
REQUIRED_SETTINGS = {'blocks_confirmed': 1, 'conf_target': 1, 'use_segwit': True, 'connection_type': 'rpc'}
def make_boolean(s):
return s.lower() in ['1', 'true']