Просмотр исходного кода

[research/ouroboros] added Input endorser

mohab 4 лет назад
Родитель
Сommit
d3b1dcebb3

+ 3 - 2
script/research/dpos/ouroboros/beacon.py

@@ -1,8 +1,7 @@
+import threading
 from ouroboros.clock import SynchedNTPClock
 from ouroboros.clock import SynchedNTPClock
 from ouroboros.vrf import VRF
 from ouroboros.vrf import VRF
 from ouroboros.logger import Logger
 from ouroboros.logger import Logger
-import threading
-import time 
 
 
 '''
 '''
 \class TrustedBeacon
 \class TrustedBeacon
@@ -51,6 +50,8 @@ class TrustedBeacon(SynchedNTPClock, threading.Thread):
         else:
         else:
             sigmas = []
             sigmas = []
             proofs = []
             proofs = []
+            #TODO since it's expensive, but to generate single (y,pi) pair as seed 
+            # and use random hash function to generate the rest randomly. 
             for i in range(self.epoch_length):
             for i in range(self.epoch_length):
                 self.log.info(f"callback: new slot of idx: {self.current_slot}, epoch slot {i}")
                 self.log.info(f"callback: new slot of idx: {self.current_slot}, epoch slot {i}")
                 y, pi = self.vrf.sign(self.current_slot)
                 y, pi = self.vrf.sign(self.current_slot)

+ 9 - 3
script/research/dpos/ouroboros/block.py

@@ -1,6 +1,7 @@
 import json
 import json
 from ouroboros.utils import encode_genesis_data, decode_gensis_data, state_hash
 from ouroboros.utils import encode_genesis_data, decode_gensis_data, state_hash
 from ouroboros.consts import *
 from ouroboros.consts import *
+from ouroboros.logger import Logger
 
 
 '''
 '''
 single block B_i for slot i in the system live time L,
 single block B_i for slot i in the system live time L,
@@ -16,7 +17,7 @@ class Block(object):
         it's one-based
         it's one-based
     @param gensis: boolean, True for gensis block
     @param gensis: boolean, True for gensis block
     '''
     '''
-    def __init__(self, previous_block, data, slot_uid, genesis=False):
+    def __init__(self, previous_block, data, slot_uid, genesis_time, genesis=False):
         # state is hte hash of the previous block in the blockchain
         # state is hte hash of the previous block in the blockchain
         self.state=''
         self.state=''
         if slot_uid>1:
         if slot_uid>1:
@@ -24,6 +25,8 @@ class Block(object):
         self.tx = data
         self.tx = data
         self.sl = slot_uid
         self.sl = slot_uid
         self.is_genesis=genesis
         self.is_genesis=genesis
+        self.endorsed=False
+        self.log = Logger(genesis_time)
 
 
     def __repr__(self):
     def __repr__(self):
         if self.is_genesis:
         if self.is_genesis:
@@ -52,6 +55,9 @@ class Block(object):
             'sl': self.sl}
             'sl': self.sl}
         return json.encoder(d)
         return json.encoder(d)
 
 
+    def set_endorsed(self):
+        self.endorsed=True
+
     
     
     @property
     @property
     def data(self):
     def data(self):
@@ -103,5 +109,5 @@ lead by offline leader
 is an empty Block
 is an empty Block
 '''
 '''
 class EmptyBlock(Block):
 class EmptyBlock(Block):
-    def __init__(self):
-        Block.__init__(self, '', -1, False)
+    def __init__(self, genesis_time):
+        Block.__init__(self, '', -1, genesis_time, False)

+ 2 - 5
script/research/dpos/ouroboros/blockchain.py

@@ -1,8 +1,8 @@
 from ouroboros.logger import Logger
 from ouroboros.logger import Logger
-import time
+
 '''
 '''
 Non-forkable Blockchain for simplicity
 Non-forkable Blockchain for simplicity
-#TODO consider forkable property
+#TODO implement forkable chain
 '''
 '''
 class Blockchain(object):
 class Blockchain(object):
     def __init__(self, R, genesis_time):
     def __init__(self, R, genesis_time):
@@ -37,6 +37,3 @@ class Blockchain(object):
                 self.__add_block(block)
                 self.__add_block(block)
             else:
             else:
                 self.log.warn(f"an empty block at index of index: {block.index},\nrelative slot:{idx}\nabsolute slot: {self.length*idx+block.slot}")
                 self.log.warn(f"an empty block at index of index: {block.index},\nrelative slot:{idx}\nabsolute slot: {self.length*idx+block.slot}")
-
-
-    

+ 59 - 12
script/research/dpos/ouroboros/environment.py

@@ -1,9 +1,11 @@
+from debugpy import configure
 import numpy as np
 import numpy as np
 import math
 import math
 import random
 import random
+import time
 from ouroboros.logger import Logger
 from ouroboros.logger import Logger
 from ouroboros.consts import *
 from ouroboros.consts import *
-import time
+
 '''
 '''
 \class Z is the environment
 \class Z is the environment
 '''
 '''
@@ -15,8 +17,10 @@ class Z(object):
         self.stakeholders = np.array(stakeholdes)
         self.stakeholders = np.array(stakeholdes)
         self.adversary_mask=np.array([True]*len(stakeholdes))
         self.adversary_mask=np.array([True]*len(stakeholdes))
         self.current_epoch_leaders=[-1]*self.epoch_length
         self.current_epoch_leaders=[-1]*self.epoch_length
+        self.current_epoch_endorsers=[-1]*self.epoch_length
         self.current_slot=0
         self.current_slot=0
         self.log.info("Z initialized")
         self.log.info("Z initialized")
+        self.current_blk_endorser_sig=None
     
     
     def __repr__(self):
     def __repr__(self):
         buff= f"envirnment of {self.length} stakholders"
         buff= f"envirnment of {self.length} stakholders"
@@ -35,13 +39,27 @@ class Z(object):
         return genesis_data
         return genesis_data
     
     
     @property
     @property
-    def current_leader_id(self):
+    def epoch_slot(self):
         return self.current_slot%self.epoch_length
         return self.current_slot%self.epoch_length
+
+    @property
+    def current_leader_id(self):
+        return self.current_epoch_leaders[self.epoch_slot]
+
     @property
     @property
     def current_stakeholder(self):
     def current_stakeholder(self):
-        self.log.info(f"getting leader of id{self.current_leader_id} of size {len(self.stakeholders)}")
+        self.log.info(f"getting leader of id: {self.current_leader_id}")
         return self.stakeholders[self.current_leader_id]
         return self.stakeholders[self.current_leader_id]
 
 
+    @property
+    def current_endorser_id(self):
+        return self.current_endorser_id[self.epoch_slot]
+
+    @property
+    def current_endorser(self):
+        self.log.info(f"getting endorser of id: {self.current_leader_id}")
+        return self.stakeholders[self.current_endorser_id]
+
     @property
     @property
     def current_leader_vrf_pk(self):
     def current_leader_vrf_pk(self):
         return self.stakeholders[self.current_leader_id].vrf_pk
         return self.stakeholders[self.current_leader_id].vrf_pk
@@ -54,6 +72,9 @@ class Z(object):
     def current_leader_sig_pk(self):
     def current_leader_sig_pk(self):
         return self.stakeholders[self.current_leader_id].sig_pk
         return self.stakeholders[self.current_leader_id].sig_pk
     
     
+    @property
+    def current_endorser_sig_pk(self):
+        return self.stakeholders[self.current_endorser_id].sig_pk
 
 
     #TODO complete
     #TODO complete
     def obfuscate_idx(self, i):
     def obfuscate_idx(self, i):
@@ -78,6 +99,7 @@ class Z(object):
     @property
     @property
     def length(self):
     def length(self):
         return len(self.stakeholders)
         return len(self.stakeholders)
+
     @property
     @property
     def honest(self):
     def honest(self):
         return len(self.stakeholders[self.adversary_mask])
         return len(self.stakeholders[self.adversary_mask])
@@ -95,13 +117,17 @@ class Z(object):
             seed = leader_selection_hash(sigma)
             seed = leader_selection_hash(sigma)
             random.seed(seed)
             random.seed(seed)
             leader_idx=seed%self.length
             leader_idx=seed%self.length
+            endorser_idx=random.randint(0,self.length)
             # only select an honest leaders
             # only select an honest leaders
-            while not self.adversary_mask[leader_idx]:
+            while leader_idx==endorser_idx or not self.adversary_mask[leader_idx] or not self.adversary_mask[endorser_idx]:
                 leader_idx=random.randint(0,self.length)
                 leader_idx=random.randint(0,self.length)
+                enderser_idx=random.randint(0,self.length)
+
             #TODO select the following leader for this epoch, note, 
             #TODO select the following leader for this epoch, note, 
             # under a single condition that no one is able to predict who is next
             # under a single condition that no one is able to predict who is next
             self.current_epoch_leaders[i]=leader_idx
             self.current_epoch_leaders[i]=leader_idx
-        return self.current_epoch_leaders
+            self.current_epoch_endorsers[i]=endorser_idx
+        return self.current_epoch_leaders, self.current_epoch_endorsers
 
 
     def new_slot(self, slot, sigma, proof):
     def new_slot(self, slot, sigma, proof):
         self.current_slot=slot
         self.current_slot=slot
@@ -111,21 +137,27 @@ class Z(object):
         if current_leader.is_leader:
         if current_leader.is_leader:
             #pass leadership to the current slot leader from the epoch leader
             #pass leadership to the current slot leader from the epoch leader
             self.stakeholders[self.current_epoch_leaders[self.current_leader_id]].set_leader()
             self.stakeholders[self.current_epoch_leaders[self.current_leader_id]].set_leader()
+            self.stakeholders[self.current_epoch_endorsers[self.current_endorser_id]].set_endorser()
     
     
     def new_epoch(self, slot, sigmas, proofs):
     def new_epoch(self, slot, sigmas, proofs):
         self.current_slot=slot
         self.current_slot=slot
-        #self.log.info(f"stakeholders: {self.stakeholders}")
-        #current_leader = self.stakeholders[self.current_leader_id]
-        #assert current_leader!=None, 'current leader cant be none'
-        #assert(current_leader.is_leader)
-        self.select_epoch_leaders(sigmas, proofs)
+        leaders, endorsers = self.select_epoch_leaders(sigmas, proofs)
+        return leaders, endorsers
 
 
     def broadcast_block(self, signed_block, slot_uid):
     def broadcast_block(self, signed_block, slot_uid):
         for stakeholder in self.stakeholders:
         for stakeholder in self.stakeholders:
             if not stakeholder.is_leader:
             if not stakeholder.is_leader:
-                stakeholder.receive_block(signed_block, slot_uid)
+                stakeholder.receive_block(signed_block, self.current_blk_endorser_sig, slot_uid)
         self.print_blockchain()
         self.print_blockchain()
 
 
+    def encorse_block(self, sig, slot_uid):
+        #TODO commit this step to handshake phases
+        self.current_blk_endorser_sig=sig
+        confirmed = self.stakeholders[self.current_leader_id].confirm_endorsing(sig, slot_uid)
+        if confirmed:
+            self.current_blk_endorser_sig=sig
+
+
     def start(self):
     def start(self):
         for sh in self.stakeholders:
         for sh in self.stakeholders:
             sh(self)
             sh(self)
@@ -134,4 +166,19 @@ class Z(object):
     def print_blockchain(self):
     def print_blockchain(self):
         for sh in self.stakeholders:
         for sh in self.stakeholders:
             bc = sh.blockchain
             bc = sh.blockchain
-            self.log.highlight(f"<blockchain>  {len(bc)} blocks: "+str(bc))
+            self.log.highlight(f"<blockchain>  {len(bc)} blocks: "+str(bc))
+
+    def confirm_endorsing(self, sig, blk_uid):
+        if blk_uid==self.current_slot:
+            self.current_blk_endorser_sig = sig
+
+    def corrupt_leader(self):
+        self.corrupt(self.current_leader_id)
+
+    def corrupt_endorse(self):
+        self.corrupt(self.current_endorser_id)
+
+    def corrupt_blk(self):
+        self.log.warn(f"<corrupt_blk> at slot: {self.current_slot}")
+        self.corrupt_leader()
+        self.corrupt_endorse()

+ 63 - 13
script/research/dpos/ouroboros/stakeholder.py

@@ -1,4 +1,3 @@
-#from asyncio.log import logger
 from ouroboros.block import Block, GensisBlock, EmptyBlock
 from ouroboros.block import Block, GensisBlock, EmptyBlock
 from ouroboros.blockchain import Blockchain
 from ouroboros.blockchain import Blockchain
 from ouroboros.epoch import Epoch
 from ouroboros.epoch import Epoch
@@ -7,13 +6,12 @@ from ouroboros.vrf import verify, VRF
 from ouroboros.utils import *
 from ouroboros.utils import *
 from ouroboros.logger import Logger
 from ouroboros.logger import Logger
 from ouroboros.consts import *
 from ouroboros.consts import *
-from copy import deepcopy
-import time
+
 '''
 '''
 \class Stakeholder
 \class Stakeholder
 '''
 '''
-
 class Stakeholder(object):
 class Stakeholder(object):
+
     def __init__(self, epoch_length=2, passwd='password'):
     def __init__(self, epoch_length=2, passwd='password'):
         #TODO (fix) remove redundant variables reley on environment
         #TODO (fix) remove redundant variables reley on environment
         self.passwd=passwd
         self.passwd=passwd
@@ -38,6 +36,7 @@ class Stakeholder(object):
         self.am_corrupt=False
         self.am_corrupt=False
         #
         #
         self.blockchain=None
         self.blockchain=None
+        self.current_blk_endorser_sig = None
 
 
     @property
     @property
     def is_leader(self):
     def is_leader(self):
@@ -78,8 +77,9 @@ class Stakeholder(object):
         self.tx = self.env.get_genesis_data()
         self.tx = self.env.get_genesis_data()
         self.tx[TX]=self.uncommited_tx
         self.tx[TX]=self.uncommited_tx
         self.uncommited_tx=''
         self.uncommited_tx=''
-        self.current_block=GensisBlock(self.current_block, self.tx, self.current_slot_uid)
+        self.current_block=GensisBlock(self.current_block, self.tx, self.current_slot_uid, self.env.genesis_time)
         self.current_epoch=Epoch(self.current_block, self.epoch_length, self.epoch_index, self.env.genesis_time)
         self.current_epoch=Epoch(self.current_block, self.epoch_length, self.epoch_index, self.env.genesis_time)
+   
     '''
     '''
     it's a callback function, and called by the diffuser
     it's a callback function, and called by the diffuser
     '''
     '''
@@ -100,6 +100,7 @@ class Stakeholder(object):
         self.__gen_genesis_epoch() 
         self.__gen_genesis_epoch() 
         if self.am_current_leader:
         if self.am_current_leader:
             self.broadcast_block()
             self.broadcast_block()
+        self.am_current_leader=False
 
 
     '''
     '''
     it's a callback function, and called by the diffuser
     it's a callback function, and called by the diffuser
@@ -117,7 +118,7 @@ class Stakeholder(object):
         if not verify(slot, sigma, proof, vrf_pk,vrf_g) :
         if not verify(slot, sigma, proof, vrf_pk,vrf_g) :
             #TODO the leader is corrupted, action to be taken against the corrupt stakeholder
             #TODO the leader is corrupted, action to be taken against the corrupt stakeholder
             #in this case this slot is empty
             #in this case this slot is empty
-            self.current_block=EmptyBlock() 
+            self.current_block=EmptyBlock(self.env.genesis_time) 
             if self.current_epoch==None:
             if self.current_epoch==None:
                 self.log.warn(f"<new_slot> leader verification fails, current_epoch is None!")
                 self.log.warn(f"<new_slot> leader verification fails, current_epoch is None!")
                 self.__gen_genesis_epoch()
                 self.__gen_genesis_epoch()
@@ -127,11 +128,24 @@ class Stakeholder(object):
             self.log.warn(f"<new_slot> current_epoch is None!")
             self.log.warn(f"<new_slot> current_epoch is None!")
             self.__gen_genesis_epoch()
             self.__gen_genesis_epoch()
         self.current_slot_uid = slot
         self.current_slot_uid = slot
-        prev_blk = self.blockchain[-1] if len(self.blockchain)>0 else EmptyBlock()
-        self.current_block=Block(prev_blk, self.tx, self.current_slot_uid)
+        prev_blk = self.blockchain[-1] if len(self.blockchain)>0 else EmptyBlock(self.env.genesis_time)
+        self.current_block=Block(prev_blk, self.tx, self.current_slot_uid, self.env.genesis_time)
         self.current_epoch.add_block(self.current_block)
         self.current_epoch.add_block(self.current_block)
         if self.am_current_leader:
         if self.am_current_leader:
             self.broadcast_block()
             self.broadcast_block()
+            self.end_leadership()
+        elif self.am_current_endorder:
+            self.endorse_block()
+            self.end_endorsing()
+
+
+    def end_leadership(self):
+        self.log(f"stakeholder:{str(self)} ending leadership for slot{self.current_slot_uid}")
+        self.am_current_leader=False
+
+    def end_endorsing(self):
+        self.log(f"stakeholder:{str(self)} ending endorsing for slot{self.current_slot_uid}")
+        self.am_current_endorder=False
 
 
     def set_leader(self):
     def set_leader(self):
         self.am_current_leader=True
         self.am_current_leader=True
@@ -145,11 +159,22 @@ class Stakeholder(object):
     def broadcast_block(self):
     def broadcast_block(self):
         self.log.highlight("broadcasting block")
         self.log.highlight("broadcasting block")
         assert(self.am_current_leader and self.current_block is not None)
         assert(self.am_current_leader and self.current_block is not None)
-        signed_block = sign_message(self.passwd, self.sig_sk, self.current_block)
+        signed_block=None
+        #TODO should wait for l slot until block is endorsed
+        if not self.current_block.endorsed:
+            self.current_block = EmptyBlock(self.env.genesis_time)
+        else:
+            signed_block = sign_message(self.passwd, self.sig_sk, self.current_block)
         self.env.broadcast_block(signed_block, self.current_slot_uid)
         self.env.broadcast_block(signed_block, self.current_slot_uid)
+        self.current_block=None
+    
+    def endorse_block(self):
+        if not self.am_current_endorder:
+            return
+        sig = sign_message(self.passwd, self.sig_sk, self.current_block)
+        self.env.endorse_block(sig, self.current_slot_uid)
 
 
-    def receive_block(self, signed_block, blk_uid):
-        self.log.highlight("receiving block")
+    def __get_blk(self, blk_uid):
         cur_blk = None
         cur_blk = None
         assert(blk_uid>0)
         assert(blk_uid>0)
         stashed=True
         stashed=True
@@ -159,9 +184,34 @@ class Stakeholder(object):
             #TODO this assumes synced blockchain
             #TODO this assumes synced blockchain
             cur_blk = self.blockchain[blk_uid]
             cur_blk = self.blockchain[blk_uid]
             stashed=False
             stashed=False
+        return cur_blk, stashed
+
+    def receive_block(self, signed_block, endorser_sig, blk_uid):
+        self.log.highlight("receiving block")
+        cur_blk, stashed = self.__get_blk(blk_uid)
+        
         #TODO to consider deley should retrive leader_pk of corresponding blk_uid
         #TODO to consider deley should retrive leader_pk of corresponding blk_uid
-        if verify_signature(self.env.current_leader_sig_pk, cur_blk, signed_block):
+        if verify_signature(self.env.current_leader_sig_pk, cur_blk, signed_block) \
+            and  verify_signature(self.env.current_endorser_sig_pk, cur_blk, endorser_sig):
             if stashed:
             if stashed:
                 self.current_epoch.add_block(cur_blk)
                 self.current_epoch.add_block(cur_blk)
         else:
         else:
-            self.env.corrupt(self.env.current_leader_id)
+            self.env.corrupt_blk()
+
+    def confirm_endorsing(self, signed_endorsed_block, blk_uid):
+        confirmed=False
+        if self.am_current_leader:
+            cur_blk, _ = self.__get_blk(blk_uid)
+            if verify_signature(self.env.current_endorser_sig_pk, cur_blk, signed_endorsed_block):
+                if blk_uid==self.current_slot_uid:
+                    self.current_block.set_endorsed()
+                else:
+                    self.blockchain[blk_uid].set_endorsed()
+                confirmed=True
+                #self.env.confirm_endorsing(signed_endorsed_block, blk_uid)
+            else:
+                confirmed=False
+                #self.env.confirm_endorsing(None, blk_uid)
+        self.log.info(f"<confirm_endorsing enderser signature: {self.current_blk_endorser_sig}")
+        return confirmed
+

+ 23 - 25
script/research/dpos/ouroboros/vrf.py

@@ -5,7 +5,29 @@ from ouroboros.utils import inverse_of
 from ouroboros.utils import vrf_hash
 from ouroboros.utils import vrf_hash
 eta.init(369)
 eta.init(369)
 
 
-
+'''
+verify signature
+@param x: signed messaged
+@param y: signature
+@param pi: [inf, x, y] proof components
+@param pk: [inf, x, y] public key components of the prover 
+@param g: group base
+'''
+def verify(x, y, pi, pk_raw, g):
+        gx = ecc.scalar_mult(x, g)
+        rhs = eta.pairing(*ecc.scalar_mult(1,g)[1:], *pi[1:])
+        if not y == rhs:
+            print(f"y: {y}, rhs: {rhs}")
+            return False
+        gxs = ecc.add(gx, pk_raw)
+        lhs = eta.pairing(*gxs[1:], *pi[1:])
+        rhs = eta.pairing(*ecc.scalar_mult(1, g)[1:], *ecc.scalar_mult(1, g)[1:])
+        if not lhs==rhs:
+            print(f"proposed {x}, {y}, {pi}, {pk_raw}, {g}")
+            print(f"lhs: {lhs},\nrhs: {rhs}")
+            return False
+        return True
+    
 class VRF(object):
 class VRF(object):
     '''
     '''
     verifiable random function implementation
     verifiable random function implementation
@@ -21,7 +43,6 @@ class VRF(object):
         self.pk = pk
         self.pk = pk
         self.sk = sk
         self.sk = sk
         self.g=g
         self.g=g
-        
 
 
     '''
     '''
     short signature without random oracle
     short signature without random oracle
@@ -58,26 +79,3 @@ class VRF(object):
             print(f"lhs: {lhs},\nrhs: {rhs}")
             print(f"lhs: {lhs},\nrhs: {rhs}")
             return False
             return False
         return True
         return True
-
-'''
-verify signature
-@param x: signed messaged
-@param y: signature
-@param pi: [inf, x, y] proof components
-@param pk: [inf, x, y] public key components of the prover 
-@param g: group base
-'''
-def verify(x, y, pi, pk_raw, g):
-        gx = ecc.scalar_mult(x, g)
-        rhs = eta.pairing(*ecc.scalar_mult(1,g)[1:], *pi[1:])
-        if not y == rhs:
-            print(f"y: {y}, rhs: {rhs}")
-            return False
-        gxs = ecc.add(gx, pk_raw)
-        lhs = eta.pairing(*gxs[1:], *pi[1:])
-        rhs = eta.pairing(*ecc.scalar_mult(1, g)[1:], *ecc.scalar_mult(1, g)[1:])
-        if not lhs==rhs:
-            print(f"proposed {x}, {y}, {pi}, {pk_raw}, {g}")
-            print(f"lhs: {lhs},\nrhs: {rhs}")
-            return False
-        return True

+ 1 - 1
script/research/dpos/simulation.py

@@ -1,6 +1,6 @@
+import time
 from ouroboros import Stakeholder
 from ouroboros import Stakeholder
 from ouroboros import Z
 from ouroboros import Z
-import time
 
 
 EPOCH_LENGTH = 2
 EPOCH_LENGTH = 2
 stakeholders = []
 stakeholders = []

+ 0 - 12
script/research/dpos/stakeholder_test.py

@@ -1,12 +0,0 @@
-from dpos.ouroboros import Stakeholder
-from dpos.ouroboros import Z
-import random
-
-EPOCH_LENGTH = 7
-stakeholders = []
-for i in range(3):
-    stakeholders.append(Stakeholder)(EPOCH_LENGTH)
-
-environment = Z(stakeholders, EPOCH_LENGTH)
-
-environment.start()