Attempting server startup.

* Created basic client

* Many bug fixes
This commit is contained in:
Eric Meehan 2025-05-31 18:14:33 -04:00
parent c83f5feb4b
commit 741b1f5b2a
26 changed files with 494 additions and 198 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
venv/* venv/*
mine_data/*
mariadb_data/*

6
Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM ericomeehan/httpd-mod-wsgi
RUN apt update && apt upgrade -y
RUN apt install -y libmariadb3 libmariadb-dev python3-dev python3-pip
COPY . /usr/local/apache2/htdocs
RUN pip3 install --break-system-packages -r /usr/local/apache2/htdocs/requirements.txt
EXPOSE 80

View File

@ -1,7 +1,7 @@
CREATE DATABASE IF NOT EXISTS market; CREATE DATABASE IF NOT EXISTS market;
USE market; USE market;
CREATE TABLE IF NOT EXISTS users( CREATE TABLE IF NOT EXISTS users(
sqlId INT PRIMARY KEY AUTO INCREMENT, sqlId INT PRIMARY KEY AUTO_INCREMENT,
userId VARCHAR(36) UNIQUE NOT NULL, userId VARCHAR(36) UNIQUE NOT NULL,
userName VARCHAR(64) UNIQUE NOT NULL, userName VARCHAR(64) UNIQUE NOT NULL,
passwordHash VARCHAR(64) NOT NULL, passwordHash VARCHAR(64) NOT NULL,
@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS users(
publicKey VARCHAR(128) NOT NULL publicKey VARCHAR(128) NOT NULL
); );
CREATE TABLE IF NOT EXISTS buy_orders( CREATE TABLE IF NOT EXISTS buy_orders(
sqlId INT PRIMARY KEY AUTO INCREMENT, sqlId INT PRIMARY KEY AUTO_INCREMENT,
buyOrderId VARCHAR(36) UNIQUE NOT NULL, buyOrderId VARCHAR(36) UNIQUE NOT NULL,
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(), timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
expires DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP() + INTERVAL 3 DAY, expires DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP() + INTERVAL 3 DAY,
@ -22,7 +22,7 @@ CREATE TABLE IF NOT EXISTS buy_orders(
FOREIGN KEY (userId) REFERENCES users(userId) FOREIGN KEY (userId) REFERENCES users(userId)
); );
CREATE TABLE IF NOT EXISTS sell_orders( CREATE TABLE IF NOT EXISTS sell_orders(
sqlId INT PRIMARY KEY AUTO INCREMENT, sqlId INT PRIMARY KEY AUTO_INCREMENT,
sellOrderId VARCHAR(36) UNIQUE NOT NULL, sellOrderId VARCHAR(36) UNIQUE NOT NULL,
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(), timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
expires DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP() + INTERVAL 3 DAY, expires DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP() + INTERVAL 3 DAY,
@ -31,15 +31,21 @@ CREATE TABLE IF NOT EXISTS sell_orders(
FOREIGN KEY (userId) REFERENCES users(userId) FOREIGN KEY (userId) REFERENCES users(userId)
); );
CREATE TABLE IF NOT EXISTS sell_orders_items( CREATE TABLE IF NOT EXISTS sell_orders_items(
sqlId INT PRIMARY KEY AUTO INCREMENT, sqlId INT PRIMARY KEY AUTO_INCREMENT,
cardId VARCHAR(36) NOT NULL, cardId VARCHAR(36) NOT NULL,
FOREIGN KEY (sellOrderId) REFERENCES sell_orders(sellOrderId) NOT NULL FOREIGN KEY (sellOrderId) REFERENCES sell_orders(sellOrderId) NOT NULL
); );
CREATE TABLE IF NOT EXISTS transactions( CREATE TABLE IF NOT EXISTS transactions(
sqlId INT PRIMARY KEY AUTO INCREMENT, sqlId INT PRIMARY KEY AUTO_INCREMENT,
transactionId VARCHAR(36) UNIQUE NOT NULL, transactionId VARCHAR(36) UNIQUE NOT NULL,
isPending BOOLEAN NOT NULL DEFAULT true, isPending BOOLEAN NOT NULL DEFAULT true,
isAbandoned BOOLEAN NOT NULL DEFAULT false, isAbandoned BOOLEAN NOT NULL DEFAULT false,
FOREIGN KEY (buyOrderId) REFERENCES buy_orders(buyOrderId) NOT NULL, FOREIGN KEY (buyOrderId) REFERENCES buy_orders(buyOrderId) NOT NULL,
FOREIGN KEY (sellOrderItemSqlId) REFERENCES sell_order_items(sqlId) 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}'@'%';

View File

@ -1,7 +1,5 @@
CREATE DATABASE IF NOT EXISTS chain;
USE chain;
CREATE TABLE IF NOT EXISTS blocks( CREATE TABLE IF NOT EXISTS blocks(
sqlId INT PRIMARY KEY AUTO INCREMENT, sqlId INT PRIMARY KEY AUTO_INCREMENT,
blockId VARCHAR(32) UNIQUE NOT NULL, blockId VARCHAR(32) 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,
@ -10,13 +8,16 @@ CREATE TABLE IF NOT EXISTS blocks(
nonce INT NOT NULL nonce INT NOT NULL
); );
CREATE TABLE IF NOT EXISTS cards( CREATE TABLE IF NOT EXISTS cards(
sqlId INT PRIMARY KEY AUTO INCREMENT, sqlId INT PRIMARY KEY AUTO_INCREMENT,
blockId VARCHAR(32) UNIQUE NOT NULL,
cardId VARCHAR(32) UNIQUE NOT NULL, cardId VARCHAR(32) 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 transactions( CREATE TABLE IF NOT EXISTS transactions(
sqlId INT PRIMARY KEY AUTO INCREMENT, sqlId INT PRIMARY KEY AUTO_INCREMENT,
blockId VARCHAR(32) NOT NULL,
cardId VARCHAR(32) UNIQUE NOT NULL,
transactionId VARCHAR(32) UNIQUE NOT NULL, transactionId VARCHAR(32) UNIQUE NOT NULL,
timestamp DATETIME NOT NULL, timestamp DATETIME NOT NULL,
sender VARCHAR(128) NOT NULL, sender VARCHAR(128) NOT NULL,
@ -28,7 +29,7 @@ CREATE TABLE IF NOT EXISTS transactions(
FOREIGN KEY (cardId) REFERENCES cards(cardId) FOREIGN KEY (cardId) REFERENCES cards(cardId)
); );
CREATE TABLE IF NOT EXISTS peers( CREATE TABLE IF NOT EXISTS peers(
sqlId INT PRIMARY KEY AUTO INCREMENT, sqlId INT PRIMARY KEY AUTO_INCREMENT,
peerId VARCHAR(32) UNIQUE NOT NULL, peerId VARCHAR(32) UNIQUE NOT NULL,
baseUrl VARCHAR(128) UNIQUE NOT NULL, baseUrl VARCHAR(128) UNIQUE NOT NULL,
isUp BOOLEAN NOT NULL, isUp BOOLEAN NOT NULL,

View File

@ -1,3 +0,0 @@
CREATE DATABASE IF NOT EXISTS peers;
USE peers;
CREATE TABLE IF NOT EXISTS peers

34
docker-compose.yaml Normal file
View File

@ -0,0 +1,34 @@
version: '3.8'
services:
wikideck:
image: ericomeehan/wikideck:latest
build:
context: .
dockerfile: Dockerfile
environment:
ROLE: api
DATA_PATH: /data
DIFFICULTY_REQUIREMENT: 3
MINE_DB_HOST: mariadb-mine
MINE_DB_USER: mine
MINE_DB_PASSWORD: 123abc
depends_on:
- mariadb-mine
ports:
- "8080:80"
volumes:
- ./mine_data:/data
mariadb-mine:
image: mariadb:latest
environment:
MARIADB_ROOT_PASSWORD: abc123
MARIADB_DATABASE: mine
MARIADB_USER: mine
MARIADB_PASSWORD: 123abc
ports:
- "3306:3306"
volumes:
mine_data:

View File

@ -1 +1,5 @@
cryptography
dotenv
flask
mariadb
wikipedia wikipedia

View File

@ -1,33 +1,40 @@
from card import Card
import datetime import datetime
import hashlib import hashlib
import json
import uuid
from Mine.Card import Card
from Mine.Transaction import Transaction
class Block(): class Block():
def __init__(self, blockId=uuid.uuid4(), previousHash=None, def __init__(self, blockId=uuid.uuid4(), previousHash="0",
timestamp=datetime.datetime.now(datetime.timezone.utc), height=0, nonce=0, timestamp=datetime.datetime.now(datetime.timezone.utc), height=0, nonce=0,
card=Card(), transactions=[], authorPublicKey=None, data=None): difficulty=0, card=Card(), transactions=[], data=None):
self.blockId = blockId
self.previousHash = previousHash
self.timestamp = timestamp
self.height = height
self.difficulty = difficulty
self.nonce = nonce
self.card = card
self.transactions = transactions.append(
Transaction(
cardId = self.card.id,
receiver = authorPublicKey
)
)
if data: if data:
self.load_from_data(data) self.load_from_data(data)
else:
self.blockId = blockId
self.previousHash = previousHash
self.timestamp = timestamp
self.height = height
self.difficulty = difficulty
self.nonce = nonce
self.card = card
self.transactions = transactions
self.update() self.update()
def update(self): def update(self):
self.blockHash = hashlib.sha256(str(self).encode('utf-8')) self.blockHash = hashlib.sha256(str(self).encode('utf-8'))
def mine(self): def mine(self, authorPrivateKey):
self.transactions.append(
Transaction(
cardId = self.card.cardId,
sender = authorPrivateKey.public_key(),
receiver = authorPrivateKey.public_key(),
authorPrivateKey = authorPrivateKey
)
)
while int(self.blockHash.hexdigest(), 16) > 2**(256-self.difficulty): while int(self.blockHash.hexdigest(), 16) > 2**(256-self.difficulty):
self.nonce += 1 self.nonce += 1
self.update() self.update()
@ -43,20 +50,21 @@ class Block():
def validate(self): def validate(self):
# TODO: validate blockId is uuid # TODO: validate blockId is uuid
# TODO: validate previousHash is sha256 hash # TODO: validate previousHash is sha256 hash
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 timestamp # TODO: validate timestamp is UTC timestamp
if not self.timestamp < datetime.datetime.now(): if not self.timestamp < datetime.datetime.now(datetime.timezone.utc):
raise self.Invalid("Timestamp in the future.") raise self.Invalid("Timestamp in the future.")
if not self.height > 0: if not self.height >= 0:
raise self.Invalid("Height less than 0.") raise self.Invalid("Height less than 0.")
if not self.difficulty > 0: if not self.difficulty >= 0:
raise self.Invalid("Difficulty less than 0.") raise self.Invalid("Difficulty less than 0.")
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()
for transaction in self.transactions: for transaction in self.transactions:
transaction.validate() transaction.validate()
# TODO validate that one transaction gives the card to the author
def load_from_data(self, data): def load_from_data(self, data):
self.blockId = uuid.UUID(data['blockId']) self.blockId = uuid.UUID(data['blockId'])
@ -87,21 +95,24 @@ class Block():
] ]
self.update() self.update()
def __str__(self): def as_dict(self):
# The hash of the block is the SHA256 hash of what this method returns. return {
return data.dumps({ "blockId": str(self.blockId),
"blockId": str(self.blockId)
"previousHash": self.previousHash, "previousHash": self.previousHash,
"timestamp": str(self.timestamp) "timestamp": str(self.timestamp),
"height": self.height, "height": self.height,
"difficulty": self.difficulty, "difficulty": self.difficulty,
"nonce": self.nonce, "nonce": self.nonce,
"author": self.author, "card": self.card.as_dict(),
"card": str(self.card), "transactions": [each.as_dict() for each in self.transactions]
"transactions": [str(each) for each in transactions] }
})
def __str__(self):
# The hash of the block is the SHA256 hash of what this method returns.
return json.dumps(self.as_dict())
class Invalid(Exception): class Invalid(Exception):
def __init__(message, status_code=406): def __init__(self, message, status_code=406):
super().__init__(message) super().__init__(message)
self.status_code = status_code self.status_code = status_code

View File

@ -1,12 +1,25 @@
import requests import requests
import json
import wikipedia import wikipedia
import uuid import uuid
class Card(): class Card():
def __init__(self, cardId=uuid.uuid4(), def __init__(self, cardId=uuid.uuid4(), pageId=None, data=None):
pageId=wikipedia.page(wikipedia.random()).pageid, data=None):
self.cardId = cardId self.cardId = cardId
self.pageId = pageId while True:
try:
self.pageId = pageId if pageId else int(
wikipedia.page(
wikipedia.random()
).pageid
)
except wikipedia.exceptions.PageError as e:
# TODO: why is the random function returning lower-case pages?
continue
except wikipedia.exceptions.DisambiguationError as e:
# TODO pick random disambiuation option
continue
break
if data: if data:
self.load_from_data(data) self.load_from_data(data)
@ -21,11 +34,14 @@ class Card():
self.cardId = uuid.UUID(data['cardId']) self.cardId = uuid.UUID(data['cardId'])
self.pageId = data['pageId'] self.pageId = data['pageId']
def __str__(self): def as_dict(self):
return json.dumps({ return {
"cardId": str(self.cardId), "cardId": str(self.cardId),
"pageId": self.pageId "pageId": self.pageId
}) }
def __str__(self):
return json.dumps(self.as_dict())
class Invalid(Exception): class Invalid(Exception):
def __init__(self, message, status_code=406): def __init__(self, message, status_code=406):

View File

@ -1,107 +1,186 @@
import mariadb
import os
import time
from Mine.Block import Block
from Mine.Transaction import Transaction
class Database(): class Database():
SQL_CREATE_DATABASE = [
"""
CREATE TABLE IF NOT EXISTS mine.blocks(
sqlId INT PRIMARY KEY AUTO_INCREMENT,
blockId VARCHAR(37) 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 mine.cards(
sqlId INT PRIMARY KEY AUTO_INCREMENT,
blockId VARCHAR(37) UNIQUE NOT NULL,
cardId VARCHAR(37) UNIQUE NOT NULL,
pageId INT NOT NULL,
FOREIGN KEY (blockId) REFERENCES blocks(blockId)
);
""",
"""
CREATE TABLE IF NOT EXISTS mine.transactions(
sqlId INT PRIMARY KEY AUTO_INCREMENT,
blockId VARCHAR(37) NOT NULL,
cardId VARCHAR(37) UNIQUE NOT NULL,
transactionId VARCHAR(37) UNIQUE NOT NULL,
timestamp DATETIME NOT NULL,
sender VARCHAR(128) NOT NULL,
receiver VARCHAR(128) NOT NULL,
signature VARCHAR(128) NOT NULL,
isPending BOOLEAN NOT NULL DEFAULT true,
isAbandoned BOOLEAN NOT NULL DEFAULT false,
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 VARCHAR(37) 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_LAST_BLOCK = """ SQL_GET_LAST_BLOCK = """
SELECT * SELECT *
FROM blocks FROM blocks
ORDER BY timestamp DESC ORDER BY timestamp DESC
LIMIT 1; LIMIT 1;
""" """
SQL_GET_CARD = """ SQL_GET_CARD = """
SELECT * SELECT *
FROM cards FROM cards
WHERE cardId == {}; WHERE cardId = '{}';
""" """
SQL_GET_CARD_OWNER = """ SQL_GET_CARD_OWNER = """
SELECT receiver SELECT receiver
FROM transactions FROM transactions
WHERE cardId == {} WHERE cardId = '{}'
AND isPending == false AND isPending = False
ORDER BY timestamp DESC ORDER BY timestamp DESC
LIMIT 1; LIMIT 1;
""" """
SQL_GET_PENDING_TRANSACTIONS = """ SQL_GET_PENDING_TRANSACTIONS = """
SELECT * SELECT *
FROM transactions FROM transactions
WHERE isPending == true; WHERE isPending = True
""" ORDER BY timestamp ASC
LIMIT {};
"""
SQL_GET_TRANSACTION = """ SQL_GET_TRANSACTION_BY_ID = """
SELECT * SELECT *
FROM transactions FROM transactions
WHERE transactionId == {} WHERE transactionId == '{}'
""" """
SQL_INSERT_BLOCK = """ SQL_INSERT_BLOCK = """
INSERT INTO blocks (blockId, blockHash, previousHash, timestamp, height, nonce) INSERT INTO blocks (blockId, blockHash, previousHash, timestamp, height, nonce)
VALUES ({}, {}, {}, {}, {}, {}); VALUES ('{}', '{}', '{}', '{}', {}, {});
""" """
SQL_INSERT_TRANSACTION = """ SQL_INSERT_TRANSACTION = """
INSERT INTO transactions ( INSERT INTO transactions (
transactionId, transactionId,
timestamp, timestamp,
sender, sender,
receiver, receiver,
signature, signature,
blockId, blockId,
cardId cardId
) )
VALUES ({}, {}, {}, {}, {}); VALUES ({}, {}, {}, {}, {});
""" """
def __init__(self, host, port, user, password): def __init__(self):
self.host = host delay = 2
self.port = port while True:
self.user = user try:
self.password = password self.conn = mariadb.connect(
self.conn = mariadb.connect( host = os.getenv('MINE_DB_HOST', "mariadb"),
host = self.host, port = os.getenv('MINE_DB_PORT', 3306),
port = self.port, user = os.getenv('MINE_DB_USER', None),
user = self.user, password = os.getenv('MINE_DB_PASSWORD', None),
password = self.password database = 'mine'
)
for each in self.SQL_CREATE_DATABASE:
cursor = self.conn.cursor().execute(each)
except mariadb.Error as e:
time.sleep(delay := delay**2)
continue
break
def get_block_by_id(self, blockId):
return Block(
data=self.conn.cursor().execute(
self.SQL_GET_BLOCK_BY_ID.format(blockId)
)
) )
def get_last_block(self): def get_last_block(self):
return Block( data=self.conn.cursor().execute(self.SQL_GET_LAST_BLOCK)
data=self.conn.cursor().execute(SQL_GET_LAST_BLOCK)[0] return Block(data=data) if data else None
)
def get_card(self, cardId): def get_card(self, cardId):
return Card( return Card(
data=self.conn.cursor().execute(SQL_GET_CARD.format(cardId))[0] data=self.conn.cursor().execute(self.SQL_GET_CARD.format(cardId))
) )
def get_card_owner(self, cardId): def get_card_owner(self, cardId):
return self.conn.cursor().execute( return self.conn.cursor().execute(
SQL_GET_CARD_OWNER.format(cardId) self.SQL_GET_CARD_OWNER.format(cardId)
)[0] )
def get_pending_transactions(self): def get_pending_transactions(self, limit=32768):
pendingTransactions = self.conn.cursor().execute(
self.SQL_GET_PENDING_TRANSACTIONS.format(limit)
)
return [ return [
Transaction( Transaction(
data=each data=each
) for each in self.conn.cursor().execute(SQL_GET_PENDING_TRANSACTIONS) ) for each in pendingTransactions
] ] if pendingTransactions else []
def get_transaction(self, transactionId): def get_transaction(self, transactionId):
return Transaction( return Transaction(
data=self.conn.cursor().execute(SQL_GET_TRANSACTION.format(transactionId)) data=self.conn.cursor().execute(self.SQL_GET_TRANSACTION.format(transactionId))
) )
def insert_block(self, block): def insert_block(self, block):
return self.conn.cursor().execute(SQL_INSERT_BLOCK.format( self.conn.cursor().execute(self.SQL_INSERT_BLOCK.format(
str(block.blockId), str(block.blockId),
block.blockHash.hexdigest(), block.blockHash.hexdigest(),
block.previousHash.hexdigest(), block.previousHash,
str(block.timestamp), str(block.timestamp),
block.height, block.height,
block.nonce block.nonce
)) ))
def insert_transaction(self, transaction): def insert_transaction(self, transaction):
return self.conn.cursor().execute(SQL_INSERT_TRANSACTION.format( return self.conn.cursor().execute(self.SQL_INSERT_TRANSACTION.format(
str(transaction.transactionId), str(transaction.transactionId),
str(transaction.timestamp), str(transaction.timestamp),
transaction.sender, transaction.sender,

View File

@ -1,24 +1,75 @@
import flask import flask
import json import json
import os
from cryptography.hazmat.primitives.asymmetric import rsa
from Mine.Block import Block
from Mine.Card import Card
from Mine.Database import Database from Mine.Database import Database
from Mine.Transaction import Transaction
BLOCK_TRANSACTION_LIMIT = int(os.getenv('BLOCK_TRANSACTION_LIMIT', 32768))
DATA_PATH = os.getenv('DATA_PATH', '/var/lib/wikideck/blocks')
DIFFICULTY_REQUIREMENT = int(os.getenv('DIFFICULTY_REQUIREMENT', 0))
mine = flask.Blueprint("mine", __name__) mine = flask.Blueprint("mine", __name__)
db = Database() db = Database()
privateKey = rsa.generate_private_key(
public_exponent=65537,
key_size=4096
)
@app.get('/') def save_block(block):
def mine(): with open(f"{DATA_PATH}/{block.blockId}.json", 'w') as f:
f.write(str(block)) # Blocks are json strings. This is the true block.
db.insert_block(block)
if not db.get_last_block():
# TODO: load blocks from files
# TODO: try to get blocks from peers
originBlock = Block(
difficulty = DIFFICULTY_REQUIREMENT,
)
originBlock.mine(privateKey)
originBlock.validate()
save_block(originBlock)
@mine.get('/')
def index_get():
# TODO: return a page to mine through the browser # TODO: return a page to mine through the browser
return 200 return "Hello world!", 200
###
# Retrieve blocks and block data.
# Returns a skeleton block to be mined by default.
# Queries for a specific block when given parameters.
###
@mine.get('/blocks')
def blocks_get():
# TODO: block queries
blockId = flask.request.args.get('blockId', None)
lastBlock = db.get_last_block()
return flask.jsonify(
Block(
previousHash = lastBlock.blockHash.hexdigest(),
height = lastBlock.height + 1,
difficulty = DIFFICULTY_REQUIREMENT,
transactions = [
Transaction(data=each) for each in db.get_pending_transactions(
BLOCK_TRANSACTION_LIMIT
)
]
).as_dict()
)
### ###
# Submit a block to the chain. # Submit a block to the chain.
# This method calls the Block().validate() method to validate the block schema. # This method calls the Block().validate() method to validate the block schema.
# This method performs additional validations against the rest of the chain. # This method performs additional validations against the rest of the chain.
### ###
@app.post('/blocks') @mine.post('/blocks')
def blocks_post(): def blocks_post():
previousBlock = get_last_block()
try: try:
newBlock = Block(data=request.get_json()) newBlock = Block(data=request.get_json())
newBlock.validate() newBlock.validate()
@ -35,7 +86,7 @@ def blocks_post():
raise Block.Invalid( raise Block.Invalid(
f"Incorrect block height - should be {previousBlock.height + 1}." f"Incorrect block height - should be {previousBlock.height + 1}."
) )
if newBlock.difficulty != DIFFICULTY_REQUIREMENT: if newBlock.difficulty < DIFFICULTY_REQUIREMENT:
raise Block.Invalid( raise Block.Invalid(
f"Incorrect difficulty - should be {DIFFICULTY_REQUIREMENT}." f"Incorrect difficulty - should be {DIFFICULTY_REQUIREMENT}."
) )
@ -73,46 +124,24 @@ def blocks_post():
raise Transaction.Invalid( raise Transaction.Invalid(
f"Incorrect signature on {transaction.transactionId}." f"Incorrect signature on {transaction.transactionId}."
) )
db.insert_block(block) save_block(block)
with open(f"{DATA_PATH}/{newBlock.blockId}.json", 'w') as f: for transaction in newBlock.transactions:
f.write(str(newBlock)) db.update_transactions_is_pending_false(transaction.transactionId)
# TODO: update transactions
# TODO: update peers # TODO: update peers
return str(newBlock), 200 return flask.jsonify(block.asDict())
except: Transaction.Invalid as e: except Transaction.Invalid as e:
return e, e.statusCode return e, e.statusCode
except: Transaction.AlreadyFulfilled as e: except Transaction.AlreadyFulfilled as e:
return e, e.statusCode return e, e.statusCode
except: Card.Invalid as e: except Card.Invalid as e:
return e, e.statusCode return e, e.statusCode
except: Block.Invalid as e: except Block.Invalid as e:
return e, e.statusCode return e, e.statusCode
###
# Retrieve blocks and block data.
# Returns a skeleton block to be mined by default.
# Queries for a specific block when given parameters.
###
@app.get('/blocks')
def blocks_get():
blockHash = request.args.get('blockHash', None)
height = request.args.get('height', None)
# TODO: block queries
return str(
Block(
previousHash = lastBlock.blockHash,
height = lastBlock.height + 1,
difficulty = DIFFICULTY_REQUIREMENT,
transactions = [
Transaction(data=each) for each in db.get_pending_transactions()
]
)
)
### ###
# Retrieve card data # Retrieve card data
### ###
@app.get('/cards') @mine.get('/cards')
def cards_get(): def cards_get():
# TODO: render cards in html # TODO: render cards in html
# TODO: query cards # TODO: query cards
@ -124,7 +153,7 @@ 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.
### ###
@app.put('/transactions') @mine.put('/transactions')
def transactions_put(): def transactions_put():
try: try:
newTransaction = Transaction(data=request.get_json()) newTransaction = Transaction(data=request.get_json())
@ -151,20 +180,25 @@ def transactions_put():
### ###
# Retrieve a transaction. # Retrieve a transaction.
### ###
@app.get('/transactions') @mine.get('/transactions')
def transactions_get(): def transactions_get():
transactionId = request.args.get('transactionId', None) transactionId = request.args.get('transactionId', None)
return get_transaction_by_id(transactionId) if transactionId else json.dumps( return get_transaction_by_id(transactionId) if transactionId else json.dumps(
db.get_pending_transactions() db.get_pending_transactions()
) )
@app.post('/peers') @mine.post('/peers')
def peers_post(): def peers_post():
# TODO: validate peer try:
# TODO: add peers to database peer = Peer(data=request.get_json())
return 200 peer.validate()
# TODO: add peers to database
return 200
# TODO: error handling
except Exception as e:
print('crap!')
@app.get('/peers') @mine.get('/peers')
def peers_get(): def peers_get():
# TODO: query peers # TODO: query peers
return 200 return 200

View File

@ -1,11 +1,17 @@
import datetime import datetime
import data import io
import json
import uuid import uuid
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
class Transaction(): class Transaction():
def __init__(self, transactionId=uuid.uuid4(), def __init__(self, transactionId=uuid.uuid4(),
timestamp=datetime.datetime.now(datetime.timezone.utc), timestamp=datetime.datetime.now(datetime.timezone.utc),
cardId=None, sender=None, receiver=None, signature=None, data=None): cardId=None, sender=None, receiver=None, authorPrivateKey=None, signature=None,
data=None):
self.transactionId = transactionId self.transactionId = transactionId
self.timestamp = timestamp self.timestamp = timestamp
self.cardId = cardId self.cardId = cardId
@ -14,6 +20,8 @@ class Transaction():
self.signature = signature self.signature = signature
if data: if data:
self.load_from_data(data) self.load_from_data(data)
if authorPrivateKey:
self.sign(authorPrivateKey)
def validate(self): def validate(self):
# TODO: validate transactionId # TODO: validate transactionId
@ -22,12 +30,48 @@ class Transaction():
# TODO: validate sender # TODO: validate sender
# TODO: validate receiver # TODO: validate receiver
# TODO: validate signature # TODO: validate signature
if False: try:
raise self.Unauthorized("Failed signature check.") self.sender.verify(
self.signature,
bytearray(
json.dumps({
"transactionId": str(self.transactionId),
"timestamp": str(self.timestamp),
"cardId": str(self.cardId),
"receiver": self.receiver.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8')
}).encode('utf-8')
),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
except Exception as e:
raise self.InvalidSignature("Invalid signature.")
def sign(self, privateKey): def sign(self, authorPrivateKey):
# TODO: use rsa private key to sign block self.signature = authorPrivateKey.sign(
return None bytearray(
json.dumps({
"transactionId": str(self.transactionId),
"timestamp": str(self.timestamp),
"cardId": str(self.cardId),
"receiver": self.receiver.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8')
}).encode('utf-8')
),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
def load_from_data(self, data): def load_from_data(self, data):
self.transactionId = uuid.UUID(data['transactionId']) self.transactionId = uuid.UUID(data['transactionId'])
@ -36,20 +80,29 @@ 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
self.sender = data['sender'] self.sender = data['sender']
self.receiver = data['receiver'] self.receiver = data['receiver']
self.signature = data['signature'] self.signature = data['signature']
def as_dict(self):
return {
"transactionId": str(self.transactionId),
"timestamp": str(self.timestamp),
"cardId": str(self.cardId),
"receiver": self.receiver.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8'),
"sender": self.sender.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8'),
"signature": self.signature.hex()
}
def __str__(self): def __str__(self):
return data.dumps({ return json.dumps(self.as_dict())
"transactionId": str(self.id),
"timestamp": str(self.timestamp),
"cardId": self.cardId,
"sender": self.sender,
"receiver": self.receiver,
"signature": self.signature
})
class Unauthorized(Exception): class Unauthorized(Exception):
def __init__(self, message, statusCode=403): def __init__(self, message, statusCode=403):
@ -70,3 +123,8 @@ class Transaction():
def __init__(self, message, statusCode=500): def __init__(self, message, statusCode=500):
super().__init__(message) super().__init__(message)
self.statusCode = statusCode self.statusCode = statusCode
class InvalidSignature(Exception):
def __init__(self, message, statusCode=400):
super().__init__(message)
self.statusCode = statusCode

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,36 @@
###
# The PeerHandler sends new blocks and transactions to available peers.
###
import requests
from Mine.Block import Block
from Mine.Transaction import Transaction
class PeerHandler():
def __init__(self):
return
def add_peer(self. peer):
try:
db.insert_peer(peer.peerId, peer.url)
# TODO: error handling
except Exception as e:
print('crap!')
def propogate_block(self, block):
try:
for peer in db.get_available_peers():
requests.post(peer.url, data=str(block))
db.update_last_try(peer.peerId)
# TODO: error handling
except Exception as e:
print('crap!')
def propogate_transaction(self, transaction):
try:
for peer in db.get_available_peers():
requests.post(peer.url, data=str(transaction))
# TODO: error handling
except Exception as e:
print('crap!')

0
wikideck/__init__.py Normal file
View File

View File

@ -1,29 +1,14 @@
import dotenv import dotenv
import flask
import time import time
import os
dotenv.load_dotenv() #from Market.Market import market
from Mine.Mine import mine
ROLE = os.getenv("ROLE", None) app = flask.Flask(__name__)
INTERVAL = os.getenv("INTERVAL", 15) #app.register_blueprint(market, url_prefix="/market")
app.register_blueprint(mine, url_prefix="/mine")
if __name__ == "__main__": if __name__ == "__main__":
if ROLE == "api": dotenv.load_dotenv()
from Market.Market import market app.run()
from Mine.Mine import mine
app = Flask(__name__)
app.register_blueprint(market, url_prefix="/market")
app.register_blueprint(mine, url_prefix="/mine")
app.run()
elif ROLE == "order_matcher":
from OrderMatcher.OrderMatcher import OrderMatcher
orderMatcher = OrderMatcher()
while True:
orderMatcher.match_orders()
time.sleep(INTERVAL)
elif ROLE == "status_checker":
from StatusChecker.StatusChecker import StatusChecker
statusChecker = StatusChecker()
while True:
statusChecker.check_pending_transactions()
time.sleep(INTERVAL)

23
wikideck/client.py Normal file
View File

@ -0,0 +1,23 @@
import json
import os
import requests
from cryptography.hazmat.primitives.asymmetric import rsa
from Mine.Block import Block
WIKIDECK_URL = os.getenv('WIKIDECK_URL', 'http://localhost:8080/')
privateKey = rsa.generate_private_key(
public_exponent=65537,
key_size=4096
)
newBlock = Block(
data = requests.get(f"{WIKIDECK_URL}/mine/blocks").json()
)
newBlock.mine(privateKey)
print(newBlock)
r = requests.post(f"{WIKIDECK_URL}/mine/blocks", data=newBlock.as_dict())
print(r)

4
wsgi_app.py Normal file
View File

@ -0,0 +1,4 @@
import sys
sys.path.insert(0, '/usr/local/apache2/htdocs/wikideck')
from app import app as application