Blocks with reveal transactions that were loaded from disk could not be distributed to other nodes before. This prevented new nodes with an old blockchain state from synchronizing the blockchain. This bug has now been fixed.
597 lines
25 KiB
Python
597 lines
25 KiB
Python
from dataclasses import dataclass
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
|
from cryptography.exceptions import InvalidSignature
|
|
import hashlib
|
|
from multiprocessing import Lock
|
|
import time
|
|
|
|
n = 22152137184697602751949152182712806144286735269755991212091578018969267364854563246965161398309375840488156997640625085213985729183180764348127989435514689722834129288342499703533613231239853168289771769536412763137391054558055082146752229593979328251181647873233949602834141648160681711983351545692646009424518816069561938917629523175464947983950548802679152115205735609960641453864298194702935993896839374645356040490091081577992299773430144650589605043643969140352237968606446474316247592579560197155686719175897498255683642121505357781103123719079205647707696181709515150954235402701095586525936356219917713227143
|
|
|
|
class Transaction:
|
|
pass
|
|
|
|
class NoTransaction(Transaction):
|
|
def is_valid(self):
|
|
return True
|
|
def is_valid_after_block(self, block):
|
|
return True
|
|
def get_transaction_raw(self):
|
|
return 149 * b"\0"
|
|
def is_empty(self):
|
|
return True
|
|
|
|
@dataclass
|
|
class PaymentTransaction(Transaction):
|
|
id: int
|
|
sender: bytes
|
|
receiver: bytes
|
|
amount: int
|
|
transaction_fee: int
|
|
signature: bytes
|
|
|
|
def from_bytes(transaction_raw):
|
|
return PaymentTransaction(
|
|
id = int.from_bytes(transaction_raw[1:5], "big"),
|
|
sender = transaction_raw[5:37],
|
|
receiver = transaction_raw[37:69],
|
|
amount = int.from_bytes(transaction_raw[69:77], "big"),
|
|
transaction_fee = int.from_bytes(transaction_raw[77:85], "big"),
|
|
signature = transaction_raw[85:149],
|
|
)
|
|
def is_valid(self):
|
|
sender_pubkey = Ed25519PublicKey.from_public_bytes(self.sender)
|
|
msg = b"\x01" + \
|
|
self.id.to_bytes(4, "big") + \
|
|
self.sender + \
|
|
self.receiver + \
|
|
self.amount.to_bytes(8, "big") + \
|
|
self.transaction_fee.to_bytes(8, "big")
|
|
try:
|
|
sender_pubkey.verify(self.signature, msg)
|
|
except InvalidSignature:
|
|
return False
|
|
return self.amount >= 1
|
|
def is_valid_after_block(self, block):
|
|
if (self.sender, self.id) in block.used_transaction_ids:
|
|
return False
|
|
balance = block.balances.get(self.sender)
|
|
if balance is None:
|
|
return False
|
|
return balance >= self.amount + self.transaction_fee
|
|
def get_transaction_raw(self):
|
|
return b"\x01" + \
|
|
self.id.to_bytes(4, "big") + \
|
|
self.sender + \
|
|
self.receiver + \
|
|
self.amount.to_bytes(8, "big") + \
|
|
self.transaction_fee.to_bytes(8, "big") + \
|
|
self.signature
|
|
def open_transactions_hash_data(self):
|
|
return b"\0" + \
|
|
self.transaction_fee.to_bytes(8, "big") + \
|
|
self.amount.to_bytes(8, "big") + \
|
|
self.sender + \
|
|
self.id.to_bytes(4, "big")
|
|
def sorting_id(self):
|
|
return (1, -self.transaction_fee, -self.amount, self.sender, self.id)
|
|
def is_empty(self):
|
|
return False
|
|
def __eq__(self, other):
|
|
return isinstance(other, PaymentTransaction) \
|
|
and (self.id, self.sender, self.receiver, self.amount, self.transaction_fee) == \
|
|
(other.id, other.sender, other.receiver, other.amount, other.transaction_fee)
|
|
|
|
@dataclass
|
|
class GamblingTransaction(Transaction):
|
|
id: int
|
|
player: bytes
|
|
amount: int
|
|
transaction_fee: int
|
|
signature: bytes
|
|
|
|
def from_bytes(transaction_raw):
|
|
if transaction_raw[117:149] != 32 * b"\0":
|
|
return InvalidTransaction()
|
|
return GamblingTransaction(
|
|
id = int.from_bytes(transaction_raw[1:5], "big"),
|
|
player = transaction_raw[5:37],
|
|
amount = int.from_bytes(transaction_raw[37:45], "big"),
|
|
transaction_fee = int.from_bytes(transaction_raw[45:53], "big"),
|
|
signature = transaction_raw[53:117],
|
|
)
|
|
def is_valid(self):
|
|
player_pubkey = Ed25519PublicKey.from_public_bytes(self.player)
|
|
msg = b"\x02" + \
|
|
self.id.to_bytes(4, "big") + \
|
|
self.player + \
|
|
self.amount.to_bytes(8, "big") + \
|
|
self.transaction_fee.to_bytes(8, "big")
|
|
try:
|
|
player_pubkey.verify(self.signature, msg)
|
|
except InvalidSignature:
|
|
return False
|
|
return self.amount >= 1
|
|
def is_valid_after_block(self, block):
|
|
if (self.player, self.id) in block.used_transaction_ids:
|
|
return False
|
|
balance = block.balances.get(self.player)
|
|
if balance is None:
|
|
return False
|
|
return balance >= self.amount + self.transaction_fee
|
|
def get_transaction_raw(self):
|
|
return b"\x02" + \
|
|
self.id.to_bytes(4, "big") + \
|
|
self.player + \
|
|
self.amount.to_bytes(8, "big") + \
|
|
self.transaction_fee.to_bytes(8, "big") + \
|
|
self.signature + \
|
|
32 * b"\0"
|
|
def open_transactions_hash_data(self):
|
|
return b"\0" + \
|
|
self.transaction_fee.to_bytes(8, "big") + \
|
|
self.amount.to_bytes(8, "big") + \
|
|
self.player + \
|
|
self.id.to_bytes(4, "big")
|
|
def sorting_id(self):
|
|
return (1, -self.transaction_fee, -self.amount, self.player, self.id)
|
|
def is_empty(self):
|
|
return False
|
|
def __eq__(self, other):
|
|
return isinstance(other, GamblingTransaction) \
|
|
and (self.id, self.player, self.amount, self.transaction_fee) == \
|
|
(other.id, other.player, other.amount, other.transaction_fee)
|
|
|
|
def is_valid_group_element(x):
|
|
return x > 0 and x <= n//2
|
|
|
|
def normalize(x):
|
|
if x <= n//2:
|
|
return x
|
|
return n - x
|
|
|
|
@dataclass
|
|
class RevealTransaction:
|
|
revealer_pubkey: bytes
|
|
associated_proof_hash: bytes
|
|
reveal_value: bytes
|
|
intermediates: list
|
|
|
|
def from_bytes(transaction_raw, associated_proof_data):
|
|
if transaction_raw[65:149] != 84 * b"\0":
|
|
return InvalidTransaction()
|
|
return RevealTransaction(
|
|
revealer_pubkey = transaction_raw[1:33],
|
|
associated_proof_hash = transaction_raw[33:65],
|
|
reveal_value = associated_proof_data[0:256],
|
|
intermediates = [associated_proof_data[(i+1)*256:(i+2)*256] for i in range(20)],
|
|
)
|
|
def is_valid(self):
|
|
to_hash = self.get_associated_data()
|
|
return hashlib.sha256(to_hash).digest() == self.associated_proof_hash
|
|
def is_valid_after_block(self, block):
|
|
if len(block.pending_commitment_blocks) == 0:
|
|
return False
|
|
claim_R = int.from_bytes(self.reveal_value, "big")
|
|
if not is_valid_group_element(claim_R):
|
|
return False
|
|
intermediate_numbers = [int.from_bytes(i, "big") for i in self.intermediates]
|
|
for intermediate in intermediate_numbers:
|
|
if not is_valid_group_element(intermediate):
|
|
return False
|
|
claim_H = int.from_bytes(block.pending_commitment_blocks[0][0], "big")
|
|
for c in range(20):
|
|
i = 30 - c
|
|
claim_I = intermediate_numbers[c]
|
|
to_hash = self.revealer_pubkey + \
|
|
claim_H.to_bytes(256, "big") + \
|
|
claim_I.to_bytes(256, "big") + \
|
|
claim_R.to_bytes(256, "big") + \
|
|
i.to_bytes(1, "big")
|
|
e = int.from_bytes(hashlib.sha256(to_hash).digest(), "big")
|
|
new_H = normalize((claim_H * pow(claim_I, e, n)) % n)
|
|
new_R = normalize((claim_I * pow(claim_R, e, n)) % n)
|
|
claim_H = new_H
|
|
claim_R = new_R
|
|
return normalize(pow(claim_H, 2**1024, n)) == claim_R
|
|
def get_transaction_raw(self):
|
|
return b"\x03" + \
|
|
self.revealer_pubkey + \
|
|
self.associated_proof_hash + \
|
|
84 * b"\0"
|
|
def open_transactions_hash_data(self):
|
|
return b"\x01" + 52 * b"\0"
|
|
def get_associated_data(self):
|
|
return b"".join([self.reveal_value] + self.intermediates)
|
|
def sorting_id(self):
|
|
return (0,)
|
|
def is_empty(self):
|
|
return False
|
|
def __eq__(self, other):
|
|
return isinstance(other, RevealTransaction) \
|
|
and (self.revealer_pubkey, self.associated_proof_hash, self.reveal_value, self.intermediates) == \
|
|
(other.revealer_pubkey, other.associated_proof_hash, other.reveal_value, other.intermediates)
|
|
|
|
class InvalidTransaction:
|
|
def is_valid(self):
|
|
return False
|
|
|
|
def associated_data_required(transaction):
|
|
return transaction[0] == 3 and transaction[65:149] == 84 * b"\0"
|
|
|
|
def transaction_from_bytes(transaction_raw, associated_data = None):
|
|
assert len(transaction_raw) == 149
|
|
if transaction_raw == 149 * b"\0":
|
|
return NoTransaction()
|
|
elif transaction_raw[0] == 1:
|
|
return PaymentTransaction.from_bytes(transaction_raw)
|
|
elif transaction_raw[0] == 2:
|
|
return GamblingTransaction.from_bytes(transaction_raw)
|
|
elif transaction_raw[0] == 3:
|
|
return RevealTransaction.from_bytes(transaction_raw, associated_data)
|
|
else:
|
|
return InvalidTransaction()
|
|
|
|
@dataclass
|
|
class Block:
|
|
nonce: int
|
|
timestamp: int
|
|
previous_hash: bytes
|
|
message: bytes
|
|
difficulty_sum: int
|
|
miner_pubkey: bytes
|
|
transaction: Transaction
|
|
own_hash: bytes
|
|
pending_commitment_blocks: list
|
|
pending_gambling_transactions: list
|
|
balances: dict
|
|
# (sender_pubkey, id) tuples
|
|
used_transaction_ids: set
|
|
block_number: int
|
|
persist_address: int
|
|
valid: bool
|
|
|
|
def from_bytes(block_raw, associated_data = None):
|
|
assert len(block_raw) == 293
|
|
transaction_raw = block_raw[0:149]
|
|
transaction = transaction_from_bytes(transaction_raw, associated_data)
|
|
return Block(
|
|
transaction = transaction,
|
|
message = block_raw[149:181],
|
|
miner_pubkey = block_raw[181:213],
|
|
previous_hash = block_raw[213:245],
|
|
timestamp = int.from_bytes(block_raw[245:253], "big"),
|
|
difficulty_sum = int.from_bytes(block_raw[253:285], "big"),
|
|
nonce = int.from_bytes(block_raw[285:293], "big"),
|
|
own_hash = hashlib.sha256(block_raw).digest(),
|
|
pending_commitment_blocks = None,
|
|
pending_gambling_transactions = None,
|
|
balances = None,
|
|
used_transaction_ids = None,
|
|
block_number = None,
|
|
persist_address = None,
|
|
valid = False,
|
|
)
|
|
def validate(self, blockchain):
|
|
if not self.transaction.is_valid():
|
|
return False
|
|
if self.previous_hash != 32 * b"\0":
|
|
prev_block = blockchain.get_block(self.previous_hash)
|
|
if prev_block is None:
|
|
return False
|
|
if not prev_block.valid:
|
|
return False
|
|
if self.timestamp <= prev_block.timestamp:
|
|
return False
|
|
if self.timestamp > time.time():
|
|
return False
|
|
if not self.transaction.is_valid_after_block(prev_block):
|
|
return False
|
|
else:
|
|
prev_block = None
|
|
if not self.transaction.is_empty():
|
|
return False
|
|
# check for the correct miner pubkey - which will become public at launch day
|
|
h = hashlib.sha256(self.miner_pubkey).hexdigest()
|
|
if h != "88023d392db35f2d3936abd0532003ae0a38b4d35e4d123a0fa28c568c7e3e2f":
|
|
return False
|
|
B_1_difficulty_sum, _ = self.get_difficulty_info(1, blockchain)
|
|
B_10_difficulty_sum, B_10_timestamp = self.get_difficulty_info(10, blockchain)
|
|
D = B_1_difficulty_sum - B_10_difficulty_sum
|
|
T = self.timestamp - B_10_timestamp
|
|
calculated_difficulty = D * 3000 // 9 // T
|
|
block_difficulty = max(calculated_difficulty, 2**28)
|
|
if B_1_difficulty_sum + block_difficulty != self.difficulty_sum:
|
|
return False
|
|
self.valid = int.from_bytes(self.own_hash, "big") * block_difficulty < 2**256
|
|
if self.valid:
|
|
self.calculate_block_number(prev_block)
|
|
self.calculate_pending_gambling_transactions(prev_block)
|
|
self.calculate_balances(prev_block)
|
|
self.calculate_used_transaction_ids(prev_block)
|
|
self.calculate_persist_address(prev_block)
|
|
return self.valid
|
|
def calculate_pending_gambling_transactions(self, prev_block):
|
|
if prev_block is None:
|
|
self.pending_commitment_blocks = []
|
|
self.pending_gambling_transactions = []
|
|
return
|
|
pending_commitment_blocks = prev_block.pending_commitment_blocks.copy()
|
|
pending_gambling_transactions = prev_block.pending_gambling_transactions.copy()
|
|
if isinstance(self.transaction, GamblingTransaction):
|
|
pending_gambling_transactions.append(self.transaction)
|
|
if self.block_number % 256 == 255:
|
|
pending_commitment_blocks.append((self.own_hash, pending_gambling_transactions))
|
|
pending_gambling_transactions = []
|
|
if isinstance(self.transaction, RevealTransaction):
|
|
pending_commitment_blocks = pending_commitment_blocks[1:]
|
|
self.pending_commitment_blocks = pending_commitment_blocks
|
|
self.pending_gambling_transactions = pending_gambling_transactions
|
|
def calculate_balances(self, prev_block):
|
|
if prev_block is None:
|
|
self.balances = {
|
|
self.miner_pubkey: 100,
|
|
}
|
|
return
|
|
balances = prev_block.balances.copy()
|
|
balances.setdefault(self.miner_pubkey, 0)
|
|
balances[self.miner_pubkey] += 100
|
|
t = self.transaction
|
|
if isinstance(t, PaymentTransaction):
|
|
balances[self.miner_pubkey] += t.transaction_fee
|
|
balances[t.sender] -= (t.amount + t.transaction_fee)
|
|
balances.setdefault(t.receiver, 0)
|
|
balances[t.receiver] += t.amount
|
|
elif isinstance(t, GamblingTransaction):
|
|
balances[self.miner_pubkey] += t.transaction_fee
|
|
balances[t.player] -= (t.amount + t.transaction_fee)
|
|
elif isinstance(t, RevealTransaction):
|
|
balances[self.miner_pubkey] += 100
|
|
balances.setdefault(t.revealer_pubkey, 0)
|
|
balances[t.revealer_pubkey] += 1500
|
|
revealed_gamblings = prev_block.pending_commitment_blocks[0][1]
|
|
for transaction in revealed_gamblings:
|
|
to_hash = transaction.id.to_bytes(4, "big") + \
|
|
transaction.player + \
|
|
t.reveal_value
|
|
h = hashlib.sha256(to_hash).digest()
|
|
if h[0] < 0x80:
|
|
continue
|
|
balances[transaction.player] += 2 * transaction.amount
|
|
self.balances = balances
|
|
def calculate_used_transaction_ids(self, prev_block):
|
|
if prev_block is None:
|
|
self.used_transaction_ids = set()
|
|
return
|
|
used_transaction_ids = prev_block.used_transaction_ids.copy()
|
|
t = self.transaction
|
|
if isinstance(t, PaymentTransaction):
|
|
used_transaction_ids.add((t.sender, t.id))
|
|
elif isinstance(t, GamblingTransaction):
|
|
used_transaction_ids.add((t.player, t.id))
|
|
self.used_transaction_ids = used_transaction_ids
|
|
def calculate_block_number(self, prev_block):
|
|
if prev_block is None:
|
|
self.block_number = 0
|
|
else:
|
|
self.block_number = prev_block.block_number + 1
|
|
def calculate_persist_address(self, prev_block):
|
|
if prev_block is None:
|
|
self.persist_address = 0
|
|
else:
|
|
self.persist_address = prev_block.persist_address + 293
|
|
if isinstance(prev_block.transaction, RevealTransaction):
|
|
self.persist_address += 5376
|
|
def get_difficulty_info(self, steps, blockchain):
|
|
if steps == 0:
|
|
return self.difficulty_sum, self.timestamp
|
|
if self.previous_hash == 32 * b"\0":
|
|
difficulty_sum = 2**29 - steps * 2**28
|
|
timestamp = self.timestamp - steps * 300
|
|
return difficulty_sum, timestamp
|
|
else:
|
|
previous_block = blockchain.get_block(self.previous_hash)
|
|
return previous_block.get_difficulty_info(steps-1, blockchain)
|
|
def get_block_raw(self):
|
|
return self.transaction.get_transaction_raw() + \
|
|
self.message + \
|
|
self.miner_pubkey + \
|
|
self.previous_hash + \
|
|
self.timestamp.to_bytes(8, "big") + \
|
|
self.difficulty_sum.to_bytes(32, "big") + \
|
|
self.nonce.to_bytes(8, "big")
|
|
|
|
class OpenTransactions:
|
|
def __init__(self, blockchain):
|
|
self.__blockchain = blockchain
|
|
self.__open_transactions = []
|
|
self.__lock = Lock()
|
|
self.__recalculate_hashes()
|
|
def add(self, transaction):
|
|
assert transaction.is_valid()
|
|
with self.__lock:
|
|
# pre-check 1: Check for duplicates
|
|
for existing_transaction in self.__open_transactions:
|
|
if transaction == existing_transaction:
|
|
return
|
|
# pre-check 2: Check if there is space
|
|
if not self.__has_space(transaction):
|
|
return
|
|
# Add the transaction
|
|
self.__open_transactions.append(transaction)
|
|
self.__cleanup()
|
|
def update(self):
|
|
with self.__lock:
|
|
self.__cleanup()
|
|
def get_hash(self, i):
|
|
assert i < 1024
|
|
with self.__lock:
|
|
return self.__hashes[i]
|
|
def get_transaction(self, i):
|
|
with self.__lock:
|
|
if i >= len(self.__open_transactions):
|
|
return None
|
|
return self.__open_transactions[i]
|
|
def __has_space(self, transaction):
|
|
if len(self.__open_transactions) < 1024:
|
|
return True
|
|
return transaction.sorting_id() < self.__open_transactions[-1].sorting_id()
|
|
def __cleanup(self):
|
|
# sort everything
|
|
self.__open_transactions.sort(key = lambda t: t.sorting_id())
|
|
# drop out invalid ones
|
|
# - reused ids
|
|
# - paying more money than available
|
|
latest_block = self.__blockchain.get_latest_block()
|
|
if latest_block is None:
|
|
self.__open_transactions = []
|
|
self.__recalculate_hashes()
|
|
return
|
|
used_transaction_ids = latest_block.used_transaction_ids.copy()
|
|
balances = latest_block.balances.copy()
|
|
contains_reveal_transaction = False
|
|
def is_valid(transaction):
|
|
nonlocal contains_reveal_transaction
|
|
if isinstance(transaction, PaymentTransaction):
|
|
sender_tuple = (transaction.sender, transaction.id)
|
|
elif isinstance(transaction, GamblingTransaction):
|
|
sender_tuple = (transaction.player, transaction.id)
|
|
elif isinstance(transaction, RevealTransaction):
|
|
latest_block = self.__blockchain.get_latest_block()
|
|
if latest_block is None:
|
|
return False
|
|
if not transaction.is_valid_after_block(latest_block):
|
|
return False
|
|
if contains_reveal_transaction:
|
|
return False
|
|
contains_reveal_transaction = True
|
|
return True
|
|
if sender_tuple in used_transaction_ids:
|
|
return False
|
|
balance = balances.get(sender_tuple[0]) or 0
|
|
if transaction.amount + transaction.transaction_fee > balance:
|
|
return False
|
|
used_transaction_ids.add(sender_tuple)
|
|
balances[sender_tuple[0]] = balance - transaction.amount - transaction.transaction_fee
|
|
return True
|
|
self.__open_transactions = [transaction for transaction in self.__open_transactions if is_valid(transaction)]
|
|
# limit to 1024
|
|
self.__open_transactions = self.__open_transactions[0:1024]
|
|
self.__recalculate_hashes()
|
|
def __recalculate_hashes(self):
|
|
self.__hashes = []
|
|
current_hash = 32 * b"\0"
|
|
for i in range(1024):
|
|
if i >= len(self.__open_transactions):
|
|
transaction_data = 53 * b"\0"
|
|
else:
|
|
transaction = self.__open_transactions[i]
|
|
transaction_data = transaction.open_transactions_hash_data()
|
|
current_hash = hashlib.sha256(current_hash + transaction_data).digest()
|
|
self.__hashes.append(current_hash)
|
|
|
|
class Blockchain:
|
|
def __init__(self):
|
|
# maps block hashes to block instances
|
|
self.__block_map = {}
|
|
self.__latest_block_hash = None
|
|
self.__associated_data = {}
|
|
self.__lock = Lock()
|
|
self.open_transactions = OpenTransactions(self)
|
|
self.__load_blocks_from_disk()
|
|
def __load_blocks_from_disk(self):
|
|
last_valid = None
|
|
try:
|
|
with open("blockchain", "rb") as f:
|
|
while True:
|
|
block = f.read(293)
|
|
if len(block) < 293:
|
|
break
|
|
if associated_data_required(block[0:149]):
|
|
associated_data = f.read(5376)
|
|
if len(associated_data) < 5376:
|
|
break
|
|
else:
|
|
associated_data = None
|
|
block_obj = self.add_block(block, associated_data)
|
|
if not block_obj.validate(self):
|
|
break
|
|
self.cache_associated_data(block_obj.transaction)
|
|
last_valid = block_obj
|
|
except FileNotFoundError:
|
|
pass
|
|
if last_valid is not None:
|
|
self.set_latest_block(last_valid.own_hash, persist = False)
|
|
def set_latest_block(self, block_hash, persist = True):
|
|
new_block = self.get_block(block_hash)
|
|
assert new_block is not None
|
|
assert new_block.valid
|
|
while True:
|
|
with self.__lock:
|
|
latest_block_hash = self.__latest_block_hash
|
|
if latest_block_hash is not None:
|
|
latest_block = self.get_block(latest_block_hash)
|
|
current_difficulty_sum = latest_block.get_difficulty_info(1, self)[0]
|
|
new_difficulty_sum = new_block.get_difficulty_info(1, self)[0]
|
|
if new_difficulty_sum <= current_difficulty_sum:
|
|
return False
|
|
else:
|
|
latest_block = None
|
|
with self.__lock:
|
|
if self.__latest_block_hash != latest_block_hash:
|
|
continue
|
|
self.__latest_block_hash = block_hash
|
|
if persist:
|
|
self.__persist_block_update(latest_block, new_block)
|
|
self.open_transactions.update()
|
|
return True
|
|
def __persist_block_update(self, old_block, new_block):
|
|
if old_block is not None:
|
|
while old_block.block_number > new_block.block_number:
|
|
old_block = self.__block_map[old_block.previous_hash]
|
|
block_list = []
|
|
while new_block is not old_block:
|
|
block_list.append(new_block)
|
|
if new_block.previous_hash == 32 * b"\0":
|
|
break
|
|
new_block = self.__block_map[new_block.previous_hash]
|
|
if old_block is not None and old_block.block_number > new_block.block_number:
|
|
old_block = self.__block_map[old_block.previous_hash]
|
|
start_addr = block_list[-1].persist_address
|
|
open_mode = "wb" if start_addr == 0 else "r+b"
|
|
with open("blockchain", open_mode) as f:
|
|
f.seek(start_addr)
|
|
for block in reversed(block_list):
|
|
f.write(block.get_block_raw())
|
|
if isinstance(block.transaction, RevealTransaction):
|
|
f.write(block.transaction.get_associated_data())
|
|
f.truncate()
|
|
def add_block(self, block_raw, associated_data = None):
|
|
with self.__lock:
|
|
block = Block.from_bytes(block_raw, associated_data)
|
|
if block.own_hash not in self.__block_map:
|
|
self.__block_map[block.own_hash] = block
|
|
return self.__block_map[block.own_hash]
|
|
def get_block(self, hash):
|
|
with self.__lock:
|
|
return self.__block_map.get(hash)
|
|
def get_second_last_difficulty_sum(self):
|
|
with self.__lock:
|
|
latest_block_hash = self.__latest_block_hash
|
|
if latest_block_hash is None:
|
|
return 0
|
|
block = self.get_block(latest_block_hash)
|
|
return block.get_difficulty_info(1, self)[0]
|
|
def get_latest_block(self):
|
|
with self.__lock:
|
|
if self.__latest_block_hash is None:
|
|
return None
|
|
return self.__block_map[self.__latest_block_hash]
|
|
def cache_associated_data(self, transaction):
|
|
if not isinstance(transaction, RevealTransaction):
|
|
return
|
|
with self.__lock:
|
|
self.__associated_data[transaction.associated_proof_hash] = transaction.get_associated_data()
|
|
def get_associated_data(self, associated_proof_hash):
|
|
with self.__lock:
|
|
return self.__associated_data.get(associated_proof_hash, None)
|