diff --git a/.gitignore b/.gitignore index 5fd039d..ed8f1b9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ venv/ __pycache__/ .vscode - +.idea .DS_Store # Byte-compiled / optimized / DLL files @@ -172,4 +172,6 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ + +logs/ \ No newline at end of file diff --git a/app/codes/auth/auth.py b/app/codes/auth/auth.py index fe16f93..b23d57c 100644 --- a/app/codes/auth/auth.py +++ b/app/codes/auth/auth.py @@ -6,6 +6,17 @@ from ...nvalues import ZERO_ADDRESS +def get_node_wallet_public(): + wallet = get_wallet() + if wallet: + return { + 'address': wallet['address'], + 'public': wallet['public'] + } + else: + return None + + def get_node_wallet_address(): wallet = get_wallet() if wallet: @@ -36,7 +47,6 @@ def get_auth(): 'public': wallet['public'], } auth_data['signature'] = sign_object(private_key, auth_data) - print('auth', auth_data) return auth_data except: auth_data = {} diff --git a/app/codes/blockchain.py b/app/codes/blockchain.py index be56df6..3d0122a 100644 --- a/app/codes/blockchain.py +++ b/app/codes/blockchain.py @@ -6,12 +6,17 @@ import sqlite3 -from ..constants import NEWRL_DB +from app.codes.clock.global_time import get_corrected_time_ms +from app.codes.receiptmanager import update_receipts_in_state + +from .fs.temp_manager import remove_block_from_temp +from ..constants import BLOCK_TIME_INTERVAL_SECONDS, NEWRL_DB, NO_BLOCK_TIMEOUT from .utils import get_time_ms from .crypto import calculate_hash -from .state_updater import update_db_states +from .state_updater import update_db_states, update_trust_scores from .utils import get_time_ms from .auth.auth import get_node_wallet_address +from .fs.mempool_manager import remove_transaction_from_mempool class Blockchain: @@ -20,7 +25,7 @@ class Blockchain: def __init__(self) -> None: self.chain = [] - def create_block(self, cur, block, block_hash): + def create_block(self, cur, block, block_hash, creator_wallet=None): """Create a block and store to db""" transactions_hash = self.calculate_hash(block['text']['transactions']) db_block_data = ( @@ -29,9 +34,10 @@ def create_block(self, cur, block, block_hash): block['proof'], block['previous_hash'], block_hash, + creator_wallet, transactions_hash ) - cur.execute('INSERT OR IGNORE INTO blocks (block_index, timestamp, proof, previous_hash, hash, transactions_hash) VALUES (?, ?, ?, ?, ?, ?)', db_block_data) + cur.execute('INSERT OR IGNORE INTO blocks (block_index, timestamp, proof, previous_hash, hash, creator_wallet, transactions_hash) VALUES (?, ?, ?, ?, ?, ?, ?)', db_block_data) return block def get_block(self, block_index): @@ -40,13 +46,19 @@ def get_block(self, block_index): cur = con.cursor() block_cursor = cur.execute( 'SELECT * FROM blocks where block_index=?', (block_index,)).fetchone() + + if block_cursor is None: + return None block = dict(block_cursor) transactions_cursor = cur.execute( 'SELECT * FROM transactions where block_index=?', (block_index,)).fetchall() transactions = [dict(ix) for ix in transactions_cursor] + transactions = list(map(lambda t: + {'transaction': t, 'signatures': [] if t['signatures'] is None else json.loads(t['signatures'])}, + transactions)) block['text'] = { - 'transactions': transactions + 'transactions': transactions, } return block @@ -96,11 +108,10 @@ def mine_block(self, cur, text, fees=0): block = { 'index': last_block_index + 1, - 'timestamp': get_time_ms(), + 'timestamp': get_corrected_time_ms(), 'proof': 0, 'text': text, 'creator_wallet': get_node_wallet_address(), - 'fees': fees, 'previous_hash': last_block_hash } @@ -110,6 +121,54 @@ def mine_block(self, cur, text, fees=0): block = self.create_block(cur, block, block_hash) return block + def propose_block(self, cur, text): + """Propose a new block and not add to chain""" + last_block_cursor = cur.execute( + 'SELECT block_index, hash FROM blocks ORDER BY block_index DESC LIMIT 1') + last_block = last_block_cursor.fetchone() + last_block_index = last_block[0] if last_block is not None else 0 + last_block_hash = last_block[1] if last_block is not None else 0 + print(f'Proposing a block with index {last_block_index + 1}') + + block = { + 'index': last_block_index + 1, + 'timestamp': get_corrected_time_ms(), + 'proof': 0, + 'text': text, + 'creator_wallet': get_node_wallet_address(), + 'previous_hash': last_block_hash + } + return block + + def mine_empty_block(self, new_block_timestamp=None): + """Mine an empty block""" + print("Mining empty block") + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() + last_block_cursor = cur.execute( + 'SELECT block_index, hash, timestamp FROM blocks ORDER BY block_index DESC LIMIT 1') + last_block = last_block_cursor.fetchone() + con.close() + last_block_index = last_block[0] if last_block is not None else 0 + last_block_hash = last_block[1] if last_block is not None else 0 + last_block_timestamp = last_block[2] if last_block is not None else 0 + + EMPTY_BLOCK_NONCE = 42 + + if new_block_timestamp is None: + new_block_timestamp = int(last_block_timestamp) + (BLOCK_TIME_INTERVAL_SECONDS + NO_BLOCK_TIMEOUT) * 1000 + + block = { + 'index': last_block_index + 1, + 'timestamp': new_block_timestamp, + 'proof': EMPTY_BLOCK_NONCE, + 'text': {"transactions": [], "signatures": []}, + 'creator_wallet': None, + 'previous_hash': last_block_hash + } + + return block + def get_latest_ts(self, cur=None): """Get the timestamp of latest block""" should_close_db_conn = False @@ -129,12 +188,14 @@ def get_latest_ts(self, cur=None): return ts -def add_block(cur, block, block_hash=None): +def add_block(cur, block, block_hash): """Add a block to db, add transactions and update states""" + last_block = get_last_block(cur) + if last_block is not None and last_block['hash'] != block['previous_hash']: + print('Previous block hash does not match current block data') + return # Needed for backward compatibility of blocks block_index = block['block_index'] if 'block_index' in block else block['index'] - if not block_hash: - block_hash = block['hash'] if 'hash' in block else '' # transactions_hash = block['transactions_hash'] if 'transactions_hash' in block else '' transactions_hash = calculate_hash(block['text']['transactions']) print('Adding block', block_index) @@ -144,10 +205,19 @@ def add_block(cur, block, block_hash=None): block['proof'], block['previous_hash'], block_hash, + block['creator_wallet'], transactions_hash ) - cur.execute('INSERT OR IGNORE INTO blocks (block_index, timestamp, proof, previous_hash, hash, transactions_hash) VALUES (?, ?, ?, ?, ?, ?)', db_block_data) + cur.execute('INSERT OR IGNORE INTO blocks (block_index, timestamp, proof, previous_hash, hash, creator_wallet, transactions_hash) VALUES (?, ?, ?, ?, ?, ?, ?)', db_block_data) update_db_states(cur, block) + # update_receipts_in_state(cur, block) + # update_trust_scores(cur, block) + + for transaction in block['text']['transactions']: + transaction = transaction['transaction'] + transaction_code = transaction['transaction_code'] if 'transaction_code' in transaction else transaction['trans_code'] + remove_transaction_from_mempool(transaction_code) + remove_block_from_temp(block_index) def get_last_block_index(): @@ -162,20 +232,41 @@ def get_last_block_index(): return last_block[0] if last_block is not None else 0 -def get_last_block_hash(): +def get_last_block(cur=None): """Get last block hash from db""" - con = sqlite3.connect(NEWRL_DB) - cur = con.cursor() + cursor_opened_inside = False + if cur is None: + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() + cursor_opened_inside = True last_block_cursor = cur.execute( - 'SELECT block_index, hash FROM blocks ORDER BY block_index DESC LIMIT 1' + 'SELECT block_index, hash, timestamp FROM blocks ORDER BY block_index DESC LIMIT 1' ) last_block = last_block_cursor.fetchone() - con.close() + + if cursor_opened_inside: + con.close() if last_block is not None: return { 'index': last_block[0], - 'hash': last_block[1] + 'hash': last_block[1], + 'timestamp': last_block[2] } else: return None + + +def block_exists(block_index): + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() + block_cursor = cur.execute( + 'SELECT * FROM blocks where block_index=?', (block_index,)).fetchone() + + if block_cursor is not None: + block_exists = True + else: + block_exists = False + + con.close() + return block_exists diff --git a/app/codes/chainscanner.py b/app/codes/chainscanner.py index bbabcc6..80d430d 100644 --- a/app/codes/chainscanner.py +++ b/app/codes/chainscanner.py @@ -71,24 +71,66 @@ def download_state(): balances_cursor = cur.execute('SELECT * FROM balances').fetchall() balances = [dict(ix) for ix in balances_cursor] + + contracts_cursor = cur.execute('SELECT * FROM contracts').fetchall() + contracts = [dict(ix) for ix in contracts_cursor] state = { 'wallets': wallets, 'tokens': tokens, 'balances': balances, + 'contracts': contracts } return state +def get_block(block_index): + chain = Blockchain() + return chain.get_block(block_index) + def get_transaction(transaction_code): con = sqlite3.connect(NEWRL_DB) con.row_factory = sqlite3.Row cur = con.cursor() transaction_cursor = cur.execute( 'SELECT * FROM transactions where transaction_code=?', (transaction_code,)).fetchone() + if transaction_cursor is None: + return None return dict(transaction_cursor) +def get_wallet(wallet_address): + con = sqlite3.connect(NEWRL_DB) + con.row_factory = sqlite3.Row + cur = con.cursor() + cur = cur.execute( + 'SELECT * FROM wallets where wallet_address=?', (wallet_address,)).fetchone() + if cur is None: + return None + return dict(cur) + + +def get_token(token_code): + con = sqlite3.connect(NEWRL_DB) + con.row_factory = sqlite3.Row + cur = con.cursor() + cur = cur.execute( + 'SELECT * FROM tokens where tokencode=?', (token_code,)).fetchone() + if cur is None: + return None + return dict(cur) + +def get_contract(contract_address): + con = sqlite3.connect(NEWRL_DB) + con.row_factory = sqlite3.Row + cur = con.cursor() + cur = cur.execute( + 'SELECT * FROM contracts where address=?', (contract_address,)).fetchone() + if cur is None: + return None + return dict(cur) + + def download_chain(): con = sqlite3.connect(NEWRL_DB) con.row_factory = sqlite3.Row diff --git a/app/codes/clock/global_time.py b/app/codes/clock/global_time.py index 008afec..9172a6a 100644 --- a/app/codes/clock/global_time.py +++ b/app/codes/clock/global_time.py @@ -1,8 +1,6 @@ -import sys import time import requests -import threading -from ...constants import BLOCK_TIME_INTERVAL_SECONDS, MAX_ALLOWED_TIME_DIFF_SECONDS, NO_RECEIPT_COMMITTEE_TIMEOUT, TIME_DIFF_WITH_GLOBAL +from ...constants import TIME_DIFF_WITH_GLOBAL_FILE def get_global_epoch(): @@ -18,39 +16,36 @@ def get_local_epoch(): return epoch_time -def no_receipt_timeout(): - print('No receipts received. Timing out.') - - -def mine(): - print('Mining block.') - - -def start_receipt_timeout(): - timer = threading.Timer(NO_RECEIPT_COMMITTEE_TIMEOUT, no_receipt_timeout) - timer.start() - - -def start_mining_clock(): - mine() - timer = threading.Timer(BLOCK_TIME_INTERVAL_SECONDS, start_mining_clock) - timer.start() +def get_time_stats(): + return { + 'local_time_ms': get_local_epoch() * 1000, + 'corrected_time_ms': get_corrected_time_ms(), + } +def get_corrected_time_ms(): + return 1000 * (get_local_epoch() - get_time_difference()) def get_time_difference(): """Return the time difference between local and global in seconds""" + try: + with open(TIME_DIFF_WITH_GLOBAL_FILE, 'r') as f: + return int(f.read()) + except: + global_epoch = get_global_epoch() + local_epoch = get_local_epoch() + diff = global_epoch - local_epoch + with open(TIME_DIFF_WITH_GLOBAL_FILE, 'w') as f: + f.write(str(diff)) + return diff + + +def sync_timer_clock_with_global(): global_epoch = get_global_epoch() local_epoch = get_local_epoch() - return global_epoch - local_epoch - - -def update_time_difference(): - TIME_DIFF_WITH_GLOBAL = get_time_difference() - print('Time difference with global is ', TIME_DIFF_WITH_GLOBAL) - if TIME_DIFF_WITH_GLOBAL > MAX_ALLOWED_TIME_DIFF_SECONDS: - print('System time is not syncronised. Time difference in seconds: ', TIME_DIFF_WITH_GLOBAL) - quit() - return True + diff = global_epoch - local_epoch + with open(TIME_DIFF_WITH_GLOBAL_FILE, 'w') as f: + f.write(str(diff)) + print('Synced clock. Time difference is', diff) if __name__ == '__main__': diff --git a/app/codes/consensus/committee.py b/app/codes/consensus/committee.py index 63d1aad..470f4f3 100644 --- a/app/codes/consensus/committee.py +++ b/app/codes/consensus/committee.py @@ -1,7 +1,6 @@ """Committee selection code""" from constants import MY_ADDRESS -from ..clock.global_time import get_local_epoch from ..p2p.peers import get_peers diff --git a/app/codes/consensus/consensus.py b/app/codes/consensus/consensus.py index ba896fc..36ac081 100644 --- a/app/codes/consensus/consensus.py +++ b/app/codes/consensus/consensus.py @@ -1,11 +1,14 @@ """Consensus related functions""" +from app.nvalues import ASQI_WALLET +from ..clock.global_time import get_corrected_time_ms from ..signmanager import sign_object -from ..blockchain import calculate_hash +from ..blockchain import calculate_hash, get_last_block from ..validator import validate_block_receipts from ..fs.mempool_manager import append_receipt_to_block, get_receipts_from_storage -from ...constants import MINIMUM_ACCEPTANCE_RATIO, MINIMUM_ACCEPTANCE_VOTES +from ...constants import BLOCK_RECEIVE_TIMEOUT_SECONDS, BLOCK_TIME_INTERVAL_SECONDS, COMMITTEE_SIZE, MINIMUM_ACCEPTANCE_RATIO from ..auth.auth import get_wallet +from ..minermanager import get_committee_for_current_block, get_miner_for_current_block try: @@ -17,19 +20,34 @@ public_key = wallet_data['public'] private_key = wallet_data['private'] -def generate_block_receipt(block): + +def generate_block_receipt(block, vote=1): receipt_data = { 'block_index': block['index'], 'block_hash': calculate_hash(block), - 'vote': 1 + 'vote': vote } return { "data": receipt_data, "public_key": public_key, - "signature": sign_object(private_key, receipt_data) + "signature": sign_object(private_key, receipt_data), + 'timestamp': get_corrected_time_ms() } +def add_my_receipt_to_block(block): + """Add node's receipt to the block. Return receipt if receipt added. None if receipt already present.""" + my_receipt = generate_block_receipt(block['data']) + my_receipt_already_added = False + for receipt in block['receipts']: + if receipt['public_key'] == my_receipt['public_key']: + my_receipt_already_added = True + if not my_receipt_already_added: + block['receipts'].append(my_receipt) + return my_receipt + return None + + def get_node_trust_score(public_key): # TODO - Return the actual trust score of the node by lookup on public_key return 1 @@ -52,16 +70,53 @@ def get_node_trust_score(public_key): # return True + def check_community_consensus(block): - receipt_counts = validate_block_receipts(block) receipts_in_temp = get_receipts_from_storage(block['index']) - + for receipt in receipts_in_temp: append_receipt_to_block(block, receipt) - - if receipt_counts['postitive_receipt_count'] / receipt_counts['total_receipt_count'] > MINIMUM_ACCEPTANCE_RATIO: + + receipt_counts = validate_block_receipts(block) + + committee = get_committee_for_current_block() + + # TODO - Deal with the case when minimum number of committee members are not avalable + # if len(committee) < 3: + # if receipt_counts['positive_receipt_count'] > 0: + # return True + # Block is received from sentinel node after a timeout. + if len(committee) == 1: + if committee[0]['wallet_address'] == ASQI_WALLET: # Todo - Check if block is empty + return True + + if receipt_counts['positive_receipt_count'] > MINIMUM_ACCEPTANCE_RATIO * COMMITTEE_SIZE: + # TODO - Check if time elapsed has exceeded receipt cut off. Do not accept otherwise + # This is to give every node sometime to send their receipts for the block + return True + return False + + +def validate_block_miner(block): + miner_address = block['creator_wallet'] + + expected_miner = get_miner_for_current_block()['wallet_address'] + + if expected_miner is None: + return True + + print(block) + if miner_address != expected_miner: + print(f"Invalid miner {miner_address} for block. Expected {expected_miner}") + return False + return True + + +def is_timeout_block_from_sentinel_node(block): + last_block = get_last_block() + if last_block is None: + return True + time_ms_elapsed_since_last_block = get_corrected_time_ms() - int(last_block['timestamp']) + block_cuttoff_triggered = time_ms_elapsed_since_last_block > (BLOCK_TIME_INTERVAL_SECONDS + BLOCK_RECEIVE_TIMEOUT_SECONDS) * 1000 + if block['proof'] == 42 and len(block['text']['transactions']) == 0 and block_cuttoff_triggered: return True - - # if receipt_counts['postitive_receipt_count'] >= MINIMUM_ACCEPTANCE_VOTES and receipt_counts['total_receipt_count']: - # return True - return False \ No newline at end of file diff --git a/app/codes/contracts/Utils.py b/app/codes/contracts/Utils.py new file mode 100644 index 0000000..f4a7362 --- /dev/null +++ b/app/codes/contracts/Utils.py @@ -0,0 +1,37 @@ +import math + +#Min Yes Votes Voting Scheme +#ProposalAccepted - if min_yes yes votes is met +#ProposalRejected - if min_yes-1 no votes is met +def voting_scheme_one(self,callparams): + + min_yes_proportion = callparams['voting_scheme_params']['min_yes_votes'] + total_votes = callparams['total_votes'] + current_yes_votes = callparams['current_yes_votes'] + current_no_votes = callparams['current_no_votes'] + + min_yes_votes = math.ceil((total_votes*min_yes_proportion)/100) + if(current_yes_votes >= min_yes_votes): + return 1 + elif(current_no_votes>= min_yes_votes-1): + return -1 + else: + return 0 + # row = cur.execute(f'''select yes_votes as yes_votes,no_votes as no_votes,abstain_votes as abstain_votes,max_votes as max_votes from proposal where proposal_id=?''', callparams['proposal_id']) + # #maxNo votes and minYes votes + # #change min yes and no votes to dao params + # data = row.fetchone() + #check if min votes are met +def voting_scheme_two(self,cur,callparams): + + pass +def voting_scheme_three(self,cur,callparams): + pass + +def voting_scheme_token(self,cur,callparams): + ''' + TODO + params : + function + ''' + pass \ No newline at end of file diff --git a/app/codes/contracts/contract_master.py b/app/codes/contracts/contract_master.py index 9c8f0d9..bbac462 100644 --- a/app/codes/contracts/contract_master.py +++ b/app/codes/contracts/contract_master.py @@ -19,6 +19,8 @@ class ContractMaster(): codehash="" #this is the hash of the entire document excluding this line, it is same for all instances of this class def __init__(self, template, version, contractaddress=None): self.address=contractaddress #this is for instances of this class created for tx creation and other non-chain work + self.type=1 + self.template=template if contractaddress: #as in this is an existing contract con = sqlite3.connect(NEWRL_DB) cur = con.cursor() @@ -82,7 +84,7 @@ def setup(self, cur, callparams): legpars=json.dumps(contractparams['legalparams']) signstr=json.dumps(contractparams['signatories']) oraclestr = json.dumps(contractparams['oracleids']) - qparams=(self.address, + qparams=((self.address).strip('\"'), contractparams['creator'], contractparams['ts_init'], contractparams['name'], diff --git a/app/codes/contracts/dao_main_template.py b/app/codes/contracts/dao_main_template.py new file mode 100644 index 0000000..3a14ee0 --- /dev/null +++ b/app/codes/contracts/dao_main_template.py @@ -0,0 +1,316 @@ +# Abstract Class for creating DAOs +import json + +from . import Utils +from .contract_master import ContractMaster +from .dao_main_template_validator import create_proposal, vote_on_proposal +from ..db_updater import * +from abc import ABCMeta, abstractmethod + +import importlib + + +class DaoMainTemplate(ContractMaster): + __metaclass__ = ABCMeta + codehash = "" # this is the hash of the entire document excluding this line, it is same for all instances of + + # this class + + def __init__(self, contractaddress): + ContractMaster.__init__(self, self.template, self.version, contractaddress) + + @abstractmethod + def update_and_deploy(self): + raise NotImplementedError("Must override updateAndDeploy") + + def create_proposal(self, cur, callparamsip): + # Method For Creating Prosposal + + callparams = input_to_dict(callparamsip) + callparams['address']=self.address + # create_proposal(cur, callparams) + dao_pid = get_pid_from_wallet(cur, self.address) + # TODO max votes for now is hard coded + cur.execute(f'''INSERT OR REPLACE INTO PROPOSAL_DATA + (dao_person_id, function_called,params,voting_start_ts,voting_end_ts,total_votes,status) + VALUES (?, ? ,? ,? ,? ,? ,? )''', ( + dao_pid, callparams['function_called'], json.dumps(callparams['params']), callparams['voting_start_ts'], + callparams['voting_end_ts'], 10, 0)) + prop_id = cur.lastrowid + response = { + "status": 200, + "proposal_id": prop_id + } + return response + + def vote_on_proposal(self, cur, callparamsip): + callparams = input_to_dict(callparamsip) + # ToDO Voting to be saved in + callparams['address']=self.address + # vote_on_proposal(cur, callparams) + if self.dao_type==2 or self.valid_member(cur, callparams): + + member_pid = get_pid_from_wallet(cur,callparams['function_caller'][0]['wallet_address']) + proposal_id=callparams['proposal_id'] + voter_db_data = cur.execute('''Select voter_data as "voter_data",yes_votes as "yes_votes",no_votes as "no_votes",abstain_votes as "abstain_votes",total_votes as "total_votes",function_called as "function_called" from proposal_data where proposal_id = ?''', (proposal_id,)) + voter_db_data=voter_db_data.fetchone() + + + # Initializing the voter_db_data variable + if (voter_db_data[0] is None): + voter_db_data = ['{}', 0, 0, 0,voter_db_data[4],voter_db_data[5]] + voter_data = input_to_dict(json.loads(voter_db_data[0])) + yes_votes = voter_db_data[1] + no_votes = voter_db_data[2] + abstain_votes = voter_db_data[3] + total_votes = voter_db_data[4] + function_called = voter_db_data[5] + weight=1 + if(self.dao_type==2): + paramtopass={} + paramtopass['dao_id']=self.address + paramtopass['person_id']=member_pid + weight=self.get_token_lock_amount(cur,json.dumps(callparamsip)) + if(self.duplicate_check(voter_db_data[0],member_pid)): + + + if(callparams['vote']==-1): + no_votes=no_votes+weight + elif(callparams['vote']==1): + yes_votes=yes_votes+weight + else: + abstain_votes = abstain_votes +weight + + + voter_data[member_pid]={"vote":callparams['vote'],"weight":weight} + cur.execute(f'''update proposal_data set voter_data=?,yes_votes=?,no_votes=?,abstain_votes=? where proposal_id = ?''',(json.dumps(voter_data),yes_votes,no_votes,abstain_votes,proposal_id)) + + + else: + return False + + #get voting scheme params from dao params + cspecs = input_to_dict(self.contractparams['contractspecs']) + voting_schemes = cspecs['voting_schemes'] + voting_scheme_params = None + voting_scheme_selected=None + for method in voting_schemes: + if(method['function'] == function_called): + voting_scheme_params = method['params'] + voting_scheme_selected=method['voting_scheme'] + + #get total votes, current yes and no votes from proposal + voting_specs = { + 'voting_scheme_params': voting_scheme_params, + 'current_yes_votes': yes_votes, + 'current_no_votes': no_votes, + 'total_votes':total_votes + } + + funct = getattr(Utils, voting_scheme_selected) + voting_result = funct(cur, voting_specs) + #check if any condition is met + #if yes (-1 or 1) + #update the db + #execute the function + if(voting_result == 1): + cur.execute('''update proposal_data set status = ? where proposal_id= ?''',("accepted",callparamsip['proposal_id'])) + self.execute(cur, callparamsip) + if(voting_result == -1): + cur.execute('''update proposal_data set status = ? where proposal_id= ?''',("rejected",callparamsip['proposal_id'])) + + return False + + def execute(self, cur, callparamsip): + # proposal ( funct , paramsip) - votes status + # Getting proposal Data + callparams = input_to_dict(callparamsip) + proposal = cur.execute('''select function_called,params from proposal_data where proposal_id=?''', + ("".join(str(callparams['proposal_id'])),)) + proposal=proposal.fetchone() + if (proposal is None): + return False + if self.check_status(cur,callparamsip): + funct = getattr(self, proposal[0]) + funct(cur, proposal[1]) + else: + return False + + def add_member(self, cur, callparamsip): + callparams = input_to_dict(callparamsip) + dao_pid = get_pid_from_wallet(cur, self.address) + is_dao_exist = cur.execute( + '''SELECT COUNT(*) FROM dao_membership WHERE dao_person_id LIKE ? AND member_person_id LIKE ?''', + (dao_pid, callparams['member_person_id'])) + is_dao_exist=is_dao_exist.fetchone() + if(is_dao_exist[0]==0): + cur.execute('''INSERT OR REPLACE INTO dao_membership + (dao_person_id, member_person_id) + VALUES (?, ?)''', (dao_pid, callparams['member_person_id'])) + return {"status":200,"message":"Successfully added."} + else: + return {"status":500,"message":"Already exists."} + + def delete_member(self, cur, callparamsip): + callparams = input_to_dict(callparamsip) + dao_pid = get_pid_from_wallet(cur, self.address) + # Sql code to update Membership table + cur.execute('''DELETE FROM dao_membership + WHERE dao_person_id= ? + AND member_person_id= ? ''', (dao_pid, callparams['member_person_id'])) + return True + + def check_status(self, cur, callparamsip): + callparams = input_to_dict(callparamsip) + cspecs = input_to_dict(self.contractparams['contractspecs']) + # if (cspecs['voting_scheme']): + # funct = getattr(Utils, cspecs['voting_scheme']) + # result = funct(cur, callparams) + # else: + # result = Utils.voting_scheme_one(cur, callparams) + # return result + return True + + + def valid_member(self, cur, callparamsip): + callparams = input_to_dict(callparamsip) + member_pid="".join(get_pid_from_wallet(cur,callparams['function_caller'][0]['wallet_address'])) + proposal = cur.execute('''Select count(*) from dao_membership where member_person_id like ?''', [member_pid]) + proposal=proposal.fetchone() + if(proposal[0]==0): + return False + return True + + def duplicate_check(self,voter_data,member_pid): + voter_data = input_to_dict(json.loads(voter_data)) + for voter in voter_data.keys(): + if(voter==member_pid): + return False + return True + + '''Token based methods''' + # Token unique to DAO created via below method hence in template + def issue_token(self, cur, callparamsip): + ''' + TODO + params : walletId , txnHash, amount + function : check txn validity and issue dao tokens to that pid + ''' + + #call params + callparams = input_to_dict(callparamsip) + # Hash of Txn + # transferTxn = callparams['transferTxn'] + + recipient_address = callparams['recipient_address'] + amount = callparams['amount'] + # Stable Coin transfer from user to DAO + transfer_tokens_and_update_balances( + cur, recipient_address, self.address, 'NWRL', amount) + #issue tokens + dao_data=cur.execute(f'''Select dao_name as dao_name from dao_main where dao_sc_address=?''',[self.address]) + dao_data=dao_data.fetchone() + token_code = dao_data[0]+'_token' #TODO fetch dao name + tokendata={ + "tokenname": token_code, + "tokencode" : token_code, + "tokentype": '1', + "tokenattributes": {}, + "first_owner": recipient_address, + "custodian": self.address, + "legaldochash": '', + "amount_created": amount, + "value_created": '', + "disallowed": {}, + "sc_flag": False + } + # tokendata = {"tokencode": token_code, + # "first_owner": recipient_address, + # "custodian": self.address, + # "amount_created": int(amount * 100), + # "value_created": amount, + # "tokendecimal": 2 + # } + add_token(cur, tokendata) + pass + + + def get_token_lock_amount(self, cur, callparamsip): + ''' + TODO + params : pid , daoId, proposal_id + function : check if any balance, add this proposal for that token status entry with current balance + ''' + + callparams = input_to_dict(callparamsip) + dao_id = callparams['address'] + person_id=get_pid_from_wallet(cur,callparams['function_caller'][0]['wallet_address']) + lock_data=cur.execute(f'''Select amount_locked from DAO_TOKEN_LOCK where person_id=? and dao_id=?''',[person_id,dao_id]).fetchone() + return lock_data[0] + #fetch current token value locked + + def set_price(self, cur, callapramsip): + # To set the price at which token to be issued + pass + + def lock_tokens(self, cur, callparamsip): + ''' + TODO + params : person_pid, dao_id, amount, txnHash (of transfer to dao) + function : add a new row with pid and amount++ + ''' + + callparams = input_to_dict(callparamsip) + dao_id = self.address + person_id = callparams['person_id'] + amount = callparams['amount'] + # proposal_id=callparams['proposal_id'] + proposal_id=None + # Transfering Tokens from User To DAO + dao_data=cur.execute(f'''Select dao_name as dao_name from dao_main where dao_sc_address=?''',[self.address]) + dao_data=dao_data.fetchone() + token_code = dao_data[0]+'_token' #TODO fetch dao name + transfer_tokens_and_update_balances( + cur, callparams['wallet_address'], self.address, token_code, amount) + lock_data = cur.execute(f'''Select dao_id as dao_id ,person_id as person_id, amount_locked as amount_locked from DAO_TOKEN_LOCK where person_id=? and dao_id=?''', + [person_id, dao_id]).fetchone() + if lock_data is None: + cur.execute( + f'''Insert into DAO_TOKEN_LOCK (dao_id,person_id,amount_locked,wallet_address) values (?,?,?,?)''', + (dao_id,person_id,amount,callparams['wallet_address'])) + else: + amount = amount+ lock_data[2] + cur.execute(f'''update DAO_TOKEN_LOCK set amount_locked=? where person_id=? and dao_id=? ''',[amount,person_id, dao_id]) + + # update token stake table + pass + # def update_token_proposal_data(self, cur, callparamsip): + # # + # callparams = input_to_dict(callparamsip) + # dao_id = callparams['dao_id'] + # person_id = callparams['person_id'] + # proposal_id=callparams['proposal_id'] + # amount_locked=callparams['amount_locked'] + # lock_data = cur.execute(f'''Select proposal_list from DAO_TOKEN_LOCK where person_id=? and dao_id=?''', + # [person_id, dao_id]).fetchone() + # if lock_data is None: + # cur.execute( + # f'''update DAO_TOKEN_LOCK set proposal_list =? , amount_locked=? where person_id=? and dao_id=?''', + # (json.dumps({proposal_id}))) + # else: + # flag=False + # lock_data=json.load(lock_data) + # for i in lock_data['proposal_list']: + # if(i==proposal_id): + # flag=True + # pass + # if not flag: + # lock_data['proposal_list'].append(proposal_id) + # cur.execute( + # f'''update DAO_TOKEN_LOCK set proposal_list =? , amount_locked=? where person_id=? and dao_id=?''', + # (lock_data['proposal_list'])) + # cur.execute() + # + # + # pass + diff --git a/app/codes/contracts/dao_main_template_validator.py b/app/codes/contracts/dao_main_template_validator.py new file mode 100644 index 0000000..bbca5cd --- /dev/null +++ b/app/codes/contracts/dao_main_template_validator.py @@ -0,0 +1,50 @@ +from ..db_updater import * + + +def create_proposal(cur, callparams): + # We can have special roles assigned to the person so that some of them can only. + dao_pid = get_pid_from_wallet(cur, callparams['address']) + is_not_valid_member = (dao_member_dup_check(cur, callparams['params'], dao_pid) == 0) + if is_not_valid_member: + return {"status": 406, "message": "Not a member of DAO."} + + +def vote_on_proposal( cur, callparams): + dao_pid = get_pid_from_wallet(cur, callparams['address']) + is_not_valid_member = (dao_member_dup_check(cur, callparams['params'], dao_pid) == 0) + if is_not_valid_member: + return {"status": 406, "message": "Not a member of DAO."} + + +def add_member( cur, callparamsip): + callparams = input_to_dict(callparamsip) + dao_pid = get_pid_from_wallet(cur, callparams['address']) + is_dao_exist = dao_member_dup_check(cur, callparams['params'], dao_pid) + if pid_check(callparams, cur) == 0: + return {"status": 406, "message": "Personid doesn't exists."} + if is_dao_exist[0] != 0: + return {"status": 406, "message": "Member already added."} + + +def delete_member( cur, callparamsip): + callparams = input_to_dict(callparamsip) + dao_pid = get_pid_from_wallet(cur, callparams['address']) + is_dao_exist = dao_member_dup_check(cur, callparams['params'], dao_pid) + if pid_check(callparams, cur) == 0: + return {"status": 406, "message": "Personid doesn't exists."} + if is_dao_exist[0] == 0: + return {"status": 406, "message": "Person is not a DAO member."} + + +def dao_member_dup_check(cur, callparams, dao_pid): + is_dao_exist = cur.execute( + '''SELECT COUNT(*) FROM dao_membership WHERE dao_person_id LIKE ? AND member_person_id LIKE ?''', + (dao_pid, callparams['member_person_id'])) + return is_dao_exist.fetchone()[0] + + +def pid_check(cur, callparams): + is_pid_exists = cur.execute( + f'''SELECT COUNT(*) FROM PERSON WHERE person_id=?''' + , (callparams['member_person_id'])) + return is_pid_exists.fetchone()[0] diff --git a/app/codes/contracts/dao_manager.py b/app/codes/contracts/dao_manager.py new file mode 100644 index 0000000..9a7b5f4 --- /dev/null +++ b/app/codes/contracts/dao_manager.py @@ -0,0 +1,83 @@ +# class to create smart contract for creating stablecoins on Newrl +from .contract_master import ContractMaster +from ..db_updater import * +from ..kycwallet import generate_wallet_address + + +class dao_manager(ContractMaster): + codehash = "" # this is the hash of the entire document excluding this line, it is same for all instances of this class + + def __init__(self, contractaddress=None): + self.template = "dao_manager" + self.version = "" + ContractMaster.__init__(self, self.template, self.version, contractaddress) + + def updateondeploy(self, cur): + pass + + def create(self, cur, callparams): + dao_params = input_to_dict(callparams) + + # create wallet and pid for contract or dao_sc_main + dao_sc_address = dao_params['dao_address'] + dao_person_id = add_pid_contract_add(cur, dao_sc_address) + + + # update dao db + dao_name = dao_params['dao_name'] + founders_personid = json.dumps(dao_params['founders']) + self.__create_dao_details(cur, dao_person_id, dao_name, founders_personid, dao_sc_address) + # if DAO is of type Token based create SC token keeper for it + + + # create contract instance for this new dao with params of dao sc main (contract table) + contractparams = {} + contractparams['status'] = 1 + contractparams['ts_init'] = time.mktime(datetime.datetime.now().timetuple()) + contractparams['address'] = dao_sc_address + # ? + contractparams['version'] = 1.0 + sdestr = 0 + # sdestr? TODO + # sdestr = 0 if not contractparams['selfdestruct'] else int(contractparams['selfdestruct']) + cstatus = 0 if not contractparams['status'] else int(contractparams['status']) + cspecs = json.dumps(dao_params['contractspecs']) + legpars = json.dumps(dao_params['legalparams']) + # signatories founders wallet address as sign for DAO's setup and deploy? voraclestr ? + # signstr = json.dumps(cspecs['signstr']) + oraclestr = {} + signstr = json.dumps(input_to_dict(cspecs)['signatories']) + + qparams = ( + dao_sc_address, founders_personid, contractparams['ts_init'], dao_params['dao_main_sc'], dao_params['dao_main_sc_version'], + # actmode? + # contractparams['actmode'] + 0 + , cstatus, # next_act_ts? + # contractparams['next_act_ts'], + 0, + signstr, # parent? + # contractparams['parent'] + 0 + , json.dumps(oraclestr), sdestr, cspecs, legpars) + cur.execute(f'''INSERT INTO contracts + (address, creator, ts_init, name, version, actmode, status, next_act_ts, signatories, parent, oracleids, selfdestruct, contractspecs, legalparams) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', qparams) + cur.execute(f'''UPDATE contracts SET status=? WHERE address=?''', ( + # contractparams['status'] + 2 + , dao_sc_address)) + + + pass + + def alter(self, cur, callParamsip): + pass + + def terminate(self, cur, callParamsip): + pass + + def __create_dao_details(self, cur, dao_personid, dao_name, founder_personid, dao_sc_address): + cur.execute(f'''INSERT OR REPLACE INTO dao_main + (dao_personid, dao_name, founder_personid, dao_sc_address) + VALUES (?, ?, ?,?)''', (dao_personid, dao_name, founder_personid, dao_sc_address)) diff --git a/app/codes/contracts/membership_dao_ver1.py b/app/codes/contracts/membership_dao_ver1.py new file mode 100644 index 0000000..682d4e2 --- /dev/null +++ b/app/codes/contracts/membership_dao_ver1.py @@ -0,0 +1,11 @@ +from .dao_main_template import DaoMainTemplate +from ..db_updater import * + +class membership_dao_ver1(DaoMainTemplate): + codehash="" + + def __init__(self, contractaddress=None): + self.template= 'membership_dao_ver1' + self.version='1.0.0' + self.dao_type=1 + super().__init__(contractaddress) diff --git a/app/codes/contracts/membership_dao_ver1_validator.py b/app/codes/contracts/membership_dao_ver1_validator.py new file mode 100644 index 0000000..1875163 --- /dev/null +++ b/app/codes/contracts/membership_dao_ver1_validator.py @@ -0,0 +1,3 @@ + +from .dao_main_template_validator import * + diff --git a/app/codes/contracts/nusd1.py b/app/codes/contracts/nusd1.py index edf693e..f386a51 100644 --- a/app/codes/contracts/nusd1.py +++ b/app/codes/contracts/nusd1.py @@ -24,7 +24,6 @@ def updateondeploy(self, cur): "custodian": self.address, "legaldochash": legaldochash, "amount_created": None, - "value_created": 0, "disallowed": [], "tokendecimal":2, "sc_flag": True @@ -54,7 +53,6 @@ def send_nusd_token(self, cur, callparamsip): "first_owner": recipient_address, "custodian": self.address, "amount_created": int(value*100), - "value_created": value, "tokendecimal":2 } add_token(cur, tokendata) diff --git a/app/codes/contracts/token_dao.py b/app/codes/contracts/token_dao.py new file mode 100644 index 0000000..9345aa2 --- /dev/null +++ b/app/codes/contracts/token_dao.py @@ -0,0 +1,12 @@ +from .dao_main_template import DaoMainTemplate +from ..db_updater import * + +class token_dao(DaoMainTemplate): + codehash="" + + def __init__(self, contractaddress=None): + self.template= 'token_dao' + self.version='1.0.0' + self.dao_type=2 + # dao_type=2 is for token based DAO and 1 is for membership DAO + super().__init__(contractaddress) diff --git a/app/codes/contracts/token_fund_dao.py b/app/codes/contracts/token_fund_dao.py new file mode 100644 index 0000000..ff98457 --- /dev/null +++ b/app/codes/contracts/token_fund_dao.py @@ -0,0 +1,67 @@ +from .dao_main_template import DaoMainTemplate +from ..db_updater import * +import math + +class token_fund_dao(DaoMainTemplate): + codehash="" + + def __init__(self, contractaddress=None): + self.template= 'token_fund_dao' + self.version='1.0.0' + self.dao_type=2 + # dao_type=2 is for token based DAO and 1 is for membership DAO + super().__init__(contractaddress) + + def invest(self,cur,callparamsip): + # Invest in any startup + callparams = input_to_dict(callparamsip) + dao_id = self.address + wallet_to_invest=callparams['wallet_to_invest'] + token_to_invest_code=callparams['invest_token_code'] + token_to_invest_amount = callparams['invest_token_amount'] + token_to_recieve=callparams['token_to_recieve'] + token_recieve_amt = callparams['token_recieve_amt'] + # Transfering Tokens from User To DAO + sender1 = wallet_to_invest + sender2 = dao_id + + tokencode1 = token_to_invest_code + amount1 = int(token_to_invest_amount or 0) + transfer_tokens_and_update_balances( + cur, sender2, sender1, tokencode1, amount1) + + tokencode2 = token_to_recieve + amount2 = int(token_recieve_amt or 0) + transfer_tokens_and_update_balances( + cur, sender1, sender2, tokencode2, amount2) + + pass + + def disburse(self,cur,callsparamip): + # function to send money to the startup + + pass + + def payout(self,cur,callparamsip): + # dividend to the members + callparams = input_to_dict(callparamsip) + dao_id = self.address + asset1_code=callparams['asset_code'] + amount=callparams['asset_amount'] + dao_data=cur.execute(f'''Select dao_name as dao_name from dao_main where dao_sc_address=?''',[self.address]) + dao_data=dao_data.fetchone() + token_code = dao_data[0]+'_token' + members=cur.execute(f'''select wallet_address as owner,balance as amount from balances where tokencode like ?''',[token_code]).fetchall() + locked_tokens=cur.execute(f'''select wallet_address as owner,amount_locked as amount from DAO_TOKEN_LOCK where dao_id like ?''',[dao_id]).fetchall() + total_tokens=0 + if members is not None: + for i in members: + total_tokens=total_tokens+i[1] + ratio=int(amount)/int(total_tokens) + for i in members: + if(dao_id!=i[0]): + transfer_tokens_and_update_balances(cur,dao_id,i[0],asset1_code,math.ceil(i[1]*ratio)) + for i in locked_tokens: + if (dao_id != i[0]): + transfer_tokens_and_update_balances(cur, dao_id, i[0], asset1_code, math.ceil(i[1] * ratio)) + pass diff --git a/app/codes/contracts/token_vote_manager.py b/app/codes/contracts/token_vote_manager.py new file mode 100644 index 0000000..6c72a6d --- /dev/null +++ b/app/codes/contracts/token_vote_manager.py @@ -0,0 +1,37 @@ +#class to manage token based voting +from .contract_master import ContractMaster +from ..db_updater import input_to_dict + + +class token_vote_manager(ContractMaster): + codehash = "" # this is the hash of the entire document excluding this line, it is same for all instances of this class + + def __init__(self, contractaddress=None): + self.template = "token_manager" + self.version = "1.0.0" + ContractMaster.__init__(self, self.template, self.version, contractaddress) + + def updateondeploy(self, cur): + pass + + + + + def unlock_tokens(self, cur, callparamsip): + callparams = input_to_dict(callparamsip) + token_lock_data=cur.execute(f'''Select status as status from DAO_TOKEN_LOCK where person_id=? and dao_id=?''',(callparams['person_id'],callparams['dao_id'])) + status=token_lock_data['status'] + if(status is not None and status): + pass + else: + return False + + + ''' + TODO + params : person_pid, dao_id,amount + function : check if any proposals are pending ands if yes return False, else transfer back the amount + ''' + + + diff --git a/app/codes/db_updater.py b/app/codes/db_updater.py index c494f2c..a21e06e 100644 --- a/app/codes/db_updater.py +++ b/app/codes/db_updater.py @@ -130,7 +130,6 @@ def add_token(cur, token, txcode=None): token['custodian'], token['legaldochash'], token['amount_created'], - token['value_created'], token['sc_flag'], disallowed_json, txcode, @@ -139,8 +138,8 @@ def add_token(cur, token, txcode=None): ) cur.execute(f'''INSERT OR IGNORE INTO tokens (tokencode, tokenname, tokentype, first_owner, custodian, legaldochash, - amount_created, value_created, sc_flag, disallowed, parent_transaction_code, tokendecimal, token_attributes) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', query_params) + amount_created, sc_flag, disallowed, parent_transaction_code, tokendecimal, token_attributes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', query_params) if token['amount_created']: update_wallet_token_balance( cur, token['first_owner'], tid, token['amount_created']) @@ -179,7 +178,10 @@ def get_wallet_token_balance(cur, wallet_address, token_code): def add_tx_to_block(cur, block_index, transactions): print(block_index, transactions) - for transaction in transactions: + for transaction_signature in transactions: + transaction = transaction_signature['transaction'] + signatures = json.dumps(transaction_signature['signatures']) + signatures = [] if signatures is None else signatures transaction_code = transaction['transaction_code'] if 'transaction_code' in transaction else transaction['trans_code'] description = transaction['descr'] if 'descr' in transaction else transaction['description'] specific_data = json.dumps( @@ -193,11 +195,12 @@ def add_tx_to_block(cur, block_index, transactions): transaction['fee'], description, transaction['valid'], - specific_data + specific_data, + signatures ) cur.execute(f'''INSERT OR IGNORE INTO transactions - (block_index, transaction_code, timestamp, type, currency, fee, description, valid, specific_data) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)''', db_transaction_data) + (block_index, transaction_code, timestamp, type, currency, fee, description, valid, specific_data, signatures) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', db_transaction_data) def update_token_amount(cur, tid, amt): @@ -269,3 +272,27 @@ def input_to_dict(ipval): else: callparams = ipval return callparams + + +def add_miner(cur, wallet_address, network_address, broadcast_timestamp): + miner_cursor = cur.execute('SELECT last_broadcast_timestamp FROM miners where wallet_address = ?', (wallet_address, )).fetchone() + if miner_cursor is not None: + last_broadcast_timestamp = int(miner_cursor[0]) + if last_broadcast_timestamp > broadcast_timestamp: + return + cur.execute('''INSERT OR REPLACE INTO miners + (id, wallet_address, network_address, last_broadcast_timestamp) + VALUES (?, ?, ?, ?)''', + (wallet_address, wallet_address, network_address, broadcast_timestamp)) + +def add_pid_contract_add(cur,ct_add): + pid = get_person_id_for_wallet_address(ct_add) + query_params = (pid, get_time_ms()) + cur.execute(f'''INSERT OR IGNORE INTO person + (person_id, created_time) + VALUES (?, ?)''', query_params) + query_params = (pid, ct_add) + cur.execute(f'''INSERT OR IGNORE INTO person_wallet + (person_id, wallet_id) + VALUES (?, ?)''', query_params) + return pid diff --git a/app/codes/fs/mempool_manager.py b/app/codes/fs/mempool_manager.py index 349698a..5e48d39 100644 --- a/app/codes/fs/mempool_manager.py +++ b/app/codes/fs/mempool_manager.py @@ -2,6 +2,7 @@ import glob import json +import os from ...constants import MEMPOOL_PATH, TMP_PATH @@ -15,15 +16,6 @@ def get_receipts_from_storage(block_index, folder=MEMPOOL_PATH): return blocks -def store_block_to_temp(block, folder=TMP_PATH): - block_index = block['index'] - existing_files_for_block = glob.glob(f'{folder}/block_{block_index}_*.json') - new_file_name = f'{folder}/block_{block_index}_{len(existing_files_for_block)}.json' - with open(new_file_name, 'w') as _file: - json.dump(block, _file) - return new_file_name - - def store_receipt_to_temp(receipt, folder=TMP_PATH): block_index = receipt['data']['block_index'] existing_files_for_block = glob.glob(f'{folder}/receipt_{block_index}_*.json') @@ -50,19 +42,6 @@ def append_receipt_to_block(block, new_receipt): return False -def append_receipt_to_block_in_storage(receipt): - block_folder=TMP_PATH - block_index = receipt['data']['block_index'] - blocks = [] - for block_file in glob.glob(f'{block_folder}/block_{block_index}_*.json'): - with open(block_file, 'r+') as _file: - block = json.load(_file) - if append_receipt_to_block(block): - json.dump(block, _file) - blocks.append(block) - return blocks - - def get_mempool_transaction(transaction_code): existing_files_for_block = glob.glob(f'{MEMPOOL_PATH}transaction-*-{transaction_code}*.json') if len(existing_files_for_block) == 0: @@ -70,4 +49,27 @@ def get_mempool_transaction(transaction_code): with open(existing_files_for_block[0], 'r') as _file: transaction = json.load(_file) - return transaction \ No newline at end of file + return transaction + + +def remove_transaction_from_mempool(transaction_code): + transaction_files = glob.glob(f'{MEMPOOL_PATH}transaction-*-{transaction_code}*.json') + for f in transaction_files: + os.remove(f) + + +def clear_mempool(): + filenames = os.listdir(MEMPOOL_PATH) + + for filename in filenames: + file = MEMPOOL_PATH + filename + os.remove(file) + + clear_temp() + +def clear_temp(): + filenames = glob.glob(f'{TMP_PATH}*.json') + print(filenames) + + for f in filenames: + os.remove(f) diff --git a/app/codes/fs/temp_manager.py b/app/codes/fs/temp_manager.py index ad4c3b7..c42acd9 100644 --- a/app/codes/fs/temp_manager.py +++ b/app/codes/fs/temp_manager.py @@ -2,6 +2,7 @@ import glob import json +import os from ...constants import MEMPOOL_PATH, TMP_PATH @@ -16,7 +17,7 @@ def get_blocks_for_index_from_storage(block_index, folder=TMP_PATH): return blocks -def get_receipts_from_storage(block_index, folder=MEMPOOL_PATH): +def get_receipts_from_storage(block_index, folder=TMP_PATH): """Returns a list of receipts matching a block index from mempool""" blocks = [] for block_file in glob.glob(f'{folder}/receipt_{block_index}_*.json'): @@ -27,7 +28,7 @@ def get_receipts_from_storage(block_index, folder=MEMPOOL_PATH): def store_block_to_temp(block, folder=TMP_PATH): - block_index = block['index'] + block_index = block['index'] if 'index' in block else 'block_index' existing_files_for_block = glob.glob(f'{folder}/block_{block_index}_*.json') new_file_name = f'{folder}/block_{block_index}_{len(existing_files_for_block)}.json' with open(new_file_name, 'w') as _file: @@ -45,9 +46,6 @@ def store_receipt_to_temp(receipt, folder=TMP_PATH): def append_receipt_to_block(block, new_receipt): - if 'receipts' not in block: - block['receipts'] = [] - receipt_already_exists = False for receipt in block['receipts']: if receipt['public_key'] == new_receipt['public_key']: @@ -66,9 +64,21 @@ def append_receipt_to_block_in_storage(receipt): block_index = receipt['data']['block_index'] blocks = [] for block_file in glob.glob(f'{block_folder}/block_{block_index}_*.json'): - with open(block_file, 'r+') as _file: - block = json.load(_file) - if append_receipt_to_block(block): - json.dump(block, _file) - blocks.append(block) - return blocks \ No newline at end of file + with open(block_file, 'r') as _rfile: + block = json.load(_rfile) + if append_receipt_to_block(block, receipt): + with open(block_file, 'w') as _rfile: + json.dump(block, _rfile) + blocks.append(block) + return blocks + + +def remove_block_from_temp(block_index): + block_folder=TMP_PATH + try: + for block_file in glob.glob(f'{block_folder}/block_{block_index}_*.json'): + os.remove(block_file) + for receipt_file in glob.glob(f'{block_folder}/receipt_{block_index}_*.json'): + os.remove(receipt_file) + except Exception as e: + print('Could not remove block from tmp with index', block_index) diff --git a/app/codes/kycwallet.py b/app/codes/kycwallet.py index 86540da..4ddf135 100644 --- a/app/codes/kycwallet.py +++ b/app/codes/kycwallet.py @@ -8,6 +8,7 @@ import base64 import sqlite3 +from .utils import get_time_ms from ..constants import TMP_PATH, NEWRL_DB from .transactionmanager import Transactionmanager @@ -113,9 +114,9 @@ def generate_wallet(kyccustodian, kycdocs, ownertype, jurisd, wallet_specific_da def create_add_wallet_transaction(wallet): transaction_data = { - 'timestamp': str(datetime.datetime.now()), + 'timestamp': get_time_ms(), 'type': 1, - 'currency': 'INR', + 'currency': 'NWRL', 'fee': 0.0, 'descr': 'New wallet', 'valid': -1, diff --git a/app/codes/log_config.py b/app/codes/log_config.py new file mode 100644 index 0000000..8f5501e --- /dev/null +++ b/app/codes/log_config.py @@ -0,0 +1,73 @@ +## my_logger.py +from datetime import datetime +import time +import os +import pathlib +## Init logging start +import logging +import logging.handlers +from sh import tail +from sse_starlette.sse import EventSourceResponse + +'''Logger Config and Foramtters''' + +path = "logs/" +filename = "newrl-node-log" + +def logger_init(): + logger = logging.getLogger() ## root logger + logger.setLevel(logging.INFO) + + pathlib.Path(path).mkdir(parents=True, exist_ok=True) + logfilename = filename + file = logging.handlers.RotatingFileHandler(f"{path}{logfilename}", backupCount= 25, maxBytes= 2*1000*1000) + fileformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s") + file.setLevel(logging.INFO) + file.setFormatter(fileformat) + + stream = logging.StreamHandler() + streamformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s") + stream.setLevel(logging.INFO) + stream.setFormatter(streamformat) + + if (logger.hasHandlers()): + logger.handlers.clear() + + logger.addHandler(file) + logger.addHandler(stream) + + +def logger_cleanup(path, days_to_keep): + lclogger = logging.getLogger(__name__) + logpath = f"{path}" + now = time.time() + for filename in os.listdir(logpath): + filestamp = os.stat(os.path.join(logpath, filename)).st_mtime + filecompare = now - days_to_keep * 86400 + if filestamp < filecompare: + lclogger.info("Delete old log " + filename) + try: + os.remove(os.path.join(logpath, filename)) + except Exception as e: + lclogger.exception(e) + continue + +'''Log Streaming Methods''' + +async def logGenerator(request): + logfilename = filename + logFile = f"{path}{logfilename}" + for line in tail("-f", logFile, _iter=True): + if await request.is_disconnected(): + print("client disconnected!") + break + yield line + time.sleep(0.9) + + +def get_past_log_content(filename=filename): + # if filename is None: + logfilename = datetime.now().strftime("%Y%m%d_%H") + f"_{filename}" + logFile = f"{path}{logfilename}" + with open(logFile) as f: + return f.read() diff --git a/app/codes/minermanager.py b/app/codes/minermanager.py new file mode 100644 index 0000000..c2be8d5 --- /dev/null +++ b/app/codes/minermanager.py @@ -0,0 +1,171 @@ +"""Miner update functions""" +import sqlite3 +import random + +from .p2p.peers import add_peer +from .clock.global_time import get_corrected_time_ms, get_time_difference +from ..nvalues import ASQI_WALLET +from .utils import get_last_block_hash +# from .p2p.outgoing import propogate_transaction_to_peers +from .p2p.utils import get_my_address +from ..constants import COMMITTEE_SIZE, IS_TEST, NEWRL_DB, TIME_MINER_BROADCAST_INTERVAL_SECONDS +from .auth.auth import get_wallet +from .signmanager import sign_transaction +from ..ntypes import TRANSACTION_MINER_ADDITION +from .utils import get_time_ms +from .transactionmanager import Transactionmanager +from .validator import validate + + +def miner_addition_transaction(wallet=None, my_address=None): + if wallet is None: + wallet = get_wallet() + if my_address is None: + my_address = get_my_address() + timestamp = get_time_ms() - get_time_difference() + transaction_data = { + 'timestamp': timestamp, + 'type': TRANSACTION_MINER_ADDITION, + 'currency': "NWRL", + 'fee': 0.0, + 'descr': "Miner addition", + 'valid': 1, + 'block_index': 0, + 'specific_data': { + 'wallet_address': wallet['address'], + 'network_address': my_address, + 'broadcast_timestamp': timestamp + } + } + + transaction_manager = Transactionmanager() + transaction_data = {'transaction': transaction_data, 'signatures': []} + transaction_manager.transactioncreator(transaction_data) + transaction = transaction_manager.get_transaction_complete() + signed_transaction = sign_transaction(wallet, transaction) + return signed_transaction + + +def get_miner_status(wallet_address): + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() + miner_cursor = cur.execute( + 'SELECT wallet_address, network_address, last_broadcast_timestamp FROM miners WHERE wallet_address=?', (wallet_address, )).fetchone() + if miner_cursor is None: + return None + miner_info = { + 'wallet_address': miner_cursor[0], + 'network_address': miner_cursor[1], + 'broadcast_timestamp': miner_cursor[2] + } + return miner_info + + +def get_my_miner_status(): + wallet = get_wallet() + my_status = get_miner_status(wallet['address']) + return my_status + + +def broadcast_miner_update(): + transaction = miner_addition_transaction() + validate(transaction, propagate=True) + + +def get_eligible_miners(): + # last_block = get_last_block_hash() + # last_block_epoch = 0 + # try: + # # Need try catch to support older block timestamps + # last_block_epoch = int(last_block['timestamp']) + # except: + # pass + # if last_block: + # cutfoff_epoch = last_block_epoch - TIME_MINER_BROADCAST_INTERVAL + # else: + # cutfoff_epoch = 0 + # last_block_epoch = int(last_block['timestamp']) + cutfoff_epoch = get_corrected_time_ms() - TIME_MINER_BROADCAST_INTERVAL_SECONDS * 2 * 1000 + + con = sqlite3.connect(NEWRL_DB) + con.row_factory = sqlite3.Row + cur = con.cursor() + miner_cursor = cur.execute( + '''SELECT wallet_address, network_address, last_broadcast_timestamp + FROM miners + WHERE last_broadcast_timestamp > ? + ORDER BY wallet_address ASC''', (cutfoff_epoch, )).fetchall() + miners = [dict(m) for m in miner_cursor] + con.close() + return miners + + +def get_miner_for_current_block(last_block=None): + if last_block is None: + last_block = get_last_block_hash() + + if not last_block: + return {'wallet_address': ASQI_WALLET} + + random.seed(last_block['index']) + + committee_list = get_committee_for_current_block() + + if len(committee_list) == 0: + return {'wallet_address': ASQI_WALLET} + + return random.choice(committee_list) + + # return committee_list[0] + + +def get_committee_for_current_block(last_block=None): + if last_block is None: + last_block = get_last_block_hash() + + if not last_block: + return [{'wallet_address': ASQI_WALLET}] + + random.seed(last_block['index']) + + miners = get_eligible_miners() + + if len(miners) == 0: + return [{'wallet_address': ASQI_WALLET}] + + committee_size = min(COMMITTEE_SIZE, len(miners)) + committee = random.sample(miners, k=committee_size) + return committee + + +def should_i_mine(last_block=None): + my_wallet = get_wallet() + miner = get_miner_for_current_block(last_block=last_block) + if miner['wallet_address'] == my_wallet['address']: + return True + return False + + +def am_i_in_current_committee(last_block=None): + my_wallet_address = get_wallet()['address'] + committee = get_committee_for_current_block(last_block) + + found = list(filter(lambda w: w['wallet_address'] == my_wallet_address, committee)) + if len(found) == 0: + return False + return True + + +def get_miner_info(): + return { + 'current_block_miner': get_miner_for_current_block(), + 'current_block_committee': get_committee_for_current_block(), + 'eligible_miners': get_eligible_miners(), + } + + +def add_miners_as_peers(): + miners = get_eligible_miners() + + for miner in miners: + add_peer(miner['network_address']) \ No newline at end of file diff --git a/app/codes/nusd1.py b/app/codes/nusd1.py deleted file mode 100644 index dea3adc..0000000 --- a/app/codes/nusd1.py +++ /dev/null @@ -1,306 +0,0 @@ -# class to create smart contract for creating stablecoins on Newrl -import codecs -from subprocess import call -import ecdsa -from Crypto.Hash import keccak -import os -import hashlib -import json -import datetime -import time -import binascii -import base64 -import sqlite3 - -#from app.codes.updater import add_token - -from .transactionmanager import Transactionmanager, is_wallet_valid -from .chainscanner import Chainscanner, get_wallet_token_balance -from .tokenmanager import create_token_transaction -from ..constants import NEWRL_DB -from .state_updater import * - - -class nusd1(): - codehash = "" # this is the hash of the entire document excluding this line, it is same for all instances of this class - - def __init__(self, contractaddress=None): - self.template = "nusd" - self.version = "1.0.0" - # this is for instances of this class created for tx creation and other non-chain work - self.address = contractaddress - if contractaddress: # as in this is an existing contract - con = sqlite3.connect(NEWRL_DB) - cur = con.cursor() - # this will populate the params for a given instance of the contract - self.loadcontract(cur, contractaddress) - con.close() - # instantiation convetion: for the first time instantiation of a contract, the contractaddress is None, this is to be immediately followed by setup call - # in a later call outside chain work or inside it, the contractaddress is present and is used to lookup the specific contract from the db - - def setup(self, cur, callparamsjson): - # this is called by a tx type 3 signed by the creator, it calls the function setp with parameters as params - # setup implies a transaction for contract address creation - callparams = json.loads(callparamsjson) - contractparams = callparams - if contractparams['status'] == -1: - print("Contract is already terminated, cannot setup. Exiting.") - return False - if contractparams['status'] == 2: - print("Contract already deployed, cannot setup. Exiting.") - return False - if contractparams['status'] == 3: - print("Contract already expired, cannot setup. Exiting.") - return False - if contractparams['status'] == 1: - print("Contract already setup, cannot setup again. Exiting.") - return False - # add other codes here if in future 4 onwards are used for specifying other contract states. - - private_key_bytes = os.urandom(32) - key = ecdsa.SigningKey.from_string( - private_key_bytes, curve=ecdsa.SECP256k1).verifying_key - key_bytes = key.to_string() - public_key = codecs.encode(key_bytes, 'hex') - public_key_bytes = codecs.decode(public_key, 'hex') - hash = keccak.new(digest_bits=256) - hash.update(public_key_bytes) - keccak_digest = hash.hexdigest() - # this overwrites the None value in the init call, whenever on-chain contract is setup - self.address = '0x' + keccak_digest[-40:] - # we have ignored the private key and public key of this because we do not want to transact through key-based signing for a contract - - # next we set up the contract status as live - # params={} - if contractparams['name'] != self.template: - print("Mismatch in contractname. Not creating a new contract.") - return False - if contractparams['version'] != self.version: - print("Mismatch in contract version. Not creating a new contract.") - return False - contractparams['status'] = 1 - # status convention: 0 or None is not setup yet, 1 is setup but not deployed, 2 is setup and deployed, 3 is expired and -1 is terminated - - # now we need to update the contract parameters in SC database; for now we are appending to the allcontracts.json - contractparams['ts_init'] = time.mktime( - datetime.datetime.now().timetuple()) - # contractparams['ts_init']=str(datetime.datetime.now() - contractparams['address'] = self.address - self.contractparams = contractparams - ######### - # code to append contractdata into allcontracts db - sdestr = 0 if not contractparams['selfdestruct'] else int( - contractparams['selfdestruct']) - cstatus = 0 if not contractparams['status'] else int( - contractparams['status']) - cspecs = json.dumps(contractparams['contractspecs']) - legpars = json.dumps(contractparams['legalparams']) - qparams = (self.address, - contractparams['creator'], - contractparams['ts_init'], - contractparams['name'], - contractparams['version'], - contractparams['actmode'], - cstatus, - contractparams['next_act_ts'], - contractparams['signatories'], - contractparams['parent'], - contractparams['oracleids'], - sdestr, - cspecs, - legpars - ) - cur.execute(f'''INSERT INTO contracts - (address, creator, ts_init, name, version, actmode, status, next_act_ts, signatories, parent, oracleids, selfdestruct, contractspecs, legalparams) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', qparams) - return self.address - - def loadcontract(self, cur, contractaddress): - # this loads the contract from the state db - # it should take as input contractaddress and output the contractparams as they are in the db as of the time of calling it - # the output will populate self.contractparams to be used by other functions - contract_cursor = cur.execute('SELECT * FROM contracts WHERE address = :address', { - 'address': contractaddress}) - contract_row = contract_cursor.fetchone() - # print(contract) - self.contractparams = dict(contract_row) - print("Loaded the contract with following data: \n", self.contractparams) - return self.contractparams - - # the below function creates the transaction of a give type -# def create_tx(self,ttype,tspdata,currency="INR",fee=0.0,descr=None): - - def create_sc_tx(self, function, signers, params, currency, feeamount, descr): - # function used by users/applications to create tx to call this contract - specificdata = {'address': self.address, - 'signers': signers, - 'params': params} - transaction = {'timestamp': time.mktime(datetime.datetime.now().timetuple()), - 'type': 3, - 'currency': currency, - 'fee': feeamount, - 'descr': descr, - 'valid': 1, - 'block_index': 0, - 'specific_data': specificdata} - trans = Transactionmanager() - signatures = [] - transactiondata = {'transaction': transaction, - 'signatures': signatures} - txdata = trans.transactioncreator(transactiondata) - return txdata # this is expected to be used in API form, hence the caller gets back the tx data as a json object - # user will take this transaction and submit its signed version for inclusion in a block, - # validators will simply check if it is signed properly and for fees - - def deploy(self, cur, sender, callparams): - # carries out the SC execution steps upon instruction from a transaction - during updater run, post block inclusion - # the contract is not setup, i.e. is either yet to be setup, already deployed or terminated - if self.contractparams['status'] != 1: - print( - "The contract is not in the post-setup stage. Exiting without deploying.") - return False - else: - if self.sendervalid(sender, self.deploy.__name__): - # changed from 1 (setup done) to 2 (deployed and live) - self.contractparams['status'] = 2 - cur.execute(f'''INSERT OR REPLACE INTO contracts - (address, status) - VALUES (?, ?)''', (self.address, self.contractparams['status'])) - print("Deployed smart contract - ", self.template, - "with address ", self.contractaddress) - self.updateondeploy(cur, callparams) - return True - else: - print("Sender not valid or not allowed this function call.") - return False - - def updateondeploy(self, cur, callparams): - tokendata = {"tokencode": "4242", - "tokenname": "NUSD1", - "tokentype": 1, - "tokenattributes": {"fiat_currency": "USD", "sc_address": self.address}, - "first_owner": None, - "custodian": self.address, - "legaldochash": self.contractparams['legalparams'], - "amount_created": None, - "value_created": 0, - "disallowed": [], - "tokendecimal": 2, - "sc_flag": True - } - add_token(cur, tokendata, callparams['trans_code']) - - def sendervalid(self, senderaddress, function): - sendervalidity = False - for appr_sender in self.contractparams['approved_senders']: - if senderaddress == appr_sender['address']: - if appr_sender['allowed'] == 'all' or function in appr_sender['allowed']: - sendervalidity = True - return sendervalidity - - def send_nusd_token(self, cur, recipient_address, sender, value): - try: - value = float(value) - except: - print("Can't read value as a valid number.") - return False - if not is_wallet_valid(recipient_address): - print("Recipient address not valid.") - return False - if not self.sendervalid(sender): - print("Sender is not in the approved senders list.") - return False - - tokendata = {"tokencode": "4242", - "first_owner": recipient_address, - "custodian": self.address, - "amount_created": int(value*100), - "value_created": value, - "tokendecimal": 2 - } - add_token(cur, tokendata) - - def destroy_nusd_token(self, sender_address, value): - pass - - def transferlend(self): - transferdata = {"asset1_code": self.contractparams['contractspecs']['tokencode'], - "asset2_code": 0, - "wallet1": self.contractparams['contractspecs']['lenderwallet'], - "wallet2": self.contractaddress, - "asset1_number": self.contractparams['contractspecs']['loanamount'], - "asset2_number": 0} - # going with default values for mempool="./mempool",statefile="./state.json",descr=None - tx = self.create_tx(5, transferdata, "INR", 0.0) - return tx - - def checkstatus(self): - # returns status without adding a chain transaction; status is one of : awaiting tx confirmations, awaiting deployment, live, terminated, etc - status = self.contractparams['status'] - if status == 0: - print("Contract not yet set up") - return 0 - if status == 1: - print("Contract set up and awaiting deployment") - return 1 - if status == 2: - print("Contract set up and deployed, currently live") - return 2 - if status == -1: - print("Contract terminated. Not live") - return -1 - - def reverseandterminate(self): - # this reverses all transfer transactions into the contract address and terminates the contract; to be used when cancelling a contract before deploying - # it can only be initiated by original smart contract creator - if self.contractparams['status'] == 2: - print("This is a deployed contract, cannot reverse it. Exiting.") - return False - if self.contractparams['status'] == 0: - print( - "This contract is yet to be setup, nothing is expected to be in its wallet. Exiting.") - return False - # it is assumed that a contract that is in any other state will check its balances and return it to where they came from - # the contract will assume that senders are as per the original params and will not take new arguments about where to send the balances - # technically a defunt contract can still return funds back to where they came from if this function is called. Useful if funds are stuck - # also, an expired but not terminated contract can also be made to return tokens to their original senders - contbalances = self.getcontaddbal() - contloanbal = contbalances['contloanbal'] - conttokbal = contbalances['conttokbal'] - contsecbal = contbalances['contsecbal'] - - ######### - # code for deployment - # 1. code to open the balances db and transfer lent tokens from contract to lender, and loan tokens from contract to borrower and sectokens from contract to secprovider - # update the status in contracts db as well - ######### - self.terminate() - print("Reversed and terminated smart contract - ", - self.template, "with address ", self.contractaddress) - return True - - def terminate(self): - if self.contractparams['status'] == 2: - print( - "This is a deployed contract, cannot terminate it while it is live. Exiting.") - return False - # changed from 1 (setup done) to -1 (terminated) - self.contractparams['status'] = -1 - # code to update the db for change in status - ######### - return True - - def close(self): - # this is to be used when a contract has run its course and has carried out what's required of it and is no longer required - if self.contractparams['status'] == 3: - print("Contract expired. Terminating.") - flag = self.terminate() - if flag: - print("Contract ", self.contractaddress, - " has been terminated.") - return True - else: - print("Couldn't terminate despite expired status; investigate.") - return False - print("Not an expired contract. Not closing.") - return False diff --git a/app/codes/p2p/outgoing.py b/app/codes/p2p/outgoing.py index a020baa..c28a46a 100644 --- a/app/codes/p2p/outgoing.py +++ b/app/codes/p2p/outgoing.py @@ -1,20 +1,34 @@ +import random import requests from threading import Thread -from ...constants import NEWRL_PORT, REQUEST_TIMEOUT, TRANSPORT_SERVER + +from ...constants import IS_TEST, MAX_BROADCAST_NODES, NEWRL_PORT, REQUEST_TIMEOUT, TRANSPORT_SERVER from ..p2p.utils import get_peers from ..p2p.utils import is_my_address -def propogate_transaction_to_peers(transaction): +def propogate_transaction_to_peers(transaction, exclude_nodes=None): + if IS_TEST: + return peers = get_peers() - + if exclude_nodes: + peers = get_excluded_peers_to_broadcast(peers, exclude_nodes) + + node_count = min(MAX_BROADCAST_NODES, len(peers)) + peers = random.sample(peers, k=node_count) + + payload = { + 'signed_transaction': transaction, + 'peers_already_broadcasted': get_excluded_node_list(peers, exclude_nodes) + } + + print('Broadcasting transaction to peers', peers) for peer in peers: if is_my_address(peer['address']): continue url = 'http://' + peer['address'] + ':' + str(NEWRL_PORT) - print('Broadcasting transaction to peer', url) try: - thread = Thread(target=send_request, args = (url + '/validate-transaction', transaction)) + thread = Thread(target=send_request, args = (url + '/receive-transaction', payload)) thread.start() except Exception as e: print(f'Error broadcasting block to peer: {url}') @@ -25,10 +39,90 @@ def send_request_in_thread(url, data): thread.start() def send_request(url, data): - requests.post(url, json=data, timeout=REQUEST_TIMEOUT) + if IS_TEST: + return + try: + requests.post(url, json=data, timeout=REQUEST_TIMEOUT) + except Exception as e: + print(f'Could not send request to node {url}') def send(payload): response = requests.post(TRANSPORT_SERVER + '/send', json=payload, timeout=REQUEST_TIMEOUT) if response.status_code != 200: print('Error sending') return response.text + + +def broadcast_receipt(receipt, nodes): + print('Broadcasting receipt to nodes') + if IS_TEST: + return + + for node in nodes: + if 'network_address' not in node: + continue + if is_my_address(node['network_address']): + continue + url = 'http://' + node['network_address'] + ':' + str(NEWRL_PORT) + print('Sending receipt to node', url) + payload = {'receipt': receipt} + try: + thread = Thread(target=send_request, args=(url + '/receive-receipt', payload)) + thread.start() + except Exception as e: + print(f'Could not send receipt to node: {url}') + + +def broadcast_block(block_payload, nodes=None, exclude_nodes=None): + if IS_TEST: + return + if nodes: + peers = [] + for node in nodes: + if 'network_address' in node: + peers.append({'address': node['network_address']}) + elif 'address' in node: + peers.append({'address': node['address']}) + if len(peers) == 0: + peers = get_random_peers(exclude_nodes) + else: + peers = get_random_peers(exclude_nodes) + + print('Broadcasting block to peers', peers) + block_payload['peers_already_broadcasted'] = get_excluded_node_list(peers, exclude_nodes) + # TODO - Do not send to self + for peer in peers: + if 'address' not in peer or is_my_address(peer['address']): + continue + url = 'http://' + peer['address'] + ':' + str(NEWRL_PORT) + try: + send_request_in_thread(url + '/receive-block', {'block': block_payload}) + # requests.post(url + '/receive-block', json={'block': block_payload}, timeout=REQUEST_TIMEOUT) + except Exception as e: + print(f'Error sending block to peer: {url}') + print(e) + return True + + +def get_excluded_peers_to_broadcast(peers, exclude_nodes): + peers = filter(lambda p: p['address'] not in exclude_nodes, peers) + return list(peers) + + +def get_random_peers(exclude_nodes=None): + peers = get_peers() + if exclude_nodes: + peers = get_excluded_peers_to_broadcast(peers, exclude_nodes) + node_count = min(MAX_BROADCAST_NODES, len(peers)) + peers = random.sample(peers, k=node_count) + return peers + + +def get_excluded_node_list(new_nodes, already_broadcasted_nodes): + new_node_addresses = list(map(lambda p: p['address'] if 'address' in p else None, new_nodes)) + if already_broadcasted_nodes: + combined_list = new_node_addresses + already_broadcasted_nodes + else: + combined_list = new_node_addresses + combined_list = list(set(combined_list)) + return combined_list diff --git a/app/codes/p2p/peers.py b/app/codes/p2p/peers.py index 82ed8ef..9306179 100644 --- a/app/codes/p2p/peers.py +++ b/app/codes/p2p/peers.py @@ -4,6 +4,7 @@ import requests import socket import subprocess +from threading import Thread from app.codes.signmanager import sign_object from app.codes.validator import validate_signature from app.migrations.init import init_newrl @@ -60,7 +61,7 @@ def add_peer(peer_address): try: logger.info('Adding peer %s', peer_address) # await register_me_with_them(peer_address) - cur.execute('INSERT INTO peers(id, address) VALUES(?, ?)', (peer_address, peer_address, )) + cur.execute('INSERT OR REPLACE INTO peers(id, address) VALUES(?, ?)', (peer_address, peer_address, )) con.commit() except Exception as e: logger.info('Did not add peer %s', peer_address) @@ -162,13 +163,15 @@ def update_my_address(): def update_software(propogate): "Update the client software from repo" - logger.info('Getting latest code from repo') - subprocess.call(["git", "pull"]) - init_newrl() if propogate is True: logger.info('Propogaring update request to network') update_peers() + logger.info('Getting latest code from repo') + subprocess.call(["git", "pull"]) + subprocess.call(["sh", "scripts/install.sh"]) + init_newrl() + def validate_auth(auth): data = { @@ -202,3 +205,27 @@ def call_api_on_peers(url): assert response.json()['status'] == 'SUCCESS' except Exception as e: print(f'Error calling API on node {address}', str(e)) + + +def remove_dead_peer(peer, my_address): + address = peer['address'] + if socket.gethostbyname(address) == my_address: + return + try: + response = requests.get( + 'http://' + address + f':{NEWRL_PORT}' + '/get-status', + timeout=REQUEST_TIMEOUT + ) + if response.status_code != 200 or response.json()['up'] != True: + remove_peer(peer['id']) + except Exception as e: + print('Removing peer', peer['id']) + remove_peer(peer['id']) + +def remove_dead_peers(): + my_peers = get_peers() + my_address = get_my_address() + + for peer in my_peers: + thread = Thread(target=remove_dead_peer, args=(peer, my_address)) + thread.start() diff --git a/app/codes/p2p/sync_chain.py b/app/codes/p2p/sync_chain.py index b18edc6..ac2cbbf 100644 --- a/app/codes/p2p/sync_chain.py +++ b/app/codes/p2p/sync_chain.py @@ -1,26 +1,41 @@ +import json import logging +import random import requests import sqlite3 import time +import copy from app.codes import blockchain +from app.codes.crypto import calculate_hash +from app.codes.minermanager import get_committee_for_current_block +from app.codes.p2p.outgoing import broadcast_receipt, broadcast_block from app.constants import NEWRL_PORT, REQUEST_TIMEOUT, NEWRL_DB from app.codes.p2p.peers import get_peers -from app.codes.validator import validate_block, validate_block_data, validate_receipt_signature -from app.codes.updater import broadcast_block -from app.codes.fs.temp_manager import append_receipt_to_block, append_receipt_to_block_in_storage, get_blocks_for_index_from_storage, store_block_to_temp, store_receipt_to_temp -from app.codes.consensus.consensus import check_community_consensus - +from app.codes.validator import validate_block, validate_block_data, validate_block_transactions, validate_receipt_signature +from app.codes.updater import TIMERS, start_mining_clock +from app.codes.fs.temp_manager import append_receipt_to_block_in_storage, get_blocks_for_index_from_storage, store_block_to_temp, store_receipt_to_temp +from app.codes.consensus.consensus import check_community_consensus, is_timeout_block_from_sentinel_node, validate_block_miner, generate_block_receipt, \ + add_my_receipt_to_block +from app.migrations.init_db import revert_chain logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +# IS_SYNCING = False +SYNC_STATUS = { + 'IS_SYNCING': False +} def get_blocks(block_indexes): blocks = [] for block_index in block_indexes: - blocks.append(get_block(block_index)) + block = get_block(block_index) + if block: + blocks.append(get_block(block_index)) + else: + break return blocks @@ -34,24 +49,49 @@ def get_last_block_index(): def receive_block(block): - print('Recieved block', block) - - block_index = block['block_index'] if 'block_index' in block else block['index'] + block_index = block['index'] if block_index > get_last_block_index() + 1: sync_chain_from_peers() + return + + if blockchain.block_exists(block_index): + logger.info('Block alredy exist in chain. Ignoring.') + return False - validate_block(block, validate_receipts=False) + logger.info(f'Received new block: {block}') - # if check_community_consensus(block): - # accept_block(block) - # else: - # store_block_to_temp(block) - con = sqlite3.connect(NEWRL_DB) - cur = con.cursor() - blockchain.add_block(cur, block['data'], block['hash']) - con.commit() - con.close() + broadcast_exclude_nodes = block['peers_already_broadcasted'] if 'peers_already_broadcasted' in block else None + if is_timeout_block_from_sentinel_node(block['data']): + original_block = copy.deepcopy(block) + accept_block(block, block['hash']) + broadcast_block(original_block, exclude_nodes=broadcast_exclude_nodes) + + if not validate_block_miner(block['data']): + return False + + if not validate_block(block): + logger.info('Invalid block. Sending receipts.') + receipt_for_invalid_block = generate_block_receipt(block['data'], vote=0) + committee = get_committee_for_current_block() + broadcast_receipt(receipt_for_invalid_block, committee) + return False + + if check_community_consensus(block): + original_block = copy.deepcopy(block) + accept_block(block, block['hash']) + broadcast_block(original_block, exclude_nodes=broadcast_exclude_nodes) + else: + my_receipt = add_my_receipt_to_block(block) + if check_community_consensus(block): + original_block = copy.deepcopy(block) + if accept_block(block, block['hash']): + broadcast_block(original_block, exclude_nodes=broadcast_exclude_nodes) + else: + if my_receipt: + committee = get_committee_for_current_block() + broadcast_receipt(my_receipt, committee) + store_block_to_temp(block) return True @@ -64,15 +104,15 @@ def sync_chain_from_node(url, block_index=None): else: their_last_block_index = block_index my_last_block = get_last_block_index() - print(f'I have {my_last_block} blocks. Node {url} has {their_last_block_index} blocks.') + logger.info(f'I have {my_last_block} blocks. Node {url} has {their_last_block_index} blocks.') block_idx = my_last_block + 1 block_batch_size = 50 # Fetch blocks in batches while block_idx <= their_last_block_index: failed_for_invalid_block = False - blocks_to_request = list(range(block_idx, min(their_last_block_index, block_idx + block_batch_size))) + blocks_to_request = list(range(block_idx, 1 + min(their_last_block_index, block_idx + block_batch_size))) blocks_request = {'block_indexes': blocks_to_request} - print(f'Asking block node {url} for blocks {blocks_request}') + logger.info(f'Asking block node {url} for blocks {blocks_request}') blocks_data = get_block_from_url_retry(url, blocks_request) # try: # response = requests.post( @@ -82,67 +122,104 @@ def sync_chain_from_node(url, block_index=None): # ) # blocks_data = response.json() # except Exception as err: - # print('Could not get block', str(err)) + # logger.info(f'Could not get block {err}') # failed_for_invalid_block = True # time.sleep(5) for block in blocks_data: + block['index'] = block['block_index'] + block['timestamp'] = int(block['timestamp']) + hash = block['hash'] + # hash = calculate_hash(block) + block.pop('hash', None) + block.pop('transactions_hash', None) + block.pop('block_index', None) + for idx, tx in enumerate(block['text']['transactions']): + specific_data = tx['transaction']['specific_data'] + while isinstance(specific_data, str): + specific_data = json.loads(specific_data) + block['text']['transactions'][idx]['transaction']['specific_data'] = specific_data + + signatures = tx['transaction']['signatures'] + while isinstance(signatures, str): + signatures = json.loads(signatures) + block['text']['transactions'][idx]['transaction']['signatures'] = signatures + block['text']['transactions'][idx]['signatures'] = signatures + if not validate_block_data(block): - print('Invalid block') + logger.info('Invalid block. Reverting by one block to retry') failed_for_invalid_block = True + revert_chain(get_last_block_index() - 1) break con = sqlite3.connect(NEWRL_DB) cur = con.cursor() - blockchain.add_block(cur, block) + blockchain.add_block(cur, block, hash) con.commit() con.close() if failed_for_invalid_block: break - block_idx += block_batch_size + block_idx += block_batch_size + 1 return their_last_block_index -def sync_chain_from_peers(): - peers = get_peers() - url, block_index = get_best_peer_to_sync(peers) - - if url: - print('Syncing from peer', url) - sync_chain_from_node(url, block_index) - else: - print('No node available to sync') - - +def sync_chain_from_peers(force_sync=False): + global SYNC_STATUS + logger.info(f'Syncing chain from peers with force_sync={force_sync} SYNC_STATUS={SYNC_STATUS}') + if force_sync: + SYNC_STATUS['IS_SYNCING'] = False + if SYNC_STATUS['IS_SYNCING']: + logger.info('Already syncing chain. Not syncing again.') + return + SYNC_STATUS['IS_SYNCING'] = True + try: + peers = get_peers() + url, block_index = get_best_peer_to_sync(peers) + + if url: + logger.info(f'Syncing from peer {url}') + sync_chain_from_node(url, block_index) + else: + logger.info('No node available to sync') + except Exception as e: + logger.info(f'Sync failed {e}') + SYNC_STATUS['IS_SYNCING'] = False # TODO - use mode of max last -def get_best_peer_to_sync(peers): - best_peer = None +def get_best_peer_to_sync(peers, return_many=False): + best_peers = [] best_peer_value = 0 + peers = random.sample(peers, k=min(len(peers), 5)) for peer in peers: url = 'http://' + peer['address'] + ':' + str(NEWRL_PORT) try: their_last_block_index = int(requests.get(url + '/get-last-block-index', timeout=REQUEST_TIMEOUT).text) - print(f'Peer {url} has last block {their_last_block_index}') + logger.info(f'Peer {url} has last block {their_last_block_index}') if their_last_block_index > best_peer_value: - best_peer = url + best_peers = [url] best_peer_value = their_last_block_index + elif their_last_block_index == best_peer_value: + best_peers.append(url) except Exception as e: - print('Error getting block index from peer at', url) - return best_peer, best_peer_value + logger.info(f'Error getting block index from peer at {url}') + if return_many: + return best_peers, best_peer_value + else: + best_peer = random.choice(best_peers) + return best_peer, best_peer_value def ask_peer_for_block(peer_url, block_index): blocks_request = {'block_indexes': [block_index]} - print(f'Asking block node {peer_url} for block {block_index}') + logger.info(f'Asking block node {peer_url} for block {block_index}') try: - blocks_data = requests.post(peer_url + '/get-blocks', json=blocks_request, timeout=REQUEST_TIMEOUT).json() + blocks_data = requests.post(peer_url + '/get-blocks', json=blocks_request, timeout=REQUEST_TIMEOUT * 5).json() return blocks_data except Exception as e: - print('Could not get block', str(e)) + logger.info(f'Could not get block {e}') return None @@ -157,13 +234,25 @@ def ask_peers_for_block(block_index): return None -def accept_block(block, broadcast=True): +def accept_block(block, hash): + global TIMERS + if not validate_block_transactions(block['data']): + logger.info('Transaction validation failed') + return False + + # if hash is None: + # hash = calculate_hash(block['data']) con = sqlite3.connect(NEWRL_DB) cur = con.cursor() - blockchain.add_block(cur, block) + blockchain.add_block(cur, block['data'], hash) + con.commit() con.close() - broadcast_block(block) + # block_timestamp = int(block['data']['timestamp']) + # start_mining_clock(block_timestamp) + TIMERS['mining_timer'] = None + TIMERS['block_receive_timeout'] = None + return True def receive_receipt(receipt): @@ -174,20 +263,24 @@ def receive_receipt(receipt): receipt_data = receipt['data'] block_index = receipt_data['block_index'] + + if blockchain.block_exists(block_index): + return False + blocks = get_blocks_for_index_from_storage(block_index) if len(blocks) == 0: store_receipt_to_temp(receipt) - block = ask_peers_for_block(block_index) - if block is not None: - append_receipt_to_block(block, receipt) - store_block_to_temp(block) + # block = ask_peers_for_block(block_index) + # if block is not None: + # append_receipt_to_block(block, receipt) + # store_block_to_temp(block) else: blocks_appended = append_receipt_to_block_in_storage(receipt) for block in blocks_appended: if check_community_consensus(block): - accept_block(block) - - + original_block = copy.deepcopy(block) + accept_block(block, block['hash']) + broadcast_block(original_block) return True @@ -199,10 +292,10 @@ def get_block_from_url_retry(url, blocks_request): response = requests.post( url + '/get-blocks', json=blocks_request, - timeout=REQUEST_TIMEOUT + # timeout=REQUEST_TIMEOUT ) except Exception as err: - print('Retrying block get', str(err)) + logger.info(f'Retrying block get {err}') failed_for_invalid_block = True time.sleep(5) blocks_data = response.json() diff --git a/app/codes/p2p/utils.py b/app/codes/p2p/utils.py index 3fb6201..3375a37 100644 --- a/app/codes/p2p/utils.py +++ b/app/codes/p2p/utils.py @@ -1,7 +1,10 @@ +import json import sqlite3 +import time import requests import socket -from ...constants import NEWRL_P2P_DB + +from ...constants import MY_ADDRESS_FILE, NEWRL_P2P_DB, TIME_MINER_BROADCAST_INTERVAL_SECONDS def get_peers(): @@ -15,11 +18,26 @@ def get_peers(): def get_my_address(): - return requests.get('https://api.ipify.org?format=json').json()['ip'] + current_time = int(time.time()) + try: + with open(MY_ADDRESS_FILE, 'r') as f: + my_address_data = json.load(f) + if my_address_data['timestamp'] < current_time - TIME_MINER_BROADCAST_INTERVAL_SECONDS: + raise Exception('Reset address') + return my_address_data['address'] + except: + ip = requests.get('https://api.ipify.org?format=json').json()['ip'] + with open(MY_ADDRESS_FILE, 'w') as f: + data = { + 'address': ip, + 'timestamp': current_time + } + json.dump(data, f) + return ip def is_my_address(address): my_address = get_my_address() if socket.gethostbyname(address) == my_address: return True - return False \ No newline at end of file + return False diff --git a/app/codes/receiptmanager.py b/app/codes/receiptmanager.py new file mode 100644 index 0000000..45fee6c --- /dev/null +++ b/app/codes/receiptmanager.py @@ -0,0 +1,68 @@ +"""Receipt manager""" + +import sqlite3 + +from ..constants import NEWRL_DB + + +def store_receipt_to_db(receipt): + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() + + wallet_cursor = cur.execute( + 'SELECT wallet_address FROM wallets where wallet_public=?', (receipt['public_key'],)).fetchone() + + if wallet_cursor is not None: + + db_receipt_data = ( + receipt['data']['block_index'], + receipt['data']['block_hash'], + receipt['data']['vote'], + wallet_cursor[0], + ) + + cur.execute(''' + INSERT OR IGNORE INTO receipts (block_index, block_hash, vote, wallet_address) + VALUES(?, ?, ?, ?) + ''', db_receipt_data) + + con.commit() + con.close() + + +def get_receipts_for_block_from_db(block_index): + con = sqlite3.connect(NEWRL_DB) + con.row_factory = sqlite3.Row + cur = con.cursor() + receipt_cursor = cur.execute( + 'SELECT * FROM receipts where block_index=?', (block_index,)) + + # if receipt_cursor is None: + # return [] + receipts = receipt_cursor.fetchall() + + return receipts + + +def update_receipts_in_state(cur, block): + receipts = block['text']['previous_block_receipts'] + + for receipt in receipts: + wallet_cursor = cur.execute( + 'SELECT wallet_address FROM wallets where wallet_public=?', + (receipt['public_key'],)).fetchone() + + if wallet_cursor is not None: + db_receipt_data = ( + receipt['data']['block_index'], + receipt['data']['block_hash'], + receipt['data']['vote'], + receipt['timestamp'], + wallet_cursor[0], + ) + + cur.execute(''' + INSERT OR IGNORE INTO receipts + (block_index, block_hash, vote, timestamp, wallet_address) + VALUES(?, ?, ?, ?, ?) + ''', db_receipt_data) diff --git a/app/codes/scoremanager.py b/app/codes/scoremanager.py index 666950b..79dad19 100644 --- a/app/codes/scoremanager.py +++ b/app/codes/scoremanager.py @@ -8,7 +8,7 @@ def update_score_transaction(personid1, address1, personid2, address2, new_score transaction = { 'timestamp': get_time_ms(), 'type': TRANSACTION_TRUST_SCORE_CHANGE, - 'currency': "INR", + 'currency': "NWRL", 'fee': 0.0, 'descr': "Score update", 'valid': 1, diff --git a/app/codes/state_updater.py b/app/codes/state_updater.py index 4527a17..f83d738 100644 --- a/app/codes/state_updater.py +++ b/app/codes/state_updater.py @@ -2,11 +2,14 @@ import json import importlib from lib2to3.pgen2 import token +from app.codes.clock.global_time import get_corrected_time_ms + +from app.nvalues import NETWORK_TRUST_MANAGER from ..constants import NEWRL_DB from .db_updater import * -from ..ntypes import NEWRL_TOKEN_CODE, NEWRL_TOKEN_NAME +from ..ntypes import NEWRL_TOKEN_CODE, NEWRL_TOKEN_NAME, TRANSACTION_MINER_ADDITION, TRANSACTION_ONE_WAY_TRANSFER, TRANSACTION_SMART_CONTRACT, TRANSACTION_TOKEN_CREATION, TRANSACTION_TRUST_SCORE_CHANGE, TRANSACTION_TWO_WAY_TRANSFER, TRANSACTION_WALLET_CREATION def update_db_states(cur, block): @@ -21,11 +24,14 @@ def update_db_states(cur, block): # latest_index = cur.execute('SELECT MAX(block_index) FROM blocks') add_tx_to_block(cur, newblockindex, transactions) - if 'creator_wallet' in block: + if 'creator_wallet' in block and block['creator_wallet'] is not None: add_block_reward(cur, block['creator_wallet'], newblockindex) for transaction in transactions: + signature=transaction['signatures'] + transaction = transaction['transaction'] transaction_data = transaction['specific_data'] + while isinstance(transaction_data, str): transaction_data = json.loads(transaction_data) @@ -36,19 +42,20 @@ def update_db_states(cur, block): transaction['type'], transaction_data, transaction_code, - transaction['timestamp'] + transaction['timestamp'], + signature ) return True -def update_state_from_transaction(cur, transaction_type, transaction_data, transaction_code, transaction_timestamp): - if transaction_type == 1: # this is a wallet creation transaction +def update_state_from_transaction(cur, transaction_type, transaction_data, transaction_code, transaction_timestamp,transaction_signer=None): + if transaction_type == TRANSACTION_WALLET_CREATION: # this is a wallet creation transaction add_wallet_pid(cur, transaction_data) - if transaction_type == 2: # this is a token creation or addition transaction + if transaction_type == TRANSACTION_TOKEN_CREATION: # this is a token creation or addition transaction add_token(cur, transaction_data, transaction_code) - if transaction_type == 4 or transaction_type == 5: # this is a transfer tx + if transaction_type == TRANSACTION_TWO_WAY_TRANSFER or transaction_type == TRANSACTION_ONE_WAY_TRANSFER: # this is a transfer tx sender1 = transaction_data['wallet1'] sender2 = transaction_data['wallet2'] @@ -62,14 +69,14 @@ def update_state_from_transaction(cur, transaction_type, transaction_data, trans transfer_tokens_and_update_balances( cur, sender2, sender1, tokencode2, amount2) - if transaction_type == 6: # score update transaction + if transaction_type == TRANSACTION_TRUST_SCORE_CHANGE: # score update transaction personid1 = get_pid_from_wallet(cur, transaction_data['address1']) personid2 = get_pid_from_wallet(cur, transaction_data['address2']) new_score = transaction_data['new_score'] tstamp = transaction_timestamp update_trust_score(cur, personid1, personid2, new_score, tstamp) - if transaction_type == 3: # smart contract transaction + if transaction_type == TRANSACTION_SMART_CONTRACT: # smart contract transaction funct = transaction_data['function'] if funct == "setup": # sc is being set up contract = dict(transaction_data['params']) @@ -83,7 +90,21 @@ def update_state_from_transaction(cur, transaction_type, transaction_data, trans sc_instance = sc_class(transaction_data['address']) # sc_instance = nusd1(transaction['specific_data']['address']) funct = getattr(sc_instance, funct) - funct(cur, transaction_data['params']) + params_for_funct=transaction_data['params'] + # adding singers address to the dict + params_for_funct['function_caller']=transaction_signer + try: + funct(cur, params_for_funct) + except Exception as e: + print('Exception durint smart contract function run', e) + if transaction_type == TRANSACTION_MINER_ADDITION: + add_miner( + cur, + transaction_data['wallet_address'], + transaction_data['network_address'], + transaction_data['broadcast_timestamp'], + ) + def add_block_reward(cur, creator, blockindex): @@ -102,9 +123,38 @@ def add_block_reward(cur, creator, blockindex): "custodian": '', "legaldochash": '', "amount_created": reward, - "value_created": '', "disallowed": {}, "sc_flag": False } add_token(cur, reward_tx_data) return True + + +def update_trust_scores(cur, block): + receipts = block['text']['previous_block_receipts'] + + for receipt in receipts: + wallet_cursor = cur.execute( + 'SELECT wallet_address FROM wallets where wallet_public=?', + (receipt['public_key'],)).fetchone() + + if wallet_cursor is not None: + wallet_address = wallet_cursor[0] + vote = receipt['data']['vote'] + + trust_score_cursor = cur.execute(''' + SELECT score FROM trust_scores where src_person_id=? and dest_person_id=? + ''', (NETWORK_TRUST_MANAGER, wallet_address)).fetchone() + + if trust_score_cursor is None: + existing_score = 1 + else: + existing_score = trust_score_cursor[0] + + # TODO - This logic might need to change condisering all the persons in chain + if vote == 1: + new_score = (existing_score + 1) / 2 + else: + new_score = (existing_score - 1) / 2 + + update_trust_score(cur, NETWORK_TRUST_MANAGER, wallet_address, new_score, get_corrected_time_ms()) diff --git a/app/codes/tokenmanager.py b/app/codes/tokenmanager.py index d33419e..e856ad9 100644 --- a/app/codes/tokenmanager.py +++ b/app/codes/tokenmanager.py @@ -1,15 +1,14 @@ # Python programm to create object that enables addition of a block -import datetime - +from .utils import get_time_ms from .transactionmanager import Transactionmanager def create_token_transaction(token_data): transaction = { - 'timestamp': str(datetime.datetime.now()), + 'timestamp': get_time_ms(), 'type': 2, - 'currency': "INR", + 'currency': "NWRL", 'fee': 0.0, 'descr': "New token creation", 'valid': 1, diff --git a/app/codes/transactionmanager.py b/app/codes/transactionmanager.py index 4e2fff1..adc8a30 100644 --- a/app/codes/transactionmanager.py +++ b/app/codes/transactionmanager.py @@ -8,7 +8,9 @@ import base64 import sqlite3 -from ..ntypes import TRANSACTION_ONE_WAY_TRANSFER, TRANSACTION_SMART_CONTRACT, TRANSACTION_TRUST_SCORE_CHANGE, TRANSACTION_TWO_WAY_TRANSFER, TRANSACTION_WALLET_CREATION, TRANSCATION_TOKEN_CREATION + +from ..ntypes import TRANSACTION_MINER_ADDITION, TRANSACTION_ONE_WAY_TRANSFER, TRANSACTION_SMART_CONTRACT, TRANSACTION_TRUST_SCORE_CHANGE, TRANSACTION_TWO_WAY_TRANSFER, TRANSACTION_WALLET_CREATION, TRANSACTION_TOKEN_CREATION + from .chainscanner import get_wallet_token_balance from ..constants import ALLOWED_CUSTODIANS_FILE, MEMPOOL_PATH, NEWRL_DB from .utils import get_time_ms @@ -20,7 +22,7 @@ def __init__(self): 'timestamp': get_time_ms(), 'trans_code': "0000", 'type': 0, - 'currency': "INR", + 'currency': "NWRL", 'fee': 0.0, 'descr': None, 'valid': 1, @@ -115,7 +117,12 @@ def verify_sign(self, sign_trans, public_key_bytes): def verifytransigns(self): # need to add later a check for addresses mentioned in the transaction (vary by type) and the signing ones - validadds = self.get_valid_addresses() + try: + validadds = self.get_valid_addresses() + if validadds == False: + return False + except Exception as e: + return False addvaliditydict = {} for valadd in validadds: addvaliditydict[valadd] = False @@ -203,7 +210,7 @@ def econvalidator(self): # from mempool only include transactions that reduce balance and not those that increase # check if the sender has enough balance to spend self.validity = 0 - if self.transaction['type'] == 1: + if self.transaction['type'] == TRANSACTION_WALLET_CREATION: custodian = self.transaction['specific_data']['custodian_wallet'] walletaddress = self.transaction['specific_data']['wallet_address'] if not is_wallet_valid(custodian): @@ -248,7 +255,7 @@ def econvalidator(self): self.validity = 0 # self.validity=0 - if self.transaction['type'] == 2: # token addition transaction + if self.transaction['type'] == TRANSACTION_TOKEN_CREATION: # token addition transaction firstowner = self.transaction['specific_data']['first_owner'] custodian = self.transaction['specific_data']['custodian'] fovalidity = False @@ -302,7 +309,7 @@ def econvalidator(self): "Tokencode provided does not exist. Will append as new one.") self.validity = 1 # tokencode is provided by user - if self.transaction['type'] == 3: + if self.transaction['type'] == TRANSACTION_SMART_CONTRACT: self.validity = 1 for wallet in self.transaction['specific_data']['signers']: if not is_wallet_valid(wallet): @@ -313,7 +320,7 @@ def econvalidator(self): self.validity = 0 # self.validity=0 - if self.transaction['type'] == 4 or self.transaction['type'] == 5: + if self.transaction['type'] == TRANSACTION_TWO_WAY_TRANSFER or self.transaction['type'] == TRANSACTION_ONE_WAY_TRANSFER: ttype = self.transaction['type'] startingbalance1 = 0 startingbalance2 = 0 @@ -404,7 +411,7 @@ def econvalidator(self): # self.transaction['valid']=1; self.validity = 1 - if self.transaction['type'] == 6: # score change transaction + if self.transaction['type'] == TRANSACTION_TRUST_SCORE_CHANGE: # score change transaction ttype = self.transaction['type'] # personid1 = self.transaction['specific_data']['personid1'] # personid2 = self.transaction['specific_data']['personid2'] @@ -430,6 +437,14 @@ def econvalidator(self): self.validity = 0 else: self.validity = 1 + + if self.transaction['type'] == TRANSACTION_MINER_ADDITION: + # No checks for fee in the beginning + if not is_wallet_valid(self.transaction['specific_data']['wallet_address']): + print("Miner wallet not in chain") + self.validity = 0 + else: + self.validity = 1 if self.validity == 1: return True @@ -515,7 +530,7 @@ def get_sc_validadds(transaction): return validadds if not address: print("Invalid call to a function of a contract yet to be set up.") - return False + return [-1] con = sqlite3.connect(NEWRL_DB) cur = con.cursor() signatories = cur.execute( @@ -523,7 +538,7 @@ def get_sc_validadds(transaction): con.close() if signatories is None: print("Contract does not exist.") - return False + return [-1] functsignmap = json.loads(signatories[0]) if funct in functsignmap: # function is allowed to be called # checking if stated signer is in allowed list @@ -534,7 +549,7 @@ def get_sc_validadds(transaction): return validadds else: print("Either function is not valid or it cannot be called in a transaction.") - return False + return [-1] def get_valid_addresses(transaction): @@ -544,11 +559,13 @@ def get_valid_addresses(transaction): if transaction_type == TRANSACTION_WALLET_CREATION: # Custodian needs to sign valid_addresses.append( transaction['specific_data']['custodian_wallet']) - if transaction_type == TRANSCATION_TOKEN_CREATION: # Custodian needs to sign + if transaction_type == TRANSACTION_TOKEN_CREATION: # Custodian needs to sign valid_addresses.append(transaction['specific_data']['custodian']) if transaction_type == TRANSACTION_SMART_CONTRACT: valid_addresses = get_sc_validadds(transaction) if transaction_type == TRANSACTION_TWO_WAY_TRANSFER: # Both senders need to sign + if transaction['specific_data']['wallet1'] == transaction['specific_data']['wallet2']: + raise Exception('Both senders cannot be same') valid_addresses.append(transaction['specific_data']['wallet1']) valid_addresses.append(transaction['specific_data']['wallet2']) if transaction_type == TRANSACTION_ONE_WAY_TRANSFER: # Only sender1 is needed to sign @@ -556,4 +573,6 @@ def get_valid_addresses(transaction): if transaction_type == TRANSACTION_TRUST_SCORE_CHANGE: # Only address1 is added, not address2 valid_addresses.append(transaction['specific_data']['address1']) + if transaction_type == TRANSACTION_MINER_ADDITION: + valid_addresses.append(transaction['specific_data']['wallet_address']) return valid_addresses diff --git a/app/codes/updater.py b/app/codes/updater.py index 959b49b..f9a1aec 100644 --- a/app/codes/updater.py +++ b/app/codes/updater.py @@ -2,28 +2,45 @@ import datetime import json import os +import logging import sqlite3 -import requests +import threading -from ..nvalues import TREASURY_WALLET_ADDRESS -from ..constants import ALLOWED_FEE_PAYMENT_TOKENS, IS_TEST, NEWRL_DB, NEWRL_PORT, REQUEST_TIMEOUT, MEMPOOL_PATH, TIME_BETWEEN_BLOCKS_SECONDS +from app.codes.receiptmanager import get_receipts_for_block_from_db + +from .clock.global_time import get_corrected_time_ms, get_time_difference +from .fs.temp_manager import get_blocks_for_index_from_storage, get_receipts_from_storage, store_block_to_temp +from .minermanager import am_i_in_current_committee, broadcast_miner_update, get_committee_for_current_block, get_miner_for_current_block, should_i_mine +from ..nvalues import ASQI_WALLET, TREASURY_WALLET_ADDRESS +from ..constants import ALLOWED_FEE_PAYMENT_TOKENS, BLOCK_RECEIVE_TIMEOUT_SECONDS, BLOCK_TIME_INTERVAL_SECONDS, COMMITTEE_SIZE, GLOBAL_INTERNAL_CLOCK_SECONDS, IS_TEST, NEWRL_DB, NEWRL_PORT, NO_BLOCK_TIMEOUT, NO_RECEIPT_COMMITTEE_TIMEOUT, REQUEST_TIMEOUT, MEMPOOL_PATH, TIME_BETWEEN_BLOCKS_SECONDS, TIME_MINER_BROADCAST_INTERVAL_SECONDS from .p2p.peers import get_peers from .p2p.utils import is_my_address from .utils import BufferedLog, get_time_ms -from .blockchain import Blockchain +from .blockchain import Blockchain, get_last_block, get_last_block_index from .transactionmanager import Transactionmanager, get_valid_addresses from .state_updater import update_db_states from .crypto import calculate_hash, sign_object, _private, _public from .consensus.consensus import generate_block_receipt from .chainscanner import get_wallet_token_balance from .db_updater import transfer_tokens_and_update_balances -from .p2p.outgoing import send_request_in_thread +from .p2p.outgoing import broadcast_block, broadcast_receipt, send_request_in_thread +from .auth.auth import get_wallet + + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +MAX_BLOCK_SIZE = 1000 -MAX_BLOCK_SIZE = 10 +TIMERS = { + 'mining_timer': None, + 'block_receive_timeout': None, +} -def run_updater(): - logger = BufferedLog() + +def run_updater(add_to_chain=False): + # logger = BufferedLog() blockchain = Blockchain() con = sqlite3.connect(NEWRL_DB) @@ -33,9 +50,8 @@ def run_updater(): latest_ts = blockchain.get_latest_ts(cur) filenames = os.listdir(MEMPOOL_PATH) # this is the mempool - logger.log("Files in mempool: ", filenames) + logger.info(f"Files in mempool: {filenames}") textarray = [] - signarray = [] transfiles = filenames txcodes = [] tmtemp = Transactionmanager() @@ -46,10 +62,10 @@ def run_updater(): file = MEMPOOL_PATH + filename try: with open(file, "r") as read_file: - logger.log("Processing ", file) + logger.info(f"Processing {file}") transaction_file_data = json.load(read_file) except: - logger.log("Couldn't load transaction file ", file) + logger.info(f"Couldn't load transaction file {file}") continue transaction = transaction_file_data['transaction'] @@ -58,7 +74,7 @@ def run_updater(): # new code for validating again trandata = tmtemp.loadtransactionpassive(file) if not tmtemp.verifytransigns(): - logger.log( + logger.info( f"Transaction id {trandata['transaction']['trans_code']} has invalid signatures") os.remove(file) continue @@ -67,12 +83,11 @@ def run_updater(): os.remove(file) continue if not tmtemp.econvalidator(): - logger.log("Economic validation failed for transaction ", - trandata['transaction']['trans_code']) + logger.info(f"Economic validation failed for transaction {trandata['transaction']['trans_code']}") os.remove(file) continue - logger.log("Found valid transaction, checking if it is already included") + logger.info("Found valid transaction, checking if it is already included") transactions_cursor = cur.execute("SELECT * FROM transactions where transaction_code='" + transaction['trans_code'] + "'") row = transactions_cursor.fetchone() if row is not None: @@ -80,85 +95,69 @@ def run_updater(): os.remove(file) continue + if not should_include_transaction(transaction): + os.remove(file) + continue + if trandata['transaction']['trans_code'] not in txcodes: - textarray.append(transaction) - signarray.append(signatures) + textarray.append(transaction_file_data) txcodes.append(trandata['transaction']['trans_code']) transaction_fees += get_fees_for_transaction(trandata['transaction']) - try: - os.remove(file) - except: - logger.log("Couldn't delete:",file) + # Delete the transaction from mempool at the stage of accepting + # try: + # os.remove(file) + # except: + # logger.info("Couldn't delete:",file) block_height += 1 if block_height >= MAX_BLOCK_SIZE: - logger.log( + logger.info( "Reached max block height, moving forward with the collected transactions") break - transactionsdata = {"transactions": textarray, "signatures": signarray} + transactionsdata = {"transactions": textarray} if len(textarray) > 0: - logger.log(f"Found {len(textarray)} transactions. Adding to chain") + logger.info(f"Found {len(textarray)} transactions. Adding to chain") else: - logger.log("No new transactions. Checking for time.") - logger.log("latest ts:", latest_ts, "\tNow: ", datetime.datetime.now()) + logger.info("No new transactions. Checking for time.") + logger.info(f"latest TS:{latest_ts} Now: {datetime.datetime.now()}") try: time_diff = get_time_ms() - int(latest_ts) except Exception as e: time_diff = TIME_BETWEEN_BLOCKS_SECONDS * 1000 + 1 # Set a high timelimit as no last block timestamp found - logger.log("Time since last block: ", time_diff, " seconds") + logger.info(f"Time since last block: {time_diff} seconds") if time_diff < TIME_BETWEEN_BLOCKS_SECONDS * 1000: # TODO - Change the block time limit - logger.log("No new transactions, not enough time since last block. Exiting.") + logger.info("No new transactions, not enough time since last block. Exiting.") return logger.get_logs() else: - logger.log(f"More than {TIME_BETWEEN_BLOCKS_SECONDS} seconds since the last block. Adding a new empty one.") - - print(transactionsdata) - block = blockchain.mine_block(cur, transactionsdata, transaction_fees) - update_db_states(cur, block) - con.commit() - con.close() + logger.info(f"More than {TIME_BETWEEN_BLOCKS_SECONDS} seconds since the last block. Adding a new empty one.") - # Generate and add a single receipt to the block of mining node - # block_receipt = generate_block_receipt(block) - # block['receipts'] = [block_receipt] - - if not IS_TEST: - broadcast_block(block) + previous_block = get_last_block(cur=cur) + transactionsdata['previous_block_receipts'] = get_receipts_from_storage(previous_block['index']) - return logger.get_logs() - -def broadcast_block(block): - peers = get_peers() - - private_key = _private - public_key = _public - signature = { - 'public': public_key, - 'msgsign': sign_object(private_key, block) - } + if add_to_chain: + block = blockchain.mine_block(cur, transactionsdata) + update_db_states(cur, block) + con.commit() + con.close() + else: + block = blockchain.propose_block(cur, transactionsdata) + block_receipt = generate_block_receipt(block) block_payload = { - 'block_index': block['index'], + 'index': block['index'], 'hash': calculate_hash(block), 'data': block, - 'signature': signature + 'receipts': [block_receipt] } + store_block_to_temp(block_payload) + logger.info(f'Stored block to temp with payload {json.dumps(block_payload)}') + if not IS_TEST: + nodes = get_committee_for_current_block() + if len(nodes) < COMMITTEE_SIZE: + nodes = get_peers() + broadcast_block(block_payload, nodes) - print(json.dumps(block_payload)) - - # TODO - Do not send to self - for peer in peers: - if is_my_address(peer['address']): - continue - url = 'http://' + peer['address'] + ':' + str(NEWRL_PORT) - print('Broadcasting to peer', url) - try: - send_request_in_thread(url + '/receive-block', {'block': block_payload}) - # requests.post(url + '/receive-block', json={'block': block_payload}, timeout=REQUEST_TIMEOUT) - except Exception as e: - print(f'Error broadcasting block to peer: {url}') - print(e) - return True + return block_payload def get_fees_for_transaction(transaction): @@ -190,3 +189,137 @@ def pay_fee_for_transaction(cur, transaction): fee / len(payees) ) return True + + +def create_empty_block_receipt_and_broadcast(): + logger.info('No block timeout. Mining empty block and sending receipts.') + # block_index = get_last_block_index() + 1 + # blocks_in_storage = get_blocks_for_index_from_storage(block_index) + # if len(blocks_in_storage) != 0: + # logger.info('Block already exist in storage. Not mining empty block.') + # return + blockchain = Blockchain() + block = blockchain.mine_empty_block() + block_receipt = generate_block_receipt(block) + block_payload = { + 'index': block['index'], + 'hash': calculate_hash(block), + 'data': block, + 'receipts': [block_receipt] + } + store_block_to_temp(block_payload) + + committee = get_committee_for_current_block() + broadcast_receipt(block_receipt, committee) + return block_payload + + +def start_empty_block_mining_clock(block_timestamp): + global TIMERS + current_ts_seconds = get_corrected_time_ms() / 1000 + block_ts_seconds = block_timestamp / 1000 + seconds_to_wait = block_ts_seconds + BLOCK_TIME_INTERVAL_SECONDS + NO_BLOCK_TIMEOUT - current_ts_seconds + timer = threading.Timer(seconds_to_wait, create_empty_block_receipt_and_broadcast) + timer.start() + TIMERS['block_receive_timeout'] = timer + + +def mine(add_to_chain=False): + if should_i_mine() or add_to_chain: + logger.info('I am the miner for this block.') + return run_updater(add_to_chain) + # elif am_i_in_current_committee(): + # start_empty_block_mining_clock() + # logger.info('I am committee member. Starting no block timeout.') + else: + miner = get_miner_for_current_block() + logger.info(f"Miner for current block is {miner['wallet_address']}. Waiting to receive block.") + + +def start_mining_clock(block_timestamp): + # if TIMERS['mining_timer'] is not None: + # TIMERS['mining_timer'].cancel() + # if TIMERS['block_receive_timeout'] is not None: + # TIMERS['block_receive_timeout'].cancel() + # TIMERS['block_receive_timeout'] = None + current_ts_seconds = get_corrected_time_ms() / 1000 + block_ts_seconds = block_timestamp / 1000 + seconds_to_wait = block_ts_seconds + BLOCK_TIME_INTERVAL_SECONDS - current_ts_seconds + logger.info(f'Block time timestamp is {block_ts_seconds}. Current timestamp is {current_ts_seconds}. Waiting {seconds_to_wait} seconds to mine next block') + timer = threading.Timer(seconds_to_wait, mine) + timer.start() + TIMERS['mining_timer'] = timer + + +def start_miner_broadcast_clock(): + logger.info('Broadcasting miner update') + try: + broadcast_miner_update() + except Exception as e: + logger.info(f'Could not broadcast miner update {e}') + timer = threading.Timer(TIME_MINER_BROADCAST_INTERVAL_SECONDS, start_miner_broadcast_clock) + timer.start() + + +def should_include_transaction(transaction): + if transaction['type'] == 7: + broadcast_timestamp = transaction['specific_data']['broadcast_timestamp'] + if broadcast_timestamp < get_corrected_time_ms() - TIME_MINER_BROADCAST_INTERVAL_SECONDS * 1000: + return False + return True + + +def global_internal_clock(): + """Reccuring clock for all node level activities""" + global TIMERS + + try: + # Check for mining delay + current_ts = get_corrected_time_ms() + last_block = get_last_block() + if last_block: + last_block_ts = int(last_block['timestamp']) + time_elapsed_seconds = (current_ts - last_block_ts) / 1000 + + if time_elapsed_seconds > BLOCK_TIME_INTERVAL_SECONDS * 2: + if am_i_sentinel_node(): + logger.info('I am sentitnel node. Mining empty block') + sentitnel_node_mine_empty() + if should_i_mine(last_block): + if TIMERS['mining_timer'] is None or not TIMERS['mining_timer'].is_alive(): + start_mining_clock(last_block_ts) + # elif am_i_in_current_committee(last_block): + # if TIMERS['block_receive_timeout'] is None or not TIMERS['block_receive_timeout'].is_alive(): + # start_empty_block_mining_clock(last_block_ts) + except Exception as e: + logger.info(f'Error in global clock {e}') + + timer = threading.Timer(GLOBAL_INTERNAL_CLOCK_SECONDS, global_internal_clock) + timer.start() + + +def am_i_sentinel_node(): + my_wallet = get_wallet() + return my_wallet['address'] == ASQI_WALLET + + +def sentitnel_node_mine_empty(): + blockchain = Blockchain() + current_time_ms = get_corrected_time_ms() + block = blockchain.mine_empty_block(current_time_ms) + block_receipt = generate_block_receipt(block) + block_payload = { + 'index': block['index'], + 'hash': calculate_hash(block), + 'data': block, + 'receipts': [block_receipt] + } + broadcast_block(block_payload=block_payload) + + +def get_timers(): + """Get timer status""" + return { + 'is_mining': TIMERS['mining_timer'] is not None and TIMERS['mining_timer'].is_alive(), + 'is_waiting_block_timeout': TIMERS['block_receive_timeout'] is not None and TIMERS['block_receive_timeout'].is_alive(), + } \ No newline at end of file diff --git a/app/codes/utils.py b/app/codes/utils.py index 106063c..d16ac7e 100644 --- a/app/codes/utils.py +++ b/app/codes/utils.py @@ -1,6 +1,10 @@ import hashlib +import sqlite3 import time +from ..constants import NEWRL_DB +from .clock.global_time import get_corrected_time_ms + def save_file_and_get_path(upload_file): if upload_file is None: @@ -25,7 +29,8 @@ def get_logs(self): def get_time_ms(): """Return time in milliseconds""" - return round(time.time() * 1000) + # return round(time.time() * 1000) + return get_corrected_time_ms() def get_person_id_for_wallet_address(wallet_address): @@ -33,3 +38,23 @@ def get_person_id_for_wallet_address(wallet_address): hs.update(wallet_address.encode()) person_id = 'pi' + hs.hexdigest() return person_id + + +def get_last_block_hash(): + """Get last block hash from db""" + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() + last_block_cursor = cur.execute( + 'SELECT block_index, hash, timestamp FROM blocks ORDER BY block_index DESC LIMIT 1' + ) + last_block = last_block_cursor.fetchone() + con.close() + + if last_block is not None: + return { + 'index': last_block[0], + 'hash': last_block[1], + 'timestamp': last_block[2] + } + else: + return None \ No newline at end of file diff --git a/app/codes/validator.py b/app/codes/validator.py index 5aacd8d..33773f3 100644 --- a/app/codes/validator.py +++ b/app/codes/validator.py @@ -10,9 +10,9 @@ from app.codes.fs.mempool_manager import get_mempool_transaction from app.codes.p2p.transport import send -from .blockchain import get_last_block_hash +from .utils import get_last_block_hash from .transactionmanager import Transactionmanager -from ..constants import MEMPOOL_PATH +from ..constants import IS_TEST, MEMPOOL_PATH from .p2p.outgoing import propogate_transaction_to_peers @@ -20,43 +20,58 @@ logger = logging.getLogger(__name__) -def validate(transaction): +def validate(transaction, propagate=False, validate_economics=True): existing_transaction = get_mempool_transaction(transaction['transaction']['trans_code']) if existing_transaction is not None: - return True + return {'valid': True, 'msg': 'Already validated and in mempool'} transaction_manager = Transactionmanager() transaction_manager.set_transaction_data(transaction) - economics_valid = transaction_manager.econvalidator() signatures_valid = transaction_manager.verifytransigns() valid = False - if economics_valid and signatures_valid: - msg = "All well" - valid = True - if not economics_valid: - msg = "Economic validation failed" if not signatures_valid: - msg = "Invalid signatures" + msg = "Transaction has invalid signatures" + if validate_economics: + economics_valid = transaction_manager.econvalidator() + if not economics_valid: + msg = "Transaction economic validation failed" + valid = False + msg = "Transaction economic validation successful" + valid = True + else: + msg = "Valid signatures. Not checking economics" + valid = True + # if economics_valid and signatures_valid: + # msg = "Transaction is valid" + # valid = True + # if not economics_valid: + # msg = "Transaction economic validation failed" + check = {'valid': valid, 'msg': msg} if valid: # Economics and signatures are both valid transaction_file = f"{MEMPOOL_PATH}transaction-{transaction_manager.transaction['type']}-{transaction_manager.transaction['trans_code']}.json" transaction_manager.save_transaction_to_mempool(transaction_file) - # Broadcast transaction to peers - propogate_transaction_to_peers(transaction_manager.get_transaction_complete()) - - # Broadcaset transaction via transport server - try: - payload = { - 'operation': 'send_transaction', - 'data': transaction_manager.get_transaction_complete() - } - send(payload) - except: - print('Error sending transaction to transport server') - - print(check) + if propagate and not IS_TEST: + # Broadcast transaction to peers via HTTP + exclude_nodes = transaction['peers_already_broadcasted'] if 'peers_already_broadcasted' in transaction else None + propogate_transaction_to_peers( + transaction_manager.get_transaction_complete(), + exclude_nodes=exclude_nodes + ) + + # Broadcaset transaction via transport server + # try: + # payload = { + # 'operation': 'send_transaction', + # 'data': transaction_manager.get_transaction_complete() + # } + # send(payload) + # except: + # print('Error sending transaction to transport server') + + print(msg) return check @@ -74,8 +89,8 @@ def validate_signature(data, public_key, signature): def validate_receipt_signature(receipt): try: - return validate_signature(receipt['data'], receipt['public'], receipt['signature']) - except: + return validate_signature(receipt['data'], receipt['public_key'], receipt['signature']) + except Exception as e: logger.error('Error validating receipt signature') return False @@ -84,58 +99,35 @@ def get_node_trust_score(public_key): # TODO - Return the actual trust score of the node by lookup on public_key return 1 + def validate_block_receipts(block): total_receipt_count = 0 - postitive_receipt_count = 0 + positive_receipt_count = 0 for receipt in block['receipts']: total_receipt_count += 1 if not validate_receipt_signature(receipt): continue - if receipt['data']['block_index'] != block['index'] or receipt['data']['block_hash'] != block['hash'] or receipt['data']['vote'] < 1: + if receipt['data']['block_index'] != block['index'] or receipt['data']['vote'] < 1: continue - trust_score = get_node_trust_score(receipt['public']) + trust_score = get_node_trust_score(receipt['public_key']) valid_probability = 0 if trust_score < 0 else (trust_score + 2) / 5 - # raise Exception('Invalid receipt signature') - if receipt['data']['block_index'] != block['index'] or receipt['data']['block_hash'] != block['hash']: - raise Exception('Invalid receipt data') - if receipt['data']['vote'] == 1: - postitive_receipt_count += 1 + positive_receipt_count += 1 return { 'total_receipt_count': total_receipt_count, - 'postitive_receipt_count': postitive_receipt_count, + 'positive_receipt_count': positive_receipt_count, } -def validate_block(block, validate_receipts=True, should_validate_signature=True): - if block['hash'][:4] != '0000': - return False - +def validate_block(block): if not validate_block_data(block['data']): return False - if should_validate_signature: - sign_valid = validate_signature( - data=block['data'], - public_key=block['signature']['public'], - signature=block['signature']['msgsign'] - ) - - if not sign_valid: - logger.info('Invalid block signature') - return False - - if validate_receipts: - receipts_valid = validate_block_receipts(block) - if not receipts_valid: - logger.info('Invalid receipts') - return False - return True @@ -154,4 +146,12 @@ def validate_block_data(block): if last_block['index'] != block_index - 1: print('New block index is not 1 more than last block index') return False - return True \ No newline at end of file + return True + + +def validate_block_transactions(block): + for transaction in block['text']['transactions']: + validation_result = validate(transaction, propagate=False, validate_economics=True) + if not validation_result['valid']: + return False + return True diff --git a/app/constants.py b/app/constants.py index 0f04fc2..97ac8aa 100644 --- a/app/constants.py +++ b/app/constants.py @@ -3,12 +3,14 @@ from .ntypes import NEWRL_TOKEN_CODE, NUSD_TOKEN_CODE +SOFTWARE_VERSION = "0.0.12" IS_TEST = os.environ.get('NEWRL_TEST') is not None if IS_TEST: print('Using constants for Test') DATA_PATH = 'data_test/' if IS_TEST else 'data/' +LOG_FILE_PATH = DATA_PATH + 'logs/' MEMPOOL_PATH = DATA_PATH + 'mempool/' TMP_PATH = DATA_PATH + 'tmp/' INCOMING_PATH = DATA_PATH + 'tmp/incoming/' @@ -22,23 +24,30 @@ BOOTSTRAP_NODES = ['testnet.newrl.net'] REQUEST_TIMEOUT = 1 -NEWRL_PORT = 8090 +NEWRL_PORT = 8182 # Devnet NEWRL_TOKEN = "newrl_token" TREASURY = "treasury_address" COINBASE_SC = "coinbase_sc_address" TRANSPORT_SERVER = 'http://localhost:8095' -TIME_BETWEEN_BLOCKS_SECONDS = 5 # The time period between blocks -COMMITTEE_SIZE = 6 +GLOBAL_INTERNAL_CLOCK_SECONDS = 5 # The time period between blocks +TIME_BETWEEN_BLOCKS_SECONDS = 30 # The time period between blocks +COMMITTEE_SIZE = 10 MINIMUM_ACCEPTANCE_VOTES = 4 -MINIMUM_ACCEPTANCE_RATIO = 0.6 +MINIMUM_ACCEPTANCE_RATIO = 0.51 +NO_BLOCK_TIMEOUT = 10 # No block received timeout in seconds NO_RECEIPT_COMMITTEE_TIMEOUT = 10 # Timeout in seconds -NO_BLOCK_TIMEOUT = 5 # No block received timeout in seconds +NETWORK_BLOCK_TIMEOUT = 25 +MAX_BROADCAST_NODES = 13 # Variables +MY_ADDRESS_FILE = DATA_PATH + 'my_address.json' +TIME_DIFF_WITH_GLOBAL_FILE = DATA_PATH + 'time_diff.txt' TIME_DIFF_WITH_GLOBAL = 0 MAX_ALLOWED_TIME_DIFF_SECONDS = 10 -BLOCK_TIME_INTERVAL_SECONDS = 30 +BLOCK_TIME_INTERVAL_SECONDS = TIME_BETWEEN_BLOCKS_SECONDS +BLOCK_RECEIVE_TIMEOUT_SECONDS = 5 +TIME_MINER_BROADCAST_INTERVAL_SECONDS = 600 MY_ADDRESS = '' ALLOWED_FEE_PAYMENT_TOKENS = [NEWRL_TOKEN_CODE, NUSD_TOKEN_CODE] \ No newline at end of file diff --git a/app/main.py b/app/main.py index 342b30e..740db5a 100644 --- a/app/main.py +++ b/app/main.py @@ -1,30 +1,24 @@ import logging +from .codes.log_config import logger_init +logger_init() import argparse +import os import uvicorn from fastapi.openapi.utils import get_openapi from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from app.codes.p2p.sync_chain import sync_chain_from_peers - +from .codes.p2p.sync_chain import sync_chain_from_peers from .constants import NEWRL_PORT from .codes.p2p.peers import init_bootstrap_nodes, update_my_address, update_software -from .codes.clock.global_time import start_mining_clock, update_time_difference +from .codes.clock.global_time import sync_timer_clock_with_global +from .codes.updater import global_internal_clock, start_miner_broadcast_clock, start_mining_clock -from .routers import blockchain -from .routers import p2p -from .routers import transport +from .routers import blockchain, system, p2p, transport -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -parser = argparse.ArgumentParser() -parser.add_argument("--disablenetwork", help="run the node local only with no network connection", action="store_true") -parser.add_argument("--disableupdate", help="run the node without updating software", action="store_true") -parser.add_argument("--disablebootstrap", help="run the node without bootstrapping", action="store_true") -args = parser.parse_args() - app = FastAPI( title="The Newrl APIs", description="This page covers all the public APIs available at present in the Newrl blockchain platform." @@ -42,25 +36,46 @@ app.include_router(blockchain.router) app.include_router(p2p.router) +app.include_router(system.router) app.include_router(transport.router) +args = { + 'disablenetwork': False, + 'disableupdate': False, + 'disablebootstrap': False, +} + @app.on_event('startup') def app_startup(): try: - if not args.disablenetwork: - if not args.disableupdate: + if not args['disablenetwork']: + sync_timer_clock_with_global() + if not args['disableupdate']: update_software(propogate=False) - if not args.disablebootstrap: + if not args['disablebootstrap']: init_bootstrap_nodes() sync_chain_from_peers() - update_time_difference() update_my_address() - # start_mining_clock() except Exception as e: print('Bootstrap failed') logging.critical(e, exc_info=True) + + start_miner_broadcast_clock() + global_internal_clock() + + +@app.on_event("shutdown") +def shutdown_event(): + print('Shutting down node') + os._exit(0) if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--disablenetwork", help="run the node local only with no network connection", action="store_true") + parser.add_argument("--disableupdate", help="run the node without updating software", action="store_true") + parser.add_argument("--disablebootstrap", help="run the node without bootstrapping", action="store_true") + _args = parser.parse_args() + args["disablenetwork"] = _args.disablenetwork uvicorn.run("app.main:app", host="0.0.0.0", port=NEWRL_PORT, reload=True) diff --git a/app/migrations/init.py b/app/migrations/init.py index 3356252..0c3e019 100644 --- a/app/migrations/init.py +++ b/app/migrations/init.py @@ -1,5 +1,5 @@ import os -from ..constants import INCOMING_PATH, MEMPOOL_PATH, TMP_PATH, DATA_PATH +from ..constants import INCOMING_PATH, MEMPOOL_PATH, TMP_PATH, DATA_PATH, LOG_FILE_PATH from ..migrations.init_db import init_db, init_trust_db from ..migrations.migrate_db import run_migrations @@ -7,6 +7,8 @@ def init_newrl(): if not os.path.exists(DATA_PATH): os.mkdir(DATA_PATH) + if not os.path.exists(LOG_FILE_PATH): + os.mkdir(LOG_FILE_PATH) if not os.path.exists(MEMPOOL_PATH): os.mkdir(MEMPOOL_PATH) if not os.path.exists(TMP_PATH): diff --git a/app/migrations/init_db.py b/app/migrations/init_db.py index 27f9795..93d27c2 100644 --- a/app/migrations/init_db.py +++ b/app/migrations/init_db.py @@ -2,6 +2,7 @@ import json from ..codes.state_updater import update_state_from_transaction +from .migrate_db import run_migrations from ..constants import NEWRL_DB, NEWRL_P2P_DB db_path = NEWRL_DB @@ -15,7 +16,11 @@ def clear_db(): cur.execute('DROP TABLE IF EXISTS blocks') cur.execute('DROP TABLE IF EXISTS transactions') cur.execute('DROP TABLE IF EXISTS transfers') + cur.execute('DROP TABLE IF EXISTS receipts') cur.execute('DROP TABLE IF EXISTS contracts') + cur.execute('DROP TABLE IF EXISTS miners') + cur.execute('DROP TABLE IF EXISTS dao_main') + cur.execute('DROP TABLE IF EXISTS dao_membership') con.commit() con.close() @@ -43,7 +48,6 @@ def init_db(): custodian text, legaldochash text, amount_created real, - value_created real, sc_flag integer, disallowed text, tokendecimal integer, @@ -68,6 +72,15 @@ def init_db(): creator_wallet text, transactions_hash text) ''') + + cur.execute(''' + CREATE TABLE IF NOT EXISTS receipts + (block_index integer, + block_hash text, + vote integer, + wallet_address text, + timestamp text) + ''') cur.execute(''' CREATE TABLE IF NOT EXISTS transactions @@ -80,7 +93,8 @@ def init_db(): fee real, description text, valid integer, - specific_data text) + specific_data text, + signatures text) ''') cur.execute(''' @@ -111,7 +125,16 @@ def init_db(): contractspecs TEXT, legalparams TEXT) ''') - + # cur.execute('DROP TABLE IF EXISTS miners') + cur.execute(''' + CREATE TABLE IF NOT EXISTS miners + (id text NOT NULL PRIMARY KEY, + wallet_address text, + network_address text NOT NULL, + last_broadcast_timestamp text, + UNIQUE (wallet_address) + ) + ''') con.commit() con.close() @@ -148,6 +171,46 @@ def init_trust_db(): score real, last_time integer) ''') + cur.execute(''' + CREATE TABLE IF NOT EXISTS dao_main + (dao_personid text NOT NULL, + dao_name text NOT NULL, + founder_personid text NOT NULL, + dao_sc_address text NOT NULL) + ''') + cur.execute(''' + CREATE TABLE IF NOT EXISTS dao_membership + (dao_person_id text NOT NULL, + member_person_id text NOT NULL) + ''') + cur.execute(''' + CREATE TABLE IF NOT EXISTS proposal_data + ( + proposal_id INTEGER PRIMARY KEY AUTOINCREMENT, + dao_person_id text NOT NULL, + function_called text NOT NULL, + params text , + yes_votes INT , + no_votes INT , + abstain_votes INT , + total_votes INT , + status text NOT NULL, + voting_start_ts text , + voting_end_ts text , + voter_data text + ) + ''') + cur.execute(''' + CREATE TABLE IF NOT EXISTS DAO_TOKEN_LOCK + ( + dao_id text Not NULL, + person_id text Not NULL, + pr oposal_list TEXT , + status INT, + amount_locked INT, + wallet_address text + ) + ''') con.commit() con.close() @@ -160,13 +223,20 @@ def revert_chain(block_index): cur = con.cursor() cur.execute(f'DELETE FROM blocks WHERE block_index > {block_index}') cur.execute(f'DELETE FROM transactions WHERE block_index > {block_index}') - cur.execute('DROP TABLE wallets') - cur.execute('DROP TABLE tokens') - cur.execute('DROP TABLE balances') + cur.execute('DROP TABLE IF EXISTS wallets') + cur.execute('DROP TABLE IF EXISTS tokens') + cur.execute('DROP TABLE IF EXISTS balances') + cur.execute('DROP TABLE IF EXISTS transfers') + cur.execute('DROP TABLE IF EXISTS contracts') + cur.execute('DROP TABLE IF EXISTS miners') con.commit() + con.close() init_db() - + run_migrations() + + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() transactions_cursor = cur.execute(f'SELECT transaction_code, block_index, type, timestamp, specific_data FROM transactions WHERE block_index <= {block_index}').fetchall() for transaction in transactions_cursor: transaction_code = transaction[0] diff --git a/app/migrations/migrate_state.py b/app/migrations/migrate_state.py index 3a71f50..66add77 100644 --- a/app/migrations/migrate_state.py +++ b/app/migrations/migrate_state.py @@ -27,9 +27,9 @@ def migrate_state(state_file_name): for token in state_data['all_tokens']: token_attributes = json.dumps(token['token_attributes']) if 'token_attributes' in token else '' - db_token_data = (token['tokencode'], token['tokenname'], token['tokentype'], token['first_owner'], token['custodian'], token['legaldochash'], token['amount_created'], token['value_created'], token['sc_flag'], token_attributes) + db_token_data = (token['tokencode'], token['tokenname'], token['tokentype'], token['first_owner'], token['custodian'], token['legaldochash'], token['amount_created'], token['sc_flag'], token_attributes) cur.execute(f'''INSERT OR IGNORE INTO tokens - (tokencode, tokenname, tokentype, first_owner, custodian, legaldochash, amount_created, value_created, sc_flag, token_attributes) + (tokencode, tokenname, tokentype, first_owner, custodian, legaldochash, amount_created, sc_flag, token_attributes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', db_token_data) for balance in state_data['all_balances']: diff --git a/app/migrations/migrations/1_token_changes.py b/app/migrations/migrations/1_token_changes.py index c1fb9ba..b6c4b87 100644 --- a/app/migrations/migrations/1_token_changes.py +++ b/app/migrations/migrations/1_token_changes.py @@ -17,7 +17,6 @@ def migrate(): custodian text, legaldochash text, amount_created integer, - value_created integer, sc_flag integer, disallowed text, parent_transaction_code text, @@ -26,13 +25,13 @@ def migrate(): ''') token_cursor = cur.execute(f'''select tokencode, tokenname, tokentype, first_owner, custodian, legaldochash, - amount_created, value_created, sc_flag, parent_transaction_code, token_attributes + amount_created, sc_flag, parent_transaction_code, token_attributes FROM tokens''').fetchall() for token in token_cursor: cur.execute(f'''INSERT OR IGNORE INTO _tokens (tokencode, tokenname, tokentype, first_owner, custodian, legaldochash, - amount_created, value_created, sc_flag, parent_transaction_code, token_attributes) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', token) + amount_created, sc_flag, parent_transaction_code, token_attributes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', token) cur.execute('DROP TABLE tokens') cur.execute('ALTER TABLE _tokens RENAME TO tokens') diff --git a/app/migrations/migrations/3_init_newrl_tokens.py b/app/migrations/migrations/3_init_newrl_tokens.py index 86e53f0..7d2a6ed 100644 --- a/app/migrations/migrations/3_init_newrl_tokens.py +++ b/app/migrations/migrations/3_init_newrl_tokens.py @@ -9,7 +9,7 @@ FOUNDATION_PUBLIC_KEY = 'sB8/+o32Q7tRTjB2XcG65QS94XOj9nP+mI7S6RIHuXzKLRlbpnu95Zw0MxJ2VGacF4TY5rdrIB8VNweKzEqGzg==' ASQI_PUBLIC_KEY = 'PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==' -FOUNDATION_RESERVE = 1500000000 +FOUNDATION_RESERVE = 3000000000 def migrate(): init_newrl_tokens() @@ -20,6 +20,8 @@ def init_newrl_tokens(): cur = con.cursor() create_newrl_tokens(cur, FOUNDATION_RESERVE * 2) + create_wallet(cur, FOUNDATION_WALLET, FOUNDATION_PUBLIC_KEY) + create_wallet(cur, ASQI_WALLET, ASQI_PUBLIC_KEY) credit_wallet(cur, FOUNDATION_WALLET, FOUNDATION_RESERVE) credit_wallet(cur, ASQI_WALLET, FOUNDATION_RESERVE) @@ -41,7 +43,16 @@ def create_newrl_tokens(cur, amount): (tokencode, tokenname, tokentype, amount_created, sc_flag, tokendecimal, token_attributes) VALUES (?, ?, ?, ?, ?, ?, ?)''', query_params) - + +def create_wallet(cur, wallet_address, wallet_public): + query_params = (wallet_address, + wallet_public, + '', '{}', '1', '91', '{}' + ) + cur.execute(f'''INSERT OR IGNORE INTO wallets + (wallet_address, wallet_public, custodian_wallet, kyc_docs, + owner_type, jurisdiction, specific_data) + VALUES (?, ?, ?, ?, ?, ?, ?)''', query_params) def credit_wallet(cur, wallet, amount): cur.execute(f'''INSERT OR IGNORE INTO balances diff --git a/app/migrations/migrations/4_add_transaction_signature.py b/app/migrations/migrations/4_add_transaction_signature.py new file mode 100644 index 0000000..9deea8f --- /dev/null +++ b/app/migrations/migrations/4_add_transaction_signature.py @@ -0,0 +1,42 @@ +import sqlite3 + +from ...constants import NEWRL_DB + + +def migrate(): + print('Running migration ' + __file__) + con = sqlite3.connect(NEWRL_DB) + # con.row_factory = sqlite3.Row + cur = con.cursor() + cur.execute(''' + CREATE TABLE IF NOT EXISTS _transactions + ( + transaction_code text PRIMARY KEY, + block_index integer, + timestamp text, + type integer, + currency text, + fee real, + description text, + valid integer, + specific_data text, + signatures text) + ''') + + cursor = cur.execute(f'''select transaction_code, block_index, timestamp, type, currency, fee, + description, valid, specific_data + FROM transactions''').fetchall() + for obj in cursor: + cur.execute(f'''INSERT OR IGNORE INTO _transactions + (transaction_code, block_index, timestamp, type, currency, fee, + description, valid, specific_data) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)''', obj) + + cur.execute('DROP TABLE transactions') + cur.execute('ALTER TABLE _transactions RENAME TO transactions') + con.commit() + con.close() + + +if __name__ == '__main__': + migrate() diff --git a/app/migrations/migrations/5_init_dao_manager.py b/app/migrations/migrations/5_init_dao_manager.py new file mode 100644 index 0000000..57b5830 --- /dev/null +++ b/app/migrations/migrations/5_init_dao_manager.py @@ -0,0 +1,58 @@ +import sqlite3 + +from app.constants import NEWRL_DB + +DAO_MANAGER = 'ct9dc895fe5905dc73a2273e70be077bf3e94ea3b7' +ASQI_WALLET = '0x20513a419d5b11cd510ae518dc04ac1690afbed6' + + +def migrate(): + print("Migrating DAO MANAGER") + init_dao_manager() + + +def init_dao_manager(): + """Initialise Dao_Manager.""" + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() + + create_dao_manager(cur, DAO_MANAGER) + + con.commit() + con.close() + + +def create_dao_manager(cur, address): + query_params = ( + address, + ASQI_WALLET, + 1648706655, + 'dao_manager', + '1.0.0', + 'hybrid', + '1', + None, + '{"setup": null, "deploy": null, "create": null}', + None, + '{}', + '1', + '{}', + '{}' + ) + dao_manager_exists = cur.execute(f'''SELECT COUNT(*) FROM CONTRACTS WHERE ADDRESS=? AND NAME LIKE ? ''', + (address, 'dao_manager')) + dao_manager_exists = dao_manager_exists.fetchone() + if (dao_manager_exists[0] == 0): + cur.execute(f'''INSERT OR IGNORE INTO CONTRACTS + (address, creator, ts_init, + name, version, actmode, status,next_act_ts, signatories, parent, oracleids, selfdestruct, contractspecs, legalparams) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', query_params) + try: + cur.execute(f'''alter table DAO_TOKEN_LOCK add COLUMN wallet_address text''') + except: + pass + + +if __name__ == '__main__': + # migrate() + pass diff --git a/app/ntypes.py b/app/ntypes.py index 6870c77..d60a913 100644 --- a/app/ntypes.py +++ b/app/ntypes.py @@ -1,12 +1,13 @@ """Type declarations""" TRANSACTION_WALLET_CREATION = 1 -TRANSCATION_TOKEN_CREATION = 2 +TRANSACTION_TOKEN_CREATION = 2 TRANSACTION_SMART_CONTRACT = 3 TRANSACTION_TWO_WAY_TRANSFER = 4 TRANSACTION_ONE_WAY_TRANSFER = 5 TRANSACTION_TRUST_SCORE_CHANGE = 6 +TRANSACTION_MINER_ADDITION = 7 NEWRL_TOKEN_CODE = 'NWRL' NUSD_TOKEN_CODE = 'NUSD' -NEWRL_TOKEN_NAME = 'Newrl' \ No newline at end of file +NEWRL_TOKEN_NAME = 'Newrl' diff --git a/app/nvalues.py b/app/nvalues.py index 0210171..ed8d517 100644 --- a/app/nvalues.py +++ b/app/nvalues.py @@ -1,2 +1,5 @@ TREASURY_WALLET_ADDRESS = '0x84d59feee611cc7b0545a1b0c0b24c3ae8c2c2dc' ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' +ASQI_WALLET = '0x20513a419d5b11cd510ae518dc04ac1690afbed6' +NETWORK_TRUST_MANAGER = '0x20513a419d5b11cd510ae518dc04ac1690afbed6' +DAO_MANAGER ='' \ No newline at end of file diff --git a/app/routers/blockchain.py b/app/routers/blockchain.py index d4127f4..a9aacfa 100644 --- a/app/routers/blockchain.py +++ b/app/routers/blockchain.py @@ -12,7 +12,7 @@ from app.codes.transactionmanager import Transactionmanager from .request_models import AddWalletRequest, BalanceRequest, BalanceType, CallSC, CreateTokenRequest, CreateWalletRequest, GetTokenRequest, RunSmartContractRequest, TransferRequest, CreateSCRequest, TscoreRequest -from app.codes.chainscanner import Chainscanner, download_chain, download_state, get_transaction +from app.codes.chainscanner import Chainscanner, download_chain, download_state, get_block, get_contract, get_token, get_transaction, get_wallet from app.codes.kycwallet import add_wallet, generate_wallet_address, get_address_from_public_key, get_digest, generate_wallet from app.codes.tokenmanager import create_token_transaction from app.codes.transfermanager import Transfermanager @@ -27,27 +27,63 @@ router = APIRouter() -v2_tag = 'V2 For Machines' +v2_tag = 'Blockchain' +legacy = 'Legacy' +system = "System" -@router.post("/run-updater", tags=[v2_tag], response_class=HTMLResponse) -def run_updater(): - try: - log = updater.run_updater() - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail=str(e)) - HTMLResponse(content=log, status_code=200) - return log - +@router.get("/get-block", tags=[v2_tag]) +def get_block_api(block_index: str): + """Get a block from the chain""" + block = get_block(block_index) + if block is None: + raise HTTPException(status_code=400, detail="Block not found") + return block @router.get("/get-transaction", tags=[v2_tag]) def get_transaction_api(transaction_code: str): - try: - return get_transaction(transaction_code) - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail=str(e)) + """Get a transaction from the chain""" + transaction = get_transaction(transaction_code) + if transaction is None: + raise HTTPException(status_code=400, detail="Transaction not found") + return transaction + +@router.get("/get-wallet", tags=[v2_tag]) +def get_wallet_api(wallet_address: str): + """Get a wallet details from the chain""" + wallet = get_wallet(wallet_address) + if wallet is None: + raise HTTPException(status_code=400, detail="Wallet not found") + return wallet + +@router.get("/get-token", tags=[v2_tag]) +def get_token_api(token_code: str): + """Get a token details from the chain""" + wallet = get_token(token_code) + if wallet is None: + raise HTTPException(status_code=400, detail="Token not found") + return wallet + +@router.get("/get-balances", tags=[v2_tag]) +def get_balances_api(balance_type: BalanceType, token_code: str = "", wallet_address: str = ""): + chain_scanner = Chainscanner() + if balance_type == BalanceType.TOKEN_IN_WALLET: + balance = chain_scanner.getbaladdtoken( + wallet_address, str(token_code)) + elif balance_type == BalanceType.ALL_TOKENS_IN_WALLET: + balance = chain_scanner.getbalancesbyaddress(wallet_address) + elif balance_type == BalanceType.ALL_WALLETS_FOR_TOKEN: + balance = chain_scanner.getbalancesbytoken(str(token_code)) + return {'balance': balance} + + +@router.get("/get-contract", tags=[v2_tag]) +def get_contract_api(contract_address: str): + """Get a contract details from the chain""" + contract = get_contract(contract_address) + if contract is None: + raise HTTPException(status_code=400, detail="Contract not found") + return contract @router.get("/download-chain", tags=[v2_tag]) def download_chain_api(): @@ -59,7 +95,7 @@ def download_state_api(): return download_state() -@router.post("/get-balance", tags=[v2_tag]) +@router.post("/get-balance", tags=[legacy]) def get_balance(req: BalanceRequest): chain_scanner = Chainscanner() if req.balance_type == BalanceType.TOKEN_IN_WALLET: @@ -118,7 +154,7 @@ def add_token( "custodian": request.custodian, "legaldochash": request.legal_doc, "amount_created": request.amount_created, - "value_created": request.value_created, + "tokendecimal": request.tokendecimal, "disallowed": request.disallowed_regions, "sc_flag": request.is_smart_contract_token } @@ -150,7 +186,7 @@ def add_transfer(transfer_request: TransferRequest): "timestamp": "", "trans_code": "000000", "type": type, - "currency": "INR", + "currency": "NWRL", "fee": 0.0, "descr": transfer_request.description, "valid": 1, @@ -203,7 +239,7 @@ def add_sc(sc_request: CreateSCRequest): "timestamp": "", "trans_code": "000000", "type": 3, - "currency": "INR", + "currency": "NWRL", "fee": 0.0, "descr": "", "valid": 1, @@ -234,7 +270,7 @@ def call_sc(sc_request: CallSC): "timestamp": "", "trans_code": "000000", "type": 3, - "currency": "INR", + "currency": "NWRL", "fee": 0.0, "descr": "", "valid": 1, @@ -264,7 +300,7 @@ def update_ts(ts_request: TscoreRequest): "timestamp": "", "trans_code": "000000", "type": 6, - "currency": "INR", + "currency": "NWRL", "fee": 0.0, "descr": "", "valid": 1, @@ -287,13 +323,38 @@ def sign_transaction(wallet_data: dict, transaction_data: dict): singed_transaction_file = signmanager.sign_transaction(wallet_data, transaction_data) return singed_transaction_file +@router.post("/submit-transaction", tags=[v2_tag]) +def submit_transaction(transaction_data: dict): + """Submit a signed transaction and adds it to the chain""" + try: + print('Received transaction: ', transaction_data) + response = validator.validate(transaction_data, propagate=True, validate_economics=True) + except Exception as e: + logger.exception(e) + raise HTTPException(status_code=500, detail=str(e)) + return {"status": "SUCCESS", "response": response} + @router.post("/validate-transaction", tags=[v2_tag]) def validate_transaction(transaction_data: dict): - """Validate a given transaction file if it's included in chain""" + """Validate a signed transaction and adds it to the chain""" try: print('Received transaction: ', transaction_data) - response = validator.validate(transaction_data) + response = validator.validate(transaction_data, propagate=True, validate_economics=True) except Exception as e: logger.exception(e) raise HTTPException(status_code=500, detail=str(e)) return {"status": "SUCCESS", "response": response} + + + +@router.post("/run-updater", tags=[system]) +def run_updater(add_to_chain_before_consensus: bool = False): + try: + # log = updater.run_updater() + updater.mine(add_to_chain_before_consensus) + return {'status': 'SUCCESS'} + except Exception as e: + logger.exception(e) + raise HTTPException(status_code=500, detail=str(e)) + # HTMLResponse(content=log, status_code=200) + # return log \ No newline at end of file diff --git a/app/routers/p2p.py b/app/routers/p2p.py index 93fc103..e6a6897 100644 --- a/app/routers/p2p.py +++ b/app/routers/p2p.py @@ -1,23 +1,27 @@ +from random import randint import sys +import threading import uvicorn from fastapi import APIRouter from fastapi.exceptions import HTTPException from starlette.requests import Request from app.codes.chainscanner import download_chain, download_state, get_transaction +from app.codes.clock.global_time import get_time_stats from app.codes.p2p.peers import add_peer, clear_peers, get_peers, update_software -from app.codes.p2p.sync_chain import get_blocks, get_last_block_index, receive_block, receive_receipt, sync_chain_from_node, sync_chain_from_peers +from app.codes.p2p.sync_chain import get_blocks, get_last_block_index, receive_block, receive_receipt, sync_chain_from_peers from app.codes.p2p.sync_mempool import get_mempool_transactions, list_mempool_transactions, sync_mempool_transactions from app.constants import NEWRL_PORT from app.migrations.init_db import clear_db, init_db, revert_chain from app.codes.p2p.peers import call_api_on_peers -from .request_models import BlockAdditionRequest, BlockRequest, ReceiptAdditionRequest, TransactionsRequest -from app.codes.auth.auth import get_node_wallet_address - +from .request_models import BlockAdditionRequest, BlockRequest, ReceiptAdditionRequest, TransactionAdditionRequest, TransactionsRequest +from app.codes.auth.auth import get_node_wallet_address, get_node_wallet_public +from app.codes.validator import validate as validate_transaction +from app.codes.minermanager import get_miner_info router = APIRouter() -p2p_tag = 'p2p' +p2p_tag = 'P2P' @router.get("/get-node-wallet-address", tags=[p2p_tag]) def api_get_node_wallet_address(): @@ -36,12 +40,16 @@ def get_mempool_transactions_api(req: TransactionsRequest): def get_mempool_transactions_api(req: BlockRequest): return get_blocks(req.block_indexes) +@router.post("/receive-transaction", tags=[p2p_tag]) +async def receive_transaction_api(req: TransactionAdditionRequest): + return validate_transaction(req.signed_transaction, propagate=True) + @router.post("/receive-block", tags=[p2p_tag]) -def receive_block_api(req: BlockAdditionRequest): +async def receive_block_api(req: BlockAdditionRequest): return receive_block(req.block) @router.post("/receive-receipt", tags=[p2p_tag]) -def receive_receipt_api(req: ReceiptAdditionRequest): +async def receive_receipt_api(req: ReceiptAdditionRequest): if receive_receipt(req.receipt): return {'status': 'SUCCESS'} else: @@ -51,73 +59,17 @@ def receive_receipt_api(req: ReceiptAdditionRequest): def get_last_block_index_api(): return get_last_block_index() -@router.post("/sync-mempool-transactions", tags=[p2p_tag]) -def sync_mempool_transactions_api(): - return sync_mempool_transactions() - -@router.post("/sync-chain-from-node", tags=[p2p_tag]) -def sync_chain_from_node_api(url: str = 'https://newrl-devnet1.herokuapp.com'): - try: - return sync_chain_from_node(url) - except Exception as e: - raise HTTPException(status_code=500, detail='No more blocks') - -@router.post("/sync-chain-from-peers", tags=[p2p_tag]) -def sync_chain_from_peers_api(): - return sync_chain_from_peers() - -@router.post("/sync-chain-from-peers", tags=[p2p_tag]) -def sync_chain_from_peers_api(): - return sync_chain_from_peers() - -@router.get("/get-transaction", tags=[p2p_tag]) -def get_transaction_api(transaction_code: str): - return get_transaction(transaction_code) - -@router.get("/download-chain", tags=[p2p_tag]) -def download_chain_api(): - return download_chain() - -@router.get("/download-chain", tags=[p2p_tag]) -def download_chain_api(): - return download_chain() - -@router.get("/download-state", tags=[p2p_tag]) -def download_state_api(): - return download_state() - -@router.post("/clear-db-test-only", tags=[p2p_tag]) -def clear_db_api(): - """ For testing only. To be removed. Clear and initialise a fresh db """ - clear_db() - init_db() - return True - @router.get("/get-peers", tags=[p2p_tag]) def get_peers_api(): return get_peers() -@router.post("/add-peer", tags=[p2p_tag]) -def add_peer_api(req: Request): - return add_peer(req.client.host) - -@router.post("/clear-peers", tags=[p2p_tag]) -def clear_peer_api(req: Request): - return clear_peers() +@router.get("/get-miners", tags=[p2p_tag]) +def get_miners_api(): + return get_miner_info() -@router.post("/initiate-peer-connection", tags=[p2p_tag]) -def initiate_peer_api(address: str): - "Test only, used to first connect a client" - return add_peer(address) - -@router.post("/revert-chain", tags=[p2p_tag]) -def revert_chain_api(block_index: int, propogate: bool = False): - revert_chain(block_index) - if propogate: - call_api_on_peers(f'/revert-chain?block_index={block_index}') - return {'status': 'SUCCESS'} - -@router.post("/update-software", tags=[p2p_tag]) -def update_software_api(propogate: bool = False): - update_software(propogate) - return {'status': 'SUCCESS'} +@router.post("/add-peer", tags=[p2p_tag]) +def add_peer_api(req: Request, dns_address: str=None): + if dns_address is None: + return add_peer(req.client.host) + else: + return add_peer(dns_address) diff --git a/app/routers/request_models.py b/app/routers/request_models.py index 5990b26..55e5617 100644 --- a/app/routers/request_models.py +++ b/app/routers/request_models.py @@ -47,7 +47,7 @@ class CreateTokenRequest(BaseModel): custodian: str = '0x762485963e99f6a6548729f11d610dd37ffd3b73' legal_doc: str = '686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401' amount_created: int = 1000000 - value_created: int = 10000 + tokendecimal: int = 0 disallowed_regions: Optional[List[str]] = [] is_smart_contract_token: bool = False token_attributes: dict @@ -107,3 +107,7 @@ class BlockAdditionRequest(BaseModel): class ReceiptAdditionRequest(BaseModel): receipt: dict + + +class TransactionAdditionRequest(BaseModel): + signed_transaction: dict diff --git a/app/routers/system.py b/app/routers/system.py new file mode 100644 index 0000000..ccfa7d0 --- /dev/null +++ b/app/routers/system.py @@ -0,0 +1,161 @@ +from random import randint +import sys +import threading +import uvicorn +from fastapi import APIRouter +from fastapi.exceptions import HTTPException +from starlette.requests import Request +from app.codes.crypto import calculate_hash +from app.codes.log_config import get_past_log_content, logGenerator +from sse_starlette.sse import EventSourceResponse +from fastapi.responses import PlainTextResponse + +from app.codes.chainscanner import download_chain, download_state +from app.codes.clock.global_time import get_time_stats +from app.codes.fs.mempool_manager import clear_mempool +from app.codes.p2p.peers import add_peer, clear_peers, get_peers, remove_dead_peers, update_software +from app.codes.p2p.sync_chain import get_blocks, get_last_block_index, sync_chain_from_node, sync_chain_from_peers +from app.codes.p2p.sync_mempool import list_mempool_transactions, sync_mempool_transactions +from app.codes.updater import TIMERS, get_timers +from app.codes.utils import get_last_block_hash +from app.constants import SOFTWARE_VERSION +from app.migrations.init_db import clear_db, init_db, revert_chain +from app.codes.p2p.peers import call_api_on_peers +from app.codes.auth.auth import get_node_wallet_public +from app.codes.minermanager import add_miners_as_peers, broadcast_miner_update, get_miner_info + +router = APIRouter() + +p2p_tag = 'System' + + +@router.get("/get-node-info", tags=[p2p_tag]) +def get_node_info(): + last_block = get_last_block_hash() + if last_block is None: + last_block_index = 0 + else: + last_block_index = last_block['index'] + node_info = { + 'software_version': SOFTWARE_VERSION, + 'wallet': get_node_wallet_public(), + 'time': get_time_stats(), + 'last_block': last_block, + 'timers': get_timers(), + 'miners': get_miner_info(), + 'peers': get_peers(), + 'recent_blocks': get_blocks(list(range(last_block_index - 5, last_block_index))), + 'mempool_transactions': list_mempool_transactions()[-10:], + } + return node_info + + +@router.get("/download-chain", tags=[p2p_tag]) +def download_chain_api(): + return download_chain() + + +@router.get("/download-state", tags=[p2p_tag]) +def download_state_api(): + return download_state() + + +@router.post("/sync-mempool-transactions", tags=[p2p_tag]) +def sync_mempool_transactions_api(): + return sync_mempool_transactions() + + +@router.post("/sync-chain-from-node", tags=[p2p_tag]) +def sync_chain_from_node_api(url: str = 'https://newrl-devnet1.herokuapp.com'): + try: + return sync_chain_from_node(url) + except Exception as e: + raise HTTPException(status_code=500, detail='No more blocks') + + +@router.post("/sync-chain-from-peers", tags=[p2p_tag]) +async def sync_chain_from_peers_api(): + return sync_chain_from_peers(force_sync=True) + + +@router.post("/initiate-peer-connection", tags=[p2p_tag]) +def initiate_peer_api(address: str): + "Test only, used to first connect a client" + return add_peer(address) + + +@router.post("/update-software", tags=[p2p_tag]) +def update_software_api(propogate: bool = False): + # update_software(propogate) + timer = threading.Timer(randint(30, 60), update_software, [propogate]) + timer.start() + return {'status': 'SUCCESS'} + + +@router.get('/stream-logs',tags=[p2p_tag]) +async def run(request: Request): + event_generator = logGenerator(request) + return EventSourceResponse(event_generator) + + +@router.get('/get-old-logs',tags=[p2p_tag], response_class=PlainTextResponse) +async def get_old_logs_api(): + return get_past_log_content() + + +@router.get("/get-status", tags=[p2p_tag]) +def get_status_api(): + return { + 'up': True, + 'timers': get_timers(), + } + +@router.post("/remove-dead-peers", tags=[p2p_tag]) +def get_status_api(): + add_miners_as_peers() + remove_dead_peers() + return {'status': 'SUCCESS'} + + +@router.post("/clear-mempool", tags=[p2p_tag]) +def clear_mempool_api(req: Request): + return clear_mempool() + + +@router.post("/broadcast-miner-update", tags=[p2p_tag]) +def broadcast_miner_update_api(): + return broadcast_miner_update() + + +# @router.post("/clear-db-test-only", tags=[p2p_tag]) +# def clear_db_api(): +# """ For testing only. To be removed. Clear and initialise a fresh db """ +# clear_db() +# init_db() +# return True + + +# @router.post("/clear-peers", tags=[p2p_tag]) +# def clear_peer_api(req: Request): +# return clear_peers() + + +# @router.post("/turn-off-mining-clock", tags=[p2p_tag]) +# def switch_mining_clock_api(): +# global TIMERS + +# if TIMERS['mining_timer'] is not None: +# print('Turning off mining clock') +# TIMERS['mining_timer'].cancel() +# return {'status': 'SUCCESS'} +# return {'status': 'FAILURE', 'message': 'Mining clock not running'} + + +@router.post("/revert-chain", tags=[p2p_tag]) +def revert_chain_api(api_key, block_index: int, propogate: bool = False): + if calculate_hash(api_key) != '7b345f5ad85b955ab9ad62885283c4420960359bf3faa3a805bf4c7586f80d23': + return {'status': 'INVALID_KEY'} + revert_chain(block_index) + if propogate: + call_api_on_peers(f'/revert-chain?block_index={block_index}') + return {'status': 'SUCCESS'} diff --git a/app/routers/transport.py b/app/routers/transport.py index 0755819..4dba690 100644 --- a/app/routers/transport.py +++ b/app/routers/transport.py @@ -3,7 +3,8 @@ router = APIRouter() -transport_tag = 'transport' +transport_tag = 'Transport' + @router.post("/receive", tags=[transport_tag]) def recieve_api(payload: dict): diff --git a/app/tests/conftest.py b/app/tests/conftest.py index 4bbd771..afc9671 100644 --- a/app/tests/conftest.py +++ b/app/tests/conftest.py @@ -1,6 +1,11 @@ import os import shutil + +from ..migrations.init import init_newrl +from ..codes.p2p.peers import init_peer_db +from ..migrations.migrate_db import run_migrations from ..migrations.init_db import init_peer_db + import pytest @@ -17,7 +22,9 @@ def setup_test_files(): if os.path.exists('data_test/.auth.json'): os.remove('data_test/.auth.json') shutil.copyfile('data_test/template/.auth.json', 'data_test/.auth.json') + init_newrl() init_peer_db() + run_migrations() os.environ['NEWRL_TEST'] = '1' diff --git a/app/tests/test_blockchain.py b/app/tests/test_blockchain.py new file mode 100644 index 0000000..06a5fec --- /dev/null +++ b/app/tests/test_blockchain.py @@ -0,0 +1,33 @@ +import time +import sqlite3 + +from app.codes.blockchain import block_exists + +from ..codes import updater +from ..codes.auth.auth import get_wallet +from ..codes.fs.mempool_manager import get_mempool_transaction +from ..codes.db_updater import update_wallet_token_balance +from fastapi.testclient import TestClient + +from ..codes.fs.temp_manager import get_blocks_for_index_from_storage +from ..ntypes import NUSD_TOKEN_CODE +from ..constants import NEWRL_DB +from ..nvalues import TREASURY_WALLET_ADDRESS +from ..migrations.init import init_newrl + +from ..main import app + +client = TestClient(app) + +init_newrl() + + +def test_block_exists(): + response = client.get('/get-last-block-index') + assert response.status_code == 200 + previous_block_index = int(response.text) + + assert block_exists(previous_block_index) == True + assert block_exists(previous_block_index - 1) == True + assert block_exists(previous_block_index + 1) == False + diff --git a/app/tests/test_consensus.py b/app/tests/test_consensus.py index a9e01bd..9c5f9d4 100644 --- a/app/tests/test_consensus.py +++ b/app/tests/test_consensus.py @@ -1,5 +1,6 @@ from fastapi.testclient import TestClient +from ..codes.fs.temp_manager import get_blocks_for_index_from_storage from ..main import app from ..codes.validator import validate_block, validate_receipt_signature from ..codes.signmanager import sign_object @@ -23,7 +24,7 @@ def test_validate_block_receipt(): receipt = { "data": receipt_data, - "public": test_wallet["public"], + "public_key": test_wallet["public"], "signature": sign_object(test_wallet["private"], receipt_data) } @@ -48,7 +49,7 @@ def test_block_validation_success(): "timestamp": "2021-09-21 10:23:34.861279", "trans_code": "9258f94aa73746c9f39eefe192e6ac02d804cf1a", "type": 4, - "currency": "INR", + "currency": "NWRL", "fee": 0.0, "descr": "", "valid": 1, @@ -107,3 +108,83 @@ def test_block_validation_success(): } # assert validate_block(block) is True + + +def test_block_receive_without_signatures(): + response = client.get('/get-last-block-index') + assert response.status_code == 200 + + previous_block_index = int(response.text) + block_index = previous_block_index + 1 + + receipt_data = { + "block_index": block_index, + "block_hash": "0000fd83acfc2f42f07493b8711d4f7fffa75333e3eece24c0d3b55c4df7b7e2", + "vote": 1 + } + + receipt = { + "data": receipt_data, + "public_key": test_wallet["public"], + "signature": sign_object(test_wallet["private"], receipt_data) + } + + block_payload = { + 'index': block_index, + 'hash': 'dd', + "receipts": [ + receipt + ], + 'data': { + "creator_wallet": "0x20513a419d5b11cd510ae518dc04ac1690afbed6", + "index": block_index, + "previous_hash": "00007736868ba1a325c3ad8eba9bc02bba06fc315b0018f193d494dba67de542", + "proof": 21054, + "text": { + "signatures": [ + [ + { + "msgsign": "C680g6RST4PtC7OoIKjTJSm+5mfVSCwM73SxEIOWfyYBzWpUA0as1qpRaDxKdfzx8xCuQn7mrOdyemT7M/e9PA==", + "wallet_address": "0xc29193dbab0fe018d878e258c93064f01210ec1a" + } + ] + ], + "transactions": [ + { + "currency": "NUSD", + "descr": "New wallet", + "fee": 2, + "specific_data": { + "custodian_wallet": "0xc29193dbab0fe018d878e258c93064f01210ec1a", + "jurisd": "910", + "kyc_docs": [ + { + "hash": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401", + "type": 1 + } + ], + "ownertype": "1", + "specific_data": {}, + "wallet_address": "0xa64c2d51965e1bc9a7925c9b4e0e817d7b6bfd62", + "wallet_public": "T3v/sATHQtKY/kRnEU2ruKVekpAnAF/hCB4LqesSelQUCS13mXBvvri6WD58Q1jxeGbQc0pjl68JjIr86AHGiQ==" + }, + "timestamp": 1647608631118, + "trans_code": "ebc2838c215c92d874dcb387b2a9ca6d85e3daf7", + "type": 1, + "valid": 1 + } + ] + }, + "timestamp": 1647608631136 + } + } + + client.post('/receive-block', json={'block': block_payload}) + + response = client.post('/get-blocks', json={'block_indexes': [block_index]}) + blocks = response.json() + assert len(blocks) == 1 + block = blocks[0] + + blocks_from_storage = get_blocks_for_index_from_storage(block['block_index']) + assert len(blocks_from_storage) > 0 diff --git a/app/tests/test_db_updater.py b/app/tests/test_db_updater.py new file mode 100644 index 0000000..abdb09c --- /dev/null +++ b/app/tests/test_db_updater.py @@ -0,0 +1,28 @@ +import time +from app.codes.blockchain import get_last_block +from app.codes.chainscanner import get_transaction +from app.codes.p2p.sync_chain import receive_block + +from app.tests.test_mempool import create_transaction + +from .test_p2p import _receive_block +from .test_miner_committee import _add_test_miner +from ..codes.updater import mine, run_updater, start_mining_clock +from ..codes.minermanager import broadcast_miner_update +from fastapi.testclient import TestClient +from ..constants import BLOCK_TIME_INTERVAL_SECONDS + +from ..main import app +from app.codes import updater + +client = TestClient(app) + + +def test_mine(): + transaction = create_transaction(0) + transaction_in_db = get_transaction(transaction['transaction']['trans_code']) + assert transaction_in_db is None + block = mine(add_to_chain=True) + transaction_in_db = get_transaction(transaction['transaction']['trans_code']) + assert transaction_in_db is not None + \ No newline at end of file diff --git a/app/tests/test_fee_rewards.py b/app/tests/test_fee_rewards.py index 8d47e59..b178e81 100644 --- a/app/tests/test_fee_rewards.py +++ b/app/tests/test_fee_rewards.py @@ -1,5 +1,7 @@ import time import sqlite3 + +from app.codes import updater from ..codes.db_updater import update_wallet_token_balance from fastapi.testclient import TestClient from ..ntypes import NUSD_TOKEN_CODE @@ -32,21 +34,21 @@ def test_mining_reward(): wallet_address = wallet['wallet_address'] assert wallet_address - check_newrl_wallet_balance(wallet_address, None) + check_newrl_wallet_balance(wallet_address, 1500001000.0) - response = client.post('/run-updater') + updater.mine(True) assert response.status_code == 200 - check_newrl_wallet_balance(wallet_address, 1000) + check_newrl_wallet_balance(wallet_address, 1500002000.0) time.sleep(2) - response = client.post('/run-updater') + updater.mine(True) assert response.status_code == 200 - check_newrl_wallet_balance(wallet_address, 1000) + check_newrl_wallet_balance(wallet_address, 1500002000.0) time.sleep(5) - response = client.post('/run-updater') + updater.mine(True) assert response.status_code == 200 - check_newrl_wallet_balance(wallet_address, 2000) + check_newrl_wallet_balance(wallet_address, 1500002000.0) def test_transaction_fee_payment(): @@ -110,7 +112,7 @@ def create_wallet_with_fee(fee): response = client.post('/validate-transaction', json=signed_transaction) assert response.status_code == 200 - response = client.post('/run-updater', json=signed_transaction) + updater.mine(True) assert response.status_code == 200 diff --git a/app/tests/test_main.py b/app/tests/test_main.py index 587df44..127a5be 100644 --- a/app/tests/test_main.py +++ b/app/tests/test_main.py @@ -2,6 +2,8 @@ from random import random import token from fastapi.testclient import TestClient + +from ..codes import updater from ..migrations.init import init_newrl import random @@ -60,8 +62,7 @@ def create_wallet(): response = client.post('/validate-transaction', json=signed_transaction) assert response.status_code == 200 - response = client.post('/run-updater', json=signed_transaction) - assert response.status_code == 200 + updater.mine(True) response = client.get('/download-state') assert response.status_code == 200 @@ -83,7 +84,7 @@ def create_token(wallet, custodian_wallet): "custodian": custodian_wallet['address'], "legal_doc": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401", "amount_created": 8888, - "value_created": 1000, + "tokendecimal": 0, "disallowed_regions": [], "is_smart_contract_token": False, "token_attributes": {} @@ -96,7 +97,7 @@ def create_token(wallet, custodian_wallet): "custodian": custodian_wallet['address'], "legal_doc": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401", "amount_created": 8888, - "value_created": 1000, + "tokendecimal": 0, "disallowed_regions": [], "is_smart_contract_token": False, "token_attributes": {}} @@ -125,8 +126,7 @@ def create_token(wallet, custodian_wallet): assert response.status_code == 200 print("running updater") - response = client.post('/run-updater', json=signed_transaction) - assert response.status_code == 200 + updater.mine(True) response = client.get('/download-state') assert response.status_code == 200 @@ -188,8 +188,7 @@ def create_transfer(wallet1, wallet2, token1, token2): response = client.post('/validate-transaction', json=signed_transaction) assert response.status_code == 200 - response = client.post('/run-updater') - assert response.status_code == 200 + updater.mine(True) response = client.post('/get-balance', json={ @@ -252,8 +251,7 @@ def add_trust_score(wallet1, wallet2, tscore): response = client.post('/validate-transaction', json=signed_transaction) assert response.status_code == 200 - response = client.post('/run-updater') - assert response.status_code == 200 + updater.mine(True) def get_token_from_tx(txcode): response = client.get('/download-state') @@ -305,8 +303,7 @@ def create_contract(wallet1, tokencode, tokenname): response = client.post('/validate-transaction', json=signed_transaction) assert response.status_code == 200 - response = client.post('/run-updater') - assert response.status_code == 200 + updater.mine(True) return address @@ -336,8 +333,7 @@ def call_contract(contractaddress, funct, wallet1, params): response = client.post('/validate-transaction', json=signed_transaction) assert response.status_code == 200 - response = client.post('/run-updater') - assert response.status_code == 200 + updater.mine(True) return tcode # return signed_transaction['transaction']['trans_code'] diff --git a/app/tests/test_mempool.py b/app/tests/test_mempool.py index 739eda4..c61736e 100644 --- a/app/tests/test_mempool.py +++ b/app/tests/test_mempool.py @@ -1,9 +1,13 @@ import time import sqlite3 -from ..codes.fs.mempool_manager import get_mempool_transaction +from ..codes import updater +from ..codes.auth.auth import get_wallet +from ..codes.fs.mempool_manager import get_mempool_transaction, remove_transaction_from_mempool from ..codes.db_updater import update_wallet_token_balance from fastapi.testclient import TestClient + +from ..codes.fs.temp_manager import get_blocks_for_index_from_storage from ..ntypes import NUSD_TOKEN_CODE from ..constants import NEWRL_DB from ..nvalues import TREASURY_WALLET_ADDRESS @@ -77,3 +81,27 @@ def create_transaction(fee): assert response.status_code == 200 return signed_transaction + + +def test_block_receipt_getting_stored(): + time.sleep(10) + block = updater.mine(True)['data'] + print('blk', block) + blocks_from_storage = get_blocks_for_index_from_storage(block['index']) + assert len(blocks_from_storage) != 0 + + block_from_storage = blocks_from_storage[0] + assert len(block_from_storage['receipts']) == 1 + receipt = block_from_storage['receipts'][0] + + assert receipt['data']['block_index'] == block['index'] + assert receipt['public_key'] == get_wallet()['public'] + +def test_transaction_remove(): + transaction = create_transaction(2) + mempool_transaction = get_mempool_transaction(transaction['transaction']['trans_code']) + assert mempool_transaction is not None + remove_transaction_from_mempool(transaction['transaction']['trans_code']) + mempool_transaction = get_mempool_transaction(transaction['transaction']['trans_code']) + assert mempool_transaction is None + \ No newline at end of file diff --git a/app/tests/test_miner_committee.py b/app/tests/test_miner_committee.py new file mode 100644 index 0000000..9d53aa4 --- /dev/null +++ b/app/tests/test_miner_committee.py @@ -0,0 +1,100 @@ +import time +import sqlite3 + +from app.codes import updater, validator +from app.codes.chainscanner import get_transaction +from app.codes.p2p.sync_chain import receive_block + +from ..codes.blockchain import get_last_block +from ..codes.utils import get_time_ms +from ..codes.auth.auth import get_wallet +from ..codes.minermanager import broadcast_miner_update, get_committee_for_current_block, get_eligible_miners, get_miner_for_current_block, get_my_miner_status, miner_addition_transaction +from ..codes.db_updater import add_miner +from fastapi.testclient import TestClient +from ..constants import NEWRL_DB, COMMITTEE_SIZE + +from ..main import app + +client = TestClient(app) + + +def test_mining_reward(): + # assert None == get_my_miner_status() + + broadcast_miner_update() + updater.mine(True) + + miner_info = get_my_miner_status() + assert miner_info['wallet_address'] == get_wallet()['address'] + + +def test_miner_selection(): + for i in range(0,20): + _add_test_miner(i) + + last_block1 = get_last_block() + committee = get_committee_for_current_block() + assert len(committee) == COMMITTEE_SIZE + + miner1 = get_miner_for_current_block() + # Check if pseudo-random with block index as seed returns the same miner + assert miner1['wallet_address'] == get_miner_for_current_block()['wallet_address'] + # my_wallet = get_wallet() + # assert miner1['wallet_address'] == my_wallet['address'] + + time.sleep(5) + updater.mine(True) + + last_block2 = get_last_block() + + # assert last_block2['index'] == last_block1['index'] + 1 + # miner2 = get_miner_for_current_block() + + # Hoping the miner changes for the new block. Totally random though + # assert miner1['wallet_address'] != miner2['wallet_address'] + + +def _add_test_miner(i): + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() + add_miner(cur, f'0x0000{i}', '127.0.0.1', get_time_ms()) + con.commit() + con.close() + +def clear_miner_db(): + con = sqlite3.connect(NEWRL_DB) + cur = con.cursor() + cur.execute('delete from miners') + con.commit() + con.close() + + +def test_miner_addition_update(): + transaction = miner_addition_transaction() + response = client.post('/validate-transaction', json=transaction) + assert response.status_code == 200 + block = updater.run_updater() + assert block is not None + receive_block(block) + transaction_in_chain = get_transaction(transaction['transaction']['trans_code']) + assert transaction_in_chain is not None + + miners = get_eligible_miners() + assert len(miners) == 1 + old_miner = miners[0] + time.sleep(5) + transaction = miner_addition_transaction(my_address='123.123.123.123') + response = client.post('/validate-transaction', json=transaction) + assert response.status_code == 200 + block = updater.run_updater() + assert block is not None + receive_block(block) + transaction_in_chain = get_transaction(transaction['transaction']['trans_code']) + assert transaction_in_chain is not None + + miners = get_eligible_miners() + assert len(miners) == 1 + new_miner = miners[0] + + assert old_miner['last_broadcast_timestamp'] != new_miner['last_broadcast_timestamp'] + assert old_miner['network_address'] != new_miner['network_address'] \ No newline at end of file diff --git a/app/tests/test_outgoing.py b/app/tests/test_outgoing.py new file mode 100644 index 0000000..39aaf1f --- /dev/null +++ b/app/tests/test_outgoing.py @@ -0,0 +1,38 @@ +import unittest +from fastapi.testclient import TestClient + +from app.codes.p2p.outgoing import get_excluded_node_list, get_excluded_peers_to_broadcast + +from ..main import app + +client = TestClient(app) + + +class OutgoingTest(unittest.TestCase): + peers = [ + {'id': '11.12.13.14', 'address': '11.12.13.14'}, + {'id': '11.12.13.15', 'address': '11.12.13.15'}, + {'id': '11.12.13.16', 'address': '11.12.13.16'}, + {'id': '11.12.13.17', 'address': '11.12.13.17'}, + {'id': '11.12.13.18', 'address': '11.12.13.18'}, + ] + + def test_get_excluded_peers_to_broadcast(self): + exclude_nodes = ['11.12.13.16', '11.12.13.17'] + + peers = get_excluded_peers_to_broadcast(self.peers, exclude_nodes) + + expected_peers = [ + {'id': '11.12.13.14', 'address': '11.12.13.14'}, + {'id': '11.12.13.15', 'address': '11.12.13.15'}, + {'id': '11.12.13.18', 'address': '11.12.13.18'}, + ] + self.assertListEqual(peers, expected_peers) + + def test_get_excluded_node_list(self): + already_broadcasted_nodes = ['11.12.13.11', '11.12.13.19'] + peer_addresses = list(map(lambda p: p['address'], self.peers)) + combined_exclusion_list = get_excluded_node_list(self.peers, already_broadcasted_nodes) + expected_combined_exclusion_list = peer_addresses + already_broadcasted_nodes + self.assertEqual(len(combined_exclusion_list), len(expected_combined_exclusion_list)) + self.assertSetEqual(set(combined_exclusion_list), set(expected_combined_exclusion_list)) diff --git a/app/tests/test_p2p.py b/app/tests/test_p2p.py index 073e302..feac4cc 100644 --- a/app/tests/test_p2p.py +++ b/app/tests/test_p2p.py @@ -1,5 +1,12 @@ from fastapi.testclient import TestClient +import pytest + +from app.codes.crypto import sign_object +from .test_miner_committee import _add_test_miner, clear_miner_db +from ..codes import updater +from ..codes.auth.auth import get_wallet from ..migrations.init import init_newrl +from ..codes.minermanager import broadcast_miner_update from ..main import app @@ -7,21 +14,48 @@ init_newrl() +test_wallet = { + "public": "wTxCEIm7oaKrYmmWIaeEcd4B49DsHb+D4VilmzhQZJCEmhT1XMFa/WmWoyBK3SRDuNGc9iOYdRBBCfeE0esH6A==", + "private": "erMsIsopb9N6MYnDxvWtC+iaNb4PmQTY72D3jM5+lFE=", + "address": "0x08a04d6f6a90248df7c392083c8eb52bba929597" +} + + def _receive_block(block_index): + response = client.post('/get-blocks', json={'block_indexes': [block_index]}) + assert response.status_code == 200 + blocks = response.json() + assert len(blocks) == 0 + + receipt_data = { + "block_index": block_index, + "block_hash": "0000fd83acfc2f42f07493b8711d4f7fffa75333e3eece24c0d3b55c4df7b7e2", + "vote": 1 + } + + receipt = { + "data": receipt_data, + "public_key": test_wallet["public"], + "signature": sign_object(test_wallet["private"], receipt_data) + } + block_payload = { - "block_index": block_index, + "index": block_index, "hash": "0000be5942ea740bdfc244ca59aee40029d32e1bbc32cd5dc6fa2cd4012ba38c", + "receipts": [ + receipt + ], "data": { "index": block_index, - "timestamp": "2022-02-17 13:29:00.746425", + "timestamp": "1645095917000", "proof": 27359, "text": { "transactions": [ { - "timestamp": "2022-02-17 13:27:25.023580", + "timestamp": "1645095917000", "trans_code": "a529a6c63c4b5480d88cd0b12e108e5340aaa25a", "type": 1, - "currency": "INR", + "currency": "NWRL", "fee": 0, "descr": "New wallet", "valid": 1, @@ -50,12 +84,10 @@ def _receive_block(block_index): ] ] }, - "previous_hash": "0000ae69c361c65f088b52af8f5372f94f5f62d84f0980ea4c2cd71551206024" + "previous_hash": "0000ae69c361c65f088b52af8f5372f94f5f62d84f0980ea4c2cd71551206024", + "receipts": [], + 'creator_wallet': get_wallet()['address'] }, - "signature": { - "public": "4trPBhDwdxWat2I8tE4Mj+7R6tiTJ+44GWtTdf5QpXnh/Ia1i5x4ETDufrCn3mjYN8gJs/w3iiMlDEmAAs7kvg==", - "msgsign": "8odtLy4zlyXNn7GFK4lpDtubGOS3bLFijmxXR1T8+TlLOl39+mA9Ajw8S4Sw3enJlGiWGorJr+0ULKdmeqf4Hw==" - } } response = client.post('/receive-block', json={'block': block_payload}) @@ -72,6 +104,11 @@ def _receive_block(block_index): def test_block_receive(): + clear_miner_db() + + broadcast_miner_update() + updater.mine(True) + response = client.get('/get-last-block-index') assert response.status_code == 200 @@ -82,4 +119,24 @@ def test_block_receive(): current_block_index = int(response.text) # Block index should've increased by 1 - assert current_block_index == (previous_block_index + 1) \ No newline at end of file + assert current_block_index == (previous_block_index + 1) + + +def test_block_reject(): + """Expect block rejection from unexpected minors""" + clear_miner_db() + + _add_test_miner(1) + + response = client.get('/get-last-block-index') + assert response.status_code == 200 + + previous_block_index = int(response.text) + with pytest.raises(Exception) as e_info: + _receive_block(previous_block_index + 1) + + response = client.get('/get-last-block-index') + current_block_index = int(response.text) + + # Block index should not increase + assert current_block_index == previous_block_index \ No newline at end of file diff --git a/app/tests/test_receiptmanager.py b/app/tests/test_receiptmanager.py new file mode 100644 index 0000000..23e9c4e --- /dev/null +++ b/app/tests/test_receiptmanager.py @@ -0,0 +1,38 @@ + +from fastapi.testclient import TestClient +from app.codes.blockchain import get_last_block_index + +from app.codes.consensus.consensus import generate_block_receipt +from app.codes.fs.temp_manager import store_receipt_to_temp +from app.codes.p2p.sync_chain import accept_block +from app.codes.receiptmanager import get_receipts_for_block_from_db, store_receipt_to_db +from app.codes.updater import run_updater + +from ..migrations.init import init_newrl + +from ..main import app + +client = TestClient(app) + +init_newrl() + + +def test_store_receipt_to_db(): + block_index = 345 + existing_receipts = get_receipts_for_block_from_db(block_index) + + receipt = generate_block_receipt({'index': block_index}) + store_receipt_to_db(receipt) + new_receipts = get_receipts_for_block_from_db(block_index) + assert len(existing_receipts) + 1 == len(new_receipts) + + +def test_block_receipt_addition(): + last_block_index = get_last_block_index() + receipt = generate_block_receipt({'index': last_block_index}) + store_receipt_to_temp(receipt) + block = run_updater() + assert block['receipts'][0] is not None + assert len(block['data']['text']['previous_block_receipts']) != 0 + + accept_block(block, block['hash']) diff --git a/app/tests/test_sync_chain.py b/app/tests/test_sync_chain.py new file mode 100644 index 0000000..f49fa75 --- /dev/null +++ b/app/tests/test_sync_chain.py @@ -0,0 +1,33 @@ +import time +from app.codes.blockchain import get_last_block +from app.codes.chainscanner import get_transaction +from app.codes.p2p.sync_chain import get_blocks, receive_block + +from app.tests.test_mempool import create_transaction + +from .test_p2p import _receive_block +from .test_miner_committee import _add_test_miner +from ..codes.updater import mine, run_updater, start_mining_clock +from ..codes.minermanager import broadcast_miner_update +from fastapi.testclient import TestClient +from ..constants import BLOCK_TIME_INTERVAL_SECONDS + +from ..main import app +from app.codes import updater + +client = TestClient(app) + + +def test_get_blocks(): + transaction = create_transaction(0) + block = mine(add_to_chain=True) + assert block is not None + index = block['index'] + transaction_in_db = get_transaction(transaction['transaction']['trans_code']) + assert transaction_in_db is not None + blocks_from_db = get_blocks([index]) + assert len(blocks_from_db) == 1 + block_from_db = blocks_from_db[0] + + transactions = block_from_db['text']['transactions'] + assert len(transactions) == 1 \ No newline at end of file diff --git a/app/tests/test_timers.py b/app/tests/test_timers.py new file mode 100644 index 0000000..d556147 --- /dev/null +++ b/app/tests/test_timers.py @@ -0,0 +1,51 @@ +import time + +from .test_p2p import _receive_block +from .test_miner_committee import _add_test_miner +from ..codes.updater import start_mining_clock +from ..codes.minermanager import broadcast_miner_update +from fastapi.testclient import TestClient +from ..constants import BLOCK_TIME_INTERVAL_SECONDS + +from ..main import app +from app.codes import updater + +client = TestClient(app) + + +def test_mining_clock(): + broadcast_miner_update() + updater.mine(True) + + response = client.get('/get-last-block-index') + assert response.status_code == 200 + previous_block_index = int(response.text) + + # start_mining_clock() + + # time.sleep(BLOCK_TIME_INTERVAL_SECONDS + 2) + + # response = client.get('/get-last-block-index') + # assert response.status_code == 200 + # block_index = int(response.text) + + # assert block_index == previous_block_index + 2 + + # for i in range(1, 3): + # _add_test_miner(i) + # time.sleep(BLOCK_TIME_INTERVAL_SECONDS * 4) + # os._exit(1) + + +# def test_all_timers(): +# broadcast_miner_update() +# updater.mine(True) + +# broadcast_miner_update() +# updater.mine(True) + +# response = client.get('/get-last-block-index') +# assert response.status_code == 200 + +# previous_block_index = int(response.text) +# _receive_block(previous_block_index + 1) \ No newline at end of file diff --git a/app/tests/test_transfer.py b/app/tests/test_transfer.py new file mode 100644 index 0000000..02cd0ef --- /dev/null +++ b/app/tests/test_transfer.py @@ -0,0 +1,252 @@ +from audioop import add +from random import random +import token +from fastapi.testclient import TestClient + +from ..codes import updater +from ..migrations.init import init_newrl +import random + +from ..main import app +from ..codes.contracts.nusd1 import nusd1 + +client = TestClient(app) + +init_newrl() + +_custodian_wallet = {"public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==","private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=","address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} + +def create_wallet(): + response = client.get("/generate-wallet-address") + assert response.status_code == 200 + wallet = response.json() + assert wallet['address'] + assert wallet['public'] + assert wallet['private'] + + response = client.post('/add-wallet', json={ + "custodian_address": _custodian_wallet['address'], + "ownertype": "1", + "jurisdiction": "910", + "kyc_docs": [ + { + "type": 1, + "hash": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401" + } + ], + "specific_data": {}, + "public_key": wallet['public'] + }) + + print(response.text) + assert response.status_code == 200 + unsigned_transaction = response.json() + assert unsigned_transaction['transaction'] + assert len(unsigned_transaction['signatures']) == 0 + + custodian_wallet = { + "address": _custodian_wallet['address'], + "public": "sB8/+o32Q7tRTjB2XcG65QS94XOj9nP+mI7S6RIHuXzKLRlbpnu95Zw0MxJ2VGacF4TY5rdrIB8VNweKzEqGzg==", + "private": "xXqOItcwz9JnjCt3WmQpOSnpCYLMcxTKOvBZyj9IDIY=" + } + + response = client.post('/sign-transaction', json={ + "wallet_data": _custodian_wallet, + "transaction_data": unsigned_transaction + }) + + assert response.status_code == 200 + signed_transaction = response.json() + assert signed_transaction['transaction'] + assert signed_transaction['signatures'] + assert len(signed_transaction['signatures']) == 1 + + response = client.post('/validate-transaction', json=signed_transaction) + assert response.status_code == 200 + + updater.mine(True) + + response = client.get('/download-state') + assert response.status_code == 200 + state = response.json() + + wallets = state['wallets'] + wallet_in_state = next( + x for x in wallets if x['wallet_address'] == wallet['address']) + assert wallet_in_state + return wallet + + +def create_token(wallet, custodian_wallet): + response = client.post('/add-token', json={ + "token_name": "TestTOKEN", + "token_code" : "", + "token_type": "string", + "first_owner": wallet['address'], + "custodian": custodian_wallet['address'], + "legal_doc": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401", + "amount_created": 8888, + "tokendecimal": 0, + "disallowed_regions": [], + "is_smart_contract_token": False, + "token_attributes": {} + }) + + postedval = {"token_name": "TestTOKEN", + "tokencode" : "", + "token_type": "string", + "first_owner": wallet['address'], + "custodian": custodian_wallet['address'], + "legal_doc": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401", + "amount_created": 8888, + "tokendecimal": 0, + "disallowed_regions": [], + "is_smart_contract_token": False, + "token_attributes": {}} + print(postedval) + + assert response.status_code == 200 + unsigned_transaction = response.json() + assert unsigned_transaction['transaction'] + assert len(unsigned_transaction['signatures']) == 0 + + response = client.post('/sign-transaction', json={ + "wallet_data": custodian_wallet, + "transaction_data": unsigned_transaction + }) + + print("adding token") + assert response.status_code == 200 + signed_transaction = response.json() + print("signing tx") + assert signed_transaction['transaction'] + assert signed_transaction['signatures'] + assert len(signed_transaction['signatures']) == 1 + + print("validating tx") + response = client.post('/validate-transaction', json=signed_transaction) + assert response.status_code == 200 + + print("running updater") + updater.mine(True) + + response = client.get('/download-state') + assert response.status_code == 200 + state = response.json() + + tokens = state['tokens'] + token_in_state = next( + x for x in tokens if x['parent_transaction_code'] == signed_transaction['transaction']['trans_code']) + assert token_in_state + print("token exists in state") + + balances = state['balances'] + balance = next(x for x in balances if x['wallet_address'] == + wallet['address'] and x['tokencode'] == token_in_state['tokencode']) + assert balance + print("token exists in balances") + assert balance['balance'] == 8888 + print("token balance correct, returning tokencode") + + return token_in_state['tokencode'] + + +def create_transfer(wallet1, wallet2, token1, token2): + response = client.post('/add-transfer', json={ + "transfer_type": 4, + "asset1_code": token1, + "asset2_code": token2, + "wallet1_address": wallet1['address'], + "wallet2_address": wallet2['address'], + "asset1_qty": 1000, + "asset2_qty": 2000 + }) + + assert response.status_code == 200 + unsigned_transaction = response.json() + assert unsigned_transaction['transaction'] + assert len(unsigned_transaction['signatures']) == 0 + + response = client.post('/sign-transaction', json={ + "wallet_data": wallet1, + "transaction_data": unsigned_transaction + }) + assert response.status_code == 200 + signed_transaction = response.json() + assert signed_transaction['transaction'] + assert signed_transaction['signatures'] + assert len(signed_transaction['signatures']) == 1 + + response = client.post('/sign-transaction', json={ + "wallet_data": wallet2, + "transaction_data": signed_transaction + }) + assert response.status_code == 200 + signed_transaction = response.json() + assert signed_transaction['transaction'] + assert signed_transaction['signatures'] + assert len(signed_transaction['signatures']) == 2 + + response = client.post('/validate-transaction', json=signed_transaction) + assert response.status_code == 200 + + updater.mine(True) + + + response = client.post('/get-balance', json={ + "balance_type": "TOKEN_IN_WALLET", + "token_code": token1, + "wallet_address": wallet1['address'] + }) + assert response.status_code == 200 + balance = response.json()['balance'] + assert balance == 7888 + + response = client.post('/get-balance', json={ + "balance_type": "TOKEN_IN_WALLET", + "token_code": token2, + "wallet_address": wallet1['address'] + }) + assert response.status_code == 200 + balance = response.json()['balance'] + assert balance == 2000 + + response = client.post('/get-balance', json={ + "balance_type": "TOKEN_IN_WALLET", + "token_code": token1, + "wallet_address": wallet2['address'] + }) + assert response.status_code == 200 + balance = response.json()['balance'] + assert balance == 1000 + + response = client.post('/get-balance', json={ + "balance_type": "TOKEN_IN_WALLET", + "token_code": token2, + "wallet_address": wallet2['address'] + }) + assert response.status_code == 200 + balance = response.json()['balance'] + assert balance == 6888 + + +def test_transfer(): + custodian_wallet = _custodian_wallet + + wallet1 = create_wallet() + wallet2 = create_wallet() + print("created wallets with addresses, ",wallet1['address']," and ",wallet2['address']) + + token1 = create_token(wallet1, custodian_wallet) + token2 = create_token(wallet2, custodian_wallet) + print("tokens created") + + create_transfer(wallet1, wallet2, token1, token2) + print("transfer done") + + response = client.get(f'/get-balances?balance_type=TOKEN_IN_WALLET&token_code={token1}&wallet_address={wallet1}') + assert response.status_code == 200 + balance = response.json()['balance'] + assert balance == 8888 + + diff --git a/app/tests/test_updater.py b/app/tests/test_updater.py new file mode 100644 index 0000000..366d34f --- /dev/null +++ b/app/tests/test_updater.py @@ -0,0 +1,51 @@ +import time +from app.codes.blockchain import get_last_block +from app.codes.consensus.consensus import generate_block_receipt +from app.codes.crypto import calculate_hash +from app.codes.p2p.sync_chain import receive_block + +from app.tests.test_mempool import create_transaction + +from .test_p2p import _receive_block +from .test_miner_committee import _add_test_miner +from ..codes.updater import create_empty_block_receipt_and_broadcast, mine, run_updater, start_mining_clock +from ..codes.minermanager import broadcast_miner_update +from fastapi.testclient import TestClient +from ..constants import BLOCK_TIME_INTERVAL_SECONDS + +from ..main import app +from app.codes import updater + +client = TestClient(app) + + +def test_mine(): + # _add_test_miner(1) + create_transaction(0) + block = mine(add_to_chain=True) + # start_mining_clock(int(get_last_block_hash()['timestamp'])) + + +def test_receive(): + create_transaction(0) + block = run_updater() + assert block is not None + receive_block(block) + + +def test_block_hash(): + create_transaction(0) + block = mine() + calculated_hash = calculate_hash(block['data']) + assert block['hash'] == calculated_hash + assert 'receipts' in block + assert len(block['receipts']) == 1 + assert block['receipts'][0]['data']['block_hash'] == calculated_hash + + receipt = generate_block_receipt(block['data']) + assert receipt['data']['block_hash'] == calculated_hash + + +def test_empty_block_mining(): + block_payload = create_empty_block_receipt_and_broadcast() + assert block_payload['hash'] == calculate_hash(block_payload['data']) \ No newline at end of file diff --git a/data_test/template/newrl.db b/data_test/template/newrl.db index a963255..a699673 100644 Binary files a/data_test/template/newrl.db and b/data_test/template/newrl.db differ diff --git a/data_test/template/newrl_p2p.db b/data_test/template/newrl_p2p.db index 82f493e..13bf9e3 100644 Binary files a/data_test/template/newrl_p2p.db and b/data_test/template/newrl_p2p.db differ diff --git a/docs/Documentation.md b/docs/Documentation.md new file mode 100644 index 0000000..f6d7863 --- /dev/null +++ b/docs/Documentation.md @@ -0,0 +1,164 @@ +### API calls for tokenizing and transacting an asset in order + +1. New partner addition (done by ASQI): output is a wallet file handed over to the partners that they can use for further wallet addition and token creation as below +2. /add-wallet[[TestNet]](https://testnet.newrl.net:8090/docs#/default/add_wallet_api_add_wallet_post) +Add new user wallet with new partner wallet from step 1 as the custodian wallet and KYC docs (can be dummy docs for test purposes but some files are required) +This returns a transaction file for the add wallet transaction. +3. /sign[[TestNet]](https://testnet.newrl.net:8090/docs#/default/sign_sign_post) +Sign the wallet creation transaction using new partner wallet from step 1. This requires uploading the full wallet file received from asqi. +4. /get-wallet-file[[TestNet]](https://testnet.newrl.net:8090/docs#/default/get_wallet_file_get_wallet_file_post) +Use the transaction file obtained in step 2 to get the user wallet file. Note down the wallet address from this file. This address can be used for making this user the first owner, or enabling transfers where this user is one of the parties. +5. /create-token[[TestNet]](https://testnet.newrl.net:8090/docs#/default/create_token_create_token_post) +Create a token requires some details about the token, a custodian and first owner. Use the new partner wallet from step 1 as the custodian wallet, first owner can be any valid wallet including the same as the custodian. The returned create_token_transaction file contains the tokencode which is needed for transfer and get-balance. +6. /sign[[TestNet]](https://testnet.newrl.net:8090/docs#/default/sign_sign_post) +Sign the token creation transaction using the wallet file of whoever has been named as custodian. +7. /run-updater [[TestNet]](https://testnet.newrl.net:8090/docs#/default/run_updater_run_updater_post) +Periodically run by scheduler but can be explicitly invoked to include new transactions to chain. +8. /get-balance[[TestNet]](https://testnet.newrl.net:8090/docs#/default/get_balance_get_balance_post) +Get balance for token in wallet +9. /create-transfer[[TestNet]](https://testnet.newrl.net:8090/docs#/default/create_transfer_create_transfer_post) +To transfer tokens from one wallet to another +10. /sign[[TestNet]](https://testnet.newrl.net:8090/docs#/default/sign_sign_post) +A transfer created at previous step need to be signed with custodian wallet + + +### Newrl Field Descriptions + +### /add-wallet + custodian_address - Address of the custodian creating the wallet + ownertype - Type of wallet holder. A list is available in the conventions doc. + jurisdiction - The phone code country in which the wallet is registered. Used for legal validations. List available in conventions doc. + kyc_docs - An array of KYC doc objects and their hashes. List available in conventions doc. + The hash is a hashcode of the digital file to prove the identity incase an investigation on the wallet arises. + specific_data - A dictionary containing specific data related to the wallet being created. + The application can decide what to include in here for future reference of wallet. + Example can be - user_type, application level user_id or email + public_key - The public key for which the wallet is being added. This can be obtained using /generate-wallet-address API + +### /add-token + token_name - A name or description of the token being created + token_code - A unique code for the new token being created. The application should check first if the token code being created + does not exist on chain. An example code for a residential property tokenised is - HSEBLR20220421 + token_type - Type of token. Allowed list available in conventions doc. + first_owner - Wallet in which the token will be first issued. + custodian - Custodian creating the token. + legal_doc - Hash of the digital file of doc with binding contract for the token. + amount_created - Quantity of tokens created + value_created - Deprecated. To be removed. + tokendecimal - Decimal places for the token. Example a token with tokendecimal as 3 with amount_created as 1000 will be considered + as 1 quantity. + disallowed_regions - a list of jurisdictions of wallets which cannot hold the token. + Example - ["910"] means Indian wallets cannot hold this token. + is_smart_contract_token - A flag for token to be used by smart contract. Default to false. Not to be used by apps directly. + token_attributes - Metadata about a token. For a property token, this can contain the address, area, build etc. + + +### /add-transfer + transfer_type - can be 4 or 5. 4 for bilateral transfer, 5 for unilateral transfer. + wallet1_address - The sender for asset1_code. + wallet2_address - The sendr for asset2_code + asset1_code - Token code for first asset + asset2_code - Token code for second asset + asset1_qty - Quantity for first asset + asset2_qty - Quantity for second asset + description - Any string description for the transfer. + additional_data - Metadata about the transfer. This is mostly used by system transfers. + + +### Conventions used for wallet jurisdictions, token types, transaction types etc. + +#### Wallets +1. Jurisdiction + 0 Undefined + 1 On-chain entity on Newrl + 2-99 reserved for on-chain entity types in future including those on non-Newrl + chains (e.g. DAO on Ethereum) + Standard convention: ISD code + 0 for two-digit ISD codes and ISD code itself for + 3-digit + 910 India + 440 UK + 970 Indonesia + 971 UAE + 972 Israel + 852 Hong Kong + 650 Singapore + etc + Exceptions: (1 digit codes) + 101 US + 199 Canada + Exceptions: (3 digit codes with 0 at the end) + TBD + Any jurisdiction that needs to be isolated from kyc point of view but does not have + separate isd code will be added above. Typically with a 3 digits code close to an + existing code for the nearest already coded jurisdiction e.g. british virgin islands + should be close to 440 if it does not have a separate isd) +2. Type of person + a. Undefined: 0 + b. DAO on Newrl: 9 + i. Smart contract on Newrl: 91 + ii. DAO LLC in US: 95 + c. Natural person: 1 + d. Private company: 2 + i. Variations in private company to be 21 - 29 + e. Public and listed company: 3 + i. Variations in public company to be 31 - 39 + f. (Unlimited liability) partnership: 4 + i. Variations in unlimited liability partnership to be 41-49 + g. Limited liability partnership: 5 + i. Variations in LLP to be 51-59 + h. Trust: 6 + i. Variations in trust to be 61-69 + i. Other: 8 + i. Variations in others to be 81-89 +3. Document types + 1. Tax id doc + 2. Doc for social security number / Aadhar / Similar number + 3. Passport + 4. Driving license + 5. Birth certificate + 6. Utility bill + 7. Bank statement / letter + 8. x + 9. x + 10. x + 11. Incorporation certificate + 12. Corporate tax certificate other than primary tax id + 13. X + 14. X + 15. x +#### Tokens + 0 platform token (utility as well as governance, includes NWRL token) + 1 Smart Money Token + 2-9 Other variants of SMT + 11 IOU + 12-19 Other variants of IOU + 31 Simple bonds + 32-39 Other variants of simple bonds + 41 Common stock + 42-49 Other variants of equity + 51 ETF + 52 Mutual fund + 53-59 Other variants of managed funds + 61 Unsecured loan + 62 Secured loan + 63 Convertible loan + 64-69 other varieties of loan + 71 Residential real estate + 72 Commercial real estate + 73 Land + 74 Industrial real estate + 75 Warehouse + 76 Agricultural land + 77-79 Other types of real estate + 81 Warehouse receipt of agricultural commodities + 82 WHR of base metals + 83 WHR of gold + 84 WHR of precious metals other than gold + 85 WHR of diamonds and precious stones + 91 Brand ownership + 92-94 Brand specific other tokens + 101 Generic contract with cash-flows + 102 Lease or rent contract + 103-109 various contract types + 111 Intellectual property rights + 121 Carbon credits \ No newline at end of file diff --git a/docs/Timings.txt b/docs/Timings.txt new file mode 100644 index 0000000..7c0eaf8 --- /dev/null +++ b/docs/Timings.txt @@ -0,0 +1,20 @@ +Timings.txt + + +TO - Start block mining +T1 - Block mining finish. Timestamp of block is T0 + +A node receive block at T2. Node time is not synchronos with network +T3_global = T0 + NO_BLOCK_TIMEOUT +T3_local = T3_global - get_time_difference() +Wait till T3_local. And then mine empty. + +T4 = Committee members propogate receipts +T5 = Live commitee members receive adequate receipts +T6_global = T0 + NO_RECEIPT_COMMITTEE_TIMEOUT +T6_local = T6_global - get_time_difference() + +T7 = Rest of network receive block and receipts +T8 = Rest of network accept the block +T9_global = T0 + NETWORK_BLOCK_TIMEOUT +T9_local = T9_global - get_time_difference() diff --git a/docs/TokenisationReadme.md b/docs/TokenisationReadme.md index 712e8b7..d860446 100644 --- a/docs/TokenisationReadme.md +++ b/docs/TokenisationReadme.md @@ -1,22 +1,22 @@ -API calls for tokenizing and transacting an asset in order +### API calls for tokenizing and transacting an asset in order 1. New partner addition (done by ASQI): output is a wallet file handed over to the partners that they can use for further wallet addition and token creation as below -2. /add-wallet[[Mainnet]](https://api.newrl.net/docs#/default/add_wallet_api_add_wallet_post)[[TestNet]](https://testnet.newrl.net/docs#/default/add_wallet_api_add_wallet_post) +2. /add-wallet[[TestNet]](https://testnet.newrl.net:8090/docs#/default/add_wallet_api_add_wallet_post) Add new user wallet with new partner wallet from step 1 as the custodian wallet and KYC docs (can be dummy docs for test purposes but some files are required) This returns a transaction file for the add wallet transaction. -3. /sign[[Mainnet]](https://api.newrl.net/docs#/default/sign_sign_post)[[TestNet]](https://testnet.newrl.net/docs#/default/sign_sign_post) +3. /sign[[TestNet]](https://testnet.newrl.net:8090/docs#/default/sign_sign_post) Sign the wallet creation transaction using new partner wallet from step 1. This requires uploading the full wallet file received from asqi. -4. /get-wallet-file[[Mainnet]](https://api.newrl.net/docs#/default/get_wallet_file_get_wallet_file_post)[[TestNet]](https://testnet.newrl.net/docs#/default/get_wallet_file_get_wallet_file_post) +4. /get-wallet-file[[TestNet]](https://testnet.newrl.net:8090/docs#/default/get_wallet_file_get_wallet_file_post) Use the transaction file obtained in step 2 to get the user wallet file. Note down the wallet address from this file. This address can be used for making this user the first owner, or enabling transfers where this user is one of the parties. -5. /create-token[[Mainnet]](https://api.newrl.net/docs#/default/create_token_create_token_post)[[TestNet]](https://testnet.newrl.net/docs#/default/create_token_create_token_post) +5. /create-token[[TestNet]](https://testnet.newrl.net:8090/docs#/default/create_token_create_token_post) Create a token requires some details about the token, a custodian and first owner. Use the new partner wallet from step 1 as the custodian wallet, first owner can be any valid wallet including the same as the custodian. The returned create_token_transaction file contains the tokencode which is needed for transfer and get-balance. -6. /sign[[Mainnet]](https://api.newrl.net/docs#/default/sign_sign_post)[[TestNet]](https://testnet.newrl.net/docs#/default/sign_sign_post) +6. /sign[[TestNet]](https://testnet.newrl.net:8090/docs#/default/sign_sign_post) Sign the token creation transaction using the wallet file of whoever has been named as custodian. -7. /run-updater [[Mainnet]](https://api.newrl.net/docs#/default/run_updater_run_updater_post)[[TestNet]](https://testnet.newrl.net/docs#/default/run_updater_run_updater_post) +7. /run-updater [[TestNet]](https://testnet.newrl.net:8090/docs#/default/run_updater_run_updater_post) Periodically run by scheduler but can be explicitly invoked to include new transactions to chain. -8. /get-balance[[Mainnet]](https://api.newrl.net/docs#/default/get_balance_get_balance_post)[[TestNet]](https://testnet.newrl.net/docs#/default/get_balance_get_balance_post) +8. /get-balance[[TestNet]](https://testnet.newrl.net:8090/docs#/default/get_balance_get_balance_post) Get balance for token in wallet -9. /create-transfer[[Mainnet]](https://api.newrl.net/docs#/default/create_transfer_create_transfer_post)[[TestNet]](https://testnet.newrl.net/docs#/default/create_transfer_create_transfer_post) +9. /create-transfer[[TestNet]](https://testnet.newrl.net:8090/docs#/default/create_transfer_create_transfer_post) To transfer tokens from one wallet to another -10. /sign[[Mainnet]](https://api.newrl.net/docs#/default/sign_sign_post)[[TestNet]](https://testnet.newrl.net/docs#/default/sign_sign_post) +10. /sign[[TestNet]](https://testnet.newrl.net:8090/docs#/default/sign_sign_post) A transfer created at previous step need to be signed with custodian wallet \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a1c36fc..24e6865 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,5 @@ starlette==0.14.2 typing-extensions==3.10.0.0 urllib3==1.26.6 uvicorn==0.14.0 +sse-starlette==0.6.2 +sh==1.14.1 diff --git a/scripts/add_all_network_node_wallets.py b/scripts/add_all_network_node_wallets.py new file mode 100644 index 0000000..8ec05f3 --- /dev/null +++ b/scripts/add_all_network_node_wallets.py @@ -0,0 +1,51 @@ +import requests + +NODE_URL = 'http://testnet.newrl.net:8182' +# NODE_URL = 'http://localhost:8182' +WALLET = {"public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==","private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=","address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} + +# NODE_URL = 'http://testnet.newrl.net:8090' +# WALLET = {"address": "0xc29193dbab0fe018d878e258c93064f01210ec1a","public": "sB8/+o32Q7tRTjB2XcG65QS94XOj9nP+mI7S6RIHuXzKLRlbpnu95Zw0MxJ2VGacF4TY5rdrIB8VNweKzEqGzg==","private": "xXqOItcwz9JnjCt3WmQpOSnpCYLMcxTKOvBZyj9IDIY="} + + +# NODE_URL = 'http://testnet.newrl.net:8090' +# WALLET = { +# "public": "pEeY8E9fdKiZ3nJizmagKXjqDSK8Fz6SAqqwctsIhv8KctDfkJlGnSS2LUj/Igk+LwAl91Y5pUHZTTafCosZiw==", +# "private": "x1Hp0sJzfTumKDqBwPh3+oj/VhNncx1+DLYmcTKHvV0=", +# "address": "0x6e206561a7018d84b593c5e4788c71861d716880" +# } + +def add_wallet(public_key): + add_wallet_request = { + "custodian_address": WALLET['address'], + "ownertype": "1", + "jurisdiction": "910", + "kyc_docs": [ + { + "type": 1, + "hash": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401" + } + ], + "specific_data": {}, + "public_key": public_key + } + + response = requests.post(NODE_URL + '/add-wallet', json=add_wallet_request) + + unsigned_transaction = response.json() + + response = requests.post(NODE_URL + '/sign-transaction', json={ + "wallet_data": WALLET, + "transaction_data": unsigned_transaction + }) + + signed_transaction = response.json() + + # print('signed_transaction', signed_transaction) + print('Sending wallet add transaction to chain') + response = requests.post(NODE_URL + '/validate-transaction', json=signed_transaction) + print('Got response from chain\n', response.text) + assert response.status_code == 200 + +for public_key in ['PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==', 'FbSwBu4b9aD3JoAaI1IzzWPtEvWiGSn6dIB1cm8h8NZlF4cQrGyESEdVGxS5tebWcalnyFSGjasnEPlyS45SAg==', 'bNSqzHBCK4tnf7rB780eF+Exph0ZCY1Vu2jnBPHq1zw2AIun4aar+vgHmUj8AY4oLF7iWfpBgkkVuHe6L1jepA==', 'KFYSB5fh0dASPz1lSys9DdkMl3Ouukydn6qpo32syTVoYkA5WYhSVCkp9kibr+Lgnw8DkOkAq5CCfP0CopiGHw==', 'Q0sw+ELDy7xWPbi3/1P0KaaYqeLkc97GEsoLumDuAAbAf1i2kNl2pYROY5sFVtmIUAtL4wUP6xoWZhIrcw4LrA==', 'ROSUDqyRFB6ZaHqQFsv5uUfx6GKUNZvpqsFNV/Gvv6Mjw1i+DTpUeFnj7qkKrMwjKZCJVdbRY+5e+Va0i2UGJw==', 'beUpeaiKW4CxBaGSYKCFYEG/f+PvFiS3H3l53OTqFoBpC7Bh1oEJz4QZ0Z1VkIBpXUw8ZcBwdBVLtr04GS/ltg==', 'Z5o1TBi438TSLd/PH1Qv7zIh8Dsx7f/siJXsnQcrEjCI8Pd70FvcMA4nPwQzreusQ6WBhvL1bmgtwx7VEZH5rA==', '4CscAOY/km2vv+5gkr8bVDb+1Po9oCWGHx3PkxBKNzadrDgPpYOJ/IaP65jgi48PQb46njNTbXhbc7ivKnt6lA==', 'KSnHvsppFRdKlQe0YbImp2Ipl3+SO1h7nlGunIB52OlydliOGggcNDtM8ZF0tDk8Fq/NxlhCTZuvVUrGwtRdkw==', 'gBqrxVsPlP/NSZFTHrozyGnp0wucGhJHOlDfefF3Q9WaGqkZWZr5RKPCOjmmKdEs3ohra39LYsGhexXjhZS7lw==', 'PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==']: + add_wallet(public_key) diff --git a/scripts/add_contract.py b/scripts/add_contract.py new file mode 100644 index 0000000..1433733 --- /dev/null +++ b/scripts/add_contract.py @@ -0,0 +1,41 @@ +import requests +import urllib3 + +NODE_URL = 'http://localhost:8182' +# NODE_URL = 'http://3.85.231.1:8182' +WALLET = {"public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==","private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=","address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} + +# NODE_URL = 'http://testnet.newrl.net:8090' +# WALLET = {"address": "0xc29193dbab0fe018d878e258c93064f01210ec1a","public": "sB8/+o32Q7tRTjB2XcG65QS94XOj9nP+mI7S6RIHuXzKLRlbpnu95Zw0MxJ2VGacF4TY5rdrIB8VNweKzEqGzg==","private": "xXqOItcwz9JnjCt3WmQpOSnpCYLMcxTKOvBZyj9IDIY="} + +response = requests.get(NODE_URL + '/generate-contract-address') +contract_address = response.text +contract_address = contract_address.replace('"', '') +print('Contract address', contract_address) + +add_wallet_request = { + "sc_address": contract_address, + "sc_name": "dao_manager", + "version": "1.0.0", + "creator": WALLET['address'], + "actmode": "hybrid", + "signatories": {"setup":"0x20513a419d5b11cd510ae518dc04ac1690afbed6", "deploy": "0x20513a419d5b11cd510ae518dc04ac1690afbed6", "create": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"}, + + "contractspecs": {}, + "legalparams": {} +} + +response = requests.post(NODE_URL + '/add-sc', json=add_wallet_request) +unsigned_transaction = response.json() + +response = requests.post(NODE_URL + '/sign-transaction', json={ +"wallet_data": WALLET, +"transaction_data": unsigned_transaction +}) + +signed_transaction = response.json() +print('signed_transaction', signed_transaction) +print('Sending wallet add transaction to chain') +response = requests.post(NODE_URL + '/validate-transaction', json=signed_transaction) +print('Got response from chain\n', response.text) +assert response.status_code == 200 diff --git a/scripts/add_token.py b/scripts/add_token.py new file mode 100644 index 0000000..9ab4b5c --- /dev/null +++ b/scripts/add_token.py @@ -0,0 +1,51 @@ +import requests + +# NODE_URL = 'http://testnet.newrl.net:8090' +# WALLET = { +# "public": "pEeY8E9fdKiZ3nJizmagKXjqDSK8Fz6SAqqwctsIhv8KctDfkJlGnSS2LUj/Igk+LwAl91Y5pUHZTTafCosZiw==", +# "private": "x1Hp0sJzfTumKDqBwPh3+oj/VhNncx1+DLYmcTKHvV0=", +# "address": "0x6e206561a7018d84b593c5e4788c71861d716880" +# } + +# NODE_URL = 'http://localhost:8182' +NODE_URL = 'http://testnet.newrl.net:8182' +WALLET = {"public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==","private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=","address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} + + +token_code = input('Enter token code: ') +amount = input('Issue amount: ') +first_owner = input('First owner[leave blank for custodian]: ') + +if first_owner == '': + first_owner = WALLET['address'] + +add_wallet_request = { + "token_name": token_code, + "token_code": token_code, + "token_type": "1", + "first_owner": first_owner, + "custodian": WALLET['address'], + "legal_doc": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401", + "amount_created": amount, + "tokendecimal": 2, + "disallowed_regions": [], + "is_smart_contract_token": False, + "token_attributes": {} +} + +response = requests.post(NODE_URL + '/add-token', json=add_wallet_request) + +unsigned_transaction = response.json() + +response = requests.post(NODE_URL + '/sign-transaction', json={ + "wallet_data": WALLET, + "transaction_data": unsigned_transaction +}) + +signed_transaction = response.json() + +print('signed_transaction', signed_transaction) +response = requests.post(NODE_URL + '/validate-transaction', json=signed_transaction) +print(response.text) +print(response.status_code) +assert response.status_code == 200 diff --git a/scripts/add_token_many.py b/scripts/add_token_many.py new file mode 100644 index 0000000..22dff75 --- /dev/null +++ b/scripts/add_token_many.py @@ -0,0 +1,57 @@ +import threading +import requests + +# NODE_URL = 'http://testnet.newrl.net:8090' +# WALLET = { +# "public": "pEeY8E9fdKiZ3nJizmagKXjqDSK8Fz6SAqqwctsIhv8KctDfkJlGnSS2LUj/Igk+LwAl91Y5pUHZTTafCosZiw==", +# "private": "x1Hp0sJzfTumKDqBwPh3+oj/VhNncx1+DLYmcTKHvV0=", +# "address": "0x6e206561a7018d84b593c5e4788c71861d716880" +# } + +NODE_URL = 'http://testnet.newrl.net:8182' +WALLET = {"public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==","private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=","address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} + + +token_code = input('Enter token code: ') +amount = input('Issue amount: ') +first_owner = input('First owner[leave blank for custodian]: ') + +if first_owner == '': + first_owner = WALLET['address'] + +def add_token(tk): + add_wallet_request = { + "token_name": 'TPSTKA'+str(tk), + "token_code": 'TPSTKA'+str(tk), + "token_type": "1", + "first_owner": first_owner, + "custodian": WALLET['address'], + "legal_doc": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401", + "amount_created": tk, + "tokendecimal": 0, + "disallowed_regions": [], + "is_smart_contract_token": False, + "token_attributes": {} + } + + response = requests.post(NODE_URL + '/add-token', json=add_wallet_request) + + unsigned_transaction = response.json() + + response = requests.post(NODE_URL + '/sign-transaction', json={ + "wallet_data": WALLET, + "transaction_data": unsigned_transaction + }) + + signed_transaction = response.json() + + print('signed_transaction', signed_transaction) + response = requests.post(NODE_URL + '/validate-transaction', json=signed_transaction) + print(response.text) + print(response.status_code) + assert response.status_code == 200 + +for tk in range(1, 10000): + add_token(tk) + timer = threading.Timer(1, add_token, (tk,)) + timer.start() \ No newline at end of file diff --git a/scripts/add_token_urllib.py b/scripts/add_token_urllib.py new file mode 100644 index 0000000..7a5b5c7 --- /dev/null +++ b/scripts/add_token_urllib.py @@ -0,0 +1,49 @@ +import json +import urllib3 + +# NODE_URL = 'http://testnet.newrl.net:8182' +NODE_URL = 'http://testnet.newrl.net:8090' +# NODE_URL = 'http://newrl.net:8090' +# WALLET = { "public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==", "private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=", "address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} +WALLET = { "address": "0xc29193dbab0fe018d878e258c93064f01210ec1a", "public": "sB8/+o32Q7tRTjB2XcG65QS94XOj9nP+mI7S6RIHuXzKLRlbpnu95Zw0MxJ2VGacF4TY5rdrIB8VNweKzEqGzg==", "private": "xXqOItcwz9JnjCt3WmQpOSnpCYLMcxTKOvBZyj9IDIY=" } + +http = urllib3.PoolManager() + +token_code = input('Enter token code: ') +amount = input('Issue amount: ') +first_owner = input('First owner[c for custodian]: ') + +if first_owner == 'c': + first_owner = WALLET['address'] + +add_token_request = { + "token_name": token_code, + "token_code": token_code, + "token_type": "1", + "first_owner": first_owner, + "custodian": WALLET['address'], + "legal_doc": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401", + "amount_created": amount, + "tokendecimal": 0, + "disallowed_regions": [], + "is_smart_contract_token": False, + "token_attributes": {} +} + +response = http.request('POST', NODE_URL + '/add-token', body=json.dumps(add_token_request), headers={'Content-Type': 'application/json'}) + +unsigned_transaction = json.loads(response.data) + +payload = { + "wallet_data": WALLET, + "transaction_data": unsigned_transaction +} +response = http.request('POST', NODE_URL + '/sign-transaction', body=json.dumps(payload), headers={'Content-Type': 'application/json'}) + +signed_transaction = json.loads(response.data) + +print('signed_transaction', signed_transaction) +print('Sending wallet add transaction to chain') +response = http.request('POST', NODE_URL + '/validate-transaction', body=json.dumps(signed_transaction), headers={'Content-Type': 'application/json'}) +print('Got response from chain\n', response.data) +assert response.status == 200 \ No newline at end of file diff --git a/scripts/add_transfer.py b/scripts/add_transfer.py new file mode 100644 index 0000000..ab7cae4 --- /dev/null +++ b/scripts/add_transfer.py @@ -0,0 +1,66 @@ +import json +import requests + +NODE_URL = 'http://testnet.newrl.net:8090' +WALLET = { + "public": "pEeY8E9fdKiZ3nJizmagKXjqDSK8Fz6SAqqwctsIhv8KctDfkJlGnSS2LUj/Igk+LwAl91Y5pUHZTTafCosZiw==", + "private": "x1Hp0sJzfTumKDqBwPh3+oj/VhNncx1+DLYmcTKHvV0=", + "address": "0x6e206561a7018d84b593c5e4788c71861d716880" +} + +transfer_type = input('Enter transfer type[4 for bilateral, 5 for unilateral]: ') +wallet1 = input('Enter first wallet address[leave blank for custodian]: ') +token1 = input('Enter first token code: ') +amount1 = input('Enter amount of first token: ') +wallet2 = input('Enter second wallet address: ') + +if wallet1 == '': + wallet1 = WALLET["address"] + +if transfer_type == '4': + token2 = input('Enter second token code: ') + amount2 = input('Enter amount of second token: ') +else: + token2 = '' + amount2 = 0 + +add_transfer_request = { + "transfer_type": int(transfer_type), + "asset1_code": token1, + "asset2_code": token2, + "wallet1_address": wallet1, + "wallet2_address": wallet2, + "asset1_qty": int(amount1), + "asset2_qty": int(amount2), + "description": "", + "additional_data": {} +} + +print(add_transfer_request) + +response = requests.post(NODE_URL + '/add-transfer', json=add_transfer_request) + +unsigned_transaction = response.json() + +response = requests.post(NODE_URL + '/sign-transaction', json={ + "wallet_data": WALLET, + "transaction_data": unsigned_transaction +}) +signed_transaction = response.json() + +# Transfer type 4 need to be signed by both wallets +if transfer_type == '4': + WALLET2 = input('Enter complete json for wallet2: ') + WALLET2 = json.loads(WALLET2) + + response = requests.post(NODE_URL + '/sign-transaction', json={ + "wallet_data": WALLET2, + "transaction_data": signed_transaction + }) + signed_transaction = response.json() + +print('signed_transaction', signed_transaction) +response = requests.post(NODE_URL + '/validate-transaction', json=signed_transaction) +print(response.text) +print(response.status_code) +assert response.status_code == 200 diff --git a/scripts/add_wallet.py b/scripts/add_wallet.py new file mode 100644 index 0000000..e7ae23b --- /dev/null +++ b/scripts/add_wallet.py @@ -0,0 +1,60 @@ +import requests + +NODE_URL = 'http://testnet.newrl.net:8182' +WALLET = {"public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==","private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=","address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} + +# NODE_URL = 'http://testnet.newrl.net:8090' +# WALLET = {"address": "0xc29193dbab0fe018d878e258c93064f01210ec1a","public": "sB8/+o32Q7tRTjB2XcG65QS94XOj9nP+mI7S6RIHuXzKLRlbpnu95Zw0MxJ2VGacF4TY5rdrIB8VNweKzEqGzg==","private": "xXqOItcwz9JnjCt3WmQpOSnpCYLMcxTKOvBZyj9IDIY="} + + +# NODE_URL = 'http://testnet.newrl.net:8090' +# WALLET = { +# "public": "pEeY8E9fdKiZ3nJizmagKXjqDSK8Fz6SAqqwctsIhv8KctDfkJlGnSS2LUj/Igk+LwAl91Y5pUHZTTafCosZiw==", +# "private": "x1Hp0sJzfTumKDqBwPh3+oj/VhNncx1+DLYmcTKHvV0=", +# "address": "0x6e206561a7018d84b593c5e4788c71861d716880" +# } + +def add_wallet(public_key): + add_wallet_request = { + "custodian_address": WALLET['address'], + "ownertype": "1", + "jurisdiction": "910", + "kyc_docs": [ + { + "type": 1, + "hash": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401" + } + ], + "specific_data": {}, + "public_key": public_key + } + + response = requests.post(NODE_URL + '/add-wallet', json=add_wallet_request) + + unsigned_transaction = response.json() + + response = requests.post(NODE_URL + '/sign-transaction', json={ + "wallet_data": WALLET, + "transaction_data": unsigned_transaction + }) + + signed_transaction = response.json() + + print('signed_transaction', signed_transaction) + print('Sending wallet add transaction to chain') + response = requests.post(NODE_URL + '/validate-transaction', json=signed_transaction) + print('Got response from chain\n', response.text) + assert response.status_code == 200 + +public_key = input('Enter public key[leave blank to create new key pair]: ') + +if public_key == '': + response = requests.get(NODE_URL + '/generate-wallet-address') + wallet = response.json() + print('New wallet\n', wallet, '\n') + public_key = wallet['public'] + +add_wallet(public_key) + +# for public_key in ['PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==', '6YCLRYLmn7xLEZoBnvFVAhqMs0RSLHh6qx4+FMFaMhc2vSDh9pxpdLQdjUSPb/JgFsA25Wpkh5r9myC0HbkaOQ==', 'BZdhXCER0+foqtVOjzfXVi6bE33oS+F7QybRgAKKNG955iIZymjEYZE8RDSKwHI1Ww0iMkaZ6a63ZVR6Q2ZOsw==', 'F4JyBkTigIviJ2ZXBDED38bwi3nKiq8NEyxFlYtmcPB0siRSIBRBpMSzdAU9hdmXodSapJcoO/lhhwlZP413CA==', 'FVatd42BznwblvNFBET9neGXHazylhevWEZX3R0OAKjpaJciUDz0AVHta6BcZrSMcelYrI/wE0HP9tLGbAgwnQ==', 'NLvTGkixKssh3NQB5Jht4pQIiYxJBw/8WYLQ7uwH7EWdO1UlPHSewrIRvPTeEmmrlpa/AmdRvh/6hkbfhZRmYg==', 'K7RISj43N9umd+rP/zarVPmUsH/4MQjukYaZeG81nIt8q2RvJaoTOPNVJRmfMHEvpzMWnD4kil7D4zN84cHloA==', 'qyMa3ncG6xzoOulTfWp1Dbf06yKGo728yqYZctWNCR21NxuCVEoUDCPR99CxEvHBhE7SbUTiLe289jpF52qh7g==', 'hKpfJMvMoOMWvONGJOV9ncQmtupushRNKgYkNLxfnU+1ZnX/XYDZT5ly6oaSypn6fQxtgJFLaHeSSvkZKq0RfA==', 'J8Xy8uUw5AlFGkB9zlLhElVXPstVsbo9V70hqVes6tPJSOspAC+v+2p9HZNESnVQaiXznXkP24rHkz/IoamCmw==', '74ZxEE/N6SUh6PW6erTWkBI4insFRli8XURit0EnTUYU1B36RoQRAFU4egQkJr/WgcJd1SZErqtPpxLn2FzSfA==', 'qcFAa8JxDAubyEjHz2xlkE22GhhPE2HIOxwlna1DfNvtCxPc+D0eK3DHXyvcAuqGgpGhi8baftOkw7He2SqyHw==', 'SG8cb1OTYpbqi230dLn8QN5CLNFnE+ZsjJKhHBGZjHExsnJ3RJ44IhUzU3/+RnGThLd/i4IkRaB0Eic0YSIiXw==', '8nkFblwk1Jq4frQURGvW36/1qHw5MhGv2A1RHnjhtdddAavfLEtO6Dq64MwXqWAy1XN+Wx4wWfztgNv9pdkh4A==', 'pzg0Ms3/R+13KIB/gIrUsISmc5jC/OqhJIJrfy0h4ggodEpiHZsWSRsEF0hZl1HCBOVLUq01jKIZFVZZ9uV8aw==', '+1pc+A8bYWfkiOuPv3XMXzJ3xs2TR6lVwHIfkzJfBYiGxKnNhT2IyRxbjlZshlSgmQEhxB+k7PraaBPdN5d7Wg==', 'JSDoAwJQqmFNVls09fVzcqtAROhh29tLDEXmLqs4ndpCmVdv0kHslX/DhFFq8BFuSv8nfb9uaWfZ9crpFNsDhg==', 'Vhl+eXpXDoc2bgO0IG+wQ+DfOSIQPerEIdXA1N7mDzuUccLHvQEmeho0uk0ormbuwAcHR/nYgK7I3LoCPuNOmA==', 'lrjRgzFVALEP2W52LeflWA2zV5hOxPiKgkJiXSD9IC1PxJShwpQbF8oz0Y6SzowBILH3MXOusiFIl4+EUMsm8Q==', 'Bw6baJzl+Z6yJATcWgd+xVzv2UrfkvMP6STN+TgYevg0gYJGDKjLi4Vr+Gf6Ps3FACcVk26dEjSAuFtHHnodFg==', 'zZuUGV7BMXRVRwo+S1mPWxLuyaSLUCYpEhiTxoX0jR+lNBcC4BSivjkRbeJj6f7/c3iRAy4B9BSTGhI2azMuZA==', '0FKo0JWB+TZa9BN/WoBkK5hqjiDvgnlgF6ONr9m5+ebG9aY1Uz+wVyucbJTdHwyq0xYVjYIoZeL6TEw4lvNYlw==', '3kRKKSz6RxRAYTGdV6s7kMLBSYKuS2+prImkx3g1XBc0xtYQB/VW7cEgVf1DR7Fdn2P1AclESpokmGJK2OENWA==', 'yN4Iuk/sWqwkFn8+wtJuyCHKLWWIIOjwBjua7NFE4AKKIGqXLR2nOvhfkZCZ7DRyGLfAqzphcSYaoH2VNvLpUg==', 'PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==']: +# add_wallet(public_key) diff --git a/scripts/add_wallet_urllib.py b/scripts/add_wallet_urllib.py new file mode 100644 index 0000000..d9a4b95 --- /dev/null +++ b/scripts/add_wallet_urllib.py @@ -0,0 +1,51 @@ +import json +import urllib3 + +# NODE_URL = 'http://testnet.newrl.net:8182' +NODE_URL = 'http://testnet.newrl.net:8090' +# NODE_URL = 'http://newrl.net:8090' +# WALLET = {"public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==","private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=","address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} +WALLET = {"address": "0xc29193dbab0fe018d878e258c93064f01210ec1a","public": "sB8/+o32Q7tRTjB2XcG65QS94XOj9nP+mI7S6RIHuXzKLRlbpnu95Zw0MxJ2VGacF4TY5rdrIB8VNweKzEqGzg==","private": "xXqOItcwz9JnjCt3WmQpOSnpCYLMcxTKOvBZyj9IDIY="} + +http = urllib3.PoolManager() + +public_key = input('Enter public key: ') + +if public_key == '0': + response = http.request('GET', NODE_URL + '/generate-wallet-address') + wallet = json.loads(response.data) + print('New wallet\n', wallet, '\n') + public_key = wallet['public'] + + +add_wallet_request = { + "custodian_address": WALLET['address'], + "ownertype": "1", + "jurisdiction": "910", + "kyc_docs": [ +{ + "type": 1, + "hash": "686f72957d4da564e405923d5ce8311b6567cedca434d252888cb566a5b4c401" +} + ], + "specific_data": {}, + "public_key": public_key +} + +response = http.request('POST', NODE_URL + '/add-wallet', body=json.dumps(add_wallet_request), headers={'Content-Type': 'application/json'}) + +unsigned_transaction = json.loads(response.data) + +payload = { + "wallet_data": WALLET, + "transaction_data": unsigned_transaction +} +response = http.request('POST', NODE_URL + '/sign-transaction', body=json.dumps(payload), headers={'Content-Type': 'application/json'}) + +signed_transaction = json.loads(response.data) + +print('signed_transaction', signed_transaction) +print('Sending wallet add transaction to chain') +response = http.request('POST', NODE_URL + '/validate-transaction', body=json.dumps(signed_transaction), headers={'Content-Type': 'application/json'}) +print('Got response from chain\n', response.data) +assert response.status == 200 diff --git a/scripts/analytics_get_token_balance_all_nodes.py b/scripts/analytics_get_token_balance_all_nodes.py new file mode 100644 index 0000000..46398aa --- /dev/null +++ b/scripts/analytics_get_token_balance_all_nodes.py @@ -0,0 +1,28 @@ +import requests + +NODE_URL = 'http://18.208.160.119:8182' +# NODE_URL = 'http://44.203.127.164:8182' + + +def process(node_addr): + url = 'http://' + node_addr + ':8182/get-token?token_code=TS343' + response = requests.get(url, timeout=1).json() + print(node_addr, response['amount_created']) + # print(f'{node_addr} - Invalid') + # print(f'{node_addr} - Reverting') + # requests.post('http://' + node_addr + ':8182/revert-chain', + # json={ + # "block_index": 0 + # }, timeout=1).json() + # requests.post('http://' + node_addr + ':8182/sync-chain-from-peers', + # json={ + # "block_index": 0 + # }, timeout=1).json() + + +for node in ['100.27.31.95', '13.127.196.33', '18.205.152.148', '18.205.189.2', '18.206.94.76', '18.207.216.103', '18.208.182.177', '18.212.66.84', '184.72.208.65', '3.80.209.8', '3.83.50.130', '3.84.50.209', '3.84.77.114', '3.86.220.174', '3.86.95.36', '3.87.215.96', '3.87.239.9', '3.87.59.246', '3.89.74.97', '3.89.88.60', '3.91.148.23', '3.91.240.161', '3.94.187.48', '34.230.43.253', '34.238.254.65', '35.171.16.241', '35.175.149.140', '44.201.104.135', '44.201.195.243', '44.202.100.22', '44.202.112.192', '44.202.36.179', '44.202.6.221', '44.203.125.108', '44.203.155.59', '44.204.232.32', '44.204.33.70', '52.203.35.134', '52.207.247.190', '52.23.232.237', '52.87.240.182', '52.90.193.227', '52.91.186.245', '52.91.72.62', '52.91.72.92', '54.158.53.69', '54.164.113.49', '54.164.15.194', '54.165.186.46', '54.173.64.147', '54.175.45.195', '54.197.74.80', '54.209.119.94', '54.209.184.138', '54.209.95.85', '54.210.130.74', '54.224.29.208', '54.237.250.151', '54.84.93.34', '54.85.121.224', 'testnet.newrl.net']: + try: + process(node) + except: + pass + # print(f'{node} - Unreachable') diff --git a/scripts/call_contract_deploy.py b/scripts/call_contract_deploy.py new file mode 100644 index 0000000..34bb724 --- /dev/null +++ b/scripts/call_contract_deploy.py @@ -0,0 +1,41 @@ +import json +import requests +import urllib3 + +# NODE_URL = 'http://localhost:8182' +NODE_URL = 'http://testnet.newrl.net:8182' +WALLET = {"public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==","private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=","address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} + +# NODE_URL = 'http://testnet.newrl.net:8090' +# WALLET = {"address": "0xc29193dbab0fe018d878e258c93064f01210ec1a","public": "sB8/+o32Q7tRTjB2XcG65QS94XOj9nP+mI7S6RIHuXzKLRlbpnu95Zw0MxJ2VGacF4TY5rdrIB8VNweKzEqGzg==","private": "xXqOItcwz9JnjCt3WmQpOSnpCYLMcxTKOvBZyj9IDIY="} + +contract_address = input('Enter Contract address: ') + + +req = { + "sc_address": contract_address, + "function_called": "deploy", + "signers": [ + WALLET['address'] + ], + "params": { + "sender": WALLET['address'] + } +} + +print(json.dumps(req)) +response = requests.post(NODE_URL + '/call-sc', json=req) +print(response.text) +unsigned_transaction = response.json() + +response = requests.post(NODE_URL + '/sign-transaction', json={ +"wallet_data": WALLET, +"transaction_data": unsigned_transaction +}) + +signed_transaction = response.json() +print('signed_transaction', signed_transaction) +print('Sending contract call transaction to chain') +response = requests.post(NODE_URL + '/validate-transaction', json=signed_transaction) +print('Got response from chain\n', response.text) +assert response.status_code == 200 diff --git a/scripts/call_contract_fn.py b/scripts/call_contract_fn.py new file mode 100644 index 0000000..da556df --- /dev/null +++ b/scripts/call_contract_fn.py @@ -0,0 +1,42 @@ +import json +import requests +import urllib3 + +# NODE_URL = 'http://localhost:8182' +NODE_URL = 'http://testnet.newrl.net:8182' +WALLET = {"public": "PizgnsfVWBzJxJ6RteOQ1ZyeOdc9n5KT+GrQpKz7IXLQIiVmSlvZ5EHw83GZL7wqZYQiGrHH+lKU7xE5KxmeKg==","private": "zhZpfvpmT3R7mUZa67ui1/G3I9vxRFEBrXNXToVctH0=","address": "0x20513a419d5b11cd510ae518dc04ac1690afbed6"} + +# NODE_URL = 'http://testnet.newrl.net:8090' +# WALLET = {"address": "0xc29193dbab0fe018d878e258c93064f01210ec1a","public": "sB8/+o32Q7tRTjB2XcG65QS94XOj9nP+mI7S6RIHuXzKLRlbpnu95Zw0MxJ2VGacF4TY5rdrIB8VNweKzEqGzg==","private": "xXqOItcwz9JnjCt3WmQpOSnpCYLMcxTKOvBZyj9IDIY="} + +contract_address = input('Enter Contract address: ') + +req = { + "sc_address": contract_address, + "function_called": "send_nusd_token", + "signers": [ + WALLET['address'] + ], + "params": { + "recipient_address": WALLET['address'], + "sender": WALLET['address'], + "value": 20 + } +} + +print(json.dumps(req)) +response = requests.post(NODE_URL + '/call-sc', json=req) +print(response.text) +unsigned_transaction = response.json() + +response = requests.post(NODE_URL + '/sign-transaction', json={ +"wallet_data": WALLET, +"transaction_data": unsigned_transaction +}) + +signed_transaction = response.json() +print('signed_transaction', signed_transaction) +print('Sending contract call transaction to chain') +response = requests.post(NODE_URL + '/validate-transaction', json=signed_transaction) +print('Got response from chain\n', response.text) +assert response.status_code == 200 diff --git a/scripts/install.sh b/scripts/install.sh index cc02881..6a3ddf6 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,3 +1,4 @@ +git pull python3 -m venv venv source venv/bin/activate pip install --upgrade pip diff --git a/scripts/kill_server.sh b/scripts/kill_server.sh index 6639b13..6d6a614 100755 --- a/scripts/kill_server.sh +++ b/scripts/kill_server.sh @@ -1 +1,2 @@ lsof -P | grep 8090 | awk '{print $2}' | xargs kill -9 +fuser -k 8182/tcp \ No newline at end of file diff --git a/scripts/node_chain_verify.py b/scripts/node_chain_verify.py new file mode 100644 index 0000000..9c85b2e --- /dev/null +++ b/scripts/node_chain_verify.py @@ -0,0 +1,47 @@ +import requests + +NODE_URL = 'http://18.208.160.119:8182' +# NODE_URL = 'http://44.203.127.164:8182' + + +def verify_node(node_address): + node_address = 'http://' + node_address + ':8182' + last_block_url = node_address + '/get-last-block-index' + print(last_block_url) + last_block = requests.get(node_address + '/get-last-block-index', timeout=1).text + last_block = int(last_block) + + blocks = requests.post(node_address + '/get-blocks', json={ + "block_indexes": list(range(1, last_block + 1)) + }, timeout=10).json() + + previous_hash = "0" + + for block in blocks: + print(block['block_index']) + if block['previous_hash'] != previous_hash: + print('Chain invalid from index', block['block_index']) + previous_hash = block['hash'] + + print(f'{node_address} - chain is valid') + + +def check_block_exist(node_addr): + url = 'http://' + node_addr + ':8182/get-block?block_index=1' + block = requests.get(url, timeout=1).json() + if 'block_index' not in block: + # print(f'{node_addr} - Invalid') + print(f'{node_addr} - Reverting') + requests.post('http://' + node_addr + ':8182/revert-chain?block_index=0&propogate=false', timeout=1).json() + requests.post('http://' + node_addr + ':8182/sync-chain-from-peers', + json={ + "block_index": 0 + }, timeout=1).json() + + +for node in ['100.27.31.95', '13.127.196.33', '18.205.152.148', '18.205.189.2', '18.206.94.76', '18.207.216.103', '18.208.182.177', '18.212.66.84', '184.72.208.65', '3.80.209.8', '3.83.50.130', '3.84.50.209', '3.84.77.114', '3.86.220.174', '3.86.95.36', '3.87.215.96', '3.87.239.9', '3.87.59.246', '3.89.74.97', '3.89.88.60', '3.91.148.23', '3.91.240.161', '3.94.187.48', '34.230.43.253', '34.238.254.65', '35.171.16.241', '35.175.149.140', '44.201.104.135', '44.201.195.243', '44.202.100.22', '44.202.112.192', '44.202.36.179', '44.202.6.221', '44.203.125.108', '44.203.155.59', '44.204.232.32', '44.204.33.70', '52.203.35.134', '52.207.247.190', '52.23.232.237', '52.87.240.182', '52.90.193.227', '52.91.186.245', '52.91.72.62', '52.91.72.92', '54.158.53.69', '54.164.113.49', '54.164.15.194', '54.165.186.46', '54.173.64.147', '54.175.45.195', '54.197.74.80', '54.209.119.94', '54.209.184.138', '54.209.95.85', '54.210.130.74', '54.224.29.208', '54.237.250.151', '54.84.93.34', '54.85.121.224', 'testnet.newrl.net']: + try: + check_block_exist(node) + except: + pass + # print(f'{node} - Unreachable') diff --git a/scripts/nodes.http b/scripts/nodes.http new file mode 100644 index 0000000..ee8a91b --- /dev/null +++ b/scripts/nodes.http @@ -0,0 +1,61 @@ + +GET http://testnet.newrl.net:8182/get-last-block-index +GET http://testnet.newrl.net:8182/get-node-wallet-address +GET http://testnet.newrl.net:8182/get-miners +GET http://testnet.newrl.net:8182/get-peers +GET http://testnet.newrl.net:8182/download-chain +GET http://testnet.newrl.net:8182/download-state +POST http://testnet.newrl.net:8182/run-updater +POST http://testnet.newrl.net:8182/revert-chain?block_index=1&propogate=false + +GET http://makemyfund.in:8182/get-last-block-index +GET http://makemyfund.in:8182/get-node-wallet-address +GET http://makemyfund.in:8182/get-miners +GET http://makemyfund.in:8182/get-peers +POST http://makemyfund.in:8182/run-updater +POST http://makemyfund.in:8182/revert-chain?block_index=1&propogate=false + +GET http://asqisys.com:8182/get-last-block-index +GET http://asqisys.com:8182/get-node-wallet-address +GET http://asqisys.com:8182/get-miners +GET http://asqisys.com:8182/get-peers +POST http://asqisys.com:8182/run-updater +GET http://asqisys.com:8182/download-chain +POST http://asqisys.com:8182/revert-chain?block_index=1&propogate=false + +GET http://localhost:8182/get-last-block-index +GET http://localhost:8182/get-node-wallet-address +GET http://localhost:8182/get-miners +GET http://localhost:8182/get-peers +POST http://localhost:8182/sync-chain-from-peers +POST http://localhost:8182/run-updater +GET http://localhost:8182/download-chain +POST http://localhost:8182/remove-dead-peers +POST http://localhost:8182/revert-chain?block_index=0&propogate=false +POST http://localhost:8182/receive-transaction + +{"transaction": {"timestamp": 1648185985420, "trans_code": "8d9817b183d61710c82323c082e9652727931ede", "type": 7, "currency": "NWRL", "fee": 0.0, "descr": "Miner addition", "valid": 1, "specific_data": {"wallet_address": "0x76d5ac405f6885c90c407202c474faff93389605", "network_address": "3.6.169.47", "broadcast_timestamp": 1648185985420}}, "signatures": [{"wallet_address": "0x76d5ac405f6885c90c407202c474faff93389605", "msgsign": "peAZ3HOcQDKDS7EVPEmjMqKMkWJcKLKbK3T1wuphelv7vjQvsYHWUEXOu2quk9O7TJKK/kK+yt6/bYbEAW733g=="}]} + +GET http://159.223.163.198:8182/get-last-block-index +GET http://159.223.163.198:8182/get-node-wallet-address +GET http://159.223.163.198:8182/get-miners +GET http://159.223.163.198:8182/get-peers +POST http://159.223.163.198:8182/run-updater +POST http://159.223.163.198:8182/revert-chain?block_index=10&propogate=false +POST http://159.223.163.198:8182/run-updater + +POST http://testnet.newrl.net:8182/update-software?propogate=true +POST http://159.223.163.198:8182/update-software?propogate=true +POST http://asqisys.com:8182/update-software?propogate=true +POST http://makemyfund.in:8182/update-software?propogate=false +POST http://localhost:8182/update-software?propogate=true + +POST http://159.223.163.198:8182/sync-chain-from-peers + +POST http://testnet.newrl.net:8182/revert-chain?block_index=1&propogate=false +POST http://159.223.163.198:8182/revert-chain?block_index=1&propogate=false +POST http://asqisys.com:8182/revert-chain?block_index=1&propogate=false +POST http://makemyfund.in:8182/revert-chain?block_index=1&propogate=false +POST http://localhost:8182/revert-chain?block_index=2230&propogate=false + +POST http://testnet.newrl.net:8182/run-updater?add_to_chain_before_consensus=true \ No newline at end of file diff --git a/scripts/start.sh b/scripts/start.sh index c41d03d..8289bf1 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -1,9 +1,9 @@ -# git pull +git pull # python3 -m venv venv source venv/bin/activate -# pip install --upgrade pip -# pip install -r requirements.txt +pip install --upgrade pip +pip install -r requirements.txt # python -m app.migrations.init # scripts/migrate_db.sh python -m app.main