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