Functional prototype
This commit is contained in:
parent
741b1f5b2a
commit
1c31de2bb4
@ -8,7 +8,7 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
ROLE: api
|
||||
DATA_PATH: /data
|
||||
DATA_PATH: /tmp
|
||||
DIFFICULTY_REQUIREMENT: 3
|
||||
MINE_DB_HOST: mariadb-mine
|
||||
MINE_DB_USER: mine
|
||||
@ -17,8 +17,6 @@ services:
|
||||
- mariadb-mine
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- ./mine_data:/data
|
||||
|
||||
mariadb-mine:
|
||||
image: mariadb:latest
|
||||
@ -29,6 +27,3 @@ services:
|
||||
MARIADB_PASSWORD: 123abc
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
volumes:
|
||||
mine_data:
|
||||
|
@ -3,23 +3,26 @@ import hashlib
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
from Mine.Card import Card
|
||||
from Mine.Transaction import Transaction
|
||||
|
||||
class Block():
|
||||
def __init__(self, blockId=uuid.uuid4(), previousHash="0",
|
||||
timestamp=datetime.datetime.now(datetime.timezone.utc), height=0, nonce=0,
|
||||
difficulty=0, card=Card(), transactions=[], data=None):
|
||||
def __init__(self, blockId=None, previousHash="0", timestamp=None, height=0, nonce=0,
|
||||
difficulty=0, card=None, transactions=[], data=None):
|
||||
if data:
|
||||
self.load_from_data(data)
|
||||
else:
|
||||
self.blockId = blockId
|
||||
self.blockId = blockId if blockId else uuid.uuid4()
|
||||
self.previousHash = previousHash
|
||||
self.timestamp = timestamp
|
||||
self.timestamp = timestamp if timestamp else datetime.datetime.now(
|
||||
datetime.timezone.utc
|
||||
)
|
||||
self.height = height
|
||||
self.difficulty = difficulty
|
||||
self.nonce = nonce
|
||||
self.card = card
|
||||
self.card = card if card else Card()
|
||||
self.transactions = transactions
|
||||
self.update()
|
||||
|
||||
@ -62,8 +65,14 @@ class Block():
|
||||
if not self.nonce >= 0:
|
||||
raise self.Invalid("Nonce less than 0.")
|
||||
self.card.validate()
|
||||
seenTransactions = []
|
||||
for transaction in self.transactions:
|
||||
if transaction in seenTransactions:
|
||||
raise self.Invalid(
|
||||
f"Contains duplicate transaction {transaction.transactionId}."
|
||||
)
|
||||
transaction.validate()
|
||||
seenTransactions.append(transaction)
|
||||
# TODO validate that one transaction gives the card to the author
|
||||
|
||||
def load_from_data(self, data):
|
||||
@ -85,12 +94,12 @@ class Block():
|
||||
transactionId = uuid.UUID(each['transactionId']),
|
||||
timestamp = datetime.datetime.strptime(
|
||||
each['timestamp'],
|
||||
"%Y-%m-%d %H:%M:%S.%f"
|
||||
"%Y-%m-%d %H:%M:%S.%f%z"
|
||||
),
|
||||
cardId = uuid.UUID(each['cardId']),
|
||||
sender = each['sender'],
|
||||
receiver = each['receiver'],
|
||||
signature = each['signature']
|
||||
sender = serialization.load_pem_public_key(each['sender'].encode('utf-8')),
|
||||
receiver = serialization.load_pem_public_key(each['receiver'].encode('utf-8')),
|
||||
signature = bytes.fromhex(each['signature'])
|
||||
) for each in data['transactions']
|
||||
]
|
||||
self.update()
|
||||
@ -110,9 +119,10 @@ class Block():
|
||||
|
||||
def __str__(self):
|
||||
# The hash of the block is the SHA256 hash of what this method returns.
|
||||
return json.dumps(self.as_dict())
|
||||
return json.dumps(self.as_dict(), sort_keys=True)
|
||||
|
||||
class Invalid(Exception):
|
||||
def __init__(self, message, status_code=406):
|
||||
def __init__(self, message, statusCode=406):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
self.message = message
|
||||
self.statusCode = statusCode
|
||||
|
@ -1,11 +1,13 @@
|
||||
import requests
|
||||
import json
|
||||
import wikipedia
|
||||
import requests
|
||||
import time
|
||||
import uuid
|
||||
import wikipedia
|
||||
|
||||
class Card():
|
||||
def __init__(self, cardId=uuid.uuid4(), pageId=None, data=None):
|
||||
self.cardId = cardId
|
||||
def __init__(self, cardId=None, pageId=None, data=None):
|
||||
self.cardId = cardId if cardId else uuid.uuid4()
|
||||
delay = 2
|
||||
while True:
|
||||
try:
|
||||
self.pageId = pageId if pageId else int(
|
||||
@ -19,6 +21,10 @@ class Card():
|
||||
except wikipedia.exceptions.DisambiguationError as e:
|
||||
# TODO pick random disambiuation option
|
||||
continue
|
||||
except wikipedia.exceptions.WikipediaException as e:
|
||||
time.sleep(delay)
|
||||
delay = delay**2
|
||||
continue
|
||||
break
|
||||
if data:
|
||||
self.load_from_data(data)
|
||||
@ -41,9 +47,10 @@ class Card():
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps(self.as_dict())
|
||||
return json.dumps(self.as_dict(), sort_keys=True)
|
||||
|
||||
class Invalid(Exception):
|
||||
def __init__(self, message, status_code=406):
|
||||
def __init__(self, message, statusCode=406):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
self.message = message
|
||||
self.statusCode = statusCode
|
||||
|
@ -1,44 +1,61 @@
|
||||
import base64
|
||||
import datetime
|
||||
import mariadb
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
from Mine.Block import Block
|
||||
from Mine.Card import Card
|
||||
from Mine.Transaction import Transaction
|
||||
|
||||
class Database():
|
||||
SQL_CREATE_DATABASE = [
|
||||
SQL_CREATE_TABLES = [
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS mine.blocks(
|
||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
||||
blockId VARCHAR(37) UNIQUE NOT NULL,
|
||||
blockId UUID UNIQUE NOT NULL,
|
||||
blockHash VARCHAR(64) UNIQUE NOT NULL,
|
||||
previousHash VARCHAR(64) UNIQUE NOT NULL,
|
||||
timestamp DATETIME NOT NULL,
|
||||
timestamp VARCHAR(32) NOT NULL,
|
||||
height INT UNIQUE NOT NULL,
|
||||
difficulty INT NOT NULL,
|
||||
nonce INT NOT NULL
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS mine.cards(
|
||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
||||
blockId VARCHAR(37) UNIQUE NOT NULL,
|
||||
cardId VARCHAR(37) UNIQUE NOT NULL,
|
||||
blockId UUID UNIQUE NOT NULL,
|
||||
cardId UUID UNIQUE NOT NULL,
|
||||
pageId INT NOT NULL,
|
||||
FOREIGN KEY (blockId) REFERENCES blocks(blockId)
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS mine.pending_transactions(
|
||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
||||
transactionId UUID UNIQUE NOT NULL,
|
||||
cardId UUID NOT NULL,
|
||||
timestamp VARCHAR(32) NOT NULL,
|
||||
sender TEXT NOT NULL,
|
||||
receiver TEXT NOT NULL,
|
||||
signature TEXT NOT NULL,
|
||||
FOREIGN KEY (cardId) REFERENCES cards(cardId)
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS mine.transactions(
|
||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
||||
blockId VARCHAR(37) NOT NULL,
|
||||
cardId VARCHAR(37) UNIQUE NOT NULL,
|
||||
transactionId VARCHAR(37) UNIQUE NOT NULL,
|
||||
timestamp DATETIME NOT NULL,
|
||||
sender VARCHAR(128) NOT NULL,
|
||||
receiver VARCHAR(128) NOT NULL,
|
||||
signature VARCHAR(128) NOT NULL,
|
||||
isPending BOOLEAN NOT NULL DEFAULT true,
|
||||
isAbandoned BOOLEAN NOT NULL DEFAULT false,
|
||||
blockId UUID NOT NULL,
|
||||
transactionId UUID UNIQUE NOT NULL,
|
||||
cardId UUID NOT NULL,
|
||||
timestamp VARCHAR(32) NOT NULL,
|
||||
sender TEXT NOT NULL,
|
||||
receiver TEXT NOT NULL,
|
||||
signature TEXT NOT NULL,
|
||||
FOREIGN KEY (blockId) REFERENCES blocks(blockId),
|
||||
FOREIGN KEY (cardId) REFERENCES cards(cardId)
|
||||
);
|
||||
@ -46,7 +63,7 @@ class Database():
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS mine.peers(
|
||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
||||
peerId VARCHAR(37) UNIQUE NOT NULL,
|
||||
peerId UUID UNIQUE NOT NULL,
|
||||
baseUrl VARCHAR(128) UNIQUE NOT NULL,
|
||||
isUp BOOLEAN NOT NULL,
|
||||
downCount INT DEFAULT 0,
|
||||
@ -58,61 +75,89 @@ class Database():
|
||||
SQL_GET_BLOCK_BY_ID = """
|
||||
SELECT *
|
||||
FROM blocks
|
||||
WHERE blockId = '{}';
|
||||
WHERE blockId = ?;
|
||||
"""
|
||||
|
||||
SQL_GET_LAST_BLOCK = """
|
||||
SELECT *
|
||||
FROM blocks
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1;
|
||||
"""
|
||||
|
||||
SQL_GET_CARD = """
|
||||
SELECT *
|
||||
SQL_GET_CARD_BY_ID = """
|
||||
SELECT cardId, pageId
|
||||
FROM cards
|
||||
WHERE cardId = '{}';
|
||||
WHERE cardId = ?;
|
||||
"""
|
||||
|
||||
SQL_GET_CARD_BY_BLOCK_ID = """
|
||||
SELECT cardId, pageId
|
||||
FROM cards
|
||||
WHERE blockId = ?;
|
||||
"""
|
||||
|
||||
SQL_GET_CARD_OWNER = """
|
||||
SELECT receiver
|
||||
FROM transactions
|
||||
WHERE cardId = '{}'
|
||||
AND isPending = False
|
||||
WHERE cardId = ?
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1;
|
||||
"""
|
||||
|
||||
SQL_GET_PENDING_TRANSACTIONS = """
|
||||
SELECT *
|
||||
FROM transactions
|
||||
WHERE isPending = True
|
||||
ORDER BY timestamp ASC
|
||||
LIMIT {};
|
||||
"""
|
||||
|
||||
SQL_GET_TRANSACTION_BY_ID = """
|
||||
SELECT *
|
||||
FROM transactions
|
||||
WHERE transactionId == '{}'
|
||||
"""
|
||||
|
||||
SQL_INSERT_BLOCK = """
|
||||
INSERT INTO blocks (blockId, blockHash, previousHash, timestamp, height, nonce)
|
||||
VALUES ('{}', '{}', '{}', '{}', {}, {});
|
||||
INSERT INTO mine.blocks (
|
||||
blockId, blockHash, previousHash, timestamp, height, difficulty, nonce
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
"""
|
||||
|
||||
SQL_GET_LAST_BLOCK = """
|
||||
SELECT blockId, previousHash, timestamp, height, difficulty, nonce
|
||||
FROM mine.blocks
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1;
|
||||
"""
|
||||
|
||||
SQL_INSERT_CARD = """
|
||||
INSERT INTO mine.cards (blockId, cardId, pageId) VALUES (?, ?, ?);
|
||||
"""
|
||||
|
||||
SQL_INSERT_PENDING_TRANSACTION = """
|
||||
INSERT INTO mine.pending_transactions (
|
||||
transactionId, timestamp, cardId, sender, receiver, signature
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
"""
|
||||
|
||||
SQL_GET_PENDING_TRANSACTIONS = """
|
||||
SELECT transactionId, timestamp, cardId, sender, receiver, signature
|
||||
FROM mine.pending_transactions
|
||||
ORDER BY timestamp ASC
|
||||
LIMIT ?;
|
||||
"""
|
||||
|
||||
SQL_DELETE_PENDING_TRANSACTION = """
|
||||
DELETE FROM mine.pending_transactions WHERE transactionId = ?;
|
||||
"""
|
||||
|
||||
SQL_INSERT_TRANSACTION = """
|
||||
INSERT INTO transactions (
|
||||
transactionId,
|
||||
timestamp,
|
||||
sender,
|
||||
receiver,
|
||||
signature,
|
||||
blockId,
|
||||
cardId
|
||||
INSERT INTO mine.transactions (
|
||||
blockId, transactionId, timestamp, cardId, sender, receiver, signature
|
||||
)
|
||||
VALUES ({}, {}, {}, {}, {});
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
"""
|
||||
|
||||
SQL_GET_PENDING_TRANSACTION_BY_ID = """
|
||||
SELECT transactionId, timestamp, cardId, sender, receiver, signature
|
||||
FROM pending_transactions
|
||||
WHERE transactionId = ?;
|
||||
"""
|
||||
|
||||
SQL_GET_TRANSACTION_BY_ID = """
|
||||
SELECT transactionId, timestamp, cardId, sender, receiver, signature
|
||||
FROM transactions
|
||||
WHERE transactionId = ?;
|
||||
"""
|
||||
|
||||
SQL_GET_TRANSACTIONS_BY_BLOCK_ID = """
|
||||
SELECT transactionId, timestamp, cardId, sender, receiver, signature
|
||||
FROM mine.transactions
|
||||
WHERE blockId = ?
|
||||
ORDER BY timestamp DESC;
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@ -124,67 +169,229 @@ class Database():
|
||||
port = os.getenv('MINE_DB_PORT', 3306),
|
||||
user = os.getenv('MINE_DB_USER', None),
|
||||
password = os.getenv('MINE_DB_PASSWORD', None),
|
||||
database = 'mine'
|
||||
database = 'mine',
|
||||
autocommit = True
|
||||
)
|
||||
for each in self.SQL_CREATE_DATABASE:
|
||||
cursor = self.conn.cursor().execute(each)
|
||||
for each in self.SQL_CREATE_TABLES:
|
||||
self.conn.cursor().execute(each)
|
||||
except mariadb.Error as e:
|
||||
time.sleep(delay := delay**2)
|
||||
continue
|
||||
break
|
||||
|
||||
def get_block_by_id(self, blockId):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_GET_BLOCK_BY_ID, [blockId])
|
||||
block = cur.fetchone()
|
||||
if block:
|
||||
blockCard = self.get_card_by_block_id(lastBlock[1])
|
||||
blockTransactions = self.get_transactions_by_block_id(lastBlock[1])
|
||||
return Block(
|
||||
data=self.conn.cursor().execute(
|
||||
self.SQL_GET_BLOCK_BY_ID.format(blockId)
|
||||
blockId = uuid.UUID(block[1]),
|
||||
previousHash = block[3],
|
||||
timestamp = block[4],
|
||||
height = block[5],
|
||||
nonce = block[6],
|
||||
difficulty = block[7],
|
||||
card = blockCard,
|
||||
transactions = blockTransactions
|
||||
)
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_card_by_id(self, cardId):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_GET_CARD_BY_ID, [str(cardId)])
|
||||
card = cur.fetchone()
|
||||
return Card(
|
||||
cardId = uuid.UUID(card[0]),
|
||||
pageId = card[1]
|
||||
)
|
||||
|
||||
def get_card_by_block_id(self, blockId):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_GET_CARD_BY_BLOCK_ID, [str(blockId)])
|
||||
card = cur.fetchone()
|
||||
return Card(
|
||||
cardId = uuid.UUID(card[0]),
|
||||
pageId = card[1]
|
||||
)
|
||||
|
||||
def get_last_block(self):
|
||||
data=self.conn.cursor().execute(self.SQL_GET_LAST_BLOCK)
|
||||
return Block(data=data) if data else None
|
||||
|
||||
def get_card(self, cardId):
|
||||
return Card(
|
||||
data=self.conn.cursor().execute(self.SQL_GET_CARD.format(cardId))
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_GET_LAST_BLOCK)
|
||||
lastBlock = cur.fetchone()
|
||||
if lastBlock:
|
||||
lastBlockCard = self.get_card_by_block_id(lastBlock[0])
|
||||
lastBlockTransactions = self.get_transactions_by_block_id(lastBlock[0])
|
||||
return Block(
|
||||
blockId = uuid.UUID(lastBlock[0]),
|
||||
previousHash = lastBlock[1],
|
||||
timestamp = datetime.datetime.strptime(
|
||||
lastBlock[2],
|
||||
"%Y-%m-%d %H:%M:%S.%f%z"
|
||||
),
|
||||
height = lastBlock[3],
|
||||
difficulty = lastBlock[4],
|
||||
nonce = lastBlock[5],
|
||||
card = lastBlockCard,
|
||||
transactions = lastBlockTransactions
|
||||
)
|
||||
|
||||
def get_card_owner(self, cardId):
|
||||
return self.conn.cursor().execute(
|
||||
self.SQL_GET_CARD_OWNER.format(cardId)
|
||||
)
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_GET_CARD_OWNER, [str(cardId)])
|
||||
owner = cur.fetchone()
|
||||
return serialization.load_pem_public_key(
|
||||
owner[0].encode('utf-8')
|
||||
) if owner else None
|
||||
|
||||
def get_pending_transactions(self, limit=32768):
|
||||
pendingTransactions = self.conn.cursor().execute(
|
||||
self.SQL_GET_PENDING_TRANSACTIONS.format(limit)
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(
|
||||
self.SQL_GET_PENDING_TRANSACTIONS,
|
||||
[limit]
|
||||
)
|
||||
pendingTransactions = cur.fetchall()
|
||||
return [
|
||||
Transaction(
|
||||
data=each
|
||||
) for each in pendingTransactions
|
||||
transactionId = uuid.UUID(pendingTransaction[0]),
|
||||
timestamp = datetime.datetime.strptime(
|
||||
pendingTransaction[1],
|
||||
"%Y-%m-%d %H:%M:%S.%f%z"
|
||||
),
|
||||
cardId = uuid.UUID(pendingTransaction[2]),
|
||||
# TODO: load rsa keys
|
||||
sender = serialization.load_pem_public_key(
|
||||
pendingTransaction[3].encode('utf-8')
|
||||
),
|
||||
receiver = serialization.load_pem_public_key(
|
||||
pendingTransaction[4].encode('utf-8')
|
||||
),
|
||||
signature = bytes.fromhex(pendingTransaction[5])
|
||||
) for pendingTransaction in pendingTransactions
|
||||
] if pendingTransactions else []
|
||||
|
||||
def get_transaction(self, transactionId):
|
||||
def get_pending_transaction_by_id(self, transactionId):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_GET_PENDING_TRANSACTION_BY_ID, [str(transactionId)])
|
||||
transaction = cur.fetchone()
|
||||
if transaction:
|
||||
return Transaction(
|
||||
data=self.conn.cursor().execute(self.SQL_GET_TRANSACTION.format(transactionId))
|
||||
transactionId = transaction[0],
|
||||
timestamp = datetime.datetime.strptime(
|
||||
transaction[1],
|
||||
"%Y-%m-%d %H:%M:%S.%f%z"
|
||||
),
|
||||
cardId = uuid.UUID(transaction[2]),
|
||||
sender = serialization.load_pem_public_key(
|
||||
transaction[3].encode('utf-8')
|
||||
),
|
||||
receiver = serialization.load_pem_public_key(
|
||||
transaction[4].encode('utf-8')
|
||||
),
|
||||
signature = bytes.fromhex(transaction[5])
|
||||
)
|
||||
|
||||
def get_transaction_by_id(self, transactionId):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_GET_TRANSACTION_BY_ID, [str(transactionId)])
|
||||
transaction = cur.fetchone()
|
||||
if transaction:
|
||||
return Transaction(
|
||||
transactionId = transaction[0],
|
||||
timestamp = datetime.datetime.strptime(
|
||||
transaction[1],
|
||||
"%Y-%m-%d %H:%M:%S.%f%z"
|
||||
),
|
||||
cardId = uuid.UUID(transaction[2]),
|
||||
sender = serialization.load_pem_public_key(
|
||||
transaction[3].encode('utf-8')
|
||||
),
|
||||
receiver = serialization.load_pem_public_key(
|
||||
transaction[4].encode('utf-8')
|
||||
),
|
||||
signature = bytes.fromhex(transaction[5])
|
||||
)
|
||||
|
||||
def get_transactions_by_block_id(self, blockId):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_GET_TRANSACTIONS_BY_BLOCK_ID, [blockId])
|
||||
transactions = cur.fetchall()
|
||||
return [
|
||||
Transaction(
|
||||
transactionId = transaction[0],
|
||||
timestamp = datetime.datetime.strptime(
|
||||
transaction[1],
|
||||
"%Y-%m-%d %H:%M:%S.%f%z"
|
||||
),
|
||||
cardId = transaction[2],
|
||||
sender = serialization.load_pem_public_key(
|
||||
transaction[3].encode('utf-8')
|
||||
),
|
||||
receiver = serialization.load_pem_public_key(
|
||||
transaction[4].encode('utf-8')
|
||||
),
|
||||
signature = bytes.fromhex(transaction[5])
|
||||
) for transaction in transactions
|
||||
]
|
||||
|
||||
def insert_block(self, block):
|
||||
self.conn.cursor().execute(self.SQL_INSERT_BLOCK.format(
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_INSERT_BLOCK, (
|
||||
str(block.blockId),
|
||||
block.blockHash.hexdigest(),
|
||||
block.previousHash,
|
||||
str(block.timestamp),
|
||||
block.height,
|
||||
block.difficulty,
|
||||
block.nonce
|
||||
))
|
||||
|
||||
def insert_transaction(self, transaction):
|
||||
return self.conn.cursor().execute(self.SQL_INSERT_TRANSACTION.format(
|
||||
def insert_card(self, blockId, card):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_INSERT_CARD, (
|
||||
str(blockId),
|
||||
str(card.cardId),
|
||||
card.pageId
|
||||
))
|
||||
|
||||
def insert_pending_transaction(self, transaction):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_INSERT_PENDING_TRANSACTION, (
|
||||
str(transaction.transactionId),
|
||||
str(transaction.timestamp),
|
||||
transaction.sender,
|
||||
transaction.receiver,
|
||||
transaction.signature,
|
||||
str(transaction.cardId)
|
||||
str(transaction.cardId),
|
||||
transaction.sender.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode('utf-8'),
|
||||
transaction.receiver.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode('utf-8'),
|
||||
transaction.signature.hex()
|
||||
))
|
||||
|
||||
def delete_pending_transaction(self, transactionId):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_DELETE_PENDING_TRANSACTION, [str(transactionId)])
|
||||
|
||||
def insert_transaction(self, blockId, transaction):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(self.SQL_INSERT_TRANSACTION, (
|
||||
str(blockId),
|
||||
str(transaction.transactionId),
|
||||
str(transaction.timestamp),
|
||||
str(transaction.cardId),
|
||||
transaction.sender.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode('utf-8'),
|
||||
transaction.receiver.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode('utf-8'),
|
||||
transaction.signature.hex()
|
||||
))
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import datetime
|
||||
import flask
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
@ -21,19 +23,27 @@ privateKey = rsa.generate_private_key(
|
||||
)
|
||||
|
||||
def save_block(block):
|
||||
# Blocks are json strings. This is the true block.
|
||||
with open(f"{DATA_PATH}/{block.blockId}.json", 'w') as f:
|
||||
f.write(str(block)) # Blocks are json strings. This is the true block.
|
||||
f.write(str(block))
|
||||
# TODO: error handling (don't reveal mariadb errors)
|
||||
db.insert_block(block)
|
||||
db.insert_card(block.blockId, block.card)
|
||||
for transaction in block.transactions:
|
||||
db.delete_pending_transaction(transaction.transactionId)
|
||||
db.insert_transaction(block.blockId, transaction)
|
||||
# TODO: delete block and card if inserts fail
|
||||
# TODO update peers
|
||||
|
||||
if not db.get_last_block():
|
||||
# TODO: load blocks from files
|
||||
# TODO: try to get blocks from peers
|
||||
def generate_origin_block():
|
||||
originBlock = Block(
|
||||
difficulty = DIFFICULTY_REQUIREMENT,
|
||||
card = Card(pageId=18618509) # You can target specific cards
|
||||
)
|
||||
originBlock.mine(privateKey)
|
||||
originBlock.validate()
|
||||
save_block(originBlock)
|
||||
return originBlock
|
||||
|
||||
@mine.get('/')
|
||||
def index_get():
|
||||
@ -42,6 +52,7 @@ def index_get():
|
||||
|
||||
###
|
||||
# Retrieve blocks and block data.
|
||||
# Creates an origin block if none exists
|
||||
# Returns a skeleton block to be mined by default.
|
||||
# Queries for a specific block when given parameters.
|
||||
###
|
||||
@ -49,17 +60,16 @@ def index_get():
|
||||
def blocks_get():
|
||||
# TODO: block queries
|
||||
blockId = flask.request.args.get('blockId', None)
|
||||
lastBlock = db.get_last_block()
|
||||
if not (lastBlock := db.get_last_block()):
|
||||
# TODO: load blocks from files
|
||||
# TODO: try to get blocks from peers
|
||||
lastBlock = generate_origin_block()
|
||||
return flask.jsonify(
|
||||
Block(
|
||||
previousHash = lastBlock.blockHash.hexdigest(),
|
||||
height = lastBlock.height + 1,
|
||||
difficulty = DIFFICULTY_REQUIREMENT,
|
||||
transactions = [
|
||||
Transaction(data=each) for each in db.get_pending_transactions(
|
||||
BLOCK_TRANSACTION_LIMIT
|
||||
)
|
||||
]
|
||||
transactions = db.get_pending_transactions()
|
||||
).as_dict()
|
||||
)
|
||||
|
||||
@ -71,16 +81,16 @@ def blocks_get():
|
||||
@mine.post('/blocks')
|
||||
def blocks_post():
|
||||
try:
|
||||
newBlock = Block(data=request.get_json())
|
||||
newBlock = Block(data=flask.request.get_json())
|
||||
newBlock.validate()
|
||||
previousBlock = db.get_last_block()
|
||||
if newBlock.previousHash != previousBlock.blockHash:
|
||||
if newBlock.previousHash != previousBlock.blockHash.hexdigest():
|
||||
raise Block.Invalid(
|
||||
f"Incorrect previous hash - should be {previousBlock.blockHash}."
|
||||
f"Incorrect previous hash - should be {previousBlock.blockHash.hexdigest()}."
|
||||
)
|
||||
if newBlock.timestamp <= previousBlock.timestamp:
|
||||
raise Block.Invalid(
|
||||
"Timestamp is later than previous block."
|
||||
f"Timestamp {newBlock.timestamp} is before {previousBlock.timestamp}."
|
||||
)
|
||||
if newBlock.height != previousBlock.height + 1:
|
||||
raise Block.Invalid(
|
||||
@ -95,15 +105,18 @@ def blocks_post():
|
||||
"Block contains no transactions."
|
||||
)
|
||||
for transaction in newBlock.transactions:
|
||||
pendingTransaction = db.get_transaction(transaction.transactionId)
|
||||
if (transaction.sender == transaction.receiver
|
||||
and transaction.cardId != newBlock.card.cardId):
|
||||
raise Transaction.Invalid(
|
||||
"Recursive transactions are only allowed to collect mining reward."
|
||||
)
|
||||
pendingTransaction = db.get_pending_transaction_by_id(transaction.transactionId)
|
||||
if not pendingTransaction:
|
||||
if transaction.cardId != newBlock.card.cardId:
|
||||
raise Transaction.Invalid(
|
||||
f"No matching pending transaction for {transaction.transactionId}."
|
||||
)
|
||||
if not pendingTransaction.pending:
|
||||
raise Transaction.AlreadyFulfilled(
|
||||
f"Transaction {transaction.transactionId} has already been fulfilled."
|
||||
)
|
||||
else:
|
||||
if transaction.timestamp != pendingTransaction.timestamp:
|
||||
raise Transaction.Invalid(
|
||||
f"Incorrect timestamp on {transaction.transactionId}."
|
||||
@ -116,27 +129,22 @@ def blocks_post():
|
||||
raise Transaction.Invalid(
|
||||
f"Incorrect sender on {transaction.transactionId}."
|
||||
)
|
||||
if transaction.recipient != pendingTransaction.recipient:
|
||||
if transaction.receiver != pendingTransaction.receiver:
|
||||
raise Transaction.Invalid(
|
||||
f"Incorrect recipient on {transaction.transactionId}."
|
||||
f"Incorrect receiver on {transaction.transactionId}."
|
||||
)
|
||||
if transaction.signature != pendingTransaction.signature:
|
||||
raise Transaction.Invalid(
|
||||
f"Incorrect signature on {transaction.transactionId}."
|
||||
)
|
||||
save_block(block)
|
||||
for transaction in newBlock.transactions:
|
||||
db.update_transactions_is_pending_false(transaction.transactionId)
|
||||
# TODO: update peers
|
||||
return flask.jsonify(block.asDict())
|
||||
except Transaction.Invalid as e:
|
||||
return e, e.statusCode
|
||||
except Transaction.AlreadyFulfilled as e:
|
||||
return e, e.statusCode
|
||||
except Card.Invalid as e:
|
||||
return e, e.statusCode
|
||||
except Block.Invalid as e:
|
||||
return e, e.statusCode
|
||||
save_block(newBlock)
|
||||
return flask.jsonify(newBlock.as_dict())
|
||||
except Exception as e:
|
||||
return flask.jsonify(
|
||||
{'Error': str(e)}
|
||||
), e.statusCode if hasattr(
|
||||
e, 'statusCode'
|
||||
) else 500
|
||||
|
||||
###
|
||||
# Retrieve card data
|
||||
@ -153,29 +161,33 @@ def cards_get():
|
||||
# This method performs a number of validations on the submitted transaction and returns
|
||||
# a status code result.
|
||||
###
|
||||
@mine.put('/transactions')
|
||||
def transactions_put():
|
||||
@mine.post('/transactions')
|
||||
def transactions_post():
|
||||
try:
|
||||
newTransaction = Transaction(data=request.get_json())
|
||||
newTransaction = Transaction(data=flask.request.get_json())
|
||||
newTransaction.validate()
|
||||
if not get_card(newTransaction.cardId):
|
||||
if not db.get_card_by_id(newTransaction.cardId):
|
||||
raise Transaction.Invalid(
|
||||
f"Card {newTransaction.cardId} does not exist.",
|
||||
404
|
||||
)
|
||||
if newTransaction.sender != get_card_owner(newTransaction.cardId):
|
||||
if newTransaction.sender != db.get_card_owner(newTransaction.cardId):
|
||||
raise Transaction.Unauthorized(
|
||||
f"{newTransaction.sender} does not own {newTransaction.cardId}."
|
||||
)
|
||||
insert_transaction(newTransaction)
|
||||
if newTransaction.sender == newTransaction.receiver:
|
||||
raise Transaction.Invalid(
|
||||
"Recursive transaction are not accepted at this endpoint."
|
||||
)
|
||||
db.insert_pending_transaction(newTransaction)
|
||||
# TODO: update peers?
|
||||
return 200
|
||||
except mariadb.Error as e:
|
||||
return e, 500
|
||||
except Transaction.Unauthorized as e:
|
||||
return e, e.statusCode
|
||||
except Transaction.Invalid as e:
|
||||
return e, e.statusCode
|
||||
return flask.jsonify(newTransaction.as_dict())
|
||||
except Exception as e:
|
||||
return flask.jsonify(
|
||||
{'Error': str(e)}
|
||||
), e.statusCode if hasattr(
|
||||
e, 'statusCode'
|
||||
) else 500
|
||||
|
||||
###
|
||||
# Retrieve a transaction.
|
||||
|
@ -3,17 +3,18 @@ import io
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
|
||||
class Transaction():
|
||||
def __init__(self, transactionId=uuid.uuid4(),
|
||||
timestamp=datetime.datetime.now(datetime.timezone.utc),
|
||||
cardId=None, sender=None, receiver=None, authorPrivateKey=None, signature=None,
|
||||
data=None):
|
||||
self.transactionId = transactionId
|
||||
self.timestamp = timestamp
|
||||
def __init__(self, transactionId=None, timestamp=None, cardId=None, sender=None,
|
||||
receiver=None, authorPrivateKey=None, signature=None, data=None):
|
||||
self.transactionId = transactionId if transactionId else uuid.uuid4()
|
||||
self.timestamp = timestamp if timestamp else datetime.datetime.now(
|
||||
datetime.timezone.utc
|
||||
)
|
||||
self.cardId = cardId
|
||||
self.sender = sender
|
||||
self.receiver = receiver
|
||||
@ -29,7 +30,6 @@ class Transaction():
|
||||
# TODO: validate cardId
|
||||
# TODO: validate sender
|
||||
# TODO: validate receiver
|
||||
# TODO: validate signature
|
||||
try:
|
||||
self.sender.verify(
|
||||
self.signature,
|
||||
@ -42,7 +42,7 @@ class Transaction():
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode('utf-8')
|
||||
}).encode('utf-8')
|
||||
}, sort_keys=True).encode('utf-8')
|
||||
),
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()),
|
||||
@ -51,7 +51,7 @@ class Transaction():
|
||||
hashes.SHA256()
|
||||
)
|
||||
except Exception as e:
|
||||
raise self.InvalidSignature("Invalid signature.")
|
||||
raise self.InvalidSignature(str(e))
|
||||
|
||||
def sign(self, authorPrivateKey):
|
||||
self.signature = authorPrivateKey.sign(
|
||||
@ -64,7 +64,7 @@ class Transaction():
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode('utf-8')
|
||||
}).encode('utf-8')
|
||||
}, sort_keys=True).encode('utf-8')
|
||||
),
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()),
|
||||
@ -80,21 +80,31 @@ class Transaction():
|
||||
"%Y-%m-%d %H:%M:%S.%f%z"
|
||||
)
|
||||
self.cardId = uuid.UUID(data['cardId'])
|
||||
# TODO: load rsa keys
|
||||
self.sender = data['sender']
|
||||
self.receiver = data['receiver']
|
||||
self.signature = data['signature']
|
||||
# TODO: why is this sometimes a tuple?
|
||||
self.sender = serialization.load_pem_public_key(
|
||||
data['sender'].encode('utf-8'),
|
||||
backend=default_backend()
|
||||
),
|
||||
if isinstance(self.sender, tuple):
|
||||
self.sender = self.sender[0]
|
||||
self.receiver = serialization.load_pem_public_key(
|
||||
data['receiver'].encode('utf-8'),
|
||||
backend=default_backend()
|
||||
),
|
||||
if isinstance(self.receiver, tuple):
|
||||
self.receiver = self.receiver[0]
|
||||
self.signature = bytes.fromhex(data['signature'])
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"transactionId": str(self.transactionId),
|
||||
"timestamp": str(self.timestamp),
|
||||
"cardId": str(self.cardId),
|
||||
"receiver": self.receiver.public_bytes(
|
||||
"sender": self.sender.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode('utf-8'),
|
||||
"sender": self.sender.public_bytes(
|
||||
"receiver": self.receiver.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
).decode('utf-8'),
|
||||
@ -102,29 +112,34 @@ class Transaction():
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps(self.as_dict())
|
||||
return json.dumps(self.as_dict(), sort_keys=True)
|
||||
|
||||
class Unauthorized(Exception):
|
||||
def __init__(self, message, statusCode=403):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.statusCode = statusCode
|
||||
|
||||
class Invalid(Exception):
|
||||
def __init__(self, message, statusCode=400):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.statusCode = statusCode
|
||||
|
||||
class AlreadyFulfilled(Exception):
|
||||
def __init__(self, message, statusCode=400):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.statusCode = statusCode
|
||||
|
||||
class Abandoned(Exception):
|
||||
def __init__(self, message, statusCode=500):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.statusCode = statusCode
|
||||
|
||||
class InvalidSignature(Exception):
|
||||
def __init__(self, message, statusCode=400):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.statusCode = statusCode
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -5,19 +5,48 @@ import requests
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
from Mine.Block import Block
|
||||
from Mine.Transaction import Transaction
|
||||
|
||||
WIKIDECK_URL = os.getenv('WIKIDECK_URL', 'http://localhost:8080/')
|
||||
|
||||
privateKey = rsa.generate_private_key(
|
||||
print("Generating RSA keys...")
|
||||
privateKeyA = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=4096
|
||||
)
|
||||
privateKeyB = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=4096
|
||||
)
|
||||
|
||||
newBlock = Block(
|
||||
print("Getting block to mine from server...")
|
||||
newBlockA = Block(
|
||||
data = requests.get(f"{WIKIDECK_URL}/mine/blocks").json()
|
||||
)
|
||||
newBlock.mine(privateKey)
|
||||
print(newBlock)
|
||||
print("Received block for user A to mine.")
|
||||
print("Mining block...")
|
||||
newBlockA.mine(privateKeyA)
|
||||
r = requests.post(f"{WIKIDECK_URL}/mine/blocks", json=newBlockA.as_dict())
|
||||
print(json.dumps(r.json(), indent=4))
|
||||
input("Press enter to continue...")
|
||||
|
||||
r = requests.post(f"{WIKIDECK_URL}/mine/blocks", data=newBlock.as_dict())
|
||||
print(r)
|
||||
print("Sending card A to user B...")
|
||||
newTransaction = Transaction(
|
||||
cardId = newBlockA.card.cardId,
|
||||
sender = privateKeyA.public_key(),
|
||||
receiver = privateKeyB.public_key(),
|
||||
authorPrivateKey = privateKeyA
|
||||
)
|
||||
r = requests.post(f"{WIKIDECK_URL}/mine/transactions", json=newTransaction.as_dict())
|
||||
print(json.dumps(r.json(), indent=4))
|
||||
input("Press enter to continue...")
|
||||
|
||||
print("Getting block to mine from server...")
|
||||
newBlockB = Block(
|
||||
data = requests.get(f"{WIKIDECK_URL}/mine/blocks").json()
|
||||
)
|
||||
print("Received block for user B to mine.")
|
||||
print("Mining block...")
|
||||
newBlockB.mine(privateKeyB)
|
||||
r = requests.post(f"{WIKIDECK_URL}/mine/blocks", json=newBlockB.as_dict())
|
||||
print(json.dumps(r.json(), indent=4))
|
||||
|
Loading…
Reference in New Issue
Block a user