Browse Source

Sync burner outputs and display in wallet-tool

master
chris-belcher 6 years ago
parent
commit
97216d3db4
No known key found for this signature in database
GPG Key ID: EF734EA677F31129
  1. 26
      jmclient/jmclient/wallet.py
  2. 49
      jmclient/jmclient/wallet_service.py
  3. 56
      jmclient/jmclient/wallet_utils.py

26
jmclient/jmclient/wallet.py

@ -1655,6 +1655,10 @@ class FidelityBondMixin(object):
#only one mixdepth will have fidelity bonds in it #only one mixdepth will have fidelity bonds in it
FIDELITY_BOND_MIXDEPTH = 0 FIDELITY_BOND_MIXDEPTH = 0
MERKLE_BRANCH_UNAVAILABLE = b"mbu"
_BURNER_OUTPUT_STORAGE_KEY = b"burner-out"
@classmethod @classmethod
def _time_number_to_timestamp(cls, timenumber): def _time_number_to_timestamp(cls, timenumber):
""" """
@ -1776,6 +1780,28 @@ class FidelityBondMixin(object):
script = self.get_script(mixdepth, address_type, index, timenumber) script = self.get_script(mixdepth, address_type, index, timenumber)
return self.script_to_addr(script) return self.script_to_addr(script)
def add_burner_output(self, path, txhex, block_height, merkle_branch,
block_index, write=True):
"""
merkle_branch = None means it was unavailable because of pruning
"""
if self._BURNER_OUTPUT_STORAGE_KEY not in self._storage.data:
self._storage.data[self._BURNER_OUTPUT_STORAGE_KEY] = {}
path = path.encode()
txhex = unhexlify(txhex)
if not merkle_branch:
merkle_branch = self.MERKLE_BRANCH_UNAVAILABLE
self._storage.data[self._BURNER_OUTPUT_STORAGE_KEY][path] = [txhex,
block_height, merkle_branch, block_index]
if write:
self._storage.save()
def get_burner_outputs(self):
"""
Result is a dict {path: [txhex, blockheight, merkleproof, blockindex]}
"""
return self._storage.data.get(self._BURNER_OUTPUT_STORAGE_KEY, {})
#class FidelityBondWatchonlyWallet(ImportWalletMixin, BIP39WalletMixin, FidelityBondMixin): #class FidelityBondWatchonlyWallet(ImportWalletMixin, BIP39WalletMixin, FidelityBondMixin):

49
jmclient/jmclient/wallet_service.py

@ -5,6 +5,7 @@ import time
import ast import ast
import binascii import binascii
import sys import sys
import itertools
from decimal import Decimal from decimal import Decimal
from copy import deepcopy from copy import deepcopy
from twisted.internet import reactor from twisted.internet import reactor
@ -17,6 +18,7 @@ from jmclient.blockchaininterface import (INF_HEIGHT, BitcoinCoreInterface,
BitcoinCoreNoHistoryInterface) BitcoinCoreNoHistoryInterface)
from jmclient.wallet import FidelityBondMixin from jmclient.wallet import FidelityBondMixin
from jmbase.support import jmprint, EXIT_SUCCESS from jmbase.support import jmprint, EXIT_SUCCESS
import jmbitcoin as btc
"""Wallet service """Wallet service
The purpose of this independent service is to allow The purpose of this independent service is to allow
@ -517,6 +519,19 @@ class WalletService(Service):
# index: # index:
self.bci.import_addresses(self.collect_addresses_gap(), self.get_wallet_name(), self.bci.import_addresses(self.collect_addresses_gap(), self.get_wallet_name(),
self.restart_callback) self.restart_callback)
if isinstance(self.wallet, FidelityBondMixin):
mixdepth = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_BURN_ID
burner_outputs = self.wallet.get_burner_outputs()
max_index = 0
for path_repr in burner_outputs:
index = self.wallet.path_repr_to_path(path_repr.decode())[-1]
max_index = max(index+1, max_index)
self.wallet.set_next_index(mixdepth, address_type, max_index,
force=True)
self.synced = True self.synced = True
def display_rescan_message_and_system_exit(self, restart_cb): def display_rescan_message_and_system_exit(self, restart_cb):
@ -543,14 +558,16 @@ class WalletService(Service):
self.wallet.set_next_index(mixdepth, address_type, self.wallet.gap_limit, self.wallet.set_next_index(mixdepth, address_type, self.wallet.gap_limit,
force=True) force=True)
highest_used_index = 0 highest_used_index = 0
known_burner_outputs = self.wallet.get_burner_outputs() known_burner_outputs = self.wallet.get_burner_outputs()
for index in range(self.wallet.gap_limit):
index = -1
while index - highest_used_index < self.wallet.gap_limit:
index += 1
self.wallet.set_next_index(mixdepth, address_type, index, force=True)
path = self.wallet.get_path(mixdepth, address_type, index) path = self.wallet.get_path(mixdepth, address_type, index)
path_privkey, engine = self.wallet._get_priv_from_path(path) path_privkey, engine = self.wallet._get_priv_from_path(path)
path_pubkey = engine.privkey_to_pubkey(path_privkey) path_pubkey = engine.privkey_to_pubkey(path_privkey)
path_pubkeyhash = btc.bin_hash160(path_pubkey) path_pubkeyhash = btc.bin_hash160(path_pubkey)
for burner_tx in burner_txes: for burner_tx in burner_txes:
burner_pubkeyhash, gettx = burner_tx burner_pubkeyhash, gettx = burner_tx
if burner_pubkeyhash != path_pubkeyhash: if burner_pubkeyhash != path_pubkeyhash:
@ -593,9 +610,35 @@ class WalletService(Service):
self.display_rescan_message_and_system_exit(self.restart_callback) self.display_rescan_message_and_system_exit(self.restart_callback)
return return
if isinstance(self.wallet, FidelityBondMixin):
tx_receive = []
burner_txes = []
for tx in self.bci._yield_transactions(wallet_name):
if tx['category'] == 'receive':
tx_receive.append(tx)
elif tx["category"] == "send":
gettx = self.bci.get_transaction(tx["txid"])
txd = self.bci.get_deser_from_gettransaction(gettx)
if len(txd["outs"]) > 1:
continue
#must be mined into a block to sync
#otherwise there's no merkleproof or block index
if gettx["confirmations"] < 1:
continue
script = binascii.unhexlify(txd["outs"][0]["script"])
if script[0] != 0x6a: #OP_RETURN
continue
pubkeyhash = script[2:]
burner_txes.append((pubkeyhash, gettx))
self.sync_burner_outputs(burner_txes)
used_addresses_gen = (tx["address"] for tx in tx_receive)
else:
#not fidelity bond wallet, significantly faster sync
used_addresses_gen = (tx['address'] used_addresses_gen = (tx['address']
for tx in self.bci._yield_transactions(wallet_name) for tx in self.bci._yield_transactions(wallet_name)
if tx['category'] == 'receive') if tx['category'] == 'receive')
used_indices = self.get_used_indices(used_addresses_gen) used_indices = self.get_used_indices(used_addresses_gen)
jlog.debug("got used indices: {}".format(used_indices)) jlog.debug("got used indices: {}".format(used_indices))
gap_limit_used = not self.check_gap_indices(used_indices) gap_limit_used = not self.check_gap_indices(used_indices)

56
jmclient/jmclient/wallet_utils.py

@ -217,6 +217,12 @@ class WalletViewEntry(WalletViewBase):
ed += self.separator + self.serclass(self.private_key) ed += self.separator + self.serclass(self.private_key)
return self.serclass(ed) return self.serclass(ed)
class WalletViewEntryBurnOutput(WalletViewEntry):
# balance in burn outputs shouldnt be counted
# towards the total balance
def get_balance(self, include_unconf=True):
return 0
class WalletViewBranch(WalletViewBase): class WalletViewBranch(WalletViewBase):
def __init__(self, wallet_path_repr, account, address_type, branchentries=None, def __init__(self, wallet_path_repr, account, address_type, branchentries=None,
xpub=None, serclass=str, custom_separator=None): xpub=None, serclass=str, custom_separator=None):
@ -261,7 +267,7 @@ class WalletViewAccount(WalletViewBase):
self.account_name = account_name self.account_name = account_name
self.xpub = xpub self.xpub = xpub
if branches: if branches:
assert len(branches) in [2, 3] #3 if imported keys assert len(branches) in [2, 3, 4] #3 if imported keys, 4 if fidelity bonds
assert all([isinstance(x, WalletViewBranch) for x in branches]) assert all([isinstance(x, WalletViewBranch) for x in branches])
self.branches = branches self.branches = branches
@ -477,13 +483,54 @@ def wallet_display(wallet_service, showprivkey, displayall=False,
entrylist.append(WalletViewEntry( entrylist.append(WalletViewEntry(
wallet_service.get_path_repr(path), m, address_type, k, wallet_service.get_path_repr(path), m, address_type, k,
addr, [balance, balance], priv=privkey, used=status)) addr, [balance, balance], priv=privkey, used=status))
#TODO fidelity bond master pub key is this, although it should include burner too #TODO fidelity bond master pub key is this, although it should include burner too
xpub_key = wallet_service.get_bip32_pub_export(m, address_type) xpub_key = wallet_service.get_bip32_pub_export(m, address_type)
path = wallet_service.get_path_repr(wallet_service.get_path(m, address_type)) path = wallet_service.get_path_repr(wallet_service.get_path(m, address_type))
branchlist.append(WalletViewBranch(path, m, address_type, entrylist, branchlist.append(WalletViewBranch(path, m, address_type, entrylist,
xpub=xpub_key)) xpub=xpub_key))
entrylist = []
address_type = FidelityBondMixin.BIP32_BURN_ID
unused_index = wallet_service.get_next_unused_index(m, address_type)
burner_outputs = wallet_service.wallet.get_burner_outputs()
wallet_service.set_next_index(m, address_type, unused_index +
wallet_service.wallet.gap_limit, force=True)
for k in range(unused_index + wallet_service.wallet.gap_limit):
path = wallet_service.get_path(m, address_type, k)
path_repr = wallet_service.get_path_repr(path)
path_repr_b = path_repr.encode()
privkey, engine = wallet_service._get_priv_from_path(path)
pubkey = engine.privkey_to_pubkey(privkey)
pubkeyhash = btc.bin_hash160(pubkey)
output = "BURN-" + binascii.hexlify(pubkeyhash).decode()
balance = 0
status = "no transaction"
if path_repr_b in burner_outputs:
txhex, blockheight, merkle_branch, blockindex = burner_outputs[path_repr_b]
txhex = binascii.hexlify(txhex).decode()
txd = btc.deserialize(txhex)
assert len(txd["outs"]) == 1
balance = txd["outs"][0]["value"]
script = binascii.unhexlify(txd["outs"][0]["script"])
assert script[0] == 0x6a #OP_RETURN
tx_pubkeyhash = script[2:]
assert tx_pubkeyhash == pubkeyhash
status = btc.txhash(txhex) + (" [NO MERKLE PROOF]" if
merkle_branch == FidelityBondMixin.MERKLE_BRANCH_UNAVAILABLE else "")
privkey = (wallet_service.get_wif_path(path) if showprivkey else "")
if displayall or balance > 0:
entrylist.append(WalletViewEntryBurnOutput(path_repr, m,
address_type, k, output, [balance, balance],
priv=privkey, used=status))
wallet_service.set_next_index(m, address_type, unused_index)
xpub_key = wallet_service.get_bip32_pub_export(m, address_type)
path = wallet_service.get_path_repr(wallet_service.get_path(m, address_type))
branchlist.append(WalletViewBranch(path, m, address_type, entrylist,
xpub=xpub_key))
ipb = get_imported_privkey_branch(wallet_service, m, showprivkey) ipb = get_imported_privkey_branch(wallet_service, m, showprivkey)
if ipb: if ipb:
branchlist.append(ipb) branchlist.append(ipb)
@ -1294,6 +1341,11 @@ def wallet_tool_main(wallet_root_path):
method = ('display' if len(args) == 1 else args[1].lower()) method = ('display' if len(args) == 1 else args[1].lower())
read_only = method in readonly_methods read_only = method in readonly_methods
#special case needed for fidelity bond burner outputs
#maybe theres a better way to do this
if options.recoversync:
read_only = False
wallet = open_test_wallet_maybe( wallet = open_test_wallet_maybe(
wallet_path, seed, options.mixdepth, read_only=read_only, wallet_path, seed, options.mixdepth, read_only=read_only,
wallet_password_stdin=options.wallet_password_stdin, gap_limit=options.gaplimit) wallet_password_stdin=options.wallet_password_stdin, gap_limit=options.gaplimit)

Loading…
Cancel
Save