Skip to content

Commit

Permalink
Implement blockchain validation
Browse files Browse the repository at this point in the history
  • Loading branch information
mariano54 committed Sep 20, 2019
1 parent 1198273 commit a5688f9
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 261 deletions.
424 changes: 248 additions & 176 deletions src/blockchain.py

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/consensus/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
NUMBER_OF_HEADS = 3 # The number of tips each full node keeps track of and propagates
DIFFICULTY_STARTING = 60 # 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
Expand All @@ -9,3 +10,6 @@
# 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 # These are in units of 2^32

MAX_FUTURE_TIME = 7200 # The next block can have a timestamp of at most these many seconds more
NUMBER_OF_TIMESTAMPS = 11 # Than the average of the last NUMBEBR_OF_TIMESTAMPS blocks
52 changes: 23 additions & 29 deletions src/full_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from src.consensus.weight_verifier import verify_weight
from src.consensus.pot_iterations import calculate_iterations
from src.consensus.constants import DIFFICULTY_TARGET
from src.blockchain import Blockchain
from src.blockchain import Blockchain, ReceiveBlockResult
from src.server.outbound_message import OutboundMessage, Delivery, NodeType, Message


Expand Down Expand Up @@ -229,7 +229,7 @@ async def sync():
block = db.potential_blocks[uint64(height)]

start = time.time()
db.blockchain.add_block(block)
db.blockchain.receive_block(block)
log.info(f"Took {time.time() - start}")
assert max([h.challenge.height for h in db.blockchain.get_current_heads()]) >= height
db.full_blocks[block.trunk_block.header.get_hash()] = block
Expand Down Expand Up @@ -537,7 +537,6 @@ async def block(block: peer_protocol.Block) -> AsyncGenerator[OutboundMessage, N
propagate challenge to farmers
propagate challenge to timelords
"""
propagate: bool = False
header_hash = block.block.trunk_block.header.get_hash()

async with db.lock:
Expand All @@ -553,11 +552,9 @@ async def block(block: peer_protocol.Block) -> AsyncGenerator[OutboundMessage, N
# 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
added = db.blockchain.add_block(block.block)
added: ReceiveBlockResult = db.blockchain.receive_block(block.block)

if not added:
log.info("Block not added!!")
# TODO(alex): is this correct? What if we already have it?
if not (added == ReceiveBlockResult.ADDED_TO_HEAD or added == ReceiveBlockResult.ADDED_AS_ORPHAN):
async with db.lock:
tip_height = max([head.challenge.height for head in db.blockchain.get_current_heads()])
if block.block.trunk_block.challenge.height > tip_height + config["sync_blocks_behind_threshold"]:
Expand Down Expand Up @@ -592,27 +589,24 @@ async def block(block: peer_protocol.Block) -> AsyncGenerator[OutboundMessage, N
async with db.lock:
db.full_blocks[header_hash] = block.block

propagate = True
difficulty = db.blockchain.get_difficulty(header_hash)

if propagate:
# TODO(alex): don't reverify, just get the quality
pos_quality = block.block.trunk_block.proof_of_space.verify_and_get_quality(
block.block.trunk_block.proof_of_time.output.challenge_hash
)
farmer_request = farmer_protocol.ProofOfSpaceFinalized(block.block.trunk_block.challenge.get_hash(),
block.block.trunk_block.challenge.height,
pos_quality,
difficulty)
timelord_request = timelord_protocol.ChallengeStart(block.block.trunk_block.challenge.get_hash())
timelord_request_end = timelord_protocol.ChallengeStart(block.block.trunk_block.proof_of_time.
output.challenge_hash)
# Tell timelord to stop previous challenge and start with new one
yield OutboundMessage(NodeType.TIMELORD, Message("challenge_end", timelord_request_end), Delivery.BROADCAST)
yield OutboundMessage(NodeType.TIMELORD, Message("challenge_start", timelord_request), Delivery.BROADCAST)

# Tell full nodes about the new block
yield OutboundMessage(NodeType.FULL_NODE, Message("block", block), Delivery.BROADCAST_TO_OTHERS)

# Tell farmer about the new block
yield OutboundMessage(NodeType.FARMER, Message("proof_of_space_finalized", farmer_request), Delivery.BROADCAST)
pos_quality = block.block.trunk_block.proof_of_space.verify_and_get_quality(
block.block.trunk_block.proof_of_time.output.challenge_hash
)
farmer_request = farmer_protocol.ProofOfSpaceFinalized(block.block.trunk_block.challenge.get_hash(),
block.block.trunk_block.challenge.height,
pos_quality,
difficulty)
timelord_request = timelord_protocol.ChallengeStart(block.block.trunk_block.challenge.get_hash())
timelord_request_end = timelord_protocol.ChallengeStart(block.block.trunk_block.proof_of_time.
output.challenge_hash)
# Tell timelord to stop previous challenge and start with new one
yield OutboundMessage(NodeType.TIMELORD, Message("challenge_end", timelord_request_end), Delivery.BROADCAST)
yield OutboundMessage(NodeType.TIMELORD, Message("challenge_start", timelord_request), Delivery.BROADCAST)

# Tell full nodes about the new block
yield OutboundMessage(NodeType.FULL_NODE, Message("block", block), Delivery.BROADCAST_TO_OTHERS)

# Tell farmer about the new block
yield OutboundMessage(NodeType.FARMER, Message("proof_of_space_finalized", farmer_request), Delivery.BROADCAST)
26 changes: 5 additions & 21 deletions src/protocols/peer_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,23 @@


"""
If already seen, ignore
Validate transaction
If consistent with at least 1/3 heads, store in mempool
Propagate transaction
Receive a transaction from a peer.
"""
@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 (?)
Receive a new proof of time from a peer.
"""
@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
Receive an unfinished block from a peer.
"""
@cbor_message(tag=4002)
class UnfinishedBlock:
Expand All @@ -49,21 +39,15 @@ class UnfinishedBlock:


"""
If have block, return block
TODO: request blocks?
Requests a block from a peer.
"""
@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
Receive a block from a peer.
"""
@cbor_message(tag=4004)
class Block:
Expand Down
12 changes: 12 additions & 0 deletions src/simulation/simulate_network.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,16 @@ python -m src.server.start_full_node "127.0.0.1" 8004 &
P5=$!
python -m src.server.start_full_node "127.0.0.1" 8005 &
P6=$!

_term() {
echo "Caught SIGTERM signal!"
kill -TERM "$P1" 2>/dev/null
kill -TERM "$P2" 2>/dev/null
kill -TERM "$P3" 2>/dev/null
kill -TERM "$P4" 2>/dev/null
kill -TERM "$P5" 2>/dev/null
kill -TERM "$P6" 2>/dev/null
}

trap _term SIGTERM
wait $P1 $P2 $P3 $P4 $P5 $P6
1 change: 1 addition & 0 deletions src/simulation/stop_all_servers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ps -e | grep python | grep "start_" | awk '{print $1}' | xargs -L1 kill -9
4 changes: 0 additions & 4 deletions src/types/block_body.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,3 @@ class BlockBody:
fees_target_info: FeesTarget
aggregated_signature: Optional[Signature]
solutions_generator: bytes32 # TODO: use actual transactions

def is_valid(self) -> bool:
# TODO
return True
8 changes: 0 additions & 8 deletions src/types/block_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,12 @@ class BlockHeaderData:
body_hash: bytes32
extension_data: bytes32

def is_valid(self):
# TODO:
return True


@streamable
class BlockHeader:
data: BlockHeaderData
plotter_signature: PrependSignature

def is_valid(self):
# TODO: Check that data is valid, and that signature signs data
return True

@property
def header_hash(self):
return bytes32(sha256(self.serialize()).digest())
4 changes: 0 additions & 4 deletions src/types/challenge.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,3 @@ class Challenge:
height: uint32
total_weight: uint64
total_iters: uint64

def is_valid(self) -> bool:
# TODO
return True
16 changes: 16 additions & 0 deletions src/types/full_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,19 @@ def is_valid(self):
return (self.trunk_block.header.data.body_hash == body_hash
and self.trunk_block.is_valid()
and self.body.is_valid())

@property
def prev_hash(self):
return self.trunk_block.header.data.prev_header_hash

@property
def height(self):
return self.trunk_block.challenge.height

@property
def weight(self):
return self.trunk_block.challenge.total_weight

@property
def header_hash(self):
return self.trunk_block.header.header_hash
9 changes: 4 additions & 5 deletions src/types/proof_of_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,20 @@ class ProofOfSpace:
size: uint8
proof: List[uint8]

def is_valid(self):
# TODO
return True

def get_plot_seed(self) -> bytes32:
return self.calculate_plot_seed(self.pool_pubkey, self.plot_pubkey)

def verify_and_get_quality(self, challenge_hash: bytes32) -> Optional[bytes32]:
if self._cached_quality:
return self._cached_quality
v: Verifier = Verifier()
plot_seed: bytes32 = self.get_plot_seed()
quality_str = v.validate_proof(plot_seed, self.size, challenge_hash,
bytes(self.proof))
if not quality_str:
return None
return sha256(challenge_hash + quality_str).digest()
self._cached_quality = sha256(challenge_hash + quality_str).digest()
return self._cached_quality

@staticmethod
def calculate_plot_seed(pool_pubkey: PublicKey, plot_pubkey: PublicKey) -> bytes32:
Expand Down
27 changes: 13 additions & 14 deletions src/types/trunk_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,18 @@ class TrunkBlock:
challenge: Optional[Challenge]
header: BlockHeader

def is_valid(self):
if not self.proof_of_time or not self.challenge:
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:
return False
if not self.proof_of_space.get_hash() == self.challenge.proof_of_space_hash:
return False
if not self.proof_of_time.output.get_hash() == self.challenge.proof_of_time_output_hash:
return False
return self.challenge.is_valid() and self.proof_of_time.is_valid() and self.header.is_valid()

@property
def prev_header_hash(self):
def prev_hash(self):
return self.header.data.prev_header_hash

@property
def height(self):
return self.challenge.height

@property
def weight(self):
return self.challenge.total_weight

@property
def header_hash(self):
return self.header.header_hash

0 comments on commit a5688f9

Please sign in to comment.