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