mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 12:28:09 +00:00
refactor(python/stellar): Use Stellar Python SDK to parse Stellar transactions.
As a side effect, support for TransactionV1 format transaction is added.
This commit is contained in:
parent
b5710b820a
commit
543b9f407c
1
python/.changelog.d/1745.added
Normal file
1
python/.changelog.d/1745.added
Normal file
@ -0,0 +1 @@
|
|||||||
|
`trezorlib.stellar.from_envelope` was added, it includes support for the Stellar [TransactionV1](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md#xdr) format transaction.
|
1
python/.changelog.d/1745.incompatible
Normal file
1
python/.changelog.d/1745.incompatible
Normal file
@ -0,0 +1 @@
|
|||||||
|
`trezorlib.stellar` was reworked to use stellar-sdk instead of providing local implementations
|
@ -55,7 +55,14 @@ units) will not be recognized, unless you install HIDAPI support (see below).
|
|||||||
pip3 install trezor[ethereum]
|
pip3 install trezor[ethereum]
|
||||||
```
|
```
|
||||||
|
|
||||||
To install both, use `pip3 install trezor[hidapi,ethereum]`.
|
* **Stellar**: To support Stellar signing from command line, additional packages are
|
||||||
|
needed. Install with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pip3 install trezor[stellar]
|
||||||
|
```
|
||||||
|
|
||||||
|
To install all three, use `pip3 install trezor[hidapi,ethereum,stellar]`.
|
||||||
|
|
||||||
### Distro packages
|
### Distro packages
|
||||||
|
|
||||||
|
@ -2,3 +2,4 @@ hidapi >= 0.7.99.post20
|
|||||||
rlp >= 1.1.0
|
rlp >= 1.1.0
|
||||||
web3 >= 4.8
|
web3 >= 4.8
|
||||||
Pillow
|
Pillow
|
||||||
|
stellar-sdk>=4.0.0,<5.0.0
|
||||||
|
@ -22,6 +22,7 @@ extras_require = {
|
|||||||
"ethereum": ["rlp>=1.1.0", "web3>=4.8"],
|
"ethereum": ["rlp>=1.1.0", "web3>=4.8"],
|
||||||
"qt-widgets": ["PyQt5"],
|
"qt-widgets": ["PyQt5"],
|
||||||
"extra": ["Pillow"],
|
"extra": ["Pillow"],
|
||||||
|
"stellar": ["stellar-sdk>=4.0.0,<5.0.0"],
|
||||||
}
|
}
|
||||||
|
|
||||||
extras_require["full"] = sum(extras_require.values(), [])
|
extras_require["full"] = sum(extras_require.values(), [])
|
||||||
|
@ -15,12 +15,21 @@
|
|||||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import sys
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from .. import stellar, tools
|
from .. import stellar, tools
|
||||||
from . import with_client
|
from . import with_client
|
||||||
|
|
||||||
|
try:
|
||||||
|
from stellar_sdk import (
|
||||||
|
parse_transaction_envelope_from_xdr,
|
||||||
|
FeeBumpTransactionEnvelope,
|
||||||
|
)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
PATH_HELP = "BIP32 path. Always use hardened paths and the m/44'/148'/ prefix"
|
PATH_HELP = "BIP32 path. Always use hardened paths and the m/44'/148'/ prefix"
|
||||||
|
|
||||||
|
|
||||||
@ -68,8 +77,20 @@ def sign_transaction(client, b64envelope, address, network_passphrase):
|
|||||||
For testnet transactions, use the following network passphrase:
|
For testnet transactions, use the following network passphrase:
|
||||||
'Test SDF Network ; September 2015'
|
'Test SDF Network ; September 2015'
|
||||||
"""
|
"""
|
||||||
|
if not stellar.HAVE_STELLAR_SDK:
|
||||||
|
click.echo("Stellar requirements not installed.")
|
||||||
|
click.echo("Please run:")
|
||||||
|
click.echo()
|
||||||
|
click.echo(" pip install stellar-sdk")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
envelope = parse_transaction_envelope_from_xdr(b64envelope, network_passphrase)
|
||||||
|
if isinstance(envelope, FeeBumpTransactionEnvelope):
|
||||||
|
click.echo("FeeBumpTransactionEnvelope is not supported")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
address_n = tools.parse_path(address)
|
address_n = tools.parse_path(address)
|
||||||
tx, operations = stellar.parse_transaction_bytes(base64.b64decode(b64envelope))
|
tx, operations = stellar.from_envelope(envelope)
|
||||||
resp = stellar.sign_tx(client, tx, operations, address_n, network_passphrase)
|
resp = stellar.sign_tx(client, tx, operations, address_n, network_passphrase)
|
||||||
|
|
||||||
return base64.b64encode(resp.signature)
|
return base64.b64encode(resp.signature)
|
||||||
|
@ -13,14 +13,45 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the License along with this library.
|
# You should have received a copy of the License along with this library.
|
||||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
from decimal import Decimal
|
||||||
import base64
|
from typing import Union
|
||||||
import struct
|
|
||||||
import xdrlib
|
|
||||||
|
|
||||||
from . import exceptions, messages
|
from . import exceptions, messages
|
||||||
from .tools import expect
|
from .tools import expect
|
||||||
|
|
||||||
|
try:
|
||||||
|
from stellar_sdk import (
|
||||||
|
AccountMerge,
|
||||||
|
AllowTrust,
|
||||||
|
Asset,
|
||||||
|
BumpSequence,
|
||||||
|
ChangeTrust,
|
||||||
|
CreateAccount,
|
||||||
|
CreatePassiveSellOffer,
|
||||||
|
HashMemo,
|
||||||
|
IdMemo,
|
||||||
|
ManageData,
|
||||||
|
ManageSellOffer,
|
||||||
|
Operation,
|
||||||
|
PathPaymentStrictReceive,
|
||||||
|
Payment,
|
||||||
|
ReturnHashMemo,
|
||||||
|
SetOptions,
|
||||||
|
TextMemo,
|
||||||
|
TransactionEnvelope,
|
||||||
|
TrustLineEntryFlag,
|
||||||
|
Price,
|
||||||
|
Network,
|
||||||
|
)
|
||||||
|
from stellar_sdk.xdr.signer_key_type import SignerKeyType
|
||||||
|
|
||||||
|
HAVE_STELLAR_SDK = True
|
||||||
|
DEFAULT_NETWORK_PASSPHRASE = Network.PUBLIC_NETWORK_PASSPHRASE
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
HAVE_STELLAR_SDK = False
|
||||||
|
DEFAULT_NETWORK_PASSPHRASE = "Public Global Stellar Network ; September 2015"
|
||||||
|
|
||||||
# Memo types
|
# Memo types
|
||||||
MEMO_TYPE_NONE = 0
|
MEMO_TYPE_NONE = 0
|
||||||
MEMO_TYPE_TEXT = 1
|
MEMO_TYPE_TEXT = 1
|
||||||
@ -33,309 +64,191 @@ ASSET_TYPE_NATIVE = 0
|
|||||||
ASSET_TYPE_ALPHA4 = 1
|
ASSET_TYPE_ALPHA4 = 1
|
||||||
ASSET_TYPE_ALPHA12 = 2
|
ASSET_TYPE_ALPHA12 = 2
|
||||||
|
|
||||||
# Operations
|
|
||||||
OP_CREATE_ACCOUNT = 0
|
|
||||||
OP_PAYMENT = 1
|
|
||||||
OP_PATH_PAYMENT = 2
|
|
||||||
OP_MANAGE_OFFER = 3
|
|
||||||
OP_CREATE_PASSIVE_OFFER = 4
|
|
||||||
OP_SET_OPTIONS = 5
|
|
||||||
OP_CHANGE_TRUST = 6
|
|
||||||
OP_ALLOW_TRUST = 7
|
|
||||||
OP_ACCOUNT_MERGE = 8
|
|
||||||
OP_INFLATION = 9 # Included for documentation purposes, not supported by Trezor
|
|
||||||
OP_MANAGE_DATA = 10
|
|
||||||
OP_BUMP_SEQUENCE = 11
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_BIP32_PATH = "m/44h/148h/0h"
|
DEFAULT_BIP32_PATH = "m/44h/148h/0h"
|
||||||
# Stellar's BIP32 differs to Bitcoin's see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md
|
# Stellar's BIP32 differs to Bitcoin's see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md
|
||||||
DEFAULT_NETWORK_PASSPHRASE = "Public Global Stellar Network ; September 2015"
|
|
||||||
|
|
||||||
|
|
||||||
def address_from_public_key(pk_bytes):
|
def from_envelope(envelope: "TransactionEnvelope"):
|
||||||
"""Returns the base32-encoded version of pk_bytes (G...)"""
|
"""Parses transaction envelope into a map with the following keys:
|
||||||
final_bytes = bytearray()
|
|
||||||
|
|
||||||
# version
|
|
||||||
final_bytes.append(6 << 3)
|
|
||||||
# public key
|
|
||||||
final_bytes.extend(pk_bytes)
|
|
||||||
# checksum
|
|
||||||
final_bytes.extend(struct.pack("<H", _crc16_checksum(final_bytes)))
|
|
||||||
|
|
||||||
return base64.b32encode(final_bytes).decode()
|
|
||||||
|
|
||||||
|
|
||||||
def address_to_public_key(address_str):
|
|
||||||
"""Returns the raw 32 bytes representing a public key by extracting
|
|
||||||
it from the G... string
|
|
||||||
"""
|
|
||||||
decoded = base64.b32decode(address_str)
|
|
||||||
|
|
||||||
# skip 0th byte (version) and last two bytes (checksum)
|
|
||||||
return decoded[1:-2]
|
|
||||||
|
|
||||||
|
|
||||||
def parse_transaction_bytes(tx_bytes):
|
|
||||||
"""Parses base64data into a map with the following keys:
|
|
||||||
tx - a StellarSignTx describing the transaction header
|
tx - a StellarSignTx describing the transaction header
|
||||||
operations - an array of protobuf message objects for each operation
|
operations - an array of protobuf message objects for each operation
|
||||||
"""
|
"""
|
||||||
|
if not HAVE_STELLAR_SDK:
|
||||||
|
raise RuntimeError("Stellar SDK not available")
|
||||||
tx = messages.StellarSignTx()
|
tx = messages.StellarSignTx()
|
||||||
unpacker = xdrlib.Unpacker(tx_bytes)
|
parsed_tx = envelope.transaction
|
||||||
|
tx.source_account = parsed_tx.source.account_id
|
||||||
tx.source_account = _xdr_read_address(unpacker)
|
tx.fee = parsed_tx.fee
|
||||||
tx.fee = unpacker.unpack_uint()
|
tx.sequence_number = parsed_tx.sequence
|
||||||
tx.sequence_number = unpacker.unpack_uhyper()
|
|
||||||
|
|
||||||
# Timebounds is an optional field
|
# Timebounds is an optional field
|
||||||
if unpacker.unpack_bool():
|
if parsed_tx.time_bounds:
|
||||||
max_timebound = 2 ** 32 - 1 # max unsigned 32-bit int
|
tx.timebounds_start = parsed_tx.time_bounds.min_time
|
||||||
# (trezor does not support the full 64-bit time value)
|
tx.timebounds_end = parsed_tx.time_bounds.max_time
|
||||||
|
|
||||||
tx.timebounds_start = unpacker.unpack_uhyper()
|
memo = parsed_tx.memo
|
||||||
tx.timebounds_end = unpacker.unpack_uhyper()
|
if isinstance(memo, TextMemo):
|
||||||
|
# memo_text is specified as UTF-8 string, but returned as bytes from the XDR parser
|
||||||
if tx.timebounds_start > max_timebound or tx.timebounds_start < 0:
|
tx.memo_type = MEMO_TYPE_TEXT
|
||||||
raise ValueError(
|
tx.memo_text = memo.memo_text.decode("utf-8")
|
||||||
"Starting timebound out of range (must be between 0 and "
|
elif isinstance(memo, IdMemo):
|
||||||
+ max_timebound
|
tx.memo_type = MEMO_TYPE_ID
|
||||||
)
|
tx.memo_id = memo.memo_id
|
||||||
if tx.timebounds_end > max_timebound or tx.timebounds_end < 0:
|
elif isinstance(memo, HashMemo):
|
||||||
raise ValueError(
|
tx.memo_type = MEMO_TYPE_HASH
|
||||||
"Ending timebound out of range (must be between 0 and " + max_timebound
|
tx.memo_hash = memo.memo_hash
|
||||||
)
|
elif isinstance(memo, ReturnHashMemo):
|
||||||
|
tx.memo_type = MEMO_TYPE_RETURN
|
||||||
# memo type determines what optional fields are set
|
tx.memo_hash = memo.memo_return
|
||||||
tx.memo_type = unpacker.unpack_uint()
|
else:
|
||||||
|
tx.memo_type = MEMO_TYPE_NONE
|
||||||
# text
|
|
||||||
if tx.memo_type == MEMO_TYPE_TEXT:
|
|
||||||
tx.memo_text = unpacker.unpack_string().decode()
|
|
||||||
# id (64-bit uint)
|
|
||||||
if tx.memo_type == MEMO_TYPE_ID:
|
|
||||||
tx.memo_id = unpacker.unpack_uhyper()
|
|
||||||
# hash / return are the same structure (32 bytes representing a hash)
|
|
||||||
if tx.memo_type == MEMO_TYPE_HASH or tx.memo_type == MEMO_TYPE_RETURN:
|
|
||||||
tx.memo_hash = unpacker.unpack_fopaque(32)
|
|
||||||
|
|
||||||
tx.num_operations = unpacker.unpack_uint()
|
|
||||||
|
|
||||||
operations = []
|
|
||||||
for _ in range(tx.num_operations):
|
|
||||||
operations.append(_parse_operation_bytes(unpacker))
|
|
||||||
|
|
||||||
|
tx.num_operations = len(parsed_tx.operations)
|
||||||
|
operations = [_read_operation(op) for op in parsed_tx.operations]
|
||||||
return tx, operations
|
return tx, operations
|
||||||
|
|
||||||
|
|
||||||
def _parse_operation_bytes(unpacker):
|
def _read_operation(op: "Operation"):
|
||||||
"""Returns a protobuf message representing the next operation as read from
|
# TODO: Let's add muxed account support later.
|
||||||
the byte stream in unpacker
|
if op.source:
|
||||||
"""
|
source_account = op.source.account_id
|
||||||
|
else:
|
||||||
# Check for and parse optional source account field
|
|
||||||
source_account = None
|
source_account = None
|
||||||
if unpacker.unpack_bool():
|
if isinstance(op, CreateAccount):
|
||||||
source_account = unpacker.unpack_fopaque(32)
|
|
||||||
|
|
||||||
# Operation type (See OP_ constants)
|
|
||||||
type = unpacker.unpack_uint()
|
|
||||||
|
|
||||||
if type == OP_CREATE_ACCOUNT:
|
|
||||||
return messages.StellarCreateAccountOp(
|
return messages.StellarCreateAccountOp(
|
||||||
source_account=source_account,
|
source_account=source_account,
|
||||||
new_account=_xdr_read_address(unpacker),
|
new_account=op.destination,
|
||||||
starting_balance=unpacker.unpack_hyper(),
|
starting_balance=_read_amount(op.starting_balance),
|
||||||
)
|
)
|
||||||
|
if isinstance(op, Payment):
|
||||||
if type == OP_PAYMENT:
|
|
||||||
return messages.StellarPaymentOp(
|
return messages.StellarPaymentOp(
|
||||||
source_account=source_account,
|
source_account=source_account,
|
||||||
destination_account=_xdr_read_address(unpacker),
|
destination_account=op.destination.account_id,
|
||||||
asset=_xdr_read_asset(unpacker),
|
asset=_read_asset(op.asset),
|
||||||
amount=unpacker.unpack_hyper(),
|
amount=_read_amount(op.amount),
|
||||||
)
|
)
|
||||||
|
if isinstance(op, PathPaymentStrictReceive):
|
||||||
if type == OP_PATH_PAYMENT:
|
operation = messages.StellarPathPaymentOp(
|
||||||
op = messages.StellarPathPaymentOp(
|
|
||||||
source_account=source_account,
|
source_account=source_account,
|
||||||
send_asset=_xdr_read_asset(unpacker),
|
send_asset=_read_asset(op.send_asset),
|
||||||
send_max=unpacker.unpack_hyper(),
|
send_max=_read_amount(op.send_max),
|
||||||
destination_account=_xdr_read_address(unpacker),
|
destination_account=op.destination.account_id,
|
||||||
destination_asset=_xdr_read_asset(unpacker),
|
destination_asset=_read_asset(op.dest_asset),
|
||||||
destination_amount=unpacker.unpack_hyper(),
|
destination_amount=_read_amount(op.dest_amount),
|
||||||
paths=[],
|
paths=[_read_asset(asset) for asset in op.path],
|
||||||
)
|
)
|
||||||
|
return operation
|
||||||
num_paths = unpacker.unpack_uint()
|
if isinstance(op, ManageSellOffer):
|
||||||
for _ in range(num_paths):
|
price = _read_price(op.price)
|
||||||
op.paths.append(_xdr_read_asset(unpacker))
|
|
||||||
|
|
||||||
return op
|
|
||||||
|
|
||||||
if type == OP_MANAGE_OFFER:
|
|
||||||
return messages.StellarManageOfferOp(
|
return messages.StellarManageOfferOp(
|
||||||
source_account=source_account,
|
source_account=source_account,
|
||||||
selling_asset=_xdr_read_asset(unpacker),
|
selling_asset=_read_asset(op.selling),
|
||||||
buying_asset=_xdr_read_asset(unpacker),
|
buying_asset=_read_asset(op.buying),
|
||||||
amount=unpacker.unpack_hyper(),
|
amount=_read_amount(op.amount),
|
||||||
price_n=unpacker.unpack_uint(),
|
price_n=price.n,
|
||||||
price_d=unpacker.unpack_uint(),
|
price_d=price.d,
|
||||||
offer_id=unpacker.unpack_uhyper(),
|
offer_id=op.offer_id,
|
||||||
)
|
)
|
||||||
|
if isinstance(op, CreatePassiveSellOffer):
|
||||||
if type == OP_CREATE_PASSIVE_OFFER:
|
price = _read_price(op.price)
|
||||||
return messages.StellarCreatePassiveOfferOp(
|
return messages.StellarCreatePassiveOfferOp(
|
||||||
source_account=source_account,
|
source_account=source_account,
|
||||||
selling_asset=_xdr_read_asset(unpacker),
|
selling_asset=_read_asset(op.selling),
|
||||||
buying_asset=_xdr_read_asset(unpacker),
|
buying_asset=_read_asset(op.buying),
|
||||||
amount=unpacker.unpack_hyper(),
|
amount=_read_amount(op.amount),
|
||||||
price_n=unpacker.unpack_uint(),
|
price_n=price.n,
|
||||||
price_d=unpacker.unpack_uint(),
|
price_d=price.d,
|
||||||
)
|
)
|
||||||
|
if isinstance(op, SetOptions):
|
||||||
if type == OP_SET_OPTIONS:
|
operation = messages.StellarSetOptionsOp(
|
||||||
op = messages.StellarSetOptionsOp(source_account=source_account)
|
source_account=source_account,
|
||||||
|
inflation_destination_account=op.inflation_dest,
|
||||||
# Inflation destination
|
clear_flags=op.clear_flags,
|
||||||
if unpacker.unpack_bool():
|
set_flags=op.set_flags,
|
||||||
op.inflation_destination_account = _xdr_read_address(unpacker)
|
master_weight=op.master_weight,
|
||||||
|
low_threshold=op.low_threshold,
|
||||||
# clear flags
|
medium_threshold=op.med_threshold,
|
||||||
if unpacker.unpack_bool():
|
high_threshold=op.high_threshold,
|
||||||
op.clear_flags = unpacker.unpack_uint()
|
home_domain=op.home_domain,
|
||||||
|
)
|
||||||
# set flags
|
if op.signer:
|
||||||
if unpacker.unpack_bool():
|
signer_type = op.signer.signer_key.signer_key.type
|
||||||
op.set_flags = unpacker.unpack_uint()
|
if signer_type == SignerKeyType.SIGNER_KEY_TYPE_ED25519:
|
||||||
|
signer_key = op.signer.signer_key.signer_key.ed25519.uint256
|
||||||
# master weight
|
elif signer_type == SignerKeyType.SIGNER_KEY_TYPE_HASH_X:
|
||||||
if unpacker.unpack_bool():
|
signer_key = op.signer.signer_key.signer_key.hash_x.uint256
|
||||||
op.master_weight = unpacker.unpack_uint()
|
elif signer_type == SignerKeyType.SIGNER_KEY_TYPE_PRE_AUTH_TX:
|
||||||
|
signer_key = op.signer.signer_key.signer_key.pre_auth_tx.uint256
|
||||||
# low threshold
|
else:
|
||||||
if unpacker.unpack_bool():
|
raise ValueError("Unsupported signer key type")
|
||||||
op.low_threshold = unpacker.unpack_uint()
|
operation.signer_type = signer_type.value
|
||||||
|
operation.signer_key = signer_key
|
||||||
# medium threshold
|
operation.signer_weight = op.signer.weight
|
||||||
if unpacker.unpack_bool():
|
return operation
|
||||||
op.medium_threshold = unpacker.unpack_uint()
|
if isinstance(op, ChangeTrust):
|
||||||
|
|
||||||
# high threshold
|
|
||||||
if unpacker.unpack_bool():
|
|
||||||
op.high_threshold = unpacker.unpack_uint()
|
|
||||||
|
|
||||||
# home domain
|
|
||||||
if unpacker.unpack_bool():
|
|
||||||
op.home_domain = unpacker.unpack_string().decode()
|
|
||||||
|
|
||||||
# signer
|
|
||||||
if unpacker.unpack_bool():
|
|
||||||
op.signer_type = unpacker.unpack_uint()
|
|
||||||
op.signer_key = unpacker.unpack_fopaque(32)
|
|
||||||
op.signer_weight = unpacker.unpack_uint()
|
|
||||||
|
|
||||||
return op
|
|
||||||
|
|
||||||
if type == OP_CHANGE_TRUST:
|
|
||||||
return messages.StellarChangeTrustOp(
|
return messages.StellarChangeTrustOp(
|
||||||
source_account=source_account,
|
source_account=source_account,
|
||||||
asset=_xdr_read_asset(unpacker),
|
asset=_read_asset(op.asset),
|
||||||
limit=unpacker.unpack_uhyper(),
|
limit=_read_amount(op.limit),
|
||||||
)
|
)
|
||||||
|
if isinstance(op, AllowTrust):
|
||||||
if type == OP_ALLOW_TRUST:
|
is_authorized = False
|
||||||
op = messages.StellarAllowTrustOp(
|
if op.authorize is True or TrustLineEntryFlag.AUTHORIZED_FLAG == op.authorize:
|
||||||
|
is_authorized = True
|
||||||
|
asset_type = (
|
||||||
|
ASSET_TYPE_ALPHA4 if len(op.asset_code) <= 4 else ASSET_TYPE_ALPHA12
|
||||||
|
)
|
||||||
|
return messages.StellarAllowTrustOp(
|
||||||
source_account=source_account,
|
source_account=source_account,
|
||||||
trusted_account=_xdr_read_address(unpacker),
|
trusted_account=op.trustor,
|
||||||
asset_type=unpacker.unpack_uint(),
|
asset_type=asset_type,
|
||||||
|
asset_code=op.asset_code,
|
||||||
|
is_authorized=is_authorized,
|
||||||
)
|
)
|
||||||
|
if isinstance(op, AccountMerge):
|
||||||
if op.asset_type == ASSET_TYPE_ALPHA4:
|
|
||||||
op.asset_code = unpacker.unpack_fstring(4).decode()
|
|
||||||
if op.asset_type == ASSET_TYPE_ALPHA12:
|
|
||||||
op.asset_code = unpacker.unpack_fstring(12).decode()
|
|
||||||
|
|
||||||
op.is_authorized = unpacker.unpack_bool()
|
|
||||||
|
|
||||||
return op
|
|
||||||
|
|
||||||
if type == OP_ACCOUNT_MERGE:
|
|
||||||
return messages.StellarAccountMergeOp(
|
return messages.StellarAccountMergeOp(
|
||||||
source_account=source_account,
|
source_account=source_account,
|
||||||
destination_account=_xdr_read_address(unpacker),
|
destination_account=op.destination.account_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Inflation is not implemented since anyone can submit this operation to the network
|
# Inflation is not implemented since anyone can submit this operation to the network
|
||||||
|
if isinstance(op, ManageData):
|
||||||
if type == OP_MANAGE_DATA:
|
return messages.StellarManageDataOp(
|
||||||
op = messages.StellarManageDataOp(
|
source_account=source_account,
|
||||||
source_account=source_account, key=unpacker.unpack_string().decode()
|
key=op.data_name,
|
||||||
|
value=op.data_value,
|
||||||
)
|
)
|
||||||
|
if isinstance(op, BumpSequence):
|
||||||
# Only set value if the field is present
|
|
||||||
if unpacker.unpack_bool():
|
|
||||||
op.value = unpacker.unpack_opaque()
|
|
||||||
|
|
||||||
return op
|
|
||||||
|
|
||||||
# Bump Sequence
|
|
||||||
# see: https://github.com/stellar/stellar-core/blob/master/src/xdr/Stellar-transaction.x#L269
|
|
||||||
if type == OP_BUMP_SEQUENCE:
|
|
||||||
return messages.StellarBumpSequenceOp(
|
return messages.StellarBumpSequenceOp(
|
||||||
source_account=source_account, bump_to=unpacker.unpack_uhyper()
|
source_account=source_account, bump_to=op.bump_to
|
||||||
)
|
)
|
||||||
|
raise ValueError(f"Unknown operation type: {op.__class__.__name__}")
|
||||||
raise ValueError("Unknown operation type: " + str(type))
|
|
||||||
|
|
||||||
|
|
||||||
def _xdr_read_asset(unpacker):
|
def _read_amount(amount: str) -> int:
|
||||||
|
return Operation.to_xdr_amount(amount)
|
||||||
|
|
||||||
|
|
||||||
|
def _read_price(price: Union["Price", str, Decimal]) -> "Price":
|
||||||
|
# In the coming stellar-sdk 5.x, the type of price must be Price,
|
||||||
|
# at that time we can remove this function
|
||||||
|
if isinstance(price, Price):
|
||||||
|
return price
|
||||||
|
return Price.from_raw_price(price)
|
||||||
|
|
||||||
|
|
||||||
|
def _read_asset(asset: "Asset") -> messages.StellarAssetType:
|
||||||
"""Reads a stellar Asset from unpacker"""
|
"""Reads a stellar Asset from unpacker"""
|
||||||
asset = messages.StellarAssetType(type=unpacker.unpack_uint())
|
if asset.is_native():
|
||||||
|
return messages.StellarAssetType(type=ASSET_TYPE_NATIVE)
|
||||||
if asset.type == ASSET_TYPE_ALPHA4:
|
if asset.guess_asset_type() == "credit_alphanum4":
|
||||||
asset.code = unpacker.unpack_fstring(4).decode()
|
return messages.StellarAssetType(
|
||||||
asset.issuer = _xdr_read_address(unpacker)
|
type=ASSET_TYPE_ALPHA4, code=asset.code, issuer=asset.issuer
|
||||||
|
)
|
||||||
if asset.type == ASSET_TYPE_ALPHA12:
|
if asset.guess_asset_type() == "credit_alphanum12":
|
||||||
asset.code = unpacker.unpack_fstring(12).decode()
|
return messages.StellarAssetType(
|
||||||
asset.issuer = _xdr_read_address(unpacker)
|
type=ASSET_TYPE_ALPHA12, code=asset.code, issuer=asset.issuer
|
||||||
|
)
|
||||||
return asset
|
raise ValueError("Unsupported asset type")
|
||||||
|
|
||||||
|
|
||||||
def _xdr_read_address(unpacker):
|
|
||||||
"""Reads a stellar address and returns the string representing the address
|
|
||||||
This method assumes the encoded address is a public address (starting with G)
|
|
||||||
"""
|
|
||||||
# First 4 bytes are the address type
|
|
||||||
address_type = unpacker.unpack_uint()
|
|
||||||
if address_type != 0:
|
|
||||||
raise ValueError("Unsupported address type")
|
|
||||||
|
|
||||||
return address_from_public_key(unpacker.unpack_fopaque(32))
|
|
||||||
|
|
||||||
|
|
||||||
def _crc16_checksum(bytes):
|
|
||||||
"""Returns the CRC-16 checksum of bytearray bytes
|
|
||||||
|
|
||||||
Ported from Java implementation at: http://introcs.cs.princeton.edu/java/61data/CRC16CCITT.java.html
|
|
||||||
|
|
||||||
Initial value changed to 0x0000 to match Stellar configuration.
|
|
||||||
"""
|
|
||||||
crc = 0x0000
|
|
||||||
polynomial = 0x1021
|
|
||||||
|
|
||||||
for byte in bytes:
|
|
||||||
for i in range(8):
|
|
||||||
bit = (byte >> (7 - i) & 1) == 1
|
|
||||||
c15 = (crc >> 15 & 1) == 1
|
|
||||||
crc <<= 1
|
|
||||||
if c15 ^ bit:
|
|
||||||
crc ^= polynomial
|
|
||||||
|
|
||||||
return crc & 0xFFFF
|
|
||||||
|
|
||||||
|
|
||||||
# ====== Client functions ====== #
|
# ====== Client functions ====== #
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,7 @@ envlist =
|
|||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
-rrequirements.txt
|
-rrequirements.txt
|
||||||
|
-rrequirements-optional.txt
|
||||||
pytest>=3.6
|
pytest>=3.6
|
||||||
pytest-random-order
|
pytest-random-order
|
||||||
importlib-metadata!=0.21
|
importlib-metadata!=0.21
|
||||||
|
Loading…
Reference in New Issue
Block a user