461 lines
16 KiB
Python
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 []
|