Initial commit
Created basic application design and data schemas.
This commit is contained in:
commit
14def28b68
0
.env_example
Normal file
0
.env_example
Normal file
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
venv/*
|
34
db/createChainDatabase.sql
Normal file
34
db/createChainDatabase.sql
Normal file
@ -0,0 +1,34 @@
|
||||
CREATE DATABASE IF NOT EXISTS chain;
|
||||
USE chain;
|
||||
CREATE TABLE IF NOT EXISTS blocks(
|
||||
rowId 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(
|
||||
rowId INT PRIMARY KEY AUTO INCREMENT,
|
||||
cardId VARCHAR(32) UNIQUE NOT NULL,
|
||||
pageId INT NOT NULL,
|
||||
FOREIGN KEY (blockId) REFERENCES blocks(blockId)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS transactions(
|
||||
rowId INT PRIMARY KEY AUTO INCREMENT,
|
||||
transactionId VARCHAR(32) UNIQUE NOT NULL,
|
||||
timestamp DATETIME NOT NULL,
|
||||
receiver 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(
|
||||
peerId INT PRIMARY KEY AUTO INCREMENT,
|
||||
baseUrl VARCHAR(128) UNIQUE NOT NULL,
|
||||
isUp BOOLEAN NOT NULL,
|
||||
downCount INT DEFAULT 0,
|
||||
lastTry TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
40
db/createMarketDatabase.sql
Normal file
40
db/createMarketDatabase.sql
Normal file
@ -0,0 +1,40 @@
|
||||
CREATE DATABASE IF NOT EXISTS market;
|
||||
USE market;
|
||||
CREATE TABLE IF NOT EXISTS users(
|
||||
rowId 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,
|
||||
public_key VARCHAR(128) NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS buyOrders(
|
||||
rowId INT PRIMARY KEY AUTO INCREMENT,
|
||||
buyOrderId VARCHAR(36) UNIQUE NOT NULL,
|
||||
pageId INT NOT NULL,
|
||||
price FLOAT NOT NULL,
|
||||
volume INT NOT NULL,
|
||||
fee FLOAT NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (userId) REFERENCES users(userId)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS sellOrders(
|
||||
rowId INT PRIMARY KEY AUTO INCREMENT,
|
||||
sellOrderId VARCHAR(36) UNIQUE NOT NULL,
|
||||
price FLOAT NOT NULL,
|
||||
fee FLOAT NOT NULL,
|
||||
FOREIGN KEY (userId) REFERENCES users(userId)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS sellOrdersItems(
|
||||
rowId INT PRIMARY KEY AUTO INCREMENT,
|
||||
cardId VARCHAR(36) NOT NULL,
|
||||
FOREIGN KEY (sellOrderId) REFERENCES sellOrders(sellOrderId) NOT NULL,
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS transactions(
|
||||
rowId 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 buyOrders(buyOrderId) NOT NULL,
|
||||
FOREIGN KEY (sellOrderId) REFERENCES sellOrders(sellOrderId) NOT NULL
|
||||
);
|
3
db/createPeersDatabase.sql
Normal file
3
db/createPeersDatabase.sql
Normal file
@ -0,0 +1,3 @@
|
||||
CREATE DATABASE IF NOT EXISTS peers;
|
||||
USE peers;
|
||||
CREATE TABLE IF NOT EXISTS peers
|
12
db/getBuyOrders.sql
Normal file
12
db/getBuyOrders.sql
Normal file
@ -0,0 +1,12 @@
|
||||
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;
|
11
db/getSellOrders.sql
Normal file
11
db/getSellOrders.sql
Normal file
@ -0,0 +1,11 @@
|
||||
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
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
wikipedia
|
0
static/client.js
Normal file
0
static/client.js
Normal file
33
wikideck/Market/BuyOrder.py
Normal file
33
wikideck/Market/BuyOrder.py
Normal file
@ -0,0 +1,33 @@
|
||||
import json
|
||||
|
||||
from Market.Order import Order
|
||||
|
||||
class BuyOrder(Order):
|
||||
def __init__(self, cardId=None, price=0, volume=0, fee=0):
|
||||
super().__init__(
|
||||
orderType = "buy",
|
||||
cardId = cardId,
|
||||
fee = fee
|
||||
)
|
||||
self.price = price
|
||||
self.volume = volume
|
||||
|
||||
def add_transaction(self, transaction):
|
||||
if len(self.transactions) < self.volume:
|
||||
# TODO: update database
|
||||
self.transactions.append(transaction)
|
||||
else:
|
||||
raise self.Fulfilled("Order volume has already been fulfilled.")
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps({
|
||||
"orderId": str(self.orderId),
|
||||
"timestamp": str(self.timestamp)
|
||||
"userId": str(self.userId)
|
||||
"orderType": str(self.orderType),
|
||||
"cardId": self.cardId,
|
||||
"price": self.price,
|
||||
"volume": self.volume,
|
||||
"fee": self.fee,
|
||||
"transactions": [str(each) for each in self.transactions]
|
||||
})
|
39
wikideck/Market/Market.py
Normal file
39
wikideck/Market/Market.py
Normal file
@ -0,0 +1,39 @@
|
||||
from flask import Flask
|
||||
|
||||
market = Flask(__name__)
|
||||
|
||||
# TODO: figure out db
|
||||
|
||||
# TODO: users and logins
|
||||
|
||||
###
|
||||
# Submit an order to the market.
|
||||
# This method calls the Order().validate() method to validate the order schema.
|
||||
# This method performs additional validations against user table.
|
||||
# User balances are reduced immediately upon receiving a buy order from their account.
|
||||
###
|
||||
@app.post('/orders')
|
||||
def order_post():
|
||||
try:
|
||||
new_order = Order(data=request.get_json())
|
||||
new_order.validate()
|
||||
# TODO: validate order against the Mine
|
||||
# TODO: write order to db
|
||||
if newOrder.orderType == 'buy':
|
||||
# TODO: reduce user balance by ((price + fee) * volume)
|
||||
continue
|
||||
return 200
|
||||
# TODO: exceptions
|
||||
except mariadb.Error as e:
|
||||
return e, 500
|
||||
|
||||
###
|
||||
# Retrieve one or more orders.
|
||||
###
|
||||
@app.get('/orders')
|
||||
def orders_get():
|
||||
# TODO: order queries
|
||||
return 200
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
32
wikideck/Market/Order.py
Normal file
32
wikideck/Market/Order.py
Normal file
@ -0,0 +1,32 @@
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Order(ABC):
|
||||
def __init__(self, orderId=uuid.uuid4(),
|
||||
timestamp=datetime.datetime.now(datetime.timezone.utc)
|
||||
userId=None, orderType=None, cardId=None, fee=0, transactions=[], data={}):
|
||||
self.orderId = orderId
|
||||
self.timestamp = timestamp
|
||||
self.userId = userId
|
||||
self.orderType = orderType
|
||||
self.cardId = cardId
|
||||
self.fee = fee
|
||||
self.transactions = transactions
|
||||
|
||||
@abstractmethod
|
||||
def add_transaction(self, transaction):
|
||||
continue
|
||||
|
||||
@abstractmethod
|
||||
def validate(self):
|
||||
continue
|
||||
|
||||
@abstractmethod
|
||||
def load_from_data(self):
|
||||
continue
|
||||
|
||||
@abstractmethod
|
||||
def __str__(self):
|
||||
continue
|
30
wikideck/Market/SellOrder.py
Normal file
30
wikideck/Market/SellOrder.py
Normal file
@ -0,0 +1,30 @@
|
||||
import json
|
||||
|
||||
class SellOrder():
|
||||
def __init__(self, cardId=None, items=[], price=0, fee=0):
|
||||
super().__init__(
|
||||
orderType = "sell",
|
||||
cardId = cardId,
|
||||
fee = fee
|
||||
)
|
||||
self.items = items
|
||||
self.price = price
|
||||
|
||||
def add_transaction(self, transaction):
|
||||
if transaction.cardId in self.items:
|
||||
# TODO: update database
|
||||
self.items.pop(transaction.cardId)
|
||||
self.transactions.append(transaction)
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps({
|
||||
"orderId": str(self.orderId),
|
||||
"timestamp": str(self.timestamp),
|
||||
"userId": str(self.userId),
|
||||
"orderType": str(self.orderType),
|
||||
"cardId": self.cardId,
|
||||
"items": self.items,
|
||||
"price": self.price,
|
||||
"fee": self.fee,
|
||||
"transactions": [str(each) for each in self.transactions]
|
||||
})
|
107
wikideck/Mine/Block.py
Normal file
107
wikideck/Mine/Block.py
Normal file
@ -0,0 +1,107 @@
|
||||
from card import Card
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
|
||||
class Block():
|
||||
def __init__(self, blockId=uuid.uuid4(), previousHash=None,
|
||||
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
|
||||
)
|
||||
)
|
||||
if data:
|
||||
self.load_from_data(data)
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
self.blockHash = hashlib.sha256(str(self).encode('utf-8'))
|
||||
|
||||
def mine(self):
|
||||
while int(self.blockHash.hexdigest(), 16) > 2**(256-self.difficulty):
|
||||
self.nonce += 1
|
||||
self.update()
|
||||
|
||||
###
|
||||
# Validate the internal block structure.
|
||||
# This method confirms that the correct data types have been used and that
|
||||
# values are basically valid.
|
||||
# Further validations against the existing chain must be done elsewhere.
|
||||
# This method calls the Card.validate() and Transaction.validate() methods to validate the
|
||||
# respective schemas.
|
||||
###
|
||||
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):
|
||||
raise self.Invalid("Hash does not meet difficulty requirement.")
|
||||
# TODO: validate timestamp is timestamp
|
||||
if not self.timestamp < datetime.datetime.now():
|
||||
raise self.Invalid("Timestamp in the future.")
|
||||
if not self.height > 0:
|
||||
raise self.Invalid("Height less than 0.")
|
||||
if not self.difficulty > 0:
|
||||
raise self.Invalid("Difficulty less than 0.")
|
||||
if not self.nonce > 0:
|
||||
raise self.Invalid("Nonce less than 0.")
|
||||
self.card.validate()
|
||||
for transaction in self.transactions:
|
||||
transaction.validate()
|
||||
|
||||
def load_from_data(self, data):
|
||||
self.blockId = uuid.UUID(data['blockId'])
|
||||
self.previousHash = data['previousHash']
|
||||
self.timestamp = datetime.datetime.strptime(
|
||||
data['timestamp'],
|
||||
"%Y-%m-%d %H:%M:%S.%f%z"
|
||||
)
|
||||
self.height = data['height']
|
||||
self.difficulty = data['difficulty']
|
||||
self.nonce = data['nonce']
|
||||
self.card = Card(
|
||||
cardId = uuid.UUID(data['card']['cardId']),
|
||||
pageId = data['card']['pageId']
|
||||
)
|
||||
self.transactions = [
|
||||
Transaction(
|
||||
transactionId = uuid.UUID(each['transactionId']),
|
||||
timestamp = datetime.datetime.strptime(
|
||||
each['timestamp'],
|
||||
"%Y-%m-%d %H:%M:%S.%f"
|
||||
),
|
||||
cardId = uuid.UUID(each['cardId']),
|
||||
sender = each['sender'],
|
||||
receiver = each['receiver'],
|
||||
signature = each['signature']
|
||||
) for each in data['transactions']
|
||||
]
|
||||
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)
|
||||
"previousHash": self.previousHash,
|
||||
"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]
|
||||
})
|
||||
|
||||
class Invalid(Exception):
|
||||
def __init__(message, status_code=406):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
25
wikideck/Mine/Card.py
Normal file
25
wikideck/Mine/Card.py
Normal file
@ -0,0 +1,25 @@
|
||||
import requests
|
||||
import wikipedia
|
||||
import uuid
|
||||
|
||||
class Card():
|
||||
def __init__(self, cardId=uuid.uuid4(), pageId=wikipedia.page(wikipedia.random()).pageid):
|
||||
self.cardId = cardId
|
||||
self.pageId = pageId
|
||||
|
||||
def validate(self):
|
||||
try:
|
||||
wikipedia.page(pageid=self.pageId)
|
||||
except Exception as e:
|
||||
raise self.Invalid("Page ID does not match a Wikipedia page.")
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps({
|
||||
"cardId": str(self.cardId),
|
||||
"pageId": self.pageId
|
||||
})
|
||||
|
||||
class Invalid(Exception):
|
||||
def __init__(self, message, status_code=406):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
158
wikideck/Mine/Mine.py
Normal file
158
wikideck/Mine/Mine.py
Normal file
@ -0,0 +1,158 @@
|
||||
from flask import Flask
|
||||
import json
|
||||
|
||||
mine = Flask(__name__)
|
||||
db = mariadb.connect(**config)
|
||||
|
||||
@app.get('/')
|
||||
def mine():
|
||||
# TODO: return a page to mine through the browser
|
||||
return 200
|
||||
|
||||
###
|
||||
# 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')
|
||||
def blocks_post():
|
||||
previousBlock = get_last_block()
|
||||
try:
|
||||
newBlock = Block(data=request.get_json())
|
||||
newBlock.validate()
|
||||
previousBlock = db.get_last_block()
|
||||
if newBlock.previousHash != previousBlock.blockHash:
|
||||
raise Block.Invalid(
|
||||
f"Incorrect previous hash - should be {previousBlock.blockHash}."
|
||||
)
|
||||
if newBlock.timestamp <= previousBlock.timestamp:
|
||||
raise Block.Invalid(
|
||||
"Timestamp is later than previous block."
|
||||
)
|
||||
if newBlock.height != previousBlock.height + 1:
|
||||
raise Block.Invalid(
|
||||
f"Incorrect block height - should be {previousBlock.height + 1}."
|
||||
)
|
||||
if newBlock.difficulty != DIFFICULTY_REQUIREMENT:
|
||||
raise Block.Invalid(
|
||||
f"Incorrect difficulty - should be {DIFFICULTY_REQUIREMENT}."
|
||||
)
|
||||
if len(newBlock.transactions) == 0:
|
||||
raise Block.Invalid(
|
||||
"Block contains no transactions."
|
||||
)
|
||||
for transaction in newBlock.transactions:
|
||||
pendingTransaction = db.get_transaction(transaction.transactionId)
|
||||
if not pendingTransaction:
|
||||
raise Transaction.Invalid(
|
||||
f"No matching pending transaction for {transaction.transactionId}."
|
||||
)
|
||||
if not pendingTransaction.pending:
|
||||
raise Transaction.AlreadyFulfilled(
|
||||
f"Transaction {transaction.transactionId} has already been fulfilled."
|
||||
)
|
||||
if transaction.timestamp != pendingTransaction.timestamp:
|
||||
raise Transaction.Invalid(
|
||||
f"Incorrect timestamp on {transaction.transactionId}."
|
||||
)
|
||||
if transaction.cardId != pendingTransaction.cardId:
|
||||
raise Transaction.Invalid(
|
||||
f"Incorrect cardId on {transaction.transactionId}."
|
||||
)
|
||||
if transaction.sender != pendingTransaction.sender:
|
||||
raise Transaction.Invalid(
|
||||
f"Incorrect sender on {transaction.transactionId}."
|
||||
)
|
||||
if transaction.recipient != pendingTransaction.recipient:
|
||||
raise Transaction.Invalid(
|
||||
f"Incorrect recipient on {transaction.transactionId}."
|
||||
)
|
||||
if transaction.signature != pendingTransaction.signature:
|
||||
raise Transaction.Invalid(
|
||||
f"Incorrect signature on {transaction.transactionId}."
|
||||
)
|
||||
# TODO: write to database
|
||||
with open(f"{DATA_PATH}/{newBlock.blockId}.json", 'w') as f:
|
||||
f.write(str(newBlock))
|
||||
# TODO: update peers
|
||||
return str(newBlock), 200
|
||||
except: Transaction.Invalid as e:
|
||||
return e, e.statusCode
|
||||
except: Transaction.AlreadyFulfilled as e:
|
||||
return e, e.statusCode
|
||||
except: Card.Invalid as e:
|
||||
return e, e.statusCode
|
||||
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 get_pending_transactions()
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
###
|
||||
# Retrieve card data
|
||||
###
|
||||
@app.get('/cards')
|
||||
def cards_get():
|
||||
# TODO: query cards
|
||||
return 200
|
||||
|
||||
###
|
||||
# Submit a transaction to be mined in a block.
|
||||
# This method performs a number of validations on the submitted transaction and returns
|
||||
# a status code result.
|
||||
###
|
||||
@app.put('/transactions')
|
||||
def transactions_put():
|
||||
try:
|
||||
newTransaction = Transaction(data=request.get_json())
|
||||
newTransaction.validate()
|
||||
# TODO: validate transaction against blockchain
|
||||
# TODO: add transaction to database
|
||||
# TODO: update peers?
|
||||
return 200
|
||||
except Transaction.Unauthorized as e:
|
||||
return e, e.statusCode
|
||||
except Transaction.Invalid as e:
|
||||
return e, e.statusCode
|
||||
|
||||
###
|
||||
# Retrieve a transaction.
|
||||
###
|
||||
@app.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')
|
||||
def peers_post():
|
||||
# TODO: validate peer
|
||||
# TODO: add peers to database
|
||||
return 200
|
||||
|
||||
@app.get('/peers')
|
||||
def peers_get():
|
||||
# TODO: query peers
|
||||
return 200
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
74
wikideck/Mine/Transaction.py
Normal file
74
wikideck/Mine/Transaction.py
Normal file
@ -0,0 +1,74 @@
|
||||
import datetime
|
||||
import data
|
||||
import uuid
|
||||
|
||||
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):
|
||||
self.transactionId = transactionId
|
||||
self.timestamp = timestamp
|
||||
self.cardId = cardId
|
||||
self.sender = sender
|
||||
self.receiver = receiver
|
||||
self.signature = signature
|
||||
if data:
|
||||
self.load_from_data(data)
|
||||
|
||||
def validate(self):
|
||||
# TODO: validate transactionId
|
||||
# TODO: validate timestamp
|
||||
# TODO: validate cardId
|
||||
# TODO: validate sender
|
||||
# TODO: validate receiver
|
||||
# TODO: validate signature
|
||||
if False:
|
||||
raise self.Unauthorized()
|
||||
elif False:
|
||||
raise self.Invalid()
|
||||
|
||||
def sign(self, privateKey):
|
||||
# TODO: use rsa private key to sign block
|
||||
return None
|
||||
|
||||
def load_from_data(self, data):
|
||||
self.transactionId = uuid.UUID(data['transactionId'])
|
||||
self.timestamp = datetime.datetime.strptime(
|
||||
data['timestamp'],
|
||||
"%Y-%m-%d %H:%M:%S.%f%z"
|
||||
)
|
||||
self.cardId = uuid.UUID(data['cardId'])
|
||||
self.sender = data['sender']
|
||||
self.receiver = data['receiver']
|
||||
self.signature = data['signature']
|
||||
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
class Unauthorized(Exception):
|
||||
def __init__(self, message, statusCode=403):
|
||||
super().__init__(message)
|
||||
self.statusCode = statusCode
|
||||
|
||||
class Invalid(Exception):
|
||||
def __init__(self, message, statusCode=400):
|
||||
super().__init__(message)
|
||||
self.statusCode = statusCode
|
||||
|
||||
class AlreadyFulfilled(Exception):
|
||||
def __init__(self, message, statusCode=400):
|
||||
super().__init__(message)
|
||||
self.statusCode = statusCode
|
||||
|
||||
class Abandoned(Exception):
|
||||
def __init__(self, message, statusCode=500):
|
||||
super().__init__(message)
|
||||
self.statusCode = statusCode
|
21
wikideck/OrderMatcher/OrderMatcher.py
Normal file
21
wikideck/OrderMatcher/OrderMatcher.py
Normal file
@ -0,0 +1,21 @@
|
||||
###
|
||||
# The OrderMatcher converts orders to transactions.
|
||||
# When new orders arrive on the market, the order matcher is triggered to search the
|
||||
# database for an existing order to fulfill the new one.
|
||||
# If the order matcher finds a match for the order, it creates a transaction and
|
||||
# sends it to the mine.
|
||||
###
|
||||
class OrderMatcher():
|
||||
def __init__(self):
|
||||
return
|
||||
|
||||
def match_orders(self):
|
||||
# TODO: find matching orders
|
||||
# TODO: generate transactions
|
||||
# TODO: send transactions to mine
|
||||
|
||||
if __name__ == '__main__':
|
||||
orderMatcher = OrderMatcher()
|
||||
while True:
|
||||
orderMatcher.match_orders()
|
||||
time.sleep(10)
|
39
wikideck/StatusChecker/StatusChecker.py
Normal file
39
wikideck/StatusChecker/StatusChecker.py
Normal file
@ -0,0 +1,39 @@
|
||||
###
|
||||
# The StatusChecker updates the market database based on the mine database.
|
||||
# It will periodically query for pending transactions, and then confirm their status against
|
||||
# the Mine API.
|
||||
# It will also update user balances upon transaction completion.
|
||||
###
|
||||
|
||||
class StatusChecker():
|
||||
def __init__(self):
|
||||
return
|
||||
|
||||
def update_pending_transactions(self):
|
||||
for pendingTransaction in self.db.get_pending_transactions():
|
||||
self.check_transaction_status(pendingTransaction)
|
||||
|
||||
def check_transaction_status(transaction):
|
||||
transaction = Transaction(
|
||||
data = transaction
|
||||
)
|
||||
# TODO: get transaction data from mine
|
||||
if not mine_transaction.is_pending:
|
||||
db.update_transaction_pending_status(
|
||||
transactionId=transactionId,
|
||||
is_pending=False
|
||||
)
|
||||
if not mine_transaction.block_id:
|
||||
raise Transaction.Abandoned(
|
||||
f"Transaction {pendingTransaction.transactionId} was abandoned."
|
||||
)
|
||||
else:
|
||||
# TODO: increase seller balance by (price - fee)
|
||||
# TODO: increase market balance by (fee)
|
||||
return
|
||||
|
||||
if __name__ == '__main__':
|
||||
statusChecker = StatusChecker()
|
||||
while True:
|
||||
transactionStatusChecker.update_pending_transactions()
|
||||
time.sleep(INTERVAL)
|
30
wikideck/app.py
Normal file
30
wikideck/app.py
Normal file
@ -0,0 +1,30 @@
|
||||
import os
|
||||
import dotenv
|
||||
|
||||
ROLE = os.getenv("ROLE", None)
|
||||
INTERVAL = os.getenv("INTERVAL", 15)
|
||||
|
||||
if __name__ == "__main__":
|
||||
dotenv.load_dotenv()
|
||||
if ROLE == "market":
|
||||
from Market.Market import market
|
||||
market.run()
|
||||
elif ROLE == "mine":
|
||||
from Mine.Mine import mine
|
||||
mine.run()
|
||||
elif ROLE == "order_matcher":
|
||||
from OrderMatcher.OrderMatcher import OrderMatcher
|
||||
import time
|
||||
orderMatcher = OrderMatcher()
|
||||
while True:
|
||||
orderMatcher.match_orders()
|
||||
time.sleep(INTERVAL)
|
||||
elif ROLE == "status_checker":
|
||||
from StatusChecker.StatusChecker import StatusChecker
|
||||
import time
|
||||
statusChecker = StatusChecker()
|
||||
while True:
|
||||
statusChecker.update_pending_transactions()
|
||||
time.sleep(INTERVAL)
|
||||
else:
|
||||
raise Exception("Role must be one of: market, mine, order_matcher, status_checker")
|
Loading…
Reference in New Issue
Block a user