Ver código fonte

implement persistence of primary blockchain, unconfirmed transactions and connected peers

Malte Kraus 8 anos atrás
pai
commit
5b8be3e9d8
2 arquivos alterados com 61 adições e 2 exclusões
  1. 15 2
      miner.py
  2. 46 0
      src/persistence.py

+ 15 - 2
miner.py

@@ -17,6 +17,7 @@ from src.block import GENESIS_BLOCK
 from src.chainbuilder import ChainBuilder
 from src.mining import Miner
 from src.transaction import TransactionInput
+from src.persistence import Persistence
 
 def parse_addr_port(val):
     url = urlparse("//" + val)
@@ -29,7 +30,7 @@ def parse_addr_port(val):
     assert url.hostname is not None
     return (url.hostname, url.port)
 
-def rpc_server(port, chainbuilder):
+def rpc_server(port, chainbuilder, persist):
     @app.route("/network-info", methods=['GET'])
     def get_network_info():
         return json.dumps([list(peer.peer_addr)[:2] for peer in chainbuilder.protocol.peers if peer.is_connected])
@@ -37,6 +38,7 @@ def rpc_server(port, chainbuilder):
     @app.route("/new-transaction", methods=['PUT'])
     def send_transaction():
         chainbuilder.protocol.received("transaction", flask.request.json, None, 0)
+        # TODO: from the right thread, we want to call persist.store() here
         return b""
 
     @app.route("/show-balance", methods=['POST'])
@@ -108,6 +110,8 @@ def main():
                         help="Addresses of other P2P peers in the network.")
     parser.add_argument("--rpc-port", type=int, default=40203,
                         help="The port number where the wallet can find an RPC server.")
+    parser.add_argument("--persist-path",
+                        help="The file where data is persisted.")
 
     args = parser.parse_args()
 
@@ -121,7 +125,16 @@ def main():
     else:
         chainbuilder = ChainBuilder(proto)
 
-    rpc_server(args.rpc_port, chainbuilder)
+    if args.persist_path:
+        persist = Persistence(args.persist_path, chainbuilder)
+        try:
+            persist.load()
+        except FileNotFoundError:
+            pass
+    else:
+        persist = None
+
+    rpc_server(args.rpc_port, chainbuilder, persist)
 
 if __name__ == '__main__':
     main()

+ 46 - 0
src/persistence.py

@@ -0,0 +1,46 @@
+import json
+import os
+import os.path
+import tempfile
+
+
+class Persistence:
+    def __init__(self, path: str, chainbuilder: 'ChainBuilder'):
+        self.chainbuilder = chainbuilder
+        self.proto = chainbuilder.protocol
+        self.path = path
+
+        chainbuilder.chain_change_handlers.append(self.store)
+        self._loading = False
+
+    def load(self):
+        self._loading = True
+        try:
+            with open(self.path, "r") as f:
+                obj = json.load(f)
+            for block in obj['blocks']:
+                self.proto.received("block", block, None, 2)
+            for trans in obj['transactions']:
+                self.proto.received("transaction", trans, None, 2)
+            for peer in obj["peers"]:
+                self.proto.received("peer", peer, None, 2)
+        finally:
+            self._loading = False
+
+    def store(self):
+        if self._loading:
+            return
+
+        obj = {
+            "blocks": [b.to_json_compatible() for b in self.chainbuilder.primary_block_chain.blocks[::-1]],
+            "transactions": [t.to_json_compatible() for t in self.chainbuilder.unconfirmed_transactions.values()],
+            "peers": [list(peer.peer_addr) for peer in self.proto.peers if peer.is_connected and peer.peer_addr is not None],
+        }
+        with tempfile.NamedTemporaryFile(dir=os.path.dirname(self.path), delete=False, mode="w") as f:
+            try:
+                json.dump(obj, f, indent=4)
+                f.close()
+                os.rename(f.name, self.path)
+            except Exception as e:
+                os.unlink(f.name)
+                raise e