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/*
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;
USE market;
CREATE TABLE IF NOT EXISTS users(
sqlId INT PRIMARY KEY AUTO INCREMENT,
sqlId INT PRIMARY KEY AUTO_INCREMENT,
userId VARCHAR(36) UNIQUE NOT NULL,
userName VARCHAR(64) UNIQUE NOT NULL,
passwordHash VARCHAR(64) NOT NULL,
@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS users(
publicKey VARCHAR(128) NOT NULL
);
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,
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
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)
);
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,
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
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)
);
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,
FOREIGN KEY (sellOrderId) REFERENCES sell_orders(sellOrderId) NOT NULL
);
CREATE TABLE IF NOT EXISTS transactions(
sqlId INT PRIMARY KEY AUTO INCREMENT,
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}'@'%';

View File

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

View File

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

View File

@ -1,12 +1,25 @@
import requests
import json
import wikipedia
import uuid
class Card():
def __init__(self, cardId=uuid.uuid4(),
pageId=wikipedia.page(wikipedia.random()).pageid, data=None):
def __init__(self, cardId=uuid.uuid4(), pageId=None, data=None):
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:
self.load_from_data(data)
@ -21,11 +34,14 @@ class Card():
self.cardId = uuid.UUID(data['cardId'])
self.pageId = data['pageId']
def __str__(self):
return json.dumps({
def as_dict(self):
return {
"cardId": str(self.cardId),
"pageId": self.pageId
})
}
def __str__(self):
return json.dumps(self.as_dict())
class Invalid(Exception):
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():
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 = """
SELECT *
FROM blocks
ORDER BY timestamp DESC
LIMIT 1;
"""
SELECT *
FROM blocks
ORDER BY timestamp DESC
LIMIT 1;
"""
SQL_GET_CARD = """
SELECT *
FROM cards
WHERE cardId == {};
"""
SELECT *
FROM cards
WHERE cardId = '{}';
"""
SQL_GET_CARD_OWNER = """
SELECT receiver
FROM transactions
WHERE cardId == {}
AND isPending == false
ORDER BY timestamp DESC
LIMIT 1;
"""
SELECT receiver
FROM transactions
WHERE cardId = '{}'
AND isPending = False
ORDER BY timestamp DESC
LIMIT 1;
"""
SQL_GET_PENDING_TRANSACTIONS = """
SELECT *
FROM transactions
WHERE isPending == true;
"""
SELECT *
FROM transactions
WHERE isPending = True
ORDER BY timestamp ASC
LIMIT {};
"""
SQL_GET_TRANSACTION = """
SELECT *
FROM transactions
WHERE transactionId == {}
"""
SQL_GET_TRANSACTION_BY_ID = """
SELECT *
FROM transactions
WHERE transactionId == '{}'
"""
SQL_INSERT_BLOCK = """
INSERT INTO blocks (blockId, blockHash, previousHash, timestamp, height, nonce)
VALUES ({}, {}, {}, {}, {}, {});
"""
INSERT INTO blocks (blockId, blockHash, previousHash, timestamp, height, nonce)
VALUES ('{}', '{}', '{}', '{}', {}, {});
"""
SQL_INSERT_TRANSACTION = """
INSERT INTO transactions (
transactionId,
timestamp,
sender,
receiver,
signature,
blockId,
cardId
)
VALUES ({}, {}, {}, {}, {});
"""
INSERT INTO transactions (
transactionId,
timestamp,
sender,
receiver,
signature,
blockId,
cardId
)
VALUES ({}, {}, {}, {}, {});
"""
def __init__(self, host, port, user, password):
self.host = host
self.port = port
self.user = user
self.password = password
self.conn = mariadb.connect(
host = self.host,
port = self.port,
user = self.user,
password = self.password
def __init__(self):
delay = 2
while True:
try:
self.conn = mariadb.connect(
host = os.getenv('MINE_DB_HOST', "mariadb"),
port = os.getenv('MINE_DB_PORT', 3306),
user = os.getenv('MINE_DB_USER', None),
password = os.getenv('MINE_DB_PASSWORD', None),
database = 'mine'
)
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):
return Block(
data=self.conn.cursor().execute(SQL_GET_LAST_BLOCK)[0]
)
data=self.conn.cursor().execute(self.SQL_GET_LAST_BLOCK)
return Block(data=data) if data else None
def get_card(self, cardId):
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):
return self.conn.cursor().execute(
SQL_GET_CARD_OWNER.format(cardId)
)[0]
self.SQL_GET_CARD_OWNER.format(cardId)
)
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 [
Transaction(
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):
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):
return self.conn.cursor().execute(SQL_INSERT_BLOCK.format(
self.conn.cursor().execute(self.SQL_INSERT_BLOCK.format(
str(block.blockId),
block.blockHash.hexdigest(),
block.previousHash.hexdigest(),
block.previousHash,
str(block.timestamp),
block.height,
block.nonce
))
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.timestamp),
transaction.sender,

View File

@ -1,24 +1,75 @@
import flask
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.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__)
db = Database()
privateKey = rsa.generate_private_key(
public_exponent=65537,
key_size=4096
)
@app.get('/')
def mine():
def save_block(block):
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
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.
# This method calls the Block().validate() method to validate the block schema.
# This method performs additional validations against the rest of the chain.
###
@app.post('/blocks')
@mine.post('/blocks')
def blocks_post():
previousBlock = get_last_block()
try:
newBlock = Block(data=request.get_json())
newBlock.validate()
@ -35,7 +86,7 @@ def blocks_post():
raise Block.Invalid(
f"Incorrect block height - should be {previousBlock.height + 1}."
)
if newBlock.difficulty != DIFFICULTY_REQUIREMENT:
if newBlock.difficulty < DIFFICULTY_REQUIREMENT:
raise Block.Invalid(
f"Incorrect difficulty - should be {DIFFICULTY_REQUIREMENT}."
)
@ -73,46 +124,24 @@ def blocks_post():
raise Transaction.Invalid(
f"Incorrect signature on {transaction.transactionId}."
)
db.insert_block(block)
with open(f"{DATA_PATH}/{newBlock.blockId}.json", 'w') as f:
f.write(str(newBlock))
# TODO: update transactions
save_block(block)
for transaction in newBlock.transactions:
db.update_transactions_is_pending_false(transaction.transactionId)
# TODO: update peers
return str(newBlock), 200
except: Transaction.Invalid as e:
return flask.jsonify(block.asDict())
except Transaction.Invalid as e:
return e, e.statusCode
except: Transaction.AlreadyFulfilled as e:
except Transaction.AlreadyFulfilled as e:
return e, e.statusCode
except: Card.Invalid as e:
except Card.Invalid as e:
return e, e.statusCode
except: Block.Invalid as e:
except Block.Invalid as e:
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
###
@app.get('/cards')
@mine.get('/cards')
def cards_get():
# TODO: render cards in html
# TODO: query cards
@ -124,7 +153,7 @@ def cards_get():
# This method performs a number of validations on the submitted transaction and returns
# a status code result.
###
@app.put('/transactions')
@mine.put('/transactions')
def transactions_put():
try:
newTransaction = Transaction(data=request.get_json())
@ -151,20 +180,25 @@ def transactions_put():
###
# Retrieve a transaction.
###
@app.get('/transactions')
@mine.get('/transactions')
def transactions_get():
transactionId = request.args.get('transactionId', None)
return get_transaction_by_id(transactionId) if transactionId else json.dumps(
db.get_pending_transactions()
)
@app.post('/peers')
@mine.post('/peers')
def peers_post():
# TODO: validate peer
# TODO: add peers to database
return 200
try:
peer = Peer(data=request.get_json())
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():
# TODO: query peers
return 200

View File

@ -1,11 +1,17 @@
import datetime
import data
import io
import json
import uuid
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
class Transaction():
def __init__(self, transactionId=uuid.uuid4(),
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.timestamp = timestamp
self.cardId = cardId
@ -14,6 +20,8 @@ class Transaction():
self.signature = signature
if data:
self.load_from_data(data)
if authorPrivateKey:
self.sign(authorPrivateKey)
def validate(self):
# TODO: validate transactionId
@ -22,12 +30,48 @@ class Transaction():
# TODO: validate sender
# TODO: validate receiver
# TODO: validate signature
if False:
raise self.Unauthorized("Failed signature check.")
try:
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):
# TODO: use rsa private key to sign block
return None
def sign(self, authorPrivateKey):
self.signature = authorPrivateKey.sign(
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):
self.transactionId = uuid.UUID(data['transactionId'])
@ -36,20 +80,29 @@ class Transaction():
"%Y-%m-%d %H:%M:%S.%f%z"
)
self.cardId = uuid.UUID(data['cardId'])
# TODO: load rsa keys
self.sender = data['sender']
self.receiver = data['receiver']
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):
return data.dumps({
"transactionId": str(self.id),
"timestamp": str(self.timestamp),
"cardId": self.cardId,
"sender": self.sender,
"receiver": self.receiver,
"signature": self.signature
})
return json.dumps(self.as_dict())
class Unauthorized(Exception):
def __init__(self, message, statusCode=403):
@ -70,3 +123,8 @@ class Transaction():
def __init__(self, message, statusCode=500):
super().__init__(message)
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 flask
import time
import os
dotenv.load_dotenv()
#from Market.Market import market
from Mine.Mine import mine
ROLE = os.getenv("ROLE", None)
INTERVAL = os.getenv("INTERVAL", 15)
app = flask.Flask(__name__)
#app.register_blueprint(market, url_prefix="/market")
app.register_blueprint(mine, url_prefix="/mine")
if __name__ == "__main__":
if ROLE == "api":
from Market.Market import market
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)
dotenv.load_dotenv()
app.run()

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