Implement gambling
This commit is contained in:
191
node.py
191
node.py
@@ -1,6 +1,6 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
import blockchain, hashlib, observer, random, socket, sys, threading, time
|
||||
import blockchain, hashlib, observer, random, secrets, socket, sys, threading, time
|
||||
from _queue import Empty
|
||||
|
||||
DEFAULT_PORT = 62039
|
||||
@@ -101,15 +101,14 @@ def send_heartbeat(node, peer, b):
|
||||
node.node_socket.sendto(heartbeat_msg, (peer.ipv6, peer.port))
|
||||
|
||||
def define_partnership(peers):
|
||||
for peer in peers:
|
||||
peer.partner = None
|
||||
peers_to_pair = [peer for peer in peers if peer.lifetime_counter >= 8]
|
||||
random.shuffle(peers_to_pair)
|
||||
pairing_count = len(peers_to_pair) // 2
|
||||
for pair_idx in range(pairing_count):
|
||||
peers_to_pair[2*pair_idx].partner = peers_to_pair[2*pair_idx+1]
|
||||
peers_to_pair[2*pair_idx+1].partner = peers_to_pair[2*pair_idx]
|
||||
# in case of an odd count, the last one will remain without partner
|
||||
if pairing_count % 2 == 1:
|
||||
peers_to_pair[-1].partner = None
|
||||
|
||||
def heartbeat(node, b):
|
||||
while True:
|
||||
@@ -216,6 +215,30 @@ def transfer_block(addr, node, receive_observer, b):
|
||||
except NoReponseException:
|
||||
pass
|
||||
|
||||
def get_associated_data(node, receive_observer, b, addr, associated_data_hash):
|
||||
cached_data = b.get_associated_data(associated_data_hash)
|
||||
if cached_data is not None:
|
||||
return cached_data
|
||||
subscription = receive_observer.listen((addr[0:2], "associated data", associated_data_hash))
|
||||
fragments = 6 * [None]
|
||||
for _ in range(10):
|
||||
request_bitfield = 0
|
||||
for i, fragment in enumerate(fragments):
|
||||
if fragment is None:
|
||||
request_bitfield |= (1 << i)
|
||||
request = b"\0\0\0\0\x0f" + bytes([request_bitfield]) + associated_data_hash
|
||||
node.node_socket.sendto(request, addr)
|
||||
try:
|
||||
while True:
|
||||
response = subscription.receive(1)
|
||||
if fragments[response["part_number"]] is None:
|
||||
fragments[response["part_number"]] = response["fragment"]
|
||||
if None not in fragments:
|
||||
return b"".join(fragments)
|
||||
except Empty:
|
||||
pass
|
||||
return None
|
||||
|
||||
def compare_open_transactions(addr, node, receive_observer, b):
|
||||
try:
|
||||
cursor = 511
|
||||
@@ -238,9 +261,16 @@ def compare_open_transactions(addr, node, receive_observer, b):
|
||||
def transaction_condition(response):
|
||||
return response["position"] == cursor
|
||||
remote_transaction = request_retry(node, addr, request, subscription, transaction_condition)
|
||||
parsed_transaction = blockchain.Transaction.from_bytes(remote_transaction["transaction"])
|
||||
if not parsed_transaction.is_valid():
|
||||
if blockchain.associated_data_required(remote_transaction["transaction"]):
|
||||
associated_data = get_associated_data(node, receive_observer, b, addr, remote_transaction["transaction"][33:65])
|
||||
if associated_data is None:
|
||||
return
|
||||
else:
|
||||
associated_data = None
|
||||
parsed_transaction = blockchain.transaction_from_bytes(remote_transaction["transaction"], associated_data)
|
||||
if not parsed_transaction.is_valid() or parsed_transaction.is_empty():
|
||||
return
|
||||
b.cache_associated_data(parsed_transaction)
|
||||
b.open_transactions.add(parsed_transaction)
|
||||
except NoReponseException:
|
||||
pass
|
||||
@@ -252,7 +282,7 @@ def receiver(node, b):
|
||||
sender = describe(addr[0], addr[1])
|
||||
msg_len = len(msg)
|
||||
if msg_len < 4:
|
||||
log("Got a udp message from {sender} that was too short.")
|
||||
log(f"Got a udp message from {sender} that was too short.")
|
||||
continue
|
||||
version = int.from_bytes(msg[0:2], "big")
|
||||
if version != 0:
|
||||
@@ -300,16 +330,26 @@ def receiver(node, b):
|
||||
node.node_socket.sendto(response_msg, addr)
|
||||
elif msg_type == 2:
|
||||
# block transfer
|
||||
if msg_len != 297:
|
||||
log(f"Got a block transfer of wrong length ({msg_len} bytes from {sender}, but expected 297 bytes)")
|
||||
if msg_len != 298:
|
||||
log(f"Got a block transfer of wrong length ({msg_len} bytes from {sender}, but expected 298 bytes)")
|
||||
continue
|
||||
block_raw = msg[5:297]
|
||||
new_block = b.add_block(block_raw)
|
||||
block_hash = new_block.own_hash
|
||||
if new_block.validate(b) and b.set_latest_block(block_hash):
|
||||
log("Got a new block")
|
||||
identifier = (addr[0:2], "block transfer")
|
||||
receive_observer.publish(identifier, block_hash)
|
||||
def handle_block_transfer(addr, msg):
|
||||
if blockchain.associated_data_required(msg[5:154]):
|
||||
associated_data = get_associated_data(node, receive_observer, b, addr, msg[38:70])
|
||||
if associated_data is None:
|
||||
return
|
||||
else:
|
||||
associated_data = None
|
||||
new_block = b.add_block(msg[5:298], associated_data)
|
||||
block_hash = new_block.own_hash
|
||||
if new_block.validate(b):
|
||||
b.cache_associated_data(new_block.transaction)
|
||||
if b.set_latest_block(block_hash):
|
||||
log("Got a new block")
|
||||
identifier = (addr[0:2], "block transfer")
|
||||
receive_observer.publish(identifier, block_hash)
|
||||
# Handle this in a thread because asynchronous back-requests might be required
|
||||
threading.Thread(target=handle_block_transfer, args=(addr, msg)).start()
|
||||
elif msg_type == 3:
|
||||
# open transaction list hash request
|
||||
if msg_len != 7:
|
||||
@@ -344,32 +384,32 @@ def receiver(node, b):
|
||||
continue
|
||||
transaction = b.open_transactions.get_transaction(list_position)
|
||||
if transaction is None:
|
||||
transaction_raw = 148 * b"\0"
|
||||
transaction_raw = 149 * b"\0"
|
||||
else:
|
||||
transaction_raw = transaction.get_transaction_raw()
|
||||
response = b"\0\0\0\0\x06" + list_position.to_bytes(2, "big") + transaction_raw
|
||||
node.node_socket.sendto(response, addr)
|
||||
elif msg_type == 6:
|
||||
# open transaction response
|
||||
if msg_len != 155:
|
||||
log(f"Got an open transaction list hash response of wrong length ({msg_len} bytes from {sender}, but expected 155 bytes)")
|
||||
if msg_len != 156:
|
||||
log(f"Got an open transaction list hash response of wrong length ({msg_len} bytes from {sender}, but expected 156 bytes)")
|
||||
continue
|
||||
event_obj = {
|
||||
"position": int.from_bytes(msg[5:7], "big"),
|
||||
"transaction": msg[7:155],
|
||||
"transaction": msg[7:156],
|
||||
}
|
||||
identifier = (addr[0:2], "transaction")
|
||||
receive_observer.publish(identifier, event_obj)
|
||||
elif msg_type == 7:
|
||||
# mining task request
|
||||
if msg_len != 257:
|
||||
log(f"Got a mining task request of wrong length ({msg_len} bytes from {sender}, but expected 257 bytes)")
|
||||
if msg_len != 258:
|
||||
log(f"Got a mining task request of wrong length ({msg_len} bytes from {sender}, but expected 258 bytes)")
|
||||
continue
|
||||
transaction = b.open_transactions.get_transaction(0)
|
||||
if transaction is not None:
|
||||
transaction_raw = transaction.get_transaction_raw()
|
||||
else:
|
||||
transaction_raw = 148 * b"\0"
|
||||
transaction_raw = 149 * b"\0"
|
||||
t = int(time.time())
|
||||
timestamp_raw = t.to_bytes(8, "big")
|
||||
latest_block = b.get_latest_block()
|
||||
@@ -395,15 +435,106 @@ def receiver(node, b):
|
||||
threshold.to_bytes(32, "big")
|
||||
node.node_socket.sendto(response, addr)
|
||||
elif msg_type == 9:
|
||||
# payment request
|
||||
if msg_len != 153:
|
||||
log(f"Got a payment of wrong length ({msg_len} bytes from {sender}, but expected 153 bytes)")
|
||||
# transaction request
|
||||
if msg_len != 154:
|
||||
log(f"Got a transaction of wrong length ({msg_len} bytes from {sender}, but expected 154 bytes)")
|
||||
continue
|
||||
parsed_transaction = blockchain.Transaction.from_bytes(msg[5:153])
|
||||
if not parsed_transaction.is_valid():
|
||||
def handle_transaction_request(msg, addr):
|
||||
if blockchain.associated_data_required(msg[5:154]):
|
||||
associated_data = get_associated_data(node, receive_observer, b, addr, msg[38:70])
|
||||
if associated_data is None:
|
||||
return
|
||||
else:
|
||||
associated_data = None
|
||||
parsed_transaction = blockchain.transaction_from_bytes(msg[5:154], associated_data)
|
||||
if not parsed_transaction.is_valid() or parsed_transaction.is_empty():
|
||||
return
|
||||
b.cache_associated_data(parsed_transaction)
|
||||
b.open_transactions.add(parsed_transaction)
|
||||
node.node_socket.sendto(b"\0\0\0\0\x0a", addr)
|
||||
# Handle this in a thread because asynchronous back-requests might be required
|
||||
threading.Thread(target=handle_transaction_request, args=(msg, addr)).start()
|
||||
elif msg_type == 11:
|
||||
# reveal mining task request
|
||||
if msg_len != 37:
|
||||
log(f"Got a reveal mining task request of wrong length ({msg_len} bytes from {sender}, but expected 37 bytes)")
|
||||
continue
|
||||
b.open_transactions.add(parsed_transaction)
|
||||
node.node_socket.sendto(b"\0\0\0\0\x0a", addr)
|
||||
next_reveal_hash = 32 * b"\0"
|
||||
latest_block = b.get_latest_block()
|
||||
if latest_block is not None and len(latest_block.pending_commitment_blocks) > 0:
|
||||
next_reveal_hash = latest_block.pending_commitment_blocks[0][0]
|
||||
node.node_socket.sendto(b"\0\0\0\0\x0c" + next_reveal_hash, addr)
|
||||
elif msg_type == 13:
|
||||
# Ping
|
||||
if msg_len != 28:
|
||||
log(f"Got a Ping message of wrong length ({msg_len} bytes from {sender}, but expected 28 bytes)")
|
||||
continue
|
||||
if msg[5:20] != "R u carrotcoin?".encode():
|
||||
# Wrong Ping question, ignore
|
||||
continue
|
||||
nonce = msg[20:28]
|
||||
node.node_socket.sendto(b"\0\0\0\0\x0e" + "I m carrotcoin!".encode() + nonce, addr)
|
||||
elif msg_type == 14:
|
||||
# Pong
|
||||
if msg_len != 28:
|
||||
log(f"Got a Pong message of wrong length ({msg_len} bytes from {sender}, but expected 28 bytes)")
|
||||
continue
|
||||
if msg[5:20] != "I m carrotcoin!".encode():
|
||||
# Wrong Pong answer, ignore
|
||||
continue
|
||||
nonce = msg[20:28]
|
||||
identifier = (addr[0:2], "pong")
|
||||
receive_observer.publish(identifier, nonce)
|
||||
elif msg_type == 15:
|
||||
# Associated revealing proof request
|
||||
if msg_len != 38:
|
||||
log(f"Got an associated revealing proof request of wrong length ({msg_len} bytes from {sender}, but expected exactly 38 bytes)")
|
||||
continue
|
||||
parts_bitfield = msg[5]
|
||||
associated_proof_hash = msg[6:38]
|
||||
proof = b.get_associated_data(associated_proof_hash)
|
||||
if proof is None:
|
||||
# Cannot send anything, proof is unknown
|
||||
continue
|
||||
def handle_associated_relvealing_proof_request(addr, parts_bitfield, associated_proof_hash, proof):
|
||||
# Ping first, otherwise this protocol part would be a really strong ddos reflection amplifier
|
||||
subscription = receive_observer.listen((addr[0:2], "pong"))
|
||||
nonce = secrets.randbits(64).to_bytes(8, "big")
|
||||
ping = b"\0\0\0\0\x0dR u carrotcoin?" + nonce
|
||||
node.node_socket.sendto(ping, addr[0:2])
|
||||
try:
|
||||
while True:
|
||||
pong_nonce = subscription.receive(5)
|
||||
if pong_nonce == nonce:
|
||||
break
|
||||
except Empty:
|
||||
# No response to ping, ignore the initial request
|
||||
return
|
||||
for part_number in range(6):
|
||||
if parts_bitfield & (1 << part_number) == 0:
|
||||
continue
|
||||
revealing_proof_response = b"\0\0\0\0\x10" + \
|
||||
bytes([part_number]) + \
|
||||
associated_proof_hash + \
|
||||
proof[part_number*896:(part_number+1)*896]
|
||||
node.node_socket.sendto(revealing_proof_response, addr[0:2])
|
||||
# Start a thread because of the asynchronous ping
|
||||
threading.Thread(target=handle_associated_relvealing_proof_request, args=(addr, parts_bitfield, associated_proof_hash, proof)).start()
|
||||
elif msg_type == 16:
|
||||
# Associated revealing proof response
|
||||
if msg_len != 934:
|
||||
log(f"Got an associated revealing proof response of wrong length ({msg_len} bytes from {sender}, but expected exactly 934 bytes)")
|
||||
continue
|
||||
part_number = msg[5]
|
||||
if part_number not in range(6):
|
||||
continue
|
||||
associated_data_hash = msg[6:38]
|
||||
fragment = msg[38:934]
|
||||
identifier = (addr[0:2], "associated data", associated_data_hash)
|
||||
receive_observer.publish(identifier, {
|
||||
"part_number": part_number,
|
||||
"fragment": fragment
|
||||
})
|
||||
else:
|
||||
log(f"Got a udp message of unknown type from {sender}. (type {msg_type})")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user