Browse Source

[research/ouroboros] added Input endorser

mohab 4 năm trước cách đây
mục cha
commit
d3b1dcebb3

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

@@ -1,8 +1,7 @@
+import threading
 from ouroboros.clock import SynchedNTPClock
 from ouroboros.vrf import VRF
 from ouroboros.logger import Logger
-import threading
-import time 
 
 '''
 \class TrustedBeacon
@@ -51,6 +50,8 @@ class TrustedBeacon(SynchedNTPClock, threading.Thread):
         else:
             sigmas = []
             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):
                 self.log.info(f"callback: new slot of idx: {self.current_slot}, epoch slot {i}")
                 y, pi = self.vrf.sign(self.current_slot)

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

@@ -1,6 +1,7 @@
 import json
 from ouroboros.utils import encode_genesis_data, decode_gensis_data, state_hash
 from ouroboros.consts import *
+from ouroboros.logger import Logger
 
 '''
 single block B_i for slot i in the system live time L,
@@ -16,7 +17,7 @@ class Block(object):
         it's one-based
     @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
         self.state=''
         if slot_uid>1:
@@ -24,6 +25,8 @@ class Block(object):
         self.tx = data
         self.sl = slot_uid
         self.is_genesis=genesis
+        self.endorsed=False
+        self.log = Logger(genesis_time)
 
     def __repr__(self):
         if self.is_genesis:
@@ -52,6 +55,9 @@ class Block(object):
             'sl': self.sl}
         return json.encoder(d)
 
+    def set_endorsed(self):
+        self.endorsed=True
+
     
     @property
     def data(self):
@@ -103,5 +109,5 @@ lead by offline leader
 is an empty 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
-import time
+
 '''
 Non-forkable Blockchain for simplicity
-#TODO consider forkable property
+#TODO implement forkable chain
 '''
 class Blockchain(object):
     def __init__(self, R, genesis_time):
@@ -37,6 +37,3 @@ class Blockchain(object):
                 self.__add_block(block)
             else:
                 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 math
 import random
+import time
 from ouroboros.logger import Logger
 from ouroboros.consts import *
-import time
+
 '''
 \class Z is the environment
 '''
@@ -15,8 +17,10 @@ class Z(object):
         self.stakeholders = np.array(stakeholdes)
         self.adversary_mask=np.array([True]*len(stakeholdes))
         self.current_epoch_leaders=[-1]*self.epoch_length
+        self.current_epoch_endorsers=[-1]*self.epoch_length
         self.current_slot=0
         self.log.info("Z initialized")
+        self.current_blk_endorser_sig=None
     
     def __repr__(self):
         buff= f"envirnment of {self.length} stakholders"
@@ -35,13 +39,27 @@ class Z(object):
         return genesis_data
     
     @property
-    def current_leader_id(self):
+    def epoch_slot(self):
         return self.current_slot%self.epoch_length
+
+    @property
+    def current_leader_id(self):
+        return self.current_epoch_leaders[self.epoch_slot]
+
     @property
     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]
 
+    @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
     def current_leader_vrf_pk(self):
         return self.stakeholders[self.current_leader_id].vrf_pk
@@ -54,6 +72,9 @@ class Z(object):
     def current_leader_sig_pk(self):
         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
     def obfuscate_idx(self, i):
@@ -78,6 +99,7 @@ class Z(object):
     @property
     def length(self):
         return len(self.stakeholders)
+
     @property
     def honest(self):
         return len(self.stakeholders[self.adversary_mask])
@@ -95,13 +117,17 @@ class Z(object):
             seed = leader_selection_hash(sigma)
             random.seed(seed)
             leader_idx=seed%self.length
+            endorser_idx=random.randint(0,self.length)
             # 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)
+                enderser_idx=random.randint(0,self.length)
+
             #TODO select the following leader for this epoch, note, 
             # under a single condition that no one is able to predict who is next
             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):
         self.current_slot=slot
@@ -111,21 +137,27 @@ class Z(object):
         if current_leader.is_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_endorsers[self.current_endorser_id]].set_endorser()
     
     def new_epoch(self, slot, sigmas, proofs):
         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):
         for stakeholder in self.stakeholders:
             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()
 
+    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):
         for sh in self.stakeholders:
             sh(self)
@@ -134,4 +166,19 @@ class Z(object):
     def print_blockchain(self):
         for sh in self.stakeholders:
             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.blockchain import Blockchain
 from ouroboros.epoch import Epoch
@@ -7,13 +6,12 @@ from ouroboros.vrf import verify, VRF
 from ouroboros.utils import *
 from ouroboros.logger import Logger
 from ouroboros.consts import *
-from copy import deepcopy
-import time
+
 '''
 \class Stakeholder
 '''
-
 class Stakeholder(object):
+
     def __init__(self, epoch_length=2, passwd='password'):
         #TODO (fix) remove redundant variables reley on environment
         self.passwd=passwd
@@ -38,6 +36,7 @@ class Stakeholder(object):
         self.am_corrupt=False
         #
         self.blockchain=None
+        self.current_blk_endorser_sig = None
 
     @property
     def is_leader(self):
@@ -78,8 +77,9 @@ class Stakeholder(object):
         self.tx = self.env.get_genesis_data()
         self.tx[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)
+   
     '''
     it's a callback function, and called by the diffuser
     '''
@@ -100,6 +100,7 @@ class Stakeholder(object):
         self.__gen_genesis_epoch() 
         if self.am_current_leader:
             self.broadcast_block()
+        self.am_current_leader=False
 
     '''
     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) :
             #TODO the leader is corrupted, action to be taken against the corrupt stakeholder
             #in this case this slot is empty
-            self.current_block=EmptyBlock() 
+            self.current_block=EmptyBlock(self.env.genesis_time) 
             if self.current_epoch==None:
                 self.log.warn(f"<new_slot> leader verification fails, current_epoch is None!")
                 self.__gen_genesis_epoch()
@@ -127,11 +128,24 @@ class Stakeholder(object):
             self.log.warn(f"<new_slot> current_epoch is None!")
             self.__gen_genesis_epoch()
         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)
         if self.am_current_leader:
             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):
         self.am_current_leader=True
@@ -145,11 +159,22 @@ class Stakeholder(object):
     def broadcast_block(self):
         self.log.highlight("broadcasting block")
         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.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
         assert(blk_uid>0)
         stashed=True
@@ -159,9 +184,34 @@ class Stakeholder(object):
             #TODO this assumes synced blockchain
             cur_blk = self.blockchain[blk_uid]
             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
-        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:
                 self.current_epoch.add_block(cur_blk)
         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
 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):
     '''
     verifiable random function implementation
@@ -21,7 +43,6 @@ class VRF(object):
         self.pk = pk
         self.sk = sk
         self.g=g
-        
 
     '''
     short signature without random oracle
@@ -58,26 +79,3 @@ class VRF(object):
             print(f"lhs: {lhs},\nrhs: {rhs}")
             return False
         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 Z
-import time
 
 EPOCH_LENGTH = 2
 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()