├── README.md ├── __pycache__ ├── _util.cpython-37.pyc ├── bcclient.cpython-37.pyc ├── bcpeer.cpython-37.pyc └── client.cpython-37.pyc ├── _util.py ├── bc_layer ├── __pycache__ │ ├── _params.cpython-37.pyc │ ├── _util.cpython-37.pyc │ ├── block.cpython-37.pyc │ ├── blockchain.cpython-37.pyc │ └── ecc.cpython-37.pyc ├── _params.py ├── _util.py ├── block.py ├── blockchain.py └── ecc.py ├── bcclient.py ├── bcpeer.py ├── implementation.md ├── main.py ├── miner_list.txt └── p2p_layer ├── __pycache__ ├── _util.cpython-37.pyc ├── connection.cpython-37.pyc └── peer.cpython-37.pyc ├── _util.py ├── connection.py └── peer.py /README.md: -------------------------------------------------------------------------------- 1 | # PBFT simulation in Python 2 | 3 | Readme and code-refactoring will be soon... 4 | -------------------------------------------------------------------------------- /__pycache__/_util.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/__pycache__/_util.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/bcclient.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/__pycache__/bcclient.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/bcpeer.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/__pycache__/bcpeer.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/client.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/__pycache__/client.cpython-37.pyc -------------------------------------------------------------------------------- /_util.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | def get_list_from_file(filename): 4 | f = open(filename, "r") 5 | obtained_list = [] 6 | while True: 7 | line = f.readline() 8 | line = line.replace('\n', '') 9 | if not line: break 10 | obtained_list.append(line) 11 | f.close() 12 | return obtained_list -------------------------------------------------------------------------------- /bc_layer/__pycache__/_params.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/bc_layer/__pycache__/_params.cpython-37.pyc -------------------------------------------------------------------------------- /bc_layer/__pycache__/_util.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/bc_layer/__pycache__/_util.cpython-37.pyc -------------------------------------------------------------------------------- /bc_layer/__pycache__/block.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/bc_layer/__pycache__/block.cpython-37.pyc -------------------------------------------------------------------------------- /bc_layer/__pycache__/blockchain.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/bc_layer/__pycache__/blockchain.cpython-37.pyc -------------------------------------------------------------------------------- /bc_layer/__pycache__/ecc.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/bc_layer/__pycache__/ecc.cpython-37.pyc -------------------------------------------------------------------------------- /bc_layer/_params.py: -------------------------------------------------------------------------------- 1 | """ ECC parameters """ 2 | ecc_curves = [ 3 | "secp256r1", 4 | "secp384r1", 5 | "secp521r1", 6 | "Brainpool-p256r1", 7 | "Brainpool-p384r1", 8 | "Brainpool-p512r1" 9 | ] # a kind of ecc curves 10 | ecc_curve_default = ecc_curves[0] # first curve is default curve 11 | 12 | """ blockchain parameters """ 13 | max_block_size = 65536 # 64 KB = 65,536 bytes 14 | max_tx_in_block = 1 # For one block, maybe 120~130 tx can be inserted 15 | genesis_block_seed = "@" # Seed to generate the prev_hash in the genesis block (promised) 16 | fault_factor = 2/3 -------------------------------------------------------------------------------- /bc_layer/_util.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | import pickle 3 | import hashlib 4 | import time 5 | 6 | def gen_timestamp(): 7 | """ generate a timestamp() """ 8 | return time.time() 9 | 10 | def dict_to_bin(dictionary): 11 | """ dictionary to binary """ 12 | return pickle.dumps(dictionary) 13 | 14 | def bin_to_dict(binary): 15 | """ binary to dictionary """ 16 | return pickle.loads(binary) 17 | 18 | def bin_to_str(binary): 19 | """ binary to string """ 20 | return binary.decode() 21 | 22 | def str_to_bin(string): 23 | """ string to binary """ 24 | return string.encode() 25 | 26 | def size_of_dict(dictionary): 27 | """ get the size of dictionary in bytes? """ 28 | return len(dict_to_bin(dictionary)) 29 | 30 | def hash256(data): 31 | """ calculate a hashed data by hashlib """ 32 | t = type(data) 33 | if t is dict: 34 | return hashlib.sha256(dict_to_bin(data)).hexdigest() 35 | elif t is str: 36 | return hashlib.sha256(str_to_bin(data)).hexdigest() 37 | 38 | 39 | -------------------------------------------------------------------------------- /bc_layer/block.py: -------------------------------------------------------------------------------- 1 | from . import _util 2 | 3 | class Block(): 4 | def __init__(self, prev_hash, payload=[], gen_time=_util.gen_timestamp()): 5 | self.prev_hash = prev_hash 6 | self.payload = payload # list of dictionary? double dictionary? 7 | self.merkle_root = self.__make_merkle_root(self.payload) 8 | self.gen_time = gen_time 9 | 10 | def to_dict(self): 11 | new_block = { 12 | 'prev_hash': self.prev_hash, 13 | 'merkle_root': self.merkle_root, 14 | 'gen_time': self.gen_time, 15 | 'payload': self.payload 16 | } 17 | return new_block 18 | 19 | def __make_merkle_root(self, payload): 20 | if payload == []: 21 | return _util.hash256("") 22 | 23 | # hashing all transactions in block 24 | to_merkle_tree = [] 25 | for tx in payload: 26 | to_merkle_tree.append(_util.hash256(tx)) 27 | 28 | while True: 29 | new_to_merkle_tree = [] 30 | while len(to_merkle_tree) != 0 : 31 | if len(to_merkle_tree) == 1: 32 | block1 = to_merkle_tree.pop() 33 | block2 = block1 34 | else : 35 | block1 = to_merkle_tree.pop() 36 | block2 = to_merkle_tree.pop() 37 | hashed_block = _util.hash256(block1 + block2) 38 | new_to_merkle_tree.append(hashed_block) 39 | if len(new_to_merkle_tree) == 1 : 40 | merkle_root = new_to_merkle_tree.pop() 41 | return merkle_root 42 | to_merkle_tree = new_to_merkle_tree 43 | 44 | def check_merkle_root(self): 45 | return self.__make_merkle_root() == self.merkle_root -------------------------------------------------------------------------------- /bc_layer/blockchain.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | from . import _util 3 | from . import _params 4 | from . import ecc 5 | from . import block 6 | 7 | class Blockchain(): 8 | def __init__(self, id, ecc_curve=_params.ecc_curve_default, miner_list=[]): 9 | self.id = id 10 | self.debug = 1 11 | self.miner_list = miner_list 12 | self.ecc = ecc.Ecc(ecc_curve) 13 | 14 | # storage settings 15 | self.txpool = [] # list of request msg 16 | self.ledger = [ 17 | block.Block(prev_hash=_util.hash256(_params.genesis_block_seed), gen_time=0).to_dict() 18 | ] 19 | 20 | # consensus settings 21 | self.view = 0 22 | self.status = None 23 | self.leader = None 24 | 25 | # Processing 26 | self.request_msg = None 27 | self.requester_id = None 28 | self.preprepare_msg = None 29 | self.prepare_msgs = [] 30 | self.commit_msgs = [] 31 | self.reply_msgs = [] # for the client 32 | 33 | # handlers 34 | self.handler = { 35 | "REQU": self.handler_request, 36 | "PPRE": self.handler_preprepare, 37 | "PREP": self.handler_prepare, 38 | "COMM": self.handler_commit, 39 | "REPL": self.handler_reply 40 | } 41 | 42 | def print_debug(self, msg): 43 | if self.debug: 44 | print("[%s] (v:%d,s:%s) %s" % (self.id, self.view, self.status, msg)) 45 | 46 | def isleader_todo(self): 47 | # view % num of miners = leader 48 | if self.leader == None: 49 | leader_index = self.view % len(self.miner_list) 50 | self.leader = self.miner_list[leader_index] 51 | return (self.id == self.leader) and (self.status == None) and (len(self.txpool) > 0) 52 | 53 | def round_finish(self): 54 | accepted_block = self.preprepare_msg["m"] 55 | self.ledger.append(accepted_block) 56 | accepted_txs = accepted_block["payload"] 57 | for tx in accepted_txs: 58 | if tx in self.txpool: 59 | self.txpool.remove(tx) 60 | else: 61 | self.print_debug("Error: no same transactions") 62 | self.view += 1 63 | 64 | self.request_msg = None 65 | self.requester_id = None 66 | self.preprepare_msg = None 67 | self.prepare_msgs = [] 68 | self.commit_msgs = [] 69 | self.reply_msgs = [] 70 | 71 | self.status = None 72 | self.leader = None 73 | 74 | self.print_debug("Round is finished, Status: " + str(self.status) + ", Now View: " + str(self.view)) 75 | 76 | def get_last_block_prev_hash(self): 77 | # prev_hash 78 | last_block = self.ledger[len(self.ledger)-1] 79 | del last_block["payload"] 80 | prev_hash = _util.hash256(last_block) # without payload = header 81 | return prev_hash 82 | 83 | def get_txpool_one(self): 84 | return [self.txpool[0]] 85 | 86 | def get_txpool_max_block_size(self, prev_hash): 87 | txs = [] 88 | new_block = block.Block(prev_hash, txs) 89 | current_size = _util.size_of_dict(new_block.to_dict()) 90 | residual_size = _params.max_block_size - current_size 91 | 92 | max_count_tx = 0 93 | for tx in self.txpool: 94 | tx_size = _util.size_of_dict(tx) 95 | if residual_size - tx_size >= 0: 96 | residual_size = residual_size - tx_size 97 | max_count_tx = max_count_tx + 1 98 | else: 99 | break 100 | 101 | txs = [] 102 | for _ in range(max_count_tx): 103 | txs.append(self.txpool.pop()) 104 | return txs 105 | 106 | def gen_request_msg(self): 107 | """ for a client to broadcast the request msg(transaction) """ 108 | msg = {} 109 | msg["client"] = self.id 110 | msg["m"] = "EXAMPLE TRANSACTION" 111 | msg["t"] = _util.gen_timestamp() 112 | 113 | self.status = "REQUEST" 114 | return msg 115 | 116 | def gen_preprepare_msg(self): 117 | """ Only leader generates a (PPRE) msg in a View """ 118 | """ The status of the leader will go through (PREP) """ 119 | msg = {} 120 | msg["status"] = "PREPREPARE" 121 | msg["v"] = self.view 122 | msg["n"] = 1 123 | 124 | prev_hash = self.get_last_block_prev_hash() 125 | txs = self.get_txpool_one() # self.get_txpool_max_block_size(prev_hash) 126 | new_block = block.Block(prev_hash, txs) # error 127 | msg["m"] = new_block.to_dict() 128 | 129 | self.status = "PREPARE" 130 | self.preprepare_msg = msg # for leader, preprepare_msg is prepared 131 | return msg 132 | 133 | def gen_prepare_msg(self): 134 | msg = {} 135 | msg["status"] = "PREPARE" 136 | msg["v"] = self.view 137 | msg["d(m)"] = _util.hash256(self.preprepare_msg) 138 | msg["n"] = self.preprepare_msg["n"] 139 | msg["i"] = self.id 140 | 141 | self.status = "PREPARE" 142 | self.prepare_msgs.append(msg) # my msg 143 | return msg 144 | 145 | def gen_commit_msg(self): 146 | msg = {} 147 | msg["status"] = "COMMIT" 148 | msg["v"] = self.view 149 | msg["d(m)"] = _util.hash256(self.preprepare_msg) 150 | msg["n"] = self.preprepare_msg["n"] 151 | msg["i"] = self.id 152 | 153 | self.status = "COMMIT" 154 | self.commit_msgs.append(msg) # my msg 155 | return msg 156 | 157 | def gen_reply_msg(self): 158 | msg = {} 159 | msg["status"] = "REPLY" 160 | msg["v"] = self.view 161 | msg["n"] = self.preprepare_msg["n"] 162 | msg["i"] = self.id 163 | 164 | self.status = "REPLY" 165 | requester = self.requester_id 166 | return (requester, msg) 167 | 168 | def handler_request(self, src_id, msgdata): 169 | self.requester_id = msgdata["client"] 170 | self.request_msg = msgdata 171 | self.txpool.append(msgdata) 172 | self.print_debug("Received (REQU) from " + str(self.requester_id)) 173 | return True 174 | 175 | def handler_preprepare(self, src_id, msgdata): 176 | self.print_debug("Received (PPRE) from " + str(self.leader)) 177 | def verify_txs(txs): 178 | return True 179 | 180 | def verify_preprepare(self, msgdata): 181 | # msg checking 182 | if msgdata["status"] != "PREPREPARE": 183 | return False 184 | elif msgdata["v"] != self.view: 185 | return False 186 | 187 | # block checking 188 | last_block = self.ledger[len(self.ledger)-1] 189 | del last_block["payload"] 190 | prev_hash = _util.hash256(last_block) # without payload = header 191 | 192 | if msgdata["m"]["prev_hash"] != prev_hash: 193 | return False 194 | elif msgdata["m"]["gen_time"] >= _util.gen_timestamp(): 195 | return False 196 | elif not verify_txs(msgdata["m"]["payload"]): 197 | return False 198 | 199 | return True 200 | 201 | if verify_preprepare(self, msgdata): 202 | self.preprepare_msg = msgdata 203 | self.requester_id = msgdata["m"]["payload"][0]["client"] # 1 transaction is in it 204 | return "PREP" 205 | else: 206 | return False 207 | 208 | def handler_prepare(self, src_id, msgdata): 209 | self.print_debug("Received (PREP) from " + str(msgdata["i"])) 210 | def verify_prepare(self, msgdata): 211 | if msgdata["status"] != "PREPARE": 212 | return False 213 | elif msgdata["v"] != self.view: 214 | return False 215 | elif msgdata["d(m)"] != _util.hash256(self.preprepare_msg): 216 | return False 217 | return True 218 | 219 | if verify_prepare(self, msgdata) and (self.status == "PREPARE"): 220 | self.prepare_msgs.append(msgdata) 221 | if len(self.prepare_msgs) > int((_params.fault_factor * (len(self.miner_list)-1))) and (self.status == "PREPARE"): 222 | self.status = "PREPARED" 223 | return "COMM" 224 | else: 225 | return False 226 | else: 227 | return False 228 | 229 | def handler_commit(self, src_id, msgdata): 230 | self.print_debug("Received (COMM) from " + str(msgdata["i"])) 231 | def verify_commit(self, msgdata): 232 | if msgdata["status"] != "COMMIT": 233 | return False 234 | elif msgdata["v"] != self.view: 235 | return False 236 | elif msgdata["d(m)"] != _util.hash256(self.preprepare_msg): 237 | return False 238 | return True 239 | 240 | if verify_commit(self, msgdata) and (self.status == "COMMIT"): 241 | self.commit_msgs.append(msgdata) 242 | if len(self.commit_msgs) > int((_params.fault_factor * (len(self.miner_list)-1))) and (self.status == "COMMIT"): 243 | self.status = "COMMITTED" 244 | return "REPL" 245 | else: 246 | return False 247 | else: 248 | return False 249 | 250 | def handler_reply(self, src_id, msgdata): 251 | self.print_debug("(Client) Received (REPL) from " + str(msgdata["i"])) 252 | if self.status == "REQUEST" and self.view == msgdata["v"]: 253 | self.reply_msgs.append(msgdata) 254 | if len(self.reply_msgs) > int((_params.fault_factor * (len(self.miner_list)-1))): 255 | self.view += 1 256 | self.reply_msgs = [] 257 | self.status = None 258 | self.print_debug("Round is finished, Status: " + str(self.status) + ", Now View: " + str(self.view)) 259 | return "REQU" 260 | else: 261 | return False 262 | else: 263 | return False 264 | -------------------------------------------------------------------------------- /bc_layer/ecc.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | import base64 3 | import hashlib 4 | from ecpy.curves import Curve,Point 5 | from ecpy.keys import ECPublicKey, ECPrivateKey 6 | from ecpy.ecdsa import ECDSA 7 | from ecpy.ecrand import rnd 8 | from Crypto import Random 9 | from Crypto.Cipher import AES 10 | 11 | class Ecc: 12 | def __init__(self, curve): 13 | self.curve_str = curve 14 | (self.pu_key, self.pv_key, self.curve, self.signer) = self.generate_key(curve) 15 | 16 | def generate_key(self, curve): # curve : curve 이름 17 | cv = Curve.get_curve(curve) 18 | pv_key = ECPrivateKey(rnd(cv.order), cv) 19 | pu_key = pv_key.get_public_key() 20 | return (pu_key, pv_key, cv, ECDSA()) 21 | 22 | def generate_key_with_peerid(self, curve, peerid): 23 | cv = Curve.get_curve(curve) 24 | pv_key = ECPrivateKey(peerid, cv) 25 | pu_key = pv_key.get_public_key() 26 | return (pu_key, pv_key, cv, ECDSA()) 27 | 28 | def ecdh(self, ppu, d): # ppu : 대칭키 상대의 공개키 d: 자신의 공개키 29 | return ppu * d 30 | 31 | def aes_enc(self, key, msg): 32 | key = hashlib.sha256(key.encode()).digest() 33 | iv = Random.new().read(AES.block_size) 34 | msg = self._pad(msg) 35 | cipher = AES.new(key, AES.MODE_CBC, iv) 36 | # return base64.b64encode(iv + cipher.encrypt(msg)) 37 | return iv + cipher.encrypt(msg) 38 | 39 | def aes_dec(self, key, enc): 40 | key = hashlib.sha256(key.encode()).digest() 41 | # enc = base64.b64decode(enc) 42 | iv = enc[:AES.block_size] 43 | cipher = AES.new(key, AES.MODE_CBC, iv) 44 | return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8') 45 | 46 | def _pad(self, s): 47 | bs = (len(s)//16 + 1)*16 48 | return s + ( bs - len(s) % bs) * chr(bs - len(s) % bs) 49 | 50 | def _unpad(self, s): 51 | return s[:-ord(s[len(s)-1:])] 52 | 53 | def generate_pu_key_with_point(self, x, y): 54 | return ECPublicKey(Point(x, y, self.curve)) 55 | 56 | # ecc = Ecc('secp256r1') 57 | # encccc = ecc.aes_enc("12455",''.join((ecc.pu_key, "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW", "3"))) 58 | # print(encccc) 59 | # deccccc = ecc.aes_dec("12455",encccc) 60 | # print(deccccc) 61 | # print(ecc.signer.verify(hashlib.sha256("123123".encode()).hexdigest().encode(),ecc.signer.sign(hashlib.sha256("123123".encode()).hexdigest().encode(),ecc.pv_key),ecc.pu_key)) 62 | -------------------------------------------------------------------------------- /bcclient.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | import random 3 | import socket 4 | import sys 5 | import time 6 | 7 | import _util 8 | import bc_layer.blockchain as b_blockchain 9 | import bc_layer._params as b_params 10 | import p2p_layer.peer as p_peer 11 | 12 | class Bcclient(): 13 | # init: ip, port, ecc_curve 14 | def __init__(self, 15 | ip=socket.gethostbyname(socket.getfqdn()), 16 | port=7250, 17 | ecc_curve=b_params.ecc_curve_default, 18 | miner_filename="miner_list.txt", 19 | debug=0 20 | ): 21 | # parameter acceptance 22 | self.id = "%s:%d" % (ip, port) 23 | self.miner_list = _util.get_list_from_file(miner_filename) 24 | self.debug = debug 25 | 26 | # # class generation 27 | self.bc = b_blockchain.Blockchain(self.id, ecc_curve, self.miner_list) 28 | self.comm = p_peer.Peer(ip, port) 29 | 30 | def print_debug(self, msg): 31 | if self.debug: 32 | print("[%s] (Client) %s" % (self.id, msg)) 33 | 34 | def main_loop(self): 35 | # mainloop 36 | self.print_debug("Main loop started.") 37 | while True: 38 | try: 39 | if self.bc.status != "REQUEST" : 40 | time.sleep(1) 41 | msg = self.bc.gen_request_msg() 42 | self.comm.broadcasting(self.miner_list, "REQU", msg) 43 | self.print_debug("Broadcast (REQU) msg") 44 | # when reply is received 45 | else: 46 | (src_id, msgtype, msgdata) = self.comm.listening() 47 | next_action = self.bc.handler[msgtype](src_id, msgdata) 48 | 49 | except KeyboardInterrupt: 50 | self.comm.close() 51 | sys.exit() 52 | break 53 | except: 54 | continue -------------------------------------------------------------------------------- /bcpeer.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | import socket 3 | import sys 4 | import time 5 | 6 | import _util 7 | import bc_layer.blockchain as b_blockchain 8 | import bc_layer._params as b_params 9 | import p2p_layer.peer as p_peer 10 | 11 | class Bcpeer: 12 | # init: ip, port, ecc_curve 13 | def __init__(self, 14 | ip=socket.gethostbyname(socket.getfqdn()), 15 | port=7250, 16 | ecc_curve=b_params.ecc_curve_default, 17 | miner_filename="miner_list.txt", 18 | debug=0 19 | ): 20 | # parameter acceptance 21 | self.id = "%s:%d" % (ip, port) 22 | self.miner_list = _util.get_list_from_file(miner_filename) 23 | self.debug = debug 24 | 25 | # class generation 26 | self.bc = b_blockchain.Blockchain(self.id, ecc_curve, self.miner_list) 27 | self.comm = p_peer.Peer(ip, port) 28 | 29 | def print_debug(self, msg): 30 | if self.debug: 31 | print("[%s] (v:%d,s:%s) %s" % (self.id, self.bc.view, self.bc.status, msg)) 32 | 33 | def main_loop(self): 34 | # mainloop 35 | self.print_debug("Main loop started.") 36 | while True: 37 | try: 38 | time.sleep(1) 39 | if self.bc.isleader_todo(): 40 | self.print_debug("Leader to do (PPRE)") 41 | msg = self.bc.gen_preprepare_msg() 42 | self.print_debug("Broadcast --(PPRE)-->> ") 43 | self.comm.broadcasting(self.miner_list, "PPRE", msg) 44 | else: 45 | (src_id, msgtype, msgdata) = self.comm.listening() 46 | next_action = self.bc.handler[msgtype](src_id, msgdata) 47 | if next_action == "PREP": 48 | msg = self.bc.gen_prepare_msg() 49 | self.print_debug("Broadcast --(PREP)-->> ") 50 | self.comm.broadcasting(self.miner_list, "PREP", msg) 51 | elif next_action == "COMM": 52 | msg = self.bc.gen_commit_msg() 53 | self.print_debug("Broadcast --(COMM)-->>") 54 | self.comm.broadcasting(self.miner_list, "COMM", msg) 55 | elif next_action == "REPL": 56 | (requester, msg) = self.bc.gen_reply_msg() 57 | self.print_debug("Send --(REPL)--> " + str(requester)) 58 | self.comm.sending(requester, "REPL", msg) 59 | self.bc.round_finish() 60 | except KeyboardInterrupt: 61 | self.comm.close() 62 | sys.exit() 63 | break 64 | except: 65 | continue -------------------------------------------------------------------------------- /implementation.md: -------------------------------------------------------------------------------- 1 | # 2020-10-27 2 | 3 | 1) bc_layer, p2p_layer 폴더 구분 후 파일 작성 4 | 2) bc_layer = blockchain layer 5 | 3) p2p_layer = p2p communication layer 6 | 4) bcpeer -> bc_layer, p2p_layer 생성 7 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import bcpeer 2 | import bcclient 3 | import multiprocessing 4 | 5 | def __gen_client(ip, port): 6 | client = bcclient.Bcclient(ip=ip, port=port, debug=1) 7 | client.main_loop() 8 | 9 | def __gen_bcpeer(ip, port): 10 | bcpeer_i = bcpeer.Bcpeer(ip=ip, port=port, debug=1) 11 | bcpeer_i.main_loop() 12 | 13 | if __name__ == "__main__" : 14 | for port in range(6230, 6234): 15 | p = multiprocessing.Process( 16 | target=__gen_bcpeer, 17 | args=("127.0.0.1", port) 18 | ) 19 | p.start() 20 | c = multiprocessing.Process( 21 | target=__gen_client, 22 | args=("127.0.0.1", 7250) 23 | ) 24 | c.start() 25 | -------------------------------------------------------------------------------- /miner_list.txt: -------------------------------------------------------------------------------- 1 | 127.0.0.1:6230 2 | 127.0.0.1:6231 3 | 127.0.0.1:6232 4 | 127.0.0.1:6233 -------------------------------------------------------------------------------- /p2p_layer/__pycache__/_util.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/p2p_layer/__pycache__/_util.cpython-37.pyc -------------------------------------------------------------------------------- /p2p_layer/__pycache__/connection.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/p2p_layer/__pycache__/connection.cpython-37.pyc -------------------------------------------------------------------------------- /p2p_layer/__pycache__/peer.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hubts/my-pbft-simulation-py/d30b6d7b17aea3f032894fa7ebffd5126b57f886/p2p_layer/__pycache__/peer.cpython-37.pyc -------------------------------------------------------------------------------- /p2p_layer/_util.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | import pickle 3 | 4 | def dict_to_bin(dictionary): 5 | """ dictionary to binary """ 6 | return pickle.dumps(dictionary) 7 | 8 | def bin_to_dict(binary): 9 | """ binary to dictionary """ 10 | return pickle.loads(binary) -------------------------------------------------------------------------------- /p2p_layer/connection.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | 4 | from . import _util 5 | 6 | class Connection: 7 | def __init__(self, destination_ip, destination_port, sock=None): 8 | if not sock: 9 | self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 10 | self.s.connect((destination_ip, int(destination_port))) 11 | else: 12 | self.s = sock 13 | self.sd = self.s.makefile("brw", 0) 14 | 15 | def transmit(self, msgtype, msgdata): 16 | try: 17 | # packing the msg 18 | msgtype = msgtype.encode() 19 | msgdata = _util.dict_to_bin(msgdata) 20 | msglen = len(msgdata) 21 | msg = struct.pack("!4sL%ds" % msglen, msgtype, msglen, msgdata) 22 | # send the msg 23 | self.sd.write(msg) 24 | self.sd.flush() 25 | except KeyboardInterrupt: 26 | self.close() 27 | return False 28 | except: 29 | return False 30 | return True 31 | 32 | def receive(self): 33 | try: 34 | msgtype = self.sd.read(4) 35 | if not msgtype : 36 | return (None, None) 37 | lenstr = self.sd.read(4) 38 | msglen = int(struct.unpack("!L", lenstr)[0]) 39 | msg = b'' 40 | while len(msg) != msglen : 41 | data = self.sd.read(min(2048, msglen - len(msg))) 42 | if not len(data) : 43 | break 44 | msg += data 45 | if len(msg) != msglen : 46 | return (None, None) 47 | except KeyboardInterrupt : 48 | self.close() 49 | return (None, None) 50 | except : 51 | return (None, None) 52 | msgtype = msgtype.decode() 53 | msgtype = msgtype.upper() 54 | msgdata = _util.bin_to_dict(msg) 55 | return (msgtype, msgdata) 56 | 57 | def close(self): 58 | self.s.close() 59 | self.s = None 60 | self.sd = None -------------------------------------------------------------------------------- /p2p_layer/peer.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | import os 4 | import psutil 5 | import socket 6 | 7 | from . import connection 8 | 9 | class Peer: 10 | def __init__(self, ip, port): 11 | self.ip = ip 12 | self.port = int(port) 13 | self.socket = self.__make_socket() 14 | 15 | def __make_socket(self, backlog=5): 16 | """ To construct and prepare a server socket listening on the given port. """ 17 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 19 | s.bind(('', self.port)) 20 | s.listen(backlog) 21 | s.settimeout(2) 22 | return s 23 | 24 | def listening(self): 25 | """ Lisetning the received msg and handling """ 26 | client_sock = self.socket.accept()[0] 27 | client_sock.settimeout(None) 28 | 29 | host, port = client_sock.getpeername() 30 | p2p_conn = connection.Connection(host, port, client_sock) 31 | try: 32 | msgtype, msgdata = p2p_conn.receive() 33 | except KeyboardInterrupt: 34 | self.close() 35 | return (False, False, False) 36 | p2p_conn.close() 37 | opposite_id = "%s:%d" % (host, port) 38 | return (opposite_id, msgtype, msgdata) 39 | 40 | def sending(self, dest_id, msgtype, msgdata): 41 | # destination peer id = ip:port 42 | ip = dest_id.split(":")[0] 43 | port = dest_id.split(":")[1] 44 | # try to send 45 | try : 46 | p2p_conn = connection.Connection(ip, port) 47 | p2p_conn.transmit(msgtype, msgdata) 48 | except KeyboardInterrupt: 49 | self.close() 50 | return False 51 | return True 52 | 53 | def broadcasting(self, dest_id_list, msgtype, msgdata): 54 | myid = "%s:%d" % (self.ip, self.port) 55 | for dest_id in dest_id_list: 56 | if dest_id != myid: 57 | self.sending(dest_id, msgtype, msgdata) 58 | return True 59 | 60 | def close(self): 61 | self.socket.close() 62 | parent_pid = os.getpid() 63 | parent = psutil.Process(parent_pid) 64 | for child in parent.children(recursive=True): 65 | child.kill() 66 | return True --------------------------------------------------------------------------------