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(
|
self.assertEqual(
|
||||||
account.private_key.extended_key_string(),
|
account.private_key.extended_key_string(),
|
||||||
'LprvXPsFZUGgrX1X9HiyxABZSf6hWJK7kHv4zGZRyyiHbBq5Wu94cE1DMvttnpLYReTPNW4eYwX9dWMvTz3PrB'
|
b'LprvXPsFZUGgrX1X9HiyxABZSf6hWJK7kHv4zGZRyyiHbBq5Wu94cE1DMvttnpLYReTPNW4eYwX9dWMvTz3PrB'
|
||||||
'wwbRafEeA1ZXL69U2egM4QJdq'
|
b'wwbRafEeA1ZXL69U2egM4QJdq'
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
account.public_key.extended_key_string(),
|
account.public_key.extended_key_string(),
|
||||||
'Lpub2hkYkGHXktBhLpwUhKKogyuJ1M7Gt9EkjFTVKyDqZiZpWdhLuCoT1eKDfXfysMFfG4SzfXXcA2SsHzrjHK'
|
b'Lpub2hkYkGHXktBhLpwUhKKogyuJ1M7Gt9EkjFTVKyDqZiZpWdhLuCoT1eKDfXfysMFfG4SzfXXcA2SsHzrjHK'
|
||||||
'Ea5aoCNRBAhjT5NPLV6hXtvEi'
|
b'Ea5aoCNRBAhjT5NPLV6hXtvEi'
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
account.receiving_keys.generate_next_address(),
|
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(
|
self.assertEqual(
|
||||||
private_key.extended_key_string(),
|
private_key.extended_key_string(),
|
||||||
'LprvXTnmVLXGKvRGo2ihBE6LJ771G3VVpAx2zhTJvjnx5P3h6iZ4VJX8PvwTcgzJZ1hqXX61Wpn4pQoP6n2wgp'
|
b'LprvXTnmVLXGKvRGo2ihBE6LJ771G3VVpAx2zhTJvjnx5P3h6iZ4VJX8PvwTcgzJZ1hqXX61Wpn4pQoP6n2wgp'
|
||||||
'S8xjzCM6H2uGzCXuAMy5H9vtA'
|
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):
|
def test_load_and_save_account(self):
|
||||||
account_data = {
|
account_data = {
|
||||||
|
@ -86,12 +86,12 @@ class TestAccount(unittest.TestCase):
|
||||||
self.assertEqual(len(account.receiving_keys.addresses), 2)
|
self.assertEqual(len(account.receiving_keys.addresses), 2)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
account.receiving_keys.addresses[0],
|
account.receiving_keys.addresses[0],
|
||||||
'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx'
|
b'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx'
|
||||||
)
|
)
|
||||||
self.assertEqual(len(account.change_keys.addresses), 1)
|
self.assertEqual(len(account.change_keys.addresses), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
account.change_keys.addresses[0],
|
account.change_keys.addresses[0],
|
||||||
'bFpHENtqugKKHDshKFq2Mnb59Y2bx4vKgL'
|
b'bFpHENtqugKKHDshKFq2Mnb59Y2bx4vKgL'
|
||||||
)
|
)
|
||||||
|
|
||||||
account_data['coin'] = 'lbc_mainnet'
|
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
|
# pub key
|
||||||
b'be16e4b0f9bd8f6d47d02b3a887049c36d3b84cb'
|
b'be16e4b0f9bd8f6d47d02b3a887049c36d3b84cb'
|
||||||
),
|
),
|
||||||
'b504636174734cdc080110011a7808011230080410011a084d616361726f6e6922002a003214416c6c207'
|
b'b504636174734cdc080110011a7808011230080410011a084d616361726f6e6922002a003214416c6c207'
|
||||||
'269676874732072657365727665642e38004a0052005a001a42080110011a30add80aaf02559ba0985363'
|
b'269676874732072657365727665642e38004a0052005a001a42080110011a30add80aaf02559ba0985363'
|
||||||
'6a0658c42b727cb5bb4ba8acedb4b7fe656065a47a31878dbf9912135ddb9e13806cc1479d220a696d616'
|
b'6a0658c42b727cb5bb4ba8acedb4b7fe656065a47a31878dbf9912135ddb9e13806cc1479d220a696d616'
|
||||||
'7652f6a7065672a5c080110031a404180cc0fa4d3839ee29cca866baed25fafb43fca1eb3b608ee889d35'
|
b'7652f6a7065672a5c080110031a404180cc0fa4d3839ee29cca866baed25fafb43fca1eb3b608ee889d35'
|
||||||
'1d3573d042c7b83e2e643db0d8e062a04e6e9ae6b90540a2f95fe28638d0f18af4361a1c2214f73de93f4'
|
b'1d3573d042c7b83e2e643db0d8e062a04e6e9ae6b90540a2f95fe28638d0f18af4361a1c2214f73de93f4'
|
||||||
'299fb32c32f949e02198a8e91101abd6d7576a914be16e4b0f9bd8f6d47d02b3a887049c36d3b84cb88ac'
|
b'299fb32c32f949e02198a8e91101abd6d7576a914be16e4b0f9bd8f6d47d02b3a887049c36d3b84cb88ac'
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
|
||||||
from torba.account import Account
|
from torba.baseaccount import Account
|
||||||
from torba.constants import CENT, COIN
|
from torba.constants import CENT, COIN
|
||||||
from torba.wallet import Wallet
|
from torba.wallet import Wallet
|
||||||
|
from torba.basetransaction import NULL_HASH
|
||||||
|
|
||||||
from lbrynet.wallet.coin import LBC
|
from lbrynet.wallet.coin import LBC
|
||||||
from lbrynet.wallet.transaction import Transaction, Output, Input
|
from lbrynet.wallet.transaction import Transaction, Output, Input
|
||||||
from lbrynet.wallet.manager import LbryWalletManager
|
from lbrynet.wallet.manager import LbryWalletManager
|
||||||
|
|
||||||
|
|
||||||
NULL_HASH = '\x00'*32
|
|
||||||
FEE_PER_BYTE = 50
|
FEE_PER_BYTE = 50
|
||||||
FEE_PER_CHAR = 200000
|
FEE_PER_CHAR = 200000
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ def get_transaction(txo=None):
|
||||||
.add_outputs([txo or Output.pay_pubkey_hash(CENT, NULL_HASH)])
|
.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(
|
return get_transaction(
|
||||||
Output.pay_claim_name_pubkey_hash(CENT, claim_name, claim, NULL_HASH)
|
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):
|
def test_claim_name_transaction_size_and_fee(self):
|
||||||
# fee based on claim name is the larger fee
|
# fee based on claim name is the larger fee
|
||||||
claim_name = 'verylongname'
|
claim_name = b'verylongname'
|
||||||
tx = get_claim_transaction(claim_name, '0'*4000)
|
tx = get_claim_transaction(claim_name, b'0'*4000)
|
||||||
base_size = tx.size - 1 - tx.inputs[0].size
|
base_size = tx.size - 1 - tx.inputs[0].size
|
||||||
self.assertEqual(tx.size, 4225)
|
self.assertEqual(tx.size, 4225)
|
||||||
self.assertEqual(tx.base_size, base_size)
|
self.assertEqual(tx.base_size, base_size)
|
||||||
self.assertEqual(self.coin.get_transaction_base_fee(tx), len(claim_name) * FEE_PER_CHAR)
|
self.assertEqual(self.coin.get_transaction_base_fee(tx), len(claim_name) * FEE_PER_CHAR)
|
||||||
# fee based on total bytes is the larger fee
|
# fee based on total bytes is the larger fee
|
||||||
claim_name = 'a'
|
claim_name = b'a'
|
||||||
tx = get_claim_transaction(claim_name, '0'*4000)
|
tx = get_claim_transaction(claim_name, b'0'*4000)
|
||||||
base_size = tx.size - 1 - tx.inputs[0].size
|
base_size = tx.size - 1 - tx.inputs[0].size
|
||||||
self.assertEqual(tx.size, 4214)
|
self.assertEqual(tx.size, 4214)
|
||||||
self.assertEqual(tx.base_size, base_size)
|
self.assertEqual(tx.base_size, base_size)
|
||||||
|
@ -168,7 +168,7 @@ class TestTransactionSerialization(unittest.TestCase):
|
||||||
"00001976a914f521178feb733a719964e1da4a9efb09dcc39cfa88ac00000000"
|
"00001976a914f521178feb733a719964e1da4a9efb09dcc39cfa88ac00000000"
|
||||||
)
|
)
|
||||||
tx = Transaction(raw)
|
tx = Transaction(raw)
|
||||||
self.assertEqual(hexlify(tx.id), b'666c3d15de1d6949a4fe717126c368e274b36957dce29fd401138c1e87e92a62')
|
self.assertEqual(tx.id, b'666c3d15de1d6949a4fe717126c368e274b36957dce29fd401138c1e87e92a62')
|
||||||
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)
|
||||||
|
@ -238,11 +238,10 @@ class TestTransactionSigning(unittest.TestCase):
|
||||||
pubkey_hash2 = account.coin.address_to_hash160(address2)
|
pubkey_hash2 = account.coin.address_to_hash160(address2)
|
||||||
|
|
||||||
tx = Transaction() \
|
tx = Transaction() \
|
||||||
.add_inputs([Input.spend(get_output(2*COIN, pubkey_hash1))]) \
|
.add_inputs([Input.spend(get_output(int(2*COIN), pubkey_hash1))]) \
|
||||||
.add_outputs([Output.pay_pubkey_hash(1.9*COIN, pubkey_hash2)]) \
|
.add_outputs([Output.pay_pubkey_hash(int(1.9*COIN), pubkey_hash2)]) \
|
||||||
.sign(account)
|
.sign(account)
|
||||||
|
|
||||||
print(hexlify(tx.inputs[0].script.values['signature']))
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
hexlify(tx.inputs[0].script.values['signature']),
|
hexlify(tx.inputs[0].script.values['signature']),
|
||||||
b'304402200dafa26ad7cf38c5a971c8a25ce7d85a076235f146126762296b1223c42ae21e022020ef9eeb8'
|
b'304402200dafa26ad7cf38c5a971c8a25ce7d85a076235f146126762296b1223c42ae21e022020ef9eeb8'
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
|
__coin__ = 'LBC'
|
||||||
|
|
||||||
from .coin import LBC, LBCRegTest
|
from .coin import LBC, LBCRegTest
|
||||||
from .manager import LbryWalletManager
|
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.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 .network import Network
|
||||||
|
from .transaction import Transaction
|
||||||
|
from .database import WalletDatabase
|
||||||
|
|
||||||
|
|
||||||
class LBCLedger(BaseLedger):
|
class Headers(BaseHeaders):
|
||||||
network_class = Network
|
|
||||||
header_size = 112
|
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
|
max_target = 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
genesis_hash = '9c89283ba0f3227f6c03b70216b9f665f0118d5e0fa729cedf4fb34d6a34f463'
|
genesis_hash = '9c89283ba0f3227f6c03b70216b9f665f0118d5e0fa729cedf4fb34d6a34f463'
|
||||||
genesis_bits = 0x1f00ffff
|
genesis_bits = 0x1f00ffff
|
||||||
target_timespan = 150
|
target_timespan = 150
|
||||||
|
|
||||||
|
|
||||||
class MainNetLedger(LBCLedger):
|
class TestNetLedger(Ledger):
|
||||||
pass
|
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):
|
class UnverifiedHeaders(Headers):
|
||||||
pass
|
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
|
max_target = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
genesis_hash = '6e3fcf1299d4ec5d79c3a4c91d624a4acf9e2e173d95a1a0504f677669687556'
|
genesis_hash = '6e3fcf1299d4ec5d79c3a4c91d624a4acf9e2e173d95a1a0504f677669687556'
|
||||||
genesis_bits = 0x207fffff
|
genesis_bits = 0x207fffff
|
||||||
target_timespan = 1
|
target_timespan = 1
|
||||||
verify_bits_to_target = False
|
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import os
|
import os
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from lbrynet.database.storage import SQLiteStorage
|
||||||
|
|
||||||
|
from torba.basetransaction import NULL_HASH
|
||||||
from torba.constants import COIN
|
from torba.constants import COIN
|
||||||
|
from torba.coinselection import CoinSelector
|
||||||
from torba.manager import WalletManager as BaseWalletManager
|
from torba.manager import WalletManager as BaseWalletManager
|
||||||
|
|
||||||
|
from .transaction import Transaction, Output, Input
|
||||||
|
|
||||||
|
|
||||||
class BackwardsCompatibleNetwork:
|
class BackwardsCompatibleNetwork:
|
||||||
def __init__(self, manager):
|
def __init__(self, manager):
|
||||||
|
@ -18,6 +24,15 @@ class BackwardsCompatibleNetwork:
|
||||||
|
|
||||||
class LbryWalletManager(BaseWalletManager):
|
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
|
@property
|
||||||
def wallet(self):
|
def wallet(self):
|
||||||
return self
|
return self
|
||||||
|
@ -54,15 +69,6 @@ class LbryWalletManager(BaseWalletManager):
|
||||||
)
|
)
|
||||||
return wallet_manager
|
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):
|
def get_best_blockhash(self):
|
||||||
return defer.succeed('')
|
return defer.succeed('')
|
||||||
|
|
||||||
|
@ -86,8 +92,9 @@ class LbryWalletManager(BaseWalletManager):
|
||||||
def get_info_exchanger(self):
|
def get_info_exchanger(self):
|
||||||
return LBRYcrdAddressRequester(self)
|
return LBRYcrdAddressRequester(self)
|
||||||
|
|
||||||
def resolve(self, *uris, **kwargs):
|
def resolve(self, *uris):
|
||||||
return defer.succeed({})
|
ledger = self.default_account.coin.ledger # type: LBCLedger
|
||||||
|
return ledger.resolve(uris)
|
||||||
|
|
||||||
def get_name_claims(self):
|
def get_name_claims(self):
|
||||||
return defer.succeed([])
|
return defer.succeed([])
|
||||||
|
@ -98,6 +105,46 @@ class LbryWalletManager(BaseWalletManager):
|
||||||
def get_history(self):
|
def get_history(self):
|
||||||
return defer.succeed([])
|
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:
|
class ReservedPoints:
|
||||||
def __init__(self, identifier, amount):
|
def __init__(self, identifier, amount):
|
||||||
|
|
|
@ -2,4 +2,6 @@ from torba.basenetwork import BaseNetwork
|
||||||
|
|
||||||
|
|
||||||
class Network(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