diff --git a/lib/chiavdf/inkfish/proof_wesolowski.py b/lib/chiavdf/inkfish/proof_wesolowski.py index 9ce818ef..91f2cc4e 100644 --- a/lib/chiavdf/inkfish/proof_wesolowski.py +++ b/lib/chiavdf/inkfish/proof_wesolowski.py @@ -24,6 +24,7 @@ def approximate_parameters(T): # 1/w is the approximate proportion of time spent on the proof w = math.floor(T / (T/k + L * pow(2, k+1))) - 2 + w = max(w, 0) return (L, k, w) diff --git a/src/blockchain.py b/src/blockchain.py index 87ba069b..fabb4c92 100644 --- a/src/blockchain.py +++ b/src/blockchain.py @@ -1,12 +1,12 @@ from collections import defaultdict -from typing import List, Dict +from typing import List, Dict, Optional import logging from src.types.sized_bytes import bytes32 from src.util.ints import uint64 +from src.util.genesis_block import genesis_block_hardcoded from src.types.trunk_block import TrunkBlock from src.types.full_block import FullBlock -from src.types.block_body import BlockBody -from src.types.block_header import BlockHeader +from src.consensus.pot_iterations import calculate_iterations from src.consensus.constants import ( DIFFICULTY_STARTING, DIFFICULTY_TARGET, @@ -17,14 +17,12 @@ from src.consensus.constants import ( ) log = logging.getLogger(__name__) -genesis_block_bytes: bytes = b'\x15N3\xd3\xf9H\xc2K\x96\xfe\xf2f\xa2\xbf\x87\x0e\x0f,\xd0\xd4\x0f6s\xb1".\\\xf5\x8a\xb4\x03\x84\x8e\xf9\xbb\xa1\xca\xdef3:\xe4?\x0c\xe5\xc6\x12\x80\x15N3\xd3\xf9H\xc2K\x96\xfe\xf2f\xa2\xbf\x87\x0e\x0f,\xd0\xd4\x0f6s\xb1".\\\xf5\x8a\xb4\x03\x84\x8e\xf9\xbb\xa1\xca\xdef3:\xe4?\x0c\xe5\xc6\x12\x80\x13\x00\x00\x00\x98\xf9\xeb\x86\x90Kj\x01\x1cZk_\xe1\x9c\x03;Z\xb9V\xe2\xe8\xa5\xc8\n\x0c\xbbU\xa6\xc5\xc5\xbcH\xa3\xb3fd\xcd\xb8\x83\t\xa9\x97\x96\xb5\x91G \xb2\x9e\x05\\\x91\xe1<\xee\xb1\x06\xc3\x18~XuI\xc8\x8a\xb5b\xd7.7\x96Ej\xf3DThs\x18s\xa5\xd4C\x1ea\xfd\xd5\xcf\xb9o\x18\xea6n\xe22*\xb0]%\x15\xd0i\x83\xcb\x9a\xa2.+\x0f1\xcd\x03Z\xf3]\'\xbf|\x8b\xa6\xbcF\x10\xe8Q\x19\xaeZ~\xe5\x1f\xf1)\xa3\xfb\x82\x1a\xb8\x12\xce\x19\xc8\xde\xb9n\x08[\xef\xfd\xf9\x0c\xec\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12k\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\xe5\xc3\xbc;4\xda-1L3\xa2\xe3\xb5\xab\x91\xd3\xbb8!\xaa\xade~j(\r\xf3M`\xc8\x19\xbe\xd4\'\x93q\x9d\xc9N\xc48~\xcc\xd1\xab\x10\x86\xd7\xd7\xa1\x1c#f\xf8\x012>$\x8c\x1btr\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x9d\x9c~\xa4\x96`\x87%\x0c&6\xed\x1f\x1f\r\xe4OL\xca\xb6\xcd\x03\x1c\xb8\x90\x7f\x0e|\x91\xe7\xe2\xd2\xf8\x9a9)\xb0\xdec\x14\x1f\xad\xcb\x05\xf9\xe3\xac\xd2\x91\x10f\xeb\xcf\xab\xda\xf7\x19\x01\xc6JP\xfb]\x03\x00\x00\x03\x8e\x00\x08\xe3\xd0\x86\xdad\xdf\xce\x085\xf0\xcfV\x9d\xfb\x9d\xb1\xe7\xfa\x9b-L\xf6\'|xz\xab[\xb0MH\x1e\x84\xfb\x9e6X\xf5\x9fej*f\xfe-\xa0\xe3>\xc4#\x18\x12\x93O\xa3\x83\x98\xef\xf7\xd0g\x7f\xe4\x00\x02\xc1I\x13\xfc6+j\x87-{U\t\xc3\x9d\x1e\x82\x18\xb2\xc8D[!\xa7t\xad\xaf\x05\x88\xf4e\xfe~\xd2\x015_z@\xc1w\x90\xd2\xf8\xc08\xfbVu3\xeebB\xd5\xb9\x0c\xcf\xc8\x8f\x92\xd1j3g\x00\x19I \xdf=|\xc8\xf9\xf9\xa8y\\\x0f5\xdc\xd6SM\x93\xb2\x1d\xc86\x84wL\x9fy\xd7\xac\xfaa\xcf\\\xae\xfbo\xb0\x0c\xf9\xe9\x17\xd2\xb6\xf1\xbc\x87\xda\xd4Q\xe1\x91>+a\xb0^`\x93%6lP/\xff\xfd\x8eQL"}!;\xa71\xbaM67\xee\xb8&oI\xfc\'a\xb0\x0b\x04>\xb1K\xa0\x87H/\xce\xd9h\xe8\x81\xf8:\xc0b\xa6{x\x1c\xc4\x87\xdd\xb2\xe7i\xc3\x8d\xad\xf2o.\xb5`\xe1\xc7\x1dY\x07\x00\x1b\x19A\xc7\xe3N\xd9\xea\xa2\x1d\xa8\x16\x8f\xb5j\x07G\x91\xc7\x96\x1du\x07\xfe\xd3\xae}\xa7\xf4CD\xd8\x02Z\x99\xee\xe1\x1c\x85\xe4\x84s\xdc\x8b\x9dv~)\xb21pX\x01A\xf4\xeb-\x93c\xbbE\x96\xe9H\x00\x0f\xcd\x94\x8a\xa6G\x0e\x7fT#\xb4sEg\xe0\x0b\xb9\xc4\xf2oc\x87\x1b-\xf0\x8d\xb0\xa1\xe2\x19\x91\x84#\x08\x8b\x00\xe8"\xbf\xb4Ct\xa0\xeb\xad\x84`G2#9\xaf\xa7\x08k\xda\xdf\x8cQ\xa4!\x83*1\x00T\r\xa8\x87z\xc9Z\x11\xff5H\xbe\xda\x96W7p\xa8K\xba8\xeeZ\xf7\xcb\xca\xcc?Q\x16C\x1b\x8e\xb7\xd5U_\xfd\xc4\xeb3A\xa3YT\xe9\x95\xe1\x9c_\x1eX+\tX>\xd3\xe5\x8e_\xfdF\xe4\xe4\x00\x15j|/b\x99\xd4\xc4}y\xfd\x007\xb8\xcb\x85\xd4\xcb\xb0\xf8\xfd\xea\x13)+6\xe7kz\x90\xe2\xba\xa2DB\xef3n\xd6-%?\xd3\x9d@Ge!\xf8\xbf\xa7p\xe0\xd2\xbd\x13\r\xd0\x9fY&\x94\x01Cfvl\xc4\xe7Y!+\x0bV$\xca\xff\xe5y\xe7\x15;\x98\x1a\x8a\xb4\xfa\xdd\x17\xf5t\xbfC\xc4\xe4%6\xab\xc8\x024\xaa@\x11\xc2\xce\x7f\xac\x0f\xb6\x93\xe9\xbb7\xc3\xa6\xd9\xa8\x0eD_\xa9\xc8<\xae\x01\x86\x16\xcb#"\xf0O\xcf\x81\xcf\x8e\x97W\xef\xa7\x00v\xec\xd6\x05\xd2p_\xa5\xb1\xd9`\xadD\xb9\xb6\xe6Tt\x15\x0e\x95\xe4\x12j<\x17RG)J\xb4\xee\xd5\x8d\x90"\xec\x1f\x99a\xfb`\xc3\xbc\xe1L^\xd9o\x18]L,&%"\xafsp7\x05\xdf\x8f\xbb\xff\xc1\x06\xfb\xf0\x16!b\x92Q\xac?!\xaf\x10\x9aD\xda\x03\x8a\xaf\xd8$\x8b{a\x8d\x0f\xf5jns\x9d\x12\x0b,\x1c\x9b\xc7`\xad\xbbD;\x9dI|\xad\x1d*(\x11X\xe3\xd3\xaa7#!4\xe2\xad|\xf1!\x01~[u\x1f\x81\x7f\x0c)\x05\xe6\xfd\xe5\xd14\\a\n\xc6I\xccJ\x0cXk\xcf,Z\x1c\xdb>\xe0\xc3\x99\xe3\x93Z\xf2/\x9ec\xc4B\xca\xee\x9c\xe9\xc5B\x85\x0fG\x9c\x0e{\xda8\xa5?\x84\xb8\xa3$\xd7$\x00\x00\x00\x00\x00\x00\x00\x17Hv\xe8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]J\xa4\xed\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00~[u\x1f\x81\x7f\x0c)\x05\xe6\xfd\xe5\xd14\\a\n\xc6I\xccJ\x0cXk\xcf,Z\x1c\xdb>\xe0\xc3z!\xc9N\xd5\x03\x8b^\xd9\xe6\xc7I\xba\xb1\x0fm\xd4\xa0=\xb6^s\x94_f\xb5\xc1\\n\xfe\xf9\xd2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Q]\xdaO\xd8\x84 \xcb\xebh\x8ei]7\xd4\x0b\xf1\xb6\xd8%\x9eh\x9a}\x04\xa3u\xe7!v\xb3\xc0c\xba\x9b\xe02\xc9\x13\x85\xa7\x93R\x9bZ\x1a-\xc4\x07\xbd\xd2;\xa3\x9f\x17\x9c\xdc\xc3\xc3\x8d\x9b\x81\xcd\x0e\x9acb\xc2M\xd9j\xa2k\xb6S\xa6\xe2\x97>&\x1b\x05_sv\xfc\xdf\xd6\x17\xff\xb8u+\x1f\x1c\xb5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n+\x93\xa0\x02\xe4\xc2\x1d\xaa5R\xe5,\xbd\xa5\x15|%}\xa4@\xe5\x11\x00\x80\x1fG\x8aH\x0b\xe7\xe9\x10\xd3tK\xda`\xb5u\xca\x8c\xa2\xf7n\x1d\xd5\x92l\xb13k\xdb\n+\xbe/\x1e\xc0\xfe\xbf\xd9\x83\x88V\x11]~.<\x14\x0f\xce`\x8b\xbf\xb9\xa7\xce"6\x19\xa5\x19|\x81!r\x15V\xa6\x82\x07\x96w\x98F\xce\xb2(G\xcfm\x17@t\xb2\x1b\xba\xcf4I}\x0b\xc4\n\xd4\x9b\xe2E\x9e\x84\x98mY||\xa8[+\x93\xa0\x02\xe4\xc2\x1d\xaa5R\xe5,\xbd\xa5\x15|%}\xa4@\xe5\x11\x00\x80\x1fG\x8aH\x0b\xe7\xe9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # noqa: E501 class Blockchain: - def __init__(self): try: - self.genesis_trunk = FullBlock.from_bytes(genesis_block_bytes).trunk_block + self.genesis_trunk = FullBlock.from_bytes(genesis_block_hardcoded).trunk_block except ValueError: raise ValueError("Failed to parse genesis block.") self.heads: List[TrunkBlock] = [self.genesis_trunk] @@ -40,57 +38,26 @@ class Blockchain: def get_current_heads(self) -> List[TrunkBlock]: return self.heads - def _reconsider_heads(self, trunk: TrunkBlock) -> bool: - if trunk.challenge.height > min(t.challenge.height for t in self.heads): - self.heads.append(trunk) - while len(self.heads) >= 4: - self.heads.sort(key=lambda b: b.challenge.height, reverse=True) - self.heads.pop() - log.info(f"Updated heads, new heights: {[t.challenge.height for t in self.heads]}") - return True - return False - - def block_can_be_added(self, block_header: BlockHeader, block_body: BlockBody) -> bool: + def is_child_of_head(self, block: FullBlock): """ - Called by the full node of the farmer, when making a new block - (that doesn't have PoT yet). True iff the block connects to some head. - - Assumes that block_header and block_body are internally valid already. + True iff the block is the direct ancestor of a head. """ - prev_header_hash = block_header.data.prev_header_hash + prev_header_hash = block.trunk_block.prev_header_hash for trunk in self.heads: - if (prev_header_hash == trunk.header.header_hash - and block_header.data.timestamp > trunk.header.data.timestamp): - # TODO: "foliage arrow" ? + if (prev_header_hash == trunk.header.header_hash): return True return False def get_trunk_block(self, header_hash: bytes32) -> TrunkBlock: return self.blocks[header_hash] - def _get_warpable_trunk(self, trunk: TrunkBlock) -> TrunkBlock: - height = trunk.challenge.height - while height % DIFFICULTY_DELAY != 1: - trunk = self.blocks[trunk.header.header_hash] - height = trunk.challenge.height - return trunk - - def _consider_warping_link(self, trunk: TrunkBlock): - # Assumes trunk is already connected - if trunk.challenge.height % DIFFICULTY_DELAY != 1: - return - warped_trunk = self.blocks[trunk.prev_header_hash] - while warped_trunk.challenge.height % DIFFICULTY_DELAY != 1: - warped_trunk = self.blocks[warped_trunk.header.header_hash] - self.header_warp[trunk.header.header_hash] = warped_trunk.header.header_hash - def get_difficulty(self, header_hash: bytes32) -> uint64: trunk = self.blocks.get(header_hash, None) if trunk is None: raise Exception("No block found for given header_hash") elif trunk is self.genesis_trunk: return uint64(DIFFICULTY_STARTING) - + prev_trunk = self.blocks.get(trunk.prev_header_hash, None) if prev_trunk is None: raise Exception("No previous block found to compare total weight to") @@ -140,6 +107,32 @@ class Blockchain: difficulty = max(min(diff_natural, Tc * 4), Tc // 4) # truncated comparison return difficulty + def get_vdf_rate_estimate(self) -> Optional[uint64]: + """ + Returns an estimate of how fast VDFs are running in the network, in iterations per second. + Looks at the last N blocks from one of the heads, and divides timestamps. Returns None + if no time has elapsed, or if genesis block. + """ + head: TrunkBlock = self.heads[0] + curr = head + total_iterations_performed = 0 + for _ in range(0, 200): + if curr.challenge.height > 1: + # Ignores the genesis block, since it may have an older timestamp + iterations_performed = calculate_iterations(curr.proof_of_space, + curr.proof_of_time.output.challenge_hash, + self.get_difficulty(curr.header.get_hash())) + total_iterations_performed += iterations_performed + curr: TrunkBlock = self.blocks[curr.header.data.prev_header_hash] + else: + break + head_timestamp: int = int(head.header.data.timestamp) + curr_timestamp: int = int(curr.header.data.timestamp) + time_elapsed_secs: int = head_timestamp - curr_timestamp + if time_elapsed_secs == 0: + return None + return uint64(total_iterations_performed // time_elapsed_secs) + def add_block(self, block: FullBlock) -> bool: if not block.is_valid(): # TODO(alex): discredit/blacklist sender @@ -221,3 +214,65 @@ class Blockchain: cur[i] = self.blocks[cur[i].prev_header_hash] heights[i] = cur[i].challenge.height return cur[0] + + def validate_unfinished_block(self, candidate: FullBlock): + """ + Returns true if the candidate block is fully valid (except for proof of time), + and extends one of the current heads. The same as validate_block, but without + #11-13. + """ + pass + + def validate_block(self, candidate: FullBlock): + """ + Block validation algorithm. Returns true iff the candidate block is fully valid, + and extends one of the current heads. + 1. Takes in chain: Blockchain, candidate: FullBlock + 2. Check previous pointer(s) + 3. Check Now+2hrs > timestamp > avg timestamp of last 11 blocks + 4. Check filter hash is correct + 5. Check proof of space hash + 6. Check body hash + 7. Check extension data + 8. Compute challenge of parent + 9. Check plotter signature of header data is valid based on plotter key + 10. Check proof of space based on challenge + 11. Check number of iterations on PoT is correct, based on prev block and PoS + 12. Check PoT + 13. and check if PoT.output.challenge_hash matches + 14. Check coinbase height = parent height + 1 + 15. Check coinbase amount + 16. Check coinbase signature with pool pk + 17. Check transactions are valid + 18. Check aggregate BLS signature is valid + 19. Check fees amount is correct + """ + return True + + def _reconsider_heads(self, trunk: TrunkBlock) -> bool: + # TODO(alex): use weight instead + if trunk.challenge.height > min(t.challenge.height for t in self.heads): + self.heads.append(trunk) + while len(self.heads) >= 4: + self.heads.sort(key=lambda b: b.challenge.height, reverse=True) + self.heads.pop() + log.info(f"Updated heads, new heights: {[t.challenge.height for t in self.heads]}") + return True + return False + + def _get_warpable_trunk(self, trunk: TrunkBlock) -> TrunkBlock: + height = trunk.challenge.height + while height % DIFFICULTY_DELAY != 1: + trunk = self.blocks[trunk.header.header_hash] + height = trunk.challenge.height + return trunk + + def _consider_warping_link(self, trunk: TrunkBlock): + # Assumes trunk is already connected + if trunk.challenge.height % DIFFICULTY_DELAY != 1: + return + warped_trunk = self.blocks[trunk.prev_header_hash] + while warped_trunk and warped_trunk.challenge.height % DIFFICULTY_DELAY != 1: + warped_trunk = self.blocks.get(warped_trunk.prev_header_hash, None) + if warped_trunk is not None: + self.header_warp[trunk.header.header_hash] = warped_trunk.header.header_hash diff --git a/src/consensus/constants.py b/src/consensus/constants.py index fba52706..773816b2 100644 --- a/src/consensus/constants.py +++ b/src/consensus/constants.py @@ -1,9 +1,11 @@ -DIFFICULTY_STARTING = 1 << 32 -DIFFICULTY_EPOCH = 2016 # The number of blocks per epoch -DIFFICULTY_TARGET = 200 # The target number of seconds per block +DIFFICULTY_STARTING = 50 # These are in units of 2^32 +DIFFICULTY_EPOCH = 10 # The number of blocks per epoch +DIFFICULTY_TARGET = 10 # The target number of seconds per block DIFFICULTY_FACTOR = 4 # The next difficulty is truncated to range [prev / FACTOR, prev * FACTOR] DIFFICULTY_WARP_FACTOR = 4 # DELAY divides EPOCH in order to warp efficiently. -DIFFICULTY_DELAY = DIFFICULTY_EPOCH // DIFFICULTY_WARP_FACTOR # The delay in blocks before the difficulty reset applies. +DIFFICULTY_DELAY = DIFFICULTY_EPOCH // DIFFICULTY_WARP_FACTOR # The delay in blocks before the difficulty reset applies DISCRIMINANT_SIZE_BITS = 1024 + +# The percentage of the difficulty target that the VDF must be run for, at a minimum MIN_BLOCK_TIME_PERCENT = 20 -MIN_VDF_ITERATIONS = 1 << 10 +MIN_VDF_ITERATIONS = 1 # These are in units of 2^32 diff --git a/src/consensus/pot_iterations.py b/src/consensus/pot_iterations.py index 613218b0..3035c774 100644 --- a/src/consensus/pot_iterations.py +++ b/src/consensus/pot_iterations.py @@ -43,7 +43,7 @@ def calculate_iterations_quality(quality: bytes32, size: uint8, difficulty: uint between 0 and 1, then divided by expected plot size, and finally multiplied by the difficulty. """ - dec_iters = (Decimal(int(difficulty)) * + dec_iters = (Decimal(int(difficulty) << 32) * (_quality_to_decimal(quality) / _expected_plot_size(size))) return uint64(max(MIN_VDF_ITERATIONS, int(dec_iters.to_integral_exact(rounding=ROUND_UP)))) diff --git a/src/farmer.py b/src/farmer.py index ef4d1193..e35f1c68 100644 --- a/src/farmer.py +++ b/src/farmer.py @@ -243,6 +243,7 @@ async def proof_of_space_arrived(proof_of_space_arrived: farmer_protocol.ProofOf @api_request async def deep_reorg_notification(deep_reorg_notification: farmer_protocol.DeepReorgNotification): # TODO: implement + # TODO: "forget everything and start over (reset db)" log.error(f"Deep reorg notification not implemented.") diff --git a/src/full_node.py b/src/full_node.py index 4b063806..7b927331 100644 --- a/src/full_node.py +++ b/src/full_node.py @@ -4,7 +4,7 @@ from secrets import token_bytes from hashlib import sha256 from chiapos import Verifier from blspy import Util, Signature, PrivateKey -from asyncio import Lock +from asyncio import Lock, sleep from typing import Dict, List, Tuple, Optional from src.util.api_decorators import api_request from src.protocols import farmer_protocol @@ -18,6 +18,7 @@ from src.types.challenge import Challenge from src.types.block_header import BlockHeaderData, BlockHeader from src.types.proof_of_space import ProofOfSpace from src.consensus.pot_iterations import calculate_iterations +from src.consensus.constants import DIFFICULTY_TARGET from src.types.full_block import FullBlock from src.types.fees_target import FeesTarget from src.blockchain import Blockchain @@ -30,6 +31,7 @@ farmer_ip = "127.0.0.1" farmer_port = 8001 timelord_ip = "127.0.0.1" timelord_port = 8003 +update_pot_estimate_interval: int = 30 class Database: @@ -44,7 +46,7 @@ class Database: # These are the blocks that we created, have PoS, but not PoT yet, keyed from the # block header hash unfinished_blocks: Dict[Tuple[bytes32, int], FullBlock] = {} - proof_of_time_estimate_ips: uint64 = uint64(3000) + proof_of_time_estimate_ips: uint64 = uint64(1500) log = logging.getLogger(__name__) @@ -86,6 +88,16 @@ async def send_challenges_to_timelords(): yield OutboundMessage("timelord", "challenge_start", request, False, True) +async def proof_of_time_estimate_interval(): + while True: + estimated_ips: Optional[uint64] = db.blockchain.get_vdf_rate_estimate() + async with db.lock: + if estimated_ips is not None: + db.proof_of_time_estimate_ips = estimated_ips + log.info(f"Updated proof of time estimate to {estimated_ips} iterations per second.") + await sleep(update_pot_estimate_interval) + + @api_request async def request_header_hash(request: farmer_protocol.RequestHeaderHash): """ @@ -167,15 +179,13 @@ async def header_signature(header_signature: farmer_protocol.HeaderSignature): [pos.plot_pubkey]) block_header: BlockHeader = BlockHeader(block_header_data, header_signature.header_signature) - - assert db.blockchain.block_can_be_added(block_header, block_body) - trunk: TrunkBlock = TrunkBlock(pos, None, None, block_header) unfinished_block_obj: FullBlock = FullBlock(trunk, block_body) # Propagate to ourselves (which validates and does further propagations) request = peer_protocol.UnfinishedBlock(unfinished_block_obj) async for m in unfinished_block(request): + # Yield all new messages (propagation to peers) yield m @@ -188,6 +198,9 @@ async def proof_of_time_finished(request: timelord_protocol.ProofOfTimeFinished) """ async with db.lock: dict_key = (request.proof.output.challenge_hash, request.proof.output.number_of_iterations) + if dict_key not in db.unfinished_blocks: + log.warn(f"Received a proof of time that we cannot use to complete a block {dict_key}") + return unfinished_block_obj: FullBlock = db.unfinished_blocks[dict_key] prev_block: TrunkBlock = db.blockchain.get_trunk_block(unfinished_block_obj.trunk_block.prev_header_hash) difficulty: uint64 = db.blockchain.get_next_difficulty(unfinished_block_obj.trunk_block.prev_header_hash) @@ -195,7 +208,8 @@ async def proof_of_time_finished(request: timelord_protocol.ProofOfTimeFinished) challenge: Challenge = Challenge(unfinished_block_obj.trunk_block.proof_of_space.get_hash(), request.proof.output.get_hash(), prev_block.challenge.height + 1, - prev_block.challenge.total_weight + difficulty) + prev_block.challenge.total_weight + difficulty, + prev_block.challenge.total_iters + request.proof.output.number_of_iterations) new_trunk_block = TrunkBlock(unfinished_block_obj.trunk_block.proof_of_space, request.proof, @@ -240,7 +254,10 @@ async def unfinished_block(unfinished_block: peer_protocol.UnfinishedBlock): timelords. """ async with db.lock: - # TODO: verify block using blockchain class, including coinbase rewards + if not db.blockchain.is_child_of_head(unfinished_block.block): + return + + # TODO(alex): verify block using blockchain class, including coinbase rewards prev_block: TrunkBlock = db.blockchain.get_trunk_block( unfinished_block.block.trunk_block.prev_header_hash) @@ -255,8 +272,14 @@ async def unfinished_block(unfinished_block: peer_protocol.UnfinishedBlock): log.info(f"Have already seen unfinished block {(challenge_hash, iterations_needed)}") return + expected_time: float = iterations_needed / db.proof_of_time_estimate_ips + + # TODO(alex): tweak this + log.info(f"Expected finish time: {expected_time}") + if expected_time > 10 * DIFFICULTY_TARGET: + return + db.unfinished_blocks[(challenge_hash, iterations_needed)] = unfinished_block.block - # TODO: Only propagate if it's actually good timelord_request = timelord_protocol.ProofOfSpaceInfo(challenge_hash, iterations_needed) yield OutboundMessage("timelord", "proof_of_space_info", timelord_request, False, True) @@ -286,7 +309,6 @@ async def block(block: peer_protocol.Block): if header_hash in db.bodies and block.block.body in db.bodies[header_hash]: log.info(f"Already have block {header_hash}") return - # TODO(alex): Check if we care about this block, we don't want to add random # disconnected blocks. For example if it's on one of the heads, or if it's an older # block that we need diff --git a/src/protocols/farmer_protocol.py b/src/protocols/farmer_protocol.py index 597446c7..36afec9a 100644 --- a/src/protocols/farmer_protocol.py +++ b/src/protocols/farmer_protocol.py @@ -9,7 +9,10 @@ from src.types.coinbase import CoinbaseInfo Protocol between farmer and full node. """ - +""" +Farmer <- Full node +Update current height +""" @cbor_message(tag=2000) class ProofOfSpaceFinalized: challenge_hash: bytes32 diff --git a/src/protocols/peer_protocol.py b/src/protocols/peer_protocol.py index dc3c058f..01573ffb 100644 --- a/src/protocols/peer_protocol.py +++ b/src/protocols/peer_protocol.py @@ -10,38 +10,74 @@ from src.types.peer_info import PeerInfo Protocol between full nodes. """ - +""" +If already seen, ignore +Validate transaction +If consistent with at least 1/3 heads, store in mempool +Propagate transaction +""" @cbor_message(tag=4000) class NewTransaction: transaction: Transaction +""" +TODO(alex): update this +If already seen, ignore +If prev block not a head, ignore +Call self.ProofOfTimeFinished +Propagate PoT (?) +""" @cbor_message(tag=4001) class NewProofOfTime: proof: ProofOfTime +""" +TODO(alex): update this +If not a child of a head, ignore +If we have a PoT to complete this block, call self.Block +Otherwise: validate, store, and propagate +""" @cbor_message(tag=4002) class UnfinishedBlock: # Block that does not have ProofOfTime and Challenge block: FullBlock +""" +If have block, return block +TODO: request blocks? +""" @cbor_message(tag=4003) class RequestBlock: header_hash: bytes32 +""" +TODO(alex): update this +If already have, ignore +If not child of a head, or ancestor of a head, ignore +Add block to head + - Validate block +If heads updated, propagate block to full nodes, farmers, timelords +""" @cbor_message(tag=4004) class Block: block: FullBlock +""" +Return full list of peers +""" @cbor_message(tag=4005) class RequestPeers: pass +""" +Update list of peers +""" @cbor_message(tag=4006) class Peers: peer_list: List[PeerInfo] diff --git a/src/protocols/timelord_protocol.py b/src/protocols/timelord_protocol.py index 14f63c58..81e76584 100644 --- a/src/protocols/timelord_protocol.py +++ b/src/protocols/timelord_protocol.py @@ -7,7 +7,11 @@ from src.types.proof_of_time import ProofOfTime Protocol between timelord and full node. """ - +""" +If don't have the unfinished block, ignore +Validate PoT +Call self.Block +""" @cbor_message(tag=3000) class ProofOfTimeFinished: proof: ProofOfTime diff --git a/src/server/start_full_node.py b/src/server/start_full_node.py index d7c799c3..c3ac44c4 100644 --- a/src/server/start_full_node.py +++ b/src/server/start_full_node.py @@ -8,6 +8,19 @@ from src.server.server import start_chia_server, start_chia_client logging.basicConfig(format='FullNode %(name)-23s: %(levelname)-8s %(message)s', level=logging.INFO) global_connections = PeerConnections() +""" +Full node startup algorithm: +- Update peer list (?) +- Start server +- Sync: + - Check which are the heaviest bkocks + - Request flyclient proofs for all heads + - Blacklist peers with invalid heads + - Sync blockchain up to heads (request blocks in batches, and add to queue) +- If connected to farmer, send challenges +- If connected to timelord, send challenges +""" + async def main(): farmer_con_task, farmer_client = await start_chia_client(full_node.farmer_ip, full_node.farmer_port, @@ -25,6 +38,9 @@ async def main(): async for msg in full_node.send_challenges_to_timelords(): timelord_client.push(msg) + # Periodically update our estimate of proof of time speeds + asyncio.create_task(full_node.proof_of_time_estimate_interval()) + await asyncio.gather(farmer_con_task, timelord_con_task, server) asyncio.run(main()) diff --git a/src/types/block_header.py b/src/types/block_header.py index 72ffa6db..34ade8ce 100644 --- a/src/types/block_header.py +++ b/src/types/block_header.py @@ -30,4 +30,4 @@ class BlockHeader: @property def header_hash(self): - return sha256(self.serialize()).digest() + return bytes32(sha256(self.serialize()).digest()) diff --git a/src/types/challenge.py b/src/types/challenge.py index 23e4bb45..db8dd847 100644 --- a/src/types/challenge.py +++ b/src/types/challenge.py @@ -9,6 +9,7 @@ class Challenge: proof_of_time_output_hash: bytes32 height: uint32 total_weight: uint64 + total_iters: uint64 def is_valid(self) -> bool: # TODO diff --git a/src/types/trunk_block.py b/src/types/trunk_block.py index 24f2cf7a..2178cc12 100644 --- a/src/types/trunk_block.py +++ b/src/types/trunk_block.py @@ -15,18 +15,14 @@ class TrunkBlock: def is_valid(self): if not self.proof_of_time or not self.challenge: - print("1 false") return False pos_quality = self.proof_of_space.verify_and_get_quality(self.proof_of_time.output.challenge_hash) # TODO: check iterations if not pos_quality: - print("2 false") return False if not self.proof_of_space.get_hash() == self.challenge.proof_of_space_hash: - print("3 false") return False if not self.proof_of_time.output.get_hash() == self.challenge.proof_of_time_output_hash: - print("4 false") return False return self.challenge.is_valid() and self.proof_of_time.is_valid() and self.header.is_valid() diff --git a/src/util/api_decorators.py b/src/util/api_decorators.py index 10a84f5f..ab1a9f72 100644 --- a/src/util/api_decorators.py +++ b/src/util/api_decorators.py @@ -20,6 +20,6 @@ def api_request(f): inter = dict(binding.arguments) print_args = {k: v for (k, v) in inter.items() if k != "source_connection" and k != "all_connections"} - log.info(f"{f.__name__}({print_args})") + log.info(f"{f.__name__}({print_args})"[:200]) return f(**inter) return f_substitute diff --git a/src/util/genesis_block.py b/src/util/genesis_block.py index af67c1f4..0e95839b 100644 --- a/src/util/genesis_block.py +++ b/src/util/genesis_block.py @@ -32,6 +32,8 @@ fee_target = sha256(sk.get_public_key().serialize()).digest() k = 19 n_wesolowski = 3 +genesis_block_hardcoded = b'\x15N3\xd3\xf9H\xc2K\x96\xfe\xf2f\xa2\xbf\x87\x0e\x0f,\xd0\xd4\x0f6s\xb1".\\\xf5\x8a\xb4\x03\x84\x8e\xf9\xbb\xa1\xca\xdef3:\xe4?\x0c\xe5\xc6\x12\x80\x15N3\xd3\xf9H\xc2K\x96\xfe\xf2f\xa2\xbf\x87\x0e\x0f,\xd0\xd4\x0f6s\xb1".\\\xf5\x8a\xb4\x03\x84\x8e\xf9\xbb\xa1\xca\xdef3:\xe4?\x0c\xe5\xc6\x12\x80\x13\x00\x00\x00\x98\xf9\xeb\x86\x90Kj\x01\x1cZk_\xe1\x9c\x03;Z\xb9V\xe2\xe8\xa5\xc8\n\x0c\xbbU\xa6\xc5\xc5\xbcH\xa3\xb3fd\xcd\xb8\x83\t\xa9\x97\x96\xb5\x91G \xb2\x9e\x05\\\x91\xe1<\xee\xb1\x06\xc3\x18~XuI\xc8\x8a\xb5b\xd7.7\x96Ej\xf3DThs\x18s\xa5\xd4C\x1ea\xfd\xd5\xcf\xb9o\x18\xea6n\xe22*\xb0]%\x15\xd0i\x83\xcb\x9a\xa2.+\x0f1\xcd\x03Z\xf3]\'\xbf|\x8b\xa6\xbcF\x10\xe8Q\x19\xaeZ~\xe5\x1f\xf1)\xa3\xfb\x82\x1a\xb8\x12\xce\x19\xc8\xde\xb9n\x08[\xef\xfd\xf9\x0c\xec\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\'\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07~\xccf\x88\xef\xc8z;\xc9\x99\xdaSO\xa2\xdbC\x84\xe1\x9d\xc9Iv\xdbH\xb4\x9fiI\x1ew\xa78Gu\xe0\x9bg\xfdtBU\xfa\xe8\x9f\x13A\xb76iVx\xadU~\x8bj^\xeaV\xfd@\xdf,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xedVsY\xdf\xa18.\x050\x90\x9c\xb3\xed\xaa\xb0Kv\x81{\x9a\xce=\xed\xc2\xc9m/\xce\x9b[\x04M\xbb\xe8\xdeCNg\xb6\xee\x01\x8e{\x8dEk\xecHt\x8d\xab\xbb\x19\x91\xfa\x1aT\xb4\xf871\xbc\x1b\x95~\xd5|\xc3\xb8\x84oG\xf2\xe2\x93b,\xdf\xae\x89MgzL\xcb\xfbb\xae\x85./\x00\x06i\xd3\x16\xf7\x85\xab\ru\xb7\xa6x{n\xc6\x8c\x91g\x1c\x9f\xee8E\x02\xdb\x9fd\xc6q\x15k@^\xe9\r"\x05q\xbfqa\xc6r\'\xd5\xa5\xbdyjx\xacG\xde8\x9e\xde\x9ah\xc4\x01\xbe\xdf\x94\xe1\x00\x05\xe9+\x00P\xb5w\x9d|\xcbcG\x8c\xd9\xdd3\x11\x1fh)\x95\xf2\xfe\xfeZAw\xf1\xff\xdb\xd1\xd6\x90\x8f\xf2\xbaCz]*)\xb7\xffv\xc9\xdb\xben\xb7\xfb%D\tN\x04CW+/7z\xe7\x04Q\x00r\xb7G\x9c\xb4\xa1`\x97\x8ddo\x9bv\x89+\xeaHx>le\x95\xde\xe3\xbb\x11=\x1a2\xc5\xd8\xcb\x01\x11\xaf\xac\xa9\x8b\xcbf\xa5\x8dR\t\xad5\x17\xf9\xfb4Z\xfe\xf6G\xff&4\r\xfe\x03\xa0\x88\xe3(\xff\xa1s\xf0\\\xac\xf4\x91\xe0\xc9\x8f|\x9e\x1c`+\xe5\xb8/\x18:\xad[f\x88\x94\xd9o\xcfa\xb6\x96\xcf\x0b%\x89i\x167bv\x18\x7fa\x18\nJ\xf6\x87\x97\xfb\x9dX.\x919T)lR<\xbcTf1\x00)\x99A\x95\'r\xed\xd6\xdb;\xb7\x06/\x1b\xcf\x9b\xcfD\x10!\xec\xa2\xa5@s:@C>\xc0v\xeb\xf7\xbcF\xcb\xb3\x85<5\'\xf2\xf0\xce\xcc\xf1\x82\xe0\xc5~\x88\xf8\xc2\x86ff\xc8\x13\xb4\x87\x98\xdf\xb18\x00\x0c\xc4\x97\xe52\xd7)n\xcb*\x9f\x97dP\x1c\xd8<\t\xd0\xa8V\xa6{6\xbfr?H\xa9\x8e\x99\xa2\xff\xbc\x81\x8bP7\x9e\x8b\xa7\x98]L\xbaM\xd2\x83*\xdf!Z\xaf0\xa8\xff_\x0f\xb8\xcc`l\xbaQ\x00\x12\xfd\x1aQ\xbe#t\x14\x1cF\xa0k\xdc\x08\x9b0\xe1>\x14\xf6\xc2.\xd8jp\xa6\xf4\xe7\xc9.o\xd1\x02\x1d\xd9\n\x1f\xaa\x9b\xc00_zF\x8f\xac\xbb\xe9\x9b`\x8c\xdd\xcb\xb0[=\x07\xe0\xc3\x10\xcehG\x89<\x0b\x08)\xd1\xe8\x99z9\xed\x08YJ6\x185\xd1\xbf9e&4\xb0\x18\xb7\x93\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]e\xe8\xd1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00~[u\x1f\x81\x7f\x0c)\x05\xe6\xfd\xe5\xd14\\a\n\xc6I\xccJ\x0cXk\xcf,Z\x1c\xdb>\xe0\xc3z!\xc9N\xd5\x03\x8b^\xd9\xe6\xc7I\xba\xb1\x0fm\xd4\xa0=\xb6^s\x94_f\xb5\xc1\\n\xfe\xf9\xd2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00A\xd0\xe6k\xb8 FullBlock: plot_seed: bytes32 = ProofOfSpace.calculate_plot_seed(pool_pk, plot_pk) @@ -63,10 +65,9 @@ def create_genesis_block(challenge_hash=bytes([0]*32)) -> FullBlock: disc: int = create_discriminant(challenge_hash, constants.DISCRIMINANT_SIZE_BITS) start_x: ClassGroup = ClassGroup.from_ab_discriminant(2, 1, disc) - y, proof_bytes = create_proof_of_time_nwesolowski(disc, start_x, number_iters, - constants.DISCRIMINANT_SIZE_BITS, n_wesolowski) - y_cl = ClassGroup.from_bytes(y, disc) - print(y_cl) + y_cl, proof_bytes = create_proof_of_time_nwesolowski( + disc, start_x, number_iters, constants.DISCRIMINANT_SIZE_BITS, n_wesolowski) + output = ProofOfTimeOutput(challenge_hash, number_iters, ClassgroupElement(y_cl[0], y_cl[1])) @@ -92,7 +93,7 @@ def create_genesis_block(challenge_hash=bytes([0]*32)) -> FullBlock: uint64(constants.DIFFICULTY_STARTING)) challenge = Challenge(proof_of_space.get_hash(), proof_of_time.get_hash(), 0, - uint64(constants.DIFFICULTY_STARTING)) + uint64(constants.DIFFICULTY_STARTING), 0) trunk_block = TrunkBlock(proof_of_space, proof_of_time, challenge, header) full_block: FullBlock = FullBlock(trunk_block, body) @@ -100,5 +101,5 @@ def create_genesis_block(challenge_hash=bytes([0]*32)) -> FullBlock: return full_block -block = create_genesis_block() -print(block.serialize()) +# block = create_genesis_block() +# print(block.serialize())