wikideck/wikideck/Mine/Database.py

461 lines
16 KiB
Python

import base64
import datetime
import logging
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_TABLES = [
"""
CREATE TABLE IF NOT EXISTS mine.blocks(
sqlId INT PRIMARY KEY AUTO_INCREMENT,
blockId UUID UNIQUE NOT NULL,
blockHash VARCHAR(64) UNIQUE NOT NULL,
previousHash VARCHAR(64) UNIQUE 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 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 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)
);
""",
"""
CREATE TABLE IF NOT EXISTS mine.peers(
sqlId INT PRIMARY KEY AUTO_INCREMENT,
peerId UUID UNIQUE NOT NULL,
baseUrl VARCHAR(128) UNIQUE NOT NULL,
isUp BOOLEAN NOT NULL,
downCount INT DEFAULT 0,
lastTry TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
"""
]
SQL_GET_BLOCK_BY_ID = """
SELECT *
FROM blocks
WHERE blockId = ?;
"""
SQL_GET_CARD_BY_ID = """
SELECT cardId, pageId
FROM cards
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 = ?
ORDER BY timestamp DESC
LIMIT 1;
"""
SQL_INSERT_BLOCK = """
INSERT INTO mine.blocks (
blockId, blockHash, previousHash, timestamp, height, difficulty, nonce
)
VALUES (?, ?, ?, ?, ?, ?, ?);
"""
SQL_GET_BLOCKS = """
SELECT blockId, previousHash, timestamp, height, difficulty, nonce
FROM mine.blocks
ORDER BY height ASC;
"""
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 mine.transactions (
blockId, transactionId, timestamp, cardId, sender, receiver, signature
)
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 ASC;
"""
SQL_GET_DECK = """
SELECT cards.cardId, cards.pageId
FROM (
SELECT transactionId, cardId, timestamp, receiver, ROW_NUMBER() OVER (
PARTITION BY transactions.cardId
ORDER BY transactions.timestamp DESC
) AS rownumber
FROM transactions
) AS subquery
JOIN cards ON cards.cardId = subquery.cardId
WHERE subquery.rownumber = 1
AND receiver = ?;
"""
def __init__(self):
logging.basicConfig(filename=os.getenv('MINE_LOG_FILE', '/var/log/mine.log'), level=logging.INFO)
self.logger = logging.getLogger(__name__)
delay = 2
while True:
try:
self.conn = mariadb.connect(
host = os.getenv('MINE_DB_HOST', "mariadb"),
port = os.getenv('MINE_DB_PORT', 3306),
user = os.getenv('MINE_DB_USER', None),
password = os.getenv('MINE_DB_PASSWORD', None),
database = 'mine',
autocommit = True
)
for each in self.SQL_CREATE_TABLES:
self.conn.cursor().execute(each)
except mariadb.Error as e:
self.logger.error(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(
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 None
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]
) if card else None
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]
) if card else None
def get_blocks(self):
cur = self.conn.cursor()
cur.execute(self.SQL_GET_LAST_BLOCK)
blocks = cur.fetchall()
if blocks:
blockCard = self.get_card_by_block_id(lastBlock[0])
blockTransactions = self.get_transactions_by_block_id(lastBlock[0])
return [
Block(
blockId = uuid.UUID(block[0]),
previousHash = block[1],
timestamp = datetime.datetime.strptime(
block[2],
"%Y-%m-%d %H:%M:%S.%f%z"
),
height = block[3],
difficulty = block[4],
nonce = block[5],
card = blockCard,
transactions = blockTransactions
)
]
else:
return None
def get_last_block(self):
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):
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):
cur = self.conn.cursor()
cur.execute(
self.SQL_GET_PENDING_TRANSACTIONS,
[limit]
)
pendingTransactions = cur.fetchall()
return [
Transaction(
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_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(
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):
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_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),
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()
))
def get_deck(self, publicKey):
cur = self.conn.cursor()
cur.execute(
self.SQL_GET_DECK,
[publicKey.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8')]
)
deck = cur.fetchall()
return [Card(
cardId = card[0],
pageId = card[1]
) for card in deck] if deck else []