Unit testing basic workflows
This commit is contained in:
parent
1c31de2bb4
commit
05fc8f9a46
@ -1,51 +0,0 @@
|
|||||||
CREATE DATABASE IF NOT EXISTS market;
|
|
||||||
USE market;
|
|
||||||
CREATE TABLE IF NOT EXISTS users(
|
|
||||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
userId VARCHAR(36) UNIQUE NOT NULL,
|
|
||||||
userName VARCHAR(64) UNIQUE NOT NULL,
|
|
||||||
passwordHash VARCHAR(64) NOT NULL,
|
|
||||||
email VARCHAR(64) UNIQUE NOT NULL,
|
|
||||||
balance FLOAT NOT NULL DEFAULT 0,
|
|
||||||
publicKey VARCHAR(128) NOT NULL
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS buy_orders(
|
|
||||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
buyOrderId VARCHAR(36) UNIQUE NOT NULL,
|
|
||||||
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
|
|
||||||
expires DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP() + INTERVAL 3 DAY,
|
|
||||||
pageId INT NOT NULL,
|
|
||||||
price FLOAT NOT NULL,
|
|
||||||
volume INT NOT NULL,
|
|
||||||
fee FLOAT NOT NULL DEFAULT 0,
|
|
||||||
sold INT NOT NULL DEFAULT 0,
|
|
||||||
FOREIGN KEY (userId) REFERENCES users(userId)
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS sell_orders(
|
|
||||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
sellOrderId VARCHAR(36) UNIQUE NOT NULL,
|
|
||||||
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
|
|
||||||
expires DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP() + INTERVAL 3 DAY,
|
|
||||||
price FLOAT NOT NULL,
|
|
||||||
fee FLOAT NOT NULL,
|
|
||||||
FOREIGN KEY (userId) REFERENCES users(userId)
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS sell_orders_items(
|
|
||||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
cardId VARCHAR(36) NOT NULL,
|
|
||||||
FOREIGN KEY (sellOrderId) REFERENCES sell_orders(sellOrderId) NOT NULL
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS transactions(
|
|
||||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
transactionId VARCHAR(36) UNIQUE NOT NULL,
|
|
||||||
isPending BOOLEAN NOT NULL DEFAULT true,
|
|
||||||
isAbandoned BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
FOREIGN KEY (buyOrderId) REFERENCES buy_orders(buyOrderId) NOT NULL,
|
|
||||||
FOREIGN KEY (sellOrderItemSqlId) REFERENCES sell_order_items(sqlId) NOT NULL
|
|
||||||
);
|
|
||||||
CREATE USER IF NOT EXISTS '${MARKET_DB_USER}'@'%' IDENTIFIED BY '${MARKET_DB_PASSWORD}';
|
|
||||||
CREATE USER IF NOT EXISTS '${ORDER_MATCHER_DB_USER}'@'%' IDENTIFIED BY '${ORDER_MATCHER_DB_PASSWORD}';
|
|
||||||
CREATE USER IF NOT EXISTS '${STATUS_CHECKER_DB_USER}'@'%' IDENTIFIED BY '${STATUS_CHECKER_DB_PASSWORD}';
|
|
||||||
GRANT ALL PRIVILEGES ON market.* TO '${MARKET_DB_USER}'@'%';
|
|
||||||
GRANT ALL PRIVILEGES ON market.* TO '${ORDER_MATCHER_DB_USER}'@'%';
|
|
||||||
GRANT ALL PRIVILEGES ON market.* TO '${STATUS_CHECKER_DB_USER}'@'%';
|
|
@ -1,12 +0,0 @@
|
|||||||
SELECT buy_order_id
|
|
||||||
FROM buy_orders
|
|
||||||
JOIN cards
|
|
||||||
ON buy_orders.title = cards.title
|
|
||||||
WHERE title == desired_title
|
|
||||||
AND price >= desired_price
|
|
||||||
AND volume > (
|
|
||||||
SELECT count(*)
|
|
||||||
FROM transactions
|
|
||||||
WHERE buy_order_id == buy_order_id;
|
|
||||||
)
|
|
||||||
ORDER BY price DESC;
|
|
@ -1,10 +0,0 @@
|
|||||||
SELECT cardId, pageId, transactionId
|
|
||||||
FROM (
|
|
||||||
SELECT cardId, pageId, transactionId, ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY cardId ORDER BY timestamp DESC
|
|
||||||
) AS rowNumber
|
|
||||||
FROM transactions
|
|
||||||
JOIN cards
|
|
||||||
WHERE receiver == {userId}
|
|
||||||
) AS subquery
|
|
||||||
WHERE rowNumber == 0;
|
|
@ -1,11 +0,0 @@
|
|||||||
SELECT sell_orders.sell_order_id, sell_order_items.card_id
|
|
||||||
FROM sell_orders
|
|
||||||
JOIN sell_order_items
|
|
||||||
ON sell_orders.sell_order_id = sell_order_items.sell_order_id
|
|
||||||
WHERE price <= desired_price
|
|
||||||
AND sell_order_items.card_id NOT IN (
|
|
||||||
SELECT card_id
|
|
||||||
FROM transactions
|
|
||||||
WHERE sell_order_id == sell_order_id;
|
|
||||||
)
|
|
||||||
ORDER BY price ASC;
|
|
@ -1,38 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS blocks(
|
|
||||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
blockId VARCHAR(32) UNIQUE NOT NULL,
|
|
||||||
blockHash VARCHAR(64) UNIQUE NOT NULL,
|
|
||||||
previousHash VARCHAR(64) UNIQUE NOT NULL,
|
|
||||||
timestamp DATETIME NOT NULL,
|
|
||||||
height INT UNIQUE NOT NULL,
|
|
||||||
nonce INT NOT NULL
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS cards(
|
|
||||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
blockId VARCHAR(32) UNIQUE NOT NULL,
|
|
||||||
cardId VARCHAR(32) UNIQUE NOT NULL,
|
|
||||||
pageId INT NOT NULL,
|
|
||||||
FOREIGN KEY (blockId) REFERENCES blocks(blockId)
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS transactions(
|
|
||||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
blockId VARCHAR(32) NOT NULL,
|
|
||||||
cardId VARCHAR(32) UNIQUE NOT NULL,
|
|
||||||
transactionId VARCHAR(32) 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,
|
|
||||||
FOREIGN KEY (blockId) REFERENCES blocks(blockId),
|
|
||||||
FOREIGN KEY (cardId) REFERENCES cards(cardId)
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS peers(
|
|
||||||
sqlId INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
peerId VARCHAR(32) 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
|
|
||||||
);
|
|
@ -1,15 +1,15 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
wikideck:
|
wikideck-mine:
|
||||||
image: ericomeehan/wikideck:latest
|
image: ericomeehan/wikideck:latest
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
environment:
|
environment:
|
||||||
ROLE: api
|
ROLE: mine
|
||||||
DATA_PATH: /tmp
|
DATA_PATH: /tmp
|
||||||
DIFFICULTY_REQUIREMENT: 3
|
DIFFICULTY_REQUIREMENT: 10
|
||||||
MINE_DB_HOST: mariadb-mine
|
MINE_DB_HOST: mariadb-mine
|
||||||
MINE_DB_USER: mine
|
MINE_DB_USER: mine
|
||||||
MINE_DB_PASSWORD: 123abc
|
MINE_DB_PASSWORD: 123abc
|
||||||
|
261
test/Mine/TestMine.py
Normal file
261
test/Mine/TestMine.py
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'wikideck')))
|
||||||
|
|
||||||
|
from Mine.Block import Block
|
||||||
|
from Mine.Transaction import Transaction
|
||||||
|
|
||||||
|
WIKIDECK_URL = os.getenv('WIKIDECK_URL', 'http://localhost:8080/')
|
||||||
|
|
||||||
|
class TestMine(unittest.TestCase):
|
||||||
|
def test_mine_block_send_card_mine_another_block(self):
|
||||||
|
# Create two users
|
||||||
|
privateKeyA = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=4096
|
||||||
|
)
|
||||||
|
privateKeyB = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=4096
|
||||||
|
)
|
||||||
|
# Mine a block with privateKeyA
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/blocks")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
newBlockA = Block(
|
||||||
|
data = response.json()
|
||||||
|
)
|
||||||
|
newBlockA.mine(privateKeyA)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/blocks", json=newBlockA.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Send card from newBlockA to privateKeyB
|
||||||
|
newTransaction = Transaction(
|
||||||
|
cardId = newBlockA.card.cardId,
|
||||||
|
sender = privateKeyA.public_key(),
|
||||||
|
receiver = privateKeyB.public_key(),
|
||||||
|
authorPrivateKey = privateKeyA
|
||||||
|
)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/transactions", json=newTransaction.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Mine a block with privateKeyB
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/blocks")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
newBlockB = Block(
|
||||||
|
data = response.json()
|
||||||
|
)
|
||||||
|
newBlockB.mine(privateKeyB)
|
||||||
|
self.assertEqual(newBlockA.blockHash.hexdigest(), newBlockB.previousHash)
|
||||||
|
self.assertTrue(newBlockA.timestamp < newBlockB.timestamp)
|
||||||
|
self.assertTrue(newBlockB.height == newBlockA.height + 1)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/blocks", json=newBlockB.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Validate decks
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/cards", params={
|
||||||
|
'publicKey': privateKeyA.public_key().public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8')
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.json()), 0)
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/cards", params={
|
||||||
|
'publicKey': privateKeyB.public_key().public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8')
|
||||||
|
})
|
||||||
|
self.assertEqual(response.json()[0]['cardId'], str(newBlockA.card.cardId))
|
||||||
|
self.assertEqual(response.json()[0]['pageId'], newBlockA.card.pageId)
|
||||||
|
self.assertEqual(response.json()[1]['cardId'], str(newBlockB.card.cardId))
|
||||||
|
self.assertEqual(response.json()[1]['pageId'], newBlockB.card.pageId)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.json()), 2)
|
||||||
|
|
||||||
|
def test_invalid_block_modified_transaction(self):
|
||||||
|
# Create two users
|
||||||
|
privateKeyA = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=4096
|
||||||
|
)
|
||||||
|
privateKeyB = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=4096
|
||||||
|
)
|
||||||
|
# Mine a block with privateKeyA
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/blocks")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
newBlockA = Block(
|
||||||
|
data = response.json()
|
||||||
|
)
|
||||||
|
newBlockA.mine(privateKeyA)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/blocks", json=newBlockA.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Send card from newBlockA to privateKeyB
|
||||||
|
newTransaction = Transaction(
|
||||||
|
cardId = newBlockA.card.cardId,
|
||||||
|
sender = privateKeyA.public_key(),
|
||||||
|
receiver = privateKeyB.public_key(),
|
||||||
|
authorPrivateKey = privateKeyA
|
||||||
|
)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/transactions", json=newTransaction.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Mine a block with privateKeyB
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/blocks")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
newBlockB = Block(
|
||||||
|
data = response.json()
|
||||||
|
)
|
||||||
|
# Modify transactions
|
||||||
|
newBlockB.transactions[0].sender = privateKeyB.public_key()
|
||||||
|
newBlockB.transactions[0].receiver = privateKeyA.public_key()
|
||||||
|
newBlockB.mine(privateKeyB)
|
||||||
|
self.assertEqual(newBlockA.blockHash.hexdigest(), newBlockB.previousHash)
|
||||||
|
self.assertTrue(newBlockA.timestamp < newBlockB.timestamp)
|
||||||
|
self.assertTrue(newBlockB.height == newBlockA.height + 1)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/blocks", json=newBlockB.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
# Validate decks
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/cards", params={
|
||||||
|
'publicKey': privateKeyA.public_key().public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8')
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.json()), 1)
|
||||||
|
self.assertEqual(response.json()[0]['cardId'], str(newBlockA.card.cardId))
|
||||||
|
self.assertEqual(response.json()[0]['pageId'], newBlockA.card.pageId)
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/cards", params={
|
||||||
|
'publicKey': privateKeyB.public_key().public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8')
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.json()), 0)
|
||||||
|
# Mine a block with privateKeyB
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/blocks")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
newBlockB = Block(
|
||||||
|
data = response.json()
|
||||||
|
)
|
||||||
|
newBlockB.mine(privateKeyB)
|
||||||
|
self.assertEqual(newBlockA.blockHash.hexdigest(), newBlockB.previousHash)
|
||||||
|
self.assertTrue(newBlockA.timestamp < newBlockB.timestamp)
|
||||||
|
self.assertTrue(newBlockB.height == newBlockA.height + 1)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/blocks", json=newBlockB.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Validate decks
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/cards", params={
|
||||||
|
'publicKey': privateKeyA.public_key().public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8')
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.json()), 0)
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/cards", params={
|
||||||
|
'publicKey': privateKeyB.public_key().public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8')
|
||||||
|
})
|
||||||
|
self.assertEqual(response.json()[0]['cardId'], str(newBlockA.card.cardId))
|
||||||
|
self.assertEqual(response.json()[0]['pageId'], newBlockA.card.pageId)
|
||||||
|
self.assertEqual(response.json()[1]['cardId'], str(newBlockB.card.cardId))
|
||||||
|
self.assertEqual(response.json()[1]['pageId'], newBlockB.card.pageId)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.json()), 2)
|
||||||
|
|
||||||
|
def test_invalid_transaction(self):
|
||||||
|
# Create three users
|
||||||
|
privateKeyA = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=4096
|
||||||
|
)
|
||||||
|
privateKeyB = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=4096
|
||||||
|
)
|
||||||
|
privateKeyC = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=4096
|
||||||
|
)
|
||||||
|
# Mine a block with privateKeyA
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/blocks")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
newBlockA = Block(
|
||||||
|
data = response.json()
|
||||||
|
)
|
||||||
|
newBlockA.mine(privateKeyA)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/blocks", json=newBlockA.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Send card from newBlockA to privateKeyB
|
||||||
|
newTransaction = Transaction(
|
||||||
|
cardId = newBlockA.card.cardId,
|
||||||
|
sender = privateKeyA.public_key(),
|
||||||
|
receiver = privateKeyB.public_key(),
|
||||||
|
authorPrivateKey = privateKeyA
|
||||||
|
)
|
||||||
|
# Invalidate transaction
|
||||||
|
newTransaction.cardId = uuid.uuid4()
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/transactions", json=newTransaction.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
newTransaction.cardId = newBlockA.card.cardId
|
||||||
|
newTransaction.sender = privateKeyC.public_key()
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/transactions", json=newTransaction.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
newTransaction.sender = privateKeyA.public_key()
|
||||||
|
newTransaction.receiver = privateKeyC.public_key()
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/transactions", json=newTransaction.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
newTransaction.receiver = privateKeyB.public_key()
|
||||||
|
newTransaction.sign(privateKeyC)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/transactions", json=newTransaction.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
newTransaction.sign(privateKeyA)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/transactions", json=newTransaction.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Mine a block with privateKeyB
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/blocks")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
newBlockB = Block(
|
||||||
|
data = response.json()
|
||||||
|
)
|
||||||
|
newBlockB.mine(privateKeyB)
|
||||||
|
self.assertEqual(newBlockA.blockHash.hexdigest(), newBlockB.previousHash)
|
||||||
|
self.assertTrue(newBlockA.timestamp < newBlockB.timestamp)
|
||||||
|
self.assertTrue(newBlockB.height == newBlockA.height + 1)
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/blocks", json=newBlockB.as_dict())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Validate decks
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/cards", params={
|
||||||
|
'publicKey': privateKeyA.public_key().public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8')
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.json()), 0)
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/cards", params={
|
||||||
|
'publicKey': privateKeyB.public_key().public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8')
|
||||||
|
})
|
||||||
|
self.assertEqual(response.json()[0]['cardId'], str(newBlockA.card.cardId))
|
||||||
|
self.assertEqual(response.json()[0]['pageId'], newBlockA.card.pageId)
|
||||||
|
self.assertEqual(response.json()[1]['cardId'], str(newBlockB.card.cardId))
|
||||||
|
self.assertEqual(response.json()[1]['pageId'], newBlockB.card.pageId)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.json()), 2)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
10
test/test.py
Normal file
10
test/test.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
||||||
|
|
||||||
|
from Mine.TestMine import TestMine
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -14,6 +14,7 @@ class Block():
|
|||||||
if data:
|
if data:
|
||||||
self.load_from_data(data)
|
self.load_from_data(data)
|
||||||
else:
|
else:
|
||||||
|
delay = 2
|
||||||
self.blockId = blockId if blockId else uuid.uuid4()
|
self.blockId = blockId if blockId else uuid.uuid4()
|
||||||
self.previousHash = previousHash
|
self.previousHash = previousHash
|
||||||
self.timestamp = timestamp if timestamp else datetime.datetime.now(
|
self.timestamp = timestamp if timestamp else datetime.datetime.now(
|
||||||
@ -22,7 +23,13 @@ class Block():
|
|||||||
self.height = height
|
self.height = height
|
||||||
self.difficulty = difficulty
|
self.difficulty = difficulty
|
||||||
self.nonce = nonce
|
self.nonce = nonce
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
self.card = card if card else Card()
|
self.card = card if card else Card()
|
||||||
|
except ReadTimeout as e:
|
||||||
|
time.sleep(delay := delay**2)
|
||||||
|
continue
|
||||||
|
break
|
||||||
self.transactions = transactions
|
self.transactions = transactions
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
@ -51,19 +58,40 @@ class Block():
|
|||||||
# respective schemas.
|
# respective schemas.
|
||||||
###
|
###
|
||||||
def validate(self):
|
def validate(self):
|
||||||
# TODO: validate blockId is uuid
|
if not isinstance(self.blockId, uuid.UUID):
|
||||||
# TODO: validate previousHash is sha256 hash
|
raise TypeError(f"Block ID should be a UUID not {type(self.blockId).__name__}.")
|
||||||
|
if not self.blockId.version == 4:
|
||||||
|
raise self.Invalid(f"Block ID version should be 4 not {self.blockId.version}.")
|
||||||
|
if not isinstance(self.previousHash, str):
|
||||||
|
raise TypeError(
|
||||||
|
f"Previous hash should be string not {type(self.previousHash).__name__}."
|
||||||
|
)
|
||||||
if not int(self.blockHash.hexdigest(), 16) <= 2**(256-self.difficulty):
|
if not int(self.blockHash.hexdigest(), 16) <= 2**(256-self.difficulty):
|
||||||
raise self.Invalid("Hash does not meet difficulty requirement.")
|
raise self.Invalid("Hash does not meet difficulty requirement.")
|
||||||
# TODO: validate timestamp is UTC timestamp
|
if not isinstance(self.timestamp, datetime.datetime):
|
||||||
|
raise TypeError(
|
||||||
|
f"Timestamp should be a datetime not {type(self.timestamp).__name__}"
|
||||||
|
)
|
||||||
|
if not self.timestamp.tzinfo == datetime.timezone.utc:
|
||||||
|
raise self.Invalid(
|
||||||
|
f"Timestamp timezone should be in UTC not {self.timestamp.tzinfo}."
|
||||||
|
)
|
||||||
if not self.timestamp < datetime.datetime.now(datetime.timezone.utc):
|
if not self.timestamp < datetime.datetime.now(datetime.timezone.utc):
|
||||||
raise self.Invalid("Timestamp in the future.")
|
raise self.Invalid(f"Timestamp {self.timestamp} in the future.")
|
||||||
|
if not isinstance(self.height, int):
|
||||||
|
raise TypeError(f"Height should be integer not {type(self.height).__name__}.")
|
||||||
if not self.height >= 0:
|
if not self.height >= 0:
|
||||||
raise self.Invalid("Height less than 0.")
|
raise self.Invalid(f"Height {self.height} less than 0.")
|
||||||
|
if not isinstance(self.difficulty, int):
|
||||||
|
raise TypeError(
|
||||||
|
f"Difficulty should be an integer not {type(self.difficulty).__name__}."
|
||||||
|
)
|
||||||
if not self.difficulty >= 0:
|
if not self.difficulty >= 0:
|
||||||
raise self.Invalid("Difficulty less than 0.")
|
raise self.Invalid(f"Difficulty {self.difficulty} less than 0.")
|
||||||
|
if not isinstance(self.nonce, int):
|
||||||
|
raise TypeError(f"Nonce should be an integer not {type(self.nonce).__name__}.")
|
||||||
if not self.nonce >= 0:
|
if not self.nonce >= 0:
|
||||||
raise self.Invalid("Nonce less than 0.")
|
raise self.Invalid(f"Nonce {self.nonce} less than 0.")
|
||||||
self.card.validate()
|
self.card.validate()
|
||||||
seenTransactions = []
|
seenTransactions = []
|
||||||
for transaction in self.transactions:
|
for transaction in self.transactions:
|
||||||
|
@ -30,11 +30,17 @@ class Card():
|
|||||||
self.load_from_data(data)
|
self.load_from_data(data)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
if not isinstance(self.cardId, uuid.UUID):
|
||||||
|
raise TypeError(f"Card ID should be a UUID not {type(self.cardId).__name__}.")
|
||||||
|
if not self.cardId.version == 4:
|
||||||
|
raise self.Invalid(f"Card ID version should be 4 not {self.cardId.version}.")
|
||||||
|
if not isinstance(self.pageId, int):
|
||||||
|
raise TypeError(f"Page ID should be an integer not {type(self.pageId).__name__}.")
|
||||||
try:
|
try:
|
||||||
# TODO: cardId is UUID
|
|
||||||
wikipedia.page(pageid=self.pageId)
|
wikipedia.page(pageid=self.pageId)
|
||||||
|
# TODO: may need more precision here.
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise self.Invalid("Page ID does not match a Wikipedia page.")
|
raise self.Invalid(f"Page ID {self.pageId} does not match a Wikipedia page.")
|
||||||
|
|
||||||
def load_from_data(self, data):
|
def load_from_data(self, data):
|
||||||
self.cardId = uuid.UUID(data['cardId'])
|
self.cardId = uuid.UUID(data['cardId'])
|
||||||
|
@ -105,6 +105,12 @@ class Database():
|
|||||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
SQL_GET_BLOCKS = """
|
||||||
|
SELECT blockId, previousHash, timestamp, height, difficulty, nonce
|
||||||
|
FROM mine.blocks
|
||||||
|
ORDER BY height ASC;
|
||||||
|
"""
|
||||||
|
|
||||||
SQL_GET_LAST_BLOCK = """
|
SQL_GET_LAST_BLOCK = """
|
||||||
SELECT blockId, previousHash, timestamp, height, difficulty, nonce
|
SELECT blockId, previousHash, timestamp, height, difficulty, nonce
|
||||||
FROM mine.blocks
|
FROM mine.blocks
|
||||||
@ -157,7 +163,21 @@ class Database():
|
|||||||
SELECT transactionId, timestamp, cardId, sender, receiver, signature
|
SELECT transactionId, timestamp, cardId, sender, receiver, signature
|
||||||
FROM mine.transactions
|
FROM mine.transactions
|
||||||
WHERE blockId = ?
|
WHERE blockId = ?
|
||||||
ORDER BY timestamp DESC;
|
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):
|
def __init__(self):
|
||||||
@ -197,7 +217,7 @@ class Database():
|
|||||||
transactions = blockTransactions
|
transactions = blockTransactions
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return False
|
return None
|
||||||
|
|
||||||
def get_card_by_id(self, cardId):
|
def get_card_by_id(self, cardId):
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
@ -206,7 +226,7 @@ class Database():
|
|||||||
return Card(
|
return Card(
|
||||||
cardId = uuid.UUID(card[0]),
|
cardId = uuid.UUID(card[0]),
|
||||||
pageId = card[1]
|
pageId = card[1]
|
||||||
)
|
) if card else None
|
||||||
|
|
||||||
def get_card_by_block_id(self, blockId):
|
def get_card_by_block_id(self, blockId):
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
@ -215,7 +235,32 @@ class Database():
|
|||||||
return Card(
|
return Card(
|
||||||
cardId = uuid.UUID(card[0]),
|
cardId = uuid.UUID(card[0]),
|
||||||
pageId = card[1]
|
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):
|
def get_last_block(self):
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
@ -395,3 +440,17 @@ class Database():
|
|||||||
transaction.signature.hex()
|
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 []
|
||||||
|
@ -4,6 +4,8 @@ import json
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
|
||||||
from Mine.Block import Block
|
from Mine.Block import Block
|
||||||
@ -11,7 +13,7 @@ from Mine.Card import Card
|
|||||||
from Mine.Database import Database
|
from Mine.Database import Database
|
||||||
from Mine.Transaction import Transaction
|
from Mine.Transaction import Transaction
|
||||||
|
|
||||||
BLOCK_TRANSACTION_LIMIT = int(os.getenv('BLOCK_TRANSACTION_LIMIT', 32768))
|
BLOCK_TRANSACTION_LIMIT = int(os.getenv('BLOCK_TRANSACTION_LIMIT', 1024))
|
||||||
DATA_PATH = os.getenv('DATA_PATH', '/var/lib/wikideck/blocks')
|
DATA_PATH = os.getenv('DATA_PATH', '/var/lib/wikideck/blocks')
|
||||||
DIFFICULTY_REQUIREMENT = int(os.getenv('DIFFICULTY_REQUIREMENT', 0))
|
DIFFICULTY_REQUIREMENT = int(os.getenv('DIFFICULTY_REQUIREMENT', 0))
|
||||||
|
|
||||||
@ -23,16 +25,16 @@ 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:
|
|
||||||
f.write(str(block))
|
|
||||||
# TODO: error handling (don't reveal mariadb errors)
|
# TODO: error handling (don't reveal mariadb errors)
|
||||||
|
# TODO: disable autocommit
|
||||||
db.insert_block(block)
|
db.insert_block(block)
|
||||||
db.insert_card(block.blockId, block.card)
|
db.insert_card(block.blockId, block.card)
|
||||||
for transaction in block.transactions:
|
for transaction in block.transactions:
|
||||||
db.delete_pending_transaction(transaction.transactionId)
|
db.delete_pending_transaction(transaction.transactionId)
|
||||||
db.insert_transaction(block.blockId, transaction)
|
db.insert_transaction(block.blockId, transaction)
|
||||||
# TODO: delete block and card if inserts fail
|
# Blocks are json strings. This is the true block.
|
||||||
|
with open(f"{DATA_PATH}/{block.blockId}.json", 'w') as f:
|
||||||
|
f.write(str(block))
|
||||||
# TODO update peers
|
# TODO update peers
|
||||||
|
|
||||||
def generate_origin_block():
|
def generate_origin_block():
|
||||||
@ -47,8 +49,14 @@ def generate_origin_block():
|
|||||||
|
|
||||||
@mine.get('/')
|
@mine.get('/')
|
||||||
def index_get():
|
def index_get():
|
||||||
# TODO: return a page to mine through the browser
|
try:
|
||||||
return "Hello world!", 200
|
return jsonify([each.as_dict() for each in db.get_blocks()])
|
||||||
|
except Exception as e:
|
||||||
|
return flask.jsonify(
|
||||||
|
{'Error': str(e)}
|
||||||
|
), e.statusCode if hasattr(
|
||||||
|
e, 'statusCode'
|
||||||
|
) else 500
|
||||||
|
|
||||||
###
|
###
|
||||||
# Retrieve blocks and block data.
|
# Retrieve blocks and block data.
|
||||||
@ -86,7 +94,7 @@ def blocks_post():
|
|||||||
previousBlock = db.get_last_block()
|
previousBlock = db.get_last_block()
|
||||||
if newBlock.previousHash != previousBlock.blockHash.hexdigest():
|
if newBlock.previousHash != previousBlock.blockHash.hexdigest():
|
||||||
raise Block.Invalid(
|
raise Block.Invalid(
|
||||||
f"Incorrect previous hash - should be {previousBlock.blockHash.hexdigest()}."
|
f"Previous hash should be {previousBlock.blockHash.hexdigest()}."
|
||||||
)
|
)
|
||||||
if newBlock.timestamp <= previousBlock.timestamp:
|
if newBlock.timestamp <= previousBlock.timestamp:
|
||||||
raise Block.Invalid(
|
raise Block.Invalid(
|
||||||
@ -94,11 +102,11 @@ def blocks_post():
|
|||||||
)
|
)
|
||||||
if newBlock.height != previousBlock.height + 1:
|
if newBlock.height != previousBlock.height + 1:
|
||||||
raise Block.Invalid(
|
raise Block.Invalid(
|
||||||
f"Incorrect block height - should be {previousBlock.height + 1}."
|
f"Height should be {previousBlock.height + 1} not {newBlock.height}."
|
||||||
)
|
)
|
||||||
if newBlock.difficulty < DIFFICULTY_REQUIREMENT:
|
if newBlock.difficulty < DIFFICULTY_REQUIREMENT:
|
||||||
raise Block.Invalid(
|
raise Block.Invalid(
|
||||||
f"Incorrect difficulty - should be {DIFFICULTY_REQUIREMENT}."
|
f"Difficulty should be {DIFFICULTY_REQUIREMENT} not {newBlock.difficulty}."
|
||||||
)
|
)
|
||||||
if len(newBlock.transactions) == 0:
|
if len(newBlock.transactions) == 0:
|
||||||
raise Block.Invalid(
|
raise Block.Invalid(
|
||||||
@ -151,10 +159,25 @@ def blocks_post():
|
|||||||
###
|
###
|
||||||
@mine.get('/cards')
|
@mine.get('/cards')
|
||||||
def cards_get():
|
def cards_get():
|
||||||
|
try:
|
||||||
# TODO: render cards in html
|
# TODO: render cards in html
|
||||||
# TODO: query cards
|
# TODO: query cards
|
||||||
# TODO: get decks
|
publicKeyString = flask.request.args.get('publicKey', None)
|
||||||
return 200
|
if publicKeyString:
|
||||||
|
deck = db.get_deck(
|
||||||
|
serialization.load_pem_public_key(
|
||||||
|
publicKeyString.encode('utf-8'),
|
||||||
|
backend=default_backend()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return flask.jsonify([card.as_dict() for card in deck] if deck else [])
|
||||||
|
except Exception as e:
|
||||||
|
return flask.jsonify(
|
||||||
|
{'Error': str(e)}
|
||||||
|
), e.statusCode if hasattr(
|
||||||
|
e, 'statusCode'
|
||||||
|
) else 500
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Submit a transaction to be mined in a block.
|
# Submit a transaction to be mined in a block.
|
||||||
|
@ -6,7 +6,7 @@ import uuid
|
|||||||
from cryptography.hazmat.backends import default_backend
|
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, rsa
|
||||||
|
|
||||||
class Transaction():
|
class Transaction():
|
||||||
def __init__(self, transactionId=None, timestamp=None, cardId=None, sender=None,
|
def __init__(self, transactionId=None, timestamp=None, cardId=None, sender=None,
|
||||||
@ -25,11 +25,45 @@ class Transaction():
|
|||||||
self.sign(authorPrivateKey)
|
self.sign(authorPrivateKey)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
# TODO: validate transactionId
|
# TODO: better error codes
|
||||||
# TODO: validate timestamp
|
if not isinstance(self.transactionId, uuid.UUID):
|
||||||
# TODO: validate cardId
|
raise self.Invalid(
|
||||||
# TODO: validate sender
|
f"Transaction ID should be a UUID not {type(self.transactionId).__name__}."
|
||||||
# TODO: validate receiver
|
)
|
||||||
|
if not self.transactionId.version == 4:
|
||||||
|
raise self.Invalid(
|
||||||
|
f"Transaction ID version should be 4 not {self.transactionId.version}."
|
||||||
|
)
|
||||||
|
if not isinstance(self.timestamp, datetime.datetime):
|
||||||
|
raise self.Invalid(
|
||||||
|
f"Timestamp should be a datetime not {type(self.timestamp).__name__}"
|
||||||
|
)
|
||||||
|
if not self.timestamp.tzinfo == datetime.timezone.utc:
|
||||||
|
raise self.Invalid(
|
||||||
|
f"Timestamp timezone should be in UTC not {self.timestamp.tzinfo}."
|
||||||
|
)
|
||||||
|
if not self.timestamp < datetime.datetime.now(datetime.timezone.utc):
|
||||||
|
raise self.Invalid(f"Timestamp {self.timestamp} in the future.")
|
||||||
|
if not isinstance(self.cardId, uuid.UUID):
|
||||||
|
raise self.Invalid(
|
||||||
|
f"Card ID should be a UUID not {type(self.cardId).__name__}."
|
||||||
|
)
|
||||||
|
if not self.cardId.version == 4:
|
||||||
|
raise self.Invalid(
|
||||||
|
f"Card ID version should be 4 not {self.cardId.version}."
|
||||||
|
)
|
||||||
|
if not isinstance(self.sender, rsa.RSAPublicKey):
|
||||||
|
raise self.Invalid(
|
||||||
|
f"Sender should be an RSA public key not {type(self.sender).__name__}."
|
||||||
|
)
|
||||||
|
if not isinstance(self.receiver, rsa.RSAPublicKey):
|
||||||
|
raise self.Invalid(
|
||||||
|
f"Receiver should be an RSA public key not {type(self.receiver).__name__}."
|
||||||
|
)
|
||||||
|
if not isinstance(self.signature, bytes):
|
||||||
|
raise self.Invalid(
|
||||||
|
f"Signature should be bytes not {type(self.signature).__name__}."
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
self.sender.verify(
|
self.sender.verify(
|
||||||
self.signature,
|
self.signature,
|
||||||
@ -38,6 +72,10 @@ class Transaction():
|
|||||||
"transactionId": str(self.transactionId),
|
"transactionId": str(self.transactionId),
|
||||||
"timestamp": str(self.timestamp),
|
"timestamp": str(self.timestamp),
|
||||||
"cardId": str(self.cardId),
|
"cardId": str(self.cardId),
|
||||||
|
"sender": self.sender.public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8'),
|
||||||
"receiver": self.receiver.public_bytes(
|
"receiver": self.receiver.public_bytes(
|
||||||
encoding=serialization.Encoding.PEM,
|
encoding=serialization.Encoding.PEM,
|
||||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
@ -50,6 +88,7 @@ class Transaction():
|
|||||||
),
|
),
|
||||||
hashes.SHA256()
|
hashes.SHA256()
|
||||||
)
|
)
|
||||||
|
# TODO: may need more precision.
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise self.InvalidSignature(str(e))
|
raise self.InvalidSignature(str(e))
|
||||||
|
|
||||||
@ -60,6 +99,10 @@ class Transaction():
|
|||||||
"transactionId": str(self.transactionId),
|
"transactionId": str(self.transactionId),
|
||||||
"timestamp": str(self.timestamp),
|
"timestamp": str(self.timestamp),
|
||||||
"cardId": str(self.cardId),
|
"cardId": str(self.cardId),
|
||||||
|
"sender": self.sender.public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode('utf-8'),
|
||||||
"receiver": self.receiver.public_bytes(
|
"receiver": self.receiver.public_bytes(
|
||||||
encoding=serialization.Encoding.PEM,
|
encoding=serialization.Encoding.PEM,
|
||||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
@ -80,7 +123,7 @@ 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: why is this sometimes a tuple?
|
# TODO: why is this a tuple when coming from POST /transactions?
|
||||||
self.sender = serialization.load_pem_public_key(
|
self.sender = serialization.load_pem_public_key(
|
||||||
data['sender'].encode('utf-8'),
|
data['sender'].encode('utf-8'),
|
||||||
backend=default_backend()
|
backend=default_backend()
|
||||||
@ -132,12 +175,6 @@ class Transaction():
|
|||||||
self.message = message
|
self.message = message
|
||||||
self.statusCode = statusCode
|
self.statusCode = statusCode
|
||||||
|
|
||||||
class Abandoned(Exception):
|
|
||||||
def __init__(self, message, statusCode=500):
|
|
||||||
super().__init__(message)
|
|
||||||
self.message = message
|
|
||||||
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)
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,13 +1,17 @@
|
|||||||
import dotenv
|
import dotenv
|
||||||
import flask
|
import flask
|
||||||
import time
|
import os
|
||||||
|
|
||||||
|
ROLE = os.getenv('ROLE', 'mine')
|
||||||
|
|
||||||
|
if ROLE == 'mine':
|
||||||
|
from Mine.Mine import mine
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.register_blueprint(mine)
|
||||||
#from Market.Market import market
|
#from Market.Market import market
|
||||||
from Mine.Mine import mine
|
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
|
||||||
#app.register_blueprint(market, url_prefix="/market")
|
#app.register_blueprint(market, url_prefix="/market")
|
||||||
app.register_blueprint(mine, url_prefix="/mine")
|
else:
|
||||||
|
raise Exception("Environment variable ROLE must be either 'mine' or 'market'.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
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/')
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Getting block to mine from server...")
|
|
||||||
newBlockA = Block(
|
|
||||||
data = requests.get(f"{WIKIDECK_URL}/mine/blocks").json()
|
|
||||||
)
|
|
||||||
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...")
|
|
||||||
|
|
||||||
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