Browse Source

new command: `mmgen-tool rescan_address`

- updates an address balance using the `scantxoutset` RPC call
The MMGen Project 2 years ago
parent
commit
5488c51353
4 changed files with 80 additions and 1 deletions
  1. 66 1
      mmgen/base_proto/bitcoin/tw/ctl.py
  2. 1 0
      mmgen/main_tool.py
  3. 5 0
      mmgen/tool/rpc.py
  4. 8 0
      test/test_py_d/ts_regtest.py

+ 66 - 1
mmgen/base_proto/bitcoin/tw/ctl.py

@@ -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

+ 1 - 0
mmgen/main_tool.py

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

+ 5 - 0
mmgen/tool/rpc.py

@@ -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 ):

+ 8 - 0
test/test_py_d/ts_regtest.py

@@ -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}')