mirror of
https://github.com/basicswap/basicswap.git
synced 2024-12-31 15:59:28 +00:00
Display warning when wallet seedid doesn't match expected.
This commit is contained in:
parent
5a163e0f86
commit
e7afd5e67d
10 changed files with 199 additions and 100 deletions
52
README.md
52
README.md
|
@ -19,54 +19,4 @@ In the future it should be possible to use data from explorers instead of runnin
|
|||
|
||||
Not ready for real-world use.
|
||||
|
||||
Features still required (of many):
|
||||
- Cached addresses must be regenerated after use.
|
||||
- Option to lookup data from public explorers / nodes.
|
||||
- Ability to swap coin-types without running nodes for all coin-types
|
||||
- More swap protocols
|
||||
- Method to load mnemonic into Particl.
|
||||
- Load seeds for other wallets from same mnemonic.
|
||||
- COIN must be defined per coin.
|
||||
|
||||
|
||||
## Seller first protocol:
|
||||
|
||||
Seller sends the 1st transaction.
|
||||
|
||||
1. Seller posts offer.
|
||||
- smsg from seller to network
|
||||
coin-from
|
||||
coin-to
|
||||
amount-from
|
||||
rate
|
||||
min-amount
|
||||
time-valid
|
||||
|
||||
2. Buyer posts bid:
|
||||
- smsg from buyer to seller
|
||||
offerid
|
||||
amount
|
||||
proof-of-funds
|
||||
address_to_buyer
|
||||
time-valid
|
||||
|
||||
3. Seller accepts bid:
|
||||
- verifies proof-of-funds
|
||||
- generates secret
|
||||
- submits initiate tx to coin-from network
|
||||
- smsg from seller to buyer
|
||||
txid
|
||||
initiatescript (includes pkhash_to_seller as the pkhash_refund)
|
||||
|
||||
4. Buyer participates:
|
||||
- inspects initiate tx in coin-from network
|
||||
- submits participate tx in coin-to network
|
||||
|
||||
5. Seller redeems:
|
||||
- constructs participatescript
|
||||
- inspects participate tx in coin-to network
|
||||
- redeems from participate tx revealing secret
|
||||
|
||||
6. Buyer redeems:
|
||||
- scans coin-to network for seller-redeem tx
|
||||
- redeems from initiate tx with revealed secret
|
||||
Discuss development and help with testing in the matrix channel [#basicswap:matrix.org](https://riot.im/app/#/room/#basicswap:matrix.org)
|
||||
|
|
|
@ -621,19 +621,39 @@ class BasicSwap(BaseApp):
|
|||
|
||||
if self.coin_clients[c]['connection_type'] == 'rpc':
|
||||
self.waitForDaemonRPC(c)
|
||||
core_version = self.coin_clients[c]['interface'].getDaemonVersion()
|
||||
self.log.info('%s Core version %d', chainparams[c]['name'].capitalize(), core_version)
|
||||
ci = self.ci(c)
|
||||
core_version = ci.getDaemonVersion()
|
||||
self.log.info('%s Core version %d', ci.coin_name(), core_version)
|
||||
self.coin_clients[c]['core_version'] = core_version
|
||||
|
||||
if c == Coins.XMR:
|
||||
self.coin_clients[c]['interface'].ensureWalletExists()
|
||||
|
||||
if c == Coins.PART:
|
||||
self.coin_clients[c]['have_spent_index'] = self.coin_clients[c]['interface'].haveSpentIndex()
|
||||
self.coin_clients[c]['have_spent_index'] = ci.haveSpentIndex()
|
||||
|
||||
# Sanity checks
|
||||
if self.callcoinrpc(c, 'getstakinginfo')['enabled'] is not False:
|
||||
self.log.warning('%s staking is not disabled.', chainparams[c]['name'].capitalize())
|
||||
self.log.warning('%s staking is not disabled.', ci.coin_name())
|
||||
elif c == Coins.XMR:
|
||||
ci.ensureWalletExists()
|
||||
|
||||
expect_address = self.getStringKV('main_wallet_addr_' + chainparams[c]['name'])
|
||||
self.log.debug('[rm] expect_address %s', expect_address)
|
||||
if expect_address is None:
|
||||
self.log.warning('Can\'t find expected main wallet address for coin {}'.format(ci.coin_name()))
|
||||
else:
|
||||
if expect_address == ci.getMainWalletAddress():
|
||||
ci.setWalletSeedWarning(False)
|
||||
else:
|
||||
self.log.warning('Wallet for coin {} not derived from swap seed.'.format(ci.coin_name()))
|
||||
else:
|
||||
expect_seedid = self.getStringKV('main_wallet_seedid_' + chainparams[c]['name'])
|
||||
self.log.debug('[rm] expect_seedid %s', expect_seedid)
|
||||
if expect_seedid is None:
|
||||
self.log.warning('Can\'t find expected wallet seed id for coin {}'.format(ci.coin_name()))
|
||||
else:
|
||||
if expect_seedid == ci.getWalletSeedID():
|
||||
ci.setWalletSeedWarning(False)
|
||||
else:
|
||||
self.log.warning('Wallet for coin {} not derived from swap seed.'.format(ci.coin_name()))
|
||||
|
||||
self.initialise()
|
||||
|
||||
|
@ -702,6 +722,8 @@ class BasicSwap(BaseApp):
|
|||
raise ValueError('{} chain is still syncing, currently at {}.'.format(self.coin_clients[c]['name'], synced))
|
||||
|
||||
def initialiseWallet(self, coin_type):
|
||||
if coin_type == Coins.PART:
|
||||
return
|
||||
ci = self.ci(coin_type)
|
||||
self.log.info('Initialising {} wallet.'.format(ci.coin_name()))
|
||||
|
||||
|
@ -709,20 +731,63 @@ class BasicSwap(BaseApp):
|
|||
key_view = self.getWalletKey(coin_type, 1, for_ed25519=True)
|
||||
key_spend = self.getWalletKey(coin_type, 2, for_ed25519=True)
|
||||
ci.initialiseWallet(key_view, key_spend)
|
||||
root_address = ci.getAddressFromKeys(key_view, key_spend)
|
||||
|
||||
key_str = 'main_wallet_addr_' + chainparams[coin_type]['name']
|
||||
self.setStringKV(key_str, root_address)
|
||||
return
|
||||
|
||||
root_key = self.getWalletKey(coin_type, 1)
|
||||
root_hash = ci.getAddressHashFromKey(root_key)
|
||||
ci.initialiseWallet(root_key)
|
||||
|
||||
key_str = 'main_wallet_seedid_' + chainparams[coin_type]['name']
|
||||
self.setStringKV(key_str, root_hash.hex())
|
||||
|
||||
def setIntKV(self, str_key, int_val):
|
||||
session = scoped_session(self.session_factory)
|
||||
kv = session.query(DBKVInt).filter_by(key=str_key).first()
|
||||
if not kv:
|
||||
kv = DBKVInt(key=str_key, value=int_val)
|
||||
session.add(kv)
|
||||
session.commit()
|
||||
session.close()
|
||||
session.remove()
|
||||
self.mxDB.acquire()
|
||||
try:
|
||||
session = scoped_session(self.session_factory)
|
||||
kv = session.query(DBKVInt).filter_by(key=str_key).first()
|
||||
if not kv:
|
||||
kv = DBKVInt(key=str_key, value=int_val)
|
||||
else:
|
||||
kv.value = int_val
|
||||
session.add(kv)
|
||||
session.commit()
|
||||
session.close()
|
||||
session.remove()
|
||||
finally:
|
||||
self.mxDB.release()
|
||||
|
||||
def setStringKV(self, str_key, str_val):
|
||||
self.mxDB.acquire()
|
||||
try:
|
||||
session = scoped_session(self.session_factory)
|
||||
kv = session.query(DBKVString).filter_by(key=str_key).first()
|
||||
if not kv:
|
||||
kv = DBKVString(key=str_key, value=str_val)
|
||||
else:
|
||||
kv.value = str_val
|
||||
session.add(kv)
|
||||
session.commit()
|
||||
session.close()
|
||||
session.remove()
|
||||
finally:
|
||||
self.mxDB.release()
|
||||
|
||||
def getStringKV(self, str_key):
|
||||
self.mxDB.acquire()
|
||||
try:
|
||||
session = scoped_session(self.session_factory)
|
||||
v = session.query(DBKVString).filter_by(key=str_key).first()
|
||||
if not v:
|
||||
return None
|
||||
return v.value
|
||||
finally:
|
||||
session.close()
|
||||
session.remove()
|
||||
self.mxDB.release()
|
||||
|
||||
def activateBid(self, session, bid):
|
||||
if bid.bid_id in self.swaps_in_progress:
|
||||
|
@ -756,7 +821,7 @@ class BasicSwap(BaseApp):
|
|||
|
||||
# TODO process addresspool if bid has previously been abandoned
|
||||
|
||||
def deactivateBid(self, offer, bid):
|
||||
def deactivateBid(self, session, offer, bid):
|
||||
# Remove from in progress
|
||||
self.log.debug('Removing bid from in-progress: %s', bid.bid_id.hex())
|
||||
self.swaps_in_progress.pop(bid.bid_id, None)
|
||||
|
@ -1126,25 +1191,8 @@ class BasicSwap(BaseApp):
|
|||
def cacheNewAddressForCoin(self, coin_type):
|
||||
self.log.debug('cacheNewAddressForCoin %s', coin_type)
|
||||
key_str = 'receive_addr_' + chainparams[coin_type]['name']
|
||||
session = scoped_session(self.session_factory)
|
||||
addr = self.getReceiveAddressForCoin(coin_type)
|
||||
self.mxDB.acquire()
|
||||
try:
|
||||
session = scoped_session(self.session_factory)
|
||||
try:
|
||||
kv = session.query(DBKVString).filter_by(key=key_str).first()
|
||||
kv.value = addr
|
||||
except Exception:
|
||||
kv = DBKVString(
|
||||
key=key_str,
|
||||
value=addr
|
||||
)
|
||||
session.add(kv)
|
||||
session.commit()
|
||||
session.close()
|
||||
session.remove()
|
||||
finally:
|
||||
self.mxDB.release()
|
||||
self.setStringKV(key_str, addr)
|
||||
return addr
|
||||
|
||||
def getCachedAddressForCoin(self, coin_type):
|
||||
|
@ -1838,9 +1886,9 @@ class BasicSwap(BaseApp):
|
|||
|
||||
# Mark bid as abandoned, no further processing will be done
|
||||
bid.setState(BidStates.BID_ABANDONED)
|
||||
self.deactivateBid(session, offer, bid)
|
||||
session.add(bid)
|
||||
session.commit()
|
||||
|
||||
self.deactivateBid(offer, bid)
|
||||
finally:
|
||||
session.close()
|
||||
session.remove()
|
||||
|
@ -3986,6 +4034,7 @@ class BasicSwap(BaseApp):
|
|||
self.setBidError(bid_id, bid, str(ex))
|
||||
|
||||
# Update copy of bid in swaps_in_progress
|
||||
assert(bid_id in self.swaps_in_progress)
|
||||
self.swaps_in_progress[bid_id] = (bid, offer)
|
||||
|
||||
def processXmrSplitMessage(self, msg):
|
||||
|
@ -4112,7 +4161,7 @@ class BasicSwap(BaseApp):
|
|||
self.setBidError(bid_id, v[0], str(ex))
|
||||
|
||||
for bid_id, bid, offer in to_remove:
|
||||
self.deactivateBid(offer, bid)
|
||||
self.deactivateBid(None, offer, bid)
|
||||
self._last_checked_progress = now
|
||||
|
||||
if now - self._last_checked_watched >= self.check_watched_seconds:
|
||||
|
@ -4157,12 +4206,20 @@ class BasicSwap(BaseApp):
|
|||
if has_changed:
|
||||
session = scoped_session(self.session_factory)
|
||||
try:
|
||||
self.saveBidInSession(bid_id, bid, session)
|
||||
session.commit()
|
||||
if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED:
|
||||
activate_bid = False
|
||||
if offer.swap_type == SwapTypes.BUYER_FIRST:
|
||||
if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED:
|
||||
activate_bid = True
|
||||
else:
|
||||
raise ValueError('TODO')
|
||||
|
||||
if activate_bid:
|
||||
self.activateBid(session, bid)
|
||||
else:
|
||||
self.deactivateBid(offer, bid)
|
||||
self.deactivateBid(session, offer, bid)
|
||||
|
||||
self.saveBidInSession(bid_id, bid, session)
|
||||
session.commit()
|
||||
finally:
|
||||
session.close()
|
||||
session.remove()
|
||||
|
@ -4222,8 +4279,9 @@ class BasicSwap(BaseApp):
|
|||
|
||||
def getWalletInfo(self, coin):
|
||||
|
||||
blockchaininfo = self.coin_clients[coin]['interface'].getBlockchainInfo()
|
||||
walletinfo = self.coin_clients[coin]['interface'].getWalletInfo()
|
||||
ci = self.ci(coin)
|
||||
blockchaininfo = ci.getBlockchainInfo()
|
||||
walletinfo = ci.getWalletInfo()
|
||||
|
||||
scale = chainparams[coin]['decimal_places']
|
||||
rv = {
|
||||
|
@ -4234,6 +4292,7 @@ class BasicSwap(BaseApp):
|
|||
'balance': format_amount(make_int(walletinfo['balance'], scale), scale),
|
||||
'unconfirmed': format_amount(make_int(walletinfo.get('unconfirmed_balance'), scale), scale),
|
||||
'synced': '{0:.2f}'.format(round(blockchaininfo['verificationprogress'], 2)),
|
||||
'expected_seed': ci.knownWalletSeed(),
|
||||
}
|
||||
return rv
|
||||
|
||||
|
|
|
@ -199,6 +199,9 @@ chainparams = {
|
|||
|
||||
|
||||
class CoinInterface:
|
||||
def __init__(self):
|
||||
self._unknown_wallet_seed = True
|
||||
|
||||
def format_amount(self, amount_int):
|
||||
return format_amount(amount_int, self.exp())
|
||||
|
||||
|
@ -207,3 +210,9 @@ class CoinInterface:
|
|||
|
||||
def ticker(self):
|
||||
return chainparams[self.coin_type()]['ticker']
|
||||
|
||||
def setWalletSeedWarning(self, value):
|
||||
self._unknown_wallet_seed = value
|
||||
|
||||
def knownWalletSeed(self):
|
||||
return not self._unknown_wallet_seed
|
||||
|
|
|
@ -254,6 +254,7 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
'blocks': w['blocks'],
|
||||
'synced': w['synced'],
|
||||
'deposit_address': w['deposit_address'],
|
||||
'expected_seed': w['expected_seed'],
|
||||
})
|
||||
if float(w['unconfirmed']) > 0.0:
|
||||
wallets_formatted[-1]['unconfirmed'] = w['unconfirmed']
|
||||
|
|
|
@ -118,6 +118,7 @@ class BTCInterface(CoinInterface):
|
|||
return abs(a - b) < 20
|
||||
|
||||
def __init__(self, coin_settings, network):
|
||||
super().__init__()
|
||||
self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'])
|
||||
self.txoType = CTxOut
|
||||
self._network = network
|
||||
|
@ -145,6 +146,9 @@ class BTCInterface(CoinInterface):
|
|||
def getWalletInfo(self):
|
||||
return self.rpc_callback('getwalletinfo')
|
||||
|
||||
def getWalletSeedID(self):
|
||||
return self.rpc_callback('getwalletinfo')['hdseedid']
|
||||
|
||||
def getNewAddress(self, use_segwit):
|
||||
args = ['swap_receive']
|
||||
if use_segwit:
|
||||
|
@ -177,6 +181,10 @@ class BTCInterface(CoinInterface):
|
|||
def getPubkey(self, privkey):
|
||||
return PublicKey.from_secret(privkey).format()
|
||||
|
||||
def getAddressHashFromKey(self, key):
|
||||
pk = self.getPubkey(key)
|
||||
return hash160(pk)
|
||||
|
||||
def verifyKey(self, k):
|
||||
i = b2i(k)
|
||||
return(i < ep.o and i > 0)
|
||||
|
|
|
@ -33,6 +33,10 @@ class PARTInterface(BTCInterface):
|
|||
self._network = network
|
||||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||
|
||||
def knownWalletSeed(self):
|
||||
# TODO: Double check
|
||||
return True
|
||||
|
||||
def getNewAddress(self, use_segwit):
|
||||
return self.rpc_callback('getnewaddress', ['swap_receive'])
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ class XMRInterface(CoinInterface):
|
|||
return 32
|
||||
|
||||
def __init__(self, coin_settings, network):
|
||||
super().__init__()
|
||||
rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'])
|
||||
rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'])
|
||||
|
||||
|
@ -154,6 +155,11 @@ class XMRInterface(CoinInterface):
|
|||
def getPubkey(self, privkey):
|
||||
return ed25519_get_pubkey(privkey)
|
||||
|
||||
def getAddressFromKeys(self, key_view, key_spend):
|
||||
pk_view = self.getPubkey(key_view)
|
||||
pk_spend = self.getPubkey(key_spend)
|
||||
return xmr_util.encode_address(pk_view, pk_spend)
|
||||
|
||||
def verifyKey(self, k):
|
||||
i = b2i(k)
|
||||
return(i < edf.l and i > 8)
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<tr><td>Balance:</td><td>{{ w.balance }}</td>{% if w.unconfirmed %}<td>Unconfirmed:</td><td>{{ w.unconfirmed }}</td>{% endif %}</tr>
|
||||
<tr><td>Blocks:</td><td>{{ w.blocks }}</td></tr>
|
||||
<tr><td>Synced:</td><td>{{ w.synced }}</td></tr>
|
||||
<tr><td>Expected Seed:</td><td>{{ w.expected_seed }}</td></tr>
|
||||
<tr><td><input type="submit" name="newaddr_{{ w.cid }}" value="Deposit Address"></td><td>{{ w.deposit_address }}</td></tr>
|
||||
<tr><td><input type="submit" name="withdraw_{{ w.cid }}" value="Withdraw"></td><td>Amount: <input type="text" name="amt_{{ w.cid }}"></td><td>Address: <input type="text" name="to_{{ w.cid }}"></td><td>Subtract fee: <input type="checkbox" name="subfee_{{ w.cid }}"></td></tr>
|
||||
<tr><td>Fee Rate:</td><td>{{ w.fee_rate }}</td><td>Est Fee:</td><td>{{ w.est_fee }}</td></tr>
|
||||
|
@ -32,4 +33,4 @@
|
|||
</form>
|
||||
|
||||
<p><a href="/">home</a></p>
|
||||
</body></html>
|
||||
</body></html>
|
||||
|
|
53
doc/notes.md
53
doc/notes.md
|
@ -4,3 +4,56 @@
|
|||
```
|
||||
python setup.py test -s tests.basicswap.test_xmr.Test.test_02_leader_recover_a_lock_tx
|
||||
```
|
||||
|
||||
## TODO
|
||||
Features still required (of many):
|
||||
- Cached addresses must be regenerated after use.
|
||||
- Option to lookup data from public explorers / nodes.
|
||||
- Ability to swap coin-types without running nodes for all coin-types
|
||||
- More swap protocols
|
||||
- Method to load mnemonic into Particl.
|
||||
- Load seeds for other wallets from same mnemonic.
|
||||
- COIN must be defined per coin.
|
||||
|
||||
|
||||
## Seller first protocol:
|
||||
|
||||
Seller sends the 1st transaction.
|
||||
|
||||
1. Seller posts offer.
|
||||
- smsg from seller to network
|
||||
coin-from
|
||||
coin-to
|
||||
amount-from
|
||||
rate
|
||||
min-amount
|
||||
time-valid
|
||||
|
||||
2. Buyer posts bid:
|
||||
- smsg from buyer to seller
|
||||
offerid
|
||||
amount
|
||||
proof-of-funds
|
||||
address_to_buyer
|
||||
time-valid
|
||||
|
||||
3. Seller accepts bid:
|
||||
- verifies proof-of-funds
|
||||
- generates secret
|
||||
- submits initiate tx to coin-from network
|
||||
- smsg from seller to buyer
|
||||
txid
|
||||
initiatescript (includes pkhash_to_seller as the pkhash_refund)
|
||||
|
||||
4. Buyer participates:
|
||||
- inspects initiate tx in coin-from network
|
||||
- submits participate tx in coin-to network
|
||||
|
||||
5. Seller redeems:
|
||||
- constructs participatescript
|
||||
- inspects participate tx in coin-to network
|
||||
- redeems from participate tx revealing secret
|
||||
|
||||
6. Buyer redeems:
|
||||
- scans coin-to network for seller-redeem tx
|
||||
- redeems from initiate tx with revealed secret
|
||||
|
|
|
@ -178,14 +178,22 @@ class Test(unittest.TestCase):
|
|||
try:
|
||||
waitForServer(12700)
|
||||
|
||||
wallets = json.loads(urlopen('http://localhost:12700/json/wallets').read())
|
||||
print('[rm] wallets', dumpj(wallets))
|
||||
wallets_0 = json.loads(urlopen('http://localhost:12700/json/wallets').read())
|
||||
print('[rm] wallets_0', dumpj(wallets_0))
|
||||
assert(wallets_0['1']['expected_seed'] == True)
|
||||
assert(wallets_0['6']['expected_seed'] == True)
|
||||
|
||||
waitForServer(12701)
|
||||
wallets = json.loads(urlopen('http://localhost:12701/json/wallets').read())
|
||||
print('[rm] wallets', dumpj(wallets))
|
||||
wallets_1 = json.loads(urlopen('http://localhost:12701/json/wallets').read())
|
||||
print('[rm] wallets_1', dumpj(wallets_1))
|
||||
|
||||
raise ValueError('TODO')
|
||||
assert(wallets_0['1']['expected_seed'] == True)
|
||||
assert(wallets_1['6']['expected_seed'] == True)
|
||||
|
||||
# TODO: Check other coins
|
||||
|
||||
assert(wallets_0['1']['deposit_address'] == wallets_1['1']['deposit_address'])
|
||||
assert(wallets_0['6']['deposit_address'] == wallets_1['6']['deposit_address'])
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
|
|
Loading…
Reference in a new issue