Handle unsigned transactions

unsigned-tx
Michał Sałaban 6 years ago
parent 5ee551fa4a
commit bfcdc5a702

@ -1,4 +1,3 @@
import warnings
from . import prio
from .transaction import PaymentManager
@ -68,7 +67,7 @@ class Account(object):
priority=prio.NORMAL, payment_id=None, unlock_time=0,
relay=True):
"""
Sends a transfer. Returns a list of resulting transactions.
Sends a transfer. Returns a set of resulting transactions.
:param address: destination :class:`Address <monero.address.Address>` or subtype
:param amount: amount to send
@ -82,7 +81,7 @@ class Account(object):
:param relay: if `True`, the wallet will relay the transaction(s) to the network
immediately; when `False`, it will only return the transaction(s)
so they might be broadcasted later
:rtype: list of :class:`Transaction <monero.transaction.Transaction>`
:rtype: set of :class:`Transaction <monero.transaction.Transaction>`
"""
return self._backend.transfer(
[(address, amount)],
@ -96,7 +95,7 @@ class Account(object):
priority=prio.NORMAL, payment_id=None, unlock_time=0,
relay=True):
"""
Sends a batch of transfers. Returns a list of resulting transactions.
Sends a batch of transfers. Returns a set of resulting transactions.
:param destinations: a list of destination and amount pairs:
[(:class:`Address <monero.address.Address>`, `Decimal`), ...]
@ -110,7 +109,7 @@ class Account(object):
:param relay: if `True`, the wallet will relay the transaction(s) to the network
immediately; when `False`, it will only return the transaction(s)
so they might be broadcasted later
:rtype: list of :class:`Transaction <monero.transaction.Transaction>`
:rtype: set of :class:`Transaction <monero.transaction.Transaction>`
"""
return self._backend.transfer(
destinations,

@ -10,7 +10,9 @@ from ..account import Account
from ..address import address, Address, SubAddress
from ..numbers import from_atomic, to_atomic, PaymentID
from ..seed import Seed
from ..transaction import Transaction, IncomingPayment, OutgoingPayment
from ..transaction import (
SignedTransaction, UnsignedTransaction, MultisigTransaction, TransactionSet,
IncomingPayment, OutgoingPayment )
_log = logging.getLogger(__name__)
@ -51,7 +53,7 @@ class JSONRPCDaemon(object):
res = self.raw_request('/get_transaction_pool', {})
txs = []
for tx in res.get('transactions', []):
txs.append(Transaction(
txs.append(SignedTransaction(
hash=tx['id_hash'],
fee=from_atomic(tx['fee']),
timestamp=datetime.fromtimestamp(tx['receive_time'])))
@ -242,7 +244,7 @@ class JSONRPCWallet(object):
'amount': from_atomic(data['amount']),
'timestamp': datetime.fromtimestamp(data['timestamp']) if 'timestamp' in data else None,
'note': data.get('note', None),
'transaction': self._tx(data),
'transaction': self._tx(data, SignedTransaction),
'local_address': laddr,
}
if 'destinations' in data:
@ -259,8 +261,8 @@ class JSONRPCWallet(object):
def _outpayment(self, data):
return OutgoingPayment(**self._paymentdict(data))
def _tx(self, data):
return Transaction(**{
def _tx(self, data, klass):
return klass(**{
'hash': data.get('txid', data.get('tx_hash')),
'fee': from_atomic(data['fee']) if 'fee' in data else None,
'key': data.get('key'),
@ -311,7 +313,23 @@ class JSONRPCWallet(object):
'tx_hash_list', 'amount_list', 'fee_list', 'tx_key_list', 'tx_blob_list')]))]
for d in _pertx:
d['payment_id'] = payment_id
return [self._tx(data) for data in _pertx]
result = TransactionSet()
klass = SignedTransaction
try:
if _transfers['unsigned_txset']:
result.unsigned_txset = _transfers['unsigned_txset']
klass = UnsignedTransaction
except KeyError:
pass
try:
if _transfers['multisig_txset']:
result.multisig_txset = _transfers['multisig_txset']
klass = MultisigTransaction
except KeyError:
pass
for data in _pertx:
result.add(self._tx(data, klass))
return result
def raw_request(self, method, params=None, squelch_error_logging=False):
hdr = {'Content-Type': 'application/json'}

@ -1,3 +1,6 @@
from .exceptions import TransactionNotSigned
from .transaction import SignedTransaction
class Daemon(object):
"""Monero daemon.
@ -28,10 +31,12 @@ class Daemon(object):
"""
Sends a transaction generated by a :class:`Wallet <monero.wallet.Wallet>`.
:param tx: :class:`Transaction <monero.transaction.Transaction>`
:param tx: :class:`SignedTransaction <monero.transaction.SignedTransaction>`
:param relay: whether to relay the transaction to peers. If `False`, the daemon will have
to mine the transaction itself in order to have it included in the blockchain.
"""
if not isinstance(tx, SignedTransaction):
raise TransactionNotSigned(tx.hash)
return self._backend.send_transaction(tx.blob, relay=relay)
def mempool(self):

@ -41,3 +41,6 @@ class SignatureCheckFailed(MoneroException):
class WalletIsNotDeterministic(MoneroException):
pass
class TransactionNotSigned(TransactionBroadcastError):
pass

@ -71,6 +71,7 @@ class Transaction(object):
key = None
blob = None
confirmations = None
signed = False
def __init__(self, **kwargs):
self.hash = kwargs.get('hash', self.hash)
@ -85,6 +86,27 @@ class Transaction(object):
return self.hash
class UnsignedTransaction(Transaction):
pass
class SignedTransaction(Transaction):
signed = True
class MultisigTransaction(Transaction):
pass
class TransactionSet(set):
unsigned_txset = None
multisig_txset = None
@property
def signed(self):
return self.unsigned_txset is None and self.multisig_txset is None
if sys.version_info < (3,):
_str_types = (str, bytes, unicode)
else:

@ -188,7 +188,7 @@ class Wallet(object):
priority=prio.NORMAL, payment_id=None, unlock_time=0,
relay=True):
"""
Sends a transfer from the default account. Returns a list of resulting transactions.
Sends a transfer from the default account. Returns a set of resulting transactions.
:param address: destination :class:`Address <monero.address.Address>` or subtype
:param amount: amount to send
@ -202,7 +202,7 @@ class Wallet(object):
:param relay: if `True`, the wallet will relay the transaction(s) to the network
immediately; when `False`, it will only return the transaction(s)
so they might be broadcasted later
:rtype: list of :class:`Transaction <monero.transaction.Transaction>`
:rtype: set of :class:`Transaction <monero.transaction.Transaction>`
"""
return self.accounts[0].transfer(
address,
@ -216,7 +216,7 @@ class Wallet(object):
priority=prio.NORMAL, payment_id=None, unlock_time=0,
relay=True):
"""
Sends a batch of transfers from the default account. Returns a list of resulting
Sends a batch of transfers from the default account. Returns a set of resulting
transactions.
:param destinations: a list of destination and amount pairs: [(address, amount), ...]
@ -230,7 +230,7 @@ class Wallet(object):
:param relay: if `True`, the wallet will relay the transaction(s) to the network
immediately; when `False`, it will only return the transaction(s)
so they might be broadcasted later
:rtype: list of :class:`Transaction <monero.transaction.Transaction>`
:rtype: set of :class:`Transaction <monero.transaction.Transaction>`
"""
return self.accounts[0].transfer_multiple(
destinations,

@ -5,12 +5,11 @@ try:
from unittest.mock import patch, Mock
except ImportError:
from mock import patch, Mock
import warnings
from monero.wallet import Wallet
from monero.address import Address
from monero.seed import Seed
from monero.transaction import IncomingPayment, OutgoingPayment, Transaction
from monero.transaction import IncomingPayment, OutgoingPayment, SignedTransaction
from monero.backends.jsonrpc import JSONRPCWallet
class SubaddrWalletTestCase(unittest.TestCase):
@ -260,7 +259,7 @@ class SubaddrWalletTestCase(unittest.TestCase):
self.assertIsInstance(pmt, IncomingPayment)
self.assertIsInstance(pmt.local_address, Address)
self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction, SignedTransaction)
self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIsInstance(pmt.transaction.height, int)
@ -411,7 +410,7 @@ class SubaddrWalletTestCase(unittest.TestCase):
self.assertIsInstance(pmt, IncomingPayment)
self.assertIsInstance(pmt.local_address, Address)
self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction, SignedTransaction)
self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIsInstance(pmt.transaction.height, (int, type(None)))
@ -454,7 +453,7 @@ class SubaddrWalletTestCase(unittest.TestCase):
self.assertIsInstance(pmt, IncomingPayment)
self.assertIsInstance(pmt.local_address, Address)
self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction, SignedTransaction)
self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIs(pmt.transaction.height, None)
@ -495,7 +494,7 @@ class SubaddrWalletTestCase(unittest.TestCase):
self.assertIsInstance(pmt, IncomingPayment)
self.assertIsInstance(pmt.local_address, Address)
self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction, SignedTransaction)
# Fee is not returned by this RPC method!
# self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIsInstance(pmt.transaction.height, int)
@ -539,7 +538,7 @@ class SubaddrWalletTestCase(unittest.TestCase):
self.assertIsInstance(pmt, IncomingPayment)
self.assertIsInstance(pmt.local_address, Address)
self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction, SignedTransaction)
# Fee is not returned by this RPC method!
# self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIsInstance(pmt.transaction.height, int)
@ -709,7 +708,7 @@ class SubaddrWalletTestCase(unittest.TestCase):
self.assertIsInstance(pmt.local_address, Address)
self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(pmt.timestamp, datetime)
self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction, SignedTransaction)
self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIsInstance(pmt.transaction.height, int)
@ -889,7 +888,7 @@ class SubaddrWalletTestCase(unittest.TestCase):
self.assertIsInstance(pmt.local_address, Address)
self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(pmt.timestamp, datetime)
self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction, SignedTransaction)
self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIsInstance(pmt.transaction.height, (int, type(None)))
@ -921,7 +920,7 @@ class SubaddrWalletTestCase(unittest.TestCase):
self.assertIsInstance(pmt.local_address, Address)
self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(pmt.timestamp, datetime)
self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction, SignedTransaction)
self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIs(pmt.transaction.height, None)
@ -943,8 +942,11 @@ class SubaddrWalletTestCase(unittest.TestCase):
'9wFuzNoQDck1pnS9ZhG47kDdLD1BUszSbWpGfWcSRy9m6Npq9NoHWd141KvGag8hu2gajEwzRXJ4iJwmxruv9ofc2CwnYCE',
3)
self.assertEqual(len(txns), 1)
txn = txns[0]
self.assertIsInstance(txn, Transaction)
self.assertIsNone(txns.unsigned_txset)
self.assertIsNone(txns.multisig_txset)
txn = txns.pop()
self.assertIsInstance(txn, SignedTransaction)
self.assertTrue(txn.signed)
self.assertIsInstance(txn.fee, Decimal)
self.assertEqual(txn.hash,
'401d8021975a0fee16fe84acbfc4d8ba6312e563fa245baba2aac382e787fb60')

@ -7,7 +7,7 @@ import sys
from monero.backends.jsonrpc import JSONRPCDaemon
from monero.daemon import Daemon
from monero.transaction import Transaction
from monero.transaction import SignedTransaction
from monero import exceptions
def url_data(url):
@ -39,7 +39,7 @@ else:
d = Daemon(JSONRPCDaemon(**args.daemon_rpc_url))
for name, blob in blobs:
logging.debug("Sending {}".format(name))
tx = Transaction(blob=blob)
tx = SignedTransaction(blob=blob)
try:
res = d.send_transaction(tx, relay=args.relay)
except exceptions.TransactionBroadcastError as e:

Loading…
Cancel
Save