mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 10:28:10 +01:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
247f23cb4a | ||
|
|
3e16ea487c | ||
|
|
c24a20b38d | ||
|
|
26eaa2f0b0 | ||
|
|
614fc9ccbd | ||
|
|
a3f5bc1a5a | ||
|
|
026b222e90 | ||
|
|
c640836fbf | ||
|
|
154c6d6832 | ||
|
|
3db723056f | ||
|
|
f1822e1443 | ||
|
|
89ca350ff2 | ||
|
|
6afbd55aec | ||
|
|
443b7f9c51 | ||
|
|
ee239aba0b | ||
|
|
3ba5532fa0 |
@@ -1,3 +1,3 @@
|
||||
name = "basicswap"
|
||||
|
||||
__version__ = "0.13.0"
|
||||
__version__ = "0.13.1"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
1
basicswap/contrib/blake256/__init__.py
Normal file
1
basicswap/contrib/blake256/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
533
basicswap/contrib/blake256/blake256.py
Normal file
533
basicswap/contrib/blake256/blake256.py
Normal 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)
|
||||
37
basicswap/contrib/blake256/test.py
Normal file
37
basicswap/contrib/blake256/test.py
Normal 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')
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
238
basicswap/interface/base.py
Normal 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()
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
4
basicswap/interface/dcr/__init__.py
Normal file
4
basicswap/interface/dcr/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
from .dcr import DCRInterface
|
||||
|
||||
__all__ = ['DCRInterface',]
|
||||
1412
basicswap/interface/dcr/dcr.py
Normal file
1412
basicswap/interface/dcr/dcr.py
Normal file
File diff suppressed because it is too large
Load Diff
204
basicswap/interface/dcr/messages.py
Normal file
204
basicswap/interface/dcr/messages.py
Normal 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
|
||||
47
basicswap/interface/dcr/rpc.py
Normal file
47
basicswap/interface/dcr/rpc.py
Normal 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
|
||||
50
basicswap/interface/dcr/script.py
Normal file
50
basicswap/interface/dcr/script.py
Normal 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
|
||||
50
basicswap/interface/dcr/util.py
Normal file
50
basicswap/interface/dcr/util.py
Normal 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()
|
||||
@@ -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'],
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -26,3 +26,5 @@ class OpCodes(IntEnum):
|
||||
OP_CHECKSIG = 0xac,
|
||||
OP_CHECKLOCKTIMEVERIFY = 0xb1,
|
||||
OP_CHECKSEQUENCEVERIFY = 0xb2,
|
||||
|
||||
OP_SHA256_DECRED = 0xc0,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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?");
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
|
||||
@@ -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
116
basicswap/util/extkey.py
Normal 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:]
|
||||
@@ -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:
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
96
pgp/keys/decred_release.pgp
Normal file
96
pgp/keys/decred_release.pgp
Normal 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-----
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
1128
tests/basicswap/extended/test_dcr.py
Normal file
1128
tests/basicswap/extended/test_dcr.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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):
|
||||
|
||||
@@ -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, ])
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
Reference in New Issue
Block a user