Attempting server startup.
* Created basic client * Many bug fixes
This commit is contained in:
parent
c83f5feb4b
commit
741b1f5b2a
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
||||
venv/*
|
||||
mine_data/*
|
||||
mariadb_data/*
|
||||
|
6
Dockerfile
Normal file
6
Dockerfile
Normal 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
|
@ -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}'@'%';
|
@ -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,
|
@ -1,3 +0,0 @@
|
||||
CREATE DATABASE IF NOT EXISTS peers;
|
||||
USE peers;
|
||||
CREATE TABLE IF NOT EXISTS peers
|
34
docker-compose.yaml
Normal file
34
docker-compose.yaml
Normal 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:
|
@ -1 +1,5 @@
|
||||
cryptography
|
||||
dotenv
|
||||
flask
|
||||
mariadb
|
||||
wikipedia
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
BIN
wikideck/Mine/__pycache__/Block.cpython-311.pyc
Normal file
BIN
wikideck/Mine/__pycache__/Block.cpython-311.pyc
Normal file
Binary file not shown.
BIN
wikideck/Mine/__pycache__/Card.cpython-311.pyc
Normal file
BIN
wikideck/Mine/__pycache__/Card.cpython-311.pyc
Normal file
Binary file not shown.
BIN
wikideck/Mine/__pycache__/Database.cpython-311.pyc
Normal file
BIN
wikideck/Mine/__pycache__/Database.cpython-311.pyc
Normal file
Binary file not shown.
BIN
wikideck/Mine/__pycache__/Mine.cpython-311.pyc
Normal file
BIN
wikideck/Mine/__pycache__/Mine.cpython-311.pyc
Normal file
Binary file not shown.
BIN
wikideck/Mine/__pycache__/Transaction.cpython-311.pyc
Normal file
BIN
wikideck/Mine/__pycache__/Transaction.cpython-311.pyc
Normal file
Binary file not shown.
36
wikideck/PeerHandler/PeerHandler.py
Normal file
36
wikideck/PeerHandler/PeerHandler.py
Normal 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
0
wikideck/__init__.py
Normal 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
23
wikideck/client.py
Normal 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
4
wsgi_app.py
Normal file
@ -0,0 +1,4 @@
|
||||
import sys
|
||||
sys.path.insert(0, '/usr/local/apache2/htdocs/wikideck')
|
||||
|
||||
from app import app as application
|
Loading…
Reference in New Issue
Block a user