From 5a6237c4523c01d4b9b0d4663ed15a78f34bed91 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 23 Aug 2024 17:29:53 -0400 Subject: [PATCH] init --- common.py | 10 +++++ data/abi/SHIO.json | 1 + logs.py | 33 ++++++++++++++ main.py | 104 +++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + sample.env | 5 +++ 6 files changed, 155 insertions(+) create mode 100644 common.py create mode 100644 data/abi/SHIO.json create mode 100644 logs.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 sample.env diff --git a/common.py b/common.py new file mode 100644 index 0000000..68ecddd --- /dev/null +++ b/common.py @@ -0,0 +1,10 @@ + +def read_file_to_list(filename): + try: + lines = [] + with open(filename, 'r') as f: + for line in f.readlines(): + lines.append(line.strip()) + return lines + except FileNotFoundError: + return [] \ No newline at end of file diff --git a/data/abi/SHIO.json b/data/abi/SHIO.json new file mode 100644 index 0000000..a7320ab --- /dev/null +++ b/data/abi/SHIO.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"RodAddress","type":"address"},{"internalType":"address","name":"ConeAddress","type":"address"},{"internalType":"address","name":"MathLib","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint64","name":"Barn","type":"uint64"}],"name":"BarnInequality","type":"error"},{"inputs":[{"internalType":"address","name":"origin","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"address","name":"what","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"DysnomiaInsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"origin","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"what","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"DysnomiaInsufficientBalance","type":"error"},{"inputs":[{"internalType":"uint64","name":"Manifold","type":"uint64"}],"name":"ManifoldInequality","type":"error"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"MarketRateNotFound","type":"error"},{"inputs":[{"internalType":"address","name":"origin","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"what","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"origin","type":"address"},{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"what","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"uint64","name":"Eta","type":"uint64"},{"internalType":"uint64","name":"Kappa","type":"uint64"}],"name":"ReactionInequalityError","type":"error"},{"inputs":[{"internalType":"uint64","name":"Eta","type":"uint64"},{"internalType":"uint64","name":"Kappa","type":"uint64"}],"name":"ReactionZeroError","type":"error"},{"inputs":[{"internalType":"uint64","name":"Ring","type":"uint64"}],"name":"RingInequality","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"Soul","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"Aura","type":"uint64"},{"indexed":false,"internalType":"string","name":"LogLine","type":"string"}],"name":"LogEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOwner","type":"address"},{"indexed":true,"internalType":"bool","name":"state","type":"bool"}],"name":"OwnershipUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"Cone","outputs":[{"internalType":"contract SHA","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"Xi","type":"uint64"},{"internalType":"uint64","name":"Alpha","type":"uint64"},{"internalType":"uint64","name":"Beta","type":"uint64"}],"name":"Generate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_a","type":"address"}],"name":"GetMarketRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"Isolate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"Isomerize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"Soul","type":"uint64"},{"internalType":"uint64","name":"Aura","type":"uint64"},{"internalType":"string","name":"LogLine","type":"string"}],"name":"Log","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"Magnetize","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"Manifold","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"Monopole","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MotzkinPrime","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_t","type":"address"},{"internalType":"uint256","name":"_a","type":"uint256"}],"name":"Purchase","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"Pi","type":"uint64"}],"name":"React","outputs":[{"internalType":"uint64","name":"","type":"uint64"},{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_t","type":"address"},{"internalType":"uint256","name":"_a","type":"uint256"}],"name":"Redeem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newName","type":"string"},{"internalType":"string","name":"newSymbol","type":"string"}],"name":"Rename","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"Rho","outputs":[{"internalType":"contract SHA","name":"Rod","type":"address"},{"internalType":"contract SHA","name":"Cone","type":"address"},{"internalType":"uint64","name":"Barn","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"Rod","outputs":[{"internalType":"contract SHA","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"Type","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"Xiao","outputs":[{"internalType":"contract atropaMath","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"addOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintToCap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"cOwner","type":"address"}],"name":"owner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"toRemove","type":"address"}],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/logs.py b/logs.py new file mode 100644 index 0000000..e5ebc46 --- /dev/null +++ b/logs.py @@ -0,0 +1,33 @@ +import logging +import os +from logging.handlers import TimedRotatingFileHandler + +from dotenv import load_dotenv + +load_dotenv() +logging.basicConfig( + format='[%(asctime)s] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + level=logging.INFO, + handlers=[ + TimedRotatingFileHandler("{}/app.log".format(os.getenv('DATA_FOLDER')), when="midnight", backupCount=7), + ] +) +loggers = {} + + +def set_channel_logger(channel_name, level=logging.INFO): + os.makedirs("{}/logs/".format(os.getenv("DATA_FOLDER")), exist_ok=True) + log_file = "{}/logs/{}.log".format(os.getenv("DATA_FOLDER"), channel_name) + handler = TimedRotatingFileHandler(log_file, when="midnight", interval=1, utc=True) + handler.setFormatter(logging.Formatter("[%(asctime)s] %(message)s", "%Y-%m-%d %H:%M:%S")) + logger = logging.getLogger(channel_name) + logger.setLevel(level) + logger.addHandler(handler) + return logger + + +def log_message(channel_name, message): + if channel_name not in loggers.keys(): + loggers[channel_name] = set_channel_logger(channel_name) + loggers[channel_name].info(message) diff --git a/main.py b/main.py new file mode 100644 index 0000000..5980e6b --- /dev/null +++ b/main.py @@ -0,0 +1,104 @@ +import asyncio +import json +import os +import warnings +from datetime import datetime, timezone +from json import JSONDecodeError + +from web3 import Web3, HTTPProvider + +from common import * +from logs import * + +web3 = Web3(HTTPProvider(os.getenv('RPC_SERVER'))) +log_contract = web3.eth.contract( + os.getenv('LOG_CONTRACT_ADDRESS'), + abi=json.load(open("./data/abi/{}".format(os.getenv('LOG_CONTRACT_ABI_FILE')))) +) + + +async def keep_chat_updated(event_filter, poll_interval): + while True: + for event in event_filter.get_new_entries(): + output_line(event) + await asyncio.sleep(poll_interval) + + +def preload_chat(earliest_block, latest_block): + event_logs = web3.eth.get_logs({ + "fromBlock": int(earliest_block), + "toBlock": int(latest_block), + "topics": ["0x6b81130c485ac9b98332fa40c2e57900867815b0fe1497e1a168caf930fc9c9d"], + "address": os.getenv('LOG_CONTRACT_ADDRESS') + }) + warnings.filterwarnings("ignore") + for log in event_logs: + output_line(log) + + +def load_chat(latest_block): + event_filter = log_contract.events.LogEvent.create_filter(fromBlock=latest_block) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + asyncio.run(keep_chat_updated(event_filter, 2)) + except KeyboardInterrupt: + pass + + +def output_line(log): + try: + tx_receipt = web3.eth.get_transaction_receipt(log.transactionHash) + decoded_data = log_contract.events.LogEvent().process_receipt(tx_receipt) + except Exception as e: + return + if decoded_data[0]['args']['Aura'] and decoded_data[0]['args']['Soul']: + # save latest block number + rooms = json.load(open(room_file := "{}/rooms.json".format(os.getenv('DATA_FOLDER')), 'r')) + rooms['VOID']['last_block'] = tx_receipt['blockNumber'] + open(room_file, 'w').write(json.dumps(rooms)) + + # get the block and save it to cache + block_number = tx_receipt['blockNumber'] + if block_number not in block_cache: + block_cache[block_number] = web3.eth.get_block(block_number) + + # log the message and output to console + log_message("VOID", decoded_data[0]['args']['LogLine']) + timestamp = datetime.fromtimestamp(block_cache[block_number]['timestamp'], tz=timezone.utc) + print("[{}] {}".format(timestamp.strftime("%Y-%m-%d %H:%M:%S"), decoded_data[0]['args']['LogLine'])) + + +if __name__ == '__main__': + # create the log and rooms folder if it doesn't exist + os.makedirs("{}/logs/".format(os.getenv('DATA_FOLDER')), exist_ok=True) + os.makedirs("{}/rooms/".format(os.getenv('DATA_FOLDER')), exist_ok=True) + + # create the rooms file if it doesn't exist + try: + room = json.load(open(room_file := "{}/rooms/{}.json".format(os.getenv('DATA_FOLDER'), os.getenv('CHANNEL_NAME')), 'r')) # TODO: change this to target an address + except (JSONDecodeError, FileNotFoundError): + room = { + "label": os.getenv('CHANNEL_NAME'), + "last_block": 0, + "preloaded": False, + } + open(room_file, 'w').write(json.dumps(room)) + log_file = "{}/logs/{}.log".format(os.getenv('DATA_FOLDER'), "VOID") + # grab the entire log if the log file doesn't exist + if not room['preloaded']: + os.remove(log_file) + latest_block = web3.eth.get_block('latest') + block_cache = {latest_block.number: latest_block} + earliest_block_number = 21220693 + preload_chat(earliest_block_number, latest_block.number) + room['preloaded'] = True + room['last_block'] = latest_block.number + else: + # print past log to console + logs = read_file_to_list(log_file) + for log in logs: + print(log) + open(room_file, 'w').write(json.dumps(room)) + # check for new messages + load_chat(room['last_block']) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7a7c7b3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +web3 +python-dotenv \ No newline at end of file diff --git a/sample.env b/sample.env new file mode 100644 index 0000000..0c35d9d --- /dev/null +++ b/sample.env @@ -0,0 +1,5 @@ +DATA_FOLDER=./data +RPC_SERVER=https://rpc.pulsechain.com +LOG_CONTRACT_ADDRESS=0x7aE73C498A308247BE73688c09c96B3fd06dDB84 +LOG_CONTRACT_ABI_FILE=SHIO.json +CHANNEL_NAME=VOID \ No newline at end of file