wip
This commit is contained in:
parent
7d3daa9fe6
commit
56175df121
16 changed files with 418 additions and 288 deletions
|
@ -1,59 +0,0 @@
|
|||
import asyncio
|
||||
from binascii import hexlify
|
||||
from orchstr8.testcase import IntegrationTestCase
|
||||
from torba.constants import COIN
|
||||
from lbrynet.wallet.transaction import Transaction, Input, Output
|
||||
|
||||
|
||||
class BasicTransactionTests(IntegrationTestCase):
|
||||
|
||||
VERBOSE = True
|
||||
|
||||
async def test_sending_and_recieving(self):
|
||||
|
||||
self.assertEqual(await self.lbrycrd.get_balance(), 10.0)
|
||||
self.assertEqual(self.manager.get_balance(), 0.0)
|
||||
|
||||
address = self.account.get_least_used_receiving_address()
|
||||
sendtxid = await self.lbrycrd.send_to_address(address.decode(), 5.5)
|
||||
await self.lbrycrd.generate(1)
|
||||
await self.on_transaction(sendtxid)
|
||||
|
||||
self.assertAlmostEqual(await self.lbrycrd.get_balance(), 5.5, places=2)
|
||||
self.assertEqual(self.manager.get_balance(), 5.5)
|
||||
|
||||
lbrycrd_address = await self.lbrycrd.get_raw_change_address()
|
||||
tx = self.manager.send_amount_to_address(5, lbrycrd_address)
|
||||
await self.broadcast(tx)
|
||||
await self.on_transaction(tx.id.decode())
|
||||
await self.lbrycrd.generate(1)
|
||||
|
||||
self.assertAlmostEqual(await self.lbrycrd.get_balance(), 11.5, places=2)
|
||||
#self.assertEqual(self.manager.get_balance(), 0.5)
|
||||
|
||||
|
||||
class AbandonClaimLookup(IntegrationTestCase):
|
||||
|
||||
async def skip_test_abandon_claim(self):
|
||||
address = yield self.lbry.wallet.get_least_used_address()
|
||||
yield self.lbrycrd.sendtoaddress(address, 0.0003 - 0.0000355)
|
||||
yield self.lbrycrd.generate(1)
|
||||
yield self.lbry.wallet.update_balance()
|
||||
yield threads.deferToThread(time.sleep, 5)
|
||||
print(self.lbry.wallet.get_balance())
|
||||
claim = yield self.lbry.wallet.claim_new_channel('@test', 0.000096)
|
||||
yield self.lbrycrd.generate(1)
|
||||
print('='*10 + 'CLAIM' + '='*10)
|
||||
print(claim)
|
||||
yield self.lbrycrd.decoderawtransaction(claim['tx'])
|
||||
abandon = yield self.lbry.wallet.abandon_claim(claim['claim_id'], claim['txid'], claim['nout'])
|
||||
print('='*10 + 'ABANDON' + '='*10)
|
||||
print(abandon)
|
||||
yield self.lbrycrd.decoderawtransaction(abandon['tx'])
|
||||
yield self.lbrycrd.generate(1)
|
||||
yield self.lbrycrd.getrawtransaction(abandon['txid'])
|
||||
|
||||
yield self.lbry.wallet.update_balance()
|
||||
yield threads.deferToThread(time.sleep, 5)
|
||||
print('='*10 + 'FINAL BALANCE' + '='*10)
|
||||
print(self.lbry.wallet.get_balance())
|
111
lbrynet/tests/integration/wallet/test_transactions.py
Normal file
111
lbrynet/tests/integration/wallet/test_transactions.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
import asyncio
|
||||
from binascii import hexlify, unhexlify
|
||||
from orchstr8.testcase import IntegrationTestCase
|
||||
from lbryschema.claim import ClaimDict
|
||||
|
||||
|
||||
class BasicTransactionTests(IntegrationTestCase):
|
||||
|
||||
VERBOSE = True
|
||||
|
||||
async def test_sending_and_recieving(self):
|
||||
|
||||
self.assertEqual(await self.get_balance(), 0.0)
|
||||
|
||||
address = self.account.get_least_used_receiving_address()
|
||||
sendtxid = await self.blockchain.send_to_address(address.decode(), 5.5)
|
||||
await self.blockchain.generate(1)
|
||||
await self.on_transaction(sendtxid)
|
||||
|
||||
self.assertAlmostEqual(await self.get_balance(), 5.5, places=2)
|
||||
|
||||
lbrycrd_address = await self.blockchain.get_raw_change_address()
|
||||
tx = self.manager.send_amount_to_address(5, lbrycrd_address)
|
||||
await self.broadcast(tx)
|
||||
await self.on_transaction(tx.id.decode())
|
||||
await self.blockchain.generate(1)
|
||||
|
||||
self.assertAlmostEqual(await self.get_balance(), 0.5, places=2)
|
||||
|
||||
|
||||
example_claim_dict = {
|
||||
"version": "_0_0_1",
|
||||
"claimType": "streamType",
|
||||
"stream": {
|
||||
"source": {
|
||||
"source": "d5169241150022f996fa7cd6a9a1c421937276a3275eb912790bd07ba7aec1fac5fd45431d226b8fb402691e79aeb24b",
|
||||
"version": "_0_0_1",
|
||||
"contentType": "video/mp4",
|
||||
"sourceType": "lbry_sd_hash"
|
||||
},
|
||||
"version": "_0_0_1",
|
||||
"metadata": {
|
||||
"license": "LBRY Inc",
|
||||
"description": "What is LBRY? An introduction with Alex Tabarrok",
|
||||
"language": "en",
|
||||
"title": "What is LBRY?",
|
||||
"author": "Samuel Bryan",
|
||||
"version": "_0_1_0",
|
||||
"nsfw": False,
|
||||
"licenseUrl": "",
|
||||
"preview": "",
|
||||
"thumbnail": "https://s3.amazonaws.com/files.lbry.io/logo.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ClaimTransactionTests(IntegrationTestCase):
|
||||
|
||||
VERBOSE = True
|
||||
|
||||
async def test_creating_updating_and_abandoning_claim(self):
|
||||
|
||||
address = self.account.get_least_used_receiving_address()
|
||||
sendtxid = await self.lbrycrd.send_to_address(address.decode(), 9.0)
|
||||
await self.lbrycrd.generate(1)
|
||||
await self.on_transaction(sendtxid)
|
||||
|
||||
self.assertAlmostEqual(self.manager.get_balance(), 9, places=2)
|
||||
|
||||
claim = ClaimDict.load_dict(example_claim_dict)
|
||||
tx = self.manager.claim_name(b'foo', 5, hexlify(claim.serialized))
|
||||
await self.broadcast(tx)
|
||||
await self.on_transaction(tx.id.decode())
|
||||
await self.lbrycrd.generate(1)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
self.assertAlmostEqual(self.manager.get_balance(), 9, places=2)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
response = await self.resolve('lbry://foo')
|
||||
print(response)
|
||||
|
||||
|
||||
#class AbandonClaimLookup(IntegrationTestCase):
|
||||
#
|
||||
# async def skip_test_abandon_claim(self):
|
||||
# address = yield self.lbry.wallet.get_least_used_address()
|
||||
# yield self.lbrycrd.sendtoaddress(address, 0.0003 - 0.0000355)
|
||||
# yield self.lbrycrd.generate(1)
|
||||
# yield self.lbry.wallet.update_balance()
|
||||
# yield threads.deferToThread(time.sleep, 5)
|
||||
# print(self.lbry.wallet.get_balance())
|
||||
# claim = yield self.lbry.wallet.claim_new_channel('@test', 0.000096)
|
||||
# yield self.lbrycrd.generate(1)
|
||||
# print('='*10 + 'CLAIM' + '='*10)
|
||||
# print(claim)
|
||||
# yield self.lbrycrd.decoderawtransaction(claim['tx'])
|
||||
# abandon = yield self.lbry.wallet.abandon_claim(claim['claim_id'], claim['txid'], claim['nout'])
|
||||
# print('='*10 + 'ABANDON' + '='*10)
|
||||
# print(abandon)
|
||||
# yield self.lbrycrd.decoderawtransaction(abandon['tx'])
|
||||
# yield self.lbrycrd.generate(1)
|
||||
# yield self.lbrycrd.getrawtransaction(abandon['txid'])
|
||||
#
|
||||
# yield self.lbry.wallet.update_balance()
|
||||
# yield threads.deferToThread(time.sleep, 5)
|
||||
# print('='*10 + 'FINAL BALANCE' + '='*10)
|
||||
# print(self.lbry.wallet.get_balance())
|
|
@ -38,25 +38,25 @@ class TestAccount(unittest.TestCase):
|
|||
)
|
||||
self.assertEqual(
|
||||
account.private_key.extended_key_string(),
|
||||
'LprvXPsFZUGgrX1X9HiyxABZSf6hWJK7kHv4zGZRyyiHbBq5Wu94cE1DMvttnpLYReTPNW4eYwX9dWMvTz3PrB'
|
||||
'wwbRafEeA1ZXL69U2egM4QJdq'
|
||||
b'LprvXPsFZUGgrX1X9HiyxABZSf6hWJK7kHv4zGZRyyiHbBq5Wu94cE1DMvttnpLYReTPNW4eYwX9dWMvTz3PrB'
|
||||
b'wwbRafEeA1ZXL69U2egM4QJdq'
|
||||
)
|
||||
self.assertEqual(
|
||||
account.public_key.extended_key_string(),
|
||||
'Lpub2hkYkGHXktBhLpwUhKKogyuJ1M7Gt9EkjFTVKyDqZiZpWdhLuCoT1eKDfXfysMFfG4SzfXXcA2SsHzrjHK'
|
||||
'Ea5aoCNRBAhjT5NPLV6hXtvEi'
|
||||
b'Lpub2hkYkGHXktBhLpwUhKKogyuJ1M7Gt9EkjFTVKyDqZiZpWdhLuCoT1eKDfXfysMFfG4SzfXXcA2SsHzrjHK'
|
||||
b'Ea5aoCNRBAhjT5NPLV6hXtvEi'
|
||||
)
|
||||
self.assertEqual(
|
||||
account.receiving_keys.generate_next_address(),
|
||||
'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx'
|
||||
b'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx'
|
||||
)
|
||||
private_key = account.get_private_key_for_address('bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx')
|
||||
private_key = account.get_private_key_for_address(b'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx')
|
||||
self.assertEqual(
|
||||
private_key.extended_key_string(),
|
||||
'LprvXTnmVLXGKvRGo2ihBE6LJ771G3VVpAx2zhTJvjnx5P3h6iZ4VJX8PvwTcgzJZ1hqXX61Wpn4pQoP6n2wgp'
|
||||
'S8xjzCM6H2uGzCXuAMy5H9vtA'
|
||||
b'LprvXTnmVLXGKvRGo2ihBE6LJ771G3VVpAx2zhTJvjnx5P3h6iZ4VJX8PvwTcgzJZ1hqXX61Wpn4pQoP6n2wgp'
|
||||
b'S8xjzCM6H2uGzCXuAMy5H9vtA'
|
||||
)
|
||||
self.assertIsNone(account.get_private_key_for_address('BcQjRlhDOIrQez1WHfz3whnB33Bp34sUgX'))
|
||||
self.assertIsNone(account.get_private_key_for_address(b'BcQjRlhDOIrQez1WHfz3whnB33Bp34sUgX'))
|
||||
|
||||
def test_load_and_save_account(self):
|
||||
account_data = {
|
||||
|
@ -86,12 +86,12 @@ class TestAccount(unittest.TestCase):
|
|||
self.assertEqual(len(account.receiving_keys.addresses), 2)
|
||||
self.assertEqual(
|
||||
account.receiving_keys.addresses[0],
|
||||
'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx'
|
||||
b'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx'
|
||||
)
|
||||
self.assertEqual(len(account.change_keys.addresses), 1)
|
||||
self.assertEqual(
|
||||
account.change_keys.addresses[0],
|
||||
'bFpHENtqugKKHDshKFq2Mnb59Y2bx4vKgL'
|
||||
b'bFpHENtqugKKHDshKFq2Mnb59Y2bx4vKgL'
|
||||
)
|
||||
|
||||
account_data['coin'] = 'lbc_mainnet'
|
||||
|
|
69
lbrynet/tests/unit/wallet/test_ledger.py
Normal file
69
lbrynet/tests/unit/wallet/test_ledger.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
import shutil
|
||||
import tempfile
|
||||
from twisted.internet import defer
|
||||
from twisted.trial import unittest
|
||||
from lbrynet import conf
|
||||
from lbrynet.database.storage import SQLiteStorage
|
||||
from lbrynet.wallet.transaction import Transaction, Output, Input
|
||||
from lbrynet.wallet.coin import LBC
|
||||
from lbrynet.wallet.manager import LbryWalletManager
|
||||
from torba.baseaccount import Account
|
||||
from torba.wallet import Wallet
|
||||
|
||||
|
||||
class LedgerTestCase(unittest.TestCase):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def setUp(self):
|
||||
conf.initialize_settings(False)
|
||||
self.db_dir = tempfile.mkdtemp()
|
||||
self.storage = SQLiteStorage(self.db_dir)
|
||||
yield self.storage.setup()
|
||||
self.manager = LbryWalletManager(self.storage)
|
||||
self.ledger = self.manager.get_or_create_ledger(LBC.get_id())
|
||||
self.coin = LBC(self.ledger)
|
||||
self.wallet = Wallet('Main', [self.coin], [Account.from_seed(
|
||||
self.coin, u'carbon smart garage balance margin twelve chest sword toast envelope botto'
|
||||
u'm stomach absent', u'lbryum'
|
||||
)])
|
||||
self.account = self.wallet.default_account
|
||||
yield self.storage.add_account(self.account)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def tearDown(self):
|
||||
yield self.storage.stop()
|
||||
shutil.rmtree(self.db_dir)
|
||||
|
||||
|
||||
class BasicAccountingTests(LedgerTestCase):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_empty_state(self):
|
||||
balance = yield self.account.get_balance()
|
||||
self.assertEqual(balance, 0)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_balance(self):
|
||||
tx = Transaction().add_outputs([Output.pay_pubkey_hash(100, b'abc1')])
|
||||
yield self.storage.add_tx_output(self.account, tx.outputs[0])
|
||||
balance = yield self.storage.get_balance_for_account(self.account)
|
||||
self.assertEqual(balance, 100)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_get_utxo(self):
|
||||
tx1 = Transaction().add_outputs([Output.pay_pubkey_hash(100, b'abc1')])
|
||||
txo = tx1.outputs[0]
|
||||
yield self.storage.add_tx_output(self.account, txo)
|
||||
balance = yield self.storage.get_balance_for_account(self.account)
|
||||
self.assertEqual(balance, 100)
|
||||
|
||||
utxos = yield self.storage.get_utxos(self.account, Output)
|
||||
self.assertEqual(len(utxos), 1)
|
||||
|
||||
txi = Transaction().add_inputs([Input.spend(txo)]).inputs[0]
|
||||
yield self.storage.add_tx_input(self.account, txi)
|
||||
balance = yield self.storage.get_balance_for_account(self.account)
|
||||
self.assertEqual(balance, 0)
|
||||
|
||||
utxos = yield self.storage.get_utxos(self.account, Output)
|
||||
self.assertEqual(len(utxos), 0)
|
|
@ -37,10 +37,10 @@ class TestPayClaimNamePubkeyHash(unittest.TestCase):
|
|||
# pub key
|
||||
b'be16e4b0f9bd8f6d47d02b3a887049c36d3b84cb'
|
||||
),
|
||||
'b504636174734cdc080110011a7808011230080410011a084d616361726f6e6922002a003214416c6c207'
|
||||
'269676874732072657365727665642e38004a0052005a001a42080110011a30add80aaf02559ba0985363'
|
||||
'6a0658c42b727cb5bb4ba8acedb4b7fe656065a47a31878dbf9912135ddb9e13806cc1479d220a696d616'
|
||||
'7652f6a7065672a5c080110031a404180cc0fa4d3839ee29cca866baed25fafb43fca1eb3b608ee889d35'
|
||||
'1d3573d042c7b83e2e643db0d8e062a04e6e9ae6b90540a2f95fe28638d0f18af4361a1c2214f73de93f4'
|
||||
'299fb32c32f949e02198a8e91101abd6d7576a914be16e4b0f9bd8f6d47d02b3a887049c36d3b84cb88ac'
|
||||
b'b504636174734cdc080110011a7808011230080410011a084d616361726f6e6922002a003214416c6c207'
|
||||
b'269676874732072657365727665642e38004a0052005a001a42080110011a30add80aaf02559ba0985363'
|
||||
b'6a0658c42b727cb5bb4ba8acedb4b7fe656065a47a31878dbf9912135ddb9e13806cc1479d220a696d616'
|
||||
b'7652f6a7065672a5c080110031a404180cc0fa4d3839ee29cca866baed25fafb43fca1eb3b608ee889d35'
|
||||
b'1d3573d042c7b83e2e643db0d8e062a04e6e9ae6b90540a2f95fe28638d0f18af4361a1c2214f73de93f4'
|
||||
b'299fb32c32f949e02198a8e91101abd6d7576a914be16e4b0f9bd8f6d47d02b3a887049c36d3b84cb88ac'
|
||||
)
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
from binascii import hexlify, unhexlify
|
||||
from twisted.trial import unittest
|
||||
|
||||
from torba.account import Account
|
||||
from torba.baseaccount import Account
|
||||
from torba.constants import CENT, COIN
|
||||
from torba.wallet import Wallet
|
||||
from torba.basetransaction import NULL_HASH
|
||||
|
||||
from lbrynet.wallet.coin import LBC
|
||||
from lbrynet.wallet.transaction import Transaction, Output, Input
|
||||
from lbrynet.wallet.manager import LbryWalletManager
|
||||
|
||||
|
||||
NULL_HASH = '\x00'*32
|
||||
FEE_PER_BYTE = 50
|
||||
FEE_PER_CHAR = 200000
|
||||
|
||||
|
@ -31,7 +31,7 @@ def get_transaction(txo=None):
|
|||
.add_outputs([txo or Output.pay_pubkey_hash(CENT, NULL_HASH)])
|
||||
|
||||
|
||||
def get_claim_transaction(claim_name, claim=''):
|
||||
def get_claim_transaction(claim_name, claim=b''):
|
||||
return get_transaction(
|
||||
Output.pay_claim_name_pubkey_hash(CENT, claim_name, claim, NULL_HASH)
|
||||
)
|
||||
|
@ -70,15 +70,15 @@ class TestSizeAndFeeEstimation(unittest.TestCase):
|
|||
|
||||
def test_claim_name_transaction_size_and_fee(self):
|
||||
# fee based on claim name is the larger fee
|
||||
claim_name = 'verylongname'
|
||||
tx = get_claim_transaction(claim_name, '0'*4000)
|
||||
claim_name = b'verylongname'
|
||||
tx = get_claim_transaction(claim_name, b'0'*4000)
|
||||
base_size = tx.size - 1 - tx.inputs[0].size
|
||||
self.assertEqual(tx.size, 4225)
|
||||
self.assertEqual(tx.base_size, base_size)
|
||||
self.assertEqual(self.coin.get_transaction_base_fee(tx), len(claim_name) * FEE_PER_CHAR)
|
||||
# fee based on total bytes is the larger fee
|
||||
claim_name = 'a'
|
||||
tx = get_claim_transaction(claim_name, '0'*4000)
|
||||
claim_name = b'a'
|
||||
tx = get_claim_transaction(claim_name, b'0'*4000)
|
||||
base_size = tx.size - 1 - tx.inputs[0].size
|
||||
self.assertEqual(tx.size, 4214)
|
||||
self.assertEqual(tx.base_size, base_size)
|
||||
|
@ -168,7 +168,7 @@ class TestTransactionSerialization(unittest.TestCase):
|
|||
"00001976a914f521178feb733a719964e1da4a9efb09dcc39cfa88ac00000000"
|
||||
)
|
||||
tx = Transaction(raw)
|
||||
self.assertEqual(hexlify(tx.id), b'666c3d15de1d6949a4fe717126c368e274b36957dce29fd401138c1e87e92a62')
|
||||
self.assertEqual(tx.id, b'666c3d15de1d6949a4fe717126c368e274b36957dce29fd401138c1e87e92a62')
|
||||
self.assertEqual(tx.version, 1)
|
||||
self.assertEqual(tx.locktime, 0)
|
||||
self.assertEqual(len(tx.inputs), 1)
|
||||
|
@ -238,11 +238,10 @@ class TestTransactionSigning(unittest.TestCase):
|
|||
pubkey_hash2 = account.coin.address_to_hash160(address2)
|
||||
|
||||
tx = Transaction() \
|
||||
.add_inputs([Input.spend(get_output(2*COIN, pubkey_hash1))]) \
|
||||
.add_outputs([Output.pay_pubkey_hash(1.9*COIN, pubkey_hash2)]) \
|
||||
.add_inputs([Input.spend(get_output(int(2*COIN), pubkey_hash1))]) \
|
||||
.add_outputs([Output.pay_pubkey_hash(int(1.9*COIN), pubkey_hash2)]) \
|
||||
.sign(account)
|
||||
|
||||
print(hexlify(tx.inputs[0].script.values['signature']))
|
||||
self.assertEqual(
|
||||
hexlify(tx.inputs[0].script.values['signature']),
|
||||
b'304402200dafa26ad7cf38c5a971c8a25ce7d85a076235f146126762296b1223c42ae21e022020ef9eeb8'
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
__coin__ = 'LBC'
|
||||
|
||||
from .coin import LBC, LBCRegTest
|
||||
from .manager import LbryWalletManager
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
from six import int2byte
|
||||
from binascii import unhexlify
|
||||
|
||||
from torba.basecoin import BaseCoin
|
||||
|
||||
from .ledger import MainNetLedger, TestNetLedger, RegTestLedger
|
||||
from .transaction import Transaction
|
||||
|
||||
|
||||
class LBC(BaseCoin):
|
||||
name = 'LBRY Credits'
|
||||
symbol = 'LBC'
|
||||
network = 'mainnet'
|
||||
|
||||
ledger_class = MainNetLedger
|
||||
transaction_class = Transaction
|
||||
|
||||
secret_prefix = int2byte(0x1c)
|
||||
pubkey_address_prefix = int2byte(0x55)
|
||||
script_address_prefix = int2byte(0x7a)
|
||||
extended_public_key_prefix = unhexlify('019c354f')
|
||||
extended_private_key_prefix = unhexlify('019c3118')
|
||||
|
||||
default_fee_per_byte = 50
|
||||
default_fee_per_name_char = 200000
|
||||
|
||||
def __init__(self, ledger, fee_per_byte=default_fee_per_byte,
|
||||
fee_per_name_char=default_fee_per_name_char):
|
||||
super(LBC, self).__init__(ledger, fee_per_byte)
|
||||
self.fee_per_name_char = fee_per_name_char
|
||||
|
||||
def to_dict(self):
|
||||
coin_dict = super(LBC, self).to_dict()
|
||||
coin_dict['fee_per_name_char'] = self.fee_per_name_char
|
||||
return coin_dict
|
||||
|
||||
def get_transaction_base_fee(self, tx):
|
||||
""" Fee for the transaction header and all outputs; without inputs. """
|
||||
return max(
|
||||
super(LBC, self).get_transaction_base_fee(tx),
|
||||
self.get_transaction_claim_name_fee(tx)
|
||||
)
|
||||
|
||||
def get_transaction_claim_name_fee(self, tx):
|
||||
fee = 0
|
||||
for output in tx.outputs:
|
||||
if output.script.is_claim_name:
|
||||
fee += len(output.script.values['claim_name']) * self.fee_per_name_char
|
||||
return fee
|
||||
|
||||
|
||||
class LBCTestNet(LBC):
|
||||
network = 'testnet'
|
||||
ledger_class = TestNetLedger
|
||||
pubkey_address_prefix = int2byte(111)
|
||||
script_address_prefix = int2byte(196)
|
||||
extended_public_key_prefix = unhexlify('043587cf')
|
||||
extended_private_key_prefix = unhexlify('04358394')
|
||||
|
||||
|
||||
class LBCRegTest(LBC):
|
||||
network = 'regtest'
|
||||
ledger_class = RegTestLedger
|
||||
pubkey_address_prefix = int2byte(111)
|
||||
script_address_prefix = int2byte(196)
|
||||
extended_public_key_prefix = unhexlify('043587cf')
|
||||
extended_private_key_prefix = unhexlify('04358394')
|
11
lbrynet/wallet/database.py
Normal file
11
lbrynet/wallet/database.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from torba.basedatabase import BaseDatabase
|
||||
|
||||
|
||||
class WalletDatabase(BaseDatabase):
|
||||
|
||||
CREATE_TABLES_QUERY = (
|
||||
BaseDatabase.CREATE_TX_TABLE +
|
||||
BaseDatabase.CREATE_PUBKEY_ADDRESS_TABLE +
|
||||
BaseDatabase.CREATE_TXO_TABLE +
|
||||
BaseDatabase.CREATE_TXI_TABLE
|
||||
)
|
|
@ -1,28 +1,157 @@
|
|||
import struct
|
||||
from six import int2byte
|
||||
from binascii import unhexlify
|
||||
|
||||
from torba.baseledger import BaseLedger
|
||||
from torba.baseheader import BaseHeaders, _ArithUint256
|
||||
from torba.util import int_to_hex, rev_hex, hash_encode
|
||||
|
||||
from .network import Network
|
||||
from .transaction import Transaction
|
||||
from .database import WalletDatabase
|
||||
|
||||
|
||||
class LBCLedger(BaseLedger):
|
||||
network_class = Network
|
||||
class Headers(BaseHeaders):
|
||||
|
||||
header_size = 112
|
||||
|
||||
@staticmethod
|
||||
def _serialize(header):
|
||||
return b''.join([
|
||||
int_to_hex(header['version'], 4),
|
||||
rev_hex(header['prev_block_hash']),
|
||||
rev_hex(header['merkle_root']),
|
||||
rev_hex(header['claim_trie_root']),
|
||||
int_to_hex(int(header['timestamp']), 4),
|
||||
int_to_hex(int(header['bits']), 4),
|
||||
int_to_hex(int(header['nonce']), 4)
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def _deserialize(height, header):
|
||||
version, = struct.unpack('<I', header[:4])
|
||||
timestamp, bits, nonce = struct.unpack('<III', header[100:112])
|
||||
return {
|
||||
'version': version,
|
||||
'prev_block_hash': hash_encode(header[4:36]),
|
||||
'merkle_root': hash_encode(header[36:68]),
|
||||
'claim_trie_root': hash_encode(header[68:100]),
|
||||
'timestamp': timestamp,
|
||||
'bits': bits,
|
||||
'nonce': nonce,
|
||||
'block_height': height,
|
||||
}
|
||||
|
||||
def _calculate_next_work_required(self, height, first, last):
|
||||
""" See: lbrycrd/src/lbry.cpp """
|
||||
|
||||
if height == 0:
|
||||
return self.ledger.genesis_bits, self.ledger.max_target
|
||||
|
||||
if self.verify_bits_to_target:
|
||||
bits = last['bits']
|
||||
bitsN = (bits >> 24) & 0xff
|
||||
assert 0x03 <= bitsN <= 0x1f, \
|
||||
"First part of bits should be in [0x03, 0x1d], but it was {}".format(hex(bitsN))
|
||||
bitsBase = bits & 0xffffff
|
||||
assert 0x8000 <= bitsBase <= 0x7fffff, \
|
||||
"Second part of bits should be in [0x8000, 0x7fffff] but it was {}".format(bitsBase)
|
||||
|
||||
# new target
|
||||
retargetTimespan = self.ledger.target_timespan
|
||||
nActualTimespan = last['timestamp'] - first['timestamp']
|
||||
|
||||
nModulatedTimespan = retargetTimespan + (nActualTimespan - retargetTimespan) // 8
|
||||
|
||||
nMinTimespan = retargetTimespan - (retargetTimespan // 8)
|
||||
nMaxTimespan = retargetTimespan + (retargetTimespan // 2)
|
||||
|
||||
# Limit adjustment step
|
||||
if nModulatedTimespan < nMinTimespan:
|
||||
nModulatedTimespan = nMinTimespan
|
||||
elif nModulatedTimespan > nMaxTimespan:
|
||||
nModulatedTimespan = nMaxTimespan
|
||||
|
||||
# Retarget
|
||||
bnPowLimit = _ArithUint256(self.ledger.max_target)
|
||||
bnNew = _ArithUint256.SetCompact(last['bits'])
|
||||
bnNew *= nModulatedTimespan
|
||||
bnNew //= nModulatedTimespan
|
||||
if bnNew > bnPowLimit:
|
||||
bnNew = bnPowLimit
|
||||
|
||||
return bnNew.GetCompact(), bnNew._value
|
||||
|
||||
|
||||
class Ledger(BaseLedger):
|
||||
name = 'LBRY Credits'
|
||||
symbol = 'LBC'
|
||||
|
||||
database_class = WalletDatabase
|
||||
headers_class = Headers
|
||||
network_class = Network
|
||||
transaction_class = Transaction
|
||||
|
||||
default_fee_per_byte = 50
|
||||
default_fee_per_name_char = 200000
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Ledger, self).__init__(*args, **kwargs)
|
||||
self.fee_per_name_char = self.config.get('fee_per_name_char', self.default_fee_per_name_char)
|
||||
|
||||
def get_transaction_base_fee(self, tx):
|
||||
""" Fee for the transaction header and all outputs; without inputs. """
|
||||
return max(
|
||||
super(Ledger, self).get_transaction_base_fee(tx),
|
||||
self.get_transaction_claim_name_fee(tx)
|
||||
)
|
||||
|
||||
def get_transaction_claim_name_fee(self, tx):
|
||||
fee = 0
|
||||
for output in tx.outputs:
|
||||
if output.script.is_claim_name:
|
||||
fee += len(output.script.values['claim_name']) * self.fee_per_name_char
|
||||
return fee
|
||||
|
||||
def resolve(self, *uris):
|
||||
return self.network.get_values_for_uris(*uris)
|
||||
|
||||
|
||||
class MainNetLedger(Ledger):
|
||||
network_name = 'mainnet'
|
||||
secret_prefix = int2byte(0x1c)
|
||||
pubkey_address_prefix = int2byte(0x55)
|
||||
script_address_prefix = int2byte(0x7a)
|
||||
extended_public_key_prefix = unhexlify('019c354f')
|
||||
extended_private_key_prefix = unhexlify('019c3118')
|
||||
|
||||
max_target = 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
genesis_hash = '9c89283ba0f3227f6c03b70216b9f665f0118d5e0fa729cedf4fb34d6a34f463'
|
||||
genesis_bits = 0x1f00ffff
|
||||
target_timespan = 150
|
||||
|
||||
|
||||
class MainNetLedger(LBCLedger):
|
||||
pass
|
||||
class TestNetLedger(Ledger):
|
||||
network_name = 'testnet'
|
||||
pubkey_address_prefix = int2byte(111)
|
||||
script_address_prefix = int2byte(196)
|
||||
extended_public_key_prefix = unhexlify('043587cf')
|
||||
extended_private_key_prefix = unhexlify('04358394')
|
||||
|
||||
|
||||
class TestNetLedger(LBCLedger):
|
||||
pass
|
||||
class UnverifiedHeaders(Headers):
|
||||
verify_bits_to_target = False
|
||||
|
||||
|
||||
class RegTestLedger(LBCLedger):
|
||||
class RegTestLedger(Ledger):
|
||||
network_name = 'regtest'
|
||||
headers_class = UnverifiedHeaders
|
||||
pubkey_address_prefix = int2byte(111)
|
||||
script_address_prefix = int2byte(196)
|
||||
extended_public_key_prefix = unhexlify('043587cf')
|
||||
extended_private_key_prefix = unhexlify('04358394')
|
||||
|
||||
max_target = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
genesis_hash = '6e3fcf1299d4ec5d79c3a4c91d624a4acf9e2e173d95a1a0504f677669687556'
|
||||
genesis_bits = 0x207fffff
|
||||
target_timespan = 1
|
||||
verify_bits_to_target = False
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import os
|
||||
from twisted.internet import defer
|
||||
|
||||
from lbrynet.database.storage import SQLiteStorage
|
||||
|
||||
from torba.basetransaction import NULL_HASH
|
||||
from torba.constants import COIN
|
||||
from torba.coinselection import CoinSelector
|
||||
from torba.manager import WalletManager as BaseWalletManager
|
||||
|
||||
from .transaction import Transaction, Output, Input
|
||||
|
||||
|
||||
class BackwardsCompatibleNetwork:
|
||||
def __init__(self, manager):
|
||||
|
@ -18,6 +24,15 @@ class BackwardsCompatibleNetwork:
|
|||
|
||||
class LbryWalletManager(BaseWalletManager):
|
||||
|
||||
def __init__(self, db, **kwargs):
|
||||
super(LbryWalletManager, self).__init__(**kwargs)
|
||||
self.db = db # type: SQLiteStorage
|
||||
|
||||
def create_ledger(self, ledger_class, *args, **kwargs):
|
||||
if issubclass(ledger_class.database_class, self.db.__class__):
|
||||
return ledger_class(*args, db=self.db, **kwargs)
|
||||
return super(LbryWalletManager, self).create_ledger(ledger_class, *args, **kwargs)
|
||||
|
||||
@property
|
||||
def wallet(self):
|
||||
return self
|
||||
|
@ -54,15 +69,6 @@ class LbryWalletManager(BaseWalletManager):
|
|||
)
|
||||
return wallet_manager
|
||||
|
||||
def start(self):
|
||||
return self.start_ledgers()
|
||||
|
||||
def stop(self):
|
||||
return self.stop_ledgers()
|
||||
|
||||
def get_balance(self):
|
||||
return float(self.default_account.get_balance()) / float(COIN)
|
||||
|
||||
def get_best_blockhash(self):
|
||||
return defer.succeed('')
|
||||
|
||||
|
@ -86,8 +92,9 @@ class LbryWalletManager(BaseWalletManager):
|
|||
def get_info_exchanger(self):
|
||||
return LBRYcrdAddressRequester(self)
|
||||
|
||||
def resolve(self, *uris, **kwargs):
|
||||
return defer.succeed({})
|
||||
def resolve(self, *uris):
|
||||
ledger = self.default_account.coin.ledger # type: LBCLedger
|
||||
return ledger.resolve(uris)
|
||||
|
||||
def get_name_claims(self):
|
||||
return defer.succeed([])
|
||||
|
@ -98,6 +105,46 @@ class LbryWalletManager(BaseWalletManager):
|
|||
def get_history(self):
|
||||
return defer.succeed([])
|
||||
|
||||
def claim_name(self, name, amount, claim):
|
||||
amount = int(amount * COIN)
|
||||
|
||||
account = self.default_account
|
||||
coin = account.coin
|
||||
ledger = coin.ledger
|
||||
|
||||
estimators = [
|
||||
txo.get_estimator(coin) for txo in ledger.get_unspent_outputs()
|
||||
]
|
||||
|
||||
cost_of_output = coin.get_input_output_fee(
|
||||
Output.pay_pubkey_hash(COIN, NULL_HASH)
|
||||
)
|
||||
|
||||
selector = CoinSelector(estimators, amount, cost_of_output)
|
||||
spendables = selector.select()
|
||||
if not spendables:
|
||||
raise ValueError('Not enough funds to cover this transaction.')
|
||||
|
||||
claim_address = account.get_least_used_receiving_address()
|
||||
outputs = [
|
||||
Output.pay_claim_name_pubkey_hash(
|
||||
amount, name, claim, coin.address_to_hash160(claim_address)
|
||||
)
|
||||
]
|
||||
|
||||
spent_sum = sum(s.effective_amount for s in spendables)
|
||||
if spent_sum > amount:
|
||||
change_address = account.get_least_used_change_address()
|
||||
change_hash160 = coin.address_to_hash160(change_address)
|
||||
outputs.append(Output.pay_pubkey_hash(spent_sum - amount, change_hash160))
|
||||
|
||||
tx = Transaction() \
|
||||
.add_inputs([s.txi for s in spendables]) \
|
||||
.add_outputs(outputs) \
|
||||
.sign(account)
|
||||
|
||||
return tx
|
||||
|
||||
|
||||
class ReservedPoints:
|
||||
def __init__(self, identifier, amount):
|
||||
|
|
|
@ -2,4 +2,6 @@ from torba.basenetwork import BaseNetwork
|
|||
|
||||
|
||||
class Network(BaseNetwork):
|
||||
pass
|
||||
|
||||
def get_values_for_uris(self, uris):
|
||||
return self.rpc('blockchain.claimtrie.getvaluesforuris', uris)
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
from torba.basescript import BaseInputScript, BaseOutputScript, Template
|
||||
from torba.basescript import PUSH_SINGLE, OP_DROP, OP_2DROP
|
||||
|
||||
|
||||
class InputScript(BaseInputScript):
|
||||
pass
|
||||
|
||||
|
||||
class OutputScript(BaseOutputScript):
|
||||
|
||||
# lbry custom opcodes
|
||||
OP_CLAIM_NAME = 0xb5
|
||||
OP_SUPPORT_CLAIM = 0xb6
|
||||
OP_UPDATE_CLAIM = 0xb7
|
||||
|
||||
CLAIM_NAME_OPCODES = (
|
||||
OP_CLAIM_NAME, PUSH_SINGLE('claim_name'), PUSH_SINGLE('claim'),
|
||||
OP_2DROP, OP_DROP
|
||||
)
|
||||
CLAIM_NAME_PUBKEY = Template('claim_name+pay_pubkey_hash', (
|
||||
CLAIM_NAME_OPCODES + BaseOutputScript.PAY_PUBKEY_HASH.opcodes
|
||||
))
|
||||
CLAIM_NAME_SCRIPT = Template('claim_name+pay_script_hash', (
|
||||
CLAIM_NAME_OPCODES + BaseOutputScript.PAY_SCRIPT_HASH.opcodes
|
||||
))
|
||||
|
||||
SUPPORT_CLAIM_OPCODES = (
|
||||
OP_SUPPORT_CLAIM, PUSH_SINGLE('claim_name'), PUSH_SINGLE('claim_id'),
|
||||
OP_2DROP, OP_DROP
|
||||
)
|
||||
SUPPORT_CLAIM_PUBKEY = Template('support_claim+pay_pubkey_hash', (
|
||||
SUPPORT_CLAIM_OPCODES + BaseOutputScript.PAY_PUBKEY_HASH.opcodes
|
||||
))
|
||||
SUPPORT_CLAIM_SCRIPT = Template('support_claim+pay_script_hash', (
|
||||
SUPPORT_CLAIM_OPCODES + BaseOutputScript.PAY_SCRIPT_HASH.opcodes
|
||||
))
|
||||
|
||||
UPDATE_CLAIM_OPCODES = (
|
||||
OP_UPDATE_CLAIM, PUSH_SINGLE('claim_name'), PUSH_SINGLE('claim_id'), PUSH_SINGLE('claim'),
|
||||
OP_2DROP, OP_2DROP
|
||||
)
|
||||
UPDATE_CLAIM_PUBKEY = Template('update_claim+pay_pubkey_hash', (
|
||||
UPDATE_CLAIM_OPCODES + BaseOutputScript.PAY_PUBKEY_HASH.opcodes
|
||||
))
|
||||
UPDATE_CLAIM_SCRIPT = Template('update_claim+pay_script_hash', (
|
||||
UPDATE_CLAIM_OPCODES + BaseOutputScript.PAY_SCRIPT_HASH.opcodes
|
||||
))
|
||||
|
||||
templates = BaseOutputScript.templates + [
|
||||
CLAIM_NAME_PUBKEY,
|
||||
CLAIM_NAME_SCRIPT,
|
||||
SUPPORT_CLAIM_PUBKEY,
|
||||
SUPPORT_CLAIM_SCRIPT,
|
||||
UPDATE_CLAIM_PUBKEY,
|
||||
UPDATE_CLAIM_SCRIPT
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def pay_claim_name_pubkey_hash(cls, claim_name, claim, pubkey_hash):
|
||||
return cls(template=cls.CLAIM_NAME_PUBKEY, values={
|
||||
'claim_name': claim_name,
|
||||
'claim': claim,
|
||||
'pubkey_hash': pubkey_hash
|
||||
})
|
||||
|
||||
@property
|
||||
def is_claim_name(self):
|
||||
return self.template.name.startswith('claim_name+')
|
||||
|
||||
@property
|
||||
def is_support_claim(self):
|
||||
return self.template.name.startswith('support_claim+')
|
||||
|
||||
@property
|
||||
def is_update_claim(self):
|
||||
return self.template.name.startswith('update_claim+')
|
||||
|
||||
@property
|
||||
def is_claim_involved(self):
|
||||
return self.is_claim_name or self.is_support_claim or self.is_update_claim
|
|
@ -1,34 +0,0 @@
|
|||
import struct
|
||||
|
||||
from torba.basetransaction import BaseTransaction, BaseInput, BaseOutput
|
||||
from torba.hash import hash160
|
||||
|
||||
from .script import InputScript, OutputScript
|
||||
|
||||
|
||||
def claim_id_hash(txid, n):
|
||||
return hash160(txid + struct.pack('>I', n))
|
||||
|
||||
|
||||
class Input(BaseInput):
|
||||
script_class = InputScript
|
||||
|
||||
|
||||
class Output(BaseOutput):
|
||||
script_class = OutputScript
|
||||
|
||||
@classmethod
|
||||
def pay_claim_name_pubkey_hash(cls, amount, claim_name, claim, pubkey_hash):
|
||||
script = cls.script_class.pay_claim_name_pubkey_hash(claim_name, claim, pubkey_hash)
|
||||
return cls(amount, script)
|
||||
|
||||
|
||||
class Transaction(BaseTransaction):
|
||||
|
||||
input_class = Input
|
||||
output_class = Output
|
||||
|
||||
def get_claim_id(self, output_index):
|
||||
output = self._outputs[output_index]
|
||||
assert output.script.is_claim_name(), 'Not a name claim.'
|
||||
return claim_id_hash(self.hash, output_index)
|
Loading…
Reference in a new issue