From db1640ac2f723a47ff2c425be752dd00e1ef98d2 Mon Sep 17 00:00:00 2001 From: Fabian Schuh <Fabian@chainsquad.com> Date: Mon, 20 Mar 2017 10:09:06 +0100 Subject: [PATCH] [bitshares] propose and bundle transaction --- bitshares/bitshares.py | 58 +++++++++++++++++++++------------ bitshares/transactionbuilder.py | 18 +++++----- docs/tutorials.rst | 50 ++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/bitshares/bitshares.py b/bitshares/bitshares.py index 15658b61..1cd61237 100644 --- a/bitshares/bitshares.py +++ b/bitshares/bitshares.py @@ -33,8 +33,11 @@ class BitShares(object): :param str rpcpassword: RPC password *(optional)* :param bool nobroadcast: Do **not** broadcast a transaction! *(optional)* :param bool debug: Enable Debugging *(optional)* - :param array,dict,string keys: Predefine the wif keys to shortcut the wallet database - :param bool offline: Boolean to prevent connecting to network (defaults to ``False``) + :param array,dict,string keys: Predefine the wif keys to shortcut the wallet database *(optional)* + :param bool offline: Boolean to prevent connecting to network (defaults to ``False``) *(optional)* + :param str proposer: Propose a transaction using this proposer *(optional)* + :param int expiration: Delay in seconds until transactions are supposed to expire *(optional)* + :param bool bundle: Do not broadcast transactions right away, but allow to bundle operations *(optional)* Three wallet operation modes are possible: @@ -106,11 +109,12 @@ class BitShares(object): self.rpc = None self.debug = debug - self.offline = kwargs.get("offline", False) - self.nobroadcast = kwargs.get("nobroadcast", False) - self.unsigned = kwargs.get("unsigned", False) + self.offline = bool(kwargs.get("offline", False)) + self.nobroadcast = bool(kwargs.get("nobroadcast", False)) + self.unsigned = bool(kwargs.get("unsigned", False)) self.expiration = int(kwargs.get("expiration", 30)) self.proposer = kwargs.get("proposer", None) + self.bundle = bool(kwargs.get("bundle", False)) if not self.offline: self._connect(node=node, @@ -119,6 +123,7 @@ class BitShares(object): **kwargs) self.wallet = Wallet(self.rpc, **kwargs) + self.txbuffer = TransactionBuilder(bitshares_instance=self) def _connect(self, node="", @@ -169,19 +174,23 @@ class BitShares(object): posting permission. Neither can you use different accounts for different operations! """ - tx = TransactionBuilder(bitshares_instance=self) - tx.appendOps(ops) + # Append transaction + self.txbuffer.appendOps(ops) if self.unsigned: - tx.addSigningInformation(account, permission) - return tx + # In case we don't want to sign anything + self.txbuffer.addSigningInformation(account, permission) + return self.txbuffer + elif self.bundle: + # In case we want to add more ops to the tx (bundle) + self.txbuffer.appendSigner(account, permission) else: - tx.appendSigner(account, permission) - tx.sign() + # default behavior: sign + broadcast + self.txbuffer.appendSigner(account, permission) + self.txbuffer.sign() + return self.txbuffer.broadcast() - return tx.broadcast() - - def sign(self, tx, wifs=[]): + def sign(self, tx=None, wifs=[]): """ Sign a provided transaction witht he provided key(s) :param dict tx: The transaction to be signed and returned @@ -190,18 +199,25 @@ class BitShares(object): from the wallet as defined in "missing_signatures" key of the transactions. """ - tx = TransactionBuilder(tx, bitshares_instance=self) - tx.appendMissingSignatures(wifs) - tx.sign() - return tx.json() + if tx: + txbuffer = TransactionBuilder(tx, bitshares_instance=self) + else: + txbuffer = self.txbuffer + txbuffer.appendWif(wifs) + txbuffer.appendMissingSignatures() + txbuffer.sign() + return txbuffer.json() - def broadcast(self, tx): + def broadcast(self, tx=None): """ Broadcast a transaction to the BitShares network :param tx tx: Signed transaction to broadcast """ - tx = TransactionBuilder(tx) - return tx.broadcast() + if tx: + # If tx is provided, we broadcast the tx + return TransactionBuilder(tx).broadcast() + else: + return self.txbuffer.broadcast() def info(self): """ Returns the global properties diff --git a/bitshares/transactionbuilder.py b/bitshares/transactionbuilder.py index a6273edd..d30e00ad 100644 --- a/bitshares/transactionbuilder.py +++ b/bitshares/transactionbuilder.py @@ -31,10 +31,9 @@ class TransactionBuilder(dict): :param list ops: One or a list of operations """ if isinstance(ops, list): - for op in ops: - self.op.append(op) + self.ops.extend(ops) else: - self.op.append(ops) + self.ops.append(ops) def appendSigner(self, account, permission): """ Try to obtain the wif key from the wallet by telling which account @@ -66,7 +65,7 @@ class TransactionBuilder(dict): def appendWif(self, wif): """ Add a wif that should be used for signing of the transaction. - """ + """ if wif: try: PrivateKey(wif) @@ -79,7 +78,7 @@ class TransactionBuilder(dict): store """ if self.bitshares.proposer: - ops = [operations.Op_wrapper(op=o) for o in list(self.op)] + ops = [operations.Op_wrapper(op=o) for o in list(self.ops)] proposer = Account( self.bitshares.proposer, bitshares_instance=self.bitshares @@ -94,7 +93,7 @@ class TransactionBuilder(dict): }) ops = [Operation(ops)] else: - ops = [Operation(o) for o in list(self.op)] + ops = [Operation(o) for o in list(self.ops)] ops = transactions.addRequiredFees(self.bitshares.rpc, ops) expiration = transactions.formatTimeFromNow(self.bitshares.expiration) @@ -170,13 +169,16 @@ class TransactionBuilder(dict): except Exception as e: raise e + self.clear() + return self def clear(self): """ Clear the transaction builder and start from scratch """ - self.op = [] + self.ops = [] self.wifs = [] + super(TransactionBuilder, self).__init__({}) def addSigningInformation(self, account, permission): """ This is a private method that adds side information to a @@ -214,7 +216,7 @@ class TransactionBuilder(dict): """ return dict(self) - def appendMissingSignatures(self, wifs=[]): + def appendMissingSignatures(self): """ Store which accounts/keys are supposed to sign the transaction This method is used for an offline-signer! diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 28df7092..5409ad09 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -1,3 +1,53 @@ ********* Tutorials ********* + +Bundle Many Operations +---------------------- + +With BitShares, you can bundle multiple operations into a single +transactions. This can be used to do a multi-send (one sender, multiple +receivers), but it also allows to use any other kind of operation. The +advantage here is that the user can be sure that the operations are +executed in the same order as they are added to the transaction. + +.. code-block:: python + + from pprint import pprint + from bitshares import BitShares + + testnet = BitShares( + "wss://node.testnet.bitshares.eu", + nobroadcast=True, + bundle=True, + ) + + testnet.wallet.unlock("supersecret") + + testnet.transfer("init0", 1, "TEST", account="xeroc") + testnet.transfer("init1", 1, "TEST", account="xeroc") + testnet.transfer("init2", 1, "TEST", account="xeroc") + testnet.transfer("init3", 1, "TEST", account="xeroc") + + pprint(testnet.broadcast()) + + +Proposing a Transaction +----------------------- + +In BitShares, you can propose a transactions to any account. This is +used to facilitate on-chain multisig transactions. With +python-bitshares, you can do this simply by using the ``proposer`` +attribute: + +.. code-block:: python + + from pprint import pprint + from bitshares import BitShares + + testnet = BitShares( + "wss://node.testnet.bitshares.eu", + proposer="xeroc" + ) + testnet.wallet.unlock("supersecret") + pprint(testnet.transfer("init0", 1, "TEST", account="xeroc")) -- GitLab