2018-10-30 17:52:52 +01:00
|
|
|
import logging
|
2018-08-16 01:23:06 +02:00
|
|
|
from decimal import Decimal
|
|
|
|
from binascii import hexlify
|
|
|
|
from datetime import datetime
|
|
|
|
from json import JSONEncoder
|
2018-10-26 00:54:07 +02:00
|
|
|
from ecdsa import BadSignatureError
|
2019-03-20 22:31:00 +01:00
|
|
|
from lbrynet.schema.claim import Claim
|
2019-04-06 21:33:07 +02:00
|
|
|
from lbrynet.wallet.ledger import MainNetLedger, Account
|
2019-03-20 06:46:23 +01:00
|
|
|
from lbrynet.wallet.transaction import Transaction, Output
|
|
|
|
from lbrynet.wallet.dewies import dewies_to_lbc
|
2019-04-06 21:33:07 +02:00
|
|
|
from lbrynet.stream.managed_stream import ManagedStream
|
2018-08-16 01:23:06 +02:00
|
|
|
|
|
|
|
|
2018-10-30 17:52:52 +01:00
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2019-04-06 21:33:07 +02:00
|
|
|
def encode_txo_doc():
|
|
|
|
return {
|
|
|
|
'txid': "hash of transaction in hex",
|
|
|
|
'height': "block where transaction was recorded",
|
|
|
|
'nout': "position in the transaction",
|
|
|
|
'amount': "value of the txo as a decimal",
|
|
|
|
'address': "address of who can spend the txo",
|
|
|
|
'confirmations': "number of confirmed blocks"
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def encode_tx_doc():
|
|
|
|
return {
|
|
|
|
'txid': "hash of transaction in hex",
|
|
|
|
'height': "block where transaction was recorded",
|
|
|
|
'inputs': [encode_txo_doc()],
|
|
|
|
'outputs': [encode_txo_doc()],
|
|
|
|
'total_input': "sum of inputs as a decimal",
|
|
|
|
'total_output': "sum of outputs, sans fee, as a decimal",
|
|
|
|
'total_fee': "fee amount",
|
|
|
|
'hex': "entire transaction encoded in hex",
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def encode_account_doc():
|
|
|
|
return {
|
|
|
|
'id': 'account_id',
|
|
|
|
'is_default': 'this account is used by default',
|
|
|
|
'ledger': 'name of crypto currency and network',
|
|
|
|
'name': 'optional account name',
|
|
|
|
'seed': 'human friendly words from which account can be recreated',
|
|
|
|
'encrypted': 'if account is encrypted',
|
|
|
|
'private_key': 'extended private key',
|
|
|
|
'public_key': 'extended public key',
|
|
|
|
'address_generator': 'settings for generating addresses',
|
|
|
|
'modified_on': 'date of last modification to account settings'
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def encode_file_doc():
|
|
|
|
return {
|
|
|
|
'completed': '(bool) true if download is completed',
|
|
|
|
'file_name': '(str) name of file',
|
|
|
|
'download_directory': '(str) download directory',
|
|
|
|
'points_paid': '(float) credit paid to download file',
|
|
|
|
'stopped': '(bool) true if download is stopped',
|
|
|
|
'stream_hash': '(str) stream hash of file',
|
|
|
|
'stream_name': '(str) stream name',
|
|
|
|
'suggested_file_name': '(str) suggested file name',
|
|
|
|
'sd_hash': '(str) sd hash of file',
|
|
|
|
'download_path': '(str) download path of file',
|
|
|
|
'mime_type': '(str) mime type of file',
|
|
|
|
'key': '(str) key attached to file',
|
|
|
|
'total_bytes_lower_bound': '(int) lower bound file size in bytes',
|
|
|
|
'total_bytes': '(int) file upper bound size in bytes',
|
|
|
|
'written_bytes': '(int) written size in bytes',
|
|
|
|
'blobs_completed': '(int) number of fully downloaded blobs',
|
|
|
|
'blobs_in_stream': '(int) total blobs on stream',
|
|
|
|
'blobs_remaining': '(int) total blobs remaining to download',
|
|
|
|
'status': '(str) downloader status',
|
|
|
|
'claim_id': '(str) None if claim is not found else the claim id',
|
|
|
|
'txid': '(str) None if claim is not found else the transaction id',
|
|
|
|
'nout': '(int) None if claim is not found else the transaction output index',
|
|
|
|
'outpoint': '(str) None if claim is not found else the tx and output',
|
|
|
|
'metadata': '(dict) None if claim is not found else the claim metadata',
|
|
|
|
'channel_claim_id': '(str) None if claim is not found or not signed',
|
|
|
|
'channel_name': '(str) None if claim is not found or not signed',
|
|
|
|
'claim_name': '(str) None if claim is not found else the claim name'
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-08-16 01:23:06 +02:00
|
|
|
class JSONResponseEncoder(JSONEncoder):
|
|
|
|
|
2018-10-12 15:49:13 +02:00
|
|
|
def __init__(self, *args, ledger: MainNetLedger, **kwargs):
|
2018-08-16 01:23:06 +02:00
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.ledger = ledger
|
|
|
|
|
2018-08-16 17:24:22 +02:00
|
|
|
def default(self, obj): # pylint: disable=method-hidden
|
2019-04-06 21:33:07 +02:00
|
|
|
if isinstance(obj, Account):
|
|
|
|
return self.encode_account(obj)
|
|
|
|
if isinstance(obj, ManagedStream):
|
|
|
|
return self.encode_file(obj)
|
2018-08-16 01:23:06 +02:00
|
|
|
if isinstance(obj, Transaction):
|
|
|
|
return self.encode_transaction(obj)
|
|
|
|
if isinstance(obj, Output):
|
|
|
|
return self.encode_output(obj)
|
2019-03-20 22:31:00 +01:00
|
|
|
if isinstance(obj, Claim):
|
2019-03-22 08:38:54 +01:00
|
|
|
claim_dict = obj.to_dict()
|
|
|
|
if obj.is_stream:
|
2019-03-28 01:32:43 +01:00
|
|
|
claim_dict['stream']['sd_hash'] = obj.stream.sd_hash
|
2019-03-30 01:26:10 +01:00
|
|
|
fee = claim_dict['stream'].get('fee', {})
|
|
|
|
if 'address' in fee:
|
|
|
|
fee['address'] = obj.stream.fee.address
|
|
|
|
if 'amount' in fee:
|
|
|
|
fee['amount'] = obj.stream.fee.amount
|
|
|
|
if 'languages' in claim_dict['stream']:
|
|
|
|
claim_dict['stream']['languages'] = obj.stream.langtags
|
2019-03-28 01:32:43 +01:00
|
|
|
elif obj.is_channel:
|
|
|
|
claim_dict['channel']['public_key'] = obj.channel.public_key
|
2019-03-30 01:26:10 +01:00
|
|
|
if 'languages' in claim_dict['channel']:
|
|
|
|
claim_dict['channel']['languages'] = obj.channel.langtags
|
2019-03-22 08:38:54 +01:00
|
|
|
return claim_dict
|
2018-08-16 01:23:06 +02:00
|
|
|
if isinstance(obj, datetime):
|
|
|
|
return obj.strftime("%Y%m%dT%H:%M:%S")
|
|
|
|
if isinstance(obj, Decimal):
|
|
|
|
return float(obj)
|
2018-08-16 21:55:33 +02:00
|
|
|
if isinstance(obj, bytes):
|
|
|
|
return obj.decode()
|
2018-08-16 01:23:06 +02:00
|
|
|
return super().default(obj)
|
|
|
|
|
|
|
|
def encode_transaction(self, tx):
|
|
|
|
return {
|
|
|
|
'txid': tx.id,
|
2018-09-22 04:19:32 +02:00
|
|
|
'height': tx.height,
|
2018-08-16 01:23:06 +02:00
|
|
|
'inputs': [self.encode_input(txo) for txo in tx.inputs],
|
|
|
|
'outputs': [self.encode_output(txo) for txo in tx.outputs],
|
2018-10-03 22:38:47 +02:00
|
|
|
'total_input': dewies_to_lbc(tx.input_sum),
|
|
|
|
'total_output': dewies_to_lbc(tx.input_sum - tx.fee),
|
|
|
|
'total_fee': dewies_to_lbc(tx.fee),
|
2018-08-16 01:23:06 +02:00
|
|
|
'hex': hexlify(tx.raw).decode(),
|
|
|
|
}
|
|
|
|
|
2019-03-24 22:44:46 +01:00
|
|
|
def encode_output(self, txo, check_signature=True):
|
2018-10-12 15:49:13 +02:00
|
|
|
tx_height = txo.tx_ref.height
|
|
|
|
best_height = self.ledger.headers.height
|
2018-09-22 04:19:32 +02:00
|
|
|
output = {
|
2018-09-19 15:58:50 +02:00
|
|
|
'txid': txo.tx_ref.id,
|
2018-08-16 01:23:06 +02:00
|
|
|
'nout': txo.position,
|
2018-10-03 22:38:47 +02:00
|
|
|
'amount': dewies_to_lbc(txo.amount),
|
2018-09-19 15:58:50 +02:00
|
|
|
'address': txo.get_address(self.ledger),
|
2018-10-12 15:49:13 +02:00
|
|
|
'height': tx_height,
|
2018-12-06 06:28:42 +01:00
|
|
|
'confirmations': (best_height+1) - tx_height if tx_height > 0 else tx_height
|
2018-08-16 01:23:06 +02:00
|
|
|
}
|
2018-09-22 04:19:32 +02:00
|
|
|
if txo.is_change is not None:
|
|
|
|
output['is_change'] = txo.is_change
|
2018-10-05 15:02:02 +02:00
|
|
|
if txo.is_my_account is not None:
|
|
|
|
output['is_mine'] = txo.is_my_account
|
2018-10-08 16:41:07 +02:00
|
|
|
|
2018-10-05 15:02:02 +02:00
|
|
|
if txo.script.is_claim_involved:
|
|
|
|
output.update({
|
|
|
|
'name': txo.claim_name,
|
|
|
|
'claim_id': txo.claim_id,
|
|
|
|
'permanent_url': txo.permanent_url,
|
|
|
|
})
|
2018-10-08 16:41:07 +02:00
|
|
|
|
|
|
|
if txo.script.is_claim_name or txo.script.is_update_claim:
|
2018-10-12 15:49:13 +02:00
|
|
|
claim = txo.claim
|
2019-03-22 08:38:54 +01:00
|
|
|
output['value'] = claim
|
2019-03-20 06:46:23 +01:00
|
|
|
if claim.is_signed:
|
2018-10-12 15:49:13 +02:00
|
|
|
output['valid_signature'] = None
|
2019-03-24 22:44:46 +01:00
|
|
|
if check_signature and txo.channel is not None:
|
2018-10-18 03:10:23 +02:00
|
|
|
output['channel_name'] = txo.channel.claim_name
|
2018-10-26 00:54:07 +02:00
|
|
|
try:
|
2019-03-20 06:46:23 +01:00
|
|
|
output['valid_signature'] = txo.is_signed_by(txo.channel, self.ledger)
|
2018-10-26 00:54:07 +02:00
|
|
|
except BadSignatureError:
|
|
|
|
output['valid_signature'] = False
|
2018-10-30 17:52:52 +01:00
|
|
|
except ValueError:
|
2018-10-30 18:48:25 +01:00
|
|
|
log.exception(
|
|
|
|
'txo.id: %s, txo.channel.id:%s, output: %s',
|
|
|
|
txo.id, txo.channel.id, output
|
|
|
|
)
|
2018-10-30 17:52:52 +01:00
|
|
|
output['valid_signature'] = False
|
2018-10-08 16:41:07 +02:00
|
|
|
|
2018-10-05 15:02:02 +02:00
|
|
|
if txo.script.is_claim_name:
|
2018-10-12 15:49:13 +02:00
|
|
|
output['type'] = 'claim'
|
2018-10-05 15:02:02 +02:00
|
|
|
elif txo.script.is_update_claim:
|
2018-10-12 15:49:13 +02:00
|
|
|
output['type'] = 'update'
|
2018-10-05 15:02:02 +02:00
|
|
|
elif txo.script.is_support_claim:
|
2018-10-12 15:49:13 +02:00
|
|
|
output['type'] = 'support'
|
|
|
|
else:
|
|
|
|
output['type'] = 'basic'
|
2018-10-08 16:41:07 +02:00
|
|
|
|
2018-09-22 04:19:32 +02:00
|
|
|
return output
|
2018-08-16 01:23:06 +02:00
|
|
|
|
|
|
|
def encode_input(self, txi):
|
2019-03-24 22:44:46 +01:00
|
|
|
return self.encode_output(txi.txo_ref.txo, False) if txi.txo_ref.txo is not None else {
|
2018-09-22 04:19:32 +02:00
|
|
|
'txid': txi.txo_ref.tx_ref.id,
|
|
|
|
'nout': txi.txo_ref.position
|
|
|
|
}
|
2019-04-06 21:33:07 +02:00
|
|
|
|
|
|
|
def encode_account(self, account):
|
|
|
|
result = account.to_dict()
|
|
|
|
result['id'] = account.id
|
|
|
|
result.pop('certificates', None)
|
|
|
|
result['is_default'] = self.ledger.accounts[0] == account
|
|
|
|
return result
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def encode_file(managed_stream):
|
|
|
|
return managed_stream.as_dict()
|