From 2777208da3c95085222fc44ce1eab1746588a7f9 Mon Sep 17 00:00:00 2001
From: Inso <insomniak.fr@gmail.com>
Date: Wed, 30 Sep 2015 19:38:44 +0200
Subject: [PATCH] Detect blockchain rollback

---
 src/cutecoin/core/net/network.py | 32 +++++++++++++++++++++++---------
 src/cutecoin/core/net/node.py    | 27 ++++++++++++++++++++++-----
 2 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/src/cutecoin/core/net/network.py b/src/cutecoin/core/net/network.py
index f9d7dc6e..4146da8d 100644
--- a/src/cutecoin/core/net/network.py
+++ b/src/cutecoin/core/net/network.py
@@ -12,7 +12,7 @@ import asyncio
 from ucoinpy.documents.peer import Peer
 from ucoinpy.documents.block import Block
 
-from ucoinpy.api import bma
+from ...tools.decorators import asyncify
 
 from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer
 from collections import Counter
@@ -25,6 +25,7 @@ class Network(QObject):
     """
     nodes_changed = pyqtSignal()
     new_block_mined = pyqtSignal(int)
+    blockchain_rollback = pyqtSignal(int)
 
     def __init__(self, currency, nodes):
         """
@@ -40,7 +41,7 @@ class Network(QObject):
             self.add_node(n)
         self.currency = currency
         self._must_crawl = False
-        self._block_found = self.latest_block_hash
+        self._refresh_block_found()
         self._timer = QTimer()
 
     @classmethod
@@ -189,6 +190,10 @@ class Network(QObject):
         else:
             return Block.Empty_Hash
 
+    def _refresh_block_found(self):
+        self._block_found = {'hash': self.latest_block_hash,
+                             'number': self.latest_block_number}
+
     def check_nodes_sync(self):
         """
         Check nodes sync with the following rules :
@@ -334,10 +339,19 @@ class Network(QObject):
                 self.nodes.remove(node)
 
         self.nodes_changed.emit()
-
-        logging.debug("{0} -> {1}".format(self._block_found[:10], self.latest_block_hash[:10]))
-        if self._block_found != self.latest_block_hash and node.state == Node.ONLINE:
-            logging.debug("Latest block changed : {0}".format(self.latest_block_number))
-            self._block_found = self.latest_block_hash
-            # Do not emit block change for empty block
-            self.new_block_mined.emit(self.latest_block_number)
+        if node.state == Node.ONLINE:
+            logging.debug("{0} -> {1}".format(self._block_found['hash'][:10], self.latest_block_hash[:10]))
+            if self._block_found['hash'] != self.latest_block_hash:
+                logging.debug("Latest block changed : {0}".format(self.latest_block_number))
+                # If new latest block is lower than the previously found one
+                # or if the previously found block is different locally
+                # than in the main chain, we declare a rollback
+                if self._block_found['number'] and \
+                    self.latest_block_number <= self._block_found['number'] \
+                    or node.main_chain_previous_block and \
+                        node.main_chain_previous_block['hash'] != self._block_found['hash']:
+                    self._refresh_block_found()
+                    self.blockchain_rollback.emit(self.latest_block_number)
+                else:
+                    self._refresh_block_found()
+                    self.new_block_mined.emit(self.latest_block_number)
diff --git a/src/cutecoin/core/net/node.py b/src/cutecoin/core/net/node.py
index c1b001ed..a64b6fa8 100644
--- a/src/cutecoin/core/net/node.py
+++ b/src/cutecoin/core/net/node.py
@@ -5,7 +5,6 @@ Created on 21 févr. 2015
 """
 
 from ucoinpy.documents.peer import Peer, Endpoint, BMAEndpoint
-from ucoinpy.documents.block import Block
 from ...tools.exceptions import InvalidNodeCurrency
 from ...tools.decorators import asyncify
 from ucoinpy.api import bma as bma
@@ -49,6 +48,7 @@ class Node(QObject):
         self._uid = uid
         self._pubkey = pubkey
         self._block = block
+        self.main_chain_previous_block = None
         self._state = state
         self._neighbours = []
         self._currency = currency
@@ -295,12 +295,29 @@ class Node(QObject):
             self.state = Node.ONLINE
 
             if not self.block or block_hash != self.block['hash']:
-                self.set_block(block_data)
-                logging.debug("Changed block {0} -> {1}".format(self.block['number'],
-                                                                block_data['number']))
-                self.changed.emit()
+                try:
+                    #TODO: Check previous block
+                    self.main_chain_previous_block = yield from bma.blockchain.Block(conn_handler,
+                                                                                     self.block['number']).get()
+                except ValueError as e:
+                    if '404' in str(e):
+                        self.main_chain_previous_block = None
+                    logging.debug("Error in block reply")
+                    self.changed.emit()
+                except ClientError:
+                    logging.debug("Client error : {0}".format(self.pubkey))
+                    self.state = Node.OFFLINE
+                except asyncio.TimeoutError:
+                    logging.debug("Timeout error : {0}".format(self.pubkey))
+                    self.state = Node.OFFLINE
+                finally:
+                    self.set_block(block_data)
+                    logging.debug("Changed block {0} -> {1}".format(self.block['number'],
+                                                                    block_data['number']))
+                    self.changed.emit()
         except ValueError as e:
             if '404' in str(e):
+                self.main_chain_previous_block = None
                 self.set_block(None)
             logging.debug("Error in block reply")
             self.changed.emit()
-- 
GitLab