new command: mmgen-tool rescan_address

- updates an address balance using the `scantxoutset` RPC call
This commit is contained in:
The MMGen Project 2022-05-26 16:07:21 +00:00
commit 5488c51353
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 80 additions and 1 deletions

View file

@ -14,7 +14,7 @@ base_proto.bitcoin.twctl: Bitcoin base protocol tracking wallet control class
from ....globalvars import g
from ....tw.ctl import TrackingWallet
from ....util import msg,msg_r,rmsg,die,write_mode
from ....util import msg,msg_r,rmsg,vmsg,die,suf,fmt_list,write_mode
class BitcoinTrackingWallet(TrackingWallet):
@ -88,3 +88,68 @@ class BitcoinTrackingWallet(TrackingWallet):
tip = await self.rpc.call('getblockcount')
msg('Done')
@write_mode
async def rescan_address(self,addrspec):
res = await self.resolve_address(addrspec,None)
if not res:
return False
return await self.rescan_addresses([res.coinaddr])
@write_mode
async def rescan_addresses(self,coin_addrs):
import asyncio
async def do_scan():
return await self.rpc.call(
'scantxoutset',
'start',
[f'addr({a})' for a in coin_addrs],
timeout = 720 ) # call may take several minutes to complete
async def do_status():
m = f'{CR}Scanning UTXO set: '
msg_r(m)
while True:
await asyncio.sleep(2)
res = await self.rpc.call('scantxoutset','status')
if res:
msg_r(m + f'{res["progress"]}% completed ')
if task1.done():
msg('')
return
CR = '\r'
if self.rpc.backend.name == 'aiohttp':
task1 = asyncio.create_task( do_scan() )
task2 = asyncio.create_task( do_status() )
res = await task1
await task2
else:
msg_r(f'Scanning UTXO set, this could take several minutes...')
res = await do_scan()
msg('done')
if not res['success']:
msg('UTXO scanning failed or was interrupted')
return False
elif res['unspents']:
blocks = sorted({ i['height'] for i in res['unspents'] })
msg('Found {} unspent output{} in {} block{}'.format(
len(res['unspents']),
suf(res['unspents']),
len(blocks),
suf(blocks) ))
vmsg(f'Blocks to rescan: {fmt_list(blocks,fmt="bare")}')
for n,block in enumerate(blocks):
msg_r(f'{CR}Rescanning block: {block} ({n+1}/{len(blocks)})')
# httplib seems to require fresh connection here, so specify timeout
await self.rpc.call('rescanblockchain',block,block,timeout=60)
msg('\nRescan completed OK')
return True
else:
msg('Imported address has no balance' if len(coin_addrs) == 1 else
'Imported addresses have no balances' )
return True

View file

@ -161,6 +161,7 @@ mods = {
'listaddresses',
'remove_address',
'remove_label',
'rescan_address',
'rescan_blockchain',
'resolve_address',
'twview',

View file

@ -179,6 +179,11 @@ class tool_cmd(tool_cmd_base):
else:
return False
async def rescan_address(self,mmgen_or_coin_addr:str):
"rescan an address in the tracking wallet to update its balance"
from ..tw.ctl import TrackingWallet
return await (await TrackingWallet(self.proto,mode='w')).rescan_address( mmgen_or_coin_addr )
async def rescan_blockchain(self,
start_block: int = None,
stop_block: int = None ):

View file

@ -203,6 +203,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
('bob_import_list', 'importing flat address list'),
('bob_import_list_rescan', 'importing flat address list with --rescan'),
('bob_resolve_addr', 'resolving an address in the tracking wallet'),
('bob_rescan_addr', 'rescanning an address'),
('bob_rescan_blockchain_all','rescanning the blockchain (full rescan)'),
('bob_rescan_blockchain_gb', 'rescanning the blockchain (Genesis block)'),
('bob_rescan_blockchain_one','rescanning the blockchain (single block)'),
@ -931,6 +932,13 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
addrfile = joinpath(self.tmpdir,'non-mmgen.addrs')
return self.user_import('bob',['--quiet','--rescan','--addrlist',addrfile],nAddr=5)
def bob_rescan_addr(self):
sid = self._user_sid('bob')
t = self.spawn('mmgen-tool',['--bob','rescan_address',f'{sid}:C:1'])
t.expect('Found 1 unspent output')
t.expect('completed OK')
return t
def bob_rescan_blockchain(self,add_args,expect):
t = self.spawn('mmgen-tool',['--bob','rescan_blockchain'] + add_args)
t.expect(f'Scanning blocks {expect}')