Select Git revision
transfer.rs
-
Hugo Trentesaux authoredHugo Trentesaux authored
transaction.py 8.69 KiB
from .document import Document, MalformedDocumentError
import re
class Transaction(Document):
"""
.. note:: A transaction document is specified by the following format :
| Document format :
| Version: VERSION
| Type: Transaction
| Currency: CURRENCY_NAME
| Issuers:
| PUBLIC_KEY
| ...
| Inputs:
| INDEX:SOURCE:NUMBER:FINGERPRINT:AMOUNT
| ...
| Outputs:
| PUBLIC_KEY:AMOUNT
| ...
| Comment: COMMENT
| ...
|
|
| Compact format :
| TX:VERSION:NB_ISSUERS:NB_INPUTS:NB_OUTPUTS:HAS_COMMENT
| PUBLIC_KEY:INDEX
| ...
| INDEX:SOURCE:FINGERPRINT:AMOUNT
| ...
| PUBLIC_KEY:AMOUNT
| ...
| COMMENT
| SIGNATURE
| ...
"""
re_type = re.compile("Type: (Transaction)\n")
re_header = re.compile("TX:([0-9]+):([0-9]+):([0-9]+):([0-9]+):(0|1)\n")
re_issuers = re.compile("Issuers:\n")
re_inputs = re.compile("Inputs:\n")
re_outputs = re.compile("Outputs:\n")
re_compact_comment = re.compile("([^\n]+)\n")
re_comment = re.compile("Comment: ([^\n]*)\n")
re_pubkey = re.compile("([1-9A-Za-z][^OIl]{42,45})\n")
fields_parsers = {**Document.fields_parsers, **{
"Type": re_type,
"TX": re_header,
"Issuers": re_issuers,
"Inputs": re_inputs,
"Outputs": re_outputs,
"Comment": re_comment,
"Compact comment": re_compact_comment,
"Pubkey": re_pubkey
}
}
def __init__(self, version, currency, issuers, inputs, outputs,
comment, signatures):
"""
Constructor
"""
super().__init__(version, currency, signatures)
self.issuers = issuers
self.inputs = inputs
self.outputs = outputs
self.comment = comment
@classmethod
def from_compact(cls, currency, compact):
lines = compact.splitlines(True)
n = 0
header_data = Transaction.re_header.match(lines[n])
if header_data is None:
raise MalformedDocumentError("Compact TX header")
version = int(header_data.group(1))
issuers_num = int(header_data.group(2))
inputs_num = int(header_data.group(3))
outputs_num = int(header_data.group(4))
has_comment = int(header_data.group(5))
n += 1
issuers = []
inputs = []
outputs = []
signatures = []
for i in range(0, issuers_num):
issuer = Transaction.parse_field("Pubkey", lines[n])
issuers.append(issuer)
n += 1
for i in range(0, inputs_num):
input_source = InputSource.from_inline(lines[n])
inputs.append(input_source)
n += 1
for i in range(0, outputs_num):
output_source = OutputSource.from_inline(lines[n])
outputs.append(output_source)
n += 1
comment = ""
if has_comment == 1:
comment = Transaction.re_compact_comment.match(lines[n]).group(1)
n += 1
while n < len(lines):
signatures.append(Transaction.re_signature.match(lines[n]).group(1))
n += 1
return cls(version, currency, issuers, inputs, outputs, comment, signatures)
@classmethod
def from_signed_raw(cls, raw):
lines = raw.splitlines(True)
n = 0
version = int(Transaction.parse_field("Version", lines[n]))
n += 1
Transaction.parse_field("Type", lines[n])
n += 1
currency = Transaction.parse_field("Currency", lines[n])
n += 1
issuers = []
inputs = []
outputs = []
signatures = []
if Transaction.re_issuers.match(lines[n]):
n += 1
while Transaction.re_inputs.match(lines[n]) is None:
issuer = Transaction.parse_field("Pubkey", lines[n])
issuers.append(issuer)
n += 1
if Transaction.re_inputs.match(lines[n]):
n += 1
while Transaction.re_outputs.match(lines[n]) is None:
input_source = InputSource.from_inline(lines[n])
inputs.append(input_source)
n += 1
if Transaction.re_outputs.match(lines[n]) is not None:
n += 1
while not Transaction.re_comment.match(lines[n]):
output = OutputSource.from_inline(lines[n])
outputs.append(output)
n += 1
comment = Transaction.parse_field("Comment", lines[n])
n += 1
if Transaction.re_signature.match(lines[n]) is not None:
while n < len(lines):
sign = Transaction.parse_field("Signature", lines[n])
signatures.append(sign)
n += 1
return cls(version, currency, issuers, inputs, outputs,
comment, signatures)
def raw(self):
doc = """Version: {0}
Type: Transaction
Currency: {1}
Issuers:
""".format(self.version,
self.currency)
for p in self.issuers:
doc += "{0}\n".format(p)
doc += "Inputs:\n"
for i in self.inputs:
doc += "{0}\n".format(i.inline())
doc += "Outputs:\n"
for o in self.outputs:
doc += "{0}\n".format(o.inline())
doc += "Comment: "
doc += "{0}\n".format(self.comment)
return doc
def compact(self):
"""
Return a transaction in its compact format.
"""
"""TX:VERSION:NB_ISSUERS:NB_INPUTS:NB_OUTPUTS:HAS_COMMENT
PUBLIC_KEY:INDEX
...
INDEX:SOURCE:FINGERPRINT:AMOUNT
...
PUBLIC_KEY:AMOUNT
...
COMMENT
"""
doc = "TX:{0}:{1}:{2}:{3}:{4}\n".format(self.version,
len(self.issuers),
len(self.inputs),
len(self.outputs),
'1' if self.comment != "" else '0')
for pubkey in self.issuers:
doc += "{0}\n".format(pubkey)
for i in self.inputs:
doc += "{0}\n".format(i.inline())
for o in self.outputs:
doc += "{0}\n".format(o.inline())
if self.comment != "":
doc += "{0}\n".format(self.comment)
for s in self.signatures:
doc += "{0}\n".format(s)
return doc
class SimpleTransaction(Transaction):
"""
As transaction class, but for only one issuer.
...
"""
def __init__(self, version, currency, issuer,
single_input, outputs, comment, signature):
"""
Constructor
"""
super().__init__(version, currency, [issuer], [single_input],
outputs, comment, [signature])
class InputSource:
"""
A Transaction INPUT
.. note:: Compact :
INDEX:SOURCE:FINGERPRINT:AMOUNT
"""
re_inline = re.compile("([0-9]+):(D|T):([0-9]+):([0-9a-fA-F]{5,40}):([0-9]+)\n")
re_compact = re.compile("([0-9]+):(D|T):([0-9a-fA-F]{5,40}):([0-9]+)\n")
def __init__(self, index, source, number, txhash, amount):
self.index = index
self.source = source
self.number = number
self.txhash = txhash
self.amount = amount
@classmethod
def from_inline(cls, inline):
data = InputSource.re_inline.match(inline)
if data is None:
raise MalformedDocumentError("Inline input")
index = int(data.group(1))
source = data.group(2)
number = int(data.group(3))
txhash = data.group(4)
amount = int(data.group(5))
return cls(index, source, number, txhash, amount)
@classmethod
def from_bma(cls, bma_data):
index = None
source = bma_data['type']
number = bma_data['number']
txhash = bma_data['fingerprint']
amount = bma_data['amount']
return cls(index, source, number, txhash, amount)
def inline(self):
return "{0}:{1}:{2}:{3}:{4}".format(self.index,
self.source,
self.number,
self.txhash,
self.amount)
class OutputSource():
"""
A Transaction OUTPUT
"""
re_inline = re.compile("([1-9A-Za-z][^OIl]{42,45}):([0-9]+)")
def __init__(self, pubkey, amount):
self.pubkey = pubkey
self.amount = amount
@classmethod
def from_inline(cls, inline):
data = OutputSource.re_inline.match(inline)
if data is None:
raise MalformedDocumentError("Inline output")
pubkey = data.group(1)
amount = int(data.group(2))
return cls(pubkey, amount)
def inline(self):
return "{0}:{1}".format(self.pubkey, self.amount)