Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug balances, updatable metadata event, publish/retrieve last commit balances #4

Open
wants to merge 6 commits into
base: aliel/add-voucher-virtual-balance
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 31 additions & 21 deletions src/aleph_nodestatus/erc20.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,28 @@
from aleph_client.asynchronous import create_post
from web3._utils.events import construct_event_topic_set
from web3.contract import get_event_data
from web3.gas_strategies.rpc import rpc_gas_price_strategy
from web3.middleware import geth_poa_middleware, local_filter_middleware

from .erc20_utils import get_contract, get_token_contract_abi
from .ethereum import get_logs, get_web3
from .settings import settings
from .voucher import getVoucherNFTBalances
from .voucher import VoucherSettings, getVoucherNFTUpdates

# from web3.gas_strategies.rpc import rpc_gas_price_strategy
# from web3.middleware import geth_poa_middleware, local_filter_middleware


LOGGER = logging.getLogger(__name__)

DECIMALS = 10**settings.ethereum_decimals


async def process_contract_history(
contract_address, start_height, platform="ETH", balances=None, last_seen=None
contract_address,
start_height,
platform="ETH",
balances=None,
last_seen=None,
process_vouchers=False,
):
web3 = get_web3()
abi = get_token_contract_abi("ALEPHERC20")
Expand All @@ -31,10 +38,7 @@ async def process_contract_history(
settings.ethereum_deployer: settings.ethereum_total_supply * DECIMALS
}
last_height = start_height
end_height = web3.eth.block_number

changed_addresses = set()

to_append = list()

if settings.chain_name != "AVAX":
Expand Down Expand Up @@ -66,22 +70,22 @@ async def process_contract_history(
last_height = height

if settings.chain_name == "AVAX":
philogicae marked this conversation as resolved.
Show resolved Hide resolved
last_height = start_height
async for claimer, balance, block_number in getVoucherNFTBalances(last_height):
LOGGER.info(f"found NFT Voucher balance for {claimer} : {balance}")
if claimer in changed_addresses:
LOGGER.info(f"Add existing balance for {claimer} : {balances[claimer]}")
balance = balance + balances[claimer]

balances[claimer] = balance
changed_addresses.add(claimer)
last_height = block_number
voucher_settings = VoucherSettings()
if process_vouchers and voucher_settings.active:
try:
async for claimer, virtual_balance_change in getVoucherNFTUpdates():
if virtual_balance_change != 0:
balances[claimer] = virtual_balance_change + (balances[claimer] if claimer in balances else 0)
changed_addresses.add(claimer)
except Exception as e:
voucher_settings.active = False
LOGGER.error(f"Stopped processing vouchers, error: {e}")

if len(changed_addresses):
yield (last_height, (balances, platform, changed_addresses))


async def update_balances(account, height, balances, changed_addresses = None):
async def update_balances(account, height, balances, changed_addresses=None):
if changed_addresses is None:
changed_addresses = list(balances.keys())

Expand Down Expand Up @@ -110,21 +114,24 @@ async def update_balances(account, height, balances, changed_addresses = None):
async def erc20_monitoring_process():
from .messages import get_aleph_account

process_vouchers = settings.enable_process_vouchers
LOGGER.info(f"Process vouchers: {process_vouchers}")
last_seen_txs = deque([], maxlen=100)
account = get_aleph_account()
LOGGER.info("processing history")
LOGGER.info("Processing history")
items = process_contract_history(
settings.ethereum_token_contract,
settings.ethereum_min_height,
last_seen=last_seen_txs,
process_vouchers=process_vouchers,
)
balances = {}
last_height = settings.ethereum_min_height
height = None
async for height, (balances, platform, changed_items) in items:
last_height = height
balances = balances
LOGGER.info("pushing current state")
LOGGER.info("Pushing current state")

if height:
await update_balances(account, height, balances)
Expand All @@ -140,12 +147,15 @@ async def erc20_monitoring_process():
last_height + 1,
balances=balances,
last_seen=last_seen_txs,
process_vouchers=process_vouchers,
):
pass

if changed_items:
LOGGER.info("New data available for addresses %s, posting" % changed_items)
await update_balances(account, height, balances, changed_addresses=changed_items)
await update_balances(
account, height, balances, changed_addresses=changed_items
)
last_height = height

await asyncio.sleep(5)
2 changes: 1 addition & 1 deletion src/aleph_nodestatus/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
from aleph_client.chains.ethereum import ETHAccount
from hexbytes import HexBytes

from .erc20 import DECIMALS
from .ethereum import get_web3
from .settings import settings

DECIMALS = 10**settings.ethereum_decimals

@lru_cache(maxsize=2)
def get_aleph_account():
Expand Down
11 changes: 8 additions & 3 deletions src/aleph_nodestatus/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Settings(BaseSettings):

node_post_type: str = "corechan-operation"
balances_post_type: str = "balances-update"
vouchers_post_type: str = "vouchers-update"
# staker_post_type: str = "corechan-delegation"
filter_tag: str = "mainnet"

Expand All @@ -77,9 +78,13 @@ class Settings(BaseSettings):

crn_inactivity_threshold_days: int = 90

# Twentysix voucher settings (sepolia)
voucher_ethereum_min_height: int = 6252157
voucher_contract_address: str = "0x3e00d39C2da56f516a2B93d1EA99B9648467A308"
# Twentysix voucher settings
enable_process_vouchers: bool = True
voucher_chain_name: str = "AVAX"
voucher_chain_id: int = 43114
voucher_avax_min_height: int = 48434223 # contract deployment
voucher_contract_address: str = "0xe10E363B704540c963b8606d27D1DDF00c96F979"
voucher_token_symbol: str = "TRY26"
voucher_abi_name: str = "VoucherNFT"
voucher_api_server: str = "https://claim.twentysix.cloud"
avax_api_server: str = None
Expand Down
202 changes: 173 additions & 29 deletions src/aleph_nodestatus/voucher.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,192 @@
from functools import lru_cache
import logging
import time

import requests
from aleph_client.asynchronous import create_post, get_posts

from .erc20_utils import get_contract, get_token_contract_abi
from .ethereum import get_web3
from .messages import get_aleph_account
from .settings import settings

DECIMALS = 10**18
LOGGER = logging.getLogger(__name__)

@lru_cache(maxsize=30)
def get_metadata(metadata_id):
DECIMALS = 10**settings.ethereum_decimals

class VoucherSettings:
_instance = None
account = None
balances = {}
vouchers = {}
metadata = {}

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

def __init__(self):
if not hasattr(self, "initialized"):
self.initialized = True
self.account = get_aleph_account()
self.web3 = get_web3(settings.avax_api_server)
self.abi = get_token_contract_abi(settings.voucher_abi_name)
self.contract = get_contract(
settings.voucher_contract_address, self.web3, self.abi
)
self._active = True
self._next_height = settings.voucher_avax_min_height

@property
def active(self):
return self._active

@active.setter
def active(self, value):
self._active = value

@property
def next_height(self):
return self._next_height

@next_height.setter
def next_height(self, value):
self._next_height = value

async def getVoucherNFTUpdates():
voucher_settings = VoucherSettings()
now = time.time()
start_height = voucher_settings.next_height
current_height = voucher_settings.web3.eth.block_number

if start_height == settings.voucher_avax_min_height:
last_commit = await fetch_last_commit_nft_balances(voucher_settings)
if last_commit:
voucher_settings.balances = last_commit['virtual_balances']
voucher_settings.vouchers = last_commit['nft_vouchers']
voucher_settings.metadata = last_commit['metadata']
start_height = last_commit['chain_height'] + 1
for claimer, balance in voucher_settings.balances.items():
yield (claimer, balance * DECIMALS)
if start_height > current_height:
return

metadata_events = voucher_settings.contract.events.BatchMetadataUpdate().getLogs(
fromBlock=start_height
)
if metadata_events:
if start_height > settings.voucher_avax_min_height:
refetch_all_metadata(voucher_settings)
for claimer, balance in recompute_all_balances(voucher_settings, now):
yield (claimer, balance * DECIMALS)

mint_events = voucher_settings.contract.events.Mint().getLogs(
fromBlock=start_height
)
for mint in mint_events:
token_id = str(mint["args"]["tokenId"])
claimer = mint["args"]["claimer"]
metadata_id = str(mint["args"]["metadataId"])
voucher_settings.vouchers[token_id] = dict(
claimer=claimer, metadata_id=metadata_id
)
metadata = check_metadata(metadata_id, voucher_settings)
virtual_balance_change = check_balance(claimer, metadata, voucher_settings.balances, now)
LOGGER.info(
f"New NFT id={token_id} for {claimer}: +{virtual_balance_change}"
)
if virtual_balance_change > 0:
yield (claimer, virtual_balance_change * DECIMALS)

voucher_settings.next_height = current_height + 1
if len(mint_events) + len(metadata_events) > 0:
await update_nft_balances(voucher_settings)

def fetch_metadata(metadata_id):
url = f"{settings.voucher_api_server}/sbt/metadata/{metadata_id}.json"
try:
response = requests.get(url)
return response.json()
except requests.exceptions.JSONDecodeError:
return None
except Exception as e:
LOGGER.error(f"Error when retrieving metadata: {e}")

def check_metadata(metadata_id, voucher_settings):
if metadata_id not in voucher_settings.metadata:
voucher_settings.metadata[metadata_id] = fetch_metadata(metadata_id)
return voucher_settings.metadata[metadata_id]

async def getVoucherNFTBalances(start_height=None):
# Connect to the network
web3 = get_web3(settings.avax_api_server)
def check_balance(addr, metadata, balances, now):
balance, start, end = 0, 0, 0
for k in metadata["attributes"]:
if k["trait_type"] == "$ALEPH Virtual Balance":
balance = k["value"]
elif k["trait_type"] == "Start":
start = k["value"]
elif k["trait_type"] == "End":
end = k["value"]
if start < now and now < end and balance > 0:
if addr not in balances:
balances[addr] = balance
else:
balances[addr] += balance
return balance
return 0

# Get the contract
abi = get_token_contract_abi(settings.voucher_abi_name)
contract = get_contract(settings.voucher_contract_address, web3, abi)
def refetch_all_metadata(voucher_settings):
LOGGER.info(f"Metadata Update: Refetch all metadata")
for metadata_id in voucher_settings.metadata:
voucher_settings.metadata[metadata_id] = fetch_metadata(metadata_id)

if start_height is None:
start_height = settings.voucher_ethereum_min_height
def recompute_all_balances(voucher_settings, now):
LOGGER.info(f"Metadata Update: Recompute all balances")
balances, diffs = {}, []
for voucher in voucher_settings.vouchers.values():
claimer, metadata_id = voucher["claimer"], voucher["metadata_id"]
metadata = voucher_settings.metadata[metadata_id]
check_balance(claimer, metadata, balances, now)
for claimer, old_balance in voucher_settings.balances.items():
new_balance = balances[claimer]
if old_balance != new_balance:
diffs.append((claimer, new_balance - old_balance))
voucher_settings.balances = balances
return diffs

# Get the events
mint_events = contract.events.Mint().getLogs(
fromBlock=start_height
async def fetch_last_commit_nft_balances(voucher_settings):
LOGGER.info(f"Process vouchers: Fetch last commit NFT balances")
chain_height, last_commit = 0, None
posts = await get_posts(
types=[settings.vouchers_post_type],
addresses=[voucher_settings.account.get_address()],
api_server=settings.aleph_api_server,
)
if posts and 'posts' in posts and len(posts['posts']) > 0:
for post in posts['posts']:
try:
current = post['content']
if current['token_contract'] == settings.voucher_contract_address and current['chain_height'] > chain_height:
last_commit, chain_height = current, current['chain_height']
except:
pass
LOGGER.info(f"Process vouchers: commit found for last_height={chain_height}")
return last_commit
LOGGER.info(f"Process vouchers: no commit found")

for mint in mint_events:
claimer = mint["args"]["claimer"]
#token_id = mint["args"]["tokenId"]
metadata_id = mint["args"]["metadataId"]
metadata = get_metadata(metadata_id)
balance = 0
if metadata:
for k in metadata["attributes"]:
if "$ALEPH" in k["trait_type"]:
balance = k["value"] * DECIMALS

yield (claimer, balance, mint["blockNumber"])
async def update_nft_balances(voucher_settings):
return await create_post(
voucher_settings.account,
{
"tags": ["ERC721", settings.voucher_contract_address, "voucher"],
"chain": settings.voucher_chain_name,
"chain_id": settings.voucher_chain_id,
"chain_height": voucher_settings.next_height - 1,
"platform": f"{settings.voucher_token_symbol}_{settings.voucher_chain_name}",
"token_symbol": settings.voucher_token_symbol,
"token_contract": settings.voucher_contract_address,
"virtual_balances": voucher_settings.balances,
"nft_vouchers": voucher_settings.vouchers,
"metadata": voucher_settings.metadata,
},
settings.vouchers_post_type,
channel=settings.aleph_channel,
api_server=settings.aleph_api_server,
)