passing tests

This commit is contained in:
Lex Berezhny 2018-06-13 20:57:57 -04:00
parent 99ae39012f
commit e9fbc875e0
18 changed files with 231 additions and 293 deletions

View file

@ -4,7 +4,11 @@ python:
- "2.7" - "2.7"
- "3.6" - "3.6"
install: pip install tox-travis coverage install:
- pip install tox-travis coverage
- pushd .. && git clone https://github.com/lbryio/electrumx.git --branch packages && popd
- pushd .. && git clone https://github.com/lbryio/orchstr8.git && popd
script: tox script: tox
after_success: after_success:

View file

View file

@ -17,22 +17,23 @@ class BasicTransactionTests(IntegrationTestCase):
address = await account1.receiving.get_or_create_usable_address().asFuture(asyncio.get_event_loop()) address = await account1.receiving.get_or_create_usable_address().asFuture(asyncio.get_event_loop())
sendtxid = await self.blockchain.send_to_address(address.decode(), 5.5) sendtxid = await self.blockchain.send_to_address(address.decode(), 5.5)
await self.on_transaction(sendtxid) await self.on_transaction(sendtxid) #mempool
await self.blockchain.generate(1) await self.blockchain.generate(1)
await asyncio.sleep(5) await self.on_transaction(sendtxid) #confirmed
self.assertEqual(await self.get_balance(account1), int(5.5*COIN)) self.assertEqual(await self.get_balance(account1), int(5.5*COIN))
self.assertEqual(await self.get_balance(account2), 0) self.assertEqual(await self.get_balance(account2), 0)
address = await account2.receiving.get_or_create_usable_address().asFuture(asyncio.get_event_loop()) address = await account2.receiving.get_or_create_usable_address().asFuture(asyncio.get_event_loop())
tx = await self.ledger.transaction_class.pay( tx = await self.ledger.transaction_class.pay(
[self.ledger.transaction_class.output_class.pay_pubkey_hash(2, self.ledger.address_to_hash160(address))], [self.ledger.transaction_class.output_class.pay_pubkey_hash(2*COIN, self.ledger.address_to_hash160(address))],
[account1], account1 [account1], account1
).asFuture(asyncio.get_event_loop()) ).asFuture(asyncio.get_event_loop())
await self.blockchain.decode_raw_transaction(tx)
await self.broadcast(tx) await self.broadcast(tx)
await self.on_transaction(tx.id.decode()) await self.on_transaction(tx.hex_id.decode()) #mempool
await self.lbrycrd.generate(1) await self.blockchain.generate(1)
await self.on_transaction(tx.hex_id.decode()) #confirmed
self.assertEqual(await self.get_balance(account1), int(3.5*COIN))
self.assertEqual(await self.get_balance(account2), int(2.0*COIN))
self.assertEqual(round(await self.get_balance(account1)/COIN, 1), 3.5)
self.assertEqual(round(await self.get_balance(account2)/COIN, 1), 2.0)

View file

@ -41,13 +41,13 @@ class TestKeyChain(unittest.TestCase):
# case #2: only one new addressed needed # case #2: only one new addressed needed
keys = yield account.receiving.get_addresses(None, True) keys = yield account.receiving.get_addresses(None, True)
yield self.ledger.db.set_address_history(keys[19]['address'], 'a:1:') yield self.ledger.db.set_address_history(keys[19]['address'], b'a:1:')
new_keys = yield account.receiving.ensure_address_gap() new_keys = yield account.receiving.ensure_address_gap()
self.assertEqual(len(new_keys), 1) self.assertEqual(len(new_keys), 1)
# case #3: 20 addresses needed # case #3: 20 addresses needed
keys = yield account.receiving.get_addresses(None, True) keys = yield account.receiving.get_addresses(None, True)
yield self.ledger.db.set_address_history(keys[0]['address'], 'a:1:') yield self.ledger.db.set_address_history(keys[0]['address'], b'a:1:')
new_keys = yield account.receiving.ensure_address_gap() new_keys = yield account.receiving.ensure_address_gap()
self.assertEqual(len(new_keys), 20) self.assertEqual(len(new_keys), 20)

View file

@ -1,11 +1,11 @@
import unittest from twisted.trial import unittest
from types import GeneratorType
from torba.coin.bitcoinsegwit import MainNetLedger from torba.coin.bitcoinsegwit import MainNetLedger
from torba.coinselection import CoinSelector, MAXIMUM_TRIES from torba.coinselection import CoinSelector, MAXIMUM_TRIES
from torba.constants import CENT from torba.constants import CENT
from torba.manager import WalletManager
from .test_transaction import Output, get_output as utxo from .test_transaction import get_output as utxo
NULL_HASH = b'\x00'*32 NULL_HASH = b'\x00'*32
@ -13,7 +13,7 @@ NULL_HASH = b'\x00'*32
def search(*args, **kwargs): def search(*args, **kwargs):
selection = CoinSelector(*args, **kwargs).branch_and_bound() selection = CoinSelector(*args, **kwargs).branch_and_bound()
return [o.output.amount for o in selection] if selection else selection return [o.txo.amount for o in selection] if selection else selection
class BaseSelectionTestCase(unittest.TestCase): class BaseSelectionTestCase(unittest.TestCase):
@ -23,7 +23,7 @@ class BaseSelectionTestCase(unittest.TestCase):
return self.ledger.db.start() return self.ledger.db.start()
def estimates(self, *args): def estimates(self, *args):
txos = args if isinstance(args[0], Output) else args[0] txos = args[0] if isinstance(args[0], (GeneratorType, list)) else args
return [txo.get_estimator(self.ledger) for txo in txos] return [txo.get_estimator(self.ledger) for txo in txos]
@ -44,7 +44,7 @@ class TestCoinSelectionTests(BaseSelectionTestCase):
self.assertEqual(selector.tries, 201) self.assertEqual(selector.tries, 201)
def test_exact_match(self): def test_exact_match(self):
fee = utxo(CENT).get_estimator(self.coin).fee fee = utxo(CENT).get_estimator(self.ledger).fee
utxo_pool = self.estimates( utxo_pool = self.estimates(
utxo(CENT + fee), utxo(CENT + fee),
utxo(CENT), utxo(CENT),
@ -52,7 +52,7 @@ class TestCoinSelectionTests(BaseSelectionTestCase):
) )
selector = CoinSelector(utxo_pool, CENT, 0) selector = CoinSelector(utxo_pool, CENT, 0)
match = selector.select() match = selector.select()
self.assertEqual([CENT + fee], [c.output.amount for c in match]) self.assertEqual([CENT + fee], [c.txo.amount for c in match])
self.assertTrue(selector.exact_match) self.assertTrue(selector.exact_match)
def test_random_draw(self): def test_random_draw(self):
@ -63,7 +63,7 @@ class TestCoinSelectionTests(BaseSelectionTestCase):
) )
selector = CoinSelector(utxo_pool, CENT, 0, '\x00') selector = CoinSelector(utxo_pool, CENT, 0, '\x00')
match = selector.select() match = selector.select()
self.assertEqual([2 * CENT], [c.output.amount for c in match]) self.assertEqual([2 * CENT], [c.txo.amount for c in match])
self.assertFalse(selector.exact_match) self.assertFalse(selector.exact_match)
@ -78,10 +78,6 @@ class TestOfficialBitcoinCoinSelectionTests(BaseSelectionTestCase):
# Branch and Bound coin selection white paper: # Branch and Bound coin selection white paper:
# https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf # https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
def setUp(self):
ledger = WalletManager().get_or_create_ledger(BTC.get_id())
self.coin = BTC(ledger, 0)
def make_hard_case(self, utxos): def make_hard_case(self, utxos):
target = 0 target = 0
utxo_pool = [] utxo_pool = []
@ -93,6 +89,8 @@ class TestOfficialBitcoinCoinSelectionTests(BaseSelectionTestCase):
return self.estimates(utxo_pool), target return self.estimates(utxo_pool), target
def test_branch_and_bound_coin_selection(self): def test_branch_and_bound_coin_selection(self):
self.ledger.fee_per_byte = 0
utxo_pool = self.estimates( utxo_pool = self.estimates(
utxo(1 * CENT), utxo(1 * CENT),
utxo(2 * CENT), utxo(2 * CENT),

View file

@ -1,3 +1,4 @@
import six
from binascii import hexlify from binascii import hexlify
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer from twisted.internet import defer
@ -6,6 +7,9 @@ from torba.coin.bitcoinsegwit import MainNetLedger
from .test_transaction import get_transaction from .test_transaction import get_transaction
if six.PY3:
buffer = memoryview
class MockNetwork: class MockNetwork:
@ -53,7 +57,7 @@ class TestSynchronization(unittest.TestCase):
self.assertEqual(self.ledger.network.get_transaction_called, [b'abc', b'def', b'ghi']) self.assertEqual(self.ledger.network.get_transaction_called, [b'abc', b'def', b'ghi'])
address_details = yield self.ledger.db.get_address(address) address_details = yield self.ledger.db.get_address(address)
self.assertEqual(address_details['history'], b'abc:1:def:2:ghi:3:') self.assertEqual(address_details['history'], buffer(b'abc:1:def:2:ghi:3:'))
self.ledger.network.get_history_called = [] self.ledger.network.get_history_called = []
self.ledger.network.get_transaction_called = [] self.ledger.network.get_transaction_called = []
@ -69,4 +73,4 @@ class TestSynchronization(unittest.TestCase):
self.assertEqual(self.ledger.network.get_history_called, [address]) self.assertEqual(self.ledger.network.get_history_called, [address])
self.assertEqual(self.ledger.network.get_transaction_called, [b'jkl']) self.assertEqual(self.ledger.network.get_transaction_called, [b'jkl'])
address_details = yield self.ledger.db.get_address(address) address_details = yield self.ledger.db.get_address(address)
self.assertEqual(address_details['history'], b'abc:1:def:2:ghi:3:jkl:4:') self.assertEqual(address_details['history'], buffer(b'abc:1:def:2:ghi:3:jkl:4:'))

View file

@ -1,8 +1,8 @@
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer
from torba.basetransaction import BaseTransaction, BaseInput, BaseOutput from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class
from torba.coin.bitcoinsegwit import MainNetLedger
from torba.constants import CENT, COIN from torba.constants import CENT, COIN
from torba.manager import WalletManager from torba.manager import WalletManager
from torba.wallet import Wallet from torba.wallet import Wallet
@ -14,30 +14,26 @@ FEE_PER_CHAR = 200000
def get_output(amount=CENT, pubkey_hash=NULL_HASH): def get_output(amount=CENT, pubkey_hash=NULL_HASH):
return BaseTransaction() \ return ledger_class.transaction_class() \
.add_outputs([BaseTransaction.output_class.pay_pubkey_hash(amount, pubkey_hash)]) \ .add_outputs([ledger_class.transaction_class.output_class.pay_pubkey_hash(amount, pubkey_hash)]) \
.outputs[0] .outputs[0]
def get_input(): def get_input():
return BaseInput.spend(get_output()) return ledger_class.transaction_class.input_class.spend(get_output())
def get_transaction(txo=None): def get_transaction(txo=None):
return BaseTransaction() \ return ledger_class.transaction_class() \
.add_inputs([get_input()]) \ .add_inputs([get_input()]) \
.add_outputs([txo or BaseOutput.pay_pubkey_hash(CENT, NULL_HASH)]) .add_outputs([txo or ledger_class.transaction_class.output_class.pay_pubkey_hash(CENT, NULL_HASH)])
def get_wallet_and_ledger():
ledger = WalletManager().get_or_create_ledger(MainNetLedger.get_id())
return Wallet('Main', [ledger], [ledger.account_class.generate(ledger, u'torba')]), ledger
class TestSizeAndFeeEstimation(unittest.TestCase): class TestSizeAndFeeEstimation(unittest.TestCase):
def setUp(self): def setUp(self):
self.wallet, self.ledger = get_wallet_and_ledger() self.ledger = ledger_class(db=':memory:')
return self.ledger.db.start()
def io_fee(self, io): def io_fee(self, io):
return self.ledger.get_input_output_fee(io) return self.ledger.get_input_output_fee(io)
@ -70,20 +66,20 @@ class TestTransactionSerialization(unittest.TestCase):
'000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4c' '000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4c'
'ef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000' 'ef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'
) )
tx = BaseTransaction(raw) tx = ledger_class.transaction_class(raw)
self.assertEqual(tx.version, 1) self.assertEqual(tx.version, 1)
self.assertEqual(tx.locktime, 0) self.assertEqual(tx.locktime, 0)
self.assertEqual(len(tx.inputs), 1) self.assertEqual(len(tx.inputs), 1)
self.assertEqual(len(tx.outputs), 1) self.assertEqual(len(tx.outputs), 1)
ledgerbase = tx.inputs[0] coinbase = tx.inputs[0]
self.assertEqual(ledgerbase.output_txid, NULL_HASH) self.assertEqual(coinbase.output_txhash, NULL_HASH)
self.assertEqual(ledgerbase.output_index, 0xFFFFFFFF) self.assertEqual(coinbase.output_index, 0xFFFFFFFF)
self.assertEqual(ledgerbase.sequence, 4294967295) self.assertEqual(coinbase.sequence, 4294967295)
self.assertTrue(ledgerbase.is_ledgerbase) self.assertTrue(coinbase.is_coinbase)
self.assertEqual(ledgerbase.script, None) self.assertEqual(coinbase.script, None)
self.assertEqual( self.assertEqual(
ledgerbase.ledgerbase[8:], coinbase.coinbase[8:],
b'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks' b'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'
) )
@ -97,7 +93,7 @@ class TestTransactionSerialization(unittest.TestCase):
tx._reset() tx._reset()
self.assertEqual(tx.raw, raw) self.assertEqual(tx.raw, raw)
def test_ledgerbase_transaction(self): def test_coinbase_transaction(self):
raw = unhexlify( raw = unhexlify(
'01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4e03' '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4e03'
'1f5a070473319e592f4254432e434f4d2f4e59412ffabe6d6dcceb2a9d0444c51cabc4ee97a1a000036ca0' '1f5a070473319e592f4254432e434f4d2f4e59412ffabe6d6dcceb2a9d0444c51cabc4ee97a1a000036ca0'
@ -105,20 +101,20 @@ class TestTransactionSerialization(unittest.TestCase):
'0000000017a914e083685a1097ce1ea9e91987ab9e94eae33d8a13870000000000000000266a24aa21a9ed' '0000000017a914e083685a1097ce1ea9e91987ab9e94eae33d8a13870000000000000000266a24aa21a9ed'
'e6c99265a6b9e1d36c962fda0516b35709c49dc3b8176fa7e5d5f1f6197884b400000000' 'e6c99265a6b9e1d36c962fda0516b35709c49dc3b8176fa7e5d5f1f6197884b400000000'
) )
tx = BaseTransaction(raw) tx = ledger_class.transaction_class(raw)
self.assertEqual(tx.version, 1) self.assertEqual(tx.version, 1)
self.assertEqual(tx.locktime, 0) self.assertEqual(tx.locktime, 0)
self.assertEqual(len(tx.inputs), 1) self.assertEqual(len(tx.inputs), 1)
self.assertEqual(len(tx.outputs), 2) self.assertEqual(len(tx.outputs), 2)
ledgerbase = tx.inputs[0] coinbase = tx.inputs[0]
self.assertEqual(ledgerbase.output_txid, NULL_HASH) self.assertEqual(coinbase.output_txhash, NULL_HASH)
self.assertEqual(ledgerbase.output_index, 0xFFFFFFFF) self.assertEqual(coinbase.output_index, 0xFFFFFFFF)
self.assertEqual(ledgerbase.sequence, 4294967295) self.assertEqual(coinbase.sequence, 4294967295)
self.assertTrue(ledgerbase.is_ledgerbase) self.assertTrue(coinbase.is_coinbase)
self.assertEqual(ledgerbase.script, None) self.assertEqual(coinbase.script, None)
self.assertEqual( self.assertEqual(
ledgerbase.ledgerbase[9:22], coinbase.coinbase[9:22],
b'/BTC.COM/NYA/' b'/BTC.COM/NYA/'
) )
@ -148,26 +144,32 @@ class TestTransactionSerialization(unittest.TestCase):
class TestTransactionSigning(unittest.TestCase): class TestTransactionSigning(unittest.TestCase):
def setUp(self):
self.ledger = ledger_class(db=':memory:')
return self.ledger.db.start()
@defer.inlineCallbacks
def test_sign(self): def test_sign(self):
ledger = WalletManager().get_or_create_ledger(BTC.get_id()) account = self.ledger.account_class.from_seed(
ledger = BTC(ledger) self.ledger,
wallet = Wallet('Main', [ledger], [Account.from_seed( u"carbon smart garage balance margin twelve chest sword toast envelope bottom stomach ab"
ledger, u'carbon smart garage balance margin twelve chest sword toast envelope bottom stom' u"sent", u"torba"
u'ach absent', u'torba' )
)])
account = wallet.default_account
address1 = account.receiving_keys.generate_next_address() yield account.ensure_address_gap()
address2 = account.receiving_keys.generate_next_address() address1 = (yield account.receiving.get_addresses())[0]
pubkey_hash1 = account.ledger.address_to_hash160(address1) address2 = (yield account.receiving.get_addresses())[0]
pubkey_hash2 = account.ledger.address_to_hash160(address2) pubkey_hash1 = self.ledger.address_to_hash160(address1)
pubkey_hash2 = self.ledger.address_to_hash160(address2)
tx = Transaction() \ tx = ledger_class.transaction_class() \
.add_inputs([Input.spend(get_output(2*COIN, pubkey_hash1))]) \ .add_inputs([ledger_class.transaction_class.input_class.spend(get_output(2*COIN, pubkey_hash1))]) \
.add_outputs([Output.pay_pubkey_hash(int(1.9*COIN), pubkey_hash2)]) \ .add_outputs([ledger_class.transaction_class.output_class.pay_pubkey_hash(int(1.9*COIN), pubkey_hash2)]) \
.sign(account)
yield tx.sign([account])
self.assertEqual( self.assertEqual(
hexlify(tx.inputs[0].script.values['signature']), hexlify(tx.inputs[0].script.values['signature']),
b'304402203d463519290d06891e461ea5256c56097ccdad53379b1bb4e51ec5abc6e9fd02022034ed15b9d7c678716c4aa7c0fd26c688e8f9db8075838f2839ab55d551b62c0a01' b'3044022064cd6b95c9e0084253c10dd56bcec2bfd816c29aad05fbea490511d79540462b02201aa9d6f73'
b'48bb0c76b28d1ad87cf4ffd51cf4de0b299af8bf0ecad70e3369ef201'
) )

View file

@ -1,75 +1,50 @@
from twisted.trial import unittest from twisted.trial import unittest
from torba.coin.bitcoinsegwit import BTC from torba.coin.bitcoinsegwit import MainNetLedger as BTCLedger
from torba.coin.bitcoincash import MainNetLedger as BCHLedger
from torba.manager import WalletManager from torba.manager import WalletManager
from torba.wallet import Account, Wallet, WalletStorage from torba.wallet import Wallet, WalletStorage
from .ftc import FTC
class TestWalletCreation(unittest.TestCase): class TestWalletCreation(unittest.TestCase):
def setUp(self): def setUp(self):
self.manager = WalletManager() self.manager = WalletManager()
self.btc_ledger = self.manager.get_or_create_ledger(BTC.get_id()) config = {'wallet_path': '/tmp/wallet'}
self.ftc_ledger = self.manager.get_or_create_ledger(FTC.get_id()) self.btc_ledger = self.manager.get_or_create_ledger(BTCLedger.get_id(), config)
self.bch_ledger = self.manager.get_or_create_ledger(BCHLedger.get_id(), config)
def test_create_wallet_and_accounts(self): def test_create_wallet_and_accounts(self):
wallet = Wallet() wallet = Wallet()
self.assertEqual(wallet.name, 'Wallet') self.assertEqual(wallet.name, 'Wallet')
self.assertEqual(wallet.coins, [])
self.assertEqual(wallet.accounts, []) self.assertEqual(wallet.accounts, [])
account1 = wallet.generate_account(self.btc_ledger) account1 = wallet.generate_account(self.btc_ledger)
account2 = wallet.generate_account(self.btc_ledger) wallet.generate_account(self.btc_ledger)
account3 = wallet.generate_account(self.ftc_ledger) wallet.generate_account(self.bch_ledger)
self.assertEqual(wallet.default_account, account1) self.assertEqual(wallet.default_account, account1)
self.assertEqual(len(wallet.coins), 2)
self.assertEqual(len(wallet.accounts), 3) self.assertEqual(len(wallet.accounts), 3)
self.assertIsInstance(wallet.coins[0], BTC)
self.assertIsInstance(wallet.coins[1], FTC)
self.assertEqual(len(account1.receiving_keys.addresses), 0)
self.assertEqual(len(account1.change_keys.addresses), 0)
self.assertEqual(len(account2.receiving_keys.addresses), 0)
self.assertEqual(len(account2.change_keys.addresses), 0)
self.assertEqual(len(account3.receiving_keys.addresses), 0)
self.assertEqual(len(account3.change_keys.addresses), 0)
account1.ensure_enough_addresses()
account2.ensure_enough_addresses()
account3.ensure_enough_addresses()
self.assertEqual(len(account1.receiving_keys.addresses), 20)
self.assertEqual(len(account1.change_keys.addresses), 6)
self.assertEqual(len(account2.receiving_keys.addresses), 20)
self.assertEqual(len(account2.change_keys.addresses), 6)
self.assertEqual(len(account3.receiving_keys.addresses), 20)
self.assertEqual(len(account3.change_keys.addresses), 6)
def test_load_and_save_wallet(self): def test_load_and_save_wallet(self):
wallet_dict = { wallet_dict = {
'name': 'Main Wallet', 'name': 'Main Wallet',
'accounts': [ 'accounts': [
{ {
'coin': 'btc_mainnet', 'ledger': 'btc_mainnet',
'seed': 'seed':
"carbon smart garage balance margin twelve chest sword toast envelope bottom stomac" "carbon smart garage balance margin twelve chest sword toast envelope bottom stomac"
"h absent", "h absent",
'encrypted': False, 'encrypted': False,
'private_key': 'private_key':
'xprv9s21ZrQH143K2dyhK7SevfRG72bYDRNv25yKPWWm6dqApNxm1Zb1m5gGcBWYfbsPjTr2v5joit8Af2Zp5P' b'xprv9s21ZrQH143K2dyhK7SevfRG72bYDRNv25yKPWWm6dqApNxm1Zb1m5gGcBWYfbsPjTr2v5joit8Af2Zp5P'
'6yz3jMbycrLrRMpeAJxR8qDg8', b'6yz3jMbycrLrRMpeAJxR8qDg8',
'public_key': 'public_key':
'xpub661MyMwAqRbcF84AR8yfHoMzf4S2ct6mPJtvBtvNeyN9hBHuZ6uGJszkTSn5fQUCdz3XU17eBzFeAUwV6f' b'xpub661MyMwAqRbcF84AR8yfHoMzf4S2ct6mPJtvBtvNeyN9hBHuZ6uGJszkTSn5fQUCdz3XU17eBzFeAUwV6f'
'iW44g14WF52fYC5J483wqQ5ZP', b'iW44g14WF52fYC5J483wqQ5ZP',
'receiving_gap': 10, 'receiving_gap': 10,
'receiving_keys': [ 'receiving_maximum_use_per_address': 2,
'0222345947a59dca4a3363ffa81ac87dd907d2b2feff57383eaeddbab266ca5f2d',
'03fdc9826d5d00a484188cba8eb7dba5877c0323acb77905b7bcbbab35d94be9f6'
],
'change_gap': 10, 'change_gap': 10,
'change_keys': [ 'change_maximum_use_per_address': 2,
'038836be4147836ed6b4df6a89e0d9f1b1c11cec529b7ff5407de57f2e5b032c83'
]
} }
] ]
} }
@ -77,21 +52,8 @@ class TestWalletCreation(unittest.TestCase):
storage = WalletStorage(default=wallet_dict) storage = WalletStorage(default=wallet_dict)
wallet = Wallet.from_storage(storage, self.manager) wallet = Wallet.from_storage(storage, self.manager)
self.assertEqual(wallet.name, 'Main Wallet') self.assertEqual(wallet.name, 'Main Wallet')
self.assertEqual(len(wallet.coins), 1)
self.assertIsInstance(wallet.coins[0], BTC)
self.assertEqual(len(wallet.accounts), 1) self.assertEqual(len(wallet.accounts), 1)
account = wallet.default_account account = wallet.default_account
self.assertIsInstance(account, Account) self.assertIsInstance(account, BTCLedger.account_class)
self.maxDiff = None
self.assertEqual(len(account.receiving_keys.addresses), 2)
self.assertEqual(
account.receiving_keys.addresses[0],
b'1PmX9T3sCiDysNtWszJa44SkKcpGc2NaXP'
)
self.assertEqual(len(account.change_keys.addresses), 1)
self.assertEqual(
account.change_keys.addresses[0],
b'1PUbu1D1f3c244JPRSJKBCxRqui5NT6geR'
)
wallet_dict['coins'] = {'btc_mainnet': {'fee_per_byte': 50}}
self.assertDictEqual(wallet_dict, wallet.to_dict()) self.assertDictEqual(wallet_dict, wallet.to_dict())

View file

@ -59,9 +59,9 @@ class KeyChain:
def get_or_create_usable_address(self): def get_or_create_usable_address(self):
addresses = yield self.get_usable_addresses(1) addresses = yield self.get_usable_addresses(1)
if addresses: if addresses:
return addresses[0] defer.returnValue(addresses[0])
addresses = yield self.ensure_address_gap() addresses = yield self.ensure_address_gap()
return addresses[0] defer.returnValue(addresses[0])
class BaseAccount: class BaseAccount:
@ -83,7 +83,7 @@ class BaseAccount:
KeyChain(self, public_key, 0, receiving_gap, receiving_maximum_use_per_address), KeyChain(self, public_key, 0, receiving_gap, receiving_maximum_use_per_address),
KeyChain(self, public_key, 1, change_gap, change_maximum_use_per_address) KeyChain(self, public_key, 1, change_gap, change_maximum_use_per_address)
) )
ledger.account_created(self) ledger.add_account(self)
@classmethod @classmethod
def generate(cls, ledger, password): # type: (torba.baseledger.BaseLedger, str) -> BaseAccount def generate(cls, ledger, password): # type: (torba.baseledger.BaseLedger, str) -> BaseAccount

View file

@ -1,5 +1,6 @@
import logging import logging
from typing import List, Union from typing import List, Union
from operator import itemgetter
import sqlite3 import sqlite3
from twisted.internet import defer from twisted.internet import defer
@ -97,11 +98,10 @@ class BaseDatabase(SQLiteMixin):
CREATE_TX_TABLE = """ CREATE_TX_TABLE = """
create table if not exists tx ( create table if not exists tx (
txid blob primary key, txhash blob primary key,
raw blob not null, raw blob not null,
height integer not null, height integer not null,
is_confirmed boolean not null, is_verified boolean not null default false
is_verified boolean not null
); );
""" """
@ -113,14 +113,14 @@ class BaseDatabase(SQLiteMixin):
position integer not null, position integer not null,
pubkey blob not null, pubkey blob not null,
history text, history text,
used_times integer default 0 used_times integer not null default 0
); );
""" """
CREATE_TXO_TABLE = """ CREATE_TXO_TABLE = """
create table if not exists txo ( create table if not exists txo (
txoid integer primary key, txoid integer primary key,
txid blob references tx, txhash blob references tx,
address blob references pubkey_address, address blob references pubkey_address,
position integer not null, position integer not null,
amount integer not null, amount integer not null,
@ -130,7 +130,7 @@ class BaseDatabase(SQLiteMixin):
CREATE_TXI_TABLE = """ CREATE_TXI_TABLE = """
create table if not exists txi ( create table if not exists txi (
txid blob references tx, txhash blob references tx,
address blob references pubkey_address, address blob references pubkey_address,
txoid integer references txo txoid integer references txo
); );
@ -143,20 +143,33 @@ class BaseDatabase(SQLiteMixin):
CREATE_TXI_TABLE CREATE_TXI_TABLE
) )
def add_transaction(self, address, hash, tx, height, is_confirmed, is_verified): def add_transaction(self, address, hash, tx, height, is_verified):
def _steps(t): def _steps(t):
if not t.execute("SELECT 1 FROM tx WHERE txid=?", (sqlite3.Binary(tx.id),)).fetchone(): current_height = t.execute("SELECT height FROM tx WHERE txhash=?", (sqlite3.Binary(tx.hash),)).fetchone()
if current_height is None:
t.execute(*self._insert_sql('tx', { t.execute(*self._insert_sql('tx', {
'txid': sqlite3.Binary(tx.id), 'txhash': sqlite3.Binary(tx.hash),
'raw': sqlite3.Binary(tx.raw), 'raw': sqlite3.Binary(tx.raw),
'height': height, 'height': height,
'is_confirmed': is_confirmed,
'is_verified': is_verified 'is_verified': is_verified
})) }))
elif current_height[0] != height:
t.execute("UPDATE tx SET height = :height WHERE txhash = :txhash", {
'txhash': sqlite3.Binary(tx.hash),
'height': height,
})
existing_txos = list(map(itemgetter(0), t.execute(
"SELECT position FROM txo WHERE txhash = ?",
(sqlite3.Binary(tx.hash),)
).fetchall()))
for txo in tx.outputs: for txo in tx.outputs:
if txo.index in existing_txos:
continue
if txo.script.is_pay_pubkey_hash and txo.script.values['pubkey_hash'] == hash: if txo.script.is_pay_pubkey_hash and txo.script.values['pubkey_hash'] == hash:
t.execute(*self._insert_sql("txo", { t.execute(*self._insert_sql("txo", {
'txid': sqlite3.Binary(tx.id), 'txhash': sqlite3.Binary(tx.hash),
'address': sqlite3.Binary(address), 'address': sqlite3.Binary(address),
'position': txo.index, 'position': txo.index,
'amount': txo.amount, 'amount': txo.amount,
@ -166,24 +179,33 @@ class BaseDatabase(SQLiteMixin):
# TODO: implement script hash payments # TODO: implement script hash payments
print('Database.add_transaction pay script hash is not implemented!') print('Database.add_transaction pay script hash is not implemented!')
existing_txis = [txi[0] for txi in t.execute(
"SELECT txoid FROM txi WHERE txhash = ? AND address = ?",
(sqlite3.Binary(tx.hash), sqlite3.Binary(address))).fetchall()]
for txi in tx.inputs: for txi in tx.inputs:
txoid = t.execute( txoid = t.execute(
"SELECT txoid, address FROM txo WHERE txid = ? AND position = ?", "SELECT txoid FROM txo WHERE txhash = ? AND position = ?",
(sqlite3.Binary(txi.output_txid), txi.output_index) (sqlite3.Binary(txi.output_txhash), txi.output_index)
).fetchone() ).fetchone()
if txoid: if txoid is not None and txoid[0] not in existing_txis:
t.execute(*self._insert_sql("txi", { t.execute(*self._insert_sql("txi", {
'txid': sqlite3.Binary(tx.id), 'txhash': sqlite3.Binary(tx.hash),
'address': sqlite3.Binary(address), 'address': sqlite3.Binary(address),
'txoid': txoid, 'txoid': txoid[0],
})) }))
return self.db.runInteraction(_steps) return self.db.runInteraction(_steps)
@defer.inlineCallbacks @defer.inlineCallbacks
def get_balance_for_account(self, account): def get_balance_for_account(self, account):
result = yield self.db.runQuery( result = yield self.db.runQuery(
"SELECT SUM(amount) FROM txo NATURAL JOIN pubkey_address WHERE account=:account AND " """
"txoid NOT IN (SELECT txoid FROM txi)", SELECT SUM(amount) FROM txo
JOIN pubkey_address ON pubkey_address.address=txo.address
WHERE account=:account AND
txoid NOT IN (SELECT txoid FROM txi)
""",
{'account': sqlite3.Binary(account.public_key.address)} {'account': sqlite3.Binary(account.public_key.address)}
) )
if result: if result:
@ -195,8 +217,8 @@ class BaseDatabase(SQLiteMixin):
def get_utxos(self, account, output_class): def get_utxos(self, account, output_class):
utxos = yield self.db.runQuery( utxos = yield self.db.runQuery(
""" """
SELECT amount, script, txid, position SELECT amount, script, txhash, txo.position
FROM txo NATURAL JOIN pubkey_address FROM txo JOIN pubkey_address ON pubkey_address.address=txo.address
WHERE account=:account AND txoid NOT IN (SELECT txoid FROM txi) WHERE account=:account AND txoid NOT IN (SELECT txoid FROM txi)
""", """,
{'account': sqlite3.Binary(account.public_key.address)} {'account': sqlite3.Binary(account.public_key.address)}

View file

@ -76,9 +76,6 @@ class BaseLedger(six.with_metaclass(LedgerRegistry)):
raw_address = self.pubkey_address_prefix + h160 raw_address = self.pubkey_address_prefix + h160
return Base58.encode(bytearray(raw_address + double_sha256(raw_address)[0:4])) return Base58.encode(bytearray(raw_address + double_sha256(raw_address)[0:4]))
def account_created(self, account): # type: (baseaccount.BaseAccount) -> None
self.accounts.add(account)
@staticmethod @staticmethod
def address_to_hash160(address): def address_to_hash160(address):
bytes = Base58.decode(address) bytes = Base58.decode(address)
@ -108,10 +105,16 @@ class BaseLedger(six.with_metaclass(LedgerRegistry)):
def add_transaction(self, address, transaction, height): def add_transaction(self, address, transaction, height):
# type: (bytes, basetransaction.BaseTransaction, int) -> None # type: (bytes, basetransaction.BaseTransaction, int) -> None
yield self.db.add_transaction( yield self.db.add_transaction(
address, self.address_to_hash160(address), transaction, height, False, False address, self.address_to_hash160(address), transaction, height, False
) )
self._on_transaction_controller.add(transaction) self._on_transaction_controller.add(transaction)
@defer.inlineCallbacks
def add_account(self, account): # type: (baseaccount.BaseAccount) -> None
self.accounts.add(account)
if self.network.is_connected:
yield self.update_account(account)
@defer.inlineCallbacks @defer.inlineCallbacks
def get_private_key_for_address(self, address): def get_private_key_for_address(self, address):
match = yield self.db.get_address(address) match = yield self.db.get_address(address)
@ -123,19 +126,6 @@ class BaseLedger(six.with_metaclass(LedgerRegistry)):
def get_unspent_outputs(self, account): def get_unspent_outputs(self, account):
return self.db.get_utxos(account, self.transaction_class.output_class) return self.db.get_utxos(account, self.transaction_class.output_class)
# def get_unspent_outputs(self, account):
# inputs, outputs, utxos = set(), set(), set()
# for address in self.addresses.values():
# for tx in address:
# for txi in tx.inputs:
# inputs.add((hexlify(txi.output_txid), txi.output_index))
# for txo in tx.outputs:
# if txo.script.is_pay_pubkey_hash and txo.script.values['pubkey_hash'] == address.pubkey_hash:
# outputs.add((txo, txo.transaction.id, txo.index))
# for output in outputs:
# if output[1:] not in inputs:
# yield output[0]
@defer.inlineCallbacks @defer.inlineCallbacks
def get_local_status(self, address): def get_local_status(self, address):
address_details = yield self.db.get_address(address) address_details = yield self.db.get_address(address)
@ -146,6 +136,8 @@ class BaseLedger(six.with_metaclass(LedgerRegistry)):
def get_local_history(self, address): def get_local_history(self, address):
address_details = yield self.db.get_address(address) address_details = yield self.db.get_address(address)
history = address_details['history'] or b'' history = address_details['history'] or b''
if six.PY2:
history = str(history)
parts = history.split(b':')[:-1] parts = history.split(b':')[:-1]
defer.returnValue(list(zip(parts[0::2], map(int, parts[1::2])))) defer.returnValue(list(zip(parts[0::2], map(int, parts[1::2]))))

View file

@ -95,7 +95,7 @@ class StratumClientProtocol(LineOnlyReceiver):
try: try:
d = self.lookup_table.pop(message['id']) d = self.lookup_table.pop(message['id'])
if message.get('error'): if message.get('error'):
d.errback(RuntimeError(*message['error'])) d.errback(RuntimeError(message['error']))
else: else:
d.callback(message.get('result')) d.callback(message.get('result'))
except KeyError: except KeyError:

View file

@ -5,12 +5,13 @@ from binascii import hexlify
from twisted.internet import defer from twisted.internet import defer
import torba.baseaccount
import torba.baseledger
from torba.basescript import BaseInputScript, BaseOutputScript from torba.basescript import BaseInputScript, BaseOutputScript
from torba.coinselection import CoinSelector from torba.coinselection import CoinSelector
from torba.constants import COIN from torba.constants import COIN
from torba.bcd_data_stream import BCDataStream from torba.bcd_data_stream import BCDataStream
from torba.hash import sha256 from torba.hash import sha256
from torba.baseaccount import BaseAccount
from torba.util import ReadOnlyList from torba.util import ReadOnlyList
@ -22,16 +23,16 @@ NULL_HASH = b'\x00'*32
class InputOutput(object): class InputOutput(object):
def __init__(self, txid, index=None): def __init__(self, txhash, index=None):
self._txid = txid # type: bytes self._txhash = txhash # type: bytes
self.transaction = None # type: BaseTransaction self.transaction = None # type: BaseTransaction
self.index = index # type: int self.index = index # type: int
@property @property
def txid(self): def txhash(self):
if self._txid is None: if self._txhash is None:
self._txid = self.transaction.id self._txhash = self.transaction.hash
return self._txid return self._txhash
@property @property
def size(self): def size(self):
@ -51,26 +52,20 @@ class BaseInput(InputOutput):
NULL_SIGNATURE = b'\x00'*72 NULL_SIGNATURE = b'\x00'*72
NULL_PUBLIC_KEY = b'\x00'*33 NULL_PUBLIC_KEY = b'\x00'*33
def __init__(self, output_or_txid_index, script, sequence=0xFFFFFFFF, txid=None): def __init__(self, output_or_txhash_index, script, sequence=0xFFFFFFFF, txhash=None):
super(BaseInput, self).__init__(txid) super(BaseInput, self).__init__(txhash)
if isinstance(output_or_txid_index, BaseOutput): if isinstance(output_or_txhash_index, BaseOutput):
self.output = output_or_txid_index # type: BaseOutput self.output = output_or_txhash_index # type: BaseOutput
self.output_txid = self.output.txid self.output_txhash = self.output.txhash
self.output_index = self.output.index self.output_index = self.output.index
else: else:
self.output = None # type: BaseOutput self.output = None # type: BaseOutput
self.output_txid, self.output_index = output_or_txid_index self.output_txhash, self.output_index = output_or_txhash_index
self.sequence = sequence self.sequence = sequence
self.is_coinbase = self.output_txid == NULL_HASH self.is_coinbase = self.output_txhash == NULL_HASH
self.coinbase = script if self.is_coinbase else None self.coinbase = script if self.is_coinbase else None
self.script = script if not self.is_coinbase else None # type: BaseInputScript self.script = script if not self.is_coinbase else None # type: BaseInputScript
def link_output(self, output):
assert self.output is None
assert self.output_txid == output.transaction.id
assert self.output_index == output.index
self.output = output
@classmethod @classmethod
def spend(cls, output): def spend(cls, output):
""" Create an input to spend the output.""" """ Create an input to spend the output."""
@ -87,18 +82,18 @@ class BaseInput(InputOutput):
@classmethod @classmethod
def deserialize_from(cls, stream): def deserialize_from(cls, stream):
txid = stream.read(32) txhash = stream.read(32)
index = stream.read_uint32() index = stream.read_uint32()
script = stream.read_string() script = stream.read_string()
sequence = stream.read_uint32() sequence = stream.read_uint32()
return cls( return cls(
(txid, index), (txhash, index),
cls.script_class(script) if not txid == NULL_HASH else script, cls.script_class(script) if not txhash == NULL_HASH else script,
sequence sequence
) )
def serialize_to(self, stream, alternate_script=None): def serialize_to(self, stream, alternate_script=None):
stream.write(self.output_txid) stream.write(self.output_txhash)
stream.write_uint32(self.output_index) stream.write_uint32(self.output_index)
if alternate_script is not None: if alternate_script is not None:
stream.write_string(alternate_script) stream.write_string(alternate_script)
@ -114,7 +109,7 @@ class BaseOutputEffectiveAmountEstimator(object):
__slots__ = 'coin', 'txi', 'txo', 'fee', 'effective_amount' __slots__ = 'coin', 'txi', 'txo', 'fee', 'effective_amount'
def __init__(self, ledger, txo): # type: (BaseLedger, BaseOutput) -> None def __init__(self, ledger, txo): # type: (torba.baseledger.BaseLedger, BaseOutput) -> None
self.txo = txo self.txo = txo
self.txi = ledger.transaction_class.input_class.spend(txo) self.txi = ledger.transaction_class.input_class.spend(txo)
self.fee = ledger.get_input_output_fee(self.txi) self.fee = ledger.get_input_output_fee(self.txi)
@ -129,8 +124,8 @@ class BaseOutput(InputOutput):
script_class = BaseOutputScript script_class = BaseOutputScript
estimator_class = BaseOutputEffectiveAmountEstimator estimator_class = BaseOutputEffectiveAmountEstimator
def __init__(self, amount, script, txid=None, index=None): def __init__(self, amount, script, txhash=None, index=None):
super(BaseOutput, self).__init__(txid, index) super(BaseOutput, self).__init__(txhash, index)
self.amount = amount # type: int self.amount = amount # type: int
self.script = script # type: BaseOutputScript self.script = script # type: BaseOutputScript
@ -259,7 +254,7 @@ class BaseTransaction:
for txout in self._outputs: for txout in self._outputs:
txout.serialize_to(stream) txout.serialize_to(stream)
stream.write_uint32(self.locktime) stream.write_uint32(self.locktime)
stream.write_uint32(1) # signature hash type: SIGHASH_ALL stream.write_uint32(self.signature_hash_type(1)) # signature hash type: SIGHASH_ALL
return stream.get_bytes() return stream.get_bytes()
def _deserialize(self): def _deserialize(self):
@ -279,7 +274,7 @@ class BaseTransaction:
@classmethod @classmethod
@defer.inlineCallbacks @defer.inlineCallbacks
def get_effective_amount_estimators(cls, funding_accounts): def get_effective_amount_estimators(cls, funding_accounts):
# type: (Iterable[BaseAccount]) -> Generator[BaseOutputEffectiveAmountEstimator] # type: (Iterable[torba.baseaccount.BaseAccount]) -> Generator[BaseOutputEffectiveAmountEstimator]
estimators = [] estimators = []
for account in funding_accounts: for account in funding_accounts:
utxos = yield account.ledger.get_unspent_outputs(account) utxos = yield account.ledger.get_unspent_outputs(account)
@ -289,7 +284,7 @@ class BaseTransaction:
@classmethod @classmethod
def ensure_all_have_same_ledger(cls, funding_accounts, change_account=None): def ensure_all_have_same_ledger(cls, funding_accounts, change_account=None):
# type: (Iterable[BaseAccount], BaseAccount) -> baseledger.BaseLedger # type: (Iterable[torba.baseaccount.BaseAccount], torba.baseaccount.BaseAccount) -> torba.baseledger.BaseLedger
ledger = None ledger = None
for account in funding_accounts: for account in funding_accounts:
if ledger is None: if ledger is None:
@ -305,12 +300,12 @@ class BaseTransaction:
@classmethod @classmethod
@defer.inlineCallbacks @defer.inlineCallbacks
def pay(cls, outputs, funding_accounts, change_account): def pay(cls, outputs, funding_accounts, change_account):
# type: (List[BaseOutput], List[BaseAccount], BaseAccount) -> BaseTransaction # type: (List[BaseOutput], List[torba.baseaccount.BaseAccount], torba.baseaccount.BaseAccount) -> BaseTransaction
""" Efficiently spend utxos from funding_accounts to cover the new outputs. """ """ Efficiently spend utxos from funding_accounts to cover the new outputs. """
tx = cls().add_outputs(outputs) tx = cls().add_outputs(outputs)
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account) ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
amount = ledger.get_transaction_base_fee(tx) amount = tx.output_sum + ledger.get_transaction_base_fee(tx)
txos = yield cls.get_effective_amount_estimators(funding_accounts) txos = yield cls.get_effective_amount_estimators(funding_accounts)
selector = CoinSelector( selector = CoinSelector(
txos, amount, txos, amount,
@ -325,34 +320,38 @@ class BaseTransaction:
spent_sum = sum(s.effective_amount for s in spendables) spent_sum = sum(s.effective_amount for s in spendables)
if spent_sum > amount: if spent_sum > amount:
change_address = change_account.change.get_or_create_usable_address() change_address = yield change_account.change.get_or_create_usable_address()
change_hash160 = change_account.ledger.address_to_hash160(change_address) change_hash160 = change_account.ledger.address_to_hash160(change_address)
change_amount = spent_sum - amount change_amount = spent_sum - amount
tx.add_outputs([cls.output_class.pay_pubkey_hash(change_amount, change_hash160)]) tx.add_outputs([cls.output_class.pay_pubkey_hash(change_amount, change_hash160)])
tx.add_inputs([s.txi for s in spendables]) tx.add_inputs([s.txi for s in spendables])
tx.sign(funding_accounts) yield tx.sign(funding_accounts)
defer.returnValue(tx) defer.returnValue(tx)
@classmethod @classmethod
def liquidate(cls, assets, funding_accounts, change_account): def liquidate(cls, assets, funding_accounts, change_account):
""" Spend assets (utxos) supplementing with funding_accounts if fee is higher than asset value. """ """ Spend assets (utxos) supplementing with funding_accounts if fee is higher than asset value. """
def sign(self, funding_accounts): # type: (Iterable[BaseAccount]) -> BaseTransaction def signature_hash_type(self, hash_type):
return hash_type
@defer.inlineCallbacks
def sign(self, funding_accounts): # type: (Iterable[torba.baseaccount.BaseAccount]) -> BaseTransaction
ledger = self.ensure_all_have_same_ledger(funding_accounts) ledger = self.ensure_all_have_same_ledger(funding_accounts)
for i, txi in enumerate(self._inputs): for i, txi in enumerate(self._inputs):
txo_script = txi.output.script txo_script = txi.output.script
if txo_script.is_pay_pubkey_hash: if txo_script.is_pay_pubkey_hash:
address = ledger.hash160_to_address(txo_script.values['pubkey_hash']) address = ledger.hash160_to_address(txo_script.values['pubkey_hash'])
private_key = ledger.get_private_key_for_address(address) private_key = yield ledger.get_private_key_for_address(address)
tx = self._serialize_for_signature(i) tx = self._serialize_for_signature(i)
txi.script.values['signature'] = private_key.sign(tx)+six.int2byte(1) txi.script.values['signature'] = \
private_key.sign(tx) + six.int2byte(self.signature_hash_type(1))
txi.script.values['pubkey'] = private_key.public_key.pubkey_bytes txi.script.values['pubkey'] = private_key.public_key.pubkey_bytes
txi.script.generate() txi.script.generate()
else: else:
raise NotImplementedError("Don't know how to spend this output.") raise NotImplementedError("Don't know how to spend this output.")
self._reset() self._reset()
return self
def sort(self): def sort(self):
# See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki # See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki
@ -361,8 +360,8 @@ class BaseTransaction:
@property @property
def input_sum(self): def input_sum(self):
return sum(i.amount for i in self._inputs) return sum(i.amount for i in self.inputs)
@property @property
def output_sum(self): def output_sum(self):
return sum(o.amount for o in self._outputs) return sum(o.amount for o in self.outputs)

View file

@ -4,63 +4,26 @@ __node_bin__ = 'bitcoin-abc-0.17.2/bin'
__node_url__ = ( __node_url__ = (
'https://download.bitcoinabc.org/0.17.2/linux/bitcoin-abc-0.17.2-x86_64-linux-gnu.tar.gz' 'https://download.bitcoinabc.org/0.17.2/linux/bitcoin-abc-0.17.2-x86_64-linux-gnu.tar.gz'
) )
__electrumx__ = 'electrumx.lib.coins.BitcoinCashRegtest'
from six import int2byte from six import int2byte
from binascii import unhexlify from binascii import unhexlify
from torba.baseledger import BaseLedger, BaseHeaders from torba.baseledger import BaseLedger
from torba.basenetwork import BaseNetwork from torba.baseheader import BaseHeaders
from torba.basescript import BaseInputScript, BaseOutputScript from torba.basetransaction import BaseTransaction
from torba.basetransaction import BaseTransaction, BaseInput, BaseOutput
from torba.basedatabase import BaseSQLiteWalletStorage
from torba.manager import BaseWalletManager
class WalletManager(BaseWalletManager):
pass
class Input(BaseInput):
script_class = BaseInputScript
class Output(BaseOutput):
script_class = BaseOutputScript
class Transaction(BaseTransaction): class Transaction(BaseTransaction):
input_class = Input
output_class = Output def signature_hash_type(self, hash_type):
return hash_type | 0x40
class BitcoinCashLedger(BaseLedger): class MainNetLedger(BaseLedger):
network_class = BaseNetwork
headers_class = BaseHeaders
database_class = BaseSQLiteWalletStorage
class MainNetLedger(BitcoinCashLedger):
pass
class UnverifiedHeaders(BaseHeaders):
verify_bits_to_target = False
class RegTestLedger(BitcoinCashLedger):
headers_class = UnverifiedHeaders
max_target = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
genesis_hash = '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206'
genesis_bits = 0x207fffff
target_timespan = 1
verify_bits_to_target = False
class BitcoinCash(BaseCoin):
name = 'BitcoinCash' name = 'BitcoinCash'
symbol = 'BCH' symbol = 'BCH'
network = 'mainnet' network_name = 'mainnet'
ledger_class = MainNetLedger
transaction_class = Transaction transaction_class = Transaction
pubkey_address_prefix = int2byte(0x00) pubkey_address_prefix = int2byte(0x00)
@ -70,15 +33,21 @@ class BitcoinCash(BaseCoin):
default_fee_per_byte = 50 default_fee_per_byte = 50
def __init__(self, ledger, fee_per_byte=default_fee_per_byte):
super(BitcoinCash, self).__init__(ledger, fee_per_byte) class UnverifiedHeaders(BaseHeaders):
verify_bits_to_target = False
class BitcoinCashRegtest(BitcoinCash): class RegTestLedger(MainNetLedger):
network = 'regtest' headers_class = UnverifiedHeaders
ledger_class = RegTestLedger network_name = 'regtest'
pubkey_address_prefix = int2byte(111) pubkey_address_prefix = int2byte(111)
script_address_prefix = int2byte(196) script_address_prefix = int2byte(196)
extended_public_key_prefix = unhexlify('043587cf') extended_public_key_prefix = unhexlify('043587cf')
extended_private_key_prefix = unhexlify('04358394') extended_private_key_prefix = unhexlify('04358394')
max_target = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
genesis_hash = '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206'
genesis_bits = 0x207fffff
target_timespan = 1

View file

@ -4,6 +4,7 @@ __node_bin__ = 'bitcoin-0.16.0/bin'
__node_url__ = ( __node_url__ = (
'https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-x86_64-linux-gnu.tar.gz' 'https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-x86_64-linux-gnu.tar.gz'
) )
__electrumx__ = 'electrumx.lib.coins.BitcoinSegwitRegtest'
from six import int2byte from six import int2byte
from binascii import unhexlify from binascii import unhexlify

View file

@ -120,6 +120,9 @@ class Base58(object):
@classmethod @classmethod
def decode(cls, txt): def decode(cls, txt):
""" Decodes txt into a big-endian bytearray. """ """ Decodes txt into a big-endian bytearray. """
if six.PY2 and isinstance(txt, buffer):
txt = str(txt)
if isinstance(txt, six.binary_type): if isinstance(txt, six.binary_type):
txt = txt.decode() txt = txt.decode()

View file

@ -1,17 +1,10 @@
import stat import stat
import json import json
import os import os
from typing import List, Dict from typing import List
from torba.baseaccount import BaseAccount import torba.baseaccount
from torba.baseledger import LedgerRegistry, BaseLedger import torba.baseledger
def inflate_ledger(manager, ledger_id, ledger_dict):
# type: ('WalletManager', str, Dict) -> BaseLedger
ledger_class = LedgerRegistry.get_ledger_class(ledger_id)
ledger = manager.get_or_create_ledger(ledger_id)
return ledger_class(ledger, **ledger_dict)
class Wallet: class Wallet:
@ -21,13 +14,14 @@ class Wallet:
by physical files on the filesystem. by physical files on the filesystem.
""" """
def __init__(self, name='Wallet', ledgers=None, accounts=None, storage=None): def __init__(self, name='Wallet', accounts=None, storage=None):
# type: (str, List[torba.baseaccount.BaseAccount], WalletStorage) -> None
self.name = name self.name = name
self.ledgers = ledgers or [] # type: List[BaseLedger] self.accounts = accounts or []
self.accounts = accounts or [] # type: List[BaseAccount]
self.storage = storage or WalletStorage() self.storage = storage or WalletStorage()
def generate_account(self, ledger): # type: (BaseLedger) -> Account def generate_account(self, ledger):
# type: (torba.baseledger.BaseLedger) -> torba.baseaccount.BaseAccount
account = ledger.account_class.generate(ledger, u'torba') account = ledger.account_class.generate(ledger, u'torba')
self.accounts.append(account) self.accounts.append(account)
return account return account
@ -36,22 +30,14 @@ class Wallet:
def from_storage(cls, storage, manager): # type: (WalletStorage, 'WalletManager') -> Wallet def from_storage(cls, storage, manager): # type: (WalletStorage, 'WalletManager') -> Wallet
json_dict = storage.read() json_dict = storage.read()
ledgers = {}
for ledger_id, ledger_dict in json_dict.get('ledgers', {}).items():
ledgers[ledger_id] = inflate_ledger(manager, ledger_id, ledger_dict)
accounts = [] accounts = []
for account_dict in json_dict.get('accounts', []): for account_dict in json_dict.get('accounts', []):
ledger_id = account_dict['ledger'] ledger = manager.get_or_create_ledger(account_dict['ledger'])
ledger = ledgers.get(ledger_id)
if ledger is None:
ledger = ledgers[ledger_id] = inflate_ledger(manager, ledger_id, {})
account = ledger.account_class.from_dict(ledger, account_dict) account = ledger.account_class.from_dict(ledger, account_dict)
accounts.append(account) accounts.append(account)
return cls( return cls(
name=json_dict.get('name', 'Wallet'), name=json_dict.get('name', 'Wallet'),
ledgers=list(ledgers.values()),
accounts=accounts, accounts=accounts,
storage=storage storage=storage
) )
@ -59,7 +45,6 @@ class Wallet:
def to_dict(self): def to_dict(self):
return { return {
'name': self.name, 'name': self.name,
'ledgers': {c.get_id(): {} for c in self.ledgers},
'accounts': [a.to_dict() for a in self.accounts] 'accounts': [a.to_dict() for a in self.accounts]
} }
@ -71,12 +56,6 @@ class Wallet:
for account in self.accounts: for account in self.accounts:
return account return account
def get_account_private_key_for_address(self, address):
for account in self.accounts:
private_key = account.get_private_key_for_address(address)
if private_key is not None:
return account, private_key
class WalletStorage: class WalletStorage:

12
tox.ini
View file

@ -1,12 +1,14 @@
[tox] [tox]
envlist = py{27,36} envlist = py27,py36-{torba.coin.bitcoincash,torba.coin.bitcoinsegwit}
[testenv] [testenv]
deps = deps =
coverage coverage
mock py36: ../orchstr8
py36: ../electrumx
extras = test
changedir = {toxinidir}/tests changedir = {toxinidir}/tests
setenv = py36: LEDGER={envname}
commands = commands =
coverage run -p --source={envsitepackagesdir}/torba -m unittest discover -v coverage run -p --source={envsitepackagesdir}/torba -m twisted.trial unit
py36: coverage run -p --source={envsitepackagesdir}/torba -m twisted.trial --reactor=asyncio integration